From 3f3f203c8442e462f41273cc514c18c6d4079a86 Mon Sep 17 00:00:00 2001 From: James Beard Date: Sat, 26 Aug 2023 13:01:24 +1000 Subject: [PATCH 1/3] Allow user to pass an optional ellipsoid definition to distance(), and if they do perform an ellipsoidal distance calculation rather than the default great circle. --- packages/turf-distance/index.ts | 103 +++++++- packages/turf-distance/package.json | 2 + packages/turf-distance/test.js | 40 +++- packages/turf-helpers/index.ts | 13 ++ packages/turf-helpers/package.json | 2 + packages/turf-length/index.ts | 3 +- packages/turf-length/test.js | 348 ++++++++++++++++++++++++++++ yarn.lock | 10 + 8 files changed, 516 insertions(+), 5 deletions(-) diff --git a/packages/turf-distance/index.ts b/packages/turf-distance/index.ts index 7f8176578a..dfee90be94 100644 --- a/packages/turf-distance/index.ts +++ b/packages/turf-distance/index.ts @@ -1,13 +1,61 @@ import { Point } from "geojson"; import { getCoord } from "@turf/invariant"; -import { radiansToLength, degreesToRadians, Coord, Units } from "@turf/helpers"; +import { + radiansToLength, + degreesToRadians, + datums, + Coord, + Datum, + Units, + convertLength, +} from "@turf/helpers"; +const LatLon = require("geodesy").LatLonEllipsoidal; //http://en.wikipedia.org/wiki/Haversine_formula //http://www.movable-type.co.uk/scripts/latlong.html +/** + * Calculates the distance between two {@link Point|points}. + * If a specific datum is passed, uses a geodesic ellipsoid calculation. + * If no datum is passed, performs a great circle calculation. + * + * @name distance + * @param {Coord | Point} from origin point or coordinate + * @param {Coord | Point} to destination point or coordinate + * @param {Object} [options={}] Optional parameters + * @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers + * @param {Datum} [options.datum=datums.WGS84] datum listed in {@link Helpers} + * @returns {number} distance between the two points in chosen units + * @example + * var from = turf.point([-75.343, 39.984]); + * var to = turf.point([-75.534, 39.123]); + * + * var options = {units: 'miles'}; + * var distance = turf.distance(from, to, options); + * + * //addToMap + * var addToMap = [from, to]; + * from.properties.distance = distance; + * to.properties.distance = distance; + */ +function distance( + from: Coord | Point, + to: Coord | Point, + options: { + units?: Units; + datum?: Datum; + } = {} +) { + if (options?.datum) { + return geodesic_ellipsoid_distance(from, to, options); + } else { + return great_circle_distance(from, to, options); + } +} + /** * Calculates the distance between two {@link Point|points} in degrees, radians, miles, or kilometers. - * This uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature. + * Performs a great circle calculation using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature. * * @name distance * @param {Coord | Point} from origin point or coordinate @@ -27,7 +75,7 @@ import { radiansToLength, degreesToRadians, Coord, Units } from "@turf/helpers"; * from.properties.distance = distance; * to.properties.distance = distance; */ -function distance( +function great_circle_distance( from: Coord | Point, to: Coord | Point, options: { @@ -51,4 +99,53 @@ function distance( ); } +/** + * Calculates the distance between two {@link Point|points}. + * Performs a geodesic ellipsoid calculation using [Vincenty's formulae](https://en.wikipedia.org/wiki/Vincenty%27s_formulae) to account for speroidal curvature. + * + * @name geodesic_ellipsoid_distance + * @param {Coord | Point} from origin point or coordinate + * @param {Coord | Point} to destination point or coordinate + * @param {Object} [options={}] Optional parameters + * @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers + * @param {Datum} [options.datum=datums.WGS84] datum listed in {@link Helpers} + * @returns {number} distance between the two points in chosen units + * @example + * var from = turf.point([-75.343, 39.984]); + * var to = turf.point([-75.534, 39.123]); + * var options = {units: 'miles', datum: datums.WGS84}; + * + * var distance = turf.distance(from, to, options); + * + * //addToMap + * var addToMap = [from, to]; + * from.properties.distance = distance; + * to.properties.distance = distance; + */ +function geodesic_ellipsoid_distance( + from: Coord | Point, + to: Coord | Point, + options: { + units?: Units; + datum?: Datum; + } = {} +) { + const fromCoord = getCoord(from); + const toCoord = getCoord(to); + const fromLatLon = new LatLon(fromCoord[1], fromCoord[0]); + const toLatLon = new LatLon(toCoord[1], toCoord[0]); + + if (options?.datum) { + // datum on from point sets the tone. + fromLatLon.datum = options.datum; + } else { + fromLatLon.datum = datums.WGS84; + } + + const meters = fromLatLon.distanceTo(toLatLon); + // geodesy lib result is in meters + return convertLength(meters, "meters", options.units); +} + +export { great_circle_distance, geodesic_ellipsoid_distance }; export default distance; diff --git a/packages/turf-distance/package.json b/packages/turf-distance/package.json index c7a424b99d..a219238f63 100644 --- a/packages/turf-distance/package.json +++ b/packages/turf-distance/package.json @@ -47,6 +47,7 @@ "test:tape": "tsx test.js" }, "devDependencies": { + "@types/geodesy": "1.1.3", "@types/tape": "*", "benchmark": "*", "load-json-file": "*", @@ -60,6 +61,7 @@ "dependencies": { "@turf/helpers": "^7.0.0-alpha.0", "@turf/invariant": "^7.0.0-alpha.0", + "geodesy": "1.1.3", "tslib": "^2.3.0" } } diff --git a/packages/turf-distance/test.js b/packages/turf-distance/test.js index 371dc5ea54..7a00755d7b 100644 --- a/packages/turf-distance/test.js +++ b/packages/turf-distance/test.js @@ -3,7 +3,7 @@ const path = require("path"); const test = require("tape"); const load = require("load-json-file"); const write = require("write-json-file"); -const { point } = require("@turf/helpers"); +import { datums, point } from "@turf/helpers"; const distance = require("./index").default; const directories = { @@ -56,3 +56,41 @@ test("distance -- throws", (t) => { ); t.end(); }); + +test("distance -- Issue #1726 line between poles", (t) => { + const p1 = point([-33.6, 81.1]); + const p2 = point([64.5, -80.8]); + + let overallDistance = distance(p1, p2, { + units: "meters", + datum: datums.WGS84, + }); + + const expected = 18682436.875; // m from QGIS + const tolerance = 0.01; // 1 cm expressed as m + t.true( + Math.abs(overallDistance - expected) < tolerance, + `${overallDistance} within ${tolerance} of ${expected}` + ); + + t.end(); +}); + +test("distance -- Issue #1726 line near equator", (t) => { + const p1 = point([34, 15.9]); + const p2 = point([21, 0.2]); + + let overallDistance = distance(p1, p2, { + units: "meters", + datum: datums.WGS84, + }); + + const expected = 2248334.18; // m from QGIS + const tolerance = 1; // 1 cm expressed as m + t.true( + Math.abs(overallDistance - expected) < tolerance, + `${overallDistance} within ${tolerance} of ${expected}` + ); + + t.end(); +}); diff --git a/packages/turf-helpers/index.ts b/packages/turf-helpers/index.ts index be3beb0386..b03f772d39 100644 --- a/packages/turf-helpers/index.ts +++ b/packages/turf-helpers/index.ts @@ -14,6 +14,7 @@ import { Position, GeoJsonProperties, } from "geojson"; +import { Datums, LatLonEllipsoidal } from "geodesy"; import { Id } from "./lib/geojson"; export * from "./lib/geojson"; @@ -115,6 +116,18 @@ export const areaFactors: Record = { yards: 1.195990046, }; +/** + * Common datum and ellipsoid definitions. Re-export verbatim from geodesy. + * + * @memberof helpers + * @type {Object} + */ +export const datums: Datums = LatLonEllipsoidal.datum; + +// Re-export type from geodesy so clients don't need to refer directly to +// geodesy types. +export type { Datum } from "geodesy"; + /** * Wraps a GeoJSON {@link Geometry} in a GeoJSON {@link Feature}. * diff --git a/packages/turf-helpers/package.json b/packages/turf-helpers/package.json index 23cb35e05f..f2d8f55d8f 100644 --- a/packages/turf-helpers/package.json +++ b/packages/turf-helpers/package.json @@ -54,6 +54,7 @@ "test:types": "tsc --esModuleInterop --noEmit --strict types.ts" }, "devDependencies": { + "@types/geodesy": "1.1.3", "@types/tape": "*", "benchmark": "*", "npm-run-all": "*", @@ -63,6 +64,7 @@ "typescript": "*" }, "dependencies": { + "geodesy": "1.1.3", "tslib": "^2.3.0" } } diff --git a/packages/turf-length/index.ts b/packages/turf-length/index.ts index c956521928..984e41f7ff 100644 --- a/packages/turf-length/index.ts +++ b/packages/turf-length/index.ts @@ -1,6 +1,6 @@ import { Feature, FeatureCollection, GeometryCollection } from "geojson"; import distance from "@turf/distance"; -import { Units } from "@turf/helpers"; +import { Datum, Units } from "@turf/helpers"; import { segmentReduce } from "@turf/meta"; /** @@ -23,6 +23,7 @@ export default function length( geojson: Feature | FeatureCollection | GeometryCollection, options: { units?: Units; + datum?: Datum; } = {} ): number { // Calculate distance from 2-vertex line segments diff --git a/packages/turf-length/test.js b/packages/turf-length/test.js index 8fb54b1bdb..1ae8719f31 100644 --- a/packages/turf-length/test.js +++ b/packages/turf-length/test.js @@ -4,6 +4,8 @@ const path = require("path"); const load = require("load-json-file"); const write = require("write-json-file"); const length = require("./index").default; +import { datums } from "@turf/helpers"; +import { lineString } from "@turf/turf"; const directories = { in: path.join(__dirname, "test", "in") + path.sep, @@ -27,3 +29,349 @@ test("turf-length", (t) => { } t.end(); }); + +// https://github.com/Turfjs/turf/issues/1726 +// Test case for path included in #1726 +// Need to do this with a specific test (as opposed to one of the test +// features) because we need to pass an explicit ellipsoid for this test +// only. +test("length -- Issue #1726 line from demo", (t) => { + const line = lineString([ + [17.917491, 59.414082], + [17.913936, 59.415673], + [17.913789, 59.415738], + [17.913681, 59.415786], + [17.913583, 59.41583], + [17.913493, 59.415869], + [17.913405, 59.415907], + [17.913322, 59.415943], + [17.913241, 59.415977], + [17.913164, 59.41601], + [17.913085, 59.416042], + [17.913008, 59.416073], + [17.912932, 59.416103], + [17.912858, 59.416132], + [17.912785, 59.41616], + [17.912713, 59.416187], + [17.912642, 59.416214], + [17.912571, 59.416239], + [17.912485, 59.416269], + [17.912398, 59.416299], + [17.912311, 59.416328], + [17.912222, 59.416356], + [17.912133, 59.416384], + [17.912044, 59.416411], + [17.911953, 59.416438], + [17.911862, 59.416464], + [17.91177, 59.416489], + [17.911678, 59.416514], + [17.911585, 59.416538], + [17.911491, 59.416562], + [17.911397, 59.416585], + [17.911302, 59.416607], + [17.911207, 59.416629], + [17.911111, 59.41665], + [17.911014, 59.41667], + [17.910917, 59.41669], + [17.910819, 59.416709], + [17.910721, 59.416727], + [17.910623, 59.416745], + [17.910524, 59.416762], + [17.910425, 59.416778], + [17.910325, 59.416794], + [17.910225, 59.416809], + [17.910124, 59.416823], + [17.910023, 59.416837], + [17.909922, 59.41685], + [17.90982, 59.416862], + [17.909719, 59.416874], + [17.909616, 59.416885], + [17.909514, 59.416895], + [17.909411, 59.416905], + [17.909308, 59.416914], + [17.909205, 59.416922], + [17.909102, 59.416929], + [17.908998, 59.416936], + [17.908894, 59.416942], + [17.90879, 59.416948], + [17.908686, 59.416953], + [17.908582, 59.416957], + [17.908478, 59.41696], + [17.908374, 59.416962], + [17.908269, 59.416964], + [17.908165, 59.416966], + [17.908061, 59.416966], + [17.907956, 59.416966], + [17.907852, 59.416965], + [17.907747, 59.416964], + [17.907643, 59.416961], + [17.907539, 59.416958], + [17.907435, 59.416955], + [17.907331, 59.41695], + [17.907227, 59.416945], + [17.907123, 59.416939], + [17.907019, 59.416933], + [17.906916, 59.416926], + [17.906812, 59.416918], + [17.906709, 59.416909], + [17.906606, 59.4169], + [17.906504, 59.41689], + [17.906402, 59.416879], + [17.906317, 59.41687], + [17.906231, 59.41686], + [17.906144, 59.41685], + [17.906055, 59.416838], + [17.905965, 59.416826], + [17.905872, 59.416814], + [17.905777, 59.4168], + [17.90568, 59.416786], + [17.905583, 59.416772], + [17.905483, 59.416757], + [17.905379, 59.41674], + [17.905269, 59.416723], + [17.905156, 59.416704], + [17.905033, 59.416684], + [17.904897, 59.416661], + [17.904711, 59.41663], + [17.898026, 59.415505], + [17.897692, 59.415449], + [17.897563, 59.415428], + [17.897446, 59.415409], + [17.897332, 59.41539], + [17.897224, 59.415373], + [17.89712, 59.415357], + [17.89702, 59.415342], + [17.896876, 59.41532], + [17.896732, 59.415299], + [17.896587, 59.415279], + [17.896442, 59.41526], + [17.896297, 59.415241], + [17.896151, 59.415222], + [17.896006, 59.415205], + [17.895859, 59.415188], + [17.895713, 59.415171], + [17.895566, 59.415155], + [17.89542, 59.41514], + [17.895272, 59.415126], + [17.895125, 59.415112], + [17.894977, 59.415099], + [17.89483, 59.415086], + [17.894682, 59.415074], + [17.894533, 59.415063], + [17.894385, 59.415052], + [17.894236, 59.415042], + [17.894088, 59.415033], + [17.893983, 59.415027], + [17.893876, 59.41502], + [17.893763, 59.415014], + [17.893643, 59.415008], + [17.893521, 59.415002], + [17.893387, 59.414996], + [17.893036, 59.41498], + [17.875072, 59.414164], + [17.874809, 59.414151], + [17.874617, 59.414142], + [17.874443, 59.414133], + [17.874283, 59.414125], + [17.874149, 59.414117], + [17.874016, 59.414109], + [17.873882, 59.4141], + [17.873748, 59.414091], + [17.873615, 59.414081], + [17.873481, 59.414071], + [17.873323, 59.414059], + [17.873151, 59.414044], + [17.87296, 59.414028], + [17.8727, 59.414005], + [17.872439, 59.413982], + [17.872249, 59.413966], + [17.872076, 59.413951], + [17.871918, 59.413939], + [17.871785, 59.413928], + [17.871651, 59.413919], + [17.871517, 59.41391], + [17.871384, 59.413901], + [17.87125, 59.413893], + [17.871116, 59.413885], + [17.870957, 59.413876], + [17.870783, 59.413868], + [17.87059, 59.413858], + [17.870327, 59.413846], + [17.869112, 59.413791], + [17.868775, 59.413775], + [17.868646, 59.413769], + [17.868528, 59.413763], + [17.868413, 59.413757], + [17.868304, 59.413752], + [17.868201, 59.413746], + [17.868101, 59.41374], + [17.868, 59.413733], + [17.867903, 59.413727], + [17.867808, 59.41372], + [17.867715, 59.413713], + [17.867625, 59.413706], + [17.867536, 59.413699], + [17.867449, 59.413692], + [17.867364, 59.413684], + [17.867251, 59.413673], + [17.867139, 59.413662], + [17.867026, 59.413649], + [17.866914, 59.413636], + [17.866802, 59.413623], + [17.86669, 59.413609], + [17.866579, 59.413594], + [17.866468, 59.413578], + [17.866358, 59.413562], + [17.866248, 59.413545], + [17.866138, 59.413527], + [17.866029, 59.413509], + [17.86592, 59.41349], + [17.865811, 59.41347], + [17.865703, 59.41345], + [17.865596, 59.413429], + [17.865489, 59.413408], + [17.865382, 59.413385], + [17.865277, 59.413363], + [17.865171, 59.413339], + [17.865066, 59.413315], + [17.864962, 59.41329], + [17.864858, 59.413265], + [17.864755, 59.413239], + [17.864653, 59.413212], + [17.864551, 59.413185], + [17.86445, 59.413157], + [17.864349, 59.413128], + [17.864249, 59.413099], + [17.86415, 59.413069], + [17.864052, 59.413039], + [17.863954, 59.413008], + [17.863857, 59.412977], + [17.863761, 59.412945], + [17.863665, 59.412912], + [17.863571, 59.412879], + [17.863477, 59.412845], + [17.863383, 59.412811], + [17.863291, 59.412776], + [17.8632, 59.41274], + [17.863109, 59.412704], + [17.863019, 59.412667], + [17.86293, 59.41263], + [17.862842, 59.412593], + [17.862755, 59.412554], + [17.862668, 59.412516], + [17.862583, 59.412476], + [17.862498, 59.412436], + [17.862415, 59.412396], + [17.862332, 59.412355], + [17.862251, 59.412314], + [17.86217, 59.412272], + [17.86209, 59.41223], + [17.862012, 59.412187], + [17.861934, 59.412144], + [17.861857, 59.4121], + [17.861781, 59.412056], + [17.861707, 59.412012], + [17.861633, 59.411966], + [17.861561, 59.411921], + [17.861489, 59.411875], + [17.861419, 59.411829], + [17.86135, 59.411782], + [17.861281, 59.411735], + [17.861214, 59.411687], + [17.861148, 59.411639], + [17.861084, 59.41159], + [17.86102, 59.411542], + [17.860957, 59.411492], + [17.860896, 59.411443], + [17.860836, 59.411393], + [17.860777, 59.411343], + [17.860719, 59.411292], + [17.860662, 59.411241], + [17.860607, 59.41119], + [17.860552, 59.411138], + [17.860499, 59.411086], + [17.860447, 59.411033], + [17.860397, 59.410981], + [17.860347, 59.410928], + [17.860299, 59.410875], + [17.860252, 59.410821], + [17.860207, 59.410767], + [17.860162, 59.410713], + [17.860119, 59.410659], + [17.860077, 59.410604], + [17.860037, 59.410549], + [17.859997, 59.410494], + [17.859959, 59.410439], + [17.859922, 59.410383], + [17.859887, 59.410328], + [17.859853, 59.410272], + [17.85982, 59.410215], + [17.859789, 59.410159], + [17.859758, 59.410102], + [17.85973, 59.410046], + [17.859702, 59.409989], + [17.859676, 59.409932], + [17.859651, 59.409874], + [17.859628, 59.409817], + [17.859605, 59.409759], + [17.859585, 59.409702], + [17.859565, 59.409644], + [17.859547, 59.409586], + [17.85953, 59.409528], + [17.859514, 59.409468], + [17.859498, 59.409404], + [17.859483, 59.409338], + [17.859468, 59.409269], + [17.859453, 59.409194], + [17.859438, 59.409111], + [17.859417, 59.408997], + [17.858396, 59.403363], + ]); + + // console.log(JSON.stringify(featureCollection([line]))); + + const overallDistance = length(line, { + units: "meters", + datum: datums.WGS84, + }); + + const expected = 4367.078; // m from QGIS + const tolerance = 0.02; // 2 cm expressed as m + t.true( + Math.abs(overallDistance - expected) < tolerance, + `${overallDistance} within ${tolerance} of ${expected}` + ); + + t.end(); +}); + +// https://github.com/Turfjs/turf/issues/1726 +// Test case for path included in #1726 +// Need to do this with a specific test (as opposed to one of the test +// features) because we need to pass an explicit ellipsoid for this test +// only. +test("length -- Issue #1726 loooong line", (t) => { + const line = lineString([ + [-39, 76], + [34, 53], + [22, -28], + [123, -28], + [-6, -75], + ]); + + // console.log(JSON.stringify(featureCollection([line]))); + + const overallDistance = length(line, { + units: "meters", + datum: datums.WGS84, + }); + + const expected = 30555616.0; // m from QGIS + const tolerance = 0.02; // 2 cm expressed as m + t.true( + Math.abs(overallDistance - expected) < tolerance, + `${overallDistance} within ${tolerance} of ${expected}` + ); + + t.end(); +}); diff --git a/yarn.lock b/yarn.lock index 2e0fc2c34c..3bd97bac58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2327,6 +2327,11 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/geodesy@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/geodesy/-/geodesy-1.1.3.tgz#26999ade11f47cada7b529d19e38bba76f164665" + integrity sha512-migQ5GRDj53SNVU+HZ8ajxRoPtTWY/vXwvQFP6OIL7WJywtBquRgtHwAUuOS/C+fLNtIi+XixdqghAvT2JeRqA== + "@types/geojson-equality@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@types/geojson-equality/-/geojson-equality-0.2.0.tgz#2c7d411f534bee6bf2e7689c14c4c6f3dced2a8c" @@ -5108,6 +5113,11 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +geodesy@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/geodesy/-/geodesy-1.1.3.tgz#a0cbd5db5c0cf8ac045e749f754f61b450382de9" + integrity sha512-H/0XSd1KjKZGZ2YGZcOYzRyY/foYAawwTEumNSo+YUwf+u5d4CfvBRg2i2Qimrx9yUEjWR8hLvMnhghuVFN0Zg== + geojson-equality@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/geojson-equality/-/geojson-equality-0.1.6.tgz#a171374ef043e5d4797995840bae4648e0752d72" From e6dfbd8e2c800aea60aee0f85259e42c655fa9f7 Mon Sep 17 00:00:00 2001 From: James Beard Date: Sun, 3 Sep 2023 22:24:52 +1000 Subject: [PATCH 2/3] Prior commit failed post-commit CI. Identified a problem with the final packages/turf/ conversion from mjs to rolled up js. "In strict mode code, functions can only be declared at top level or inside a block." --- packages/turf-distance/index.ts | 14 +++++++++----- packages/turf-distance/test.js | 2 +- packages/turf-helpers/index.ts | 11 ++++++++--- packages/turf-length/index.ts | 2 +- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/turf-distance/index.ts b/packages/turf-distance/index.ts index dfee90be94..186b15a58f 100644 --- a/packages/turf-distance/index.ts +++ b/packages/turf-distance/index.ts @@ -1,14 +1,15 @@ import { Point } from "geojson"; import { getCoord } from "@turf/invariant"; +import type { Coord, Datum, Units } from "@turf/helpers"; import { + datums, radiansToLength, degreesToRadians, - datums, - Coord, - Datum, - Units, convertLength, } from "@turf/helpers"; +// Unable to find an equivalent import statement that works. Fails when building +// index.mjs → turf.min.js in packages/turf/ +// import { LatLonEllipsoidal as LatLon } from "geodesy"; const LatLon = require("geodesy").LatLonEllipsoidal; //http://en.wikipedia.org/wiki/Haversine_formula @@ -142,7 +143,10 @@ function geodesic_ellipsoid_distance( fromLatLon.datum = datums.WGS84; } - const meters = fromLatLon.distanceTo(toLatLon); + // LatLonEllipsoidal types don't properly list all base LatLon functions + // e.g. distanceTo. Should be able to remove when we can move to a newer + // version of geodesy. + const meters = (fromLatLon as any).distanceTo(toLatLon); // geodesy lib result is in meters return convertLength(meters, "meters", options.units); } diff --git a/packages/turf-distance/test.js b/packages/turf-distance/test.js index 7a00755d7b..fcc6281302 100644 --- a/packages/turf-distance/test.js +++ b/packages/turf-distance/test.js @@ -3,7 +3,7 @@ const path = require("path"); const test = require("tape"); const load = require("load-json-file"); const write = require("write-json-file"); -import { datums, point } from "@turf/helpers"; +const { datums, point } = require("@turf/helpers"); const distance = require("./index").default; const directories = { diff --git a/packages/turf-helpers/index.ts b/packages/turf-helpers/index.ts index b03f772d39..871d255226 100644 --- a/packages/turf-helpers/index.ts +++ b/packages/turf-helpers/index.ts @@ -14,7 +14,11 @@ import { Position, GeoJsonProperties, } from "geojson"; -import { Datums, LatLonEllipsoidal } from "geodesy"; +import type { Datum } from "geodesy"; +// Unable to find an equivalent import statement that works. Fails when building +// index.mjs → turf.min.js in packages/turf/ +// import { LatLonEllipsoidal as LatLon } from "geodesy"; +const LatLon = require("geodesy").LatLonEllipsoidal; import { Id } from "./lib/geojson"; export * from "./lib/geojson"; @@ -122,11 +126,12 @@ export const areaFactors: Record = { * @memberof helpers * @type {Object} */ -export const datums: Datums = LatLonEllipsoidal.datum; +const datums = LatLon.datum; +export { datums }; // Re-export type from geodesy so clients don't need to refer directly to // geodesy types. -export type { Datum } from "geodesy"; +export type { Datum }; /** * Wraps a GeoJSON {@link Geometry} in a GeoJSON {@link Feature}. diff --git a/packages/turf-length/index.ts b/packages/turf-length/index.ts index 984e41f7ff..a91fa15875 100644 --- a/packages/turf-length/index.ts +++ b/packages/turf-length/index.ts @@ -1,6 +1,6 @@ import { Feature, FeatureCollection, GeometryCollection } from "geojson"; import distance from "@turf/distance"; -import { Datum, Units } from "@turf/helpers"; +import type { Datum, Units } from "@turf/helpers"; import { segmentReduce } from "@turf/meta"; /** From 13d093c87582a011ce1c4af5bd631b9ede81e9b4 Mon Sep 17 00:00:00 2001 From: James Beard Date: Thu, 14 Sep 2023 13:19:26 +1000 Subject: [PATCH 3/3] Upgrading to geodesy 2.3.0 (possible now that #2489 is merged) meaning we can use import rather than require. Tightened up distance module function names and their optional parameters as well. --- packages/turf-distance/index.ts | 48 +++++++++++++---------------- packages/turf-distance/package.json | 4 +-- packages/turf-distance/test.js | 21 ++++++++++++- packages/turf-helpers/index.ts | 7 ++--- packages/turf-helpers/package.json | 4 +-- yarn.lock | 16 +++++----- 6 files changed, 55 insertions(+), 45 deletions(-) diff --git a/packages/turf-distance/index.ts b/packages/turf-distance/index.ts index 186b15a58f..4008d1f119 100644 --- a/packages/turf-distance/index.ts +++ b/packages/turf-distance/index.ts @@ -7,11 +7,7 @@ import { degreesToRadians, convertLength, } from "@turf/helpers"; -// Unable to find an equivalent import statement that works. Fails when building -// index.mjs → turf.min.js in packages/turf/ -// import { LatLonEllipsoidal as LatLon } from "geodesy"; -const LatLon = require("geodesy").LatLonEllipsoidal; - +import LatLon from "geodesy/latlon-ellipsoidal-vincenty.js"; //http://en.wikipedia.org/wiki/Haversine_formula //http://www.movable-type.co.uk/scripts/latlong.html @@ -48,9 +44,12 @@ function distance( } = {} ) { if (options?.datum) { - return geodesic_ellipsoid_distance(from, to, options); + return geodesicEllipsoidDistance(from, to, { + datum: options.datum, // Must pass a datum option + ...options, + }); } else { - return great_circle_distance(from, to, options); + return greatCircleDistance(from, to, options); } } @@ -58,7 +57,7 @@ function distance( * Calculates the distance between two {@link Point|points} in degrees, radians, miles, or kilometers. * Performs a great circle calculation using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature. * - * @name distance + * @name greatCircleDistance * @param {Coord | Point} from origin point or coordinate * @param {Coord | Point} to destination point or coordinate * @param {Object} [options={}] Optional parameters @@ -69,14 +68,14 @@ function distance( * var to = turf.point([-75.534, 39.123]); * var options = {units: 'miles'}; * - * var distance = turf.distance(from, to, options); + * var distance = turf.greatCircleDistance(from, to, options); * * //addToMap * var addToMap = [from, to]; * from.properties.distance = distance; * to.properties.distance = distance; */ -function great_circle_distance( +function greatCircleDistance( from: Coord | Point, to: Coord | Point, options: { @@ -104,7 +103,7 @@ function great_circle_distance( * Calculates the distance between two {@link Point|points}. * Performs a geodesic ellipsoid calculation using [Vincenty's formulae](https://en.wikipedia.org/wiki/Vincenty%27s_formulae) to account for speroidal curvature. * - * @name geodesic_ellipsoid_distance + * @name geodesicEllipsoidDistance * @param {Coord | Point} from origin point or coordinate * @param {Coord | Point} to destination point or coordinate * @param {Object} [options={}] Optional parameters @@ -116,40 +115,35 @@ function great_circle_distance( * var to = turf.point([-75.534, 39.123]); * var options = {units: 'miles', datum: datums.WGS84}; * - * var distance = turf.distance(from, to, options); + * var distance = turf.geodesicEllipsoidDistance(from, to, options); * * //addToMap * var addToMap = [from, to]; * from.properties.distance = distance; * to.properties.distance = distance; */ -function geodesic_ellipsoid_distance( +function geodesicEllipsoidDistance( from: Coord | Point, to: Coord | Point, options: { units?: Units; - datum?: Datum; - } = {} + datum: Datum; + } = { datum: datums.WGS84 } ) { const fromCoord = getCoord(from); const toCoord = getCoord(to); + + const datum = options.datum; + const fromLatLon = new LatLon(fromCoord[1], fromCoord[0]); + // datum on from point sets the tone. + fromLatLon.datum = datum; const toLatLon = new LatLon(toCoord[1], toCoord[0]); - if (options?.datum) { - // datum on from point sets the tone. - fromLatLon.datum = options.datum; - } else { - fromLatLon.datum = datums.WGS84; - } - - // LatLonEllipsoidal types don't properly list all base LatLon functions - // e.g. distanceTo. Should be able to remove when we can move to a newer - // version of geodesy. - const meters = (fromLatLon as any).distanceTo(toLatLon); + const meters = fromLatLon.distanceTo(toLatLon); // geodesy lib result is in meters return convertLength(meters, "meters", options.units); } -export { great_circle_distance, geodesic_ellipsoid_distance }; +export { greatCircleDistance, geodesicEllipsoidDistance }; export default distance; diff --git a/packages/turf-distance/package.json b/packages/turf-distance/package.json index a219238f63..b6edde00af 100644 --- a/packages/turf-distance/package.json +++ b/packages/turf-distance/package.json @@ -47,7 +47,7 @@ "test:tape": "tsx test.js" }, "devDependencies": { - "@types/geodesy": "1.1.3", + "@types/geodesy": "^2.2.3", "@types/tape": "*", "benchmark": "*", "load-json-file": "*", @@ -61,7 +61,7 @@ "dependencies": { "@turf/helpers": "^7.0.0-alpha.0", "@turf/invariant": "^7.0.0-alpha.0", - "geodesy": "1.1.3", + "geodesy": "^2.3.0", "tslib": "^2.3.0" } } diff --git a/packages/turf-distance/test.js b/packages/turf-distance/test.js index fcc6281302..ee6f645270 100644 --- a/packages/turf-distance/test.js +++ b/packages/turf-distance/test.js @@ -5,6 +5,7 @@ const load = require("load-json-file"); const write = require("write-json-file"); const { datums, point } = require("@turf/helpers"); const distance = require("./index").default; +const { geodesicEllipsoidDistance } = require("./index"); const directories = { in: path.join(__dirname, "test", "in") + path.sep, @@ -86,7 +87,25 @@ test("distance -- Issue #1726 line near equator", (t) => { }); const expected = 2248334.18; // m from QGIS - const tolerance = 1; // 1 cm expressed as m + const tolerance = 0.01; // 1 cm expressed as m + t.true( + Math.abs(overallDistance - expected) < tolerance, + `${overallDistance} within ${tolerance} of ${expected}` + ); + + t.end(); +}); + +test("distance -- calling ellipsoid distance with empty options", (t) => { + // Make sure calling geodesicEllipsoidDistance directly without a datum + // option defaults to WGS84. + const p1 = point([-33.6, 81.1]); + const p2 = point([64.5, -80.8]); + + let overallDistance = geodesicEllipsoidDistance(p1, p2); + + const expected = 18682.436875; // m from QGIS as km + const tolerance = 0.00001; // 1 cm expressed as km t.true( Math.abs(overallDistance - expected) < tolerance, `${overallDistance} within ${tolerance} of ${expected}` diff --git a/packages/turf-helpers/index.ts b/packages/turf-helpers/index.ts index 871d255226..40ac26e230 100644 --- a/packages/turf-helpers/index.ts +++ b/packages/turf-helpers/index.ts @@ -15,10 +15,7 @@ import { GeoJsonProperties, } from "geojson"; import type { Datum } from "geodesy"; -// Unable to find an equivalent import statement that works. Fails when building -// index.mjs → turf.min.js in packages/turf/ -// import { LatLonEllipsoidal as LatLon } from "geodesy"; -const LatLon = require("geodesy").LatLonEllipsoidal; +import LatLonEllipsoidalVincenty from "geodesy/latlon-ellipsoidal-vincenty.js"; import { Id } from "./lib/geojson"; export * from "./lib/geojson"; @@ -126,7 +123,7 @@ export const areaFactors: Record = { * @memberof helpers * @type {Object} */ -const datums = LatLon.datum; +const datums = LatLonEllipsoidalVincenty.datums; export { datums }; // Re-export type from geodesy so clients don't need to refer directly to diff --git a/packages/turf-helpers/package.json b/packages/turf-helpers/package.json index f2d8f55d8f..dea2bcf431 100644 --- a/packages/turf-helpers/package.json +++ b/packages/turf-helpers/package.json @@ -54,7 +54,7 @@ "test:types": "tsc --esModuleInterop --noEmit --strict types.ts" }, "devDependencies": { - "@types/geodesy": "1.1.3", + "@types/geodesy": "^2.2.3", "@types/tape": "*", "benchmark": "*", "npm-run-all": "*", @@ -64,7 +64,7 @@ "typescript": "*" }, "dependencies": { - "geodesy": "1.1.3", + "geodesy": "^2.3.0", "tslib": "^2.3.0" } } diff --git a/yarn.lock b/yarn.lock index 3bd97bac58..359b1d7b5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2327,10 +2327,10 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/geodesy@1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@types/geodesy/-/geodesy-1.1.3.tgz#26999ade11f47cada7b529d19e38bba76f164665" - integrity sha512-migQ5GRDj53SNVU+HZ8ajxRoPtTWY/vXwvQFP6OIL7WJywtBquRgtHwAUuOS/C+fLNtIi+XixdqghAvT2JeRqA== +"@types/geodesy@^2.2.3": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@types/geodesy/-/geodesy-2.2.3.tgz#70ff892715cb69607469c50347421151331a9198" + integrity sha512-zpgJ6W/PH8nQN5fyiynfRGLa7X9Fij8QuvBzZhN+/63+nrLaj6sLF7KbgKf55HBGryCuyuA8fEB8QUeAFCPWrg== "@types/geojson-equality@^0.2.0": version "0.2.0" @@ -5113,10 +5113,10 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -geodesy@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/geodesy/-/geodesy-1.1.3.tgz#a0cbd5db5c0cf8ac045e749f754f61b450382de9" - integrity sha512-H/0XSd1KjKZGZ2YGZcOYzRyY/foYAawwTEumNSo+YUwf+u5d4CfvBRg2i2Qimrx9yUEjWR8hLvMnhghuVFN0Zg== +geodesy@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/geodesy/-/geodesy-2.4.0.tgz#6a0ad3dab3c1f95d8430aaa04fbe620b639c3e48" + integrity sha512-tHjQ1sXq8UAIEg1V0Pa6mznUxGU0R+3H5PIF6NULr0yPCAVLKqJro93Bbr19jSE18BMfyjN4osWDI4sm92m0kw== geojson-equality@0.1.6: version "0.1.6"