Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement pointToLineDistance #189

Merged
merged 1 commit into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading