mirror of
https://github.com/acamarata/qibla-dart.git
synced 2026-06-30 19:04:27 +00:00
Initial release: qibla v1.0.0
This commit is contained in:
parent
871f701a5a
commit
55023d2c6c
9 changed files with 562 additions and 0 deletions
41
.github/workflows/ci.yml
vendored
Normal file
41
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
sdk: [stable, beta]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dart-lang/setup-dart@v1
|
||||
with:
|
||||
sdk: ${{ matrix.sdk }}
|
||||
- run: dart pub get
|
||||
- run: dart analyze
|
||||
- run: dart test
|
||||
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dart-lang/setup-dart@v1
|
||||
with:
|
||||
sdk: stable
|
||||
- run: dart format --set-exit-if-changed .
|
||||
|
||||
publish-dry-run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dart-lang/setup-dart@v1
|
||||
with:
|
||||
sdk: stable
|
||||
- run: dart pub get
|
||||
- run: dart pub publish --dry-run
|
||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
pubspec.lock
|
||||
doc/api/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
.DS_Store
|
||||
.claude/
|
||||
.env
|
||||
.env.*
|
||||
.vscode/*
|
||||
.codex/
|
||||
.cursor/
|
||||
.aider/
|
||||
.aider.chat.history.md
|
||||
.continue/
|
||||
.windsurf/
|
||||
.gemini/
|
||||
.codeium/
|
||||
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Changelog
|
||||
|
||||
## 1.0.0 - 2026-03-08
|
||||
|
||||
### Added
|
||||
|
||||
- `qiblaAngle(lat, lng)` computes the initial bearing to the Ka'bah in degrees.
|
||||
- `compassDir(bearing)` returns an eight-point compass abbreviation (N, NE, E, etc.).
|
||||
- `compassName(bearing)` returns the full compass direction name (North, Northeast, etc.).
|
||||
- `qiblaGreatCircle(lat, lng, [steps])` generates waypoints along the great circle to the Ka'bah via spherical linear interpolation.
|
||||
- `distanceKm(lat1, lng1, lat2, lng2)` computes haversine distance between two points.
|
||||
- Constants: `kaabaLat`, `kaabaLng`, `earthRadiusKm`.
|
||||
- RangeError validation for all coordinate inputs.
|
||||
82
README.md
Normal file
82
README.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# qibla
|
||||
|
||||
[](https://pub.dev/packages/qibla)
|
||||
[](https://github.com/acamarata/qibla-dart/actions/workflows/ci.yml)
|
||||
[](LICENSE)
|
||||
|
||||
Qibla direction, great-circle path, and haversine distance for Dart and Flutter. Pure math, zero dependencies.
|
||||
|
||||
## Installation
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
qibla: ^1.0.0
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```dart
|
||||
import 'package:qibla/qibla.dart';
|
||||
|
||||
// Bearing from New York to the Ka'bah
|
||||
final bearing = qiblaAngle(40.7128, -74.006);
|
||||
print(bearing); // ~58.48
|
||||
print(compassDir(bearing)); // NE
|
||||
|
||||
// Distance in kilometers
|
||||
final km = distanceKm(40.7128, -74.006, kaabaLat, kaabaLng);
|
||||
print(km); // ~9634
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `qiblaAngle(lat, lng)`
|
||||
|
||||
Computes the initial bearing (forward azimuth) from the given coordinates to the Ka'bah.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------ | -------- | ----------------------------------------------- |
|
||||
| `lat` | `double` | Latitude in decimal degrees (-90 to 90) |
|
||||
| `lng` | `double` | Longitude in decimal degrees (-180 to 180) |
|
||||
| **Returns** | `double` | Bearing in degrees clockwise from north (0-360) |
|
||||
|
||||
Throws `RangeError` if coordinates are out of bounds.
|
||||
|
||||
### `compassDir(bearing)`
|
||||
|
||||
Eight-point compass abbreviation: N, NE, E, SE, S, SW, W, NW.
|
||||
|
||||
### `compassName(bearing)`
|
||||
|
||||
Full compass name: North, Northeast, East, Southeast, South, Southwest, West, Northwest.
|
||||
|
||||
### `qiblaGreatCircle(lat, lng, [steps])`
|
||||
|
||||
Generates waypoints along the great circle from (lat, lng) to the Ka'bah using spherical linear interpolation (Slerp). Returns `steps + 1` points (default: 121).
|
||||
|
||||
Useful for drawing Qibla direction lines on maps.
|
||||
|
||||
### `distanceKm(lat1, lng1, lat2, lng2)`
|
||||
|
||||
Haversine distance between two points in kilometers (spherical Earth approximation, R = 6,371 km).
|
||||
|
||||
### Constants
|
||||
|
||||
| Name | Value | Description |
|
||||
| -------------- | --------- | -------------------------------------- |
|
||||
| `kaabaLat` | 21.422511 | Ka'bah center latitude (degrees north) |
|
||||
| `kaabaLng` | 39.826150 | Ka'bah center longitude (degrees east) |
|
||||
| `earthRadiusKm`| 6371 | WGS-84 volumetric mean radius |
|
||||
|
||||
## Compatibility
|
||||
|
||||
Dart 3.7+. Works with Flutter and standalone Dart applications.
|
||||
|
||||
## Related
|
||||
|
||||
- [qibla](https://www.npmjs.com/package/qibla) (npm) - The TypeScript version of this package.
|
||||
- [pray-calc](https://github.com/acamarata/pray-calc) - Islamic prayer times calculator.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
1
analysis_options.yaml
Normal file
1
analysis_options.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
include: package:lints/recommended.yaml
|
||||
7
lib/qibla.dart
Normal file
7
lib/qibla.dart
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/// Qibla direction, great-circle path, and haversine distance for Dart.
|
||||
///
|
||||
/// Pure math, zero external dependencies. Computes the initial bearing
|
||||
/// from any point on Earth to the Ka'bah using spherical trigonometry.
|
||||
library;
|
||||
|
||||
export 'src/qibla.dart';
|
||||
150
lib/src/qibla.dart
Normal file
150
lib/src/qibla.dart
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/// Qibla direction utilities. Pure math, zero external dependencies.
|
||||
///
|
||||
/// Computes the initial bearing (forward azimuth) from any point on Earth to
|
||||
/// the Ka'bah using the spherical law of cosines. Includes compass direction
|
||||
/// lookup, great-circle interpolation, and haversine distance.
|
||||
///
|
||||
/// Ka'bah coordinates sourced from verified GPS data.
|
||||
library;
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
/// Latitude of the Ka'bah center, Masjid al-Haram, Mecca (degrees north).
|
||||
const double kaabaLat = 21.422511;
|
||||
|
||||
/// Longitude of the Ka'bah center, Masjid al-Haram, Mecca (degrees east).
|
||||
const double kaabaLng = 39.82615;
|
||||
|
||||
/// Mean radius of the Earth in kilometers (WGS-84 volumetric mean).
|
||||
const double earthRadiusKm = 6371;
|
||||
|
||||
const double _deg = pi / 180;
|
||||
|
||||
/// Eight-point compass abbreviations.
|
||||
const List<String> _compassAbbr = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
|
||||
|
||||
/// Eight-point compass full names.
|
||||
const List<String> _compassNames = [
|
||||
'North',
|
||||
'Northeast',
|
||||
'East',
|
||||
'Southeast',
|
||||
'South',
|
||||
'Southwest',
|
||||
'West',
|
||||
'Northwest',
|
||||
];
|
||||
|
||||
/// Qibla bearing in degrees clockwise from true north.
|
||||
///
|
||||
/// Uses the forward azimuth formula from spherical trigonometry.
|
||||
/// Result range: [0, 360).
|
||||
///
|
||||
/// [lat] is the observer latitude in decimal degrees (-90 to 90).
|
||||
/// [lng] is the observer longitude in decimal degrees (-180 to 180).
|
||||
///
|
||||
/// Returns the bearing in degrees clockwise from north
|
||||
/// (0 = N, 90 = E, 180 = S, 270 = W).
|
||||
///
|
||||
/// Throws [RangeError] if latitude is outside [-90, 90] or longitude
|
||||
/// outside [-180, 180].
|
||||
double qiblaAngle(double lat, double lng) {
|
||||
if (lat < -90 || lat > 90) {
|
||||
throw RangeError('Latitude must be between -90 and 90, got $lat');
|
||||
}
|
||||
if (lng < -180 || lng > 180) {
|
||||
throw RangeError('Longitude must be between -180 and 180, got $lng');
|
||||
}
|
||||
final phi1 = lat * _deg;
|
||||
final lam1 = lng * _deg;
|
||||
final phi2 = kaabaLat * _deg;
|
||||
final lam2 = kaabaLng * _deg;
|
||||
final y = sin(lam2 - lam1) * cos(phi2);
|
||||
final x = cos(phi1) * sin(phi2) - sin(phi1) * cos(phi2) * cos(lam2 - lam1);
|
||||
return (atan2(y, x) / _deg + 360) % 360;
|
||||
}
|
||||
|
||||
/// Eight-point compass abbreviation for a bearing.
|
||||
///
|
||||
/// [bearing] is the bearing in degrees (0-360).
|
||||
///
|
||||
/// Returns a compass abbreviation: N, NE, E, SE, S, SW, W, or NW.
|
||||
String compassDir(double bearing) {
|
||||
return _compassAbbr[(bearing / 45).round() % 8];
|
||||
}
|
||||
|
||||
/// Full compass direction name for a bearing.
|
||||
///
|
||||
/// [bearing] is the bearing in degrees (0-360).
|
||||
///
|
||||
/// Returns the full direction name (North, Northeast, etc.).
|
||||
String compassName(double bearing) {
|
||||
return _compassNames[(bearing / 45).round() % 8];
|
||||
}
|
||||
|
||||
/// Great-circle waypoints from ([lat], [lng]) to the Ka'bah.
|
||||
///
|
||||
/// Uses Slerp (spherical linear interpolation). Useful for drawing Qibla
|
||||
/// direction lines on maps.
|
||||
///
|
||||
/// [lat] is the origin latitude in decimal degrees.
|
||||
/// [lng] is the origin longitude in decimal degrees.
|
||||
/// [steps] is the number of segments (default: 120, producing 121 points).
|
||||
///
|
||||
/// Returns a list of [lat, lng] pairs in degrees.
|
||||
///
|
||||
/// Throws [RangeError] if coordinates are out of bounds.
|
||||
List<List<double>> qiblaGreatCircle(double lat, double lng, [int steps = 120]) {
|
||||
if (lat < -90 || lat > 90) {
|
||||
throw RangeError('Latitude must be between -90 and 90, got $lat');
|
||||
}
|
||||
if (lng < -180 || lng > 180) {
|
||||
throw RangeError('Longitude must be between -180 and 180, got $lng');
|
||||
}
|
||||
final phi1 = lat * _deg;
|
||||
final lam1 = lng * _deg;
|
||||
final phi2 = kaabaLat * _deg;
|
||||
final lam2 = kaabaLng * _deg;
|
||||
|
||||
final d =
|
||||
2 *
|
||||
asin(
|
||||
sqrt(
|
||||
pow(sin((phi2 - phi1) / 2), 2) +
|
||||
cos(phi1) * cos(phi2) * pow(sin((lam2 - lam1) / 2), 2),
|
||||
),
|
||||
);
|
||||
|
||||
if (d == 0) {
|
||||
return [
|
||||
[lat, lng],
|
||||
];
|
||||
}
|
||||
|
||||
final points = <List<double>>[];
|
||||
for (var i = 0; i <= steps; i++) {
|
||||
final f = i / steps;
|
||||
final a = sin((1 - f) * d) / sin(d);
|
||||
final b = sin(f * d) / sin(d);
|
||||
final x = a * cos(phi1) * cos(lam1) + b * cos(phi2) * cos(lam2);
|
||||
final y = a * cos(phi1) * sin(lam1) + b * cos(phi2) * sin(lam2);
|
||||
final z = a * sin(phi1) + b * sin(phi2);
|
||||
points.add([atan2(z, sqrt(x * x + y * y)) / _deg, atan2(y, x) / _deg]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
/// Haversine distance between two coordinate pairs.
|
||||
///
|
||||
/// [lat1], [lng1] define the first point in decimal degrees.
|
||||
/// [lat2], [lng2] define the second point in decimal degrees.
|
||||
///
|
||||
/// Returns the distance in kilometers (spherical Earth approximation).
|
||||
double distanceKm(double lat1, double lng1, double lat2, double lng2) {
|
||||
final dLat = (lat2 - lat1) * _deg;
|
||||
final dLng = (lng2 - lng1) * _deg;
|
||||
final a =
|
||||
pow(sin(dLat / 2), 2) +
|
||||
cos(lat1 * _deg) * cos(lat2 * _deg) * pow(sin(dLng / 2), 2);
|
||||
return earthRadiusKm * 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||
}
|
||||
21
pubspec.yaml
Normal file
21
pubspec.yaml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
name: qibla
|
||||
description: >
|
||||
Qibla direction, great-circle path, and haversine distance for Dart and
|
||||
Flutter. Pure math, zero dependencies. Computes bearing to the Ka'bah
|
||||
from any point on Earth.
|
||||
version: 1.0.0
|
||||
repository: https://github.com/acamarata/qibla-dart
|
||||
issue_tracker: https://github.com/acamarata/qibla-dart/issues
|
||||
topics:
|
||||
- qibla
|
||||
- islamic
|
||||
- compass
|
||||
- geodesic
|
||||
- geolocation
|
||||
|
||||
environment:
|
||||
sdk: ^3.7.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^5.0.0
|
||||
test: ^1.25.8
|
||||
225
test/qibla_test.dart
Normal file
225
test/qibla_test.dart
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
import 'package:qibla/qibla.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('KAABA constants', () {
|
||||
test('latitude is approximately 21.42 N', () {
|
||||
expect((kaabaLat - 21.42).abs(), lessThan(0.1));
|
||||
});
|
||||
test('longitude is approximately 39.83 E', () {
|
||||
expect((kaabaLng - 39.83).abs(), lessThan(0.1));
|
||||
});
|
||||
test('earthRadiusKm is 6371', () {
|
||||
expect(earthRadiusKm, equals(6371));
|
||||
});
|
||||
});
|
||||
|
||||
group('qiblaAngle', () {
|
||||
test('returns a number between 0 and 360', () {
|
||||
final angle = qiblaAngle(40.7128, -74.006);
|
||||
expect(angle, greaterThanOrEqualTo(0));
|
||||
expect(angle, lessThan(360));
|
||||
});
|
||||
|
||||
test('New York City (~58 NE)', () {
|
||||
final angle = qiblaAngle(40.7128, -74.006);
|
||||
expect(angle, greaterThan(50));
|
||||
expect(angle, lessThan(70));
|
||||
});
|
||||
|
||||
test('London (~119 SE)', () {
|
||||
final angle = qiblaAngle(51.5074, -0.1278);
|
||||
expect(angle, greaterThan(110));
|
||||
expect(angle, lessThan(130));
|
||||
});
|
||||
|
||||
test('Tokyo (~293 NW)', () {
|
||||
final angle = qiblaAngle(35.6762, 139.6503);
|
||||
expect(angle, greaterThan(280));
|
||||
expect(angle, lessThan(310));
|
||||
});
|
||||
|
||||
test('Sydney (~277 W)', () {
|
||||
final angle = qiblaAngle(-33.8688, 151.2093);
|
||||
expect(angle, greaterThan(260));
|
||||
expect(angle, lessThan(300));
|
||||
});
|
||||
|
||||
test('Islamabad (~268 W)', () {
|
||||
final angle = qiblaAngle(33.6844, 73.0479);
|
||||
expect(angle, greaterThan(250));
|
||||
expect(angle, lessThan(290));
|
||||
});
|
||||
|
||||
test('returns finite number at Ka\'bah (degenerate case)', () {
|
||||
final angle = qiblaAngle(kaabaLat, kaabaLng);
|
||||
expect(angle.isFinite, isTrue);
|
||||
});
|
||||
|
||||
test('equator east of Mecca points NW', () {
|
||||
final angle = qiblaAngle(0, 80);
|
||||
expect(angle, greaterThan(270));
|
||||
expect(angle, lessThan(360));
|
||||
});
|
||||
|
||||
test('result is stable (same input gives same output)', () {
|
||||
final a = qiblaAngle(40.7128, -74.006);
|
||||
final b = qiblaAngle(40.7128, -74.006);
|
||||
expect(a, equals(b));
|
||||
});
|
||||
|
||||
test('throws RangeError for invalid latitude', () {
|
||||
expect(() => qiblaAngle(91, 0), throwsRangeError);
|
||||
expect(() => qiblaAngle(-91, 0), throwsRangeError);
|
||||
});
|
||||
|
||||
test('throws RangeError for invalid longitude', () {
|
||||
expect(() => qiblaAngle(0, 181), throwsRangeError);
|
||||
expect(() => qiblaAngle(0, -181), throwsRangeError);
|
||||
});
|
||||
});
|
||||
|
||||
group('compassDir', () {
|
||||
test('returns N for 0', () => expect(compassDir(0), equals('N')));
|
||||
test('returns N for 360', () => expect(compassDir(360), equals('N')));
|
||||
test('returns NE for 45', () => expect(compassDir(45), equals('NE')));
|
||||
test('returns E for 90', () => expect(compassDir(90), equals('E')));
|
||||
test('returns SE for 135', () => expect(compassDir(135), equals('SE')));
|
||||
test('returns S for 180', () => expect(compassDir(180), equals('S')));
|
||||
test('returns SW for 225', () => expect(compassDir(225), equals('SW')));
|
||||
test('returns W for 270', () => expect(compassDir(270), equals('W')));
|
||||
test('returns NW for 315', () => expect(compassDir(315), equals('NW')));
|
||||
test('returns NE for NYC Qibla', () {
|
||||
final bearing = qiblaAngle(40.7128, -74.006);
|
||||
expect(compassDir(bearing), equals('NE'));
|
||||
});
|
||||
});
|
||||
|
||||
group('compassName', () {
|
||||
test('returns North for 0', () {
|
||||
expect(compassName(0), equals('North'));
|
||||
});
|
||||
test('returns Northeast for 45', () {
|
||||
expect(compassName(45), equals('Northeast'));
|
||||
});
|
||||
test('returns East for 90', () {
|
||||
expect(compassName(90), equals('East'));
|
||||
});
|
||||
test('returns Southeast for 135', () {
|
||||
expect(compassName(135), equals('Southeast'));
|
||||
});
|
||||
test('returns South for 180', () {
|
||||
expect(compassName(180), equals('South'));
|
||||
});
|
||||
test('returns Southwest for 225', () {
|
||||
expect(compassName(225), equals('Southwest'));
|
||||
});
|
||||
test('returns West for 270', () {
|
||||
expect(compassName(270), equals('West'));
|
||||
});
|
||||
test('returns Northwest for 315', () {
|
||||
expect(compassName(315), equals('Northwest'));
|
||||
});
|
||||
test('returns North for 360', () {
|
||||
expect(compassName(360), equals('North'));
|
||||
});
|
||||
});
|
||||
|
||||
group('qiblaGreatCircle', () {
|
||||
test('returns a list of [lat, lng] pairs', () {
|
||||
final points = qiblaGreatCircle(40.7128, -74.006);
|
||||
expect(points, isNotEmpty);
|
||||
expect(points[0].length, equals(2));
|
||||
});
|
||||
|
||||
test('returns 121 points by default', () {
|
||||
final points = qiblaGreatCircle(40.7128, -74.006);
|
||||
expect(points.length, equals(121));
|
||||
});
|
||||
|
||||
test('respects custom steps parameter', () {
|
||||
final points = qiblaGreatCircle(40.7128, -74.006, 60);
|
||||
expect(points.length, equals(61));
|
||||
});
|
||||
|
||||
test('first point is close to origin', () {
|
||||
final point = qiblaGreatCircle(40.7128, -74.006)[0];
|
||||
expect((point[0] - 40.7128).abs(), lessThan(0.01));
|
||||
expect((point[1] - -74.006).abs(), lessThan(0.01));
|
||||
});
|
||||
|
||||
test('last point is close to Ka\'bah', () {
|
||||
final points = qiblaGreatCircle(40.7128, -74.006);
|
||||
final last = points[points.length - 1];
|
||||
expect((last[0] - kaabaLat).abs(), lessThan(0.01));
|
||||
expect((last[1] - kaabaLng).abs(), lessThan(0.01));
|
||||
});
|
||||
|
||||
test('all points have valid coordinates', () {
|
||||
final points = qiblaGreatCircle(51.5074, -0.1278, 10);
|
||||
for (final point in points) {
|
||||
expect(point[0].isFinite, isTrue);
|
||||
expect(point[1].isFinite, isTrue);
|
||||
expect(point[0], greaterThanOrEqualTo(-90));
|
||||
expect(point[0], lessThanOrEqualTo(90));
|
||||
expect(point[1], greaterThanOrEqualTo(-180));
|
||||
expect(point[1], lessThanOrEqualTo(180));
|
||||
}
|
||||
});
|
||||
|
||||
test('returns single point at Ka\'bah', () {
|
||||
final points = qiblaGreatCircle(kaabaLat, kaabaLng);
|
||||
expect(points.length, equals(1));
|
||||
expect((points[0][0] - kaabaLat).abs(), lessThan(0.0001));
|
||||
expect((points[0][1] - kaabaLng).abs(), lessThan(0.0001));
|
||||
});
|
||||
|
||||
test('throws RangeError for invalid coordinates', () {
|
||||
expect(() => qiblaGreatCircle(91, 0), throwsRangeError);
|
||||
expect(() => qiblaGreatCircle(0, 181), throwsRangeError);
|
||||
});
|
||||
});
|
||||
|
||||
group('distanceKm', () {
|
||||
test('returns 0 for the same point', () {
|
||||
expect(
|
||||
distanceKm(40.7128, -74.006, 40.7128, -74.006).abs(),
|
||||
lessThan(0.001),
|
||||
);
|
||||
});
|
||||
|
||||
test('NYC to Ka\'bah is approximately 9600 km', () {
|
||||
final km = distanceKm(40.7128, -74.006, kaabaLat, kaabaLng);
|
||||
expect(km, greaterThan(9000));
|
||||
expect(km, lessThan(10500));
|
||||
});
|
||||
|
||||
test('London to Ka\'bah is approximately 4950 km', () {
|
||||
final km = distanceKm(51.5074, -0.1278, kaabaLat, kaabaLng);
|
||||
expect(km, greaterThan(4500));
|
||||
expect(km, lessThan(5500));
|
||||
});
|
||||
|
||||
test('distance is symmetric', () {
|
||||
final d1 = distanceKm(40.7128, -74.006, kaabaLat, kaabaLng);
|
||||
final d2 = distanceKm(kaabaLat, kaabaLng, 40.7128, -74.006);
|
||||
expect((d1 - d2).abs(), lessThan(0.001));
|
||||
});
|
||||
|
||||
test('quarter equator is approximately 10,018 km', () {
|
||||
final d = distanceKm(0, 0, 0, 90);
|
||||
expect(d, greaterThan(9800));
|
||||
expect(d, lessThan(10200));
|
||||
});
|
||||
|
||||
test('pole to pole is approximately 20,000 km', () {
|
||||
final d = distanceKm(90, 0, -90, 0);
|
||||
expect(d, greaterThan(19000));
|
||||
expect(d, lessThan(21000));
|
||||
});
|
||||
|
||||
test('returns positive for distinct points', () {
|
||||
expect(distanceKm(0, 0, 10, 10), greaterThan(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue