-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement
nearestPointOn(Multi)Line
(#86)
Co-authored-by: Levente Morva <[email protected]>
- Loading branch information
Showing
6 changed files
with
537 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
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; | ||
|
||
_NearestMulti({ | ||
required Point point, | ||
required num distance, | ||
required int index, | ||
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, | ||
'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; | ||
|
||
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: candidate.index, | ||
location: candidate.location, | ||
line: i, | ||
); | ||
} | ||
} | ||
|
||
return nearest; | ||
} | ||
|
||
Feature<Point> nearestPointOnLine( | ||
LineString line, | ||
Point point, [ | ||
Unit unit = Unit.kilometers, | ||
]) { | ||
return _nearestPointOnLine(line, point, unit).toFeature(); | ||
} | ||
|
||
Feature<Point>? nearestPointOnMultiLine( | ||
MultiLineString lines, | ||
Point point, [ | ||
Unit unit = Unit.kilometers, | ||
]) { | ||
return _nearestPointOnMultiLine(lines, point, unit)?.toFeature(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.