3. Simplify the route off the JS thread
An async method that runs on the Lucent thread, and stops when JavaScript aborts it.
return 2 * EARTH_RADIUS_M * Math.asin(Math.sqrt(h));}
/** Meters from p to the line through a and b, on a flat map around a. */function offset(p: Fix, a: Fix, b: Fix): number { const scale = Math.cos(radians(a.latitude)) * EARTH_RADIUS_M; const x = (f: Fix) => radians(f.longitude - a.longitude) * scale; const y = (f: Fix) => radians(f.latitude - a.latitude) * EARTH_RADIUS_M; const length = Math.hypot(x(b), y(b)); if (length === 0) return Math.hypot(x(p), y(p)); return Math.abs(x(b) * y(p) - y(b) * x(p)) / length;}
/** Meters per second from a to b. */export function speed(a: Fix, b: Fix): number { const seconds = (b.time - a.time) / 1000;… const seconds = first && last ? (last.time - first.time) / 1000 : 0; return { fixes: this.fixes.length, meters, seconds, topSpeed }; }
/** The fixes that shape the route, within `tolerance` meters (Douglas–Peucker). */ async simplify(tolerance: number, signal: AbortSignal): Promise<Fix[]> { const fixes = this.fixes; if (fixes.length < 3) return [...fixes]; const keep = fixes.map((_, i) => i === 0 || i === fixes.length - 1); const spans: [number, number][] = [[0, fixes.length - 1]]; while (spans.length > 0) { signal.throwIfAborted(); const [first, last] = spans.pop()!; let farthest = -1; let farthestOffset = tolerance; for (let i = first + 1; i < last; i++) { const d = offset(fixes[i]!, fixes[first]!, fixes[last]!); if (d > farthestOffset) { farthest = i; farthestOffset = d; } } if (farthest >= 0) { keep[farthest] = true; spans.push([first, farthest], [farthest, last]); } } return fixes.filter((_, i) => keep[i] === true); }}import { useEffect, useState } from "react";import { Text } from "react-native";import { Trip } from "./src/trip.lucent";
…for (const fix of walk) trip.add(fix);
export default function App() { const [shape, setShape] = useState<number | null>(null);
useEffect(() => { const controller = new AbortController(); trip.simplify(100, controller.signal).then( (fixes) => setShape(fixes.length), () => {}, // aborted: the screen is gone ); return () => controller.abort(); }, []);
const summary = trip.summary(); return ( <Text> {summary.fixes} fixes, {(summary.meters / 1000).toFixed(2)} km, top speed{" "} {summary.topSpeed.toFixed(1)} m/s {summary.topSpeed.toFixed(1)} m/s. {shape === null ? " Simplifying…" : ` ${shape} fixes shape the route.`} </Text> );}simplify keeps the fixes that shape the route, and drops those within tolerance meters of a straight line. On a long trip that is slow work, so it's async: it runs on the Lucent thread, and the JS thread stays free.
The AbortSignal comes from an AbortController in JavaScript. When the screen goes away, controller.abort() sets the signal, and signal.throwIfAborted() stops the loop with an AbortError.
Run it
Section titled “Run it”The screen shows Simplifying… for a moment, then 4 fixes shape the route.: one fix sat within 100 meters of the line through its neighbors.