Skip to content

Commit

Permalink
Implement nearestPointOn(Multi)Line and internal intersects helpe…
Browse files Browse the repository at this point in the history
…r (Attempt 2) (#87)

* Implement `nearestPointOn(Multi)Line` (#86)

Co-authored-by: Levente Morva <levente95@gmail.com>

* localIndex and index

* add documentation

* Fix `globalIndex` behaviour

Co-authored-by: Levente Morva <levente95@gmail.com>

* fix test, according to new globalIndex behaviour

* add intersection test

Co-authored-by: Levente Morva <levente95@gmail.com>
  • Loading branch information
lukas-h and skreborn authored Apr 20, 2022
1 parent a940904 commit 57c3616
Show file tree
Hide file tree
Showing 7 changed files with 595 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] lineSliceAlong
- [ ] lineSplit
- [ ] mask
- [ ] nearestPointOnLine
- [x] [nearestPointOnLine](https://github.com/dartclub/turf_dart/blob/master/lib/nearest_point_on_line.dart)
- [ ] sector
- [ ] shortestPath
- [ ] unkinkPolygon
Expand Down
3 changes: 3 additions & 0 deletions lib/nearest_point_on_line.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_nearest_point_on_line;

export 'src/nearest_point_on_line.dart';
41 changes: 41 additions & 0 deletions lib/src/intersection.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'geojson.dart';

Point? intersects(LineString line1, LineString line2) {
if (line1.coordinates.length != 2) {
throw Exception('line1 must only contain 2 coordinates');
}

if (line2.coordinates.length != 2) {
throw Exception('line2 must only contain 2 coordinates');
}

final x1 = line1.coordinates[0][0]!;
final y1 = line1.coordinates[0][1]!;
final x2 = line1.coordinates[1][0]!;
final y2 = line1.coordinates[1][1]!;
final x3 = line2.coordinates[0][0]!;
final y3 = line2.coordinates[0][1]!;
final x4 = line2.coordinates[1][0]!;
final y4 = line2.coordinates[1][1]!;

final denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);

if (denom == 0) {
return null;
}

final numeA = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
final numeB = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);

final uA = numeA / denom;
final uB = numeB / denom;

if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
final x = x1 + uA * (x2 - x1);
final y = y1 + uA * (y2 - y1);

return Point(coordinates: Position.named(lng: x, lat: y));
}

return null;
}
216 changes: 216 additions & 0 deletions lib/src/nearest_point_on_line.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import 'dart:math';

import 'bearing.dart';
import 'destination.dart';
import 'distance.dart';
import 'geojson.dart';
import 'helpers.dart';
import 'intersection.dart';

class _Nearest {
final Point point;
final num distance;
final int index;
final num location;

_Nearest({
required this.point,
required this.distance,
required this.index,
required this.location,
});

Feature<Point> toFeature() {
return Feature(
geometry: point,
properties: {
'dist': distance,
'index': index,
'location': location,
},
);
}
}

class _NearestMulti extends _Nearest {
final int line;
final int localIndex;

_NearestMulti({
required Point point,
required num distance,
required int index,
required this.localIndex,
required num location,
required this.line,
}) : super(
point: point,
distance: distance,
index: index,
location: location,
);

@override
Feature<Point> toFeature() {
return Feature(
geometry: point,
properties: {
'dist': super.distance,
'line': line,
'index': super.index,
'localIndex': localIndex,
'location': super.location,
},
);
}
}

_Nearest _nearestPointOnLine(
LineString line,
Point point, [
Unit unit = Unit.kilometers,
]) {
_Nearest? nearest;

num length = 0;

for (var i = 0; i < line.coordinates.length - 1; ++i) {
final startCoordinates = line.coordinates[i];
final stopCoordinates = line.coordinates[i + 1];

final startPoint = Point(coordinates: startCoordinates);
final stopPoint = Point(coordinates: stopCoordinates);

final sectionLength = distance(startPoint, stopPoint, unit);

final start = _Nearest(
point: startPoint,
distance: distance(point, startPoint, unit),
index: i,
location: length,
);

final stop = _Nearest(
point: stopPoint,
distance: distance(point, stopPoint, unit),
index: i + 1,
location: length + sectionLength,
);

final heightDistance = max(start.distance, stop.distance);
final direction = bearing(startPoint, stopPoint);

final perpendicular1 = destination(
point,
heightDistance,
direction + 90,
unit,
);

final perpendicular2 = destination(
point,
heightDistance,
direction - 90,
unit,
);

final intersectionPoint = intersects(
LineString.fromPoints(points: [perpendicular1, perpendicular2]),
LineString.fromPoints(points: [startPoint, stopPoint]),
);

_Nearest? intersection;

if (intersectionPoint != null) {
intersection = _Nearest(
point: intersectionPoint,
distance: distance(point, intersectionPoint, unit),
index: i,
location: length + distance(startPoint, intersectionPoint, unit),
);
}

if (nearest == null || start.distance < nearest.distance) {
nearest = start;
}

if (stop.distance < nearest.distance) {
nearest = stop;
}

if (intersection != null && intersection.distance < nearest.distance) {
nearest = intersection;
}

length += sectionLength;
}

/// A `LineString` is guaranteed to have at least two points and thus a
/// nearest point has to exist.
return nearest!;
}

_NearestMulti? _nearestPointOnMultiLine(
MultiLineString lines,
Point point, [
Unit unit = Unit.kilometers,
]) {
_NearestMulti? nearest;

var globalIndex = 0;

for (var i = 0; i < lines.coordinates.length; ++i) {
final line = LineString(coordinates: lines.coordinates[i]);

final candidate = _nearestPointOnLine(line, point);

if (nearest == null || candidate.distance < nearest.distance) {
nearest = _NearestMulti(
point: candidate.point,
distance: candidate.distance,
index: globalIndex + candidate.index,
localIndex: candidate.index,
location: candidate.location,
line: i,
);
}

globalIndex += line.coordinates.length;
}

return nearest;
}

/// Takes a [Point] and a [LineString] and calculates the closest Point on the [LineString].
/// ```dart
/// var line = LineString(
/// coordinates: [
/// Position.of([-77.031669, 38.878605]),
/// Position.of([-77.029609, 38.881946]),
/// Position.of([-77.020339, 38.884084]),
/// Position.of([-77.025661, 38.885821]),
/// Position.of([-77.021884, 38.889563]),
/// Position.of([-77.019824, 38.892368)]
/// ]);
/// var pt = Point(coordinates: Position(lat: -77.037076, lng: 38.884017));
///
/// var snapped = nearestPointOnLine(line, pt, Unit.miles);
/// ```
///
Feature<Point> nearestPointOnLine(
LineString line,
Point point, [
Unit unit = Unit.kilometers,
]) {
return _nearestPointOnLine(line, point, unit).toFeature();
}

/// Takes a [Point] and a [MultiLineString] and calculates the closest Point on the [MultiLineString].
Feature<Point>? nearestPointOnMultiLine(
MultiLineString lines,
Point point, [
Unit unit = Unit.kilometers,
]) {
return _nearestPointOnMultiLine(lines, point, unit)?.toFeature();
}
1 change: 1 addition & 0 deletions lib/turf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export 'src/geojson.dart';
export 'src/midpoint.dart';
export 'src/helpers.dart';
export 'src/nearest_point.dart';
export 'src/nearest_point_on_line.dart';
31 changes: 31 additions & 0 deletions test/components/intersection_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:test/test.dart';
import 'package:turf/helpers.dart';
import 'package:turf/src/intersection.dart';

final l1 = LineString(coordinates: [
Position(0, 0),
Position(2, 2),
]);

final l2 = LineString(coordinates: [
Position(2, 0),
Position(0, 2),
]);

final l3 = LineString(coordinates: [
Position(2, 2),
Position(2, 0),
]);

final l4 = LineString(coordinates: [
Position(0, 0),
Position(0, 2),
]);

main() {
test('test intersects()', () {
expect(intersects(l1, l2)?.coordinates, Position(1, 1));
expect(intersects(l1, l3)?.coordinates, Position(2, 2));
expect(intersects(l3, l4), null);
});
}
Loading

0 comments on commit 57c3616

Please sign in to comment.