1#![cfg_attr(not(test), no_std)]
113
114extern crate angle_sc;
115extern crate nalgebra as na;
116
117pub mod great_circle;
118pub mod vector;
119
120use angle_sc::min;
121pub use angle_sc::{Angle, Degrees, Radians, Validate};
122use thiserror::Error;
123
124#[must_use]
128pub fn is_valid_latitude(degrees: f64) -> bool {
129 (-90.0..=90.0).contains(°rees)
130}
131
132#[must_use]
136pub fn is_valid_longitude(degrees: f64) -> bool {
137 (-180.0..=180.0).contains(°rees)
138}
139
140#[derive(Clone, Copy, Debug, PartialEq)]
142pub struct LatLong {
143 lat: Degrees,
144 lon: Degrees,
145}
146
147impl Validate for LatLong {
148 fn is_valid(&self) -> bool {
153 is_valid_latitude(self.lat.0) && is_valid_longitude(self.lon.0)
154 }
155}
156
157impl LatLong {
158 #[must_use]
159 pub const fn new(lat: Degrees, lon: Degrees) -> Self {
160 Self { lat, lon }
161 }
162
163 #[must_use]
164 pub const fn lat(&self) -> Degrees {
165 self.lat
166 }
167
168 #[must_use]
169 pub const fn lon(&self) -> Degrees {
170 self.lon
171 }
172}
173
174#[derive(Error, Debug, PartialEq)]
176pub enum LatLongError {
177 #[error("invalid latitude value: `{0}`")]
178 Latitude(f64),
179 #[error("invalid longitude value: `{0}`")]
180 Longitude(f64),
181}
182
183impl TryFrom<(f64, f64)> for LatLong {
184 type Error = LatLongError;
185
186 fn try_from(lat_long: (f64, f64)) -> Result<Self, Self::Error> {
190 if !is_valid_latitude(lat_long.0) {
191 Err(LatLongError::Latitude(lat_long.0))
192 } else if !is_valid_longitude(lat_long.1) {
193 Err(LatLongError::Longitude(lat_long.1))
194 } else {
195 Ok(Self::new(Degrees(lat_long.0), Degrees(lat_long.1)))
196 }
197 }
198}
199
200#[must_use]
207pub fn calculate_azimuth_and_distance(a: &LatLong, b: &LatLong) -> (Angle, Radians) {
208 let a_lat = Angle::from(a.lat);
209 let b_lat = Angle::from(b.lat);
210 let delta_long = Angle::from((b.lon, a.lon));
211 (
212 great_circle::calculate_gc_azimuth(a_lat, b_lat, delta_long),
213 great_circle::calculate_gc_distance(a_lat, b_lat, delta_long),
214 )
215}
216
217#[must_use]
225pub fn haversine_distance(a: &LatLong, b: &LatLong) -> Radians {
226 let a_lat = Angle::from(a.lat);
227 let b_lat = Angle::from(b.lat);
228 let delta_lat = Angle::from((b.lat, a.lat));
229 let delta_long = Angle::from(b.lon - a.lon);
230 great_circle::calculate_haversine_distance(a_lat, b_lat, delta_long, delta_lat)
231}
232
233#[allow(clippy::module_name_repetitions)]
235pub type Vector3d = na::Vector3<f64>;
236
237impl From<&LatLong> for Vector3d {
238 fn from(a: &LatLong) -> Self {
246 vector::to_point(Angle::from(a.lat), Angle::from(a.lon))
247 }
248}
249
250impl From<&Vector3d> for LatLong {
251 fn from(value: &Vector3d) -> Self {
253 Self::new(
254 Degrees::from(vector::latitude(value)),
255 Degrees::from(vector::longitude(value)),
256 )
257 }
258}
259
260#[derive(Clone, Copy, Debug, PartialEq)]
262pub struct Arc {
263 a: Vector3d,
265 pole: Vector3d,
267 length: Radians,
269 half_width: Radians,
271}
272
273impl Validate for Arc {
274 fn is_valid(&self) -> bool {
279 vector::is_unit(&self.a)
280 && vector::is_unit(&self.pole)
281 && vector::are_orthogonal(&self.a, &self.pole)
282 && !self.length.0.is_sign_negative()
283 && !self.half_width.0.is_sign_negative()
284 }
285}
286
287impl Arc {
288 #[must_use]
295 pub const fn new(a: Vector3d, pole: Vector3d, length: Radians, half_width: Radians) -> Self {
296 Self {
297 a,
298 pole,
299 length,
300 half_width,
301 }
302 }
303
304 #[must_use]
310 pub fn from_lat_lon_azi_length(a: &LatLong, azimuth: Angle, length: Radians) -> Self {
311 Self::new(
312 Vector3d::from(a),
313 vector::calculate_pole(Angle::from(a.lat()), Angle::from(a.lon()), azimuth),
314 length,
315 Radians(0.0),
316 )
317 }
318
319 #[must_use]
324 pub fn between_positions(a: &LatLong, b: &LatLong) -> Self {
325 let (azimuth, length) = calculate_azimuth_and_distance(a, b);
326 let a_lat = Angle::from(a.lat());
327 if a_lat.cos().0 < great_circle::MIN_VALUE {
329 Self::from_lat_lon_azi_length(&LatLong::new(a.lat(), b.lon()), azimuth, length)
331 } else {
332 Self::from_lat_lon_azi_length(a, azimuth, length)
333 }
334 }
335
336 #[must_use]
340 pub const fn set_half_width(&mut self, half_width: Radians) -> &mut Self {
341 self.half_width = half_width;
342 self
343 }
344
345 #[must_use]
347 pub const fn a(&self) -> Vector3d {
348 self.a
349 }
350
351 #[must_use]
353 pub const fn pole(&self) -> Vector3d {
354 self.pole
355 }
356
357 #[must_use]
359 pub const fn length(&self) -> Radians {
360 self.length
361 }
362
363 #[must_use]
365 pub const fn half_width(&self) -> Radians {
366 self.half_width
367 }
368
369 #[must_use]
371 pub fn azimuth(&self) -> Angle {
372 vector::calculate_azimuth(&self.a, &self.pole)
373 }
374
375 #[must_use]
377 pub fn direction(&self) -> Vector3d {
378 vector::direction(&self.a, &self.pole)
379 }
380
381 #[must_use]
383 pub fn position(&self, distance: Radians) -> Vector3d {
384 vector::position(&self.a, &self.direction(), Angle::from(distance))
385 }
386
387 #[must_use]
389 pub fn b(&self) -> Vector3d {
390 self.position(self.length)
391 }
392
393 #[must_use]
395 pub fn mid_point(&self) -> Vector3d {
396 self.position(Radians(0.5 * self.length.0))
397 }
398
399 #[must_use]
406 pub fn perp_position(&self, point: &Vector3d, distance: Radians) -> Vector3d {
407 vector::position(point, &self.pole, Angle::from(distance))
408 }
409
410 #[must_use]
416 pub fn angle_position(&self, angle: Angle) -> Vector3d {
417 vector::rotate_position(&self.a, &self.pole, angle, Angle::from(self.length))
418 }
419
420 #[must_use]
426 pub fn end_arc(&self, at_b: bool) -> Self {
427 let p = if at_b { self.b() } else { self.a };
428 let pole = vector::direction(&p, &self.pole);
429 if self.half_width.0 < great_circle::MIN_VALUE {
430 Self::new(p, pole, Radians(0.0), Radians(0.0))
431 } else {
432 let a = self.perp_position(&p, self.half_width);
433 Self::new(a, pole, self.half_width + self.half_width, Radians(0.0))
434 }
435 }
436
437 #[must_use]
444 pub fn calculate_atd_and_xtd(&self, point: &Vector3d) -> (Radians, Radians) {
445 vector::calculate_atd_and_xtd(&self.a, &self.pole(), point)
446 }
447
448 #[must_use]
454 pub fn shortest_distance(&self, point: &Vector3d) -> Radians {
455 let (atd, xtd) = self.calculate_atd_and_xtd(point);
456 if vector::intersection::is_alongside(atd, self.length, Radians(4.0 * f64::EPSILON)) {
457 xtd.abs()
458 } else {
459 let sq_a = vector::sq_distance(&self.a, point);
460 let sq_b = vector::sq_distance(&self.b(), point);
461 let sq_d = min(sq_a, sq_b);
462 great_circle::e2gc_distance(libm::sqrt(sq_d))
463 }
464 }
465}
466
467#[derive(Error, Debug, PartialEq)]
469pub enum ArcError {
470 #[error("positions are too close: `{0}`")]
471 PositionsTooClose(f64),
472 #[error("positions are too far apart: `{0}`")]
473 PositionsTooFar(f64),
474}
475
476impl TryFrom<(&LatLong, &LatLong)> for Arc {
477 type Error = ArcError;
478
479 fn try_from(params: (&LatLong, &LatLong)) -> Result<Self, Self::Error> {
483 let a = Vector3d::from(params.0);
485 let b = Vector3d::from(params.1);
486 vector::normalise(&a.cross(&b), vector::MIN_SQ_NORM).map_or_else(
488 || {
489 let sq_d = vector::sq_distance(&a, &b);
490 if sq_d < 1.0 {
491 Err(ArcError::PositionsTooClose(sq_d))
492 } else {
493 Err(ArcError::PositionsTooFar(sq_d))
494 }
495 },
496 |pole| {
497 Ok(Self::new(
498 a,
499 pole,
500 great_circle::e2gc_distance(vector::distance(&a, &b)),
501 Radians(0.0),
502 ))
503 },
504 )
505 }
506}
507
508#[must_use]
517pub fn calculate_intersection_distances(arc1: &Arc, arc2: &Arc) -> (Radians, Radians) {
518 vector::intersection::calculate_intersection_point_distances(
519 &arc1.a,
520 &arc1.pole,
521 arc1.length(),
522 &arc2.a,
523 &arc2.pole,
524 arc2.length(),
525 &(0.5 * (arc1.mid_point() + arc2.mid_point())),
526 )
527}
528
529#[must_use]
562pub fn calculate_intersection_point(arc1: &Arc, arc2: &Arc) -> Option<Vector3d> {
563 let (distance1, distance2) = calculate_intersection_distances(arc1, arc2);
564
565 if vector::intersection::is_alongside(distance1, arc1.length(), Radians(4.0 * f64::EPSILON))
567 && vector::intersection::is_alongside(distance2, arc2.length(), Radians(4.0 * f64::EPSILON))
568 {
569 Some(arc1.position(distance1.clamp(arc1.length())))
570 } else {
571 None
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578 use angle_sc::{is_within_tolerance, Degrees};
579
580 #[test]
581 fn test_is_valid_latitude() {
582 assert!(!is_valid_latitude(-90.0001));
584 assert!(is_valid_latitude(-90.0));
586 assert!(is_valid_latitude(90.0));
588 assert!(!is_valid_latitude(90.0001));
590 }
591
592 #[test]
593 fn test_is_valid_longitude() {
594 assert!(!is_valid_longitude(-180.0001));
596 assert!(is_valid_longitude(-180.0));
598 assert!(is_valid_longitude(180.0));
600 assert!(!is_valid_longitude(180.0001));
602 }
603
604 #[test]
605 fn test_latlong_traits() {
606 let a = LatLong::try_from((0.0, 90.0)).unwrap();
607
608 assert!(a.is_valid());
609
610 let a_clone = a.clone();
611 assert!(a_clone == a);
612
613 assert_eq!(Degrees(0.0), a.lat());
614 assert_eq!(Degrees(90.0), a.lon());
615
616 println!("LatLong: {:?}", a);
617
618 let invalid_lat = LatLong::try_from((91.0, 0.0));
619 assert_eq!(Err(LatLongError::Latitude(91.0)), invalid_lat);
620 println!("invalid_lat: {:?}", invalid_lat);
621
622 let invalid_lon = LatLong::try_from((0.0, 181.0));
623 assert_eq!(Err(LatLongError::Longitude(181.0)), invalid_lon);
624 println!("invalid_lon: {:?}", invalid_lon);
625 }
626
627 #[test]
628 fn test_vector3d_traits() {
629 let a = LatLong::try_from((0.0, 90.0)).unwrap();
630 let point = Vector3d::from(&a);
631
632 assert_eq!(0.0, point.x);
633 assert_eq!(1.0, point.y);
634 assert_eq!(0.0, point.z);
635
636 assert_eq!(Degrees(0.0), Degrees::from(vector::latitude(&point)));
637 assert_eq!(Degrees(90.0), Degrees::from(vector::longitude(&point)));
638
639 let result = LatLong::from(&point);
640 assert_eq!(a, result);
641 }
642
643 #[test]
644 fn test_great_circle_90n_0n_0e() {
645 let a = LatLong::new(Degrees(90.0), Degrees(0.0));
646 let b = LatLong::new(Degrees(0.0), Degrees(0.0));
647 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
648
649 assert!(is_within_tolerance(
650 core::f64::consts::FRAC_PI_2,
651 dist.0,
652 f64::EPSILON
653 ));
654 assert_eq!(180.0, Degrees::from(azimuth).0);
655
656 let dist = haversine_distance(&a, &b);
657 assert!(is_within_tolerance(
658 core::f64::consts::FRAC_PI_2,
659 dist.0,
660 f64::EPSILON
661 ));
662 }
663
664 #[test]
665 fn test_great_circle_90s_0n_50e() {
666 let a = LatLong::new(Degrees(-90.0), Degrees(0.0));
667 let b = LatLong::new(Degrees(0.0), Degrees(50.0));
668 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
669
670 assert!(is_within_tolerance(
671 core::f64::consts::FRAC_PI_2,
672 dist.0,
673 f64::EPSILON
674 ));
675 assert_eq!(0.0, Degrees::from(azimuth).0);
676
677 let dist = haversine_distance(&a, &b);
678 assert!(is_within_tolerance(
679 core::f64::consts::FRAC_PI_2,
680 dist.0,
681 f64::EPSILON
682 ));
683 }
684
685 #[test]
686 fn test_great_circle_0n_60e_0n_60w() {
687 let a = LatLong::new(Degrees(0.0), Degrees(60.0));
688 let b = LatLong::new(Degrees(0.0), Degrees(-60.0));
689 let (azimuth, dist) = calculate_azimuth_and_distance(&a, &b);
690
691 assert!(is_within_tolerance(
692 2.0 * core::f64::consts::FRAC_PI_3,
693 dist.0,
694 2.0 * f64::EPSILON
695 ));
696 assert_eq!(-90.0, Degrees::from(azimuth).0);
697
698 let dist = haversine_distance(&a, &b);
699 assert!(is_within_tolerance(
700 2.0 * core::f64::consts::FRAC_PI_3,
701 dist.0,
702 2.0 * f64::EPSILON
703 ));
704 }
705
706 #[test]
707 fn test_arc() {
708 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
710
711 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
713
714 let mut arc = Arc::between_positions(&g_eq, &e_eq);
715 let arc = arc.set_half_width(Radians(0.01));
716 assert!(arc.is_valid());
717 assert_eq!(Radians(0.01), arc.half_width());
718
719 assert_eq!(Vector3d::from(&g_eq), arc.a());
720 assert_eq!(Vector3d::new(0.0, 0.0, 1.0), arc.pole());
721 assert!(is_within_tolerance(
722 core::f64::consts::FRAC_PI_2,
723 arc.length().0,
724 f64::EPSILON
725 ));
726 assert_eq!(Angle::from(Degrees(90.0)), arc.azimuth());
727 let b = Vector3d::from(&e_eq);
728 assert!(is_within_tolerance(
729 0.0,
730 vector::distance(&b, &arc.b()),
731 f64::EPSILON
732 ));
733
734 let mid_point = arc.mid_point();
735 assert_eq!(0.0, mid_point.z);
736 assert!(is_within_tolerance(
737 45.0,
738 Degrees::from(vector::longitude(&mid_point)).0,
739 32.0 * f64::EPSILON
740 ));
741
742 let start_arc = arc.end_arc(false);
743 assert_eq!(0.02, start_arc.length().0);
744
745 let start_arc_a = start_arc.a();
746 assert_eq!(start_arc_a, arc.perp_position(&arc.a(), Radians(0.01)));
747
748 let angle_90 = Angle::from(Degrees(90.0));
749 let pole_0 = Vector3d::new(0.0, 0.0, 1.0);
750 assert!(vector::distance(&pole_0, &arc.angle_position(angle_90)) <= f64::EPSILON);
751
752 let end_arc = arc.end_arc(true);
753 assert_eq!(0.02, end_arc.length().0);
754
755 let end_arc_a = end_arc.a();
756 assert_eq!(end_arc_a, arc.perp_position(&arc.b(), Radians(0.01)));
757 }
758
759 #[test]
760 fn test_north_and_south_poles() {
761 let north_pole = LatLong::new(Degrees(90.0), Degrees(0.0));
762 let south_pole = LatLong::new(Degrees(-90.0), Degrees(0.0));
763
764 let (azimuth, distance) = calculate_azimuth_and_distance(&south_pole, &north_pole);
765 assert_eq!(0.0, Degrees::from(azimuth).0);
766 assert_eq!(core::f64::consts::PI, distance.0);
767
768 let (azimuth, distance) = calculate_azimuth_and_distance(&north_pole, &south_pole);
769 assert_eq!(180.0, Degrees::from(azimuth).0);
770 assert_eq!(core::f64::consts::PI, distance.0);
771
772 let e_eq = LatLong::new(Degrees(0.0), Degrees(50.0));
774
775 let arc = Arc::between_positions(&north_pole, &e_eq);
776 assert!(is_within_tolerance(
777 e_eq.lat().0,
778 LatLong::from(&arc.b()).lat().abs().0,
779 1e-13
780 ));
781 assert!(is_within_tolerance(
782 e_eq.lon().0,
783 LatLong::from(&arc.b()).lon().0,
784 50.0 * f64::EPSILON
785 ));
786
787 let arc = Arc::between_positions(&south_pole, &e_eq);
788 assert!(is_within_tolerance(
789 e_eq.lat().0,
790 LatLong::from(&arc.b()).lat().abs().0,
791 1e-13
792 ));
793 assert!(is_within_tolerance(
794 e_eq.lon().0,
795 LatLong::from(&arc.b()).lon().0,
796 50.0 * f64::EPSILON
797 ));
798
799 let w_eq = LatLong::new(Degrees(0.0), Degrees(-140.0));
800
801 let arc = Arc::between_positions(&north_pole, &w_eq);
802 assert!(is_within_tolerance(
803 w_eq.lat().0,
804 LatLong::from(&arc.b()).lat().abs().0,
805 1e-13
806 ));
807 assert!(is_within_tolerance(
808 w_eq.lon().0,
809 LatLong::from(&arc.b()).lon().0,
810 256.0 * f64::EPSILON
811 ));
812
813 let arc = Arc::between_positions(&south_pole, &w_eq);
814 assert!(is_within_tolerance(
815 w_eq.lat().0,
816 LatLong::from(&arc.b()).lat().abs().0,
817 1e-13
818 ));
819 assert!(is_within_tolerance(
820 w_eq.lon().0,
821 LatLong::from(&arc.b()).lon().0,
822 256.0 * f64::EPSILON
823 ));
824
825 let invalid_arc = Arc::try_from((&north_pole, &north_pole));
826 assert_eq!(Err(ArcError::PositionsTooClose(0.0)), invalid_arc);
827 println!("invalid_arc: {:?}", invalid_arc);
828
829 let arc = Arc::between_positions(&north_pole, &north_pole);
830 assert_eq!(north_pole, LatLong::from(&arc.b()));
831
832 let invalid_arc = Arc::try_from((&north_pole, &south_pole));
833 assert_eq!(Err(ArcError::PositionsTooFar(4.0)), invalid_arc);
834 println!("invalid_arc: {:?}", invalid_arc);
835
836 let arc = Arc::between_positions(&north_pole, &south_pole);
837 assert_eq!(south_pole, LatLong::from(&arc.b()));
838
839 let arc = Arc::between_positions(&south_pole, &north_pole);
840 assert_eq!(north_pole, LatLong::from(&arc.b()));
841
842 let arc = Arc::between_positions(&south_pole, &south_pole);
843 assert_eq!(south_pole, LatLong::from(&arc.b()));
844 }
845
846 #[test]
847 fn test_arc_atd_and_xtd() {
848 let g_eq = LatLong::new(Degrees(0.0), Degrees(0.0));
850
851 let e_eq = LatLong::new(Degrees(0.0), Degrees(90.0));
853
854 let arc = Arc::try_from((&g_eq, &e_eq)).unwrap();
855 assert!(arc.is_valid());
856
857 let start_arc = arc.end_arc(false);
858 assert_eq!(0.0, start_arc.length().0);
859
860 let start_arc_a = start_arc.a();
861 assert_eq!(arc.a(), start_arc_a);
862
863 let longitude = Degrees(1.0);
864
865 for lat in -83..84 {
868 let latitude = Degrees(lat as f64);
869 let latlong = LatLong::new(latitude, longitude);
870 let point = Vector3d::from(&latlong);
871
872 let expected = (lat as f64).to_radians();
873 let (atd, xtd) = arc.calculate_atd_and_xtd(&point);
874 assert!(is_within_tolerance(1_f64.to_radians(), atd.0, f64::EPSILON));
875 assert!(is_within_tolerance(expected, xtd.0, 2.0 * f64::EPSILON));
876
877 let d = arc.shortest_distance(&point);
878 assert!(is_within_tolerance(
879 libm::fabs(expected),
880 d.0,
881 2.0 * f64::EPSILON
882 ));
883 }
884
885 let point = Vector3d::from(&g_eq);
886 let d = arc.shortest_distance(&point);
887 assert_eq!(0.0, d.0);
888
889 let point = Vector3d::from(&e_eq);
890 let d = arc.shortest_distance(&point);
891 assert_eq!(0.0, d.0);
892
893 let latlong = LatLong::new(Degrees(0.0), Degrees(-1.0));
894 let point = Vector3d::from(&latlong);
895 let d = arc.shortest_distance(&point);
896 assert!(is_within_tolerance(1_f64.to_radians(), d.0, f64::EPSILON));
897
898 let point = -point;
899 let d = arc.shortest_distance(&point);
900 assert!(is_within_tolerance(89_f64.to_radians(), d.0, f64::EPSILON));
901 }
902
903 #[test]
904 fn test_arc_intersection_point() {
905 let istanbul = LatLong::new(Degrees(42.0), Degrees(29.0));
909 let washington = LatLong::new(Degrees(39.0), Degrees(-77.0));
910 let reyjavik = LatLong::new(Degrees(64.0), Degrees(-22.0));
911 let accra = LatLong::new(Degrees(6.0), Degrees(0.0));
912
913 let arc1 = Arc::try_from((&istanbul, &washington)).unwrap();
914 let arc2 = Arc::try_from((&reyjavik, &accra)).unwrap();
915
916 let intersection_point = calculate_intersection_point(&arc1, &arc2).unwrap();
917 let lat_long = LatLong::from(&intersection_point);
918 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
920 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
922
923 let intersection_point = calculate_intersection_point(&arc2, &arc1).unwrap();
925 let lat_long = LatLong::from(&intersection_point);
926 assert!(is_within_tolerance(54.72, lat_long.lat().0, 0.05));
928 assert!(is_within_tolerance(-14.56, lat_long.lon().0, 0.02));
930 }
931
932 #[test]
933 fn test_arc_intersection_same_great_circles() {
934 let south_pole_1 = LatLong::new(Degrees(-88.0), Degrees(-180.0));
935 let south_pole_2 = LatLong::new(Degrees(-87.0), Degrees(0.0));
936
937 let arc1 = Arc::try_from((&south_pole_1, &south_pole_2)).unwrap();
938
939 let intersection_lengths = calculate_intersection_distances(&arc1, &arc1);
940 assert_eq!(Radians(0.0), intersection_lengths.0);
941 assert_eq!(Radians(0.0), intersection_lengths.1);
942
943 let intersection_point = calculate_intersection_point(&arc1, &arc1).unwrap();
944 assert_eq!(0.0, vector::sq_distance(&arc1.a(), &intersection_point));
945
946 let south_pole_3 = LatLong::new(Degrees(-85.0), Degrees(0.0));
947 let south_pole_4 = LatLong::new(Degrees(-86.0), Degrees(0.0));
948 let arc2 = Arc::try_from((&south_pole_3, &south_pole_4)).unwrap();
949 let intersection_point = calculate_intersection_point(&arc1, &arc2);
950 assert!(intersection_point.is_none());
951 }
952}