Tiny, typed browser GPS speed and movement detection.
npm install browser-car-speedbrowser-car-speed reads the standard Geolocation API, prefers the browser's
reported coords.speed, and uses a guarded rolling-coordinate estimate when
that value is missing or stuck at zero. It has no runtime dependencies and does
nothing until you call start().
Live example: Speedo · Simulated demo without location
import {
start,
stop,
subscribe,
getCarSpeed,
isCarMoving,
} from "browser-car-speed";
const unsubscribe = subscribe((snapshot) => {
console.log(snapshot.status); // "locating" | "live" | ...
console.log(snapshot.reading?.kilometersPerHour);
});
// Call from a click/tap because the browser may show a location prompt.
startButton.addEventListener("click", start);
getCarSpeed(); // km/h: number | null
getCarSpeed("mph"); // mph: number | null
isCarMoving(); // boolean | null
unsubscribe();
stop();null always means unknown or unavailable. It is never silently converted to
zero. This distinction matters when GPS accuracy is too weak to prove that the
device is stationary.
Use createCarSpeed() when an app needs custom thresholds, an injected adapter,
or more than one tracker.
import { createCarSpeed } from "browser-car-speed";
const car = createCarSpeed({
movingThresholdKmh: 4,
stationaryThresholdKmh: 2,
staleAfterMs: 8_000,
});
const unsubscribe = car.subscribe(({ status, reading, moving }) => {
if (status !== "live") return;
console.log(reading.kilometersPerHour, moving);
});
car.start();
// Later:
unsubscribe();
car.stop();start()/startCarSpeed()— begin high-accuracy location tracking.stop()/stopCarSpeed()— clear the watcher, timer, and retained state.getCarSpeed(unit?)— current full-precision speed in"km/h"(default),"mph", or"m/s";nullwhen unavailable or stale.isCarMoving()— hysteresis-basedtrueorfalse;nullwhen unknown.getSnapshot()/getCarSpeedSnapshot()— current state and metadata.subscribe(listener, options?)/subscribeCarSpeed(...)— state updates; returns an idempotent unsubscribe function.carSpeed— the shared tracker used by these functions.
createCarSpeed(options?) returns:
interface CarSpeedTracker {
start(): void;
stop(): void;
getCarSpeed(unit?: "km/h" | "mph" | "m/s"): number | null;
isCarMoving(): boolean | null;
getSnapshot(): CarSpeedSnapshot;
subscribe(listener: CarSpeedListener, options?: { emitCurrent?: boolean }): () => void;
}interface CarSpeedSnapshot {
status: "idle" | "locating" | "live" | "stale" | "error" | "unsupported";
reading: SpeedReading | null; // fresh reading only
lastReading: SpeedReading | null; // retained while stale
moving: boolean | null;
error: CarSpeedError | null;
running: boolean;
supported: boolean;
accuracyMeters: number | null;
lastPositionTimestamp: number | null;
}
interface SpeedReading {
metersPerSecond: number;
kilometersPerHour: number;
milesPerHour: number;
source: "sensor" | "calculated";
accuracyMeters: number | null;
timestamp: number;
}Snapshots and readings are immutable.
- Displacement inside the reported accuracy radius remains unknown, not stationary.
- A rolling window preserves evidence of slow movement instead of repeatedly resetting the baseline.
- Two consistent, progressing coordinate estimates are required before fallback speed is accepted.
- A fixed GPS jump is rejected because its displacement does not continue.
- Old smoothing samples are reset after a stale gap.
- A browser-provided zero can be overridden by sustained coordinate evidence.
- Separate enter/exit thresholds keep movement state from flickering near zero.
This package estimates whether the browser/device is moving. It does not read Tesla CAN data, vehicle telemetry, wheel speed, or a calibrated dashboard sensor. Results are indicative and depend on the browser's Geolocation implementation, GPS conditions, hardware, permissions, and software version.
- Use HTTPS (or localhost); browsers normally restrict Geolocation on insecure origins.
- Ask for location from a user gesture where possible.
- Do not use this package as a safety system or a replacement for the vehicle's speedometer.
- Tesla fullscreen/Theater workarounds belong in the application layer and are deliberately not included here.
import { convertSpeed, haversineMeters } from "browser-car-speed";
convertSpeed(100, "km/h", "mph");
haversineMeters(41.0082, 28.9784, 41.0151, 28.9795);npm install
npm run verifyThe verification suite type-checks and builds the package, runs behavior tests, checks package exports/types, inspects the tarball allow-list, and installs the packed tarball into a clean temporary project.
MIT
