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}