Skip to content

Commit

Permalink
feat(geobase): convert geographic coordinates to another datum using …
Browse files Browse the repository at this point in the history
…helmert 7-parameter transformation #256
  • Loading branch information
navispatial committed Jan 1, 2025
1 parent a5cade8 commit 9be1405
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 10 deletions.
38 changes: 31 additions & 7 deletions dart/geobase/lib/src/geodesy/ellipsoidal/datum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ import 'package:meta/meta.dart';
import '/src/common/functions/position_functions.dart';
import '/src/common/reference/ellipsoid.dart';
import '/src/coordinates/base/position.dart';
import '/src/coordinates/geographic/geographic.dart';
import '/src/utils/object_utils.dart';

import 'ellipsoidal.dart';

/// A geodetic datum with a reference ellipsoid and datum transformation
/// parameters.
///
Expand Down Expand Up @@ -166,13 +169,34 @@ class Datum {
transform: [0, 0, -4.5, -0.22, 0, 0, 0.554],
);

/// Converts the cartesian [geocentric] position (X, Y, Z) to another datum a
/// specified by [to] using the Helmert 7-parameter transformation.
/// Converts the [geographic] position in this datum to another datum specified
/// by [to].
///
/// The geographic position is first converted to geocentric cartesian, then
/// the Helmert 7-parameter transformation is applied to convert the position,
/// and finally the result is converted back to a geographic position.
///
/// The returned position is a geographic position in the [to] datum.
Geographic convertGeographic(Geographic geographic, {required Datum to}) {
final geocentric = geographic.toGeocentricCartesian(ellipsoid: ellipsoid);
final converted = convertGeocentric(geocentric, to: to);
return EllipsoidalExtension.fromGeocentricCartesian(
converted,
ellipsoid: to.ellipsoid,
);
}

/// Converts the cartesian [geocentric] position (X, Y, Z) in this datum to
/// another datum a specified by [to] using the Helmert 7-parameter
/// transformation.
///
/// The returned position is also a cartesian geocentric position (X, Y, Z) in
/// the [to] datum.
/// The returned position is a cartesian geocentric position (X, Y, Z) in the
/// [to] datum.
Position convertGeocentric(Position geocentric, {required Datum to}) {
if (this == WGS84) {
if (this == to) {
// no datum change
return geocentric;
} else if (this == WGS84) {
// converting from WGS 84
return _applyTransform(geocentric, to.transform);
} else if (to == WGS84) {
Expand All @@ -183,7 +207,7 @@ class Datum {
);
}

// neither this.datum nor toDatum are WGS84: convert this to WGS84 first
// neither this.datum nor toDatum are WGS84: convert origin to WGS84 first
return _applyTransform(
convertGeocentric(geocentric, to: WGS84),
to.transform,
Expand All @@ -204,7 +228,7 @@ class Datum {
// z-shift in metres
final tz = t[2];
// scale: normalise parts-per-million to (s+1)
final s = t[3] / 1e6 + 1.0;
final s = t[3] / 1.0e6 + 1.0;
// x-rotation: normalise arcseconds to radians
final rx = (t[4] / 3600.0).toRadians();
// y-rotation: normalise arcseconds to radians
Expand Down
51 changes: 48 additions & 3 deletions dart/geobase/test/geodesy/ellipsoidal_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,19 @@ void main() {
});

group('Ellipsoidal calculations on datums', () {
test('Datum conversions using Datum class', () {
test('Datum conversions using Datum class and geocentric coordinates', () {
// test values
// https://github.com/chrisveness/geodesy/blob/master/test/latlon-ellipsoidal-datum-tests.js

const dms6 = Dms(decimals: 6);

const geo1 = Geographic(lat: 51.47788, lon: -0.00147);
final gc1 = geo1.toGeocentricCartesian();
final gc1conv = Datum.WGS84.convertGeocentric(gc1, to: Datum.OSGB36);
final geo1conv = EllipsoidalExtension.fromGeocentricCartesian(gc1conv,
ellipsoid: Datum.OSGB36.ellipsoid);
expect(geo1conv.latDms(), '51.4774°N');
expect(geo1conv.lonDms(), '0.0001°E');
expect(geo1conv.latDms(dms6), '51.477364°N');
expect(geo1conv.lonDms(dms6), '0.000150°E');

final gc2 = Position.create(
x: 4027893.924,
Expand All @@ -146,5 +148,48 @@ void main() {
expect(geo2conv.latDms(), '50.7971°N');
expect(geo2conv.lonDms(), '4.3612°E');
});

test('Datum conversions using Datum class and geographic coordinates', () {
// test values
// https://github.com/chrisveness/geodesy/blob/master/test/latlon-ellipsoidal-datum-tests.js

const dms5 = Dms(decimals: 5);
const dms6 = Dms(decimals: 6);
const degMinSec = Dms(type: DmsType.degMinSec, decimals: 3);

const geo1 = Geographic(lat: 51.47788, lon: -0.00147);
final geo1conv = Datum.WGS84.convertGeographic(geo1, to: Datum.OSGB36);
expect(geo1conv.latDms(dms6), '51.477364°N');
expect(geo1conv.lonDms(dms6), '0.000150°E');
final geo1b = Datum.OSGB36.convertGeographic(geo1conv, to: Datum.WGS84);
expect(geo1b.latDms(dms5), '51.47788°N');
expect(geo1b.lonDms(dms5), '0.00147°W');

final gc2 = Position.create(
x: 4027893.924,
y: 307041.993,
z: 4919474.294,
);
final geo2 = EllipsoidalExtension.fromGeocentricCartesian(gc2);
final geo2conv = Datum.WGS84.convertGeographic(geo2, to: Datum.OSGB36);
expect(geo2conv.latDms(), '50.7971°N');
expect(geo2conv.lonDms(), '4.3612°E');
final geo2b = Datum.OSGB36.convertGeographic(geo2conv, to: Datum.WGS84);
final gc2b = geo2b.toGeocentricCartesian();
expect(gc2b.toText(decimals: 1), gc2.toText(decimals: 1));

const geo3 = Geographic(lat: 53.0, lon: 1.0, elev: 50.0);
final geo3osgb = Datum.WGS84.convertGeographic(geo3, to: Datum.OSGB36);
expect(geo3osgb.latLonDms(format: degMinSec),
'52°59′58.719″N, 1°00′06.490″E, 3.99m');

final geo3ed = Datum.WGS84.convertGeographic(geo3, to: Datum.ED50);
expect(geo3ed.latLonDms(format: degMinSec),
'53°00′02.887″N, 1°00′05.101″E, 2.72m');

final geo3ed2 = Datum.OSGB36.convertGeographic(geo3osgb, to: Datum.ED50);
final geo3wgs2 = Datum.ED50.convertGeographic(geo3ed2, to: Datum.WGS84);
expect(geo3wgs2.latLonDms(elevDecimals: 1), '53.0000°N, 1.0000°E, 50.0m');
});
});
}

0 comments on commit 9be1405

Please sign in to comment.