Skip to content

Commit 8286447

Browse files
committed
basic play / follow in coverage
1 parent e117c95 commit 8286447

5 files changed

Lines changed: 877 additions & 0 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Coverage playback controls
2+
3+
## Overview
4+
5+
Add media-style playback controls to the coverage view, allowing users to animate coverage data over time (like Environment Canada's weather radar). Includes a follow-vehicle mode that tracks a selected vehicle during playback.
6+
7+
## State
8+
9+
`PlowApp` gains a `playback` object:
10+
11+
```js
12+
{
13+
playing: false,
14+
startVal: 0, // left handle position at play start
15+
endVal: 1000, // right handle position at play start
16+
speed: 15, // seconds to complete playback
17+
followVehicleId: null,
18+
startTime: null, // Date.now() when playback started
19+
animFrame: null, // requestAnimationFrame ID
20+
}
21+
```
22+
23+
## HTML controls
24+
25+
Below `#time-slider`, a new `#playback-controls` div:
26+
27+
- **Play** button -- starts playback, locks UI
28+
- **Stop** button -- exits playback, unlocks UI
29+
- **Speed** `<select>`: "15s" (default), "30s", "1m", "5m", "Realtime"
30+
- **Follow** `<select>`: "None" (default), then vehicle descriptions from coverage data
31+
32+
## UI locking
33+
34+
When playing:
35+
- Slider handles are disabled (`timeSliderEl.setAttribute("disabled", true)`)
36+
- Time presets, date picker, view toggle (lines/heatmap) are disabled
37+
- Speed dropdown is locked (can't change mid-playback)
38+
- Follow dropdown remains interactive (can start/stop following mid-playback)
39+
40+
Stop unlocks everything.
41+
42+
## Animation loop
43+
44+
1. On play: record `startVal` (left handle), `endVal` (right handle).
45+
2. Set right handle to `startVal` (empty view -- start of playback).
46+
3. Compute `durationMs` from speed: 15000, 30000, 60000, 300000, or for "Realtime" the actual time span (`coverageUntil - coverageSince`) * `(endVal - startVal) / 1000`.
47+
4. `requestAnimationFrame` loop:
48+
- `elapsed = Date.now() - startTime`
49+
- `progress = Math.min(elapsed / durationMs, 1)`
50+
- `currentVal = startVal + progress * (endVal - startVal)`
51+
- `timeSliderEl.noUiSlider.set([startVal, currentVal])` -- triggers existing `renderCoverage` via the slider's `update` handler
52+
- If following a vehicle, interpolate position and `easeTo`
53+
- If `progress >= 1`: stop animation, set right handle to `endVal`, set `playing = false`, re-enable controls
54+
55+
No auto-loop -- playback stops at the end, user must press play again.
56+
57+
## Follow vehicle
58+
59+
### Dropdown population
60+
61+
Populated from `this.coverageData.features` when:
62+
- Coverage data loads
63+
- Type filter checkboxes change
64+
65+
Extracts unique `(vehicle_id, description)` pairs, filtered by current type filter. Sorted alphabetically by description.
66+
67+
### Camera behavior
68+
69+
On each animation frame when following:
70+
1. Convert `currentVal` to a time via `sliderToTime`.
71+
2. Search the followed vehicle's trail segments for the one containing that time.
72+
3. Interpolate position between nearest coordinates using timestamp proportion.
73+
4. `map.easeTo({ center: [lng, lat], duration: 300 })` -- 300ms ease smooths frame-to-frame jumps.
74+
5. If vehicle has no position at current time (in a gap), skip the pan.
75+
76+
Selecting "None" stops following; map stays wherever it is.
77+
78+
## Implementation plan
79+
80+
### Step 1: HTML + CSS
81+
- Add `#playback-controls` div below `#time-slider` in index.html
82+
- Style play/stop buttons as icon buttons, dropdowns match existing theme
83+
- Add a hint: "Animate coverage data over time"
84+
85+
### Step 2: PlowApp playback state
86+
- Add `playback` object to constructor
87+
- Add `startPlayback()` method: lock UI, record positions, start rAF loop
88+
- Add `stopPlayback()` method: cancel rAF, unlock UI, restore handle
89+
- Add `playbackTick()` method: the per-frame logic
90+
91+
### Step 3: Follow vehicle
92+
- Add `populateFollowDropdown()` method, called after coverage load and filter change
93+
- Add `interpolateVehiclePosition(vehicleId, time)` method
94+
- Integrate into `playbackTick()`: if following, interpolate + easeTo
95+
96+
### Step 4: Event wiring
97+
- Play button calls `startPlayback()`
98+
- Stop button calls `stopPlayback()`
99+
- Speed dropdown reads value into `playback.speed` before play
100+
- Follow dropdown sets `playback.followVehicleId`
101+
102+
### Step 5: UI locking
103+
- `lockPlaybackUI()` / `unlockPlaybackUI()` helpers
104+
- Disable: slider, presets, date picker, view toggle, speed dropdown
105+
- Keep interactive: follow dropdown, stop button

0 commit comments

Comments
 (0)