1use crate::proj::{
2 CoordinateStep, EPS10, GeodGeodesic, GeodGeodesicline, GeodMask, Proj, ProjMethod, ProjMode,
3 ProjectCoordinates, TransformCoordinates, geod_geninverse, geod_genposition, geod_init,
4 geod_lineinit,
5};
6use alloc::rc::Rc;
7use core::{cell::RefCell, f64::consts::FRAC_PI_2};
8use libm::{asin, atan, atan2, cos, fabs, hypot, sin, sqrt};
9
10#[derive(Debug, Default, Clone, PartialEq)]
12pub struct GnomData {
13 sinph0: f64,
14 cosph0: f64,
15 mode: ProjMode,
16 g: GeodGeodesic,
17}
18
19#[derive(Debug, Clone, PartialEq)]
67pub struct GnomonicProjection {
68 proj: Rc<RefCell<Proj>>,
69 store: RefCell<GnomData>,
70 method: ProjMethod,
71}
72impl ProjectCoordinates for GnomonicProjection {
73 fn code(&self) -> i64 {
74 -1
75 }
76 fn name(&self) -> &'static str {
77 "Gnomonic"
78 }
79 fn names() -> &'static [&'static str] {
80 &["Gnomonic", "gnom"]
81 }
82}
83impl CoordinateStep for GnomonicProjection {
84 fn new(proj: Rc<RefCell<Proj>>) -> Self {
85 let mut store = GnomData::default();
86 let method: ProjMethod;
87 {
88 let proj = &mut proj.borrow_mut();
89
90 method = if proj.es == 0. {
91 if fabs(fabs(proj.phi0) - FRAC_PI_2) < EPS10 {
92 store.mode = if proj.phi0 < 0. { ProjMode::SPole } else { ProjMode::NPole };
93 } else if fabs(proj.phi0) < EPS10 {
94 store.mode = ProjMode::Equit;
95 } else {
96 store.mode = ProjMode::Obliq;
97 store.sinph0 = sin(proj.phi0);
98 store.cosph0 = cos(proj.phi0);
99 }
100 ProjMethod::Spheroidal
101 } else {
102 geod_init(&mut store.g, 1., proj.f);
103 ProjMethod::Ellipsoidal
104 };
105 proj.es = 0.;
106 }
107 GnomonicProjection { proj, store: store.into(), method }
108 }
109 fn forward<P: TransformCoordinates>(&self, p: &mut P) {
110 if self.method == ProjMethod::Ellipsoidal {
111 gnom_e_forward(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
112 } else {
113 gnom_s_forward(&mut self.store.borrow_mut(), p);
114 }
115 }
116 fn inverse<P: TransformCoordinates>(&self, p: &mut P) {
117 if self.method == ProjMethod::Ellipsoidal {
118 gnom_e_inverse(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
119 } else {
120 gnom_s_inverse(&mut self.store.borrow_mut(), &self.proj.borrow(), p);
121 }
122 }
123}
124
125pub fn gnom_s_forward<P: TransformCoordinates>(gnom: &mut GnomData, p: &mut P) {
127 let mut y;
128
129 let sinphi = sin(p.phi());
130 let cosphi = cos(p.phi());
131 let mut coslam = cos(p.lam());
132
133 match gnom.mode {
134 ProjMode::Equit => {
135 y = cosphi * coslam;
136 }
137 ProjMode::Obliq => {
138 y = gnom.sinph0 * sinphi + gnom.cosph0 * cosphi * coslam;
139 }
140 ProjMode::SPole => {
141 y = -sinphi;
142 }
143 ProjMode::NPole => {
144 y = sinphi;
145 }
146 }
147
148 if y <= EPS10 {
149 panic!("Coordinate outside projection domain");
150 }
151
152 y = 1. / y;
153 let x = (y) * cosphi * sin(p.lam());
154 match gnom.mode {
155 ProjMode::Equit => {
156 y *= sinphi;
157 }
158 ProjMode::Obliq => {
159 y *= gnom.cosph0 * sinphi - gnom.sinph0 * cosphi * coslam;
160 }
161 ProjMode::NPole => {
162 coslam = -coslam;
163 y *= cosphi * coslam;
164 }
165 ProjMode::SPole => {
166 y *= cosphi * coslam;
167 }
168 }
169
170 p.set_x(x);
171 p.set_y(y);
172}
173
174pub fn gnom_s_inverse<P: TransformCoordinates>(gnom: &mut GnomData, proj: &Proj, p: &mut P) {
176 let mut x = p.x();
177 let mut y = p.y();
178
179 let rh = hypot(x, y);
180 let mut phi = atan(rh);
181 let lam;
182 let sinz = sin(phi);
183 let cosz = sqrt(1. - sinz * sinz);
184
185 if fabs(rh) <= EPS10 {
186 phi = proj.phi0;
187 lam = 0.;
188 } else {
189 match gnom.mode {
190 ProjMode::Obliq => {
191 phi = cosz * gnom.sinph0 + y * sinz * gnom.cosph0 / rh;
192 if fabs(phi) >= 1. {
193 phi = if phi > 0. { FRAC_PI_2 } else { -FRAC_PI_2 };
194 } else {
195 phi = asin(phi);
196 }
197 y = (cosz - gnom.sinph0 * sin(phi)) * rh;
198 x *= sinz * gnom.cosph0;
199 }
200 ProjMode::Equit => {
201 phi = y * sinz / rh;
202 if fabs(phi) >= 1. {
203 phi = if phi > 0. { FRAC_PI_2 } else { -FRAC_PI_2 };
204 } else {
205 phi = asin(phi);
206 }
207 y = cosz * rh;
208 x *= sinz;
209 }
210 ProjMode::SPole => {
211 phi -= FRAC_PI_2;
212 }
213 ProjMode::NPole => {
214 phi = FRAC_PI_2 - phi;
215 y = -y;
216 }
217 }
218 lam = atan2(x, y);
219 }
220
221 p.set_phi(phi);
222 p.set_lam(lam);
223}
224
225pub fn gnom_e_forward<P: TransformCoordinates>(gnom: &mut GnomData, proj: &Proj, p: &mut P) {
227 let lat0 = proj.phi0.to_degrees();
228 let lon0 = 0.;
229 let lat1 = p.phi().to_degrees();
230 let lon1 = p.lam().to_degrees();
231 let mut azi0 = 0.;
232 let mut m = 0.;
233 let mut _m = 0.;
234
235 geod_geninverse(
236 &mut gnom.g,
237 lat0,
238 lon0,
239 lat1,
240 lon1,
241 &mut 0.,
242 &mut azi0,
243 &mut 0.,
244 &mut m,
245 &mut _m,
246 &mut 0.,
247 &mut 0.,
248 );
249 if _m <= 0. {
250 panic!("Coordinate outside projection domain {_m}");
251 } else {
252 let rho = m / _m;
253 azi0 = azi0.to_radians();
254 p.set_x(rho * sin(azi0));
255 p.set_y(rho * cos(azi0));
256 }
257}
258
259pub fn gnom_e_inverse<P: TransformCoordinates>(gnom: &mut GnomData, proj: &Proj, p: &mut P) {
261 let numit_ = 10;
262 let eps_ = 0.01 * sqrt(f64::EPSILON);
263 let lat0 = proj.phi0.to_degrees();
264 let lon0 = 0.;
265 let azi0 = atan2(p.x(), p.y()).to_degrees();
266 let mut rho = hypot(p.x(), p.y());
267 let mut s = atan(rho);
268 let little = rho <= 1.;
269 if !little {
270 rho = 1. / rho;
271 }
272 let mut l = GeodGeodesicline::default();
273 geod_lineinit(
274 &mut l,
275 &gnom.g,
276 lat0,
277 lon0,
278 azi0,
279 GeodMask::GeodLatitude as u32
280 | GeodMask::GeodLongitude as u32
281 | GeodMask::GeodDistanceIn as u32
282 | GeodMask::GeodReducedlength as u32
283 | GeodMask::GeodGeodesicScale as u32,
284 );
285
286 let mut lat1 = 0.;
287 let mut lon1 = 0.;
288 let mut count = numit_;
289 let mut trip = 0;
290 while count != 0 {
291 count -= 1;
292 let mut m = 0.;
294 let mut _m = 0.;
295 geod_genposition(
296 &l, 0, s, &mut lat1, &mut lon1, &mut 0., &mut s, &mut m, &mut _m, &mut 0., &mut 0.,
297 );
298 if trip != 0 {
299 break;
300 }
301 let ds = if little { (m - rho * _m) * _m } else { (rho * m - _m) * m };
304 s -= ds;
305 if fabs(ds) < eps_ {
307 trip += 1;
308 }
309 }
310 if trip != 0 {
311 p.set_phi(lat1.to_radians());
312 p.set_lam(lon1.to_radians());
313 } else {
314 panic!("Coordinate outside projection domain");
315 }
316}