Skip to content

feat: implement turning circle support for u-turns#7388

Open
Claude wants to merge 21 commits into
masterfrom
claude/use-turning-circles-u-turns
Open

feat: implement turning circle support for u-turns#7388
Claude wants to merge 21 commits into
masterfrom
claude/use-turning-circles-u-turns

Conversation

@Claude

@Claude Claude AI commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

Implements support for using highway=turning_circle, highway=turning_loop, and highway=mini_roundabout nodes for u-turn maneuvers, eliminating unnecessary u-turn penalties at designated turning facilities.

Implementation

C++ Core Changes

  • Added has_turning_facility field to ExtractionTurn structure
  • Modified edge_based_graph_factory.cpp to detect turning facilities at intersections using ObstacleMap
  • Updated graph_compressor.cpp to handle turning facilities during node compression
  • Extended scripting_environment_lua.cpp to expose the field to Lua profiles (API v3 and v4)

Routing Logic

  • Modified profiles/car.lua to check turn.has_turning_facility before applying u-turn penalty
  • U-turns at turning facilities: 0 second penalty (no penalty)
  • U-turns elsewhere: 20 second penalty (existing behavior preserved)

Testing

  • Created comprehensive test suite in features/car/turning_circle_uturn.feature
  • Tests validate preference for turning facilities, multiple facility types, dead ends, one-way streets, and regression scenarios

Code Formatting

  • Applied clang-format-18 to src/extractor/graph_compressor.cpp to fix constructor alignment violations

Documentation

  • Added has_turning_facility field to process_turn API documentation in docs/profiles.md
  • Updated CHANGELOG.md with feature entry under Routing section

The has_turning_facility boolean is now available in Lua profiles:

function process_turn(profile, turn)
  if turn.is_u_turn then
    -- No penalty at designated turning facilities
    if not turn.has_turning_facility then
      turn.duration = turn.duration + profile.properties.u_turn_penalty
    end
  end
end

Design Decisions

  • Penalty Amount: 0 seconds at turning facilities (simplest approach)
  • Scope: Includes turning_circle, turning_loop, and mini_roundabout
  • Profile: Car profile only (extensible to others later)

Files Modified

  • include/extractor/extraction_turn.hpp - Add field (~9 lines)
  • src/extractor/edge_based_graph_factory.cpp - Detect facility (~3 lines)
  • src/extractor/graph_compressor.cpp - Handle compression & formatting (~18 lines)
  • src/extractor/scripting_environment_lua.cpp - Expose to Lua (~4 lines)
  • profiles/car.lua - Adjust penalty (~5 lines)
  • features/car/turning_circle_uturn.feature - Tests (new, ~140 lines)
  • CHANGELOG.md - Document feature (~1 line)
  • docs/profiles.md - Document API (~1 line)

Total: ~180 lines across 8 files

Original prompt

This section details on the original issue you should resolve

<issue_title>highway=turning_circle should be used for u-turns</issue_title>
<issue_description># Feature

OSRM should use nearby turning circles for u-turns.

Current situation

OSRM ignores perfectly good turning opportunities and instead routes you literally through the woods, like here: (Note that there is no road where OSRM tells you to turn!)

Screenshot from 2024-12-31 11-47-19

https://map.project-osrm.org/?z=18&center=46.562307%2C11.718265&loc=46.562099%2C11.718770&loc=46.562176%2C11.718206&loc=46.562121%2C11.718603&hl=en&alt=0&srv=0

Maybe duplicate of: #620, but that one was closed without proper resolution and I'm not allowed to reopen it.</issue_description>

<agent_instructions>Investigate how to do this. Propose an analysis and a plan including test coverage. But don’t start coding just yet. </agent_instructions>

Comments on the Issue (you are @claude[agent] in this section)

Todo

  • Find examples for turning facilities in the Monaco test set
  • Add tape tests for each of the above examples
  • Post comment with a screenshot showing changes routing behavior

@Claude Claude AI changed the title [WIP] Add support for using turning circles in u-turn routing Investigation: Enable turning circles for u-turns Mar 1, 2026
@DennisOSRM

This comment was marked as outdated.

- 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>
@Claude

This comment was marked as outdated.

@Claude Claude AI changed the title Investigation: Enable turning circles for u-turns Enable turning circles for u-turns Mar 1, 2026
- 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>
@Claude Claude AI changed the title Enable turning circles for u-turns Fix formatting and update documentation for turning circle u-turn feature Mar 1, 2026
@DennisOSRM

This comment was marked as outdated.

@Claude

This comment was marked as outdated.

@Claude Claude AI changed the title Fix formatting and update documentation for turning circle u-turn feature Implement turning circle support for u-turns Mar 1, 2026
Copilot AI and others added 9 commits March 2, 2026 06:42
* 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>
- 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>
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>
- 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>
@DennisOSRM DennisOSRM changed the title Implement turning circle support for u-turns feat: implement turning circle support for u-turns Apr 13, 2026
…ne-ways, and bearings

Add 4 new complex scenarios to the turning circle feature tests:
- Intermediate turning circle on through road used for u-turn
- One-way main road forces u-turn at intermediate turning circle
- Multiple intermediate turning facilities with bearings
- U-turn at turning circle beats competing dead-end paths (2 variants)

These test that turning circles at intermediate nodes (not just leaves)
are correctly preferred for u-turns over plain dead-ends due to the
lower penalty (5s vs 20s). Also verifies that intersection_analysis
blocks u-turns at regular degree-3 intersections, making turning
circles essential for legal u-turns on non-trivial road networks.

All 13 scenarios pass with both CH and MLD algorithms.
@DennisOSRM DennisOSRM marked this pull request as ready for review May 31, 2026 21:06
Copilot AI review requested due to automatic review settings May 31, 2026 21:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a has_turning_facility signal from the C++ extractor through to Lua profiles, and uses it in car.lua to apply a reduced u-turn penalty when the u-turn happens at a highway=turning_circle, turning_loop, or mini_roundabout node. Adds a Cucumber feature exercising the routing behavior across several topologies, plus docs/CHANGELOG updates.

Changes:

  • Plumb a new has_turning_facility boolean into ExtractionTurn, populate it from ObstacleMap in edge_based_graph_factory.cpp, hardcode false for fake compression turns, and expose it to Lua API v3/v4.
  • Add turning_circle_penalty = 5 to car.lua and use it instead of u_turn_penalty when turn.has_turning_facility is set; refactor obstacles.lua to use an explicit highway_to_obstacle_type table.
  • Add features/car/turning_circle_uturn.feature (route/turn assertions only, no time/weight), plus docs and a obstacles.hpp any(to, type) helper.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
include/extractor/extraction_turn.hpp Adds has_turning_facility member and threads it through both constructors.
include/extractor/obstacles.hpp Adds two-argument any(to, type) helper on ObstacleMap.
src/extractor/edge_based_graph_factory.cpp Detects turning facility at intersection and passes it into ExtractionTurn.
src/extractor/graph_compressor.cpp Reformats fake_turn ctor and passes has_turning_facility=false (turning nodes are incompressible).
src/extractor/scripting_environment_lua.cpp Exposes has_turning_facility to Lua API v3 and v4.
profiles/car.lua Adds turning_circle_penalty = 5 and uses it for u-turns at turning facilities.
profiles/lib/obstacles.lua Replaces dynamic obstacle_type[highway] lookup with explicit allow-list table.
features/car/turning_circle_uturn.feature New scenarios asserting routes/turns for turning-facility u-turns.
features/step_definitions/trip.js Unrelated lint-style rename ri_ri.
features/step_definitions/distance_matrix.js Unrelated lint-style cleanups (_ri, dropped response destructure).
docs/profiles.md Documents new has_turning_facility field on process_turn.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread profiles/car.lua Outdated
Comment on lines +41 to +44
-- 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,
Comment thread profiles/car.lua
Comment on lines +572 to +574
if turn.has_turning_facility then
-- No regular u-turn penalty at designated turning facilities (turning_circle, turning_loop, mini_roundabout)
turn.duration = turn.duration + profile.turning_circle_penalty
Comment thread features/step_definitions/trip.js Outdated

await this.reprocessAndLoadData();
const testRow = function (row) {
const testRow = function (row, _ri) {
Comment on lines +28 to +30
When I route I should get
| waypoints | bearings | route | turns |
| a,a | 90,10 270,10 | abce,bd,bd,abce,abce | depart,turn right,continue uturn,turn left,arrive |
@codecov

codecov Bot commented May 31, 2026

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

- Fix misleading comments: penalty is 5s not 0s, clarify 'reduced' vs 'no'
- Keep necessary lint fixes in distance_matrix.js (unused _ri param, response destructure)
- Add time and weight assertions (+-10s margin) to turning circle u-turn scenarios
  to verify the 5s penalty is actually applied at turning facilities
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

highway=turning_circle should be used for u-turns

4 participants