docs: update elevation docs and changelog for two-button recalculation and DEM fix

This commit is contained in:
Davide Scaini
2026-04-20 21:43:28 +02:00
parent ebac3f50f4
commit 88b24a6274
3 changed files with 94 additions and 48 deletions
+35 -24
View File
@@ -26,37 +26,48 @@ the last committed value. GPS noise is suppressed without losing real climbs.
suppression, barometric vs GPS threshold difference, real climb approximation, suppression, barometric vs GPS threshold difference, real climb approximation,
unknown-treated-as-gps invariant unknown-treated-as-gps invariant
### New feature — DEM-based elevation recalculation from the edit drawer ### New feature — On-demand elevation recalculation from the edit drawer
A new **"Recalculate from terrain map (DEM)"** button in the activity edit Two new buttons in the activity edit drawer fix inaccurate elevation stats
drawer replaces noisy GPS altitude with SRTM terrain data, then re-applies without re-uploading the file:
hysteresis accumulation to compute corrected gain/loss.
This is the recommended fix for activities that still show inaccurate **📐 Recalculate (hysteresis)** — re-applies source-aware hysteresis
elevation after the hysteresis improvement (e.g. activities recorded before accumulation to the original recorded elevation. Fast, offline, no network
re-extracting from sources, or uploads where the source file had severe GPS required. Best for barometric altimeters (Karoo 2, Garmin with
altitude noise). `enhanced_altitude`, Wahoo) that were extracted before the noise-filtering
improvement.
How it works: **⛰ Recalculate (DEM)** — replaces GPS altitude with SRTM terrain data, then
1. The server subsamples the activity's 1 Hz GPS track (one point every 10 s) re-applies hysteresis. Best for GPS-only devices where the recorded altitude
2. Queries an Open-Elevation-compatible API for terrain elevation is noisy.
3. Linearly interpolates DEM elevation back to every GPS-valid second
4. Applies 5 m hysteresis to compute the corrected gain and loss
5. Writes the updated `elevation_m` array to the timeseries (chart updates)
6. Patches `elevation_gain_m` / `elevation_loss_m` in the activity JSON and
`index.json` summary
- **`bincio/extract/dem.py`** (new) — `lookup_elevations()` (batched HTTP POST, DEM pipeline (revised after discovering that a naive 5 m threshold produced
Open-Elevation wire format) + `recalculate_elevation()` (full pipeline above) results worse than no correction on some activities):
- **`POST /api/activity/{id}/recalculate-elevation`** — on both `bincio serve` 1. Subsample GPS track to one point per 10 s
(auth-gated, triggers `merge_one` + rebuild) and `bincio edit` (no auth) 2. Query Open-Elevation API in batches of 512
3. Linearly interpolate back to the full 1 Hz series
4. Apply a **45 s sliding median filter** to suppress SRTM tile-boundary
steps (occur every ~7 s at cycling speed; were accumulating through 5 m
threshold and inflating gain by 50 %+)
5. Apply **10 m hysteresis** to the smoothed series
6. Back up original `elevation_m` as `elevation_m_original` in the timeseries
on the first DEM run (never overwrites an existing backup)
- **`bincio/extract/dem.py`** (new) — `lookup_elevations()`,
`recalculate_elevation()` (DEM + median + 10 m hysteresis),
`recalculate_elevation_hysteresis()` (offline, reads `elevation_m_original`
if available, uses 5 m/10 m source-aware threshold)
- **`POST /api/activity/{id}/recalculate-elevation/dem`** and
**`POST /api/activity/{id}/recalculate-elevation/hysteresis`** — on both
`bincio serve` (auth-gated, triggers `merge_one` + rebuild) and
`bincio edit` (no auth)
- **`bincio serve --dem-url URL`** / **`bincio edit --dem-url URL`** — override - **`bincio serve --dem-url URL`** / **`bincio edit --dem-url URL`** — override
the default DEM endpoint (also read from `DEM_URL` env var) the default DEM endpoint (also read from `DEM_URL` env var)
- Default DEM endpoint: **`https://api.open-elevation.com`** — works out of the - Default DEM endpoint: **`https://api.open-elevation.com`** — works out of
box with no configuration the box with no configuration
- **`GET /api/me`** response gains `dem_configured: bool` - **`GET /api/me`** response gains `dem_configured: bool`
- **`EditDrawer.svelte`** — button with spinner, shows `↑ Xm ↓ Ym` on success - **`EditDrawer.svelte`** — two side-by-side buttons with individual spinners,
or an inline error (e.g. if the DEM API is unreachable) shows `↑ Xm ↓ Ym` on success or inline error
--- ---
+45 -13
View File
@@ -277,23 +277,55 @@ require re-extraction from source files.
### Medium term — ✅ Implemented (2026-04-20) ### Medium term — ✅ Implemented (2026-04-20)
**On-demand DEM correction** via the edit drawer, using the Open-Elevation API **Two on-demand recalculation options** in the activity edit drawer:
(SRTM30 data):
#### Option 1 — Hysteresis (fast, offline)
Re-applies the same source-aware hysteresis accumulation as the extract
pipeline directly to the recorded elevation, with no network calls.
- Uses `elevation_m_original` from the timeseries (the backup saved on the
first DEM run) if present; otherwise uses the current `elevation_m`.
- Threshold: **5 m** for barometric sources, **10 m** for GPS.
- Does not modify the elevation array in the timeseries — only patches
`elevation_gain_m` / `elevation_loss_m`.
- Best for: devices with a barometric altimeter (e.g. Karoo 2, Garmin with
`enhanced_altitude`) where the recorded elevation is already accurate but
was extracted before the hysteresis fix was deployed.
#### Option 2 — DEM terrain correction (SRTM30, requires network)
Replaces the recorded GPS altitude with terrain data from an
Open-Elevation-compatible API (SRTM30, ~30 m resolution):
1. GPS track subsampled to one point per 10 s to minimise API calls. 1. GPS track subsampled to one point per 10 s to minimise API calls.
2. Terrain elevation fetched via `POST https://api.open-elevation.com/api/v1/lookup` 2. Terrain elevation fetched via `POST https://api.open-elevation.com/api/v1/lookup`
in batches of 512. in batches of 512.
3. DEM elevation linearly interpolated back to the full 1 Hz series. 3. DEM elevation linearly interpolated back to the full 1 Hz series.
4. 5 m hysteresis applied to the interpolated series. 4. **45 s sliding median filter** applied to suppress SRTM tile-boundary
5. Timeseries and activity JSON patched in place; chart and stats update immediately. steps (these occur every ~7 s at cycling speed and accumulate as phantom
gain through a naive threshold).
5. **10 m hysteresis** applied to the smoothed series.
6. Original elevation backed up as `elevation_m_original` in the timeseries
(only on the first DEM run — never overwrites an existing backup).
7. Timeseries and activity JSON patched in place; chart and stats update.
Implementation: `bincio/extract/dem.py` + `POST /api/activity/{id}/recalculate-elevation` Best for: GPS-only devices (no barometric sensor) where the recorded
on both servers. Default endpoint: `https://api.open-elevation.com`; override with altitude is noisy and the DEM terrain is a better ground truth.
> **Why median + 10 m, not 5 m?** SRTM30 at 1 Hz produces step changes at
> tile boundaries of 13 m every few seconds. A 5 m threshold lets most of
> these through; they accumulate and can inflate the result by 50 %+. The
> 45 s median smooths the steps before the dead-band sees them; 10 m catches
> any residual outliers.
Implementation: `bincio/extract/dem.py``lookup_elevations()`,
`recalculate_elevation()`, `recalculate_elevation_hysteresis()`.
API endpoints: `POST /api/activity/{id}/recalculate-elevation/dem` and
`POST /api/activity/{id}/recalculate-elevation/hysteresis` on both servers.
Default DEM endpoint: `https://api.open-elevation.com`; override with
`--dem-url` or `DEM_URL` env var. `--dem-url` or `DEM_URL` env var.
This is the recommended fix for activities uploaded before the hysteresis improvement,
or any activity where GPS noise is severe.
--- ---
## Implementation status ## Implementation status
@@ -305,8 +337,8 @@ or any activity where GPS noise is severe.
| `bincio/extract/parsers/gpx.py` | ✅ sets `altitude_source = "gps"` | | `bincio/extract/parsers/gpx.py` | ✅ sets `altitude_source = "gps"` |
| `bincio/extract/parsers/tcx.py` | ✅ sets `altitude_source = "gps"` | | `bincio/extract/parsers/tcx.py` | ✅ sets `altitude_source = "gps"` |
| `bincio/extract/metrics.py` | ✅ hysteresis `_elevation()` with source-aware threshold | | `bincio/extract/metrics.py` | ✅ hysteresis `_elevation()` with source-aware threshold |
| `bincio/extract/dem.py` | ✅ `lookup_elevations()` + `recalculate_elevation()` | | `bincio/extract/dem.py` | ✅ `lookup_elevations()` + `recalculate_elevation()` (median+10m) + `recalculate_elevation_hysteresis()` |
| `bincio/serve/server.py` | ✅ `POST /api/activity/{id}/recalculate-elevation` | | `bincio/serve/server.py` | ✅ `POST /api/activity/{id}/recalculate-elevation/{dem\|hysteresis}` |
| `bincio/edit/server.py` | ✅ same endpoint (single-user) | | `bincio/edit/server.py` | ✅ same endpoints (single-user) |
| `site/src/components/EditDrawer.svelte` | ✅ "Recalculate from terrain map" button | | `site/src/components/EditDrawer.svelte` | ✅ two buttons: "Recalculate (hysteresis)" + "Recalculate (DEM)" |
| `tests/test_metrics.py` | ✅ 5 parametric tests | | `tests/test_metrics.py` | ✅ 5 parametric tests |
+14 -11
View File
@@ -73,20 +73,23 @@ Click **Edit** on any activity to:
Changes save instantly. The site rebuilds in the background. Changes save instantly. The site rebuilds in the background.
### Recalculating elevation from terrain data ### Recalculating elevation
If an activity shows an unrealistic elevation gain (common with GPS-only devices on flat If an activity shows an unrealistic elevation gain, the edit drawer has two buttons:
routes, or with older Garmin/Wahoo files), the edit drawer has a
**"Recalculate from terrain map (DEM)"** button.
Clicking it replaces the recorded GPS altitude with SRTM terrain data from the **📐 Recalculate (hysteresis)** — recomputes gain and loss from the original recorded
[Open-Elevation API](https://open-elevation.com) and recomputes the gain and loss. The elevation using a noise-filtering dead-band algorithm. Fast and offline — no network
elevation chart and the summary stats both update. This usually brings the numbers in call. Best for devices with a barometric altimeter (Garmin, Karoo, Wahoo) whose
line with what Strava or your device's app reports. elevation data is accurate but was extracted before the noise-filtering was improved.
> **Note:** The correction requires a GPS track (activities marked *No GPS* cannot be **⛰ Recalculate (DEM)** — replaces the recorded GPS altitude with SRTM terrain data
> corrected). The DEM has ~30 m horizontal resolution, so very short or indoor activities from the [Open-Elevation API](https://open-elevation.com) and recomputes gain and
> are not meaningfully improved. loss. The elevation chart and summary stats both update. Best for GPS-only devices
(no barometric sensor) where the recorded altitude is noisy.
> **Note:** Both corrections require a GPS track (activities marked *No GPS* cannot be
> corrected). The DEM option uses ~30 m resolution terrain data; very short or indoor
> activities see little improvement from DEM correction.
### Photo gallery ### Photo gallery