1use crate::proj::{
2 CZECH, CoordinateStep, KROVAK, KROVAK_MODIFIED, KROVAK_MODIFIED_NORTH_ORIENTED,
3 KROVAK_NORTH_ORIENTED, LATITUDE_OF_PROJECTION_CENTRE, LONGITUDE_OF_ORIGIN,
4 LONGITUDE_OF_PROJECTION_CENTRE, Proj, ProjValue, ProjectCoordinates,
5 SCALE_FACTOR_AT_NATURAL_ORIGIN, TransformCoordinates,
6};
7use alloc::rc::Rc;
8use core::{
9 cell::RefCell,
10 f64::consts::{FRAC_PI_2, FRAC_PI_4},
11};
12use libm::{asin, atan, atan2, cos, fabs, pow, sin, sqrt, tan};
13const EPS: f64 = 1e-15;
64const UQ: f64 = 1.04216856380474; const S0: f64 = 1.37008346281555; const MAX_ITER: usize = 100;
68
69#[derive(Debug, Default, Clone, PartialEq)]
71pub struct KrovakData {
72 alpha: f64,
73 k: f64,
74 n: f64,
75 rho0: f64,
76 ad: f64,
77 easting_northing: bool,
79 modified: bool,
80}
81
82const X0: f64 = 1089000.0;
83const Y0: f64 = 654000.0;
84const C1: f64 = 2.946529277E-02;
85const C2: f64 = 2.515965696E-02;
86const C3: f64 = 1.193845912E-07;
87const C4: f64 = -4.668270147E-07;
88const C5: f64 = 9.233980362E-12;
89const C6: f64 = 1.523735715E-12;
90const C7: f64 = 1.696780024E-18;
91const C8: f64 = 4.408314235E-18;
92const C9: f64 = -8.331083518E-24;
93const C10: f64 = -3.689471323E-24;
94
95fn mod_krovak_compute_dx_dy(xr: f64, yr: f64) -> (f64, f64) {
102 let x_r2 = xr * xr;
103 let y_r2 = yr * yr;
104 let x_r4 = x_r2 * x_r2;
105 let y_r4 = y_r2 * y_r2;
106
107 let d_x = C1 + C3 * xr - C4 * yr - 2. * C6 * xr * yr
108 + C5 * (x_r2 - y_r2)
109 + C7 * xr * (x_r2 - 3. * y_r2)
110 - C8 * yr * (3. * x_r2 - y_r2)
111 + 4. * C9 * xr * yr * (x_r2 - y_r2)
112 + C10 * (x_r4 + y_r4 - 6. * x_r2 * y_r2);
113 let d_y = C2
114 + C3 * yr
115 + C4 * xr
116 + 2. * C5 * xr * yr
117 + C6 * (x_r2 - y_r2)
118 + C8 * xr * (x_r2 - 3. * y_r2)
119 + C7 * yr * (3. * x_r2 - y_r2)
120 - 4. * C10 * xr * yr * (x_r2 - y_r2)
121 + C9 * (x_r4 + y_r4 - 6. * x_r2 * y_r2);
122
123 (d_x, d_y)
124}
125
126fn krovak_setup(proj: &mut Proj, modified: bool) -> KrovakData {
128 let mut data = KrovakData::default();
129
130 proj.a = 6377397.155;
132 proj.es = 0.006674372230614;
133 proj.e = sqrt(proj.es);
134
135 let lat_0 =
136 proj.params.get(&LATITUDE_OF_PROJECTION_CENTRE).unwrap_or(&ProjValue::default()).f64();
137 proj.phi0 = if lat_0 == 0. { 0.863937979737193 } else { lat_0 };
138
139 let lon_0 = proj
143 .params
144 .get(&LONGITUDE_OF_ORIGIN)
145 .or_else(|| proj.params.get(&LONGITUDE_OF_PROJECTION_CENTRE))
146 .unwrap_or(&ProjValue::default())
147 .f64();
148 proj.lam0 = if lon_0 == 0. { 0.7417649320975901 - 0.308341501185665 } else { lon_0 };
149
150 let k = proj.params.get(&SCALE_FACTOR_AT_NATURAL_ORIGIN).unwrap_or(&ProjValue::default()).f64();
152 proj.k0 = if k == 0. { 0.9999 } else { k };
153
154 data.modified = modified;
155
156 data.easting_northing = true;
157 if proj.params.contains_key(&CZECH) {
158 data.easting_northing = false;
159 }
160
161 data.alpha = sqrt(1. + (proj.es * pow(cos(proj.phi0), 4.)) / (1. - proj.es));
163 let u0 = asin(sin(proj.phi0) / data.alpha);
164 let g = pow(
165 (1. + proj.e * sin(proj.phi0)) / (1. - proj.e * sin(proj.phi0)),
166 data.alpha * proj.e / 2.,
167 );
168 let tan_half_phi0_plus_pi_4 = tan(proj.phi0 / 2. + FRAC_PI_4);
169 if tan_half_phi0_plus_pi_4 == 0.0 {
170 panic!("Invalid value for lat_0: lat_0 + PI/4 should be different from 0");
171 }
172 data.k = tan(u0 / 2. + FRAC_PI_4) / pow(tan_half_phi0_plus_pi_4, data.alpha) * g;
173 let n0 = sqrt(1. - proj.es) / (1. - proj.es * pow(sin(proj.phi0), 2.));
174 data.n = sin(S0);
175 data.rho0 = proj.k0 * n0 / tan(S0);
176 data.ad = FRAC_PI_2 - UQ;
177
178 data
179}
180
181pub type KrovakProjection = KrovakBaseProjection<KROVAK, false>;
185pub type KrovakNorthOrientedProjection = KrovakBaseProjection<KROVAK_NORTH_ORIENTED, false>;
189pub type KrovakModifiedProjection = KrovakBaseProjection<KROVAK_MODIFIED, true>;
193pub type KrovakModifiedNorthOrientedProjection =
197 KrovakBaseProjection<KROVAK_MODIFIED_NORTH_ORIENTED, true>;
198
199#[derive(Debug, Clone, PartialEq)]
245pub struct KrovakBaseProjection<const C: i64, const E: bool> {
246 proj: Rc<RefCell<Proj>>,
247 store: RefCell<KrovakData>,
248}
249impl<const C: i64, const E: bool> ProjectCoordinates for KrovakBaseProjection<C, E> {
250 fn code(&self) -> i64 {
251 C
252 }
253 fn name(&self) -> &'static str {
254 "Krovak"
255 }
256 fn names() -> &'static [&'static str] {
257 &["Krovak", "Modified Krovak"]
258 }
259}
260impl<const C: i64, const E: bool> CoordinateStep for KrovakBaseProjection<C, E> {
261 fn new(proj: Rc<RefCell<Proj>>) -> Self {
262 let store = krovak_setup(&mut proj.borrow_mut(), E);
263 KrovakBaseProjection { proj, store: store.into() }
264 }
265 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
266 krovak_e_forward(&self.store.borrow(), &self.proj.borrow(), p);
267 }
268 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
269 krovak_e_inverse(&self.store.borrow(), &self.proj.borrow(), p);
270 }
271}
272
273pub fn krovak_e_forward<P: TransformCoordinates>(krovak: &KrovakData, proj: &Proj, p: &mut P) {
275 let mut x;
276 let mut y;
277
278 let gfi = pow(
279 (1. + proj.e * sin(p.phi())) / (1. - proj.e * sin(p.phi())),
280 krovak.alpha * proj.e / 2.,
281 );
282
283 let u =
284 2. * (atan(krovak.k * pow(tan(p.phi() / 2. + FRAC_PI_4), krovak.alpha) / gfi) - FRAC_PI_4);
285 let deltav = -p.lam() * krovak.alpha;
286
287 let s = asin(cos(krovak.ad) * sin(u) + sin(krovak.ad) * cos(u) * cos(deltav));
288 let cos_s = cos(s);
289 if cos_s < 1e-12 {
290 x = 0.;
291 y = 0.;
292 p.set_x(x);
293 p.set_y(y);
294 return;
295 }
296 let d = asin(cos(u) * sin(deltav) / cos_s);
297
298 let eps = krovak.n * d;
299 let rho = krovak.rho0 * pow(tan(S0 / 2. + FRAC_PI_4), krovak.n)
300 / pow(tan(s / 2. + FRAC_PI_4), krovak.n);
301
302 x = rho * cos(eps);
303 y = rho * sin(eps);
304
305 if krovak.modified {
308 let xp = x;
309 let yp = y;
310
311 let xr = xp * proj.a - X0;
313 let yr = yp * proj.a - Y0;
314
315 let (dx, dy) = mod_krovak_compute_dx_dy(xr, yr);
316
317 x = xp - dx / proj.a;
318 y = yp - dy / proj.a;
319 }
320
321 core::mem::swap(&mut x, &mut y);
324
325 if krovak.easting_northing {
326 x = -x - 2. * proj.x0 / proj.a;
330 y = -y - 2. * proj.y0 / proj.a;
331 }
332
333 p.set_x(x);
334 p.set_y(y);
335}
336
337pub fn krovak_e_inverse<P: TransformCoordinates>(krovak: &KrovakData, proj: &Proj, p: &mut P) {
339 let mut x = p.x();
340 let mut y = p.y();
341
342 if krovak.easting_northing {
343 y = -y - 2. * proj.x0 / proj.a;
347 x = -x - 2. * proj.y0 / proj.a;
348 }
349
350 core::mem::swap(&mut x, &mut y);
351
352 if krovak.modified {
353 let x_r = x * proj.a - X0;
356 let y_r = y * proj.a - Y0;
357
358 let (d_x, d_y) = mod_krovak_compute_dx_dy(x_r, y_r);
359
360 x += d_x / proj.a;
361 y += d_y / proj.a;
362 }
363
364 let rho = sqrt(x * x + y * y);
365 let eps = atan2(y, x);
366
367 let d = eps / sin(S0);
368 let s = if rho == 0.0 {
369 FRAC_PI_2
370 } else {
371 2. * (atan(pow(krovak.rho0 / rho, 1. / krovak.n) * tan(S0 / 2. + FRAC_PI_4)) - FRAC_PI_4)
372 };
373
374 let u = asin(cos(krovak.ad) * sin(s) - sin(krovak.ad) * cos(s) * cos(d));
375 let deltav = asin(cos(s) * sin(d) / cos(u));
376
377 p.set_lam(proj.lam0 - deltav / krovak.alpha);
378
379 let mut fi1 = u;
381
382 let mut i = MAX_ITER;
383 while i > 0 {
384 p.set_phi(
385 2. * (atan(
386 pow(krovak.k, -1. / krovak.alpha)
387 * pow(tan(u / 2. + FRAC_PI_4), 1. / krovak.alpha)
388 * pow((1. + proj.e * sin(fi1)) / (1. - proj.e * sin(fi1)), proj.e / 2.),
389 ) - FRAC_PI_4),
390 );
391
392 if fabs(fi1 - p.phi()) < EPS {
393 break;
394 }
395 fi1 = p.phi();
396 i -= 1;
397 }
398 if i == 0 {
399 panic!("Coordinate outside projection domain")
400 }
401
402 p.set_lam(p.lam() - proj.lam0);
403}