use core::f64::consts::PI;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
const ARCSEC_TO_RAD: f64 = PI / (180.0 * 3600.0);
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Ellipsoid {
pub name: &'static str,
pub a: f64,
pub inv_f: f64,
}
impl Ellipsoid {
#[must_use]
pub const fn new(a: f64, inv_f: f64) -> Self {
Self {
name: "custom",
a,
inv_f,
}
}
#[must_use]
pub fn f(&self) -> f64 {
if self.inv_f == 0.0 {
0.0
} else {
1.0 / self.inv_f
}
}
#[must_use]
pub fn b(&self) -> f64 {
self.a * (1.0 - self.f())
}
#[must_use]
pub fn e2(&self) -> f64 {
let f = self.f();
2.0 * f - f * f
}
#[must_use]
pub fn e_prime2(&self) -> f64 {
let e2 = self.e2();
e2 / (1.0 - e2)
}
#[must_use]
pub fn n_radius(&self, lat_rad: f64) -> f64 {
let sin_lat = lat_rad.sin();
self.a / (1.0 - self.e2() * sin_lat * sin_lat).sqrt()
}
#[must_use]
pub fn m_radius(&self, lat_rad: f64) -> f64 {
let sin_lat = lat_rad.sin();
let w2 = 1.0 - self.e2() * sin_lat * sin_lat;
self.a * (1.0 - self.e2()) / (w2 * w2.sqrt())
}
pub const WGS84: Ellipsoid = Ellipsoid {
name: "WGS84",
a: 6_378_137.0,
inv_f: 298.257_223_563,
};
pub const GRS80: Ellipsoid = Ellipsoid {
name: "GRS80",
a: 6_378_137.0,
inv_f: 298.257_222_101,
};
pub const INTERNATIONAL: Ellipsoid = Ellipsoid {
name: "International 1924",
a: 6_378_388.0,
inv_f: 297.0,
};
pub const BESSEL: Ellipsoid = Ellipsoid {
name: "Bessel 1841",
a: 6_377_397.155,
inv_f: 299.152_812_8,
};
pub const AIRY: Ellipsoid = Ellipsoid {
name: "Airy 1830",
a: 6_377_563.396,
inv_f: 299.324_964_6,
};
pub const CLARKE1866: Ellipsoid = Ellipsoid {
name: "Clarke 1866",
a: 6_378_206.4,
inv_f: 294.978_698_2,
};
pub const GDA94: Ellipsoid = Ellipsoid {
name: "GRS80",
a: 6_378_137.0,
inv_f: 298.257_222_101,
};
}
#[must_use]
pub fn geodetic_to_ecef(lat: f64, lon: f64, h: f64, ellipsoid: &Ellipsoid) -> (f64, f64, f64) {
let n = ellipsoid.n_radius(lat);
let cos_lat = lat.cos();
let sin_lat = lat.sin();
let cos_lon = lon.cos();
let sin_lon = lon.sin();
let x = (n + h) * cos_lat * cos_lon;
let y = (n + h) * cos_lat * sin_lon;
let z = (n * (1.0 - ellipsoid.e2()) + h) * sin_lat;
(x, y, z)
}
#[must_use]
pub fn ecef_to_geodetic(x: f64, y: f64, z: f64, ellipsoid: &Ellipsoid) -> (f64, f64, f64) {
let a = ellipsoid.a;
let e2 = ellipsoid.e2();
let b = ellipsoid.b();
let lon = y.atan2(x);
let p = (x * x + y * y).sqrt();
let mut lat = (z / p * (1.0 - e2)).atan();
for _ in 0..5 {
let sin_lat = lat.sin();
let n = a / (1.0 - e2 * sin_lat * sin_lat).sqrt();
let tan_lat_new = (z + e2 * n * sin_lat) / p;
let lat_new = tan_lat_new.atan();
if (lat_new - lat).abs() < 1e-12 {
lat = lat_new;
break;
}
lat = lat_new;
}
let sin_lat = lat.sin();
let cos_lat = lat.cos();
let n = a / (1.0 - e2 * sin_lat * sin_lat).sqrt();
let h = if cos_lat.abs() > 1e-10 {
p / cos_lat - n
} else {
z.abs() / sin_lat.abs() - b
};
(lat, lon, h)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MolodenskyParams {
pub dx: f64,
pub dy: f64,
pub dz: f64,
pub da: f64,
pub df: f64,
}
impl MolodenskyParams {
#[must_use]
pub fn new(dx: f64, dy: f64, dz: f64, source: &Ellipsoid, target: &Ellipsoid) -> Self {
Self {
dx,
dy,
dz,
da: target.a - source.a,
df: target.f() - source.f(),
}
}
#[must_use]
pub fn transform(&self, lat: f64, lon: f64, h: f64, source: &Ellipsoid) -> (f64, f64, f64) {
let a = source.a;
let e2 = source.e2();
let da = self.da;
let df = self.df;
let sin_lat = lat.sin();
let cos_lat = lat.cos();
let sin_lon = lon.sin();
let cos_lon = lon.cos();
let sin2_lat = sin_lat * sin_lat;
let n = a / (1.0 - e2 * sin2_lat).sqrt();
let m = a * (1.0 - e2) / (1.0 - e2 * sin2_lat).powf(1.5);
let d_lat = (-self.dx * sin_lat * cos_lon - self.dy * sin_lat * sin_lon
+ self.dz * cos_lat
+ da * (n * e2 * sin_lat * cos_lat) / a
+ df * (m / (1.0 - source.f()) + n * (1.0 - source.f())) * sin_lat * cos_lat)
/ (m + h);
let d_lon = (-self.dx * sin_lon + self.dy * cos_lon) / ((n + h) * cos_lat);
let d_h = self.dx * cos_lat * cos_lon + self.dy * cos_lat * sin_lon + self.dz * sin_lat
- da * a / n
+ df * n * (1.0 - source.f()) * sin2_lat;
(lat + d_lat, lon + d_lon, h + d_h)
}
#[must_use]
pub fn transform_abridged(
&self,
lat: f64,
lon: f64,
h: f64,
source: &Ellipsoid,
) -> (f64, f64, f64) {
let a = source.a;
let e2 = source.e2();
let da = self.da;
let df = self.df;
let sin_lat = lat.sin();
let cos_lat = lat.cos();
let sin_lon = lon.sin();
let cos_lon = lon.cos();
let sin2_lat = sin_lat * sin_lat;
let n = a / (1.0 - e2 * sin2_lat).sqrt();
let m = a * (1.0 - e2) / (1.0 - e2 * sin2_lat).powf(1.5);
let d_lat = (-self.dx * sin_lat * cos_lon - self.dy * sin_lat * sin_lon
+ self.dz * cos_lat
+ da * (n * e2 * sin_lat * cos_lat) / a
+ df * (m * (1.0 - source.f()) + n / (1.0 - source.f())) * sin_lat * cos_lat)
/ m;
let d_lon = (-self.dx * sin_lon + self.dy * cos_lon) / (n * cos_lat);
let d_h = self.dx * cos_lat * cos_lon + self.dy * cos_lat * sin_lon + self.dz * sin_lat
- da * a / n
+ df * n * sin2_lat;
(lat + d_lat, lon + d_lon, h + d_h)
}
#[must_use]
pub fn wgs84_to_ed50() -> (Self, &'static Ellipsoid, &'static Ellipsoid) {
let params = Self::new(
89.5,
93.8,
123.1,
&Ellipsoid::WGS84,
&Ellipsoid::INTERNATIONAL,
);
(params, &Ellipsoid::WGS84, &Ellipsoid::INTERNATIONAL)
}
#[must_use]
pub fn wgs84_to_tokyo() -> (Self, &'static Ellipsoid, &'static Ellipsoid) {
let params = Self::new(-148.0, 507.0, 685.0, &Ellipsoid::WGS84, &Ellipsoid::BESSEL);
(params, &Ellipsoid::WGS84, &Ellipsoid::BESSEL)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BursaWolfParams {
pub tx: f64,
pub ty: f64,
pub tz: f64,
pub rx: f64,
pub ry: f64,
pub rz: f64,
pub ds: f64,
}
impl BursaWolfParams {
#[must_use]
pub const fn new(tx: f64, ty: f64, tz: f64, rx: f64, ry: f64, rz: f64, ds: f64) -> Self {
Self {
tx,
ty,
tz,
rx,
ry,
rz,
ds,
}
}
#[must_use]
pub fn transform_ecef(&self, x: f64, y: f64, z: f64) -> (f64, f64, f64) {
let rx = self.rx * ARCSEC_TO_RAD;
let ry = self.ry * ARCSEC_TO_RAD;
let rz = self.rz * ARCSEC_TO_RAD;
let s = self.ds * 1.0e-6;
let x_out = self.tx + (1.0 + s) * (x + rz * y - ry * z);
let y_out = self.ty + (1.0 + s) * (-rz * x + y + rx * z);
let z_out = self.tz + (1.0 + s) * (ry * x - rx * y + z);
(x_out, y_out, z_out)
}
#[must_use]
pub fn inverse(&self) -> Self {
Self {
tx: -self.tx,
ty: -self.ty,
tz: -self.tz,
rx: -self.rx,
ry: -self.ry,
rz: -self.rz,
ds: -self.ds,
}
}
#[must_use]
pub fn transform_geodetic(
&self,
lat: f64,
lon: f64,
h: f64,
source: &Ellipsoid,
target: &Ellipsoid,
) -> (f64, f64, f64) {
let (xe, ye, ze) = geodetic_to_ecef(lat, lon, h, source);
let (xt, yt, zt) = self.transform_ecef(xe, ye, ze);
ecef_to_geodetic(xt, yt, zt, target)
}
#[must_use]
pub fn ed50_to_wgs84() -> Self {
Self::new(-89.5, -93.8, -123.1, 0.0, 0.0, 0.156, -1.2)
}
#[must_use]
pub fn osgb36_to_wgs84() -> Self {
Self::new(
446.448, -125.157, 542.060, -0.1502, -0.2470, -0.8421, -20.4894,
)
}
#[must_use]
pub fn tokyo_to_wgs84() -> Self {
Self::new(-146.414, 507.337, 680.507, 0.0, 0.0, 0.0, 0.0)
}
#[must_use]
pub fn nad27_to_wgs84_conus() -> Self {
Self::new(-8.0, 160.0, 176.0, 0.0, 0.0, 0.0, 0.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ItRfFrame {
Itrf2020,
Itrf2014,
Itrf2008,
Itrf2005,
Itrf2000,
Itrf97,
Itrf96,
Gda2020,
Gda94,
}
impl ItRfFrame {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Itrf2020 => "ITRF2020",
Self::Itrf2014 => "ITRF2014",
Self::Itrf2008 => "ITRF2008",
Self::Itrf2005 => "ITRF2005",
Self::Itrf2000 => "ITRF2000",
Self::Itrf97 => "ITRF97",
Self::Itrf96 => "ITRF96",
Self::Gda2020 => "GDA2020",
Self::Gda94 => "GDA94",
}
}
#[must_use]
pub fn reference_epoch(&self) -> f64 {
match self {
Self::Itrf2020 => 2015.0,
Self::Itrf2014 => 2010.0,
Self::Itrf2008 => 2005.0,
Self::Itrf2005 => 2000.0,
Self::Itrf2000 => 1997.0,
Self::Itrf97 => 1997.0,
Self::Itrf96 => 1997.0,
Self::Gda2020 => 2020.0,
Self::Gda94 => 1994.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ItrfEpoch {
pub frame: ItRfFrame,
pub epoch: f64,
}
impl ItrfEpoch {
#[must_use]
pub const fn new(frame: ItRfFrame, epoch: f64) -> Self {
Self { frame, epoch }
}
}
#[derive(Debug, Clone, Copy)]
pub struct EpochTransformArgs<'e> {
pub lat: f64,
pub lon: f64,
pub h: f64,
pub source: &'e Ellipsoid,
pub target: &'e Ellipsoid,
pub ref_epoch: f64,
pub epoch: f64,
}
impl<'e> EpochTransformArgs<'e> {
#[must_use]
pub const fn new(
lat: f64,
lon: f64,
h: f64,
source: &'e Ellipsoid,
target: &'e Ellipsoid,
ref_epoch: f64,
epoch: f64,
) -> Self {
Self {
lat,
lon,
h,
source,
target,
ref_epoch,
epoch,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ItrfTransformParams {
pub bursa_wolf: BursaWolfParams,
pub rates: [f64; 7],
}
impl ItrfTransformParams {
#[must_use]
pub const fn new(bursa_wolf: BursaWolfParams, rates: [f64; 7]) -> Self {
Self { bursa_wolf, rates }
}
#[must_use]
pub fn params_at_epoch(&self, epoch: f64, ref_epoch: f64) -> BursaWolfParams {
let dt = epoch - ref_epoch;
BursaWolfParams {
tx: self.bursa_wolf.tx + self.rates[0] * dt,
ty: self.bursa_wolf.ty + self.rates[1] * dt,
tz: self.bursa_wolf.tz + self.rates[2] * dt,
rx: self.bursa_wolf.rx + self.rates[3] * dt,
ry: self.bursa_wolf.ry + self.rates[4] * dt,
rz: self.bursa_wolf.rz + self.rates[5] * dt,
ds: self.bursa_wolf.ds + self.rates[6] * dt,
}
}
#[must_use]
pub fn transform_at_epoch(&self, args: EpochTransformArgs<'_>) -> (f64, f64, f64) {
let bw = self.params_at_epoch(args.epoch, args.ref_epoch);
bw.transform_geodetic(args.lat, args.lon, args.h, args.source, args.target)
}
#[must_use]
pub fn itrf2014_to_itrf2008() -> Self {
let bw = BursaWolfParams::new(
1.6e-3, 1.9e-3, 2.4e-3, 0.0, 0.0, 0.0, -0.02e-3, );
let rates = [0.0e-4, 0.0e-4, -0.1e-4, 0.0, 0.0, 0.0, 0.003e-3];
Self::new(bw, rates)
}
#[must_use]
pub fn itrf2008_to_itrf2005() -> Self {
let bw = BursaWolfParams::new(
-2.0e-3, -0.9e-3, -4.7e-3, 0.0, 0.0, 0.0, 0.94e-3, );
let rates = [0.3e-4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
Self::new(bw, rates)
}
#[must_use]
pub fn itrf2000_to_itrf97() -> Self {
let bw = BursaWolfParams::new(
6.7e-3, 6.1e-3, -18.5e-3, 0.0, 0.0, 0.0, 1.55e-3, );
let rates = [0.0, -0.6e-4, -1.4e-4, 0.0, 0.0, 0.0, 0.01e-3];
Self::new(bw, rates)
}
#[must_use]
pub fn inverse(&self) -> Self {
let bw = self.bursa_wolf;
let inverse_bw = BursaWolfParams {
tx: -bw.tx,
ty: -bw.ty,
tz: -bw.tz,
rx: -bw.rx,
ry: -bw.ry,
rz: -bw.rz,
ds: -bw.ds,
};
let inverse_rates = [
-self.rates[0],
-self.rates[1],
-self.rates[2],
-self.rates[3],
-self.rates[4],
-self.rates[5],
-self.rates[6],
];
Self::new(inverse_bw, inverse_rates)
}
}
#[derive(Debug, Clone, Copy)]
pub struct ItrfPreset {
pub source_name: &'static str,
pub target_name: &'static str,
pub params: ItrfTransformParams,
pub ref_epoch: f64,
}
impl ItrfPreset {
#[must_use]
pub const fn new(
source_name: &'static str,
target_name: &'static str,
params: ItrfTransformParams,
ref_epoch: f64,
) -> Self {
Self {
source_name,
target_name,
params,
ref_epoch,
}
}
}
#[must_use]
pub fn itrf_preset_table() -> [ItrfPreset; 3] {
[
ItrfPreset::new(
"ITRF2014",
"ITRF2008",
ItrfTransformParams::itrf2014_to_itrf2008(),
ItRfFrame::Itrf2014.reference_epoch(), ),
ItrfPreset::new(
"ITRF2008",
"ITRF2005",
ItrfTransformParams::itrf2008_to_itrf2005(),
ItRfFrame::Itrf2008.reference_epoch(), ),
ItrfPreset::new(
"ITRF2000",
"ITRF97",
ItrfTransformParams::itrf2000_to_itrf97(),
ItRfFrame::Itrf2000.reference_epoch(), ),
]
}
#[must_use]
pub fn find_itrf_params(
source_itrf: &str,
target_itrf: &str,
) -> Option<(ItrfTransformParams, f64)> {
let table = itrf_preset_table();
for preset in &table {
if preset.source_name == source_itrf && preset.target_name == target_itrf {
return Some((preset.params, preset.ref_epoch));
}
}
for preset in &table {
if preset.source_name == target_itrf && preset.target_name == source_itrf {
return Some((preset.params.inverse(), preset.ref_epoch));
}
}
None
}
#[derive(Debug, Clone)]
pub enum TransformMethod {
Molodensky(MolodenskyParams),
BursaWolf(BursaWolfParams),
Itrf(ItrfTransformParams, f64),
Identity,
}
#[derive(Debug, Clone)]
pub struct DatumTransformer {
pub method: TransformMethod,
pub source: Ellipsoid,
pub target: Ellipsoid,
}
impl DatumTransformer {
#[must_use]
pub const fn new(method: TransformMethod, source: Ellipsoid, target: Ellipsoid) -> Self {
Self {
method,
source,
target,
}
}
#[must_use]
pub fn transform_degrees(&self, lat: f64, lon: f64, h: f64) -> (f64, f64, f64) {
let lat_r = lat.to_radians();
let lon_r = lon.to_radians();
let (lat_out_r, lon_out_r, h_out) = match &self.method {
TransformMethod::Identity => (lat_r, lon_r, h),
TransformMethod::Molodensky(params) => params.transform(lat_r, lon_r, h, &self.source),
TransformMethod::BursaWolf(params) => {
params.transform_geodetic(lat_r, lon_r, h, &self.source, &self.target)
}
TransformMethod::Itrf(params, epoch) => {
let ref_epoch = ItRfFrame::Itrf2014.reference_epoch();
params.transform_at_epoch(EpochTransformArgs::new(
lat_r,
lon_r,
h,
&self.source,
&self.target,
ref_epoch,
*epoch,
))
}
};
(lat_out_r.to_degrees(), lon_out_r.to_degrees(), h_out)
}
#[must_use]
pub fn transform_batch(&self, points: &[(f64, f64, f64)]) -> Vec<(f64, f64, f64)> {
points
.iter()
.map(|&(lat, lon, h)| self.transform_degrees(lat, lon, h))
.collect()
}
#[must_use]
pub fn accuracy_meters(&self) -> f64 {
match &self.method {
TransformMethod::Identity => 0.0,
TransformMethod::Molodensky(_) => 1.0,
TransformMethod::BursaWolf(_) => 0.5,
TransformMethod::Itrf(_, _) => 0.01,
}
}
}