Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
.DS_Store
npm-debug.log
/.idea/
.idea/
dist/
65 changes: 39 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,50 @@
# Haversine

A simple haversine formula module for Node.js

## Installation
`$ npm install haversine`

## Usage
### haversine (start, end, options)

const haversine = require('haversine')

const start = {
latitude: 30.849635,
longitude: -83.24559
}
`npm install haversine` or `yarn add haversine`

const end = {
latitude: 27.950575,
longitude: -82.457178
}

console.log(haversine(start, end))
console.log(haversine(start, end, {unit: 'mile'}))
console.log(haversine(start, end, {unit: 'meter'}))
console.log(haversine(start, end, {threshold: 1}))
console.log(haversine(start, end, {threshold: 1, unit: 'mile'}))
console.log(haversine(start, end, {threshold: 1, unit: 'meter'}))
## Usage

### haversine (start, end, options)

#### api
- `options.unit` - Unit of measurement applied to result (default `km`, available `km, mile, meter, nmi`)
- `options.threshold` - If passed, will result in library returning `boolean` value of whether or not the start and end points are within that supplied threshold. (default `null`)
- `options.format` - The format of start and end coordinate arguments. See table below for available values. (default `null`)
```js
const haversine = require('haversine');
// import haversine from 'haversine';

const start = {
latitude: 30.849635,
longitude: -83.24559
};

const end = {
latitude: 27.950575,
longitude: -82.457178
};

console.log(haversine(start, end)); // unit: 'km'
// 331.281531778995
console.log(haversine(start, end, { unit: 'mile' }));
// 205.91349330479048
console.log(haversine(start, end, { unit: 'meter' }));
// 331281.531778995
console.log(haversine(start, end, { threshold: 1 }));
// false
console.log(haversine(start, end, { threshold: 1, unit: 'mile' }));
// false
console.log(haversine(start, end, { threshold: 1, unit: 'meter' }));
// false
```

#### API Reference

- `options.unit = 'km'` - Unit of measurement applied to result { `km` for kilometer, `mile`, `meter`, `nmi` for nautical mile }
- if invalid, the function will throw a TypeError
- `options.threshold = undefined` - If a number is provided, the funciton will return a boolean indicating if the two points are within this distance
- `options.format = undefined` - The format of start and end coordinate arguments. See table below for available values.
- if invalid, the function will throw a TypeError

| Format | Example
| ------------- |--------------------------|
Expand All @@ -41,5 +55,4 @@ A simple haversine formula module for Node.js
| `{lat,lng}` | `{ lat: 30.849635, lng: -83.24559 }`
| `geojson` | `{ type: 'Feature', geometry: { coordinates: [-83.24559, 30.849635] } }`


[MIT License](http://opensource.org/licenses/MIT)
62 changes: 0 additions & 62 deletions haversine.js

This file was deleted.

22 changes: 17 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
{
"name": "haversine",
"version": "1.1.1",
"version": "2.0.0",
"description": "A simple haversine module",
"main": "haversine.js",
"main": "dist/src/haversine.js",
"types": "dist/src/haversine.d.ts",
"directories": {
"test": "test"
},
"scripts": {
"test": "mocha test/test.js --ui tdd"
"build": "tsc",
"prepack": "tsc",
"prepublish": "npm run build",
"test": "ts-node node_modules/mocha/bin/mocha test/ts/test.ts --ui tdd"
},
"repository": {
"type": "git",
Expand All @@ -20,8 +24,16 @@
"geolocation"
],
"author": "Nick Justice (niix)",
"maintainers": [
"Elijah Lopez"
],
"license": "MIT",
"devDependencies": {
"mocha": "*"
}
"@types/mocha": "^10.0.10",
"@types/node": "^22.13.8",
"mocha": "*",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
},
"packageManager": "[email protected]+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
}
117 changes: 117 additions & 0 deletions src/haversine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
export type Unit = 'km' | 'mile' | 'meter' | 'nmi';

export type CoordinateFormat = '[lat,lon]' | '[lon,lat]' | '{lon,lat}' | '{lat,lng}' | 'geojson' | undefined;

interface StandardCoordinates {
latitude: number;
longitude: number;
}

interface LatLonCoordinates {
lat: number;
lon: number;
}

interface LatLngCoordinates {
lat: number;
lng: number;
}

interface GeoJsonCoordinates {
geometry: {
coordinates: [number, number];
};
}

type CoordinateInput =
| StandardCoordinates
| [number, number]
| LatLonCoordinates
| LatLngCoordinates
| GeoJsonCoordinates;

interface HaversineOptions {
unit?: Unit;
format?: CoordinateFormat;
}

const RADII: Record<Unit, number> = {
km: 6371, // kilometer
mile: 3960, // mile
meter: 6371000, // meter
nmi: 3440 // nautical mile
};

// num: coordinate difference
function convertToRadian(num: number): number {
return num * Math.PI / 180;
}

// convert coordinates to standard format based on the passed format option
function convertCoordinates(format: CoordinateFormat, coordinates: CoordinateInput): StandardCoordinates {
switch (format) {
case '[lat,lon]':
const latLon = coordinates as [number, number];
return { latitude: latLon[0], longitude: latLon[1] };
case '[lon,lat]':
const lonLat = coordinates as [number, number];
return { latitude: lonLat[1], longitude: lonLat[0] };
case '{lon,lat}':
const latLonObj = coordinates as LatLonCoordinates;
return { latitude: latLonObj.lat, longitude: latLonObj.lon };
case '{lat,lng}':
const latLngObj = coordinates as LatLngCoordinates;
return { latitude: latLngObj.lat, longitude: latLngObj.lng };
case 'geojson':
const geoJson = coordinates as GeoJsonCoordinates;
return { latitude: geoJson.geometry.coordinates[1], longitude: geoJson.geometry.coordinates[0] };
case undefined:
return coordinates as StandardCoordinates;
default:
throw new TypeError(`Invalid format provided. Got ${JSON.stringify(format)}`);
}
}

/**
* Calculate the haversine distance between two points.
* @param startCoordinates Starting coordinates in the format provided by `format`
* @param endCoordinates Ending coordinates in the format provided by `format`
* @param options Options object with unit, threshold, and format
* @returns Distance apart
*/
export default function haversine(
startCoordinates: CoordinateInput,
endCoordinates: CoordinateInput,
{ unit = 'km', format }: HaversineOptions = {}
): number {
if (!(unit in RADII)) throw new TypeError(`Invalid unit provided to haversine. Got ${unit}`);

const R = RADII[unit];

let start: StandardCoordinates;
let end: StandardCoordinates;

try {
start = convertCoordinates(format, startCoordinates);
end = convertCoordinates(format, endCoordinates);
} catch (e) {
throw e;
}

const dLat = convertToRadian(end.latitude - start.latitude);
const dLon = convertToRadian(end.longitude - start.longitude);
const lat1 = convertToRadian(start.latitude);
const lat2 = convertToRadian(end.latitude);

const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

return R * c;
}

/**
* Returns `true` if the distance between two coordinates is strictly less than the threshold
*/
export function haversineIsWithin(startCoordinates: CoordinateInput, endCoordinates: CoordinateInput, threshold: number, { unit = 'km', format }: HaversineOptions = {}) {
return threshold > haversine(startCoordinates, endCoordinates, { unit, format });
}
82 changes: 0 additions & 82 deletions test/test.js

This file was deleted.

Loading