diff --git a/.gitignore b/.gitignore index 360fc0f5..bd71dd88 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ coverage/ .idea/ +.DS_Store \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 36864a3f..4f825f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.0.9 + +- Implements `length`, `along` [#153](https://github.com/dartclub/turf_dart/pull/153) +- Documentation: Improves pub.dev scores by fixing bad links in Readme.md + ## 0.0.8 - Implements `transformRotate`, `rhumbDistance`, `rhumbDestination`, `centroid` [#147](https://github.com/dartclub/turf_dart/pull/147) @@ -38,7 +43,8 @@ ## 0.0.5 -- Implements *all* meta functions and`lineSegment` + +- Implements *all* meta functions and `lineSegment` - Adds a lot of documentation - Several bug and type fixes diff --git a/README.md b/README.md index a20a98f7..c0d28b11 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 @@ -123,7 +123,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the - [ ] dissolve - [ ] intersect - [ ] lineOffset -- [x] [polygonSmooth](ttps://github.com/dartclub/turf_dart/blob/main/lib/src/polygon_smooth.dart) +- [x] [polygonSmooth](https://github.com/dartclub/turf_dart/blob/main/lib/src/polygon_smooth.dart) - [ ] simplify - [ ] tesselate - [x] [transformRotate](https://github.com/dartclub/turf_dart/blob/main/lib/src/transform_rotate.dart) @@ -234,7 +234,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the - [x] [booleanCrosses](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_crosses.dart) - [x] [booleanDisjoint](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_disjoint.dart) - [x] [booleanEqual](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_equal.dart) -- [x] [booleanIntersects](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_intersect.dart) +- [x] [booleanIntersects](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_intersects.dart) - [ ] booleanOverlap - [x] [booleanParallel](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_parallel.dart) - [x] [booleanPointInPolygon](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_point_in_polygon.dart) @@ -252,4 +252,4 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the - [x] [radiansToLength](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart) - [x] [radiansToDegrees](https://github.com/dartclub/turf_dart/blob/main/lib/src/helpers.dart) - [ ] toMercator -- [ ] toWgs84 +- [ ] toWgs84 \ No newline at end of file diff --git a/lib/along.dart b/lib/along.dart new file mode 100644 index 00000000..d8d18fa5 --- /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 00000000..fa72f3ee --- /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 00000000..f4b50267 --- /dev/null +++ b/lib/src/along.dart @@ -0,0 +1,54 @@ +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 +Feature 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 Feature(geometry: 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 Feature(geometry: interpolated); + } else { + travelled += measure_distance.distance(Point(coordinates: coords[i]), + Point(coordinates: coords[i + 1]), unit); + } + } + return Feature( + geometry: Point(coordinates: coords[coords.length - 1])); +} diff --git a/lib/src/length.dart b/lib/src/length.dart new file mode 100644 index 00000000..b7d5e1f7 --- /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/src/nearest_point_on_line.dart b/lib/src/nearest_point_on_line.dart index 12554074..4656bef1 100644 --- a/lib/src/nearest_point_on_line.dart +++ b/lib/src/nearest_point_on_line.dart @@ -183,6 +183,13 @@ _NearestMulti? _nearestPointOnMultiLine( } /// Takes a [Point] and a [LineString] and calculates the closest Point on the [LineString]. +/// +/// The properties of returned [Point] will contain three values: +/// * index: closest point was found on nth line part +/// * dist: distance between [point] and the closest point on line +/// * location: distance along the line between start and the closest point. +/// +/// Example: /// ```dart /// var line = LineString( /// coordinates: [ diff --git a/lib/turf.dart b/lib/turf.dart index 01f1bc68..25d03aea 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/pubspec.yaml b/pubspec.yaml index d2f9c492..26a6587b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,23 @@ name: turf description: A turf.js-like geospatial analysis library working with GeoJSON, written in pure Dart. -version: 0.0.8 +version: 0.0.9 environment: - sdk: ">=2.15.0 <4.0.0" + sdk: ">=2.17.0 <4.0.0" homepage: https://github.com/dartclub/turf_dart repository: https://github.com/dartclub/turf_dart dependencies: json_annotation: ^4.8.1 - turf_equality: ^0.0.2 - turf_pip: ^0.0.1+1 + + turf_equality: ^0.0.3 + turf_pip: ^0.0.2 rbush: ^1.1.0 - sweepline_intersections: ^0.0.3+1 + sweepline_intersections: ^0.0.4 dev_dependencies: - lints: ^2.1.1 + lints: ^3.0.0 test: ^1.24.3 json_serializable: ^6.7.0 build_runner: ^2.4.5 - analyzer: ^5.13.0 + analyzer: ^6.4.0 benchmark: ^0.3.0 diff --git a/test/.DS_Store b/test/.DS_Store deleted file mode 100644 index 8dca1b0c..00000000 Binary files a/test/.DS_Store and /dev/null differ diff --git a/test/components/along_test.dart b/test/components/along_test.dart new file mode 100644 index 00000000..f2c9d304 --- /dev/null +++ b/test/components/along_test.dart @@ -0,0 +1,98 @@ +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.geometry, isNotNull); + expect(resolvedViaPoint.geometry!.coordinates, equals(via)); + }); + test('along - to start point', () { + final resolvedStartPoint = along(line, 0, Unit.meters); + expect(resolvedStartPoint.geometry, isNotNull); + expect(resolvedStartPoint.geometry!.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.geometry, isNotNull); + expect(resolvedViaPoint.geometry!.coordinates.lat.toStringAsFixed(6), + equals('55.709028')); + expect(resolvedViaPoint.geometry!.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.geometry, isNotNull); + expect(resolvedViaPoint.geometry!.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.geometry, isNotNull); + expect(resolvedViaPoint.geometry!.coordinates.lat.toStringAsFixed(6), + equals('55.708330')); + expect(resolvedViaPoint.geometry!.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.geometry, isNotNull); + expect(resolvedEndPoint.geometry!.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.geometry, isNotNull); + expect(resolvedEndPoint.geometry!.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.geometry, isNotNull); + expect(resolvedEndPoint.geometry!.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 00000000..d93278fc --- /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, + ], + ), +); diff --git a/test/examples/.DS_Store b/test/examples/.DS_Store deleted file mode 100644 index 0c63b13f..00000000 Binary files a/test/examples/.DS_Store and /dev/null differ diff --git a/test/examples/booleans/.DS_Store b/test/examples/booleans/.DS_Store deleted file mode 100644 index f22ab784..00000000 Binary files a/test/examples/booleans/.DS_Store and /dev/null differ diff --git a/test/examples/booleans/disjoint/.DS_Store b/test/examples/booleans/disjoint/.DS_Store deleted file mode 100644 index 78e31eef..00000000 Binary files a/test/examples/booleans/disjoint/.DS_Store and /dev/null differ