Skip to content

Commit

Permalink
feat: implement pointToLineDistance (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhammerc authored Aug 10, 2024
1 parent 0a58cb6 commit da48c29
Show file tree
Hide file tree
Showing 26 changed files with 726 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Dart. This is an on going project and functions are being added once needed. If
- [x] [midpoint](https://github.com/dartclub/turf_dart/blob/main/lib/src/midpoint.dart)
- [ ] pointOnFeature
- [ ] polygonTangents
- [ ] pointToLineDistance
- [x] [pointToLineDistance](https://github.com/dartclub/turf_dart/blob/main/lib/src/point_to_line_distance.dart)
- [x] [rhumbBearing](https://github.com/dartclub/turf_dart/blob/main/lib/src/rhumb_bearing.dart)
- [x] [rhumbDestination](https://github.com/dartclub/turf_dart/blob/main/lib/src/rhumb_destination.dart)
- [x] [rhumbDistance](https://github.com/dartclub/turf_dart/blob/main/lib/src/rhumb_distance.dart)
Expand Down
4 changes: 4 additions & 0 deletions lib/point_to_line_distance.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
library turf_point_to_line_distance;

export 'package:geotypes/geotypes.dart';
export 'src/point_to_line_distance.dart';
12 changes: 12 additions & 0 deletions lib/src/helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ enum Corner {
centroid,
}

/// Whether to calculate the distance based on geodesic (spheroid) or
/// planar (flat) method.
enum DistanceGeometry {
/// Calculations will be made on a 2D plane, NOT taking into account the
/// earth curvature.
planar,

/// Calculate the distance with geodesic (spheroid) equations. It will take
/// into account the earth as a sphere.
geodesic,
}

/// Earth Radius used with the Harvesine formula and approximates using a spherical (non-ellipsoid) Earth.
const earthRadius = 6371008.8;

Expand Down
79 changes: 79 additions & 0 deletions lib/src/point_to_line_distance.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'package:turf/distance.dart';
import 'package:turf/line_segment.dart';
import 'helpers.dart';

// Sourced from https://turfjs.org (MIT license) and from
// http://geomalgorithms.com/a02-_lines.html

/// Returns the minimum distance between a [point] and a [line], being the
/// distance from a line the minimum distance between the point and any
/// segment of the [LineString].
///
/// Example:
/// ```dart
/// final point = Point(coordinates: Position(0, 0));
/// final line = LineString(coordinates: [Position(1, 1), Position(-1, 1)]);
///
/// final distance = pointToLineDistance(point, line, unit: Unit.miles);
/// // distance == 69.11854715938406
/// ```
num pointToLineDistance(
Point point,
LineString line, {
Unit unit = Unit.kilometers,
DistanceGeometry method = DistanceGeometry.geodesic,
}) {
var distance = double.infinity;
final position = point.coordinates;

segmentEach(line, (segment, _, __, ___, ____) {
final a = segment.geometry!.coordinates[0];
final b = segment.geometry!.coordinates[1];
final d = _distanceToSegment(position, a, b, method: method);

if (d < distance) {
distance = d.toDouble();
}
});

return convertLength(distance, Unit.degrees, unit);
}

/// Returns the distance between a point P on a segment AB.
num _distanceToSegment(
Position p,
Position a,
Position b, {
required DistanceGeometry method,
}) {
final v = b - a;
final w = p - a;

final c1 = w.dotProduct(v);
if (c1 <= 0) {
return _calcDistance(p, a, method: method, unit: Unit.degrees);
}

final c2 = v.dotProduct(v);
if (c2 <= c1) {
return _calcDistance(p, b, method: method, unit: Unit.degrees);
}

final b2 = c1 / c2;
final pb = a + Position(v[0]! * b2, v[1]! * b2);
return _calcDistance(p, pb, method: method, unit: Unit.degrees);
}

num _calcDistance(
Position a,
Position b, {
required Unit unit,
required DistanceGeometry method,
}) {
if (method == DistanceGeometry.planar) {
return rhumbDistance(Point(coordinates: a), Point(coordinates: b), unit);
}

// Otherwise DistanceGeometry.geodesic
return distanceRaw(a, b, unit);
}
1 change: 1 addition & 0 deletions lib/turf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export 'meta.dart';
export 'midpoint.dart';
export 'nearest_point_on_line.dart';
export 'nearest_point.dart';
export 'point_to_line_distance.dart';
export 'polygon_smooth.dart';
export 'polygon_to_line.dart';
export 'polyline.dart';
Expand Down
96 changes: 96 additions & 0 deletions test/components/point_to_line_distance_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import 'dart:convert';
import 'dart:io';

import 'package:test/test.dart';
import 'package:turf/helpers.dart';
import 'package:turf/src/point_to_line_distance.dart';

final distances = {
"city-line1.geojson": 1.0299686758,
"city-line2.geojson": 3.6186172981,
"city-segment-inside1.geojson": 1.1489389115,
"city-segment-inside2.geojson": 1.0280898152,
"city-segment-inside3.geojson": 3.5335695907,
"city-segment-obtuse1.geojson": 2.8573246363,
"city-segment-obtuse2.geojson": 3.3538913334,
"city-segment-projected1.geojson": 3.5886611693,
"city-segment-projected2.geojson": 4.163469898,
"issue-1156.geojson": 189.6618028794,
"line-fiji.geojson": 27.1266612008,
"line-resolute-bay.geojson": 425.0745081528,
"line1.geojson": 23.4224834672,
"line2.geojson": 188.015686924,
"segment-fiji.geojson": 27.6668301762,
"segment1.geojson": 69.0934195756,
"segment1a.geojson": 69.0934195756,
"segment2.geojson": 69.0934195756,
"segment3.geojson": 69.0828960461,
"segment4.geojson": 332.8803863574
};

void main() {
group('pointToLineDistance', () {
group('in == out', () {
final inDir = Directory('./test/examples/point_to_line_distance/in');

for (final file in inDir.listSync(recursive: true)) {
if (file is File && file.path.endsWith('.geojson')) {
testFile(file);
}
}
});

group('unit tests', () {
testPlanarGeodesic();
});
});
}

void testFile(File file) {
test(file.path, () {
final inSource = file.readAsStringSync();
final collection = FeatureCollection.fromJson(jsonDecode(inSource));

final rawPoint = collection.features[0];
final rawLine = collection.features[1];

final point = Feature<Point>.fromJson(rawPoint.toJson());
final line = Feature<LineString>.fromJson(rawLine.toJson());

final properties = rawPoint.properties ?? {};
final unitRaw = properties["units"] as String?;

var unit = Unit.kilometers;
if (unitRaw == 'meters') {
unit = Unit.meters;
} else if (unitRaw == 'miles') {
unit = Unit.miles;
} else {
expect(unitRaw, null, reason: '"units" was given but not handled.');
}

final distance =
pointToLineDistance(point.geometry!, line.geometry!, unit: unit);

final name = file.path.substring(file.path.lastIndexOf('/') + 1);

expect(distance, closeTo(distances[name]!, 0.01));
});
}

void testPlanarGeodesic() {
test('Check planar and geodesic results are different', () {
final pt = Point(coordinates: Position(0, 0));
final line = LineString(coordinates: [
Position(10, 10),
Position(-1, 1),
]);

final geoOut =
pointToLineDistance(pt, line, method: DistanceGeometry.geodesic);
final planarOut =
pointToLineDistance(pt, line, method: DistanceGeometry.planar);

expect(geoOut, isNot(equals(planarOut)));
});
}
30 changes: 30 additions & 0 deletions test/examples/point_to_line_distance/in/city-line1.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-0.3767967224121093, 39.4689324766527]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-0.40567874908447266, 39.47386857192064],
[-0.3963661193847656, 39.47578991028725],
[-0.38035869598388666, 39.482216070269594],
[-0.3776121139526367, 39.48195108571802],
[-0.3689002990722656, 39.47641930269614],
[-0.35945892333984375, 39.46349905420083],
[-0.35782814025878906, 39.45982131412374],
[-0.3458118438720703, 39.453890134716616]
]
}
}
]
}
27 changes: 27 additions & 0 deletions test/examples/point_to_line_distance/in/city-line2.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-3.592529296875, 40.573804799488194]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-3.8884735107421875, 40.420292132688964],
[-3.736724853515625, 40.276906410822825],
[-3.5025787353515625, 40.422383097039905],
[-3.5018920898437496, 40.516409213865586],
[-3.668060302734375, 40.559199680578075]
]
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"units": "miles"
},
"geometry": {
"type": "Point",
"coordinates": [-6.0047149658203125, 37.365109304227246]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-6.0150146484375, 37.38011551844836],
[-5.931415557861328, 37.39702801486944]
]
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-0.3767967224121093, 39.4689324766527]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-0.3689861297607422, 39.47648555419739],
[-0.3595447540283203, 39.46363158174706]
]
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-3.592529296875, 40.573804799488194]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-3.503265380859375, 40.51693121343741],
[-3.6694335937500004, 40.560764667193595]
]
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-6.030292510986328, 37.35746862390723]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [
[-6.0150146484375, 37.38011551844836],
[-5.931415557861328, 37.39702801486944]
]
}
}
]
}
Loading

0 comments on commit da48c29

Please sign in to comment.