feat(engine): measure how much room a point has inside an area - #7693
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new “clearance oracle” to the engine’s open-area geometry tooling, intended to support upcoming path-smoothing work by reporting the nearest geometry, distance, gradient direction, and the originating ring/segment.
Changes:
- Introduces
Clearance+clearance(point, rings)for nearest-point distance/gradient plus ring+segment provenance. - Adds
metres_per_projected_unit(latitude_degrees)to convert the projected units used by the area predicates into metres. - Adds unit tests covering wall/corner cases, ties, degeneracy handling, outside points, gradient correctness, and latitude-dependent conversion.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| include/engine/area_clearance.hpp | Declares the Clearance result type and the public clearance + conversion APIs. |
| src/engine/area_clearance.cpp | Implements nearest-point-on-segment scanning and latitude-based metre conversion. |
| unit_tests/engine/area_clearance.cpp | Adds focused unit tests validating distance/gradient behavior and conversion properties. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+21
to
+25
| struct Clearance | ||
| { | ||
| //! Distance to the nearest point of the area's geometry, in projected units. | ||
| double distance = 0.0; | ||
| //! The nearest point itself, which is on the segment named below. |
Comment on lines
+95
to
+99
| namespace detail = util::coordinate_calculation::detail; | ||
| const auto metres_per_degree = | ||
| static_cast<double>(detail::EARTH_RADIUS) * detail::DEGREE_TO_RAD; | ||
| const auto latitude = std::clamp(latitude_degrees, -89.9, 89.9); | ||
| return metres_per_degree * std::cos(latitude * detail::DEGREE_TO_RAD); |
Comment on lines
+57
to
+60
| * Web Mercator is conformal, so locally it scales every direction alike and a distance in | ||
| * projected units can be turned into metres by one factor. That factor grows with | ||
| * latitude, so it is taken at the area being worked on rather than globally. | ||
| * |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #7693 +/- ##
==========================================
+ Coverage 94.75% 94.76% +0.01%
==========================================
Files 519 523 +4
Lines 41582 41753 +171
==========================================
+ Hits 39402 39569 +167
- Misses 2180 2184 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This was referenced Aug 16, 2026
Adds clearance(): the distance from a point to the nearest piece of an area's geometry, the nearest point itself, the direction the clearance grows in, and which ring and segment the answer came from. The outer boundary counts the same as the holes. A path has to stay off the walls of a plaza as much as off the fountain in the middle of it, and a caller that wanted only the obstacles can pass only the obstacles. The distance is unsigned and the point need not be inside, so a point outside the area gets its distance to the boundary rather than a negative number. Ties go to the first segment scanned. A point equidistant from two segments is on the medial axis, where which one is named is arbitrary; what matters is that the answer is the same on every platform and in every run, because paths get built out of these answers. The scan is flat over the segments. Plazas have hundreds of them, and an r-tree here would buy a logarithm and cost the ability to check the result against a brute-force scan, which is how it is tested. metres_per_projected_unit() comes with it. Mercator is conformal, so locally it scales every direction alike and one factor converts projected units to metres. It is taken at the latitude of the area rather than globally. This keeps parameters a person would recognise, such as a quarter of a metre of clearance, apart from the predicates, which all work in projected units. Nothing calls this yet. It is the quantity an elastic band is built on, and the band follows separately.
DennisOSRM
force-pushed
the
area-clearance-oracle
branch
from
August 16, 2026 17:24
527e1c4 to
b88ecb0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
No issue. This is the first piece of the open-area path smoothing work, split out so it
can be reviewed on its own.
What this adds
clearance(point, rings)answers how much room a point has inside an area: the distanceto the nearest piece of geometry, the nearest point itself, the direction the clearance
grows in, and which ring and segment the answer came from.
That last part is what makes it useful rather than just a distance function. A caller
that wants to push a path away from an obstacle needs to know which obstacle, and one
that wants to reason about homotopy needs to know it did not swap to a different one.
Decisions worth reviewing
The outer boundary counts the same as the holes. A path has to stay off the walls of a
plaza as much as off the fountain in the middle of it. A caller that wanted only the
obstacles can pass only the obstacles.
The distance is unsigned and the point need not be inside. A point outside the area
gets its distance to the boundary, not a negative number. Signed distance would need an
inside test at every query and would answer arbitrarily on the boundary itself, which is
exactly where portals sit.
Ties go to the first segment scanned. A point equidistant from two segments is on the
medial axis, where which one is named is arbitrary. What is not arbitrary is that the
answer has to be the same on every platform and in every run, because paths get built out
of these answers and a path that differs between
-O0and-O3cannot be asserted on inthe integration suite.
The scan is flat over the segments. Plazas have hundreds of them. An r-tree here would
buy a logarithm and cost the ability to check the result against a brute-force scan, which
is how it is tested. Worth revisiting when a profile says to, not before.
metres_per_projected_unit()comes with it. Mercator is conformal, so locally itscales every direction alike and one factor converts projected units to metres. It is
taken at the latitude of the area rather than globally. This is what keeps parameters a
person would recognise, such as a quarter of a metre of clearance, apart from the
predicates, which all work in projected units.
Nothing calls this yet
It is the quantity an elastic band is built on, and the band follows in its own PR. On its
own this changes no behaviour anywhere: no existing file is touched, and the whole diff is
three new files.
Tests
Eleven cases on a 10x10 square with a 2x2 block in the middle, so every expected answer can
be worked out on paper: distance to a wall, distance to a corner, the block counting as
much as the walls, a point on the geometry having no gradient, a point outside measuring to
the boundary, a point inside the block measuring to the block, degenerate rings not
dividing by zero, no rings at all, the gradient pointing the way clearance grows, and the
metre conversion at three latitudes.
engine-testspasses, 75 cases.Was this change primarily generated using an AI tool? Yes.
🤖 Claude Code, Claude Opus 5
Tasklist
Requirements / Relations
Follows #7691 and #7692, which fixed the two geometry predicates this work uncovered.
An elastic band built on this oracle follows separately.