#![allow(non_snake_case)]
use crate::authoring::*;
fn common(
op: &Op,
_ctx: &dyn Context,
operands: &mut dyn CoordinateSet,
direction: Direction,
) -> usize {
let ellps = op.params.ellps(0);
let a = ellps.semimajor_axis();
let f = ellps.flattening();
let es = ellps.eccentricity_squared();
let abridged = op.params.boolean("abridged");
let Ok(dx) = op.params.real("dx") else {
return 0;
};
let Ok(dy) = op.params.real("dy") else {
return 0;
};
let Ok(dz) = op.params.real("dz") else {
return 0;
};
let Ok(da) = op.params.real("da") else {
return 0;
};
let Ok(df) = op.params.real("df") else {
return 0;
};
let adffda = ellps.semimajor_axis() * df + ellps.flattening() * da;
let moped = Molodensky {
a,
f,
es,
dx,
dy,
dz,
da,
df,
adffda,
ellps,
abridged,
};
let n = operands.len();
for i in 0..n {
let mut coord = operands.get_coord(i);
let par = calc_molodensky_params(&moped, &coord);
if direction == Fwd {
coord[0] += par[0];
coord[1] += par[1];
coord[2] += par[2];
} else {
coord[0] -= par[0];
coord[1] -= par[1];
coord[2] -= par[2];
}
operands.set_coord(i, &coord);
}
n
}
fn fwd(op: &Op, ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
common(op, ctx, operands, Fwd)
}
fn inv(op: &Op, ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
common(op, ctx, operands, Inv)
}
#[rustfmt::skip]
pub const GAMUT: [OpParameter; 10] = [
OpParameter::Flag { key: "inv" },
OpParameter::Flag { key: "abridged" },
OpParameter::Real { key: "dx", default: Some(0f64) },
OpParameter::Real { key: "dy", default: Some(0f64) },
OpParameter::Real { key: "dz", default: Some(0f64) },
OpParameter::Real { key: "da", default: Some(0f64) },
OpParameter::Real { key: "df", default: Some(0f64) },
OpParameter::Text { key: "ellps", default: Some("GRS80") },
OpParameter::Text { key: "ellps_0", default: Some("GRS80") },
OpParameter::Text { key: "ellps_1", default: Some("GRS80") },
];
pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error> {
let def = ¶meters.instantiated_as;
let mut params = ParsedParameters::new(parameters, &GAMUT)?;
let ellps_0 = params.ellps(0);
let ellps_1 = params.ellps(1);
if params.given.contains_key("ellps_0") && params.given.contains_key("ellps_1") {
let da = ellps_1.semimajor_axis() - ellps_0.semimajor_axis();
let df = ellps_1.flattening() - ellps_0.flattening();
params.real.insert("da", da);
params.real.insert("df", df);
}
let descriptor = OpDescriptor::new(def, InnerOp(fwd), Some(InnerOp(inv)));
Ok(Op {
descriptor,
params,
steps: None,
})
}
struct Molodensky {
a: f64,
f: f64,
es: f64,
dx: f64,
dy: f64,
dz: f64,
da: f64,
df: f64,
adffda: f64,
ellps: Ellipsoid,
abridged: bool,
}
fn calc_molodensky_params(op: &Molodensky, coord: &Coor4D) -> Coor4D {
let lam = coord[0];
let phi = coord[1];
let h = coord[2];
let (slam, clam) = lam.sin_cos();
let (sphi, cphi) = phi.sin_cos();
let N = op.ellps.prime_vertical_radius_of_curvature(phi);
let M = op.ellps.meridian_radius_of_curvature(phi);
let fac = op.dx * clam + op.dy * slam;
if op.abridged {
let dphi = (-fac * sphi + op.dz * cphi + op.adffda * (2.0 * phi).sin()) / M;
let dlam_denom = N * cphi;
if dlam_denom == 0.0 {
return Coor4D::nan();
}
let dlam = (op.dy * clam - op.dx * slam) / dlam_denom;
let dh = fac * cphi + (op.dz + op.adffda * sphi) * sphi - op.da;
return Coor4D::raw(dlam, dphi, dh, 0.0);
}
let mut dphi = (op.dz + ((N * op.es * sphi * op.da) / op.a)) * cphi - fac * sphi
+ (M / (1.0 - op.f) + N * (1.0 - op.f)) * op.df * sphi * cphi;
let dphi_denom = M + h;
if dphi_denom == 0.0 {
return Coor4D::nan();
}
dphi /= dphi_denom;
let dlam_denom = (N + h) * cphi;
if dlam_denom == 0.0 {
return Coor4D::nan();
}
let dlam = (op.dy * clam - op.dx * slam) / dlam_denom;
let dh =
fac * cphi + op.dz * sphi - (op.a / N) * op.da + N * (1.0 - op.f) * op.df * sphi * sphi;
Coor4D::raw(dlam, dphi, dh, 0.)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::angular;
#[test]
fn molodensky() -> Result<(), Error> {
let mut ctx = Minimal::default();
let e = Ellipsoid::default();
let definition = "
molodensky ellps_0=WGS84 ellps_1=intl
dx=84.87 dy=96.49 dz=116.95
";
let op = ctx.op(definition)?;
let lat = angular::dms_to_dd(53, 48, 33.82);
let lon = angular::dms_to_dd(2, 7, 46.38);
let WGS84 = Coor4D::geo(lat, lon, 73., 0.0);
let lat = 53.8101570592;
let lon = 2.1309658097;
let ED50 = Coor4D::geo(lat, lon, 28.02470, 0.0);
let mut operands = [WGS84];
ctx.apply(op, Fwd, &mut operands)?;
assert!(e.distance(&ED50, &operands[0]) < 0.005);
assert!((ED50[2] - operands[0][2]).abs() < 0.005);
let mut operands = [ED50];
ctx.apply(op, Inv, &mut operands)?;
assert!(e.distance(&WGS84, &operands[0]) < 0.005);
assert!((WGS84[2] - operands[0][2]).abs() < 0.001);
let definition = "
molodensky ellps_0=WGS84 ellps_1=intl
dx=84.87 dy=96.49 dz=116.95 abridged
";
let op = ctx.op(definition)?;
let mut operands = [WGS84];
ctx.apply(op, Fwd, &mut operands)?;
assert!(e.distance(&ED50, &operands[0]) < 0.1);
assert!((ED50[2] - operands[0][2]).abs() < 0.075);
let mut operands = [ED50];
ctx.apply(op, Inv, &mut operands)?;
assert!(e.distance(&WGS84, &operands[0]) < 0.1);
assert!((WGS84[2] - operands[0][2]).abs() < 0.075);
Ok(())
}
}