trails/openspec/changes/elevation-profile-hardening/proposal.md
Ullrich Schäfer 5dd4968626 docs+openspec: prior-art research (Organic Maps, Endurain, wanderer) and 15 proposals
Add docs/inspirations.md as the durable record of the 2026-07-05/06
prior-art research — per-project learnings with source paths, canonical
credit lines, and the changes each spawned — and extend the
acknowledgment lists in philosophy.md/architecture.md (Organic Maps,
Endurain, wanderer).

New OpenSpec changes (proposal/design/specs/tasks each):
- Organic Maps: elevation-profile-hardening, gpx-parser-robustness,
  hiking-time-estimate, poi-index, hiking-foot-profile
- Endurain: account-export, activity-duplicate-review,
  fit-parsing-hardening, activity-locations, self-hosting-guide,
  activity-privacy-controls
- wanderer: federation-hardening, link-share-tokens
- credits-page (user-visible acknowledgments)

Updated in-flight changes with wanderer prior-art sections:
route-federation, route-discovery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 07:23:00 +02:00

2.9 KiB
Raw Blame History

Why

Ascent/descent totals for imported and synced GPX are computed by summing every positive point-to-point elevation delta (packages/gpx/src/parse.ts computeElevation), with no noise handling. Real-world GPS/barometer traces are noisy, so the journal systematically overstates elevation gain — often by 2050% on long activities — and single-point altitude spikes distort the elevation profile chart. Organic Maps solved both problems with two small, well-tested algorithms (slope-outlier despiking + threshold-filtered accumulation) that we reviewed and can adapt to @trails-cool/gpx.

What Changes

  • Add spike removal to elevation processing: single points whose slopes to both neighbors exceed a maximum slope and have opposite signs (peak/pit spikes) are replaced by interpolation before any totals or series are computed.
  • Add threshold-filtered ascent/descent: accumulate gain/loss only when altitude deviates from a moving reference by more than a threshold (hysteresis), suppressing jitter. Both raw and filtered totals are computed; filtered is preferred, raw is the fallback when filtering removes everything (near-flat tracks).
  • Apply the same cleaned elevation data to the elevation profile series (elevationSeries) so the chart and the headline stats agree.
  • Apply filtered accumulation to per-day ascent/descent (compute-days.ts) so day totals stay consistent with the route total.
  • No change to planner routing stats: planned-route ascent continues to come from BRouter (totalAscend).
  • No backfill of stored elevationGain/elevationLoss on existing journal rows; values update when a GPX is re-parsed (new imports, syncs, edits). A backfill job is explicitly out of scope.

Capabilities

New Capabilities

  • elevation-computation: How elevation series, ascent/descent totals, and per-day elevation stats are computed from GPX track points in @trails-cool/gpx — despiking, threshold-filtered accumulation, raw fallback, and series consistency guarantees.

Modified Capabilities

Impact

  • packages/gpx/src/parse.tscomputeElevation gains despike + filtered accumulation.
  • packages/gpx/src/elevation-series.ts — series built from despiked points.
  • packages/gpx/src/compute-days.ts — cumulative ascent/descent arrays use the filtered accumulator.
  • New packages/gpx/src/elevation-clean.ts (despike + filter primitives) with co-located unit tests and noisy-track fixtures.
  • Consumers (journal gpx-save.server.ts, route/activity detail loaders, planner multi-day breakdown) get corrected numbers with no API change — GpxData["elevation"] shape stays { gain, loss, profile }.
  • No DB schema change, no migration, no API contract change.