feat: DEM-based elevation recalculation via edit drawer button
Adds a "Recalculate from terrain map (DEM)" button to the activity edit
drawer. On click it queries an Open-Elevation-compatible API to replace
GPS altitude with SRTM terrain data, applies 5m hysteresis, and updates
the activity's elevation stats and timeseries chart in place.
- bincio/extract/dem.py: lookup_elevations() (batched HTTP POST) +
recalculate_elevation() (subsample → DEM → interpolate → hysteresis →
patch activity JSON, timeseries JSON, index.json)
- POST /api/activity/{id}/recalculate-elevation on both serve and edit
servers; serve endpoint is auth-gated and triggers merge + rebuild
- --dem-url flag (also DEM_URL env var) on bincio serve and bincio edit;
logged at startup; missing URL returns a clear 503 with setup instructions
- /api/me response gains dem_configured bool
- EditDrawer: button with loading state, shows new ↑/↓ values on success
This commit is contained in:
@@ -24,6 +24,11 @@
|
||||
let confirmDelete = false;
|
||||
let deleting = false;
|
||||
|
||||
// Elevation recalculation from DEM
|
||||
let recalculating = false;
|
||||
let recalcStatus = '';
|
||||
let recalcOk = false;
|
||||
|
||||
// Form state
|
||||
let title = '';
|
||||
let sport: Sport = 'cycling';
|
||||
@@ -119,6 +124,26 @@
|
||||
: [...hideStats, key];
|
||||
}
|
||||
|
||||
async function recalculateElevation() {
|
||||
recalculating = true;
|
||||
recalcStatus = '';
|
||||
recalcOk = false;
|
||||
try {
|
||||
const res = await fetch(`${api}/recalculate-elevation`, { method: 'POST' });
|
||||
const d = await res.json();
|
||||
if (!res.ok) throw new Error(d.detail ?? await res.text());
|
||||
recalcOk = true;
|
||||
const gain = d.elevation_gain_m != null ? `↑ ${Math.round(d.elevation_gain_m)} m` : '';
|
||||
const loss = d.elevation_loss_m != null ? `↓ ${Math.round(d.elevation_loss_m)} m` : '';
|
||||
recalcStatus = [gain, loss].filter(Boolean).join(' ');
|
||||
} catch (e: any) {
|
||||
recalcStatus = e.message;
|
||||
recalcOk = false;
|
||||
} finally {
|
||||
recalculating = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteActivity() {
|
||||
if (!confirmDelete) { confirmDelete = true; return; }
|
||||
deleting = true;
|
||||
@@ -264,6 +289,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Elevation recalculation -->
|
||||
<div class="mb-4">
|
||||
<p class="text-xs text-zinc-500 mb-2">Elevation</p>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg border border-zinc-700 text-xs text-zinc-400 hover:border-zinc-500 hover:text-zinc-200 transition-colors disabled:opacity-40"
|
||||
disabled={recalculating}
|
||||
on:click={recalculateElevation}
|
||||
>
|
||||
{recalculating ? 'Querying terrain data…' : '⛰ Recalculate from terrain map (DEM)'}
|
||||
</button>
|
||||
{#if recalcStatus}
|
||||
<p class="text-xs mt-1.5 text-center" class:text-green-400={recalcOk} class:text-red-400={!recalcOk}>
|
||||
{recalcStatus}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Flags -->
|
||||
<div class="flex gap-3 mb-2">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user