OpenDRIVE round-trip: keep left lanes on the left, regenerate lateral edits surgically - #105
Merged
Conversation
…l reference direction Bundles made entirely of imported left lanes (positive odr_lane_id) used to regenerate as <right> lanes of a reversed reference line: lane-id signs flipped (+2 -> -2) and s ran backwards (s_new = L - s_orig). Such bundles now keep the boundary polylines in original reference-line order, emit their lanes on the <left> side (+1..+n outward), and orient the unsigned closest-point fallback of normalOffsets to the negative-t side so widths survive. Road/lane links and junction connecting-road contact points are derived from the lane-id signs (a left road's travel exit is its geometric start), matching the importer's travel-direction semantics.
…rence direction, links)
Add hashRoadSemantics() and store a per-road semanticHash in the sidecar record alongside the existing full stateHash. It hashes only the road's non-geometric state (lane attributes, connectivity, right-of-way and the regulatory shapes, with all boundary / stop-line point sequences dropped). When the full hash changes but the semantic hash still matches, the edit touched only boundary geometry -- the precondition the exporter needs to decide an edit is a candidate for lateral-only (surgical) regeneration.
A carry-through edit that moves boundary points only across the travel direction changes nothing but the lane widths: the road's plan view, laneOffset, elevation profile, signals, objects and links are all still valid. The fitting exporter regenerated the whole road anyway, rebuilding the reference line from the leftmost boundary -- dropping the original laneOffset, shifting s over the road length, coarsening the elevation profile and dropping signals / objects. buildSurgicalRoad() instead keeps the original <road> element verbatim and rewrites only the lane <width> records, recomputed from the edited boundaries measured along the original reference line + laneOffset. It runs only when (a) the semantic hash still matches (no attribute / connectivity / regulatory change) and (b) every edited boundary point still projects onto the reference line at its original station (no longitudinal drift) with its inner boundary on the accumulated datum. Anything else -- a point dragged along the road, a moved reference/center edge, a skipped micro section, a changed vertex count -- fails the check and the road regenerates fully, so the surgical path can never emit wrong widths. planCarryThrough attempts the surgical rebuild for each edited road; a success keeps the road clean (its lanes stay out of full regeneration and join the connectivity maps as external endpoints) and emits the width-rewritten text through the same verbatim path (link rewriting intact).
- lateral edit on a real curved road with a laneOffset (soderleden road 0) keeps its length / plan-view primitive count / laneOffset / elevation records, leaves every other road byte-verbatim, and reproduces the moved point on re-import - longitudinal edit on the same road falls back to full regeneration - unedited round trip stays fully verbatim (surgical never fires) Also switch the existing junction-regeneration test's edit from a lateral nudge to a longitudinal drag: a lateral nudge of an outgoing road is now handled surgically (road + junction stay verbatim), so forcing full regeneration -- the behaviour that test pins -- needs a non-lateral edit.
kosuke55
force-pushed
the
fix/odr-roundtrip-fidelity
branch
from
August 22, 2026 00:13
90b3084 to
379dac6
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.
Summary
Two fixes for how edited roads regenerate on OpenDRIVE export.
Left-side lanes — a bundle made entirely of imported left lanes (positive lane ids) used to come back as
<right>lanes of a reversed reference line: lane-id signs flipped (+2 → -2) and the road's s coordinate ran backwards. The exporter now keeps these bundles on the<left>side with the original reference direction. A sign bug in the closest-point width fallback (unsigned distance rejected left-side intersections) is fixed along the way.Surgical (lateral-only) regeneration — editing one boundary point used to regenerate the whole road: reference line refit,
laneOffsetdropped, elevation coarsened, s drifting over the full length. When every moved point's displacement is lateral (no s-direction component), the exporter now keeps the originalplanView/laneOffset/elevationProfile/ signals / objects verbatim and rewrites only the<width>records, recomputed against the original reference line. Anything else — longitudinal drags, structural changes, ambiguous cases — falls back to full regeneration. A geometry-free semantic hash per road distinguishes the two.Verification