Navigation engine: windowed snapping to the route polyline #31
Labels
No labels
area:companion
area:docs
area:shared
area:tooling
area:watchapp
blocker
kind:chore
kind:feature
kind:spike
kind:test
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Blocks
Depends on
#32 Off-route detection with hysteresis and auto-recovery
robert/PedalPebble
#33 Next turn, then-turn, remaining distance and ETA
robert/PedalPebble
#39 Unit tests: snapping on out-and-back routes, off-route hysteresis
robert/PedalPebble
#40 Viewport selection, polyline clip, project and simplify
robert/PedalPebble
#57 Off-route re-routing back to the course
robert/PedalPebble
#24 GPX share-sheet import intent, parser and simplification
robert/PedalPebble
Reference
robert/PedalPebble#31
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Locate the rider along the route cheaply and without jumping.
Acceptance criteria
Files
companion/.../route/nav/RouteSnapper.ktUpdate — 2026-09-01: monotonicity and re-routing interact
asserted in tests (#39), not merely intended
its own monotonic distance axis, swapped in once at the moment the detour is accepted —
rather than mutating the route this engine is mid-way through reading
See D30. The rejoin point is the part everyone thinks of; the splice is the part that breaks
invariants.
Update — 2026-09-02: monotonicity is now free
D42 removes mid-ride re-routing from Phase 3, so the route object never changes during a ride.
Distance-along-route is therefore monotonic by construction rather than by rule.
out-and-back
The Phase 5 re-routing issue reintroduces a route swap, and carries the monotonic-axis requirement
itself.
Closed by PR #114 (
area/navigation-snapping).New
RouteSnapper/RouteSnapincompanion/core/.../route/nav/RouteSnapper.kt, reusingRouteGeodesy.locateAlongPolyline(#26) directly rather than reimplementing point-to-segment projection — the only change needed was calling it withmaxOffsetMeters = Double.MAX_VALUE, since a live snapper wants the honest (possibly large) offset reported, not discarded the way tier-0 cue matching wants it discarded.Windowing: forward-only search cursor persisting across
snap()calls, 150 m normal horizon (NFR-B2's ~1 Hz fix rate at plausible speed), widening toelapsedSeconds x 20 m/safter a >5 s GPS gap, and to a flat 500 m after the previous fix was >50 m off-route (a re-acquisition case, not #32's own off-route determination — that threshold is purely internal to this class's own window sizing).Output type
RouteSnap(segmentStartIndex, distanceAlongRouteMeters, offsetMeters)carriesoffsetMetersunfiltered specifically for #32's future hysteresis logic to consume.Real test coverage (6 tests,
./gradlew :companion:core:test, genuinely re-run not cached): a real ~119km komoot GPX fixture confirming monotonic distance-along-route across a long real polyline; an out-and-back route proving the forward-only cursor doesn't jump to the geometric twin ~3000m away on the return leg; a genuinely off-route case (two geometrically-close "lanes" far apart in route order) proving a large honest offset is reported instead of a wrong nearest-order match; and two widening-specific tests (GPS gap, off-route excursion) with tight distance tolerances that would catch a regression to the normal 150m horizon.This unblocks #39 and #40.