diff --git a/.prettierrc b/.prettierrc index b2df5d9..35c3986 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,6 +4,7 @@ tabWidth: 4 trailingComma: es5 semi: true arrowParens: always +quoteProps: consistent overrides: - files: "*.json" diff --git a/CHANGELOG.md b/CHANGELOG.md index a04cecd..5a271c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Breaking Changes in 3.0.0 -- removed limitation to 8 decimal places in decimal coordinates +- removed artificial limitation to 8 decimal places in decimal coordinates - `sexagesimal2decimal` was renamed to `sexagesimalToDecimal` +- `getCenter()` is not returning the distance in addition to the center anymore diff --git a/src/constants.ts b/src/constants.ts index 28e0c11..97a2313 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,7 @@ import { LongitudeKeys, LatitudeKeys, AltitudeKeys } from './types'; export const sexagesimalPattern = /^([0-9]{1,3})°\s*([0-9]{1,3}(?:\.(?:[0-9]{1,2}))?)'\s*(([0-9]{1,3}(\.([0-9]{1,4}))?)"\s*)?([NEOSW]?)$/; -export const radius = 6378137; +export const earthRadius = 6378137; export const minLat = -90; export const maxLat = 90; export const minLon = -180; diff --git a/src/getCenter.test.js b/src/getCenter.test.js new file mode 100644 index 0000000..afee1bd --- /dev/null +++ b/src/getCenter.test.js @@ -0,0 +1,58 @@ +import getCenter from './getCenter'; + +const cities = { + Berlin: { + latitude: 52.518611, + longitude: 13.408056, + }, + Boston: { + latitude: 42.357778, + longitude: '71° 3\' 34" W', + }, + Dortmund: { + latitude: '51° 31\' 10.11" N', + longitude: '7° 28\' 01" E', + }, + London: { + latitude: "51° 31' N", + longitude: "0° 7' W", + }, + Manchester: { + latitude: "53° 29' N", + longitude: "2° 14' W", + }, + NewYorkCity: { + latitude: 40.715517, + longitude: -73.9991, + }, + SanFrancisco: { + latitude: 37.774514, + longitude: -122.418079, + }, + Sydney: [151.210046, -33.869085], + Moscow: { + latitude: 55.751667, + longitude: 37.617778, + }, +}; + +describe('getCenter', () => { + it('gets the center of two points', () => { + expect(getCenter([cities.Berlin, cities.Moscow])).toEqual({ + longitude: 25.0332388360222, + latitude: 54.74368339960522, + }); + expect(getCenter([cities.Sydney, cities.SanFrancisco])).toEqual({ + longitude: -166.9272249630353, + latitude: 2.6764932317022576, + }); + }); + + it('gets the center of multiple points', () => { + const values = Object.values(cities); + expect(getCenter(values)).toEqual({ + latitude: 65.41916196002177, + longitude: -28.01313266917171, + }); + }); +}); diff --git a/src/getCenter.ts b/src/getCenter.ts new file mode 100644 index 0000000..89ee2c2 --- /dev/null +++ b/src/getCenter.ts @@ -0,0 +1,39 @@ +import toRad from './toRad'; +import toDeg from './toDeg'; +import getLatitude from './getLatitude'; +import getLongitude from './getLongitude'; +import { GeolibInputCoordinates } from './types'; + +// Calculates the center of a collection of points +const getCenter = (points: GeolibInputCoordinates[]) => { + if (Array.isArray(points) === false || points.length === 0) { + return false; + } + + const numberOfPoints = points.length; + + const sum = points.reduce( + (acc, point) => { + const pointLat = toRad(getLatitude(point)); + const pointLon = toRad(getLongitude(point)); + + return { + X: acc.X + Math.cos(pointLat) * Math.cos(pointLon), + Y: acc.Y + Math.cos(pointLat) * Math.sin(pointLon), + Z: acc.Z + Math.sin(pointLat), + }; + }, + { X: 0, Y: 0, Z: 0 } + ); + + const X = sum.X / numberOfPoints; + const Y = sum.Y / numberOfPoints; + const Z = sum.Z / numberOfPoints; + + return { + longitude: toDeg(Math.atan2(Y, X)), + latitude: toDeg(Math.atan2(Z, Math.sqrt(X * X + Y * Y))), + }; +}; + +export default getCenter; diff --git a/src/getDistance.test.js b/src/getDistance.test.js new file mode 100644 index 0000000..0ace7f4 --- /dev/null +++ b/src/getDistance.test.js @@ -0,0 +1,31 @@ +import getDistance from './getDistance'; + +describe('getDistance', () => { + it('calculates the distance between any two points', () => { + expect( + getDistance( + { latitude: 52.518611, longitude: 13.408056 }, + { latitude: 51.519475, longitude: 7.46694444 } + ) + ).toEqual(421786); + + expect( + getDistance( + { latitude: 52.518611, longitude: 13.408056 }, + { latitude: 51.519475, longitude: 7.46694444 }, + 100 + ) + ).toEqual(421800); + + expect( + getDistance( + { latitude: 37.774514, longitude: -122.418079 }, + { latitude: 51.519475, longitude: 7.46694444 } + ) + ).toEqual(8967172); + + expect( + getDistance([-122.418079, 37.774514], [7.46694444, 51.519475]) + ).toEqual(8967172); + }); +}); diff --git a/src/getDistance.ts b/src/getDistance.ts new file mode 100644 index 0000000..c43abde --- /dev/null +++ b/src/getDistance.ts @@ -0,0 +1,34 @@ +import getLatitude from './getLatitude'; +import getLongitude from './getLongitude'; +import toRad from './toRad'; +import { earthRadius } from './constants'; +import { GeolibInputCoordinates } from './types'; + +// Calculates the distance between two points. +// This method is simple but also more inaccurate +const getDistance = ( + from: GeolibInputCoordinates, + to: GeolibInputCoordinates, + accuracy: number = 1 +) => { + accuracy = + typeof accuracy !== 'undefined' && !isNaN(accuracy) + ? Math.floor(accuracy) + : 1; + + const distance = Math.round( + Math.acos( + Math.sin(toRad(getLatitude(to))) * + Math.sin(toRad(getLatitude(from))) + + Math.cos(toRad(getLatitude(to))) * + Math.cos(toRad(getLatitude(from))) * + Math.cos( + toRad(getLongitude(from)) - toRad(getLongitude(to)) + ) + ) * earthRadius + ); + + return Math.floor(Math.round(distance / accuracy) * accuracy); +}; + +export default getDistance; diff --git a/src/getLatitude.ts b/src/getLatitude.ts index 221d09d..9106426 100644 --- a/src/getLatitude.ts +++ b/src/getLatitude.ts @@ -3,7 +3,7 @@ import { latitudeKeys } from './constants'; import getCoordinateKey from './getCoordinateKey'; import toDecimal from './toDecimal'; -const getLatitude = (point: GeolibInputCoordinates, raw: boolean) => { +const getLatitude = (point: GeolibInputCoordinates, raw?: boolean) => { const latKey = getCoordinateKey(point, latitudeKeys); if (typeof latKey === 'undefined' || latKey === null) { diff --git a/src/getLongitude.test.js b/src/getLongitude.test.js new file mode 100644 index 0000000..072d4a8 --- /dev/null +++ b/src/getLongitude.test.js @@ -0,0 +1,23 @@ +import getLongitude from './getLongitude'; + +describe('getLongitude', () => { + it('gets the longitude for a point', () => { + expect(getLongitude({ lng: 1 })).toEqual(1); + }); + + it('converts the longitude for a point to decimal', () => { + expect(getLongitude({ lng: "71° 0'" })).toEqual(71); + }); + + it('gets the longitude from a GeoJSON array', () => { + expect(getLongitude([1, 2])).toEqual(1); + }); + + it('does not convert to decimal if second parameter is set to true', () => { + expect(getLongitude({ lng: "71° 0'" }, true)).toEqual("71° 0'"); + }); + + it('gets the longitude from a GeoJSON array without conversion', () => { + expect(getLongitude(["71° 0'", "71° 0'"], true)).toEqual("71° 0'"); + }); +}); diff --git a/src/getLongitude.ts b/src/getLongitude.ts new file mode 100644 index 0000000..22c0601 --- /dev/null +++ b/src/getLongitude.ts @@ -0,0 +1,18 @@ +import { GeolibInputCoordinates, LongitudeKeys } from './types'; +import { longitudeKeys } from './constants'; +import getCoordinateKey from './getCoordinateKey'; +import toDecimal from './toDecimal'; + +const getLongitude = (point: GeolibInputCoordinates, raw?: boolean) => { + const latKey = getCoordinateKey(point, longitudeKeys); + + if (typeof latKey === 'undefined' || latKey === null) { + return; + } + + const value = point[latKey as keyof LongitudeKeys]; + + return raw === true ? value : toDecimal(value); +}; + +export default getLongitude;