navigo
simply manipulate GPS/geospatial data — in rust
See CHANGELOG.md for release notes.
api / usage
location
A location is a GPS coordinate defined by a longitude, a latitude, and an altitude.
let location = Location ;
- distance to another location (km):
let distance: f64 = paris.calculate_distance_to;
- bearing to another location (degrees):
let bearing: f64 = paris.calculate_bearing_to;
- elevation change to another location:
let elevation: Elevation = paris.calculate_elevation_to;
// elevation.positive — gain in meters
// elevation.negative — loss in meters
- check if inside a bounding box:
let area = Area ;
let is_in: bool = location.is_in_area;
- check if inside a radius (km):
let is_in: bool = location.is_in_radius;
trace
Trace::new ingests raw GPS locations and precomputes everything in one shot:
- Douglas-Peucker simplification (for traces > 1 000 points, ε = 15 m)
- Cumulative distances
- Denoised cumulative elevation gain / loss (median smoothing + hysteresis)
- Smoothed slope at each point
- Peaks and valleys (AMPD algorithm with prominence filter)
- Qualifying climb segments (Garmin-style thresholds)
A Trace is never empty — construction fails with TraceError::EmptyTrace
if locations is empty, so every other method can assume at least one point.
let trace: = new;
// or via the convenience wrapper:
let trace: = build_trace;
precomputed fields
trace.locations // Vec<Location> — simplified working set
trace.cumulative_distances // Vec<f64> — km from start, [0] == 0.0
trace.cumulative_elevation_gains // Vec<f64> — denoised gain in meters
trace.cumulative_elevation_losses // Vec<f64> — denoised loss in meters
trace.slopes // Vec<f64> — % grade at each point
trace.peaks // Vec<usize> — indices of detected peaks
trace.valleys // Vec<usize> — indices of detected valleys
trace.climbs // Vec<ClimbStats> — qualifying climb segments
trace.total_distance // f64 — total distance in km
trace.total_elevation_gain // f64 — total denoised gain in meters
trace.total_elevation_loss // f64 — total denoised loss in meters
methods
- total length (km):
let length: f64 = trace.length; // alias for total_distance
- location at a cumulative distance (km):
let loc: = trace.point_at_distance;
- index at a cumulative distance (binary search):
let idx: usize = trace.index_at_distance;
- slice between two distance marks (km, both ends inclusive):
let section: = trace.slice_between_distances;
- closest location to a point (early-stop heuristic for loop courses):
let = trace.find_closest_point.unwrap;
// start search from a known index (e.g. to handle loop courses):
let result = trace.find_closest_point_from;
- bounding box (never fails — a trace always has at least one point):
let area: Area = trace.area;
- sub-section by index range (inclusive):
let section: = trace.get_section;
climb stats
Climbs are qualified using Garmin Climb Pro thresholds:
distance ≥ 500 m, average gradient ≥ 3 %, distance × gradient > 3 500 m·%
WebAssembly
The library can be compiled to WASM for use in web applications via the wasm feature.
Prebuilt bindings are published to npm as @totorototo/navigo — npm install @totorototo/navigo.
how it works
All data lives in WASM linear memory. The JS side holds a thin pointer (WasmTrace).
Only the boundaries cross the WASM↔JS membrane — scalars are free (registers), bulk arrays are copied once on demand.
buildTrace(Float64Array) ← one O(n) copy JS→WASM, null if no points
│
▼
WasmTrace stays in WASM memory
│
├── trace.total_distance → free (register)
├── trace.find_closest_point() → free (scalars in/out)
├── trace.locations_flat → one O(n) copy, cache it
└── trace.free() ← you must call this (no GC bridge)
build
usage
import init from "./navigo.js";
await ;
// build — one copy in, all computation in WASM
const pts = ;
const trace = ;
// → WasmTrace, or null if pts carries no points
// scalar getters — free
trace.; // number (km)
trace.; // number (m)
trace.; // number (m)
trace.; // number
// array getters — copy once, then cache on the JS side
const locs = trace.; // Float64Array [lon,lat,alt,…]
const dists = trace.; // Float64Array (km)
const gains = trace.; // Float64Array (m)
const losses = trace.; // Float64Array (m)
const slopes = trace.; // Float64Array (%)
const peaks = trace.; // Uint32Array (indices)
const valleys = trace.; // Uint32Array (indices)
// query methods — scalars in, one small object out
trace.;
// → { longitude, latitude, altitude } | undefined
trace.;
// → number
trace.;
// → { location: { longitude, latitude, altitude }, index, distance } | undefined
trace.;
// → same shape | undefined (use on live-tracking loops)
trace.;
// → Float64Array [lon,lat,alt,…] | undefined
trace.;
// → Float64Array [lon,lat,alt,…] (throws on out-of-bounds / invalid range)
trace.;
// → { min_longitude, max_longitude, min_latitude, max_latitude }
trace.;
// → { positive, negative } (raw, non-denoised)
trace.;
// → [{ start_index, end_index, start_dist_km, climb_dist_km,
// elevation_gain, summit_elev, avg_gradient }, …]
// always release when done — Rust allocator has no GC bridge
trace.;
memory management
WasmTrace lives in WASM linear memory. The JS object is just a pointer — Rust cannot reclaim it when the JS variable is GC'd. Always call .free(), or register a FinalizationRegistry:
const registry = ;
const trace = ;
registry.;