Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions lib/great_circle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_great_circle;

export 'src/great_circle.dart';
103 changes: 103 additions & 0 deletions lib/src/great_circle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import 'dart:math' as math;
import 'package:turf/turf.dart';
import 'helpers.dart';

/// Calculates the great circle route between two points on a sphere
///
/// Useful link: https://en.wikipedia.org/wiki/Great-circle_distance

List<List<double>> greatCircle(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make sure our function returns something similar to the turfJS version.

Let's return a Feature with either
a LineString or MultiLineString as the payload.

https://turfjs.org/docs/api/greatCircle

dynamic start,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of dynamic please choose a more concrete type (the JS version asks for coordinates, which would equal to our Position type

dynamic end,
{
Map <String, dynamic> properties = const {},
int npoints = 100, // npoints = number of intermediate points less one (e.g if you want 5 intermediate points, set npoints = 6)
int offset = 10
}) {
if (start.length != 2 || end.length != 2) {
/// Coordinate checking
throw ArgumentError("Both start and end coordinates should have two values - a latitude and longitude");
}

// If start and end points are the same,
if (start[0] == end[0] && start[1] == end[1]) {
return List.generate(npoints, (_) => [start[0], start[1]]);
}
// Coordinate checking for valid values
if (start[0] < -90) {
throw ArgumentError("Starting latitude (vertical) coordinate is less than -90. This is not a valid coordinate.");
}

if (start[0] > 90) {
throw ArgumentError("Starting latitude (vertical) coordinate is greater than 90. This is not a valid coordinate.");
}

if (start[1] < -180) {
throw ArgumentError('Starting longitude (horizontal) coordinate is less than -180. This is not a valid coordinate.');
}

if (start[1] > 180) {
throw ArgumentError('Starting longitude (horizontal) coordinate is greater than 180. This is not a valid coordinate.');
}

if (end[0] < -90) {
throw ArgumentError("Ending latitude (vertical) coordinate is less than -90. This is not a valid coordinate.");
}

if (end[0] > 90) {
throw ArgumentError("Ending latitude (vertical) coordinate is greater than 90. This is not a valid coordinate.");
}

if (end[1] < -180) {
throw ArgumentError('Ending longitude (horizontal) coordinate is less than -180. This is not a valid coordinate.');
}

if (end[1] > 180) {
throw ArgumentError('Ending longitude (horizontal) coordinate is greater than 180. This is not a valid coordinate.');
}

List<List<double>> line = [];

num lat1 = degreesToRadians(start[0]);
num lng1 = degreesToRadians(start[1]);
num lat2 = degreesToRadians(end[0]);
num lng2 = degreesToRadians(end[1]);

// Harvesine formula
for (int i = 0; i <= npoints; i++) {
double f = i / npoints;
double delta = 2 *
math.asin(math.sqrt(math.pow(math.sin((lat2 - lat1) / 2), 2) +
math.cos(lat1) * math.cos(lat2) * math.pow(math.sin((lng2 - lng1) / 2), 2)));
double A = math.sin((1 - f) * delta) / math.sin(delta);
double B = math.sin(f * delta) / math.sin(delta);
double x = A * math.cos(lat1) * math.cos(lng1) + B * math.cos(lat2) * math.cos(lng2);
double y = A * math.cos(lat1) * math.sin(lng1) + B * math.cos(lat2) * math.sin(lng2);
double z = A * math.sin(lat1) + B * math.sin(lat2);

double lat = math.atan2(z, math.sqrt(x * x + y * y));
double lng = math.atan2(y, x);

List<double> point = [double.parse(radiansToDegrees(lat).toStringAsFixed(2)), double.parse(radiansToDegrees(lng).toStringAsFixed(2))];
line.add(point);
}
/// Check for multilinestring if path crosses anti-meridian
bool crossAntiMeridian = (start[0] - end[0]).abs() > 180;

/// If it crossed antimeridian, we need to split our lines
if (crossAntiMeridian) {
List<List<double>> multiLine = [];
List<List<double>> currentLine = [];

for (var point in line) {
if ((point[0] - line[0][0]).abs() > 180) {
multiLine.addAll(currentLine);
currentLine = [];
}
currentLine.add(point);
}
multiLine.addAll(currentLine);
return multiLine;
}
return line;
}
42 changes: 42 additions & 0 deletions test/components/great_circle_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:turf/great_circle.dart';
import 'package:test/test.dart';

void main() {
//First test - simple coordinates

List<double> start = [-90, 0];
List<double> end = [-80,0];

List<List<double>> resultsFirstTest1 = [[-90.0, 0.0], [-88.0,0.0], [-86.0,0.0], [-84.0, 0.0], [-82.0, 0.0],[-80.0, 0.0]];
List<List<double>> resultsFirstTest2 = [[-90.0, 0.0], [-89.0,0.0], [-88.0,0.0], [-87.0, 0.0], [-86.0, 0.0],[-85.0,0.0], [-84.0,0.0], [-83.0, 0.0], [-82.0, 0.0],[-81.0, 0.0], [-80.0, 0.0]];

test('Great circle simple tests:', () {
expect(greatCircle(start, end, npoints: 5), resultsFirstTest1);
expect(greatCircle(start, end, npoints: 10), resultsFirstTest2);
});

// Second test - intermediate coordiantes (non-straight lines)
List<double> start2 = [48, -122];
List<double> end2 = [39, -77];

List<List<double>> resultsSecondTest1 = [[48.0, -122.0], [45.75, -97.73], [39.0, -77.0]];
List<List<double>> resultsSecondTest2 = [[48.0, -122.0], [47.52, -109.61], [45.75, -97.73], [42.85, -86.80], [39.0, -77.0]];


test('Great circle intermediate tests:', () {
expect(greatCircle(start2, end2, npoints: 2), resultsSecondTest1);
expect(greatCircle(start2, end2, npoints: 4), resultsSecondTest2);
});

// Third test - complex coordinates (crossing anti-meridian)

List<double> start3 = [-21, 143];
List<double> end3 = [41, -140];

List<List<double>> resultsThirdTest1 = [[-21.0, 143.0], [12.65, 176.68], [41, -140]];
List<List<double>> resultsThirdTest2 = [[-21.0, 143.0], [-4.36, 160.22], [12.65, 176.68], [28.52, -164.56], [41, -140]];
test('Great circle complex tests:', () {
expect(greatCircle(start3, end3, npoints: 2), resultsThirdTest1);
expect(greatCircle(start3, end3, npoints: 4), resultsThirdTest2);
});
}