Skip to content

Commit

Permalink
Implement length and along (#153)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
leiflinse-trivector authored Jan 25, 2024
1 parent c3537d6 commit bec5af9
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions lib/along.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_along;

export "src/along.dart";
3 changes: 3 additions & 0 deletions lib/length.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_length;

export "src/length.dart";
53 changes: 53 additions & 0 deletions lib/src/along.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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
Point along(Feature<LineString> 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 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 interpolated;
} else {
travelled += measure_distance.distance(Point(coordinates: coords[i]),
Point(coordinates: coords[i + 1]), unit);
}
}
return Point(coordinates: coords[coords.length - 1]);
}
25 changes: 25 additions & 0 deletions lib/src/length.dart
Original file line number Diff line number Diff line change
@@ -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<LineString> line, [Unit unit = Unit.kilometers]) {
return segmentReduce<num>(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;
}
2 changes: 2 additions & 0 deletions lib/turf.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
library turf;

export 'src/along.dart';
export 'src/area.dart';
export 'src/bbox.dart';
export 'src/bearing.dart';
Expand All @@ -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';
Expand Down
90 changes: 90 additions & 0 deletions test/components/along_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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.coordinates, equals(via));
});
test('along - to start point', () {
final resolvedStartPoint = along(line, 0, Unit.meters);
expect(resolvedStartPoint.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.coordinates.lat.toStringAsFixed(6),
equals('55.709028'));
expect(resolvedViaPoint.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.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.coordinates.lat.toStringAsFixed(6),
equals('55.708330'));
expect(resolvedViaPoint.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.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.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.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<LineString>(
geometry: LineString(
coordinates: [
start,
via,
end,
],
),
);
36 changes: 36 additions & 0 deletions test/components/length_test.dart
Original file line number Diff line number Diff line change
@@ -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<LineString>(
geometry: LineString(
coordinates: [
start,
via,
end,
],
),
);

0 comments on commit bec5af9

Please sign in to comment.