Two copies for two jobs. rippr-src/ is a browsable git archive export of the tracked tree at 2f76983 - no build outputs, no local.properties, no nested .git - which is convenient to read in gitea but carries no history and will drift. rippr-full-history.bundle is the real backup: all 18 commits, verified as "records a complete history" and test-cloned before committing. This matters because ~/dojo/rippr has no git remote and otherwise exists only on one machine. rippr-src/SNAPSHOT.md explains the difference and how to restore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.5 KiB
T14 — Path rendering
Phase 5 · Depends on T04, T13 · Status Done
Goal
Draw the ride on the map: one polyline per segment, coloured by speed, decimated for performance, auto-fitted to the route. Done when a real ride renders smoothly with visible gaps at pauses and colour that tracks pace.
Context
This is the feature Dylan actually asked for — "I wanna see my movements plotted on a map like Google Maps or Uber, see the exact path I traversed."
The data has been there since v1. Every point carries latitude, longitude, and
speedKmh at 1–2 Hz. Nothing new is captured; this renders what already exists.
Scale is the constraint: a three-hour ride at 2 Hz is ~21,600 points. Handing that straight to a polyline renderer janks badly on pan and zoom.
Design
Decimation — render only
Geo.simplify() (T04) with an epsilon chosen from current zoom, roughly "half a pixel at
this scale". A sensible default is ~5 m at typical zoom, cutting a 21,600-point ride to a
few thousand with no visible difference.
Raw points are never decimated in storage or export. Decimation exists purely in the render path. T15's tests assert full point counts precisely to catch any leak of this optimisation into exported data.
One polyline per segment
Segments must not be joined. A pause means the rider stopped recording; connecting across
it draws a straight line through terrain never travelled — the exact artefact the Segment
model exists to prevent. Each segment gets its own Polyline, so gaps appear naturally.
Speed colouring
Gradient from a cool colour at low speed to the app's accent orange (#FF5722) at high
speed, normalised against the trip's own max speed so every ride uses the full range.
osmdroid 6.1 offers PolyChromaticPaintList for per-vertex colouring. It is fiddly. If
it fights back, fall back to bucketed polylines — split each segment into runs of
similar speed (say 10 km/h buckets) and draw each run as its own monochrome polyline.
Visually near-identical, considerably more predictable. Do not burn hours on the
chromatic API.
Include a small legend; an unexplained colour gradient is decoration rather than information.
Auto-fit and markers
Geo.bounds() (T04) → mapView.zoomToBoundingBox(bounds, false, padding). Guard the
degenerate case: a stationary "ride" has zero-area bounds and zooming to it either throws
or lands at maximum zoom. Fall back to centring at a fixed zoom.
Start and end markers, styled minimally.
Implementation
- Load points grouped by segment (already ordered
segmentId, idfrom T02). - Decimate each segment independently — never across a boundary.
- Build one
Polylineper segment with speed-derived colour. - Add start/end markers.
- Auto-fit with padding; handle degenerate bounds.
- Recompute decimation on significant zoom change; debounce it.
- Legend.
Acceptance criteria
- Real ride renders with no straight-line artefact across pauses
- Colour visibly tracks speed
- A 20,000-point ride pans and zooms smoothly
- Auto-fit frames the whole route with padding
- A stationary ride does not crash the fit
- Exported GPX still contains every raw point (verified: 24 points recorded, 24 exported)
- Single-point and empty trips render without crashing
Not verified. Unchecked items above were not tested. Speed colouring in particular is unverifiable on the emulator, which reports zero velocity — the rendered path is uniformly the low-speed colour there. Tracked in T18.
Tests
Unit: decimation is applied per segment and never merges across boundaries; the speed→colour mapping is monotonic and handles a zero-range (constant-speed) ride.
Instrumented/manual: render a synthetic paused ride on the emulator and screenshot;
confirm the gap is visible. Then confirm on a real ride — the emulator cannot produce
velocity, so speed colouring can only be truly validated on a real ride
(adb emu geo fix teleports, leaving Location.speed at 0 and every point the same
colour).
Risks / gotchas
- Speed colouring is unverifiable on the emulator. Expect a uniform-colour path there; that is the harness, not a bug. Validate on a real ride.
- Zero-range normalisation — a constant-speed ride divides by zero. Guard it.
- Decimation leaking into export is the correctness risk in this task. Keep the simplified list strictly local to rendering.
- Debounce zoom-triggered re-decimation or a pinch gesture recomputes dozens of times.
Out of scope
Live path during recording (v3+); route matching; heatmaps.