Skip to content

feat(engine): measure how much room a point has inside an area - #7693

Merged
DennisOSRM merged 1 commit into
masterfrom
area-clearance-oracle
Aug 16, 2026
Merged

feat(engine): measure how much room a point has inside an area#7693
DennisOSRM merged 1 commit into
masterfrom
area-clearance-oracle

Conversation

@DennisOSRM

Copy link
Copy Markdown
Collaborator

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 distance
to 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 -O0 and -O3 cannot be asserted on in
the 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 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 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-tests passes, 75 cases.

Was this change primarily generated using an AI tool? Yes.

🤖 Claude Code, Claude Opus 5

Tasklist

  • self-review code for correctness and following the coding guidelines
  • add tests
  • update relevant wiki pages
  • review
  • adjust for comments

Requirements / Relations

Follows #7691 and #7692, which fixed the two geometry predicates this work uncovered.
An elastic band built on this oracle follows separately.

Copilot AI lite review requested due to automatic review settings August 16, 2026 11:26

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

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.76%. Comparing base (9229f17) to head (b88ecb0).
⚠️ Report is 4 commits behind head on master.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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
DennisOSRM force-pushed the area-clearance-oracle branch from 527e1c4 to b88ecb0 Compare August 16, 2026 17:24
@DennisOSRM
DennisOSRM merged commit 9be1c7a into master Aug 16, 2026
23 checks passed
@DennisOSRM
DennisOSRM deleted the area-clearance-oracle branch August 16, 2026 19:55
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.

2 participants