use crate::authoring::*;
use std::f64::consts::FRAC_PI_2;
const EPS10: f64 = 1e-10;
fn fwd(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
let ellps = op.params.ellps(0);
let a = ellps.semimajor_axis();
let e = ellps.eccentricity();
let lon_0 = op.params.lon(0);
let k_0 = op.params.k(0);
let x_0 = op.params.x(0);
let y_0 = op.params.y(0);
let Ok(n) = op.params.real("n") else { return 0 };
let Ok(c) = op.params.real("c") else { return 0 };
let Ok(rho0) = op.params.real("rho0") else {
return 0;
};
let mut successes = 0_usize;
let length = operands.len();
for i in 0..length {
let (mut lam, phi) = operands.xy(i);
lam -= lon_0;
let mut rho = 0.;
if (phi.abs() - FRAC_PI_2).abs() < EPS10 {
if phi * n <= 0. {
operands.set_coord(i, &Coor4D::nan());
continue;
}
} else {
rho = c * crate::math::ancillary::ts(phi.sin_cos(), e).powf(n);
}
let sc = (lam * n).sin_cos();
let x = a * k_0 * rho * sc.0 + x_0;
let y = a * k_0 * (rho0 - rho * sc.1) + y_0;
operands.set_xy(i, x, y);
successes += 1;
}
successes
}
fn inv(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
let ellps = op.params.ellps(0);
let a = ellps.semimajor_axis();
let e = ellps.eccentricity();
let lon_0 = op.params.lon(0);
let k_0 = op.params.k(0);
let x_0 = op.params.x(0);
let y_0 = op.params.y(0);
let Ok(n) = op.params.real("n") else { return 0 };
let Ok(c) = op.params.real("c") else { return 0 };
let Ok(rho0) = op.params.real("rho0") else {
return 0;
};
let mut successes = 0_usize;
for i in 0..operands.len() {
let (mut x, mut y) = operands.xy(i);
x = (x - x_0) / (a * k_0);
y = rho0 - (y - y_0) / (a * k_0);
let mut rho = x.hypot(y);
if rho == 0. {
let lon = 0.;
let lat = FRAC_PI_2.copysign(n);
operands.set_xy(i, lon, lat);
successes += 1;
continue;
}
if n < 0. {
rho = -rho;
x = -x;
y = -y;
}
let ts0 = (rho / c).powf(1. / n);
let lat = crate::math::ancillary::pj_phi2(ts0, e);
if lat.is_infinite() || lat.is_nan() {
operands.set_coord(i, &Coor4D::nan());
continue;
}
let lon = x.atan2(y) / n + lon_0;
operands.set_xy(i, lon, lat);
successes += 1;
}
successes
}
#[rustfmt::skip]
pub const GAMUT: [OpParameter; 9] = [
OpParameter::Flag { key: "inv" },
OpParameter::Text { key: "ellps", default: Some("GRS80") },
OpParameter::Real { key: "lat_1", default: Some(0_f64) },
OpParameter::Real { key: "lat_2", default: Some(f64::NAN) },
OpParameter::Real { key: "lat_0", default: Some(f64::NAN) },
OpParameter::Real { key: "lon_0", default: Some(0_f64) },
OpParameter::Real { key: "k_0", default: Some(1_f64) },
OpParameter::Real { key: "x_0", default: Some(0_f64) },
OpParameter::Real { key: "y_0", default: Some(0_f64) },
];
pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error> {
let def = ¶meters.instantiated_as;
let mut params = ParsedParameters::new(parameters, &GAMUT)?;
if !params.real.contains_key("lat_2") {
params.real.insert("lat_2", params.lat(1));
}
let phi1 = params.lat(1).to_radians();
let mut phi2 = params.lat(2).to_radians();
if phi2.is_nan() {
phi2 = phi1;
}
params
.real
.insert("lon_0", params.real["lon_0"].to_radians());
params
.real
.insert("lat_0", params.real["lat_0"].to_radians());
params.real.insert("lat_1", phi1);
params.real.insert("lat_2", phi2);
let mut lat_0 = params.lat(0);
if lat_0.is_nan() {
lat_0 = 0.;
if (phi1 - phi2).abs() < EPS10 {
lat_0 = phi1;
}
}
let sc = phi1.sin_cos();
let mut n = sc.0;
let ellps = params.ellps(0);
let e = ellps.eccentricity();
let es = ellps.eccentricity_squared();
if (phi1 + phi2).abs() < EPS10 {
return Err(Error::General(
"Lcc: Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0",
));
}
if sc.1.abs() < EPS10 || phi1.abs() >= FRAC_PI_2 {
return Err(Error::General(
"Lcc: Invalid value for lat_1: |lat_1| should be < 90°",
));
}
if phi2.cos().abs() < EPS10 || phi2.abs() >= FRAC_PI_2 {
return Err(Error::General(
"Lcc: Invalid value for lat_2: |lat_2| should be < 90°",
));
}
let m1 = crate::math::ancillary::pj_msfn(sc, es);
let ml1 = crate::math::ancillary::ts(sc, e);
if (phi1 - phi2).abs() >= EPS10 {
let sc = phi2.sin_cos();
n = (m1 / crate::math::ancillary::pj_msfn(sc, es)).ln();
if n == 0. {
return Err(Error::General("Lcc: Invalid value for eccentricity"));
}
let ml2 = crate::math::ancillary::ts(sc, e);
let denom = (ml1 / ml2).ln();
if denom == 0. {
return Err(Error::General("Lcc: Invalid value for eccentricity"));
}
n /= denom;
}
let c = m1 * ml1.powf(-n) / n;
let mut rho0 = 0.;
if (lat_0.abs() - FRAC_PI_2).abs() > EPS10 {
rho0 = c * crate::math::ancillary::ts(lat_0.sin_cos(), e).powf(n);
}
params.real.insert("c", c);
params.real.insert("n", n);
params.real.insert("rho0", rho0);
params.real.insert("lat_0", lat_0);
let descriptor = OpDescriptor::new(def, InnerOp(fwd), Some(InnerOp(inv)));
Ok(Op {
descriptor,
params,
steps: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_standard_parallel() -> Result<(), Error> {
let mut ctx = Minimal::default();
let definition = "lcc lat_1=57 lon_0=12";
let op = ctx.op(definition)?;
let geo = [
Coor4D::geo(55., 12., 0., 0.),
Coor4D::geo(55., 10., 0., 0.),
Coor4D::geo(59., 14., 0., 0.),
];
let projected = [
Coor4D::raw(-0.000000000101829246, -222_728.122_307_816_05, 0., 0.),
Coor4D::raw(-128_046.472_438_652_24, -220_853.700_160_506_4, 0., 0.),
Coor4D::raw(115_005.414_566_200_68, 224_484.514_376_338_9, 0., 0.),
];
let mut operands = geo;
ctx.apply(op, Fwd, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&projected[i]) < 2e-9);
}
ctx.apply(op, Inv, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&geo[i]) < 1e-9);
}
Ok(())
}
#[test]
fn two_standard_parallels() -> Result<(), Error> {
let mut ctx = Minimal::default();
let definition = "lcc lat_1=33 lat_2=45 lon_0=10";
let op = ctx.op(definition)?;
let geo = [Coor4D::geo(40., 12., 0., 0.)];
let projected = [Coor4D::raw(
169_863.026_093_938_3,
4_735_925.219_292_451,
0.,
0.,
)];
let mut operands = geo;
ctx.apply(op, Fwd, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&projected[i]) < 9e-9);
}
ctx.apply(op, Inv, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&geo[i]) < 1e-9);
}
Ok(())
}
#[test]
fn one_standard_parallel_and_latitudinal_offset() -> Result<(), Error> {
let mut ctx = Minimal::default();
let definition = "lcc lat_1=39 lat_0=35 lon_0=10";
let op = ctx.op(definition)?;
let geo = [Coor4D::geo(40., 12., 0., 0.)];
let projected = [Coor4D([
170_800.011_728_740_65,
557_172.361_112_929_4,
0.,
0.,
])];
let mut operands = geo;
ctx.apply(op, Fwd, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&projected[i]) < 2e-9);
}
ctx.apply(op, Inv, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&geo[i]) < 2e-9);
}
Ok(())
}
#[test]
fn two_standard_parallels_and_latitudinal_offset() -> Result<(), Error> {
let mut ctx = Minimal::default();
let definition = "lcc lat_1=33 lat_2=45 lat_0=35 lon_0=10";
let op = ctx.op(definition)?;
let geo = [Coor4D::geo(40., 12., 0., 0.)];
let projected = [Coor4D::raw(
169_863.026_093_938_36,
554_155.440_793_916_6,
0.,
0.,
)];
let mut operands = geo;
ctx.apply(op, Fwd, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&projected[i]) < 2e-9);
}
ctx.apply(op, Inv, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&geo[i]) < 1e-9);
}
Ok(())
}
#[test]
fn two_sp_lat_offset_xy_offset() -> Result<(), Error> {
let mut ctx = Minimal::default();
let definition = "lcc lat_1=33 lat_2=45 lat_0=35 lon_0=10 x_0=12345 y_0=67890";
let op = ctx.op(definition)?;
let geo = [Coor4D::geo(40., 12., 0., 0.)];
let projected = [Coor4D([
182_208.026_093_938_3,
622_045.440_793_916_6,
0.,
0.,
])];
let mut operands = geo;
ctx.apply(op, Fwd, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&projected[i]) < 2e-9);
}
ctx.apply(op, Inv, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&geo[i]) < 1e-9);
}
Ok(())
}
#[test]
fn two_sp_lat_offset_xy_offset_scaling() -> Result<(), Error> {
let mut ctx = Minimal::default();
let definition = "lcc lat_1=33 lat_2=45 lat_0=35 lon_0=10 x_0=12345 y_0=67890 k_0=0.99";
let op = ctx.op(definition)?;
let geo = [Coor4D::geo(40., 12., 0., 0.)];
let projected = [Coor4D([
180_509.395_832_998_9,
616_503.886_385_977_5,
0.,
0.,
])];
let mut operands = geo;
ctx.apply(op, Fwd, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&projected[i]) < 2e-9);
}
ctx.apply(op, Inv, &mut operands)?;
for i in 0..operands.len() {
assert!(operands[i].hypot2(&geo[i]) < 1e-9);
}
Ok(())
}
}