navigo 0.9.0

GPS/geospatial data for Rust — trace analysis, GPX parsing, Minetti pace model, race route analysis (legs/sections/stages), live calibration.
Documentation
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **Breaking (Rust):** `Trace` fields are now `pub(crate)` — access via public
  accessor methods (`locations()`, `cumulative_distances()`, `slopes()`,
  `peaks()`, `valleys()`, `climbs()`, `total_distance()`,
  `total_elevation_gain()`, `total_elevation_loss()`, `area()`, `elevation()`).
- **Breaking (Rust):** `trace.area()` returns `&Area` (was owned `Area`);
  `trace.elevation()` returns `&Elevation`.
- **Breaking (Rust):** `trace.get_section()` returns `Result<&[Location], _>`
  (was `Result<Vec<Location>, _>`).
- **Breaking (Rust):** `section::compute_from_waypoints` and
  `stage::compute_from_waypoints` now take `&AnalysisOptions` instead of
  separate `base_pace`, `k_fatigue`, `life_base_stop_s`, `&WeatherLookup`
  parameters.
- **Breaking (Rust):** `calibration::recalibrate_from_current` takes
  `&AnalysisOptions` as last param (was 4 separate args).
- **Breaking (Rust):** `segment::compute` takes `&SegmentParams` +
  `&mut SegmentState` instead of 6 loose parameters.
- **Breaking (WASM):** Array getters on `Trace` are now explicit methods
  (`getLocationsFlat()`, `getCumulativeDistances()`, `getSlopes()`, etc.)
  instead of property-style getters.
- **Breaking (WASM):** `parseGpx` now only parses `<trkpt>` (lean path);
  use `parseWaypoints`/`parseMetadata` separately, or `analyzeGpx` for the
  full analysis.

### Added

- **`AnalysisOptions`** builder struct: `AnalysisOptions::default().base_pace(500.0).fatigue(0.002).life_base_stop(3600)`.
- **`SegmentParams` / `SegmentState`** structs for the segment computation loop.
- **`interval` module** (crate-internal): shared template for section/stage
  computation — eliminates ~280 lines of duplicated code.
- **WASM `parseWaypoints(bytes)`**: parse only `<wpt>` elements.
- **WASM `parseMetadata(bytes)`**: parse only the `<metadata>` block.
- **WASM `parseGpxAll(bytes)`**: parses track-points, waypoints and metadata
  in one call and returns a `Trace` ready for `.analyze()` / `.recalibrate()`
  — fills the gap left by `parseGpx` going lean (it only loads track-points,
  so its `Trace` has no waypoints for those two methods to use).
- **WASM console warnings**: `from_value`/`to_value` errors now log to
  `console.warn` instead of silently returning `null`.
- **Integration tests** (`tests/integration.rs`): 22 tests covering GPX
  parsing, trace construction, race analysis, calibration, edge cases, and
  the real GPX fixture end-to-end.
- Module-level `//!` documentation on `location.rs`, `error.rs`, `leg.rs`.

### Performance

- `minetti::cmet`: Horner's method polynomial via `mul_add` chain.
- `minetti::pace_factor`: multiply by precomputed `INV_CMET_FLAT` instead of dividing.
- `gpx::parse_trace_points` / `parse_all`: `Vec::with_capacity(bytes.len() / 100)`.
- `trace.rs`: single-pass cumulative gain/loss mapping with `with_capacity`.
- `elevation.rs`: `copy_from_slice` on pre-extracted `alt_cache` in median smoother.
- `extrema.rs`: `.into()` instead of `.to_vec()` when prominence ≤ 0.

## [0.7.0] - 2026-06-28

### Changed

- **Breaking (WASM):** All JS-facing property and method names are now camelCase.

## [0.6.1] - 2026-06-28

### Changed

- JS property names camelCased (non-breaking re-release of 0.6.0 fixes).

## [0.6.0] - 2026-06-27

### Added

- **WASM `Trace::recalibrate(options)`**: live ETA recalibration via the
  calibration module, exposed as a method on the JS `Trace` class.

## [0.5.0] - 2026-06-27

### Changed

- **Breaking (WASM):** `parseGpxFull(bytes, basePaceSPerKm, kFatigue, lifeBaseStopS)`
  is replaced by `analyzeGpx(bytes, options)` and `trace.analyze(options)`
  — a single options object instead of positional args, so adding parameters
  no longer risks transposed call sites.
- **Breaking (WASM):** `parseGpx` now stores the parsed waypoints and metadata
  inside the returned `Trace` (previously only track-points). This is what
  makes `trace.analyze()` possible without re-sending the GPX bytes.
- **Breaking (WASM):** the shared `IntervalStats` result shape is split into
  `SectionStats` (has `stage_idx`) and `StageStats` (no stage index) — sections
  and stages no longer share one DTO with a field that was meaningless on one
  side.

### Added

- **WASM `trace.analyze(options)`**: method on `Trace` for the race
  analysis (waypoints, legs, sections, stages) — pure opaque-handle-in,
  JSON-out, no bytes cross the boundary a second time and the
  simplification/elevation/climb pass is never repeated. Lives alongside
  `Trace`'s other methods (`.climbs()`, `.area()`, …) instead of as a
  free function, for discoverability and consistency.
- **WASM `analyzeGpx(bytes, options)`**: one-call convenience wrapper around
  `parseGpx` + `trace.analyze()` for callers who just want the JSON result.
- `options.weather`: optional per-checkpoint forecast
  (`{ name, temperatureC, humidityPct, windKmh, precipProbPct }`) now actually
  reaches the pace model — previously `parseGpxFull` always used neutral
  weather with no way to override it from JS.
- WASM pipeline tests (`wasm::pipeline_tests`): parse an embedded GPX fixture
  through `parse_gpx``compute_route_analysis`, asserting on the resulting
  legs/sections/stages — previously this path was only checked manually via
  the demo. Test suite now 144 tests (was 141).

## [0.4.0] - 2026-06-25

### Added

- **GPX parser** (`gpx` module): zero-dependency byte-scanning parser —
  `parse_trace_points`, `parse_waypoints`, `parse_metadata` / `GpxMetadata`.
- **`Waypoint`** struct with `is_section_boundary()` (any typed waypoint) and
  `is_stage_boundary()` (Start / LifeBase / Arrival only) classification.
- **`parse_iso8601_to_epoch`** (`time` module): ISO 8601 string → Unix seconds,
  handles `Z` and `±HH:MM` offsets.
- **Minetti 2002 pace model** (`minetti` module): `cmet(slope)` — 5th-degree
  polynomial metabolic cost (J/(kg·m)), `pace_factor(slope)`, `CMET_FLAT`.
- **Extended pace model** (`pace_model` module): `fatigue_factor` (exponential
  decay with constant `K_FATIGUE`), `circadian_factor` (cosine model, −15% at
  03:30 UTC), `WeatherLookup` / `WeatherConditions` for per-checkpoint weather
  corrections (thermal, wind, precipitation).
- **`SegmentMetrics` / `compute`** (`segment` module): min/max elevation,
  max slope, weighted time and distance for any index range of a trace.
- **`LegStats` / `compute_from_waypoints`** (`leg` module): Naismith-based
  per-leg analysis — total distance, gain/loss, bearing, difficulty (1–5),
  estimated duration.
- **`SectionStats` / `compute_from_waypoints`** (`section` module):
  pace-modelled section analysis — `pace_factor`, `max_completion_time`,
  `cutoff_ratio`, `stop_duration`.
- **`StageStats` / `compute_from_waypoints`** (`stage` module): same metrics
  grouped by Start / LifeBase / Arrival boundaries.
- **`Recalibration` / `recalibrate_from_current`** (`calibration` module): live
  ETA recalibration from actual vs predicted elapsed time; factor clamped to
  `[0.5, 3.0]`, gated on ≥ 300 s predicted to avoid early noise.
- **WASM `parseGpx(Uint8Array)`**: parse GPX bytes directly into a `WasmTrace`.
- **WASM `parseGpxFull(bytes, basePace, kFatigue, lifeBaseStop)`**: one-call
  full analysis returning `{ trace, waypoints, legs, sections, stages, metadata }`
  as a plain JS object.
- **Demo**: updated to load a real GPX file (GRP 2026 Ultra Solo, 174.7 km,
  15 checkpoints) via `fetch`, displaying an elevation profile, Checkpoints,
  Sections and Stages tables, all driven by the Minetti pace model.
- Test suite expanded to 141 tests (was 42), 0 warnings.

## [0.3.1] - 2026-06-24

### Added

- Published WASM bindings to npm as
  [`@totorototo/navigo`]https://www.npmjs.com/package/@totorototo/navigo,
  built for both the `bundler` (Vite/webpack, zero-config) and `web`
  (plain ESM, manual `init()`) wasm-pack targets.
- CI: tagged releases now publish to npm via Trusted Publishing (OIDC) —
  no stored npm token — alongside the existing crates.io publish.

## [0.3.0] - 2026-06-23

### Changed

- **Breaking:** `Trace` can no longer be empty. `Trace::new` and `build_trace` now
  return `Result<Trace, TraceError>`, rejecting empty input instead of silently
  producing a zeroed-out trace.
- **Breaking:** `Trace::area()` is now infallible and returns `Area` directly
  (previously `Result<Area, &str>`).
- **Breaking:** `Trace::get_section()` now returns `Result<Vec<Location>, TraceError>`
  instead of `Result<Vec<Location>, &str>`.
- The WASM `buildTrace()` binding now returns `WasmTrace | null` (`null` on empty
  input) instead of always constructing a trace.

### Added

- `TraceError` enum (`EmptyTrace`, `InvalidRange`, `IndexOutOfBounds`) implementing
  `std::error::Error`, replacing stringly-typed errors.
- `LICENSE` file (MIT), matching the `license` field already declared in `Cargo.toml`.

## [0.2.0] - 2026-06-23

### Added

- WebAssembly bindings (`wasm` feature): `build_wasm_trace` / `buildTrace`, with a
  `WasmTrace` class exposing scalar/array getters and query methods.
- Demo web app (Vite) under `demo/` showcasing the WASM bindings.
- CI: WASM build coverage and code coverage gating.

## [0.1.0] - 2026-06-23

### Added

- Initial release: `Location`, `Trace`, `Area`, `Elevation` / `GainLoss` types.
- Distance, bearing, elevation-change, area/radius containment helpers on `Location`.
- `Trace::new` precomputing Douglas-Peucker simplification, cumulative distances,
  denoised elevation gain/loss, smoothed slopes, peak/valley detection (AMPD), and
  Garmin-style climb segment detection.
- CI pipeline (build, clippy, fmt, tests) and crates.io publish workflow.

[Unreleased]: https://github.com/totorototo/navigo/compare/v0.7.0...HEAD
[0.7.0]: https://github.com/totorototo/navigo/compare/v0.6.1...v0.7.0
[0.6.1]: https://github.com/totorototo/navigo/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/totorototo/navigo/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/totorototo/navigo/compare/v0.4.4...v0.5.0
[0.4.0]: https://github.com/totorototo/navigo/compare/v0.3.1...v0.4.0
[0.3.1]: https://github.com/totorototo/navigo/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/totorototo/navigo/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/totorototo/navigo/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/totorototo/navigo/releases/tag/v0.1.0