Skip to content

feat(mapping): report positional accuracy against the Lanelet2 input - #14

Draft
hakuturu583 wants to merge 4 commits into
masterfrom
feat/geo-mapping-accuracy-report
Draft

hakuturu583 wants to merge 4 commits into
masterfrom
feat/geo-mapping-accuracy-report

Conversation

@hakuturu583

Copy link
Copy Markdown
Collaborator

Pull Request

Summary

Migrated from tier4/autoware_lanelet2_to_opendrive#586, originally authored by @woo-cie (a draft on tier4). This PR carries the same commits, with the current master merged in. Its tier4 base (#585) has already landed here as #9. It stays a draft and will not be merged as-is. The migration review found design issues that need the author's decision. They are listed below so the redesign can start from them.

✨ What it adds (from the original PR)

  • road_lanelet_geo_mapping records, for each matched road, the distance between its reference line and the matched Lanelet2 boundary. It summarises them as a positional-accuracy report written to the mapping JSON and the log, with a WARNING above 1.75 m.

🔍 Migration review findings (open)

  1. Measures vertex spacing, not geometric error (high). _symmetric_mean_distance takes the max of both directions. One direction measures from 0.5 m reference-line samples to the raw Lanelet2 boundary vertices, with no point-to-segment projection and no densification. An exact straight road on a boundary with vertices 10 m apart reports about 2.5 m and trips the warning.
  2. One distribution mixes different metrics (high). The distances come from whichever fallback level matched: symmetric at levels 1 and 2, directed (boundary to reference only) at level 3. The directed metric hides reference-line drift on exactly the worst roads.
  3. Biased sample (medium). Rescued matches (up to 5.25 m) are excluded. A pre-walk retarget keeps the old best_lid. Only one boundary per road is measured.
  4. Lanelet ids are not stable input ids (medium). The docstring says they are, but main.py passes the preprocessed map, where merged lanelets get new ids.
  5. Always on (low). There is no opt-out. It changes the mapping JSON on every run and can warn on maps that are fine. It should be opt-in via Hydra config, or log at INFO until the metric is fixed.
  6. Tests do not cover the main path. Nothing checks that build_mapping fills the distances from raw_dists, and the level-3 fallback is not exercised.

✅ Testing

Pre-commit and the full test suite pass on the merged head in an Ubuntu 22.04 container mirroring CI.


📝 Related Issues

🤖 Generated with Claude Code

https://claude.ai/code/session_01Cem9r5Xdxd8M6H1jrYH5gc


Generated by Claude Code

woo-cie and others added 4 commits August 18, 2026 09:55
The comment claimed "The merged lanelet is added to the map (originals are
kept)". The originals are removed: execute_merge_operations creates the merged
lanelets in Step 1, then Step 2 rebuilds the map with copy_map_excluding, which
drops every listed lanelet (preprocess_lanelet.py:724-731). The function's own
docstring already says so.

Verified against the nishishinjuku output: the mapping produced with merge
enabled contains none of the 14 configured lanelet ids and carries 6 synthetic
merged ids instead (893 - 14 + 6 = 885 lanelets).

The same wrong comment appeared in all three configs that carry a
merge_operations section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…njuku

Only [112, 114] is load-bearing. The other five hide nothing: with them
removed the converter reproduces the same map quality, so they were pure
operational overhead in a file a human has to hand-author per map.

Measured, current master config vs this one, same input .osm:

                        master   this   delta
  roads                    693    698      +5
  junctions                 57     58      +1
  non-center lanes         938    945      +7
  widest lane (m)       12.012 12.012       0
  lanes > 15 m               0      0       0

The +5 roads are exactly the five groups no longer merged: their 12
lanelets reappear as themselves (12/12 present in the mapping, none
dropped) and regroup by ordinary lateral adjacency. No lane exceeds the
15 m plausibility threshold on either side, and the widest lane is
bit-identical, so nothing degraded -- the map is described differently,
not worse.

[112, 114] stays because removing it does degrade the output: lanelet 112
becomes a 1.48 m road carrying a 19.30 m wide lane, the defect sitting
exactly on the 112/114 seam.

Config file: 70 -> 60 lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
build_mapping already measured, for every road it matched, the mean
nearest-point distance between that road's reference line and the
boundary of the lanelet the line was generated from. That number is the
conversion's positional error against its own input. It was used as a
matching tolerance and then discarded.

Keep it. GeoRoadLaneletMapping gains reference_match_distances (keyed by
lanelet id, not road id -- road ids are reassigned whenever preprocessing
changes the lanelet grouping, lanelet ids are not), it is written to
.mapping.json, and validate_and_save_mapping logs the distribution plus
the roads furthest from their source geometry. The distances fall out of
the matching pass, so this costs nothing extra.

_MATCH_THRESHOLD answers "is this the same road?", not "is this road
accurate?". A road 3.4 m from its source boundary -- most of a lane
width -- passed without comment. Only the distribution shows it.

Measured on nishishinjuku (644 matched roads):

  median 0.771 m   p90 1.273 m   p95 1.501 m   max 3.240 m
  >= 1.0 m: 178 roads    >= 2.0 m: 15    >= 3.0 m: 6

Two things stand out. The tolerance is 3.5 m and the worst road is at
3.240 m, leaving 0.26 m of margin on a check that decides which lanelet a
road matches. And _MATCH_THRESHOLD's own comment sizes it against the
spline fitting error (max_avg_error = 2.0 m), which 15 roads exceed.

The 1.75 m warn threshold is half the matching tolerance -- a reporting
default, not a calibrated pass/fail. Separating healthy from degraded
needs known-bad cases, and this distribution is a continuum.

35 tests pass (8 new). ruff, ruff-format and mypy clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants