proj4rs_geodesic/lib.rs
1//! Rust interface for geodesic calculation from [GeographicLib](https://geographiclib.sourceforge.io/html/)
2//! This is the original implementation in C with a Rust interface and inspired
3//! from the [geographiclib rust project](https://github.com/savage13/geographiclib/tree/master)
4//!
5//! This is only a part of the geographiclib implementation needed by proj4rs
6//! for implementing some projections.
7//!
8//! Example
9//!
10//! ```rust
11//! use proj4rs_geodesic::Geodesic;
12//! let g = Geodesic::wgs84();
13//! let (lat1, lon1) = (37.87622, -122.23558); // Berkeley, California
14//! let (lat2, lon2) = (-9.4047, 147.1597); // Port Moresby, New Guinea
15//! let (d_m, az1, az2) = g.inverse(lat1, lon1, lat2, lon2);
16//!
17//! assert_eq!(d_m, 10700471.955233702); // Distance in meters
18//! assert_eq!(az1, -96.91639942294974); // Azimuth at (lat1, lon1)
19//! assert_eq!(az2, -127.32548874543627); // Azimuth at (lat2, lon2)
20//! ```
21//!
22//! Note: this is an alternative to the Vincenty distance calculation.
23//!
24
25use std::sync;
26
27#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
28static GEOD_INIT: std::cell::OnceCell<bool> = std::cell::OnceCell::new();
29
30#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
31static GEOD_INIT: sync::OnceLock<bool> = sync::OnceLock::new();
32
33
34/// Ellipsoid on which Geodesic Calculations are computed
35#[repr(C)]
36#[derive(Clone)]
37pub struct Geodesic {
38 /// Semi-major axis
39 a: f64,
40 /// Flattening
41 f: f64,
42 f1: f64,
43 e2: f64,
44 ep2: f64,
45 n: f64,
46 b: f64,
47 c2: f64,
48 etol2: f64,
49 a3x: [f64; 6],
50 c3x: [f64; 15],
51 c4x: [f64; 21],
52}
53
54impl std::fmt::Display for Geodesic {
55 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
56 write!(f, "Geodesic {{ a: {}, f: {} }}", self.a, self.f)
57 }
58}
59impl std::fmt::Debug for Geodesic {
60 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
61 write!(f, "Geodesic {{ a: {}, f: {} }}", self.a, self.f)
62 }
63}
64
65#[link(name = "geodesic", kind = "static")]
66extern "C" {
67 fn Init();
68 fn geod_init(g: *mut Geodesic, a: f64, f: f64) -> bool;
69 fn geod_inverse(
70 g: *const Geodesic,
71 lat1: f64,
72 lon1: f64,
73 lat2: f64,
74 lon2: f64,
75 ps12: *mut f64,
76 pazi1: *mut f64,
77 pazi2: *mut f64,
78 );
79 fn geod_direct(
80 g: *const Geodesic,
81 lat1: f64,
82 lon1: f64,
83 azi1: f64,
84 s12: f64,
85 plat2: *mut f64,
86 plon2: *mut f64,
87 pazi2: *mut f64,
88 );
89}
90
91impl Geodesic {
92 /// Create new Ellipsoid with semi-major axis `a` in meters and a flattening `f`
93 ///
94 /// ```rust
95 /// use proj4rs_geodesic::Geodesic;
96 /// let g = Geodesic::new(6_378_145.0, 1.0/298.25);
97 /// println!("{}", g);
98 /// // Geodesic { a: 6378145, f: 0.003352891869237217 }
99 /// ```
100 pub fn new(a: f64, f: f64) -> Self {
101 GEOD_INIT.get_or_init(||{
102 unsafe { Init(); }
103 true
104 });
105 unsafe {
106 let mut g = std::mem::MaybeUninit::<Geodesic>::uninit();
107 if !geod_init(g.as_mut_ptr(), a, f) {
108 panic!("geodesic is not initialized");
109 }
110 g.assume_init()
111 }
112 }
113
114 #[allow(non_upper_case_globals)]
115 pub fn wgs84() -> Self {
116 const a: f64 = 6_378_137.0;
117 const f: f64 = 1.0/298.257_223_563; /* WGS84 */
118 Self::new(a, f)
119 }
120
121 /// Compute distance and azimuth from (`lat1`,`lon1`) to (`lat2`,`lon2`)
122 ///
123 /// # Arguments
124 /// - lat1: Latitude of 1st point [degrees] [-90., 90.]
125 /// - lon1: Longitude of 1st point [degrees] [-180., 180.]
126 /// - lat2: Latitude of 2nd point [degrees] [-90. 90]
127 /// - lon2: Longitude of 2nd point [degrees] [-180., 180.]
128 ///
129 /// # Returns
130 /// - s12: Distance from 1st to 2nd point [meters]
131 /// - azi1: Azimuth at 1st point [degrees]
132 /// - azi2: Azimuth at 2nd point [degrees]
133 ///
134 /// If either point is at a pole, the azimuth is defined by keeping the
135 /// longitude fixed, writing lat = ±(90° − ε), and taking the limit ε → 0+.
136 ///
137 /// The solution to the inverse problem is found using Newton's method.
138 /// If this fails to converge (this is very unlikely in geodetic applications
139 /// but does occur for very eccentric ellipsoids), then the bisection method
140 /// is used to refine the solution.
141 ///
142 /// ```rust
143 /// // Example, determine the distance between JFK and Singapore Changi Airport:
144 /// use proj4rs_geodesic::Geodesic;
145 /// let g = Geodesic::wgs84();
146 /// let (jfk_lat, jfk_lon) = (40.64, -73.78);
147 /// let (sin_lat, sin_lon) = (1.36, 103.99);
148 /// let (m, a1, a2) = g.inverse(jfk_lat, jfk_lon, sin_lat, sin_lon);
149 /// assert_eq!(m, 15347512.94051294); // Distance meters
150 /// assert_eq!(a1, 3.3057734780176125); // Azimuth at 1st point
151 /// assert_eq!(a2, 177.48784020815515); // Azimuth at 2nd point (forward)
152 /// ```
153 ///
154 pub fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> (f64, f64, f64) {
155 let mut s12 = 0.0;
156 let mut azi1 = 0.0;
157 let mut azi2 = 0.0;
158 unsafe {
159 geod_inverse(
160 self as *const Geodesic,
161 lat1,
162 lon1,
163 lat2,
164 lon2,
165 &mut s12 as *mut f64,
166 &mut azi1 as *mut f64,
167 &mut azi2 as *mut f64,
168 )
169 };
170 (s12, azi1, azi2)
171 }
172
173 /// Compute a new location (`lat2`,`lon2`) from (`lat1`,`lon1`) a distance `s12` at an azimuth of `azi1`
174 ///
175 /// # Arguments
176 /// - lat1 - Latitude of 1st point [degrees] [-90.,90.]
177 /// - lon1 - Longitude of 1st point [degrees] [-180., 180.]
178 /// - azi1 - Azimuth at 1st point [degrees] [-180., 180.]
179 /// - s12 - Distance from 1st to 2nd point [meters] Value may be negative
180 ///
181 /// # Returns
182 /// - lat2 - Latitude of 2nd point [degrees]
183 /// - lon2 - Longitude of 2nd point [degrees]
184 /// - azi2 - Azimuth at 2nd point
185 ///
186 /// If either point is at a pole, the azimuth is defined by keeping the
187 /// longitude fixed, writing lat = ±(90° − ε), and taking the limit ε → 0+.
188 /// An arc length greater that 180° signifies a geodesic which is not a
189 /// shortest path. (For a prolate ellipsoid, an additional condition is
190 /// necessary for a shortest path: the longitudinal extent must not
191 /// exceed of 180°.)
192 ///
193 /// ```rust
194 /// // Example, determine the point 10000 km NE of JFK:
195 /// use proj4rs_geodesic::Geodesic;
196 /// let g = Geodesic::wgs84();
197 /// let (lat,lon,az) = g.direct(40.64, -73.78, 45.0, 10e6);
198 /// assert_eq!(lat, 32.621100463725796);
199 /// assert_eq!(lon, 49.05248709295982);
200 /// assert_eq!(az, 140.40598587680074);
201 /// ```
202 ///
203 pub fn direct(&self, lat1: f64, lon1: f64, azi1: f64, s12: f64) -> (f64, f64, f64) {
204 let mut lat2 = 0.0;
205 let mut lon2 = 0.0;
206 let mut azi2 = 0.0;
207 unsafe {
208 geod_direct(
209 self as *const Geodesic,
210 lat1,
211 lon1,
212 azi1,
213 s12,
214 &mut lat2 as &mut f64,
215 &mut lon2 as &mut f64,
216 &mut azi2 as &mut f64,
217 )
218 };
219 (lat2, lon2, azi2)
220 }
221}
222
223#[cfg(test)]
224mod tests {
225
226 #[test]
227 fn dist_az_test() {
228 struct TestCase {
229 pub lat1: f64,
230 pub lon1: f64,
231 pub azi1: f64,
232 pub lat2: f64,
233 pub lon2: f64,
234 pub azi2: f64,
235 pub s12: f64,
236 //pub a12: f64,
237 //pub m12: f64,
238 //pub mm12: f64, // M12
239 //pub mm21: f64, // M21
240 //pub ss12: f64, // S12
241 }
242 impl TestCase {
243 fn vec(v: &[f64]) -> Self {
244 Self {
245 lat1: v[0],
246 lon1: v[1],
247 azi1: v[2],
248 lat2: v[3],
249 lon2: v[4],
250 azi2: v[5],
251 s12: v[6],
252 //a12: v[7],
253 //m12: v[8],
254 //mm12: v[9],
255 //mm21: v[10],
256 //ss12: v[11],
257 }
258 }
259 }
260
261 let testcases = [
262 TestCase::vec(&[
263 35.60777,
264 -139.44815,
265 111.098748429560326,
266 -11.17491,
267 -69.95921,
268 129.289270889708762,
269 8935244.5604818305,
270 80.50729714281974,
271 6273170.2055303837,
272 0.16606318447386067,
273 0.16479116945612937,
274 12841384694976.432,
275 ]),
276 TestCase::vec(&[
277 55.52454,
278 106.05087,
279 22.020059880982801,
280 77.03196,
281 197.18234,
282 109.112041110671519,
283 4105086.1713924406,
284 36.892740690445894,
285 3828869.3344387607,
286 0.80076349608092607,
287 0.80101006984201008,
288 61674961290615.615,
289 ]),
290 TestCase::vec(&[
291 -21.97856,
292 142.59065,
293 -32.44456876433189,
294 41.84138,
295 98.56635,
296 -41.84359951440466,
297 8394328.894657671,
298 75.62930491011522,
299 6161154.5773110616,
300 0.24816339233950381,
301 0.24930251203627892,
302 -6637997720646.717,
303 ]),
304 TestCase::vec(&[
305 -66.99028,
306 112.2363,
307 173.73491240878403,
308 -12.70631,
309 285.90344,
310 2.512956620913668,
311 11150344.2312080241,
312 100.278634181155759,
313 6289939.5670446687,
314 -0.17199490274700385,
315 -0.17722569526345708,
316 -121287239862139.744,
317 ]),
318 TestCase::vec(&[
319 -17.42761,
320 173.34268,
321 -159.033557661192928,
322 -15.84784,
323 5.93557,
324 -20.787484651536988,
325 16076603.1631180673,
326 144.640108810286253,
327 3732902.1583877189,
328 -0.81273638700070476,
329 -0.81299800519154474,
330 97825992354058.708,
331 ]),
332 TestCase::vec(&[
333 32.84994,
334 48.28919,
335 150.492927788121982,
336 -56.28556,
337 202.29132,
338 48.113449399816759,
339 16727068.9438164461,
340 150.565799985466607,
341 3147838.1910180939,
342 -0.87334918086923126,
343 -0.86505036767110637,
344 -72445258525585.010,
345 ]),
346 TestCase::vec(&[
347 6.96833,
348 52.74123,
349 92.581585386317712,
350 -7.39675,
351 206.17291,
352 90.721692165923907,
353 17102477.2496958388,
354 154.147366239113561,
355 2772035.6169917581,
356 -0.89991282520302447,
357 -0.89986892177110739,
358 -1311796973197.995,
359 ]),
360 TestCase::vec(&[
361 -50.56724,
362 -16.30485,
363 -105.439679907590164,
364 -33.56571,
365 -94.97412,
366 -47.348547835650331,
367 6455670.5118668696,
368 58.083719495371259,
369 5409150.7979815838,
370 0.53053508035997263,
371 0.52988722644436602,
372 41071447902810.047,
373 ]),
374 TestCase::vec(&[
375 -58.93002,
376 -8.90775,
377 140.965397902500679,
378 -8.91104,
379 133.13503,
380 19.255429433416599,
381 11756066.0219864627,
382 105.755691241406877,
383 6151101.2270708536,
384 -0.26548622269867183,
385 -0.27068483874510741,
386 -86143460552774.735,
387 ]),
388 TestCase::vec(&[
389 -68.82867,
390 -74.28391,
391 93.774347763114881,
392 -50.63005,
393 -8.36685,
394 34.65564085411343,
395 3956936.926063544,
396 35.572254987389284,
397 3708890.9544062657,
398 0.81443963736383502,
399 0.81420859815358342,
400 -41845309450093.787,
401 ]),
402 TestCase::vec(&[
403 -10.62672,
404 -32.0898,
405 -86.426713286747751,
406 5.883,
407 -134.31681,
408 -80.473780971034875,
409 11470869.3864563009,
410 103.387395634504061,
411 6184411.6622659713,
412 -0.23138683500430237,
413 -0.23155097622286792,
414 4198803992123.548,
415 ]),
416 TestCase::vec(&[
417 -21.76221,
418 166.90563,
419 29.319421206936428,
420 48.72884,
421 213.97627,
422 43.508671946410168,
423 9098627.3986554915,
424 81.963476716121964,
425 6299240.9166992283,
426 0.13965943368590333,
427 0.14152969707656796,
428 10024709850277.476,
429 ]),
430 TestCase::vec(&[
431 -19.79938,
432 -174.47484,
433 71.167275780171533,
434 -11.99349,
435 -154.35109,
436 65.589099775199228,
437 2319004.8601169389,
438 20.896611684802389,
439 2267960.8703918325,
440 0.93427001867125849,
441 0.93424887135032789,
442 -3935477535005.785,
443 ]),
444 TestCase::vec(&[
445 -11.95887,
446 -116.94513,
447 92.712619830452549,
448 4.57352,
449 7.16501,
450 78.64960934409585,
451 13834722.5801401374,
452 124.688684161089762,
453 5228093.177931598,
454 -0.56879356755666463,
455 -0.56918731952397221,
456 -9919582785894.853,
457 ]),
458 TestCase::vec(&[
459 -87.85331,
460 85.66836,
461 -65.120313040242748,
462 66.48646,
463 16.09921,
464 -4.888658719272296,
465 17286615.3147144645,
466 155.58592449699137,
467 2635887.4729110181,
468 -0.90697975771398578,
469 -0.91095608883042767,
470 42667211366919.534,
471 ]),
472 TestCase::vec(&[
473 1.74708,
474 128.32011,
475 -101.584843631173858,
476 -11.16617,
477 11.87109,
478 -86.325793296437476,
479 12942901.1241347408,
480 116.650512484301857,
481 5682744.8413270572,
482 -0.44857868222697644,
483 -0.44824490340007729,
484 10763055294345.653,
485 ]),
486 TestCase::vec(&[
487 -25.72959,
488 -144.90758,
489 -153.647468693117198,
490 -57.70581,
491 -269.17879,
492 -48.343983158876487,
493 9413446.7452453107,
494 84.664533838404295,
495 6356176.6898881281,
496 0.09492245755254703,
497 0.09737058264766572,
498 74515122850712.444,
499 ]),
500 TestCase::vec(&[
501 -41.22777,
502 122.32875,
503 14.285113402275739,
504 -7.57291,
505 130.37946,
506 10.805303085187369,
507 3812686.035106021,
508 34.34330804743883,
509 3588703.8812128856,
510 0.82605222593217889,
511 0.82572158200920196,
512 -2456961531057.857,
513 ]),
514 TestCase::vec(&[
515 11.01307,
516 138.25278,
517 79.43682622782374,
518 6.62726,
519 247.05981,
520 103.708090215522657,
521 11911190.819018408,
522 107.341669954114577,
523 6070904.722786735,
524 -0.29767608923657404,
525 -0.29785143390252321,
526 17121631423099.696,
527 ]),
528 TestCase::vec(&[
529 -29.47124,
530 95.14681,
531 -163.779130441688382,
532 -27.46601,
533 -69.15955,
534 -15.909335945554969,
535 13487015.8381145492,
536 121.294026715742277,
537 5481428.9945736388,
538 -0.51527225545373252,
539 -0.51556587964721788,
540 104679964020340.318,
541 ]),
542 ];
543 let g = crate::Geodesic::wgs84();
544 for t in &testcases {
545 let (s, azi1, azi2) = g.inverse(t.lat1, t.lon1, t.lat2, t.lon2);
546 assert!((s - t.s12).abs() < 1e-8, "{} {}", s, t.s12);
547 assert!((azi1 - t.azi1).abs() < 1e-13, "{} {}", azi1, t.azi1);
548 assert!((azi2 - t.azi2).abs() < 1e-13, "{} {}", azi2, t.azi2);
549 }
550 let (s, az1, az2) = g.inverse(0.0, 0.0, 0.0, 10.0);
551 let s0 = 1113194.9079327357;
552 assert!((s - s0).abs() < 1e-5, "{} {}", s, s0);
553 assert_eq!(az1, 90.0);
554 assert_eq!(az2, 90.0);
555 }
556
557 #[test]
558 fn test_debug() {
559 use crate::Geodesic;
560 let g = Geodesic::new(6_378_145.0, 1.0 / 298.25);
561 assert_eq!(
562 format!("{}", g),
563 "Geodesic { a: 6378145, f: 0.003352891869237217 }"
564 );
565 }
566}