Skip to content

Commit 4de3bab

Browse files
authored
add initial line_slice_along implementation (#190)
1 parent da48c29 commit 4de3bab

File tree

8 files changed

+30786
-1
lines changed

8 files changed

+30786
-1
lines changed

Progress.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Dart. This is an on going project and functions are being added once needed. If
7777
- [x] [lineOverlap](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_overlap.dart)
7878
- [x] [lineSegment](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_segment.dart)
7979
- [x] [lineSlice](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_slice.dart)
80-
- [ ] lineSliceAlong
80+
- [x] [lineSliceAlong](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_slice_along.dart)
8181
- [ ] lineSplit
8282
- [ ] mask
8383
- [x] [nearestPointOnLine](https://github.com/dartclub/turf_dart/blob/main/lib/src/nearest_point_on_line.dart)

lib/line_slice_along.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
library turf_line_slice_along;
2+
3+
export 'package:geotypes/geotypes.dart';
4+
export "src/line_slice_along.dart";

lib/src/line_slice_along.dart

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'package:turf/bearing.dart';
2+
import 'package:turf/destination.dart';
3+
import 'package:turf/distance.dart';
4+
import 'package:turf/helpers.dart';
5+
6+
/// Takes a [line], at a start distance [startDist] and a stop distance [stopDist]
7+
/// and returns a subsection of the line in-between those distances.
8+
///
9+
/// If [startDist] and [stopDist] resolve to the same point on [line], null is returned
10+
/// as the resolved line would only contain one point which isn't supported by LineString.
11+
///
12+
/// This can be useful for extracting only the part of a route between distances on the route.
13+
LineString lineSliceAlongRaw(
14+
LineString line,
15+
double startDist,
16+
double stopDist, [
17+
Unit unit = Unit.kilometers,
18+
]) {
19+
List<Position> coords = line.coordinates;
20+
var slice = <Position>[];
21+
22+
var origCoordsLength = coords.length;
23+
double travelled = 0;
24+
double? overshot;
25+
double direction = 0;
26+
Position? interpolated;
27+
for (var i = 0; i < coords.length; i++) {
28+
if (startDist >= travelled && i == coords.length - 1) {
29+
break;
30+
} else if (travelled > startDist && slice.isEmpty) {
31+
overshot = startDist - travelled;
32+
if (overshot == 0) {
33+
slice.add(coords[i]);
34+
return LineString(coordinates: slice);
35+
}
36+
direction = bearingRaw(coords[i], coords[i - 1]) - 180;
37+
interpolated = destinationRaw(coords[i], overshot, direction, unit);
38+
slice.add(interpolated);
39+
}
40+
41+
if (travelled >= stopDist) {
42+
overshot = stopDist - travelled;
43+
if (overshot == 0) {
44+
slice.add(coords[i]);
45+
return LineString(coordinates: slice);
46+
}
47+
direction = bearingRaw(coords[i], coords[i - 1]) - 180;
48+
interpolated = destinationRaw(coords[i], overshot, direction, unit);
49+
slice.add(interpolated);
50+
return LineString(coordinates: slice);
51+
}
52+
53+
if (travelled >= startDist) {
54+
slice.add(coords[i]);
55+
}
56+
57+
if (i == coords.length - 1) {
58+
return LineString(coordinates: slice);
59+
}
60+
61+
travelled += distanceRaw(coords[i], coords[i + 1], unit);
62+
}
63+
64+
if (travelled < startDist && coords.length == origCoordsLength) {
65+
throw Exception("Start position is beyond line");
66+
}
67+
68+
final last = coords[coords.length - 1];
69+
return LineString(coordinates: [last, last]);
70+
}
71+
72+
/// Takes a [line], at a start distance [startDist] and a stop distance [stopDist]
73+
/// and returns a subsection of the line in-between those distances.
74+
///
75+
/// If [startDist] and [stopDist] resolve to the same point on [line], null is returned
76+
/// as the resolved line would only contain one point which isn't supported by LineString.
77+
///
78+
/// This can be useful for extracting only the part of a route between distances on the route.
79+
Feature<LineString> lineSliceAlong(
80+
Feature<LineString> line,
81+
double startDist,
82+
double stopDist, [
83+
Unit unit = Unit.kilometers,
84+
]) {
85+
return Feature<LineString>(
86+
geometry: lineSliceAlongRaw(line.geometry!, startDist, stopDist, unit),
87+
properties: line.properties,
88+
);
89+
}

lib/turf.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export 'line_intersect.dart';
2222
export 'line_overlap.dart';
2323
export 'line_segment.dart';
2424
export 'line_slice.dart';
25+
export 'line_slice_along.dart';
2526
export 'line_to_polygon.dart';
2627
export 'meta.dart';
2728
export 'midpoint.dart';
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import 'package:test/test.dart';
2+
import 'package:turf/turf.dart';
3+
4+
import '../context/load_test_cases.dart';
5+
6+
void main() {
7+
loadGeoJson('./test/examples/line_slice_along/fixtures/line1.geojson',
8+
(path, geoJson) {
9+
final line1 = Feature<LineString>(
10+
geometry: (geoJson as Feature).geometry as LineString,
11+
);
12+
13+
test('turf-line-slice-along -- line1', () {
14+
const start = 500.0;
15+
const stop = 750.0;
16+
const options = Unit.miles;
17+
18+
final startPoint = along(line1, start, options);
19+
final endPoint = along(line1, stop, options);
20+
final sliced = lineSliceAlong(line1, start, stop, options);
21+
22+
expect(sliced, isA<Feature<LineString>>());
23+
expect(sliced.type, GeoJSONObjectType.feature);
24+
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
25+
expect(sliced.geometry?.coordinates[0],
26+
equals(startPoint.geometry!.coordinates));
27+
expect(
28+
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
29+
equals(endPoint.geometry!.coordinates),
30+
);
31+
});
32+
33+
test('turf-line-slice-along -- line1 overshoot', () {
34+
const start = 500.0;
35+
const stop = 1500.0;
36+
const options = Unit.miles;
37+
38+
final startPoint = along(line1, start, options);
39+
final endPoint = along(line1, stop, options);
40+
final sliced = lineSliceAlong(line1, start, stop, options);
41+
42+
expect(sliced, isA<Feature<LineString>>());
43+
expect(sliced.type, GeoJSONObjectType.feature);
44+
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
45+
expect(sliced.geometry?.coordinates[0],
46+
equals(startPoint.geometry!.coordinates));
47+
expect(
48+
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
49+
equals(endPoint.geometry!.coordinates),
50+
);
51+
});
52+
53+
test('turf-line-slice-along -- start longer than line length', () {
54+
const start = 500000.0;
55+
const stop = 800000.0;
56+
const options = Unit.miles;
57+
58+
expect(
59+
() => lineSliceAlong(line1, start, stop, options),
60+
throwsA(isA<Exception>().having(
61+
(e) => e.toString(),
62+
'message',
63+
contains('Start position is beyond line'),
64+
)),
65+
);
66+
});
67+
68+
test('turf-line-slice-along -- start equal to line length', () {
69+
const options = Unit.miles;
70+
final start = length(line1, options);
71+
final stop = start + 100;
72+
73+
final startPoint = along(line1, start, options);
74+
final endPoint = along(line1, stop, options);
75+
final sliced =
76+
lineSliceAlong(line1, start.toDouble(), stop.toDouble(), options);
77+
78+
expect(sliced, isA<Feature<LineString>>());
79+
expect(sliced.type, GeoJSONObjectType.feature);
80+
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
81+
expect(sliced.geometry?.coordinates[0],
82+
equals(startPoint.geometry!.coordinates));
83+
expect(
84+
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
85+
endPoint.geometry!.coordinates,
86+
);
87+
});
88+
});
89+
90+
loadGeoJson('./test/examples/line_slice_along/fixtures/route1.geojson',
91+
(path, geoJson) {
92+
final route1 = Feature<LineString>(
93+
geometry: (geoJson as Feature).geometry as LineString,
94+
);
95+
96+
test('turf-line-slice-along -- route1', () {
97+
const start = 500.0;
98+
const stop = 750.0;
99+
const options = Unit.miles;
100+
101+
final startPoint = along(route1, start, options);
102+
final endPoint = along(route1, stop, options);
103+
final sliced = lineSliceAlong(route1, start, stop, options);
104+
105+
expect(sliced, isA<Feature<LineString>>());
106+
expect(sliced.type, GeoJSONObjectType.feature);
107+
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
108+
expect(sliced.geometry?.coordinates[0],
109+
equals(startPoint.geometry!.coordinates));
110+
expect(
111+
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
112+
equals(endPoint.geometry!.coordinates),
113+
);
114+
});
115+
});
116+
117+
loadGeoJson('./test/examples/line_slice_along/fixtures/route2.geojson',
118+
(path, geoJson) {
119+
final route2 = Feature<LineString>(
120+
geometry: (geoJson as Feature).geometry as LineString,
121+
);
122+
123+
test('turf-line-slice-along -- route2', () {
124+
const start = 25.0;
125+
const stop = 50.0;
126+
const options = Unit.miles;
127+
128+
final startPoint = along(route2, start, options);
129+
final endPoint = along(route2, stop, options);
130+
final sliced = lineSliceAlong(route2, start, stop, options);
131+
132+
expect(sliced, isA<Feature<LineString>>());
133+
expect(sliced.type, GeoJSONObjectType.feature);
134+
expect(sliced.geometry?.type, GeoJSONObjectType.lineString);
135+
expect(sliced.geometry?.coordinates[0],
136+
equals(startPoint.geometry!.coordinates));
137+
expect(
138+
sliced.geometry?.coordinates[sliced.geometry!.coordinates.length - 1],
139+
equals(endPoint.geometry!.coordinates),
140+
);
141+
});
142+
});
143+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"type": "Feature",
3+
"properties": {},
4+
"geometry": {
5+
"type": "LineString",
6+
"coordinates": [
7+
[113.99414062499999, 22.350075806124867],
8+
[116.76269531249999, 23.241346102386135],
9+
[117.7734375, 24.367113562651276],
10+
[118.828125, 25.20494115356912],
11+
[119.794921875, 26.78484736105119],
12+
[120.80566406250001, 28.110748760633534],
13+
[121.59667968749999, 29.49698759653577],
14+
[121.59667968749999, 31.12819929911196],
15+
[120.84960937499999, 32.84267363195431],
16+
[119.83886718750001, 34.125447565116126],
17+
[118.69628906249999, 35.31736632923788],
18+
[121.4208984375, 36.80928470205937],
19+
[122.82714843749999, 37.37015718405753]
20+
]
21+
}
22+
}

0 commit comments

Comments
 (0)