Skip to content

Commit bec5af9

Browse files
Implement length and along (#153)
* Implement length with tests * Implement along with tests * Fix export added functions * Document along behaviour when distance is outside line length range * Format along test * Change length and along to accept Feature<LineString> instead of LineString * Change length to never return null. To my understanding, the reason why segmentReduce in its signature may return null is due to initialValue may be null, but in length() we supply 0.0 as the default. * Change along to throw Exception when empty line is passed instead of returning null It appears to be more in line with how turf.js operates and also existing error handling in turf_dart. * Change along to count from back when distance is negative I raised an issue with turf.js about behavour being undefined when distance is zero and they where in favour of counting from back behavour over clamping to the end. I think it is best to use same behavour as upstream turf.js so changing to their solution. * Update along test for negative distance * Fix along test for negative distance * Add along test using default unit * Add test of length() using default unit * Fix docs for along() when distance is negative * Change along() implementation to handle travelled == distance eagerly
1 parent c3537d6 commit bec5af9

File tree

8 files changed

+214
-2
lines changed

8 files changed

+214
-2
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
8080

8181
### Measurement
8282

83-
- [ ] along
83+
- [x] [along](https://github.com/dartclub/turf_dart/blob/main/lib/src/along.dart)
8484
- [x] [area](https://github.com/dartclub/turf_dart/blob/main/lib/src/area.dart)
8585
- [x] [bbox](https://github.com/dartclub/turf_dart/blob/main/lib/src/bbox.dart)
8686
- [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
9191
- [x] [destination](https://github.com/dartclub/turf_dart/blob/main/lib/src/destination.dart)
9292
- [x] [distance](https://github.com/dartclub/turf_dart/blob/main/lib/src/distance.dart)
9393
- [ ] envelope
94-
- [ ] length
94+
- [x] [length](https://github.com/dartclub/turf_dart/blob/main/lib/src/length.dart)
9595
- [x] [midpoint](https://github.com/dartclub/turf_dart/blob/main/lib/src/midpoint.dart)
9696
- [ ] pointOnFeature
9797
- [ ] polygonTangents

lib/along.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
library turf_along;
2+
3+
export "src/along.dart";

lib/length.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
library turf_length;
2+
3+
export "src/length.dart";

lib/src/along.dart

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'dart:math';
2+
3+
import 'package:turf/bearing.dart';
4+
import 'package:turf/destination.dart';
5+
import 'package:turf/helpers.dart';
6+
import 'package:turf/length.dart';
7+
import 'package:turf/src/distance.dart' as measure_distance;
8+
import 'package:turf/src/invariant.dart';
9+
10+
/// Takes a [line] and returns a [Point] at a specified [distance] along the line.
11+
///
12+
/// If [distance] is less than 0, it will count distance along the line from end
13+
/// to start of line. If negative [distance] overshoots the length of the line,
14+
/// the start point of the line is returned.
15+
/// If [distance] is larger than line length, the end point is returned
16+
/// If [line] have no geometry or coordinates, an Exception is thrown
17+
Point along(Feature<LineString> line, num distance,
18+
[Unit unit = Unit.kilometers]) {
19+
// Get Coords
20+
final coords = getCoords(line);
21+
if (coords.isEmpty) {
22+
throw Exception('line must contain at least one coordinate');
23+
}
24+
if (distance < 0) {
25+
distance = max(0, length(line, unit) + distance);
26+
}
27+
num travelled = 0;
28+
for (int i = 0; i < coords.length; i++) {
29+
if (distance >= travelled && i == coords.length - 1) {
30+
break;
31+
}
32+
if (travelled == distance) {
33+
return Point(coordinates: coords[i]);
34+
}
35+
if (travelled > distance) {
36+
final overshot = distance - travelled;
37+
final direction = bearing(Point(coordinates: coords[i]),
38+
Point(coordinates: coords[i - 1])) -
39+
180;
40+
final interpolated = destination(
41+
Point(coordinates: coords[i]),
42+
overshot,
43+
direction,
44+
unit,
45+
);
46+
return interpolated;
47+
} else {
48+
travelled += measure_distance.distance(Point(coordinates: coords[i]),
49+
Point(coordinates: coords[i + 1]), unit);
50+
}
51+
}
52+
return Point(coordinates: coords[coords.length - 1]);
53+
}

lib/src/length.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import 'package:turf/distance.dart';
2+
import 'package:turf/helpers.dart';
3+
import 'package:turf/line_segment.dart';
4+
5+
/// Takes a [line] and measures its length in the specified [unit].
6+
num length(Feature<LineString> line, [Unit unit = Unit.kilometers]) {
7+
return segmentReduce<num>(line, (
8+
previousValue,
9+
currentSegment,
10+
initialValue,
11+
featureIndex,
12+
multiFeatureIndex,
13+
geometryIndex,
14+
segmentIndex,
15+
) {
16+
final coords = currentSegment.geometry!.coordinates;
17+
return previousValue! +
18+
distance(
19+
Point(coordinates: coords[0]),
20+
Point(coordinates: coords[1]),
21+
unit,
22+
);
23+
}, 0.0) ??
24+
0.0;
25+
}

lib/turf.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
library turf;
22

3+
export 'src/along.dart';
34
export 'src/area.dart';
45
export 'src/bbox.dart';
56
export 'src/bearing.dart';
@@ -9,6 +10,7 @@ export 'src/destination.dart';
910
export 'src/distance.dart';
1011
export 'src/geojson.dart';
1112
export 'src/helpers.dart';
13+
export 'src/length.dart';
1214
export 'src/midpoint.dart';
1315
export 'src/nearest_point.dart';
1416
export 'src/polyline.dart';

test/components/along_test.dart

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import 'package:test/test.dart';
2+
import 'package:turf/along.dart';
3+
import 'package:turf/distance.dart';
4+
import 'package:turf/helpers.dart';
5+
import 'package:turf/length.dart';
6+
7+
void main() {
8+
test('along - negative distance should count backwards', () {
9+
final viaToEndDistance =
10+
distance(Point(coordinates: via), Point(coordinates: end), Unit.meters);
11+
expect(viaToEndDistance.round(), equals(198));
12+
final resolvedViaPoint = along(line, -1 * viaToEndDistance, Unit.meters);
13+
expect(resolvedViaPoint.coordinates, equals(via));
14+
});
15+
test('along - to start point', () {
16+
final resolvedStartPoint = along(line, 0, Unit.meters);
17+
expect(resolvedStartPoint.coordinates, equals(start));
18+
});
19+
test('along - to point between start and via', () {
20+
final startToViaDistance = distance(
21+
Point(coordinates: start), Point(coordinates: via), Unit.meters);
22+
expect(startToViaDistance.round(), equals(57));
23+
final resolvedViaPoint = along(line, startToViaDistance / 2, Unit.meters);
24+
expect(resolvedViaPoint.coordinates.lat.toStringAsFixed(6),
25+
equals('55.709028'));
26+
expect(resolvedViaPoint.coordinates.lng.toStringAsFixed(6),
27+
equals('13.185096'));
28+
});
29+
test('along - to via point', () {
30+
final startToViaDistance = distance(
31+
Point(coordinates: start), Point(coordinates: via), Unit.meters);
32+
expect(startToViaDistance.round(), equals(57));
33+
final resolvedViaPoint = along(line, startToViaDistance, Unit.meters);
34+
expect(resolvedViaPoint.coordinates, equals(via));
35+
});
36+
test('along - to point between via and end', () {
37+
final startToViaDistance = distance(
38+
Point(coordinates: start), Point(coordinates: via), Unit.meters);
39+
final viaToEndDistance =
40+
distance(Point(coordinates: via), Point(coordinates: end), Unit.meters);
41+
expect(startToViaDistance.round(), equals(57));
42+
expect(viaToEndDistance.round(), equals(198));
43+
final resolvedViaPoint =
44+
along(line, startToViaDistance + viaToEndDistance / 2, Unit.meters);
45+
expect(resolvedViaPoint.coordinates.lat.toStringAsFixed(6),
46+
equals('55.708330'));
47+
expect(resolvedViaPoint.coordinates.lng.toStringAsFixed(6),
48+
equals('13.186555'));
49+
});
50+
test('along - to end point', () {
51+
final len = length(line, Unit.meters);
52+
expect(len.round(), equals(254));
53+
final resolvedEndPoint = along(line, len, Unit.meters);
54+
expect(resolvedEndPoint.coordinates, equals(end));
55+
});
56+
test('along - to end point - default unit (km)', () {
57+
final len = length(line);
58+
expect((len * 1000).round(), equals(254));
59+
final resolvedEndPoint = along(line, len);
60+
expect(resolvedEndPoint.coordinates, equals(end));
61+
});
62+
test('along - beyond end point', () {
63+
final len = length(line, Unit.meters);
64+
expect(len.round(), equals(254));
65+
final resolvedEndPoint = along(line, len + 100, Unit.meters);
66+
expect(resolvedEndPoint.coordinates, equals(end));
67+
});
68+
}
69+
70+
final start = Position.named(
71+
lat: 55.7090430186194,
72+
lng: 13.184645393920405,
73+
);
74+
final via = Position.named(
75+
lat: 55.70901279569489,
76+
lng: 13.185546616182755,
77+
);
78+
final end = Position.named(
79+
lat: 55.70764669578079,
80+
lng: 13.187563637197076,
81+
);
82+
final line = Feature<LineString>(
83+
geometry: LineString(
84+
coordinates: [
85+
start,
86+
via,
87+
end,
88+
],
89+
),
90+
);

test/components/length_test.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import 'package:test/test.dart';
2+
import 'package:turf/helpers.dart';
3+
import 'package:turf/length.dart';
4+
5+
void main() {
6+
test('length - in meters', () {
7+
final len = length(line, Unit.meters);
8+
expect(len.round(), equals(254));
9+
});
10+
test('length - default unit (km)', () {
11+
final len = length(line);
12+
expect((len * 1000).round(), equals(254));
13+
});
14+
}
15+
16+
final start = Position.named(
17+
lat: 55.7090430186194,
18+
lng: 13.184645393920405,
19+
);
20+
final via = Position.named(
21+
lat: 55.70901279569489,
22+
lng: 13.185546616182755,
23+
);
24+
final end = Position.named(
25+
lat: 55.70764669578079,
26+
lng: 13.187563637197076,
27+
);
28+
final line = Feature<LineString>(
29+
geometry: LineString(
30+
coordinates: [
31+
start,
32+
via,
33+
end,
34+
],
35+
),
36+
);

0 commit comments

Comments
 (0)