Skip to content

Commit

Permalink
add initial line_slice_along implementation (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukas-h authored Aug 10, 2024
1 parent da48c29 commit 4de3bab
Show file tree
Hide file tree
Showing 8 changed files with 30,786 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Dart. This is an on going project and functions are being added once needed. If
- [x] [lineOverlap](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_overlap.dart)
- [x] [lineSegment](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_segment.dart)
- [x] [lineSlice](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_slice.dart)
- [ ] lineSliceAlong
- [x] [lineSliceAlong](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_slice_along.dart)
- [ ] lineSplit
- [ ] mask
- [x] [nearestPointOnLine](https://github.com/dartclub/turf_dart/blob/main/lib/src/nearest_point_on_line.dart)
Expand Down
4 changes: 4 additions & 0 deletions lib/line_slice_along.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
library turf_line_slice_along;

export 'package:geotypes/geotypes.dart';
export "src/line_slice_along.dart";
89 changes: 89 additions & 0 deletions lib/src/line_slice_along.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'package:turf/bearing.dart';
import 'package:turf/destination.dart';
import 'package:turf/distance.dart';
import 'package:turf/helpers.dart';

/// Takes a [line], at a start distance [startDist] and a stop distance [stopDist]
/// and returns a subsection of the line in-between those distances.
///
/// If [startDist] and [stopDist] resolve to the same point on [line], null is returned
/// as the resolved line would only contain one point which isn't supported by LineString.
///
/// This can be useful for extracting only the part of a route between distances on the route.
LineString lineSliceAlongRaw(
LineString line,
double startDist,
double stopDist, [
Unit unit = Unit.kilometers,
]) {
List<Position> coords = line.coordinates;
var slice = <Position>[];

var origCoordsLength = coords.length;
double travelled = 0;
double? overshot;
double direction = 0;
Position? interpolated;
for (var i = 0; i < coords.length; i++) {
if (startDist >= travelled && i == coords.length - 1) {
break;
} else if (travelled > startDist && slice.isEmpty) {
overshot = startDist - travelled;
if (overshot == 0) {
slice.add(coords[i]);
return LineString(coordinates: slice);
}
direction = bearingRaw(coords[i], coords[i - 1]) - 180;
interpolated = destinationRaw(coords[i], overshot, direction, unit);
slice.add(interpolated);
}

if (travelled >= stopDist) {
overshot = stopDist - travelled;
if (overshot == 0) {
slice.add(coords[i]);
return LineString(coordinates: slice);
}
direction = bearingRaw(coords[i], coords[i - 1]) - 180;
interpolated = destinationRaw(coords[i], overshot, direction, unit);
slice.add(interpolated);
return LineString(coordinates: slice);
}

if (travelled >= startDist) {
slice.add(coords[i]);
}

if (i == coords.length - 1) {
return LineString(coordinates: slice);
}

travelled += distanceRaw(coords[i], coords[i + 1], unit);
}

if (travelled < startDist && coords.length == origCoordsLength) {
throw Exception("Start position is beyond line");
}

final last = coords[coords.length - 1];
return LineString(coordinates: [last, last]);
}

/// Takes a [line], at a start distance [startDist] and a stop distance [stopDist]
/// and returns a subsection of the line in-between those distances.
///
/// If [startDist] and [stopDist] resolve to the same point on [line], null is returned
/// as the resolved line would only contain one point which isn't supported by LineString.
///
/// This can be useful for extracting only the part of a route between distances on the route.
Feature<LineString> lineSliceAlong(
Feature<LineString> line,
double startDist,
double stopDist, [
Unit unit = Unit.kilometers,
]) {
return Feature<LineString>(
geometry: lineSliceAlongRaw(line.geometry!, startDist, stopDist, unit),
properties: line.properties,
);
}
1 change: 1 addition & 0 deletions lib/turf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export 'line_intersect.dart';
export 'line_overlap.dart';
export 'line_segment.dart';
export 'line_slice.dart';
export 'line_slice_along.dart';
export 'line_to_polygon.dart';
export 'meta.dart';
export 'midpoint.dart';
Expand Down
143 changes: 143 additions & 0 deletions test/components/line_slice_along_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import 'package:test/test.dart';
import 'package:turf/turf.dart';

import '../context/load_test_cases.dart';

void main() {
loadGeoJson('./test/examples/line_slice_along/fixtures/line1.geojson',
(path, geoJson) {
final line1 = Feature<LineString>(
geometry: (geoJson as Feature).geometry as LineString,
);

test('turf-line-slice-along -- line1', () {
const start = 500.0;
const stop = 750.0;
const options = Unit.miles;

final startPoint = along(line1, start, options);
final endPoint = along(line1, stop, options);
final sliced = lineSliceAlong(line1, start, stop, options);

expect(sliced, isA<Feature<LineString>>());
expect(sliced.type, GeoJSONObjectType.feature);
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
expect(sliced.geometry?.coordinates[0],
equals(startPoint.geometry!.coordinates));
expect(
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
equals(endPoint.geometry!.coordinates),
);
});

test('turf-line-slice-along -- line1 overshoot', () {
const start = 500.0;
const stop = 1500.0;
const options = Unit.miles;

final startPoint = along(line1, start, options);
final endPoint = along(line1, stop, options);
final sliced = lineSliceAlong(line1, start, stop, options);

expect(sliced, isA<Feature<LineString>>());
expect(sliced.type, GeoJSONObjectType.feature);
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
expect(sliced.geometry?.coordinates[0],
equals(startPoint.geometry!.coordinates));
expect(
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
equals(endPoint.geometry!.coordinates),
);
});

test('turf-line-slice-along -- start longer than line length', () {
const start = 500000.0;
const stop = 800000.0;
const options = Unit.miles;

expect(
() => lineSliceAlong(line1, start, stop, options),
throwsA(isA<Exception>().having(
(e) => e.toString(),
'message',
contains('Start position is beyond line'),
)),
);
});

test('turf-line-slice-along -- start equal to line length', () {
const options = Unit.miles;
final start = length(line1, options);
final stop = start + 100;

final startPoint = along(line1, start, options);
final endPoint = along(line1, stop, options);
final sliced =
lineSliceAlong(line1, start.toDouble(), stop.toDouble(), options);

expect(sliced, isA<Feature<LineString>>());
expect(sliced.type, GeoJSONObjectType.feature);
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
expect(sliced.geometry?.coordinates[0],
equals(startPoint.geometry!.coordinates));
expect(
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
endPoint.geometry!.coordinates,
);
});
});

loadGeoJson('./test/examples/line_slice_along/fixtures/route1.geojson',
(path, geoJson) {
final route1 = Feature<LineString>(
geometry: (geoJson as Feature).geometry as LineString,
);

test('turf-line-slice-along -- route1', () {
const start = 500.0;
const stop = 750.0;
const options = Unit.miles;

final startPoint = along(route1, start, options);
final endPoint = along(route1, stop, options);
final sliced = lineSliceAlong(route1, start, stop, options);

expect(sliced, isA<Feature<LineString>>());
expect(sliced.type, GeoJSONObjectType.feature);
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
expect(sliced.geometry?.coordinates[0],
equals(startPoint.geometry!.coordinates));
expect(
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
equals(endPoint.geometry!.coordinates),
);
});
});

loadGeoJson('./test/examples/line_slice_along/fixtures/route2.geojson',
(path, geoJson) {
final route2 = Feature<LineString>(
geometry: (geoJson as Feature).geometry as LineString,
);

test('turf-line-slice-along -- route2', () {
const start = 25.0;
const stop = 50.0;
const options = Unit.miles;

final startPoint = along(route2, start, options);
final endPoint = along(route2, stop, options);
final sliced = lineSliceAlong(route2, start, stop, options);

expect(sliced, isA<Feature<LineString>>());
expect(sliced.type, GeoJSONObjectType.feature);
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
expect(sliced.geometry?.coordinates[0],
equals(startPoint.geometry!.coordinates));
expect(
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
equals(endPoint.geometry!.coordinates),
);
});
});
}
22 changes: 22 additions & 0 deletions test/examples/line_slice_along/fixtures/line1.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[113.99414062499999, 22.350075806124867],
[116.76269531249999, 23.241346102386135],
[117.7734375, 24.367113562651276],
[118.828125, 25.20494115356912],
[119.794921875, 26.78484736105119],
[120.80566406250001, 28.110748760633534],
[121.59667968749999, 29.49698759653577],
[121.59667968749999, 31.12819929911196],
[120.84960937499999, 32.84267363195431],
[119.83886718750001, 34.125447565116126],
[118.69628906249999, 35.31736632923788],
[121.4208984375, 36.80928470205937],
[122.82714843749999, 37.37015718405753]
]
}
}
Loading

0 comments on commit 4de3bab

Please sign in to comment.