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