diff --git a/README.md b/README.md index a20a98f..d6e131f 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the ### Measurement -- [ ] along +- [x] [along](https://github.com/dartclub/turf_dart/blob/main/lib/src/along.dart) - [x] [area](https://github.com/dartclub/turf_dart/blob/main/lib/src/area.dart) - [x] [bbox](https://github.com/dartclub/turf_dart/blob/main/lib/src/bbox.dart) - [x] [bboxPolygon](https://github.com/dartclub/turf_dart/blob/main/lib/src/bbox_polygon.dart) @@ -91,7 +91,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the - [x] [destination](https://github.com/dartclub/turf_dart/blob/main/lib/src/destination.dart) - [x] [distance](https://github.com/dartclub/turf_dart/blob/main/lib/src/distance.dart) - [ ] envelope -- [ ] length +- [x] [length](https://github.com/dartclub/turf_dart/blob/main/lib/src/length.dart) - [x] [midpoint](https://github.com/dartclub/turf_dart/blob/main/lib/src/midpoint.dart) - [ ] pointOnFeature - [ ] polygonTangents diff --git a/lib/along.dart b/lib/along.dart new file mode 100644 index 0000000..d8d18fa --- /dev/null +++ b/lib/along.dart @@ -0,0 +1,3 @@ +library turf_along; + +export "src/along.dart"; diff --git a/lib/length.dart b/lib/length.dart new file mode 100644 index 0000000..fa72f3e --- /dev/null +++ b/lib/length.dart @@ -0,0 +1,3 @@ +library turf_length; + +export "src/length.dart"; diff --git a/lib/src/along.dart b/lib/src/along.dart new file mode 100644 index 0000000..9fb4c7e --- /dev/null +++ b/lib/src/along.dart @@ -0,0 +1,53 @@ +import 'dart:math'; + +import 'package:turf/bearing.dart'; +import 'package:turf/destination.dart'; +import 'package:turf/helpers.dart'; +import 'package:turf/length.dart'; +import 'package:turf/src/distance.dart' as measure_distance; +import 'package:turf/src/invariant.dart'; + +/// Takes a [line] and returns a [Point] at a specified [distance] along the line. +/// +/// If [distance] is less than 0, it will count distance along the line from end +/// to start of line. If negative [distance] overshoots the length of the line, +/// the start point of the line is returned. +/// If [distance] is larger than line length, the end point is returned +/// If [line] have no geometry or coordinates, an Exception is thrown +Point along(Feature line, num distance, + [Unit unit = Unit.kilometers]) { + // Get Coords + final coords = getCoords(line); + if (coords.isEmpty) { + throw Exception('line must contain at least one coordinate'); + } + if (distance < 0) { + distance = max(0, length(line, unit) + distance); + } + num travelled = 0; + for (int i = 0; i < coords.length; i++) { + if (distance >= travelled && i == coords.length - 1) { + break; + } + if (travelled == distance) { + return Point(coordinates: coords[i]); + } + if (travelled > distance) { + final overshot = distance - travelled; + final direction = bearing(Point(coordinates: coords[i]), + Point(coordinates: coords[i - 1])) - + 180; + final interpolated = destination( + Point(coordinates: coords[i]), + overshot, + direction, + unit, + ); + return interpolated; + } else { + travelled += measure_distance.distance(Point(coordinates: coords[i]), + Point(coordinates: coords[i + 1]), unit); + } + } + return Point(coordinates: coords[coords.length - 1]); +} diff --git a/lib/src/length.dart b/lib/src/length.dart new file mode 100644 index 0000000..b7d5e1f --- /dev/null +++ b/lib/src/length.dart @@ -0,0 +1,25 @@ +import 'package:turf/distance.dart'; +import 'package:turf/helpers.dart'; +import 'package:turf/line_segment.dart'; + +/// Takes a [line] and measures its length in the specified [unit]. +num length(Feature line, [Unit unit = Unit.kilometers]) { + return segmentReduce(line, ( + previousValue, + currentSegment, + initialValue, + featureIndex, + multiFeatureIndex, + geometryIndex, + segmentIndex, + ) { + final coords = currentSegment.geometry!.coordinates; + return previousValue! + + distance( + Point(coordinates: coords[0]), + Point(coordinates: coords[1]), + unit, + ); + }, 0.0) ?? + 0.0; +} diff --git a/lib/turf.dart b/lib/turf.dart index 01f1bc6..25d03ae 100644 --- a/lib/turf.dart +++ b/lib/turf.dart @@ -1,5 +1,6 @@ library turf; +export 'src/along.dart'; export 'src/area.dart'; export 'src/bbox.dart'; export 'src/bearing.dart'; @@ -9,6 +10,7 @@ export 'src/destination.dart'; export 'src/distance.dart'; export 'src/geojson.dart'; export 'src/helpers.dart'; +export 'src/length.dart'; export 'src/midpoint.dart'; export 'src/nearest_point.dart'; export 'src/polyline.dart'; diff --git a/test/components/along_test.dart b/test/components/along_test.dart new file mode 100644 index 0000000..dbc81a1 --- /dev/null +++ b/test/components/along_test.dart @@ -0,0 +1,90 @@ +import 'package:test/test.dart'; +import 'package:turf/along.dart'; +import 'package:turf/distance.dart'; +import 'package:turf/helpers.dart'; +import 'package:turf/length.dart'; + +void main() { + test('along - negative distance should count backwards', () { + final viaToEndDistance = + distance(Point(coordinates: via), Point(coordinates: end), Unit.meters); + expect(viaToEndDistance.round(), equals(198)); + final resolvedViaPoint = along(line, -1 * viaToEndDistance, Unit.meters); + expect(resolvedViaPoint.coordinates, equals(via)); + }); + test('along - to start point', () { + final resolvedStartPoint = along(line, 0, Unit.meters); + expect(resolvedStartPoint.coordinates, equals(start)); + }); + test('along - to point between start and via', () { + final startToViaDistance = distance( + Point(coordinates: start), Point(coordinates: via), Unit.meters); + expect(startToViaDistance.round(), equals(57)); + final resolvedViaPoint = along(line, startToViaDistance / 2, Unit.meters); + expect(resolvedViaPoint.coordinates.lat.toStringAsFixed(6), + equals('55.709028')); + expect(resolvedViaPoint.coordinates.lng.toStringAsFixed(6), + equals('13.185096')); + }); + test('along - to via point', () { + final startToViaDistance = distance( + Point(coordinates: start), Point(coordinates: via), Unit.meters); + expect(startToViaDistance.round(), equals(57)); + final resolvedViaPoint = along(line, startToViaDistance, Unit.meters); + expect(resolvedViaPoint.coordinates, equals(via)); + }); + test('along - to point between via and end', () { + final startToViaDistance = distance( + Point(coordinates: start), Point(coordinates: via), Unit.meters); + final viaToEndDistance = + distance(Point(coordinates: via), Point(coordinates: end), Unit.meters); + expect(startToViaDistance.round(), equals(57)); + expect(viaToEndDistance.round(), equals(198)); + final resolvedViaPoint = + along(line, startToViaDistance + viaToEndDistance / 2, Unit.meters); + expect(resolvedViaPoint.coordinates.lat.toStringAsFixed(6), + equals('55.708330')); + expect(resolvedViaPoint.coordinates.lng.toStringAsFixed(6), + equals('13.186555')); + }); + test('along - to end point', () { + final len = length(line, Unit.meters); + expect(len.round(), equals(254)); + final resolvedEndPoint = along(line, len, Unit.meters); + expect(resolvedEndPoint.coordinates, equals(end)); + }); + test('along - to end point - default unit (km)', () { + final len = length(line); + expect((len * 1000).round(), equals(254)); + final resolvedEndPoint = along(line, len); + expect(resolvedEndPoint.coordinates, equals(end)); + }); + test('along - beyond end point', () { + final len = length(line, Unit.meters); + expect(len.round(), equals(254)); + final resolvedEndPoint = along(line, len + 100, Unit.meters); + expect(resolvedEndPoint.coordinates, equals(end)); + }); +} + +final start = Position.named( + lat: 55.7090430186194, + lng: 13.184645393920405, +); +final via = Position.named( + lat: 55.70901279569489, + lng: 13.185546616182755, +); +final end = Position.named( + lat: 55.70764669578079, + lng: 13.187563637197076, +); +final line = Feature( + geometry: LineString( + coordinates: [ + start, + via, + end, + ], + ), +); diff --git a/test/components/length_test.dart b/test/components/length_test.dart new file mode 100644 index 0000000..5c45f8d --- /dev/null +++ b/test/components/length_test.dart @@ -0,0 +1,36 @@ +import 'package:test/test.dart'; +import 'package:turf/helpers.dart'; +import 'package:turf/length.dart'; + +void main() { + test('length - in meters', () { + final len = length(line, Unit.meters); + expect(len.round(), equals(254)); + }); + test('length - default unit (km)', () { + final len = length(line); + expect((len * 1000).round(), equals(254)); + }); +} + +final start = Position.named( + lat: 55.7090430186194, + lng: 13.184645393920405, +); +final via = Position.named( + lat: 55.70901279569489, + lng: 13.185546616182755, +); +final end = Position.named( + lat: 55.70764669578079, + lng: 13.187563637197076, +); +final line = Feature( + geometry: LineString( + coordinates: [ + start, + via, + end, + ], + ), +); \ No newline at end of file