Skip to content

Commit 088d4ed

Browse files
committed
Prevent merging of circular-shaped roads
1 parent bf03dcd commit 088d4ed

File tree

5 files changed

+102
-3
lines changed

5 files changed

+102
-3
lines changed

features/guidance/merge-segregated-roads.feature

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -560,5 +560,5 @@ Feature: Merge Segregated Roads
560560
| jd | Hubertusallee | yes |
561561

562562
When I route I should get
563-
| waypoints | route | turns |
564-
| i,h | Kurfürstendamm,Rathenauplatz,Hubertusallee,Hubertusallee | depart,turn slight left,turn slight right,arrive |
563+
| waypoints | route | turns |
564+
| i,h | Kurfürstendamm,Hubertusallee,Hubertusallee | depart,turn straight,arrive |

include/util/coordinate_calculation.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ bool areParallel(const iterator_type lhs_begin,
378378
return std::abs(slope_rhs) < 0.20; // twenty percent incline at the most
379379
}
380380

381+
double computeArea(const std::vector<Coordinate> &polygon);
382+
381383
} // ns coordinate_calculation
382384
} // ns util
383385
} // ns osrm

src/extractor/guidance/mergable_road_detector.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ bool MergableRoadDetector::HaveSameDirection(const NodeID intersection_node,
297297

298298
// many roads only do short parallel segments. To get a good impression of how `parallel` two
299299
// roads are, we look 100 meters down the road (wich can be quite short for very broad roads).
300-
const double constexpr distance_to_extract = 100;
300+
const double constexpr distance_to_extract = 150;
301301

302302
std::tie(distance_traversed_to_the_left, coordinates_to_the_left) =
303303
getCoordinatesAlongWay(lhs.eid, distance_to_extract);
@@ -317,6 +317,36 @@ bool MergableRoadDetector::HaveSameDirection(const NodeID intersection_node,
317317

318318
const auto connect_again = (coordinates_to_the_left.back() == coordinates_to_the_right.back());
319319

320+
// Tuning parameter to detect and don't merge roads close to circular shapes
321+
// if the area to squared circumference ratio is between the lower bound and 1/(4π)
322+
// that correspond to isoperimetric inequality 4πA ≤ L² or lower bound ≤ A/L² ≤ 1/(4π).
323+
// The lower bound must be larger enough to allow merging of square-shaped intersections
324+
// with A/L² = 1/16 or 78.6%
325+
// The condition suppresses roads merging for intersections like
326+
// . .
327+
// . .
328+
// ---- ----
329+
// . .
330+
// . .
331+
// but will allow roads merging for intersections like
332+
// -------
333+
// / \ 
334+
// ---- ----
335+
// \ /
336+
// -------
337+
const auto constexpr CIRCULAR_POLYGON_ISOPERIMETRIC_LOWER_BOUND = 0.85 / (4 * M_PI);
338+
if (connect_again && coordinates_to_the_left.front() == coordinates_to_the_left.back())
339+
{ // if the left and right roads connect again and are closed polygons ...
340+
const auto area = util::coordinate_calculation::computeArea(coordinates_to_the_left);
341+
const auto perimeter = distance_traversed_to_the_left;
342+
const auto area_to_squared_perimeter_ratio = std::abs(area) / (perimeter * perimeter);
343+
344+
// then don't merge roads if A/L² is greater than the lower bound
345+
BOOST_ASSERT(area_to_squared_perimeter_ratio <= 1. / (4 * M_PI));
346+
if (area_to_squared_perimeter_ratio >= CIRCULAR_POLYGON_ISOPERIMETRIC_LOWER_BOUND)
347+
return false;
348+
}
349+
320350
// sampling to correctly weight longer segments in regression calculations
321351
const auto constexpr SAMPLE_INTERVAL = 5;
322352
coordinates_to_the_left = coordinate_extractor.SampleCoordinates(

src/util/coordinate_calculation.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,48 @@ Coordinate difference(const Coordinate lhs, const Coordinate rhs)
381381
return {util::FixedLongitude{lon_diff_int}, util::FixedLatitude{lat_diff_int}};
382382
}
383383

384+
double computeArea(const std::vector<Coordinate> &polygon)
385+
{
386+
using util::coordinate_calculation::haversineDistance;
387+
388+
if (polygon.empty())
389+
return 0.;
390+
391+
BOOST_ASSERT(polygon.front() == polygon.back());
392+
393+
// Take the reference point with the smallest latitude.
394+
// ⚠ ref_latitude is the standard parallel for the equirectangular projection
395+
// that is not an area-preserving projection
396+
const auto ref_point =
397+
std::min_element(polygon.begin(), polygon.end(), [](const auto &lhs, const auto &rhs) {
398+
return lhs.lat < rhs.lat;
399+
});
400+
const auto ref_latitude = ref_point->lat;
401+
402+
// Compute area of under a curve and a line that is parallel the equator with ref_latitude
403+
// For closed curves it corresponds to the shoelace algorithm for polygon areas
404+
double area = 0.;
405+
auto first = polygon.begin();
406+
auto previous_base = util::Coordinate{first->lon, ref_latitude};
407+
auto previous_y = haversineDistance(previous_base, *first);
408+
for (++first; first != polygon.end(); ++first)
409+
{
410+
BOOST_ASSERT(first->lat >= ref_latitude);
411+
412+
const auto current_base = util::Coordinate{first->lon, ref_latitude};
413+
const auto current_y = haversineDistance(current_base, *first);
414+
const auto chunk_area =
415+
haversineDistance(previous_base, current_base) * (previous_y + current_y);
416+
417+
area += (current_base.lon >= previous_base.lon) ? chunk_area : -chunk_area;
418+
419+
previous_base = current_base;
420+
previous_y = current_y;
421+
}
422+
423+
return area / 2.;
424+
}
425+
384426
} // ns coordinate_calculation
385427
} // ns util
386428
} // ns osrm

unit_tests/util/coordinate_calculation.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,29 @@ BOOST_AUTO_TEST_CASE(regression_test_3516)
408408
BOOST_CHECK_EQUAL(nearest_location, v);
409409
}
410410

411+
BOOST_AUTO_TEST_CASE(computeArea)
412+
{
413+
using osrm::util::coordinate_calculation::computeArea;
414+
415+
//
416+
auto rhombus = std::vector<Coordinate>{{FloatLongitude{.00}, FloatLatitude{.00}},
417+
{FloatLongitude{.01}, FloatLatitude{.01}},
418+
{FloatLongitude{.02}, FloatLatitude{.00}},
419+
{FloatLongitude{.01}, FloatLatitude{-.01}},
420+
{FloatLongitude{.00}, FloatLatitude{.00}}};
421+
422+
BOOST_CHECK_CLOSE(2 * 1112.263 * 1112.263, computeArea(rhombus), 1e-3);
423+
424+
// edge cases
425+
auto self_intersection = std::vector<Coordinate>{{FloatLongitude{.00}, FloatLatitude{.00}},
426+
{FloatLongitude{.00}, FloatLatitude{.02}},
427+
{FloatLongitude{.01}, FloatLatitude{.01}},
428+
{FloatLongitude{.02}, FloatLatitude{.00}},
429+
{FloatLongitude{.02}, FloatLatitude{.02}},
430+
{FloatLongitude{.01}, FloatLatitude{.01}},
431+
{FloatLongitude{.00}, FloatLatitude{.00}}};
432+
BOOST_CHECK(computeArea(self_intersection) < 1e-3);
433+
BOOST_CHECK_CLOSE(0, computeArea({}), 1e-3);
434+
}
435+
411436
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)