Viewport selection, polyline clip, project and simplify #40
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
#41 MAP_POLYLINE chunked transport with backpressure handling
robert/PedalPebble
#7 shared/message_keys.json plus C and Kotlin codegen
robert/PedalPebble
#31 Navigation engine: windowed snapping to the route polyline
robert/PedalPebble
Reference
robert/PedalPebble#40
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
Prepare a map frame small enough to send once a second.
Acceptance criteria
int16pairsFiles
companion/.../map/ViewportRenderer.ktNotes
Vector, not raster: a 200x228 bitmap is roughly 45 KB against an 8000 byte AppMessage cap and a few KB/s of Bluetooth.
Update — 2026-09-02: send geometry, not screen coordinates
The phone no longer projects (D39). It sends a slice of the route in a stable metric frame and the
watch projects it.
Three problems this fixes: the phone was never told the watch's screen geometry; zoom and re-centre
were Bluetooth round-trips, making NFR-P6 unsatisfiable; and a dropout froze the map.
MAP_ANCHOR_LAT/MAP_ANCHOR_LONplusint16decimetre offsets east/north of theanchor — ±3.2 km of range, more than any viewport offered
SCREEN_W/SCREEN_H, not to a hard-coded 200×228 pixel gridper few hundred metres — not at 1 Hz
MAP_CUES, rider position asMAP_POS, in the same frameThis is what takes the map off the critical path for bandwidth: link traffic for it drops by roughly
an order of magnitude.
Closed by PR #117 (
area/map-viewport-geometry). Scoped to D39's 2026-09-02 update, which supersedes this issue's original "project to 200x228 screen pixels" body — the phone now sends a slice of route geometry in a stable metric frame; the watch does its own rotation/scaling.New
companion/mapclasses:MapAnchor(plain signed lat/lon),MetricOffsetMeters(pre-quantisation Double meters, kept separate from the quantised type so Douglas-Peucker runs before rounding, not after),MetricPoint(the actualint16decimetre wire shape, hardrequired intoShort.MIN_VALUE..Short.MAX_VALUE— throws rather than silently clamping on overflow),MetricPolylineSimplifier(a genuinely justified second Douglas-Peucker implementation — operates directly in the already-metric frame with no per-run trigonometry, unlike core's lat/lon version),MapCueMarker,MapSlice(withneedsRefresh()implementing D39's "as the rider nears the edge" cadence, correctly suppressed once the slice already reaches the route's end), andMapSliceBuilder(the pipeline: anchor = the rider's own interpolated on-route position, clip window widened to bracketing vertices, DP tolerance derived from the connected watch's actual screen dimensions viamax(width,height)— reasoned against heading-up rotation possibly aligning either axis with travel — never a hard-coded 200×228 grid).Reuses
RouteSnapper/RouteSnap(#31, including its #39-audited self-crossing fix) for rider position rather than re-deriving it, andcumulativeDistancesMeters(widenedinternal→public, its first cross-module consumer) for real clip-window distances. Correctly stayed out of #41 (chunked wire transport), #42 (watch-side rendering), and #43 (zoom/recentre) — produces a real, tested in-memoryMapSlicefor #41 to serialize, nothing here touches AppMessage.Verified the wire keys this feeds (
MAP_ANCHOR_LAT/LON,MAP_POLYLINE,MAP_SEQ,MAP_CUES,MAP_POS,MAP_HEADING) already existed inshared/message_keys.json/docs/PROTOCOL.md§2.5 from prior codegen — no wire changes needed here,MetricPoint's shape matches them exactly so #41 only serializes, never converts.Real verification: genuine
./gradlew :companion:map:test :companion:core:test :companion:route:test :companion:assembleDebug --rerun-tasksfull rebuild, all green — 40 tests across 5 suites in:companion:map(MapAnchorTest 5, MapSliceBuilderTest 13, MapSliceTest 9, MetricPointTest 7 incl. the exact int16 boundary at 32767/32768 decimetres, MetricPolylineSimplifierTest 6), plus a real end-to-endRouteSnapper→MapSliceBuilderrun against the real ~119km komoot GPX fixture.41 issues closed.