gistools/proj/project/
lcc.rs1use crate::proj::{
2 _msfn, CoordinateStep, EPS10, LAMBERT_CONFORMAL_CONIC_1SP, LAMBERT_CONFORMAL_CONIC_2SP,
3 LATITUDE_OF_FIRST_STANDARD_PARALLEL, LATITUDE_OF_NATURAL_ORIGIN, LATITUDE_OF_PROJECTION_CENTRE,
4 LATITUDE_OF_SECOND_STANDARD_PARALLEL, LONGITUDE_OF_NATURAL_ORIGIN, Proj, ProjValue,
5 ProjectCoordinates, TransformCoordinates, phi2, tsfn,
6};
7use alloc::rc::Rc;
8use core::{
9 cell::RefCell,
10 f64::consts::{FRAC_PI_2, FRAC_PI_4},
11};
12use libm::{atan, atan2, cos, fabs, hypot, log, pow, sin, tan};
13
14#[derive(Debug, Default, Clone, PartialEq)]
16pub struct LccData {
17 phi1: f64,
18 phi2: f64,
19 n: f64,
20 rho0: f64,
21 c: f64,
22}
23
24pub type LambertConformalConic1SPProjection =
28 LambertConformalConicProjection<LAMBERT_CONFORMAL_CONIC_1SP>;
29pub type LambertConformalConic2SPProjection =
33 LambertConformalConicProjection<LAMBERT_CONFORMAL_CONIC_2SP>;
34
35#[derive(Debug, Clone, PartialEq)]
93pub struct LambertConformalConicProjection<const C: i64> {
94 proj: Rc<RefCell<Proj>>,
95 store: RefCell<LccData>,
96}
97impl<const C: i64> ProjectCoordinates for LambertConformalConicProjection<C> {
98 fn code(&self) -> i64 {
99 C
100 }
101 fn name(&self) -> &'static str {
102 "Lambert Conformal Conic"
103 }
104 fn names() -> &'static [&'static str] {
105 &[
106 "Lambert Conic Conformal (1SP)",
107 "Lambert_Conformal_Conic_1SP",
108 "Lambert Conic Conformal (2SP)",
109 "Lambert_Conformal_Conic_2SP",
110 "Lambert Conic Conformal (LCC)",
111 "Lambert_Conformal_Conic",
112 "Lambert Conformal Conic",
113 "LambertConformalConic",
114 "lcc",
115 ]
116 }
117}
118impl<const C: i64> CoordinateStep for LambertConformalConicProjection<C> {
119 fn new(proj: Rc<RefCell<Proj>>) -> Self {
120 let mut store = LccData::default();
122 {
123 let proj = &mut proj.borrow_mut();
124 let lat_1 = proj
125 .params
126 .get(&LATITUDE_OF_NATURAL_ORIGIN)
127 .or_else(|| proj.params.get(&LATITUDE_OF_FIRST_STANDARD_PARALLEL))
128 .unwrap_or(&ProjValue::default())
129 .f64();
130 let lat_2 = proj
131 .params
132 .get(&LONGITUDE_OF_NATURAL_ORIGIN)
133 .or_else(|| proj.params.get(&LATITUDE_OF_SECOND_STANDARD_PARALLEL))
134 .unwrap_or(&ProjValue::default())
135 .f64();
136 store.phi1 = lat_1;
137 if lat_2 != 0. {
138 store.phi2 = lat_2;
139 } else {
140 store.phi2 = store.phi1;
141 if proj.params.contains_key(&LATITUDE_OF_PROJECTION_CENTRE) {
142 proj.phi0 = store.phi1;
143 }
144 }
145
146 if fabs(store.phi1 + store.phi2) < EPS10 {
147 panic!("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0");
148 }
149
150 let mut sinphi = sin(store.phi1);
151 store.n = sinphi;
152 let cosphi = cos(store.phi1);
153
154 if fabs(cosphi) < EPS10 || fabs(store.phi1) >= FRAC_PI_2 {
155 panic!("Invalid value for lat_1: |lat_1| should be < 90°");
156 }
157 if fabs(cos(store.phi2)) < EPS10 || fabs(store.phi2) >= FRAC_PI_2 {
158 panic!("Invalid value for lat_2: |lat_2| should be < 90°");
159 }
160
161 let secant = fabs(store.phi1 - store.phi2) >= EPS10;
162 if proj.es != 0. {
163 let m1 = _msfn(sinphi, cosphi, proj.es);
164 let ml1 = tsfn(store.phi1, sinphi, proj.e);
165 if secant {
166 sinphi = sin(store.phi2);
168 store.n = log(m1 / _msfn(sinphi, cos(store.phi2), proj.es));
169 if store.n == 0. {
170 panic!("Invalid value for eccentricity");
171 }
172 let ml2 = tsfn(store.phi2, sinphi, proj.e);
173 let denom = log(ml1 / ml2);
174 if denom == 0. {
175 panic!("Invalid value for eccentricity");
176 }
177 store.n /= denom;
178 }
179 store.rho0 = m1 * pow(ml1, -store.n) / store.n;
180 store.c = store.rho0;
181 store.rho0 *= if fabs(fabs(proj.phi0) - FRAC_PI_2) < EPS10 {
182 0.
183 } else {
184 pow(tsfn(proj.phi0, sin(proj.phi0), proj.e), store.n)
185 };
186 } else {
187 if secant {
188 store.n = log(cosphi / cos(store.phi2))
189 / log(tan(FRAC_PI_4 + 0.5 * store.phi2) / tan(FRAC_PI_4 + 0.5 * store.phi1));
190 }
191 if store.n == 0. {
192 panic!("Invalid value for lat_1 and lat_2: |lat_1 + lat_2| should be > 0");
195 }
196 store.c = cosphi * pow(tan(FRAC_PI_4 + 0.5 * store.phi1), store.n) / store.n;
197 store.rho0 = if fabs(fabs(proj.phi0) - FRAC_PI_2) < EPS10 {
198 0.
199 } else {
200 store.c * pow(tan(FRAC_PI_4 + 0.5 * proj.phi0), -store.n)
201 };
202 }
203 }
204 LambertConformalConicProjection { proj, store: store.into() }
205 }
206 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
207 lcc_e_forward(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
208 }
209 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
210 lcc_e_inverse(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
211 }
212}
213
214pub fn lcc_e_forward<P: TransformCoordinates>(lcc: &mut LccData, proj: &Proj, p: &mut P) {
216 let rho = if fabs(fabs(p.phi()) - FRAC_PI_2) < EPS10 {
217 if (p.phi() * lcc.n) <= 0. {
218 panic!("Coordinate outside projection domain");
219 }
220 0.
221 } else {
222 lcc.c
223 * (if proj.es != 0. {
224 pow(tsfn(p.phi(), sin(p.phi()), proj.e), lcc.n)
225 } else {
226 pow(tan(FRAC_PI_4 + 0.5 * p.phi()), -lcc.n)
227 })
228 };
229 p.set_lam(p.lam() * lcc.n);
230 p.set_x(proj.k0 * (rho * sin(p.lam())));
231 p.set_y(proj.k0 * (lcc.rho0 - rho * cos(p.lam())));
232}
233
234pub fn lcc_e_inverse<P: TransformCoordinates>(lcc: &mut LccData, proj: &Proj, p: &mut P) {
236 let mut x = p.x() / proj.k0;
237 let mut y = p.y() / proj.k0;
238 let phi;
239 let lam;
240
241 y = lcc.rho0 - y;
242 let mut rho = hypot(x, y);
243 if rho != 0. {
244 if lcc.n < 0. {
245 rho = -rho;
246 x = -x;
247 y = -y;
248 }
249 if proj.es != 0. {
250 phi = phi2(pow(rho / lcc.c, 1. / lcc.n), proj.e);
251 if phi == f64::MAX {
252 panic!("Coordinate outside projection domain");
253 }
254 } else {
255 phi = 2. * atan(pow(lcc.c / rho, 1. / lcc.n)) - FRAC_PI_2;
256 }
257 lam = atan2(x, y) / lcc.n;
258 } else {
259 lam = 0.;
260 phi = if lcc.n > 0. { FRAC_PI_2 } else { -FRAC_PI_2 };
261 }
262
263 p.set_phi(phi);
264 p.set_lam(lam);
265}