@verifyhash/geo-distance
v0.1.0
Published
Zero-dependency great-circle and WGS-84 ellipsoidal geodesy: haversine/Vincenty distance, bearings, destination point, midpoint, and unit conversions.
Maintainers
Readme
geo-distance
TODO (owner): pick the final npm name/scope before publishing. The npm package name is not finalized by this project. Convention used here:
package.jsonkeeps the working sluggeo-distanceas a placeholder — replace it (and thenpm installline below) with the real published name/scope before runningnpm publish.
A tiny, zero-dependency Node.js library for great-circle and ellipsoidal geodesy from latitude/longitude points. Compute distances, bearings, destination points, and midpoints, plus a handful of exact unit conversions.
No network, no I/O, no daemon, no external packages — just pure functions and
node:assert for the tests. Drop src/geo.js into any project.
What it is
Given two points on Earth expressed as { lat, lon } in decimal degrees,
geo-distance answers the everyday spherical-trig questions:
- How far apart are they? (spherical haversine, or ellipsoidal Vincenty)
- What compass bearing do I leave on, and what bearing do I arrive on?
- If I walk 343.5 km from London on bearing 148°, where do I end up?
- What's the half-way point of the route?
- How many miles / nautical miles is that?
Who it's for
Mapping, logistics, aviation/marine, and travel developers who need correct geodesy without pulling in a heavy GIS dependency — e.g. estimating delivery radii, sorting "nearest store" results, drawing a route's midpoint label, or converting a leg length from kilometres to nautical miles.
Two distance models — and when to use which
| Function | Model | Accuracy | Notes |
|---|---|---|---|
| haversineDistance | Sphere (mean radius 6 371 008.77 m) | ~0.3–0.5% vs the real ellipsoid | Fast, always converges, great for ranking/filtering |
| vincentyDistance | WGS-84 ellipsoid, iterative | sub-millimetre | Slower; does not converge for near-antipodal points |
Honest limits. The haversine model treats Earth as a perfect sphere, so it
can be off by a few tenths of a percent (up to ~0.5% on long north–south legs).
Vincenty's inverse formula is accurate to a fraction of a millimetre but is
iterative and fails to converge when the two points are almost exactly on
opposite sides of the planet — that case throws (see below) so you can fall
back to haversineDistance. Bearings, destinationPoint, and midpoint use
the spherical model, which is what you want for map labels and short/medium
legs; they are not ellipsoidal.
Install / use
There is nothing to install. Require the module directly:
const geo = require('./src/geo.js');
const london = { lat: 51.5074, lon: -0.1278 };
const paris = { lat: 48.8566, lon: 2.3522 };
geo.haversineDistance(london, paris); // => 343498... metres (~343.5 km)
geo.vincentyDistance(london, paris); // => 343923... metres (~343.9 km)
geo.initialBearing(london, paris); // => ~148.1 (degrees, clockwise from N)Worked example: fall back to haversine on antipodal failure
function robustDistance(a, b) {
try {
return geo.vincentyDistance(a, b); // sub-mm on the ellipsoid
} catch (e) {
if (e.code === 'VINCENTY_NO_CONVERGE') {
return geo.haversineDistance(a, b); // spherical fallback
}
throw e; // real error (bad input)
}
}Worked example: walk a bearing and convert units
const start = { lat: 40.7128, lon: -74.0060 }; // New York
const dest = geo.destinationPoint(start, 90, 100_000); // 100 km due east
// dest => { lat: ~40.71, lon: ~-72.82 }
const km = geo.haversineDistance(start, dest) / 1000; // ~100
geo.units.kmToNauticalMiles(km); // ~54.0 nmAPI
Points are { lat, lon } in decimal degrees, lat ∈ [-90, 90],
lon ∈ [-180, 180]. Every function validates its inputs and throws a
TypeError (wrong type / missing field) or RangeError (out of range).
Distances are metres; bearings are degrees in [0, 360) measured
clockwise from true north.
haversineDistance(a, b)→number(metres). Spherical great-circle distance. Coincident points return0.vincentyDistance(a, b, [opts])→number(metres). WGS-84 ellipsoidal distance via Vincenty's inverse formula.opts.maxIterations(default 1000) andopts.tolerance(default 1e-12) tune convergence. Throws anErrorwith.code === 'VINCENTY_NO_CONVERGE'for near-antipodal inputs.initialBearing(a, b)→number(degrees). Forward azimuth leavinga.finalBearing(a, b)→number(degrees). Direction of travel on arrival atb.destinationPoint(a, bearingDeg, distanceM)→{ lat, lon }. Where you arrive travellingdistanceMmetres fromaonbearingDeg(spherical).midpoint(a, b)→{ lat, lon }. Great-circle half-way point.validatePoint(p, [label])→p. Throws on invalid input; returns the point otherwise. Useful for validating your own data.units— exact conversions (international mile = 1609.344 m, nautical mile = 1852 m):kmToMiles,milesToKm,kmToNauticalMiles,nauticalMilesToKm,metresToKm,metresToMiles,metresToNauticalMiles.constants—WGS84_A,WGS84_B,WGS84_F,EARTH_RADIUS_M,METRES_PER_KM,METRES_PER_MILE,METRES_PER_NAUTICAL_MILE.
Running the tests
One command, no dependencies or test runner required:
node test/geo.test.jsThe suite uses node:assert with known-city fixtures and stated tolerances:
London→Paris ≈ 343.5 km (haversine) / 343.9 km (Vincenty), NYC→LA ≈ 3936 km,
London→Sydney ≈ 16 990 km, plus equator/pole edge cases, the antipodal
non-convergence path, bearing round-trips through destinationPoint, midpoint
equidistance, and unit-conversion round-trips. It prints an ok line per check
and a final assertion count, exiting non-zero on the first failure.
Status
This is an incubator project and an npm-graduation candidate — it is not published to any registry. Publishing is a separate, human-approved step.
License
MIT.
Install
Placeholder name: the
geo-distancebelow is the working slug, not a finalized npm name — see the owner TODO near the top of this README.
npm install geo-distanceconst geo = require('geo-distance');
const london = { lat: 51.5074, lon: -0.1278 };
const paris = { lat: 48.8566, lon: 2.3522 };
geo.haversineDistance(london, paris); // ~343498 metres (~343.5 km)
geo.initialBearing(london, paris); // ~148.1 degrees (clockwise from N)