diff --git a/lib/line_slice.dart b/lib/line_slice.dart new file mode 100644 index 0000000..dafdd38 --- /dev/null +++ b/lib/line_slice.dart @@ -0,0 +1,3 @@ +library turf_along; + +export "src/line_slice.dart"; diff --git a/lib/src/line_slice.dart b/lib/src/line_slice.dart new file mode 100644 index 0000000..8f41051 --- /dev/null +++ b/lib/src/line_slice.dart @@ -0,0 +1,36 @@ +import 'package:turf/helpers.dart'; +import 'package:turf/nearest_point_on_line.dart'; +import 'package:turf/src/invariant.dart'; + +/// Takes a [line], at a start point [startPt], and a stop point [stopPt] +/// and returns a subsection of the line in-between those points. +/// The start & stop points don't need to fall exactly on the line. +/// +/// This can be useful for extracting only the part of a route between waypoints. +Feature lineSlice( + Point startPt, Point stopPt, Feature line) { + final coords = line.geometry; + if (coords == null) { + throw Exception('line has no geometry'); + } + + final startVertex = nearestPointOnLine(coords, startPt); + final stopVertex = nearestPointOnLine(coords, stopPt); + late final List> ends; + if (startVertex.properties!['index'] <= stopVertex.properties!['index']) { + ends = [startVertex, stopVertex]; + } else { + ends = [stopVertex, startVertex]; + } + final List clipCoords = [getCoord(ends[0])]; + for (var i = ends[0].properties!['index'] + 1; + i < ends[1].properties!['index'] + 1; + i++) { + clipCoords.add(coords.coordinates[i]); + } + clipCoords.add(getCoord(ends[1])); + return Feature( + geometry: LineString(coordinates: clipCoords), + properties: line.properties, + ); +} diff --git a/test/components/line_slice_test.dart b/test/components/line_slice_test.dart new file mode 100644 index 0000000..cf42499 --- /dev/null +++ b/test/components/line_slice_test.dart @@ -0,0 +1,73 @@ +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'; +import 'package:turf/src/line_slice.dart'; + +void main() { + test('lineSlice - exact points', () { + final slice = lineSlice( + Point(coordinates: start), Point(coordinates: via), lineFeature); + expect(slice.properties, isNotNull); + expect(slice.properties!.keys, contains(propName)); + expect(slice.properties![propName], equals(propValue)); + + final expectedLineFeature = Feature( + geometry: LineString(coordinates: [start, via]), + ); + expect(slice.geometry, isNotNull); + expect(length(slice).round(), equals(length(expectedLineFeature).round())); + }); + test('lineSlice - interpolation', () { + const skipDist = 10; + + final startPt = along(lineFeature, skipDist, Unit.meters); + expect(startPt, isNotNull); + + final slice = lineSlice(startPt, Point(coordinates: via), lineFeature); + expect(slice.properties, isNotNull); + expect(slice.properties!.keys, contains(propName)); + expect(slice.properties![propName], equals(propValue)); + + final expectedLine = Feature( + geometry: LineString(coordinates: [start, via]), + ); + expect(slice.geometry, isNotNull); + expect( + length(slice, Unit.meters).round(), + equals(length(expectedLine, Unit.meters).round() - skipDist), + ); + + // Sanity check of test data. No interpolation occurs if start and via are skipDist apart. + expect(distance(Point(coordinates: start), Point(coordinates: via)).round(), + isNot(equals(skipDist))); + }); +} + +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, +); +const propName = 'prop1'; +const propValue = 1; +final lineFeature = Feature( + geometry: LineString( + coordinates: [ + start, + via, + end, + ], + ), + properties: { + propName: propValue, + }, +);