From b34b1921ad34e17d0c9a3dc43a5b8365dca0a49c Mon Sep 17 00:00:00 2001 From: Lukas Himsel Date: Sun, 3 Jul 2022 17:28:34 +0200 Subject: [PATCH 1/3] initial commit, stolen from @armantorkzaban --- README.md | 2 +- lib/bearing.dart | 1 + lib/src/rhumb_bearing.dart | 148 ++++++++++++++++++ test/components/rhumb_bearing_test.dart | 94 +++++++++++ test/examples/rhumb_bearing/in/pair1.geojson | 31 ++++ test/examples/rhumb_bearing/out/pair1.geojson | 71 +++++++++ test/examples/rhumb_bearing/out/pair1.json | 4 + 7 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 lib/src/rhumb_bearing.dart create mode 100644 test/components/rhumb_bearing_test.dart create mode 100644 test/examples/rhumb_bearing/in/pair1.geojson create mode 100644 test/examples/rhumb_bearing/out/pair1.geojson create mode 100644 test/examples/rhumb_bearing/out/pair1.json diff --git a/README.md b/README.md index 86badee..d2546b1 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the - [ ] pointOnFeature - [ ] polygonTangents - [ ] pointToLineDistance -- [ ] rhumbBearing +- [x] [rhumbBearing](https://github.com/dartclub/turf_dart/blob/main/lib/src/rhumb_bearing.dart) - [ ] rhumbDestination - [ ] rhumbDistance - [ ] square diff --git a/lib/bearing.dart b/lib/bearing.dart index 9b3eeee..64c1d49 100644 --- a/lib/bearing.dart +++ b/lib/bearing.dart @@ -1,3 +1,4 @@ library turf_bearing; export 'src/bearing.dart'; +export 'src/rhumb_bearing.dart'; diff --git a/lib/src/rhumb_bearing.dart b/lib/src/rhumb_bearing.dart new file mode 100644 index 0000000..7cfee50 --- /dev/null +++ b/lib/src/rhumb_bearing.dart @@ -0,0 +1,148 @@ +// https://en.wikipedia.org/wiki/Rhumb_line +import 'package:turf/src/invariant.dart'; +import 'dart:math' as math; +import '../helpers.dart'; + +/// Takes two [Point] and finds the bearing angle between them along a Rhumb line +/// i.e. the angle measured in degrees start the north line (0 degrees) +/// [kFinal] calculates the final bearing if true. +/// Returns bearing from north in decimal degrees, between -180 and 180 degrees (positive clockwise) +/// example: +/// ```dart +/// var point1 = Feature(geometry: Point(coordinates: Position.of([-75.343, 39.984])), properties: {"marker-color": "#F00"}); +/// var point2 = Feature(geometry: Point(coordinates: Position.of([-75.534, 39.123])), properties: {"marker-color": "#00F"}); +/// var bearing = rhumbBearing(point1.geometry, point2.geometry); +/// //addToMap +/// var addToMap = [point1, point2]; +/// point1.properties['bearing'] = bearing; +/// point2.properties['bearing'] = bearing; +/// ``` +num rhumbBearing(Point start, Point end, {bool kFinal = false}) { + num bear360; + if (kFinal) { + bear360 = calculateRhumbBearing(getCoord(end), getCoord(start)); + } else { + bear360 = calculateRhumbBearing(getCoord(start), getCoord(end)); + } + + var bear180 = bear360 > 180 ? -(360 - bear360) : bear360; + + return bear180; +} + +/// Returns the bearing from ‘this’ [Point] to destination [Point] along a rhumb line. +/// Adapted from Geodesy: https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js +/// Returns Bearing in degrees from north. +/// example +/// ```dart +/// var p1 = Position.named(lng: 51.127, lat: 1.338); +/// var p2 = Position.named(lng: 50.964, lat: 1.853); +/// var d = p1.rhumbBearingTo(p2); // 116.7 m +/// ``` +num calculateRhumbBearing(Position from, Position to) { + // φ => phi + // Δλ => deltaLambda + // Δψ => deltaPsi + // θ => theta + num phi1 = degreesToRadians(from.lat); + num phi2 = degreesToRadians(to.lat); + num deltaLambda = degreesToRadians(to.lng - from.lng); + // if deltaLambda over 180° take shorter rhumb line across the anti-meridian: + if (deltaLambda > math.pi) { + deltaLambda -= 2 * math.pi; + } + if (deltaLambda < -math.pi) { + deltaLambda += 2 * math.pi; + } + + double deltaPsi = math + .log(math.tan(phi2 / 2 + math.pi / 4) / math.tan(phi1 / 2 + math.pi / 4)); + + double theta = math.atan2(deltaLambda, deltaPsi); + + return (radiansToDegrees(theta) + 360) % 360; +} + +/** + * // https://en.wikipedia.org/wiki/Rhumb_line +import { Coord, degreesToRadians, radiansToDegrees } from "@turf/helpers"; +import { getCoord } from "@turf/invariant"; + +/** + * Takes two {@link Point|points} and finds the bearing angle between them along a Rhumb line + * i.e. the angle measured in degrees start the north line (0 degrees) + * + * @name rhumbBearing + * @param {Coord} start starting Point + * @param {Coord} end ending Point + * @param {Object} [options] Optional parameters + * @param {boolean} [options.final=false] calculates the final bearing if true + * @returns {number} bearing from north in decimal degrees, between -180 and 180 degrees (positive clockwise) + * @example + * var point1 = turf.point([-75.343, 39.984], {"marker-color": "#F00"}); + * var point2 = turf.point([-75.534, 39.123], {"marker-color": "#00F"}); + * + * var bearing = turf.rhumbBearing(point1, point2); + * + * //addToMap + * var addToMap = [point1, point2]; + * point1.properties.bearing = bearing; + * point2.properties.bearing = bearing; + */ +function rhumbBearing( + start: Coord, + end: Coord, + options: { final?: boolean } = {} +): number { + let bear360; + if (options.final) { + bear360 = calculateRhumbBearing(getCoord(end), getCoord(start)); + } else { + bear360 = calculateRhumbBearing(getCoord(start), getCoord(end)); + } + + const bear180 = bear360 > 180 ? -(360 - bear360) : bear360; + + return bear180; +} + +/** + * Returns the bearing from ‘this’ point to destination point along a rhumb line. + * Adapted from Geodesy: https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js + * + * @private + * @param {Array} from - origin point. + * @param {Array} to - destination point. + * @returns {number} Bearing in degrees from north. + * @example + * var p1 = new LatLon(51.127, 1.338); + * var p2 = new LatLon(50.964, 1.853); + * var d = p1.rhumbBearingTo(p2); // 116.7 m + */ +function calculateRhumbBearing(from: number[], to: number[]) { + // φ => phi + // Δλ => deltaLambda + // Δψ => deltaPsi + // θ => theta + const phi1 = degreesToRadians(from[1]); + const phi2 = degreesToRadians(to[1]); + let deltaLambda = degreesToRadians(to[0] - from[0]); + // if deltaLambdaon over 180° take shorter rhumb line across the anti-meridian: + if (deltaLambda > Math.PI) { + deltaLambda -= 2 * Math.PI; + } + if (deltaLambda < -Math.PI) { + deltaLambda += 2 * Math.PI; + } + + const deltaPsi = Math.log( + Math.tan(phi2 / 2 + Math.PI / 4) / Math.tan(phi1 / 2 + Math.PI / 4) + ); + + const theta = Math.atan2(deltaLambda, deltaPsi); + + return (radiansToDegrees(theta) + 360) % 360; +} + +export default rhumbBearing; + */ \ No newline at end of file diff --git a/test/components/rhumb_bearing_test.dart b/test/components/rhumb_bearing_test.dart new file mode 100644 index 0000000..6b3e17e --- /dev/null +++ b/test/components/rhumb_bearing_test.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:test/test.dart'; +import 'package:turf/helpers.dart'; +import 'package:turf/src/rhumb_bearing.dart'; + +main() { + group( + '', + () { + Directory inDir = Directory('./test/examples/rhumb_bearing/in'); + for (var file in inDir.listSync(recursive: true)) { + if (file is File && file.path.endsWith('.geojson')) { + test( + file.path, + () { + var inSource = file.readAsStringSync(); + var inGeom = GeoJSONObject.fromJson(jsonDecode(inSource)); + + var start = (inGeom as FeatureCollection).features[0]; + var end = inGeom.features[1]; + var initialBearing = + rhumbBearing(start.geometry as Point, end.geometry as Point); + var finalBearing = rhumbBearing( + start.geometry as Point, end.geometry as Point, + kFinal: true); + var result = { + 'initialBearing': initialBearing, + 'finalBearing': finalBearing, + }; + + Directory outDir = Directory('./test/examples/rhumb_bearing/out'); + for (var file in outDir.listSync(recursive: true)) { + if (file is File && file.path.endsWith('.json')) { + var outSource = jsonDecode(file.readAsStringSync()); + expect(result, outSource); + } + } + }, + ); + } + } + }, + ); +} + +/* +const fs = require("fs"); +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"); +const rhumbBearing = require("./index").default; + +const directories = { + in: path.join(__dirname, "test", "in") + path.sep, + out: path.join(__dirname, "test", "out") + path.sep, +}; + +let fixtures = fs.readdirSync(directories.in).map((filename) => { + return { + filename, + name: path.parse(filename).name, + geojson: load.sync(directories.in + filename), + }; +}); + +test("bearing", (t) => { + fixtures.forEach((fixture) => { + const name = fixture.name; + const geojson = fixture.geojson; + + const start = geojson.features[0]; + const end = geojson.features[1]; + + const initialBearing = rhumbBearing(start, end); + const finalBearing = rhumbBearing(start, end, { final: true }); + + const result = { + initialBearing: initialBearing, + finalBearing: finalBearing, + }; + if (process.env.REGEN) write.sync(directories.out + name + ".json", result); + t.deepEqual(load.sync(directories.out + name + ".json"), result, name); + }); + + t.throws(() => { + rhumbBearing(point([12, -54]), "point"); + }, "invalid point"); + t.end(); +}); +*/ \ No newline at end of file diff --git a/test/examples/rhumb_bearing/in/pair1.geojson b/test/examples/rhumb_bearing/in/pair1.geojson new file mode 100644 index 0000000..b48344e --- /dev/null +++ b/test/examples/rhumb_bearing/in/pair1.geojson @@ -0,0 +1,31 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "marker-color": "#F00" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -75, + 45 + ] + } + }, + { + "type": "Feature", + "properties": { + "marker-color": "#00F" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 20, + 60 + ] + } + } + ] +} \ No newline at end of file diff --git a/test/examples/rhumb_bearing/out/pair1.geojson b/test/examples/rhumb_bearing/out/pair1.geojson new file mode 100644 index 0000000..b5ab5e2 --- /dev/null +++ b/test/examples/rhumb_bearing/out/pair1.geojson @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "marker-color": "#F00" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -75, + 45 + ] + } + }, + { + "type": "Feature", + "properties": { + "marker-color": "#00F" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 20, + 60 + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -75, + 45 + ], + [ + -66.10068737769872, + 51.79325008492101 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 20, + 60 + ], + [ + 2.3844816279733956, + 63.440396381483744 + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/test/examples/rhumb_bearing/out/pair1.json b/test/examples/rhumb_bearing/out/pair1.json new file mode 100644 index 0000000..171e525 --- /dev/null +++ b/test/examples/rhumb_bearing/out/pair1.json @@ -0,0 +1,4 @@ +{ + "initialBearing": 75.28061364784332, + "finalBearing": -104.7193863521567 +} \ No newline at end of file From 83628689f73f190bcbbbbfbdc67dfb32db72255f Mon Sep 17 00:00:00 2001 From: Lukas Himsel Date: Sun, 3 Jul 2022 17:45:00 +0200 Subject: [PATCH 2/3] fix formatting --- lib/src/rhumb_bearing.dart | 2 +- test/components/rhumb_bearing_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/rhumb_bearing.dart b/lib/src/rhumb_bearing.dart index 7cfee50..155cc43 100644 --- a/lib/src/rhumb_bearing.dart +++ b/lib/src/rhumb_bearing.dart @@ -145,4 +145,4 @@ function calculateRhumbBearing(from: number[], to: number[]) { } export default rhumbBearing; - */ \ No newline at end of file + */ diff --git a/test/components/rhumb_bearing_test.dart b/test/components/rhumb_bearing_test.dart index 6b3e17e..dc42692 100644 --- a/test/components/rhumb_bearing_test.dart +++ b/test/components/rhumb_bearing_test.dart @@ -91,4 +91,4 @@ test("bearing", (t) => { }, "invalid point"); t.end(); }); -*/ \ No newline at end of file +*/ From 3cd1171d7c54d5e3af1d005bf735f6db6411194a Mon Sep 17 00:00:00 2001 From: Lukas Himsel Date: Sun, 3 Jul 2022 17:49:30 +0200 Subject: [PATCH 3/3] cleanup comments --- lib/src/rhumb_bearing.dart | 84 ------------------------- test/components/rhumb_bearing_test.dart | 48 -------------- 2 files changed, 132 deletions(-) diff --git a/lib/src/rhumb_bearing.dart b/lib/src/rhumb_bearing.dart index 155cc43..b3fc67d 100644 --- a/lib/src/rhumb_bearing.dart +++ b/lib/src/rhumb_bearing.dart @@ -62,87 +62,3 @@ num calculateRhumbBearing(Position from, Position to) { return (radiansToDegrees(theta) + 360) % 360; } - -/** - * // https://en.wikipedia.org/wiki/Rhumb_line -import { Coord, degreesToRadians, radiansToDegrees } from "@turf/helpers"; -import { getCoord } from "@turf/invariant"; - -/** - * Takes two {@link Point|points} and finds the bearing angle between them along a Rhumb line - * i.e. the angle measured in degrees start the north line (0 degrees) - * - * @name rhumbBearing - * @param {Coord} start starting Point - * @param {Coord} end ending Point - * @param {Object} [options] Optional parameters - * @param {boolean} [options.final=false] calculates the final bearing if true - * @returns {number} bearing from north in decimal degrees, between -180 and 180 degrees (positive clockwise) - * @example - * var point1 = turf.point([-75.343, 39.984], {"marker-color": "#F00"}); - * var point2 = turf.point([-75.534, 39.123], {"marker-color": "#00F"}); - * - * var bearing = turf.rhumbBearing(point1, point2); - * - * //addToMap - * var addToMap = [point1, point2]; - * point1.properties.bearing = bearing; - * point2.properties.bearing = bearing; - */ -function rhumbBearing( - start: Coord, - end: Coord, - options: { final?: boolean } = {} -): number { - let bear360; - if (options.final) { - bear360 = calculateRhumbBearing(getCoord(end), getCoord(start)); - } else { - bear360 = calculateRhumbBearing(getCoord(start), getCoord(end)); - } - - const bear180 = bear360 > 180 ? -(360 - bear360) : bear360; - - return bear180; -} - -/** - * Returns the bearing from ‘this’ point to destination point along a rhumb line. - * Adapted from Geodesy: https://github.com/chrisveness/geodesy/blob/master/latlon-spherical.js - * - * @private - * @param {Array} from - origin point. - * @param {Array} to - destination point. - * @returns {number} Bearing in degrees from north. - * @example - * var p1 = new LatLon(51.127, 1.338); - * var p2 = new LatLon(50.964, 1.853); - * var d = p1.rhumbBearingTo(p2); // 116.7 m - */ -function calculateRhumbBearing(from: number[], to: number[]) { - // φ => phi - // Δλ => deltaLambda - // Δψ => deltaPsi - // θ => theta - const phi1 = degreesToRadians(from[1]); - const phi2 = degreesToRadians(to[1]); - let deltaLambda = degreesToRadians(to[0] - from[0]); - // if deltaLambdaon over 180° take shorter rhumb line across the anti-meridian: - if (deltaLambda > Math.PI) { - deltaLambda -= 2 * Math.PI; - } - if (deltaLambda < -Math.PI) { - deltaLambda += 2 * Math.PI; - } - - const deltaPsi = Math.log( - Math.tan(phi2 / 2 + Math.PI / 4) / Math.tan(phi1 / 2 + Math.PI / 4) - ); - - const theta = Math.atan2(deltaLambda, deltaPsi); - - return (radiansToDegrees(theta) + 360) % 360; -} - -export default rhumbBearing; - */ diff --git a/test/components/rhumb_bearing_test.dart b/test/components/rhumb_bearing_test.dart index dc42692..61893e1 100644 --- a/test/components/rhumb_bearing_test.dart +++ b/test/components/rhumb_bearing_test.dart @@ -44,51 +44,3 @@ main() { }, ); } - -/* -const fs = require("fs"); -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"); -const rhumbBearing = require("./index").default; - -const directories = { - in: path.join(__dirname, "test", "in") + path.sep, - out: path.join(__dirname, "test", "out") + path.sep, -}; - -let fixtures = fs.readdirSync(directories.in).map((filename) => { - return { - filename, - name: path.parse(filename).name, - geojson: load.sync(directories.in + filename), - }; -}); - -test("bearing", (t) => { - fixtures.forEach((fixture) => { - const name = fixture.name; - const geojson = fixture.geojson; - - const start = geojson.features[0]; - const end = geojson.features[1]; - - const initialBearing = rhumbBearing(start, end); - const finalBearing = rhumbBearing(start, end, { final: true }); - - const result = { - initialBearing: initialBearing, - finalBearing: finalBearing, - }; - if (process.env.REGEN) write.sync(directories.out + name + ".json", result); - t.deepEqual(load.sync(directories.out + name + ".json"), result, name); - }); - - t.throws(() => { - rhumbBearing(point([12, -54]), "point"); - }, "invalid point"); - t.end(); -}); -*/