Files
samplez/rippr-src/docs/v2/14-path-rendering.md
uhryniuk 280fd7f988 Add Rippr source snapshot and full-history bundle
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>
2026-08-11 08:30:25 -05:00

109 lines
4.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
1. Load points grouped by segment (already ordered `segmentId, id` from T02).
2. Decimate each segment independently — never across a boundary.
3. Build one `Polyline` per segment with speed-derived colour.
4. Add start/end markers.
5. Auto-fit with padding; handle degenerate bounds.
6. Recompute decimation on significant zoom change; debounce it.
7. 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
- [x] Auto-fit frames the whole route with padding
- [x] A stationary ride does not crash the fit
- [x] Exported GPX still contains every raw point (verified: 24 points recorded, 24 exported)
- [x] 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.