Skip to main content

unit_sphere/
vector.rs

1// Copyright (c) 2024-2026 Ken Barker
2
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation the
6// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7// sell copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21//! The `vector` module contains functions for performing great circle
22//! calculations using `Vector3`s to represent points and great circle poles
23//! on a unit sphere.
24//!
25//! A `Vector3` is a [nalgebra](https://crates.io/crates/nalgebra) `Vector3`.
26
27extern crate nalgebra as na;
28
29use crate::{Vector3, great_circle};
30use angle_sc::{Angle, Radians, trig};
31use core::mem::size_of;
32use num_traits::{Float, float::FloatConst};
33
34pub mod intersection;
35
36/// The minimum value of the sine of an f64 angle to normalise.
37/// Approximately 7.504e-9 seconds
38pub const MIN_SIN_MULTIPLE: u32 = 16384;
39
40/// The minimum value of the sine of an f32 angle to normalise.
41pub const MIN_SIN_MULTIPLE_F32: u32 = 4096;
42
43/// Get the minimum value of the sine of an angle for type `T`.
44#[allow(clippy::missing_panics_doc)]
45#[must_use]
46pub fn get_min_sin_angle<T: Float>() -> T {
47    let min_angle_multiple = if size_of::<T>() < size_of::<f64>() {
48        MIN_SIN_MULTIPLE_F32
49    } else {
50        MIN_SIN_MULTIPLE
51    };
52    let min_angle_multiple =
53        T::from(min_angle_multiple).expect("Could not convert constant to Float");
54    min_angle_multiple * T::epsilon()
55}
56
57/// Convert a latitude and longitude to a point on the unit sphere.
58///
59/// @pre |lat| <= 90.0 degrees.
60/// * `lat` - the latitude.
61/// * `lon` - the longitude.
62///
63/// returns a `Vector3` of the point on the unit sphere.
64#[must_use]
65pub fn to_point<T: Float>(lat: Angle<T>, lon: Angle<T>) -> Vector3<T> {
66    Vector3::<T>::new(
67        lat.cos().0 * lon.cos().0,
68        lat.cos().0 * lon.sin().0,
69        lat.sin().0,
70    )
71}
72
73/// Calculate the latitude of a point.
74///
75/// * `a` - the point.
76///
77/// returns the latitude of the point
78#[must_use]
79pub fn latitude<T: Float>(a: &Vector3<T>) -> Angle<T> {
80    Angle::from_y_x(a[2], a[0].hypot(a[1]))
81}
82
83/// Calculate the longitude of a point.
84///
85/// * `a` - the point.
86///
87/// returns the longitude of the point
88#[must_use]
89pub fn longitude<T: Float>(a: &Vector3<T>) -> Angle<T> {
90    Angle::from_y_x(a[1], a[0])
91}
92
93/// Determine whether a `Vector3` is a unit vector.
94///
95/// * `a` - the vector.
96///
97/// returns true if `a` is a unit vector, false otherwise.
98#[allow(clippy::missing_panics_doc)]
99#[must_use]
100pub fn is_unit<T>(a: &Vector3<T>) -> bool
101where
102    T: Float + na::Scalar + na::ComplexField<RealField = T>,
103{
104    let twelve = T::from(12).expect("Could not convert constant to Float");
105    let min_sq_length = T::one() - twelve * T::epsilon();
106    let max_sq_length = T::one() + twelve * T::epsilon();
107
108    (min_sq_length..=max_sq_length).contains(&(a.norm_squared()))
109}
110
111/// Normalize a vector to lie on the surface of the unit sphere.
112///
113/// Note: this function returns an `Option` so uses the British spelling of
114/// `normalise` to differentiate it from the standard `normalize` function.
115/// * `a` the `Vector3`
116/// * `min_sq_value` the minimum square of a vector length to normalize.
117///
118/// return the nomalized point or None if the vector is too small to normalize.
119#[must_use]
120pub fn normalise<T>(a: &Vector3<T>, min_sq_value: T) -> Option<Vector3<T>>
121where
122    T: Float + na::Scalar + na::ComplexField<RealField = T>,
123{
124    if a.norm_squared() < min_sq_value {
125        None
126    } else {
127        Some(a.normalize())
128    }
129}
130
131/// Calculate the square of the Euclidean distance between two points.
132///
133/// Note: points do NOT need to be valid Points.
134/// @post for unit vectors: result <= 4
135/// * `a`, `b` the points.
136///
137/// returns the square of the Euclidean distance between the points.
138#[must_use]
139pub fn sq_distance<T>(a: &Vector3<T>, b: &Vector3<T>) -> T
140where
141    T: Float + na::Scalar + na::ComplexField<RealField = T>,
142{
143    (b - a).norm_squared()
144}
145
146/// Calculate the shortest (Euclidean) distance between two Points.
147///
148/// @post for unit vectors: result <= 2
149/// * `a`, `b` the points.
150///
151/// returns the shortest (Euclidean) distance between the points.
152#[must_use]
153pub fn distance<T>(a: &Vector3<T>, b: &Vector3<T>) -> T
154where
155    T: Float + na::Scalar + na::ComplexField<RealField = T>,
156{
157    (b - a).norm()
158}
159
160/// Determine whether two `Vector3`s are orthogonal (perpendicular).
161///
162/// * `a`, `b` the `Vector3`s.
163///
164/// returns true if a and b are orthogonal, false otherwise.
165#[must_use]
166pub fn are_orthogonal<T>(a: &Vector3<T>, b: &Vector3<T>) -> bool
167where
168    T: Float + na::Scalar + na::ComplexField<RealField = T>,
169{
170    let two_epsilon = T::epsilon() + T::epsilon();
171    let max_length = two_epsilon + two_epsilon;
172
173    (-max_length..=max_length).contains(&(a.dot(b)))
174}
175
176/// Calculate the relative longitude of point a from point b.
177///
178/// * `a`, `b` - the points.
179///
180/// returns the relative longitude of point a from point b,
181/// negative if a is West of b, positive otherwise.
182#[must_use]
183pub fn delta_longitude<T>(a: &Vector3<T>, b: &Vector3<T>) -> Angle<T>
184where
185    T: Float + na::Scalar + na::ComplexField<RealField = T>,
186{
187    let a_lon = a.xy();
188    let b_lon = b.xy();
189    Angle::from_y_x(b_lon.perp(&a_lon), b_lon.dot(&a_lon))
190}
191
192/// Determine whether point a is South of point b.
193///
194/// It calculates and compares the z component of the two points.
195/// * `a`, `b` - the points.
196///
197/// returns true if a is South of b, false otherwise.
198#[must_use]
199pub fn is_south_of<T: Float>(a: &Vector3<T>, b: &Vector3<T>) -> bool {
200    a[2] < b[2]
201}
202/// Determine whether point a is West of point b.
203///
204/// It calculates and compares the perp product of the two points.
205/// * `a`, `b` - the points.
206///
207/// returns true if a is West of b, false otherwise.
208#[must_use]
209pub fn is_west_of<T>(a: &Vector3<T>, b: &Vector3<T>) -> bool
210where
211    T: Float + na::Scalar + na::ComplexField<RealField = T>,
212{
213    b.xy().perp(&a.xy()) < T::zero()
214}
215
216/// Calculate the right hand pole vector of a Great Circle from an initial
217/// position and an azimuth.
218///
219/// See: <http://www.movable-type.co.uk/scripts/latlong-vectors.html#distance>
220/// * `lat` - start point Latitude.
221/// * `lon` - start point Longitude.
222/// * `azi` - start point azimuth.
223///
224/// returns the right hand pole vector of the great circle.
225#[must_use]
226pub fn calculate_pole<T: Float>(lat: Angle<T>, lon: Angle<T>, azi: Angle<T>) -> Vector3<T> {
227    let x = trig::UnitNegRange::<T>::clamp(
228        lon.sin().0 * azi.cos().0 - lat.sin().0 * lon.cos().0 * azi.sin().0,
229    );
230    let y = trig::UnitNegRange::<T>::clamp(
231        T::zero() - lon.cos().0 * azi.cos().0 - lat.sin().0 * lon.sin().0 * azi.sin().0,
232    );
233    let z = trig::UnitNegRange::<T>(lat.cos().0 * azi.sin().0);
234
235    Vector3::new(x.0, y.0, z.0)
236}
237
238/// Calculate the azimuth at a point on the Great Circle defined by pole.
239///
240/// * `point` - the point.
241/// * `pole` - the right hand pole of the Great Circle.
242///
243/// returns the azimuth at the point on the great circle.
244#[must_use]
245pub fn calculate_azimuth<T>(point: &Vector3<T>, pole: &Vector3<T>) -> Angle<T>
246where
247    T: Float + na::Scalar + na::ComplexField<RealField = T>,
248{
249    let max_lat = T::one() - (T::epsilon() + T::epsilon());
250
251    let sin_lat: T = point[2];
252    // if the point is close to the North or South poles, azimuth is 180 or 0.
253    if max_lat <= Float::abs(sin_lat) {
254        // azimuth is zero or 180 degrees
255        return if sin_lat.is_sign_negative() {
256            Angle::default()
257        } else {
258            Angle::new(trig::UnitNegRange(T::zero()), trig::UnitNegRange(-T::one()))
259        };
260    }
261
262    Angle::from_y_x(pole[2], pole.xy().perp(&point.xy()))
263}
264
265/// Calculate the direction vector along a Great Circle from an initial
266/// position and an azimuth.
267///
268/// See: Panou and Korakitis equations: 30, 31, & 32a
269/// <https://arxiv.org/abs/1811.03513>
270/// * `lat` - start point Latitude.
271/// * `lon` - start point Longitude.
272/// * `azi` - start point azimuth.
273///
274/// returns the direction vector at the point on the great circle.
275#[must_use]
276pub fn calculate_direction<T: Float>(lat: Angle<T>, lon: Angle<T>, azi: Angle<T>) -> Vector3<T> {
277    let x = trig::UnitNegRange::clamp(
278        T::zero() - lat.sin().0 * lon.cos().0 * azi.cos().0 - lon.sin().0 * azi.sin().0,
279    );
280    let y = trig::UnitNegRange::clamp(
281        T::zero() - lat.sin().0 * lon.sin().0 * azi.cos().0 + lon.cos().0 * azi.sin().0,
282    );
283    let z = trig::UnitNegRange(lat.cos().0 * azi.cos().0);
284
285    Vector3::new(x.0, y.0, z.0)
286}
287
288/// Calculate the direction vector of a Great Circle arc.
289///
290/// * `a` - the start point.
291/// * `pole` - the pole of a Great Circle.
292///
293/// returns the direction vector at the point on the great circle.
294#[must_use]
295pub fn direction<T>(a: &Vector3<T>, pole: &Vector3<T>) -> Vector3<T>
296where
297    T: Float + na::Scalar + na::ComplexField<RealField = T>,
298{
299    pole.cross(a)
300}
301
302/// Calculate the position of a point along a Great Circle arc.
303///
304/// * `a` - the start point.
305/// * `dir` - the direction vector of a Great Circle at a.
306/// * `distance` - the a Great Circle as an Angle.
307///
308/// returns the position vector at the point on the great circle.
309#[must_use]
310pub fn position<T>(a: &Vector3<T>, dir: &Vector3<T>, distance: Angle<T>) -> Vector3<T>
311where
312    T: Float + na::Scalar + na::ComplexField<RealField = T>,
313{
314    a * distance.cos().0 + dir * distance.sin().0
315}
316
317/// Calculate the direction vector of a Great Circle rotated by angle.
318///
319/// * `dir` - the direction vector of a Great Circle arc.
320/// * `pole` - the pole of a Great Circle.
321/// * `angle` - the angle to rotate the direction vector by.
322///
323/// returns the direction vector at the point on the great circle
324/// rotated by angle.
325#[must_use]
326pub fn rotate<T>(dir: &Vector3<T>, pole: &Vector3<T>, angle: Angle<T>) -> Vector3<T>
327where
328    T: Float + na::Scalar + na::ComplexField<RealField = T>,
329{
330    position(dir, pole, angle)
331}
332
333/// Calculate the position of a point rotated by angle at radius.
334///
335/// * `a` - the start point.
336/// * `pole` - the pole of a Great Circle.
337/// * `angle` - the angle to rotate the direction vector by.
338/// * `radius` - the radius from the start point.
339///
340/// returns the position vector at angle and radius from the start point.
341#[must_use]
342pub fn rotate_position<T>(
343    a: &Vector3<T>,
344    pole: &Vector3<T>,
345    angle: Angle<T>,
346    radius: Angle<T>,
347) -> Vector3<T>
348where
349    T: Float + na::Scalar + na::ComplexField<RealField = T>,
350{
351    position(a, &rotate(&direction(a, pole), pole, angle), radius)
352}
353
354/// The sine of the across track distance of a point relative to a Great Circle pole.
355///
356/// It is simply the dot product of the pole and the point: pole . point
357/// * `pole` - the Great Circle pole.
358/// * `point` - the point.
359///
360/// returns the sine of the across track distance of point relative to the pole.
361#[must_use]
362fn sin_xtd<T>(pole: &Vector3<T>, point: &Vector3<T>) -> trig::UnitNegRange<T>
363where
364    T: Float + na::Scalar + na::ComplexField<RealField = T>,
365{
366    trig::UnitNegRange::clamp(pole.dot(point))
367}
368
369/// Determine whether point is right of a Great Circle pole.
370///
371/// It compares the dot product of the pole and point.
372/// * `pole` - the Great Circle pole.
373/// * `point` - the point.
374///
375/// returns true if the point is right of the pole,
376/// false if on or to the left of the Great Circle.
377#[must_use]
378pub fn is_right_of<T>(pole: &Vector3<T>, point: &Vector3<T>) -> bool
379where
380    T: Float + na::Scalar + na::ComplexField<RealField = T>,
381{
382    pole.dot(point) < T::zero()
383}
384
385/// The across track distance of a point relative to a Great Circle pole.
386///
387/// * `pole` - the Great Circle pole.
388/// * `point` - the point.
389///
390/// returns the across track distance of point relative to pole, in `Radians`.
391#[must_use]
392pub fn cross_track_distance<T>(pole: &Vector3<T>, point: &Vector3<T>) -> Radians<T>
393where
394    T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
395{
396    let sin_d = sin_xtd(pole, point);
397    if Float::abs(sin_d.0) < T::epsilon() {
398        Radians(T::zero())
399    } else {
400        Radians(Float::asin(sin_d.0))
401    }
402}
403
404/// The square of the Euclidean cross track distance of a point relative to a
405/// Great Circle pole.
406///
407/// * `pole` - the Great Circle pole.
408/// * `point` - the point.
409///
410/// returns the square of the euclidean distance of point relative to pole.
411#[must_use]
412pub fn sq_cross_track_distance<T>(pole: &Vector3<T>, point: &Vector3<T>) -> T
413where
414    T: Float + na::Scalar + na::ComplexField<RealField = T>,
415{
416    let two = T::one() + T::one();
417    let sin_d = sin_xtd(pole, point);
418    if Float::abs(sin_d.0) < T::epsilon() {
419        T::zero()
420    } else {
421        two * (T::one() - trig::swap_sin_cos(sin_d).0)
422    }
423}
424
425/// Calculate the closest point on a plane to the given point.
426///
427/// See: [Closest Point on Plane](https://gdbooks.gitbooks.io/3dcollisions/content/Chapter1/closest_point_on_plane.html)
428/// * `pole` - the Great Circle pole (aka normal) of the plane.
429/// * `point` - the point.
430///
431/// returns the closest point on a plane to the given point.
432#[must_use]
433fn calculate_point_on_plane<T>(pole: &Vector3<T>, point: &Vector3<T>) -> Vector3<T>
434where
435    T: Float + na::Scalar + na::ComplexField<RealField = T>,
436{
437    let t = sin_xtd(pole, point);
438    point - pole * t.0
439}
440
441/// The sine of the along track distance of a point along a Great Circle arc.
442///
443/// It is the triple product of the pole, a and the point:
444/// (pole X a) . point = pole . (a X point)
445/// * `a` - the start point of the Great Circle arc.
446/// * `pole` - the pole of the Great Circle arc.
447/// * `point` - the point.
448///
449/// returns the sine of the along track distance of point relative to the start
450/// of a great circle arc.
451#[must_use]
452pub fn sin_atd<T>(a: &Vector3<T>, pole: &Vector3<T>, point: &Vector3<T>) -> trig::UnitNegRange<T>
453where
454    T: Float + na::Scalar + na::ComplexField<RealField = T>,
455{
456    trig::UnitNegRange::clamp(pole.cross(a).dot(point))
457}
458
459/// Calculate the relative distance of two points on a Great Circle arc.
460///
461/// @pre both points must be on the Great Circle defined by `pole`.
462/// * `a` - the start point of the Great Circle arc.
463/// * `pole` - the pole of the Great Circle arc.
464/// * `point` - a point in the Great Circle.
465///
466/// returns the Great Circle along track distance in `Radians`.
467#[must_use]
468pub fn calculate_great_circle_atd<T>(
469    a: &Vector3<T>,
470    pole: &Vector3<T>,
471    point: &Vector3<T>,
472) -> Radians<T>
473where
474    T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
475{
476    let min_distance = T::epsilon() + T::epsilon();
477    let min_sq_distance = min_distance * min_distance;
478
479    let sq_atd = sq_distance(a, point);
480    if sq_atd < min_sq_distance {
481        Radians(T::zero())
482    } else {
483        Radians(
484            great_circle::e2gc_distance(Float::sqrt(sq_atd))
485                .0
486                .copysign(sin_atd(a, pole, point).0),
487        )
488    }
489}
490
491/// The Great Circle distance of a point along the arc relative to a,
492/// (+ve) ahead of a, (-ve) behind a.
493///
494/// * `a` - the start point of the Great Circle arc.
495/// * `pole` - the pole of the Great Circle arc.
496/// * `point` - the point.
497///
498/// returns the along track distance of point relative to the start of a great circle arc.
499#[allow(clippy::missing_panics_doc)]
500#[must_use]
501pub fn along_track_distance<T>(a: &Vector3<T>, pole: &Vector3<T>, point: &Vector3<T>) -> Radians<T>
502where
503    T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
504{
505    let min_sin_angle = get_min_sin_angle::<T>();
506    let min_sq_norm = min_sin_angle * min_sin_angle;
507
508    let plane_point = calculate_point_on_plane(pole, point);
509    normalise(&plane_point, min_sq_norm).map_or_else(
510        || Radians(T::zero()), // point is too close to a pole
511        |c| calculate_great_circle_atd(a, pole, &c),
512    )
513}
514
515/// Calculate the square of the Euclidean along track distance of a point
516/// from the start of an Arc.
517///
518/// It is calculated using the closest point on the plane to the point.
519/// * `a` - the start point of the Great Circle arc.
520/// * `pole` - the pole of the Great Circle arc.
521/// * `point` - the point.
522///
523/// returns the square of the Euclidean along track distance
524#[allow(clippy::missing_panics_doc)]
525#[must_use]
526pub fn sq_along_track_distance<T>(a: &Vector3<T>, pole: &Vector3<T>, point: &Vector3<T>) -> T
527where
528    T: Float + na::Scalar + na::ComplexField<RealField = T>,
529{
530    let min_distance = T::epsilon() + T::epsilon();
531    let min_sq_distance = min_distance * min_distance;
532
533    let min_sin_angle = get_min_sin_angle::<T>();
534    let min_sq_norm = min_sin_angle * min_sin_angle;
535
536    let plane_point = calculate_point_on_plane(pole, point);
537    normalise(&plane_point, min_sq_norm).map_or_else(
538        || T::zero(), // point is too close to a pole
539        |c| {
540            let sq_d = sq_distance(a, &(c));
541            if sq_d < min_sq_distance {
542                T::zero()
543            } else {
544                sq_d
545            }
546        },
547    )
548}
549
550/// Calculate Great Circle along and across track distances.
551///
552/// * `a` - the start point of the Great Circle arc.
553/// * `pole` - the pole of the Great Circle arc.
554/// * `p` - the point.
555///
556/// returns the along and across track distances of point relative to the
557/// start of a great circle arc.
558#[allow(clippy::missing_panics_doc)]
559#[allow(clippy::similar_names)]
560#[must_use]
561pub fn calculate_atd_and_xtd<T>(
562    a: &Vector3<T>,
563    pole: &Vector3<T>,
564    p: &Vector3<T>,
565) -> (Radians<T>, Radians<T>)
566where
567    T: Float + FloatConst + na::Scalar + na::ComplexField<RealField = T>,
568{
569    let min_distance = T::epsilon() + T::epsilon();
570    let min_sq_distance = min_distance * min_distance;
571
572    let min_sin_angle = get_min_sin_angle::<T>();
573    let min_sq_norm = min_sin_angle * min_sin_angle;
574
575    let mut atd = Radians(T::zero());
576    let mut xtd = Radians(T::zero());
577
578    let sq_d = sq_distance(a, p);
579    if sq_d >= min_sq_distance {
580        // point is not close to a
581        let sin_xtd = sin_xtd(pole, p).0;
582        if Float::abs(sin_xtd) >= T::epsilon() {
583            xtd = Radians(Float::asin(sin_xtd));
584        }
585
586        // the closest point on the plane of the pole to the point
587        let plane_point = p - pole * sin_xtd;
588        atd = normalise(&plane_point, min_sq_norm).map_or_else(
589            || Radians(T::zero()), // point is too close to a pole
590            |c| calculate_great_circle_atd(a, pole, &c),
591        );
592    }
593
594    (atd, xtd)
595}
596
597/// Normalise a centroid on coincident great circles.
598///
599/// Note: it handles the case where the centroid is too small to normalise.
600///
601/// * `centroid` - the centroid to be normalised.
602/// * `point` - a mid-point.
603/// * `pole` - the pole of the Great Circle arc.
604///
605/// returns the normalise centroid.
606#[must_use]
607pub fn normalise_centroid<T>(
608    centroid: &Vector3<T>,
609    point: &Vector3<T>,
610    pole: &Vector3<T>,
611) -> Vector3<T>
612where
613    T: Float + na::Scalar + na::ComplexField<RealField = T>,
614{
615    let min_sin_angle = get_min_sin_angle::<T>();
616    let min_sq_norm = min_sin_angle * min_sin_angle;
617
618    normalise(centroid, min_sq_norm).unwrap_or_else(|| {
619        // centroid is half way between points
620
621        // calculate a point on the coincident great circle
622        // half way between points, closer to the start of the arc
623        position(
624            point,
625            &direction(point, pole),
626            Angle::default().quarter_turn_ccw(),
627        )
628    })
629}
630
631#[cfg(test)]
632mod tests {
633    use core::f64;
634
635    use super::*;
636    use crate::LatLong;
637    use angle_sc::{Degrees, Radians, is_within_tolerance};
638
639    pub const MIN_SIN_ANGLE: f64 = (MIN_SIN_MULTIPLE as f64) * f64::EPSILON;
640    pub const MIN_SQ_NORM: f64 = MIN_SIN_ANGLE * MIN_SIN_ANGLE;
641
642    #[test]
643    fn test_normalise() {
644        let zero = Vector3::new(0.0, 0.0, 0.0);
645        assert!(normalise(&zero, MIN_SQ_NORM).is_none());
646
647        // Greenwich equator
648        let g_eq = Vector3::new(1.0, 0.0, 0.0);
649        assert!(normalise(&g_eq, MIN_SQ_NORM).is_some());
650
651        // A vector just too small to normalize
652        let too_small = Vector3::new(16383.0 * f64::EPSILON, 0.0, 0.0);
653        assert!(normalise(&too_small, MIN_SQ_NORM).is_none());
654
655        assert_eq!(2.0844083160439303e-10, MIN_SIN_ANGLE.to_degrees().asin());
656
657        // A vector just large enough to normalize
658        let small = Vector3::new(MIN_SIN_ANGLE, 0.0, 0.0);
659        let result = normalise(&small, MIN_SQ_NORM);
660        assert!(result.is_some());
661
662        assert!(is_unit(&result.unwrap()));
663        assert_eq!(result.unwrap(), g_eq);
664    }
665
666    #[test]
667    fn test_point_lat_longs() {
668        // Test South pole
669        let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(180.0));
670        let point_south = Vector3::from(&lat_lon_south);
671        assert!(is_unit(&point_south));
672        assert_eq!(Vector3::new(0.0, 0.0, -1.0), point_south);
673
674        assert_eq!(Degrees(-90.0), Degrees::from(latitude(&point_south)));
675        assert_eq!(Degrees(0.0), Degrees::from(longitude(&point_south)));
676
677        let result = LatLong::from(&point_south);
678        assert_eq!(-90.0, result.lat().0);
679        // Note: longitude is now zero, since the poles do not have a Longitude
680        assert_eq!(0.0, result.lon().0);
681
682        // Test Greenwich equator
683        let lat_lon_0_0 = LatLong::new(Degrees(0.0), Degrees(0.0));
684        let point_0 = Vector3::from(&lat_lon_0_0);
685        assert!(is_unit(&point_0));
686        assert_eq!(Vector3::new(1.0, 0.0, 0.0), point_0);
687        assert_eq!(lat_lon_0_0, LatLong::from(&point_0));
688
689        // Test antimeridian equator
690        let lat_lon_0_180 = LatLong::new(Degrees(0.0), Degrees(180.0));
691        let point_1 = Vector3::from(&lat_lon_0_180);
692        assert!(is_unit(&point_1));
693        assert_eq!(Vector3::new(-1.0, 0.0, 0.0), point_1);
694        assert_eq!(false, is_west_of(&point_0, &point_1));
695        assert_eq!(
696            Radians(core::f64::consts::PI),
697            Radians::from(delta_longitude(&point_0, &point_1)).abs()
698        );
699
700        let lat_lon_0_m180 = LatLong::new(Degrees(0.0), Degrees(-180.0));
701        let point_2 = Vector3::from(&lat_lon_0_m180);
702        assert!(is_unit(&point_2));
703        assert_eq!(Vector3::new(-1.0, 0.0, 0.0), point_2);
704        // Converts back to +ve longitude
705        assert_eq!(lat_lon_0_180, LatLong::from(&point_2));
706
707        assert_eq!(false, is_west_of(&point_0, &point_2));
708        assert_eq!(
709            -core::f64::consts::PI,
710            Radians::from(delta_longitude(&point_0, &point_2)).0
711        );
712
713        let lat_lon_0_r3 = LatLong::new(Degrees(0.0), Degrees(3.0_f64.to_degrees()));
714        let point_3 = Vector3::from(&lat_lon_0_r3);
715        assert!(is_unit(&point_3));
716        let result = LatLong::from(&point_3);
717        assert_eq!(0.0, result.lat().0);
718        assert_eq!(
719            3.0_f64,
720            Radians::from(delta_longitude(&point_3, &point_0)).0
721        );
722        assert_eq!(3.0_f64.to_degrees(), result.lon().0);
723        assert!(is_west_of(&point_0, &point_3));
724        assert_eq!(-3.0, Radians::from(delta_longitude(&point_0, &point_3)).0);
725
726        assert_eq!(false, is_west_of(&point_1, &point_3));
727        assert!(is_within_tolerance(
728            core::f64::consts::PI - 3.0,
729            Radians::from(delta_longitude(&point_1, &point_3)).0,
730            f64::EPSILON
731        ));
732
733        let lat_lon_0_mr3 = LatLong::new(Degrees(0.0), Degrees(-3.0_f64.to_degrees()));
734        let point_4 = Vector3::from(&lat_lon_0_mr3);
735        assert!(is_unit(&point_4));
736        assert_eq!(3.0, Radians::from(delta_longitude(&point_0, &point_4)).0);
737
738        let result = LatLong::from(&point_4);
739        assert_eq!(0.0, result.lat().0);
740        assert_eq!(-3.0_f64.to_degrees(), result.lon().0);
741        assert!(is_west_of(&point_1, &point_4));
742        assert!(is_within_tolerance(
743            3.0 - core::f64::consts::PI,
744            Radians::from(delta_longitude(&point_1, &point_4)).0,
745            f64::EPSILON
746        ));
747    }
748
749    #[test]
750    fn test_point_distance() {
751        let lat_lon_south = LatLong::new(Degrees(-90.0), Degrees(0.0));
752        let south_pole = Vector3::from(&lat_lon_south);
753
754        let lat_lon_north = LatLong::new(Degrees(90.0), Degrees(0.0));
755        let north_pole = Vector3::from(&lat_lon_north);
756
757        assert_eq!(0.0, sq_distance(&south_pole, &south_pole));
758        assert_eq!(0.0, sq_distance(&north_pole, &north_pole));
759        assert_eq!(4.0, sq_distance(&south_pole, &north_pole));
760
761        assert_eq!(0.0, distance(&south_pole, &south_pole));
762        assert_eq!(0.0, distance(&north_pole, &north_pole));
763        assert_eq!(2.0, distance(&south_pole, &north_pole));
764
765        // Greenwich equator
766        let g_eq = Vector3::new(1.0, 0.0, 0.0);
767
768        // Test IDL equator
769        let idl_eq = Vector3::new(-1.0, 0.0, 0.0);
770
771        assert_eq!(0.0, sq_distance(&g_eq, &g_eq));
772        assert_eq!(0.0, sq_distance(&idl_eq, &idl_eq));
773        assert_eq!(4.0, sq_distance(&g_eq, &idl_eq));
774
775        assert_eq!(0.0, distance(&g_eq, &g_eq));
776        assert_eq!(0.0, distance(&idl_eq, &idl_eq));
777        assert_eq!(2.0, distance(&g_eq, &idl_eq));
778    }
779
780    #[test]
781    fn test_calculate_azimuth_at_poles() {
782        // Greenwich equator
783        let g_eq = Vector3::new(1.0, 0.0, 0.0);
784        let south_pole = Vector3::new(0.0, 0.0, -1.0);
785        let result = calculate_azimuth(&south_pole, &g_eq);
786        assert_eq!(Angle::default(), result);
787
788        let north_pole = Vector3::new(0.0, 0.0, 1.0);
789        let result = calculate_azimuth(&north_pole, &g_eq);
790        assert_eq!(Angle::default().opposite(), result);
791    }
792
793    #[test]
794    fn test_calculate_pole_azimuth_and_direction() {
795        // Greenwich equator
796        let g_eq = Vector3::new(1.0, 0.0, 0.0);
797
798        // 90 degrees East on the equator
799        let e_eq = Vector3::new(0.0, 1.0, 0.0);
800
801        // 90 degrees West on the equator
802        let w_eq = Vector3::new(0.0, -1.0, 0.0);
803
804        let angle_90 = Angle::from(Degrees(90.0));
805        let pole_a = calculate_pole(
806            Angle::from(Degrees(0.0)),
807            Angle::from(Degrees(0.0)),
808            angle_90,
809        );
810        assert!(are_orthogonal(&g_eq, &pole_a));
811
812        let dir_a = calculate_direction(
813            Angle::from(Degrees(0.0)),
814            Angle::from(Degrees(0.0)),
815            angle_90,
816        );
817        assert!(are_orthogonal(&g_eq, &dir_a));
818        assert!(are_orthogonal(&pole_a, &dir_a));
819        assert_eq!(dir_a, direction(&g_eq, &pole_a));
820
821        let north_pole = Vector3::new(0.0, 0.0, 1.0);
822        assert_eq!(north_pole, pole_a);
823
824        let result = g_eq.cross(&e_eq);
825        assert_eq!(north_pole, result);
826
827        let result = calculate_azimuth(&g_eq, &pole_a);
828        assert_eq!(angle_90, result);
829
830        let pole_b = calculate_pole(
831            Angle::from(Degrees(0.0)),
832            Angle::from(Degrees(0.0)),
833            -angle_90,
834        );
835        assert!(are_orthogonal(&g_eq, &pole_b));
836
837        let dir_b = calculate_direction(
838            Angle::from(Degrees(0.0)),
839            Angle::from(Degrees(0.0)),
840            -angle_90,
841        );
842        assert!(are_orthogonal(&g_eq, &dir_b));
843        assert!(are_orthogonal(&pole_b, &dir_b));
844        assert_eq!(dir_b, direction(&g_eq, &pole_b));
845
846        let south_pole = Vector3::new(0.0, 0.0, -1.0);
847        assert_eq!(south_pole, pole_b);
848
849        let result = g_eq.cross(&w_eq);
850        assert_eq!(south_pole, result);
851
852        let result = calculate_azimuth(&g_eq, &pole_b);
853        assert_eq!(-angle_90, result);
854    }
855
856    #[test]
857    fn test_calculate_position() {
858        // Greenwich equator
859        let g_eq = Vector3::new(1.0, 0.0, 0.0);
860
861        // 90 degrees East on the equator
862        let e_eq = Vector3::new(0.0, 1.0, 0.0);
863
864        let pole_0 = g_eq.cross(&e_eq);
865
866        let angle_90 = Angle::from(Degrees(90.0));
867
868        let pos_1 = position(&g_eq, &direction(&g_eq, &pole_0), angle_90);
869        assert_eq!(e_eq, pos_1);
870
871        let pos_2 = rotate_position(&g_eq, &pole_0, Angle::default(), angle_90);
872        assert_eq!(e_eq, pos_2);
873
874        let pos_3 = rotate_position(&g_eq, &pole_0, angle_90, angle_90);
875        assert_eq!(pole_0, pos_3);
876    }
877
878    #[test]
879    fn test_calculate_cross_track_distance_and_square() {
880        // Greenwich equator
881        let g_eq = Vector3::new(1.0, 0.0, 0.0);
882
883        // 90 degrees East on the equator
884        let e_eq = Vector3::new(0.0, 1.0, 0.0);
885
886        let pole_0 = g_eq.cross(&e_eq);
887
888        let longitude = Degrees(1.0);
889
890        for lat in -89..90 {
891            let latitude = Degrees(f64::from(lat));
892            let latlong = LatLong::new(latitude, longitude);
893            let point = Vector3::from(&latlong);
894
895            assert_eq!(lat < 0, is_south_of(&point, &g_eq));
896            assert_eq!(lat >= 0, !is_south_of(&point, &e_eq));
897            assert_eq!(lat < 0, is_right_of(&pole_0, &point));
898
899            let expected = (f64::from(lat)).to_radians();
900            let xtd = cross_track_distance(&pole_0, &point);
901            // Accuracy reduces outside of this range
902            let tolerance = if (-83..84).contains(&lat) {
903                2.0 * f64::EPSILON
904            } else {
905                32.0 * f64::EPSILON
906            };
907            assert!(is_within_tolerance(expected, xtd.0, tolerance));
908
909            let expected = great_circle::gc2e_distance(Radians(expected));
910            let expected = expected * expected;
911            let xtd2 = sq_cross_track_distance(&pole_0, &point);
912            // Accuracy reduces outside of this range
913            let tolerance = if (-83..84).contains(&lat) {
914                4.0 * f64::EPSILON
915            } else {
916                64.0 * f64::EPSILON
917            };
918            assert!(is_within_tolerance(expected, xtd2, tolerance));
919        }
920    }
921
922    #[test]
923    fn test_calculate_along_track_distance_and_square() {
924        // Greenwich equator
925        let g_eq = Vector3::new(1.0, 0.0, 0.0);
926
927        // 90 degrees East on the equator
928        let e_eq = Vector3::new(0.0, 1.0, 0.0);
929
930        let pole_0 = g_eq.cross(&e_eq);
931
932        // North of Equator
933        let latitude = Degrees(1.0);
934
935        for lon in -179..180 {
936            let longitude = Degrees(f64::from(lon));
937            let latlong = LatLong::new(latitude, longitude);
938            let point = Vector3::from(&latlong);
939
940            let expected = (f64::from(lon)).to_radians();
941            let atd = along_track_distance(&g_eq, &pole_0, &point);
942            // Accuracy reduces outside of this range
943            let tolerance = if (-153..154).contains(&lon) {
944                4.0 * f64::EPSILON
945            } else {
946                32.0 * f64::EPSILON
947            };
948            assert!(is_within_tolerance(expected, atd.0, tolerance));
949
950            let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &point);
951            assert!(is_within_tolerance(expected, atd.0, tolerance));
952            assert!(is_within_tolerance(1_f64.to_radians(), xtd.0, f64::EPSILON));
953
954            let expected = great_circle::gc2e_distance(Radians(expected));
955            let expected = expected * expected;
956            let atd2 = sq_along_track_distance(&g_eq, &pole_0, &point);
957            // Accuracy reduces outside of this range
958            let tolerance = if (-86..87).contains(&lon) {
959                2.0 * f64::EPSILON
960            } else {
961                32.0 * f64::EPSILON
962            };
963            assert!(is_within_tolerance(expected, atd2, tolerance));
964        }
965    }
966
967    #[test]
968    fn test_calculate_along_track_distance_and_square_f32() {
969        // Greenwich equator
970        let g_eq = Vector3::new(1.0_f32, 0.0_f32, 0.0_f32);
971
972        // 90 degrees East on the equator
973        let e_eq = Vector3::new(0.0_f32, 1.0_f32, 0.0_f32);
974
975        let pole_0 = g_eq.cross(&e_eq);
976
977        // North of Equator
978        let latitude = Degrees(1.0_f32);
979
980        for lon in -179..180 {
981            let longitude = lon as f32;
982            let latlong = LatLong::new(latitude, Degrees(longitude));
983            let point = Vector3::from(&latlong);
984
985            let expected = longitude.to_radians();
986            let atd = along_track_distance(&g_eq, &pole_0, &point);
987            // Accuracy reduces outside of this range
988            let tolerance = if (-153..154).contains(&lon) {
989                4.0 * f32::EPSILON
990            } else {
991                32.0 * f32::EPSILON
992            };
993            assert!(is_within_tolerance(expected, atd.0, tolerance));
994
995            let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &point);
996            assert!(is_within_tolerance(expected, atd.0, tolerance));
997            assert!(is_within_tolerance(
998                1.0_f32.to_radians(),
999                xtd.0,
1000                f32::EPSILON
1001            ));
1002
1003            let expected = great_circle::gc2e_distance(Radians(expected));
1004            let expected = expected * expected;
1005            let atd2 = sq_along_track_distance(&g_eq, &pole_0, &point);
1006            // Accuracy reduces outside of this range
1007            let tolerance = if (-86..87).contains(&lon) {
1008                2.0 * f32::EPSILON
1009            } else {
1010                8.0 * f32::EPSILON
1011            };
1012            assert!(is_within_tolerance(expected, atd2, tolerance));
1013        }
1014    }
1015
1016    #[test]
1017    fn test_special_cases() {
1018        // Greenwich equator
1019        let g_eq = Vector3::new(1.0, 0.0, 0.0);
1020
1021        // 90 degrees East on the equator
1022        let e_eq = Vector3::new(0.0, 1.0, 0.0);
1023
1024        let pole_0 = g_eq.cross(&e_eq);
1025
1026        // points are at the poles, so atc and sq_atd are zero
1027        assert_eq!(0.0, along_track_distance(&g_eq, &pole_0, &pole_0).0);
1028        assert_eq!(0.0, sq_along_track_distance(&g_eq, &pole_0, &pole_0));
1029
1030        let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &g_eq);
1031        assert_eq!(0.0, atd.0);
1032        assert_eq!(0.0, xtd.0);
1033
1034        let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &pole_0);
1035        assert_eq!(0.0, atd.0);
1036        assert_eq!(core::f64::consts::FRAC_PI_2, xtd.0);
1037
1038        let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &-pole_0);
1039        assert_eq!(0.0, atd.0);
1040        assert_eq!(-core::f64::consts::FRAC_PI_2, xtd.0);
1041
1042        // Test for 100% code coverage
1043        let near_north_pole = LatLong::new(Degrees(89.99999), Degrees(0.0));
1044        let p = Vector3::from(&near_north_pole);
1045        let (atd, xtd) = calculate_atd_and_xtd(&g_eq, &pole_0, &p);
1046        assert_eq!(0.0, atd.0);
1047        assert!(is_within_tolerance(
1048            core::f64::consts::FRAC_PI_2,
1049            xtd.0,
1050            0.000001
1051        ));
1052    }
1053
1054    #[test]
1055    fn test_normalise_centroid() {
1056        let point_0 = Vector3::new(0.0, 0.0, 0.0);
1057        let point_1 = Vector3::new(1.0, 0.0, 0.0);
1058        let point_m1 = -point_1;
1059        let pole_1 = Vector3::new(0.0, 0.0, 1.0);
1060
1061        // normalised centroid from point_1
1062        let result = normalise_centroid(&point_0, &point_1, &pole_1);
1063        assert_eq!(Vector3::new(0.0, -1.0, 0.0), result);
1064
1065        // normalised centroid from point_1 antoipodal point
1066        let result = normalise_centroid(&point_0, &point_m1, &pole_1);
1067        assert_eq!(Vector3::new(0.0, 1.0, 0.0), result);
1068
1069        // normalised centroid from point_1 centroid
1070        let point_2 = point_1 + point_1;
1071        let result = normalise_centroid(&point_2, &point_1, &pole_1);
1072        assert_eq!(point_1, result);
1073    }
1074
1075    #[test]
1076    fn test_normalise_centroid_f32() {
1077        let point_0 = Vector3::new(0.0_f32, 0.0_f32, 0.0_f32);
1078        let point_1 = Vector3::new(1.0_f32, 0.0_f32, 0.0_f32);
1079        let point_m1 = -point_1;
1080        let pole_1 = Vector3::new(0.0_f32, 0.0_f32, 1.0_f32);
1081
1082        // normalised centroid from point_1
1083        let result = normalise_centroid(&point_0, &point_1, &pole_1);
1084        assert_eq!(Vector3::new(0.0_f32, -1.0_f32, 0.0_f32), result);
1085
1086        // normalised centroid from point_1 antoipodal point
1087        let result = normalise_centroid(&point_0, &point_m1, &pole_1);
1088        assert_eq!(Vector3::new(0.0_f32, 1.0_f32, 0.0_f32), result);
1089
1090        // normalised centroid from point_1 centroid
1091        let point_2 = point_1 + point_1;
1092        let result = normalise_centroid(&point_2, &point_1, &pole_1);
1093        assert_eq!(point_1, result);
1094    }
1095}