From 0e08b39b0782cc07fa8a5f58241ad7baf132568a Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:13:42 +0000 Subject: [PATCH 01/10] Initial plan From 1e2d0e518f07ff79ac36326f139fbbfa42c3e302 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:38:55 +0000 Subject: [PATCH 02/10] Implement turning circle support for u-turns - Add has_turning_facility field to ExtractionTurn structure - Detect turning facilities (turning_circle, turning_loop, mini_roundabout) at intersections - Expose has_turning_facility to Lua profiles via scripting environment - Modify car.lua to eliminate u-turn penalty at turning facilities - Add comprehensive test scenarios for turning circle u-turn behavior Co-authored-by: DennisOSRM <1067895+DennisOSRM@users.noreply.github.com> --- features/car/turning_circle_uturn.feature | 150 ++++++++++++++++++++ include/extractor/extraction_turn.hpp | 9 +- profiles/car.lua | 5 +- src/extractor/edge_based_graph_factory.cpp | 3 + src/extractor/graph_compressor.cpp | 4 +- src/extractor/scripting_environment_lua.cpp | 4 + 6 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 features/car/turning_circle_uturn.feature diff --git a/features/car/turning_circle_uturn.feature b/features/car/turning_circle_uturn.feature new file mode 100644 index 00000000000..70ff884d302 --- /dev/null +++ b/features/car/turning_circle_uturn.feature @@ -0,0 +1,150 @@ +@routing @car @turning_circle @uturn +Feature: Car - Use turning circles for u-turns + + Background: + Given the profile "car" + + Scenario: Car - Should prefer turning_circle for u-turn over direct u-turn + Given the node map + """ + a---b---c + | + d + """ + + And the ways + | nodes | highway | + | abc | primary | + | bd | primary | + + And the nodes + | node | highway | + | d | turning_circle | + + When I route I should get + | waypoints | route | turns | + | a,a | abc,bd,abc | depart,turn right,turn right,arrive | + + Scenario: Car - U-turn at turning_loop should have no penalty + Given the node map + """ + a---b---c---d + | + e + """ + + And the ways + | nodes | highway | + | abcd | primary | + | be | primary | + + And the nodes + | node | highway | + | e | turning_loop | + + When I route I should get + | waypoints | route | turns | + | a,a | abcd,be,abcd | depart,turn right,turn right,arrive | + + Scenario: Car - U-turn at mini_roundabout should work + Given the node map + """ + a---b---c---d + | + e + """ + + And the ways + | nodes | highway | + | abcd | primary | + | be | primary | + + And the nodes + | node | highway | + | e | mini_roundabout | + + When I route I should get + | waypoints | route | turns | + | a,a | abcd,be,abcd | depart,turn right,turn right,arrive | + + Scenario: Car - Multiple turning facilities, use closest + Given the node map + """ + a---b---c---d---e + | | + f g + """ + + And the ways + | nodes | highway | + | abcde | primary | + | bf | primary | + | dg | primary | + + And the nodes + | node | highway | + | f | turning_circle | + | g | turning_loop | + + When I route I should get + | waypoints | route | turns | + | a,a | abcde,bf,abcde | depart,turn right,turn right,arrive | + + Scenario: Car - Dead end with turning_circle + Given the node map + """ + a---b---c + | + d + """ + + And the ways + | nodes | highway | + | abc | primary | + | cd | primary | + + And the nodes + | node | highway | + | d | turning_circle | + + When I route I should get + | waypoints | route | turns | + | a,d | abc,cd | depart,turn left,arrive | + | d,a | cd,abc | depart,turn right,arrive | + + Scenario: Car - Regular u-turn without turning facility still penalized + Given the node map + """ + a---b---c---d + """ + + And the ways + | nodes | highway | + | abcd | primary | + + # Note: No turning facility nodes defined + + When I route I should get + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abcd,abcd | depart,continue uturn,arrive | + + Scenario: Car - Turning circle on one-way should respect direction + Given the node map + """ + a---b---c + | + d + """ + + And the ways + | nodes | highway | oneway | + | abc | primary | no | + | bd | primary | yes | + + And the nodes + | node | highway | + | d | turning_circle | + + When I route I should get + | waypoints | route | + | a,a | abc,bd,abc | diff --git a/include/extractor/extraction_turn.hpp b/include/extractor/extraction_turn.hpp index 8a71c7a2f60..22d5ea95216 100644 --- a/include/extractor/extraction_turn.hpp +++ b/include/extractor/extraction_turn.hpp @@ -82,6 +82,7 @@ struct ExtractionTurn int number_of_roads, bool is_u_turn, bool has_traffic_light, + bool has_turning_facility, bool is_left_hand_driving, bool source_restricted, @@ -112,7 +113,8 @@ struct ExtractionTurn const NodeID via, const NodeID to) : angle(180. - angle), number_of_roads(number_of_roads), is_u_turn(is_u_turn), - has_traffic_light(has_traffic_light), is_left_hand_driving(is_left_hand_driving), + has_traffic_light(has_traffic_light), has_turning_facility(has_turning_facility), + is_left_hand_driving(is_left_hand_driving), source_restricted(source_restricted), source_mode(source_mode), source_is_motorway(source_is_motorway), source_is_link(source_is_link), @@ -146,11 +148,13 @@ struct ExtractionTurn const ExtractionTurnLeg::EdgeData &target_edge, const std::vector &roads_on_the_right, const std::vector &roads_on_the_left, - const bool has_traffic_light) + const bool has_traffic_light, + const bool has_turning_facility) : ExtractionTurn{0, 2, false, has_traffic_light, + has_turning_facility, false, // source false, @@ -187,6 +191,7 @@ struct ExtractionTurn const int number_of_roads; const bool is_u_turn; const bool has_traffic_light; + const bool has_turning_facility; const bool is_left_hand_driving; // source info diff --git a/profiles/car.lua b/profiles/car.lua index 6a91e003a0b..f912d680b46 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -533,7 +533,10 @@ function process_turn(profile, turn) end if turn.is_u_turn then - turn.duration = turn.duration + profile.properties.u_turn_penalty + -- No penalty for u-turns at designated turning facilities (turning_circle, turning_loop, mini_roundabout) + if not turn.has_turning_facility then + turn.duration = turn.duration + profile.properties.u_turn_penalty + end end end diff --git a/src/extractor/edge_based_graph_factory.cpp b/src/extractor/edge_based_graph_factory.cpp index bc161308b06..d5047fa313e 100644 --- a/src/extractor/edge_based_graph_factory.cpp +++ b/src/extractor/edge_based_graph_factory.cpp @@ -621,6 +621,8 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( // node. But we'll check anyway. const bool is_traffic_light = scripting_environment.m_obstacle_map.any( node_along_road_entering, intersection_node, Obstacle::Type::TrafficSignals); + const bool has_turning_facility = scripting_environment.m_obstacle_map.any( + intersection_node, Obstacle::Type::Turning); const bool is_uturn = guidance::getTurnDirection(turn_angle) == guidance::DirectionModifier::UTurn; @@ -631,6 +633,7 @@ void EdgeBasedGraphFactory::GenerateEdgeExpandedEdges( is_uturn), is_uturn, is_traffic_light, + has_turning_facility, m_edge_based_node_container.GetAnnotation(edge_data1.annotation_data) .is_left_hand_driving, // source info diff --git a/src/extractor/graph_compressor.cpp b/src/extractor/graph_compressor.cpp index ab6c7d8621e..1498b534477 100644 --- a/src/extractor/graph_compressor.cpp +++ b/src/extractor/graph_compressor.cpp @@ -273,7 +273,9 @@ void GraphCompressor::Compress(ScriptingEnvironment &scripting_environment, no_other_roads, no_other_roads, scripting_environment.m_obstacle_map.any( - from, via, Obstacle::Type::TrafficSignals)}; + from, via, Obstacle::Type::TrafficSignals), + scripting_environment.m_obstacle_map.any( + via, Obstacle::Type::Turning)}; scripting_environment.ProcessTurn(fake_turn); penalties.duration += to_alias(fake_turn.duration * SECOND_TO_DECISECOND); diff --git a/src/extractor/scripting_environment_lua.cpp b/src/extractor/scripting_environment_lua.cpp index 861236e4741..584be85273a 100644 --- a/src/extractor/scripting_environment_lua.cpp +++ b/src/extractor/scripting_environment_lua.cpp @@ -765,6 +765,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) }), "has_traffic_light", &ExtractionTurn::has_traffic_light, + "has_turning_facility", + &ExtractionTurn::has_turning_facility, "weight", &ExtractionTurn::weight, "duration", @@ -890,6 +892,8 @@ void Sol2ScriptingEnvironment::InitContext(LuaScriptingContext &context) &ExtractionTurn::is_u_turn, "has_traffic_light", &ExtractionTurn::has_traffic_light, + "has_turning_facility", + &ExtractionTurn::has_turning_facility, "is_left_hand_driving", &ExtractionTurn::is_left_hand_driving, From 080cf39a81a98c085c476658acf0fc68238b8aa1 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:42:53 +0000 Subject: [PATCH 03/10] Fix formatting and update documentation - Apply clang-format to graph_compressor.cpp - Add turning circle feature to CHANGELOG.md - Document has_turning_facility field in profiles.md Co-authored-by: DennisOSRM <1067895+DennisOSRM@users.noreply.github.com> --- CHANGELOG.md | 1 + docs/profiles.md | 1 + src/extractor/graph_compressor.cpp | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd79b14d084..fa6274a5468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - Changes from 6.0.0 - Routing: + - ADDED: Use turning circles, turning loops, and mini roundabouts for u-turns with zero penalty [#620](https://github.com/Project-OSRM/osrm-backend/issues/620) - FIXED: Crash when route starts or ends at `type=manoeuvre` relation via node [#7287](https://github.com/Project-OSRM/osrm-backend/issues/7287) - Extraction: - ADDED: Emit warning when ways reference nodes not present in input data [#1596](https://github.com/Project-OSRM/osrm-backend/issues/1596) diff --git a/docs/profiles.md b/docs/profiles.md index 48e8aebd986..16381f08359 100644 --- a/docs/profiles.md +++ b/docs/profiles.md @@ -416,6 +416,7 @@ angle | Read | Float | number_of_roads | Read | Integer | Number of ways at the intersection of the turn is_u_turn | Read | Boolean | Is the turn a u-turn? has_traffic_light | Read | Boolean | Is a traffic light present at this turn? +has_turning_facility | Read | Boolean | Is a turning facility (turning_circle, turning_loop, or mini_roundabout) present at this intersection? is_left_hand_driving | Read | Boolean | Is left-hand traffic? source_restricted | Read | Boolean | Is it from a restricted access road? (See definition in `process_way`) source_mode | Read | Enum | Travel mode before the turn. Defined in `include/extractor/travel_mode.hpp` diff --git a/src/extractor/graph_compressor.cpp b/src/extractor/graph_compressor.cpp index 1498b534477..7e98e251df7 100644 --- a/src/extractor/graph_compressor.cpp +++ b/src/extractor/graph_compressor.cpp @@ -265,17 +265,17 @@ void GraphCompressor::Compress(ScriptingEnvironment &scripting_environment, EdgePenalties &penalties) { // generate an artificial turn for the turn penalty generation - ExtractionTurn fake_turn{from, - via, - to, - from_edge, - to_edge, - no_other_roads, - no_other_roads, - scripting_environment.m_obstacle_map.any( - from, via, Obstacle::Type::TrafficSignals), - scripting_environment.m_obstacle_map.any( - via, Obstacle::Type::Turning)}; + ExtractionTurn fake_turn{ + from, + via, + to, + from_edge, + to_edge, + no_other_roads, + no_other_roads, + scripting_environment.m_obstacle_map.any( + from, via, Obstacle::Type::TrafficSignals), + scripting_environment.m_obstacle_map.any(via, Obstacle::Type::Turning)}; scripting_environment.ProcessTurn(fake_turn); penalties.duration += to_alias(fake_turn.duration * SECOND_TO_DECISECOND); From 8cdded5db422f9f0dd7635c4c3eab3dfe2f43197 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 06:42:18 +0100 Subject: [PATCH 04/10] Fix missing `ObstacleMap::any(NodeID, Obstacle::Type)` overload (#7391) --- include/extractor/obstacles.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/extractor/obstacles.hpp b/include/extractor/obstacles.hpp index 3eaa400395e..8e5cb1ba72f 100644 --- a/include/extractor/obstacles.hpp +++ b/include/extractor/obstacles.hpp @@ -174,6 +174,13 @@ class ObstacleMap // inexpensive general test bool any(NodeID to) const { return obstacles.contains(to); } + // is there any obstacle of type 'type' at node 'to' (from any direction)? + // 'type' can be a bitwise-or combination of Obstacle::Type + bool any(NodeID to, Obstacle::Type type) const + { + return any(to) && !get(SPECIAL_NODEID, to, type).empty(); + } + // is there any obstacle of type 'type' at node 'to' when coming from node 'from'? // pass SPECIAL_NODEID as 'from' to query all obstacles at 'to' // 'type' can be a bitwise-or combination of Obstacle::Type From 7e29f3724b38771fd3c4b9cb6e58f4993930919d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:57:11 +0100 Subject: [PATCH 05/10] Fix incorrect test expectations in turning_circle_uturn feature (#7392) --- features/car/turning_circle_uturn.feature | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/features/car/turning_circle_uturn.feature b/features/car/turning_circle_uturn.feature index 70ff884d302..bcb6b0c82de 100644 --- a/features/car/turning_circle_uturn.feature +++ b/features/car/turning_circle_uturn.feature @@ -108,9 +108,9 @@ Feature: Car - Use turning circles for u-turns | d | turning_circle | When I route I should get - | waypoints | route | turns | - | a,d | abc,cd | depart,turn left,arrive | - | d,a | cd,abc | depart,turn right,arrive | + | waypoints | route | turns | + | a,d | abc,cd,cd | depart,new name right,arrive | + | d,a | cd,abc,abc | depart,new name left,arrive | Scenario: Car - Regular u-turn without turning facility still penalized Given the node map @@ -125,8 +125,8 @@ Feature: Car - Use turning circles for u-turns # Note: No turning facility nodes defined When I route I should get - | waypoints | bearings | route | turns | - | a,a | 90,10 270,10 | abcd,abcd | depart,continue uturn,arrive | + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abcd,abcd,abcd | depart,continue uturn,arrive | Scenario: Car - Turning circle on one-way should respect direction Given the node map From dff8728ae00746a28219fb749779b7e6872544cf Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:09:17 +0100 Subject: [PATCH 06/10] Initial plan (#7393) From d29ad56b00be741bc51ac272932c72c1820cb170 Mon Sep 17 00:00:00 2001 From: Claude <242468646+Claude@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:47:57 +0100 Subject: [PATCH 07/10] Fix: Turning circle u-turn test failures (#7394) * Initial plan * Fix turning facility detection by adding explicit highway-to-obstacle-type mapping The Lua profile was attempting to use obstacle_type[highway] as a table lookup, but Sol2's new_enum() doesn't automatically create string-indexed access. Added an explicit mapping table (highway_to_obstacle_type) to correctly map highway tag values like "turning_circle", "turning_loop", and "mini_roundabout" to their corresponding obstacle type enums. This fixes the issue where turning facilities were not being recognized during route calculation, causing u-turns to use direct u-turn penalties instead of taking advantage of designated turning facilities. Co-authored-by: DennisOSRM <1067895+DennisOSRM@users.noreply.github.com> --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: DennisOSRM <1067895+DennisOSRM@users.noreply.github.com> --- profiles/lib/obstacles.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/profiles/lib/obstacles.lua b/profiles/lib/obstacles.lua index a2ba2ca4aa7..c0ec4f06ee4 100644 --- a/profiles/lib/obstacles.lua +++ b/profiles/lib/obstacles.lua @@ -3,12 +3,24 @@ local Obstacles = {} +-- Mapping from highway tag values to obstacle types +local highway_to_obstacle_type = { + ["traffic_signals"] = obstacle_type.traffic_signals, + ["stop"] = obstacle_type.stop, + ["give_way"] = obstacle_type.give_way, + ["crossing"] = obstacle_type.crossing, + ["traffic_calming"] = obstacle_type.traffic_calming, + ["mini_roundabout"] = obstacle_type.mini_roundabout, + ["turning_loop"] = obstacle_type.turning_loop, + ["turning_circle"] = obstacle_type.turning_circle +} + -- process the obstacles at the given node -- note: does not process barriers function Obstacles.process_node(profile, node) local highway = node:get_value_by_key("highway") if highway then - local type = obstacle_type[highway] + local type = highway_to_obstacle_type[highway] -- barriers already handled in car.lua if type and type ~= obstacle_type.barrier then local direction = node:get_value_by_key("direction") From 9dd116ad1efda43274bae17da47e44e51b8d706f Mon Sep 17 00:00:00 2001 From: Dennis Luxen Date: Tue, 3 Mar 2026 21:04:48 +0100 Subject: [PATCH 08/10] Fix turning circle u-turn test scenarios - Add bearings (90,10 270,10) to all directional u-turn scenarios so OSRM is forced to make a real turn rather than returning a trivial zero-distance route for same start/end waypoints - Add no_u_turn turn restrictions to scenarios 1-3, modeling the real-world case where turning circles exist precisely because direct u-turns on the road are restricted - Fix expected routes: side-street turning facilities produce abc,bd,bd,abc,abc (not abc,bd,abc) since the outbound and inbound traversals of the spur road are reported separately - Fix expected turns: continue uturn + turn left (not turn right twice) - Fix one-way scenario: with bd being one-way the turning circle at d is inaccessible for u-turns, so OSRM correctly falls back to a regular u-turn on the main road (abc,abc,abc, continue uturn) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- features/car/turning_circle_uturn.feature | 38 +++++++++++++++-------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/features/car/turning_circle_uturn.feature b/features/car/turning_circle_uturn.feature index bcb6b0c82de..6963e110ca1 100644 --- a/features/car/turning_circle_uturn.feature +++ b/features/car/turning_circle_uturn.feature @@ -4,7 +4,7 @@ Feature: Car - Use turning circles for u-turns Background: Given the profile "car" - Scenario: Car - Should prefer turning_circle for u-turn over direct u-turn + Scenario: Car - Should use turning_circle for u-turn when direct u-turn is restricted Given the node map """ a---b---c @@ -21,11 +21,15 @@ Feature: Car - Use turning circles for u-turns | node | highway | | d | turning_circle | + And the relations + | type | way:from | node:via | way:to | restriction | + | restriction | abc | b | abc | no_u_turn | + When I route I should get - | waypoints | route | turns | - | a,a | abc,bd,abc | depart,turn right,turn right,arrive | + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abc,bd,bd,abc,abc | depart,turn right,continue uturn,turn left,arrive | - Scenario: Car - U-turn at turning_loop should have no penalty + Scenario: Car - Should use turning_loop for u-turn when direct u-turn is restricted Given the node map """ a---b---c---d @@ -42,11 +46,15 @@ Feature: Car - Use turning circles for u-turns | node | highway | | e | turning_loop | + And the relations + | type | way:from | node:via | way:to | restriction | + | restriction | abcd | b | abcd | no_u_turn | + When I route I should get - | waypoints | route | turns | - | a,a | abcd,be,abcd | depart,turn right,turn right,arrive | + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abcd,be,be,abcd,abcd | depart,turn right,continue uturn,turn left,arrive | - Scenario: Car - U-turn at mini_roundabout should work + Scenario: Car - Should use mini_roundabout for u-turn when direct u-turn is restricted Given the node map """ a---b---c---d @@ -63,9 +71,13 @@ Feature: Car - Use turning circles for u-turns | node | highway | | e | mini_roundabout | + And the relations + | type | way:from | node:via | way:to | restriction | + | restriction | abcd | b | abcd | no_u_turn | + When I route I should get - | waypoints | route | turns | - | a,a | abcd,be,abcd | depart,turn right,turn right,arrive | + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abcd,be,be,abcd,abcd | depart,turn right,continue uturn,turn left,arrive | Scenario: Car - Multiple turning facilities, use closest Given the node map @@ -87,8 +99,8 @@ Feature: Car - Use turning circles for u-turns | g | turning_loop | When I route I should get - | waypoints | route | turns | - | a,a | abcde,bf,abcde | depart,turn right,turn right,arrive | + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abcde,bf,bf,abcde,abcde | depart,turn right,continue uturn,turn left,arrive | Scenario: Car - Dead end with turning_circle Given the node map @@ -146,5 +158,5 @@ Feature: Car - Use turning circles for u-turns | d | turning_circle | When I route I should get - | waypoints | route | - | a,a | abc,bd,abc | + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abc,abc,abc | depart,continue uturn,arrive | From 6198a8f7389fef3be6734969443bff74ef360d84 Mon Sep 17 00:00:00 2001 From: Dennis Luxen Date: Wed, 4 Mar 2026 20:01:03 +0100 Subject: [PATCH 09/10] Apply 5-second penalty for u-turns at turning facilities Instead of zero penalty, u-turns at turning circles, turning loops, and mini roundabouts now incur a small 5-second delay. This reflects the real-world time cost of navigating the facility while still being much cheaper than a regular u-turn penalty (20 seconds). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- profiles/car.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/profiles/car.lua b/profiles/car.lua index 0ba21787632..c5d1edc61ff 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -534,8 +534,10 @@ function process_turn(profile, turn) end if turn.is_u_turn then - -- No penalty for u-turns at designated turning facilities (turning_circle, turning_loop, mini_roundabout) - if not turn.has_turning_facility then + if turn.has_turning_facility then + -- Small penalty for u-turns at designated turning facilities (turning_circle, turning_loop, mini_roundabout) + turn.duration = turn.duration + 5 + else turn.duration = turn.duration + profile.properties.u_turn_penalty end end From 1947a1a6692c6d1e7c24430e6987197232e7e9f8 Mon Sep 17 00:00:00 2001 From: Dennis Luxen Date: Fri, 20 Mar 2026 15:26:50 +0100 Subject: [PATCH 10/10] Fix turning circle u-turn tests and implementation issues - Fix Scenario 1 topology: extend main road to a---b---c---e so the dead-end is 2 hops from the junction (matching scenarios 2/3), preventing the cheaper dead-end u-turn at c from shadowing the turning_circle at d - Add 'Prefer turning_circle over plain dead end' scenario: topology with a---b + bc (plain dead end) + bd (turning_circle), no_u_turn at b. Directly validates that the 5s penalty beats the 20s penalty (29s via turning_circle vs 44s via dead end) - Remove dead code in graph_compressor.cpp: turning facilities are always Obstacle::Type::Incompressible, so the get_obstacle_penalty lambda is never reached for them; replace the any(via, Turning) query with a documented 'false' - Make turning_circle_penalty a configurable profile property (default 5s) instead of a hardcoded literal; placed alongside turn_penalty and turn_bias in car.lua - Correct CHANGELOG entry from 'zero penalty' to 'configurable reduced penalty (default 5s)' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- features/car/turning_circle_uturn.feature | 38 ++++++++++++++++++++--- profiles/car.lua | 8 +++-- src/extractor/graph_compressor.cpp | 4 ++- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/features/car/turning_circle_uturn.feature b/features/car/turning_circle_uturn.feature index 6963e110ca1..63244e864d9 100644 --- a/features/car/turning_circle_uturn.feature +++ b/features/car/turning_circle_uturn.feature @@ -7,14 +7,14 @@ Feature: Car - Use turning circles for u-turns Scenario: Car - Should use turning_circle for u-turn when direct u-turn is restricted Given the node map """ - a---b---c + a---b---c---e | d """ And the ways | nodes | highway | - | abc | primary | + | abce | primary | | bd | primary | And the nodes @@ -23,11 +23,11 @@ Feature: Car - Use turning circles for u-turns And the relations | type | way:from | node:via | way:to | restriction | - | restriction | abc | b | abc | no_u_turn | + | restriction | abce | b | abce | no_u_turn | When I route I should get - | waypoints | bearings | route | turns | - | a,a | 90,10 270,10 | abc,bd,bd,abc,abc | depart,turn right,continue uturn,turn left,arrive | + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | abce,bd,bd,abce,abce | depart,turn right,continue uturn,turn left,arrive | Scenario: Car - Should use turning_loop for u-turn when direct u-turn is restricted Given the node map @@ -79,6 +79,34 @@ Feature: Car - Use turning circles for u-turns | waypoints | bearings | route | turns | | a,a | 90,10 270,10 | abcd,be,be,abcd,abcd | depart,turn right,continue uturn,turn left,arrive | + Scenario: Car - Prefer turning_circle over plain dead end for u-turn + Given the node map + """ + c + | + a---b + | + d + """ + + And the ways + | nodes | highway | + | ab | primary | + | bc | primary | + | bd | primary | + + And the nodes + | node | highway | + | d | turning_circle | + + And the relations + | type | way:from | node:via | way:to | restriction | + | restriction | ab | b | ab | no_u_turn | + + When I route I should get + | waypoints | bearings | route | turns | + | a,a | 90,10 270,10 | ab,bd,bd,ab,ab | depart,turn right,continue uturn,turn left,arrive | + Scenario: Car - Multiple turning facilities, use closest Given the node map """ diff --git a/profiles/car.lua b/profiles/car.lua index f8f20ed161d..19b66add346 100644 --- a/profiles/car.lua +++ b/profiles/car.lua @@ -37,6 +37,10 @@ function setup() speed_reduction = 0.8, turn_bias = 1.075, cardinal_directions = false, + -- Penalty in seconds for u-turns at designated turning facilities + -- (highway=turning_circle, turning_loop, mini_roundabout). + -- Lower than u_turn_penalty because these nodes are specifically designed for turning around. + turning_circle_penalty = 5, -- Penalty multiplier for roads with no lane markings (lane_markings=no) -- Applied to bidirectional roads to prefer roads with clear lane markings @@ -539,8 +543,8 @@ function process_turn(profile, turn) if turn.is_u_turn then if turn.has_turning_facility then - -- Small penalty for u-turns at designated turning facilities (turning_circle, turning_loop, mini_roundabout) - turn.duration = turn.duration + 5 + -- No regular u-turn penalty at designated turning facilities (turning_circle, turning_loop, mini_roundabout) + turn.duration = turn.duration + profile.turning_circle_penalty else turn.duration = turn.duration + profile.properties.u_turn_penalty end diff --git a/src/extractor/graph_compressor.cpp b/src/extractor/graph_compressor.cpp index 7e98e251df7..cc10fba2697 100644 --- a/src/extractor/graph_compressor.cpp +++ b/src/extractor/graph_compressor.cpp @@ -275,7 +275,9 @@ void GraphCompressor::Compress(ScriptingEnvironment &scripting_environment, no_other_roads, scripting_environment.m_obstacle_map.any( from, via, Obstacle::Type::TrafficSignals), - scripting_environment.m_obstacle_map.any(via, Obstacle::Type::Turning)}; + // Turning facilities (turning_circle, turning_loop, mini_roundabout) are + // always Incompressible, so compressed nodes are never turning facilities. + false}; scripting_environment.ProcessTurn(fake_turn); penalties.duration += to_alias(fake_turn.duration * SECOND_TO_DECISECOND);