1use std::{
2 num::ParseFloatError,
3 ops::{Add, Sub},
4};
5
6use itertools::*;
7use thiserror::Error;
8
9pub type RawCoord = i32;
10
11const RAW_COORD_INVALID: RawCoord = std::i32::MIN;
13const RAW_COORD_MAX: RawCoord = std::i32::MAX;
14const RAW_COORD_MIN: RawCoord = -RAW_COORD_MAX;
15
16#[derive(Debug, Error)]
17pub enum CoordRangeError {
18 #[error("degrees out of range")]
19 Degrees(f64),
20
21 #[error("radians out of range")]
22 Radians(f64),
23}
24
25#[derive(Debug, Error)]
26pub enum CoordInputError {
27 #[error(transparent)]
28 Range(#[from] CoordRangeError),
29
30 #[error(transparent)]
31 Parse(#[from] ParseFloatError),
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct GeoCoord(RawCoord);
37
38impl GeoCoord {
39 const INVALID: Self = Self(RAW_COORD_INVALID);
40
41 pub const fn max() -> Self {
42 Self(RAW_COORD_MAX)
43 }
44
45 pub const fn min() -> Self {
46 Self(RAW_COORD_MIN)
47 }
48
49 pub const fn to_raw(self) -> RawCoord {
50 self.0
51 }
52
53 pub const fn from_raw(raw: RawCoord) -> Self {
54 Self(raw)
55 }
56
57 pub fn is_valid(self) -> bool {
58 self != Self::INVALID
59 }
60}
61
62impl Default for GeoCoord {
63 fn default() -> Self {
64 let res = Self::INVALID;
65 debug_assert!(!res.is_valid());
66 res
67 }
68}
69
70impl std::cmp::PartialOrd for GeoCoord {
71 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
72 if self == other {
73 Some(std::cmp::Ordering::Equal)
74 } else if self.is_valid() && other.is_valid() {
75 Some(self.to_raw().cmp(&other.to_raw()))
76 } else {
77 None
78 }
79 }
80}
81
82#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd)]
83pub struct LatCoord(GeoCoord);
84
85impl LatCoord {
86 const RAD_MAX: f64 = std::f64::consts::FRAC_PI_2;
87 const RAD_MIN: f64 = -std::f64::consts::FRAC_PI_2;
88 const TO_RAD: f64 =
89 (Self::RAD_MAX - Self::RAD_MIN) / (RAW_COORD_MAX as f64 - RAW_COORD_MIN as f64);
90 const FROM_RAD: f64 =
91 (RAW_COORD_MAX as f64 - RAW_COORD_MIN as f64) / (Self::RAD_MAX - Self::RAD_MIN);
92
93 const DEG_MAX: f64 = 90.0;
94 const DEG_MIN: f64 = -90.0;
95 const TO_DEG: f64 =
96 (Self::DEG_MAX - Self::DEG_MIN) / (RAW_COORD_MAX as f64 - RAW_COORD_MIN as f64);
97 const FROM_DEG: f64 =
98 (RAW_COORD_MAX as f64 - RAW_COORD_MIN as f64) / (Self::DEG_MAX - Self::DEG_MIN);
99
100 pub const fn max() -> Self {
101 Self(GeoCoord::max())
102 }
103
104 pub const fn min() -> Self {
105 Self(GeoCoord::min())
106 }
107
108 pub const fn to_raw(self) -> RawCoord {
109 self.0.to_raw()
110 }
111
112 pub const fn from_raw(raw: RawCoord) -> Self {
113 Self(GeoCoord::from_raw(raw))
114 }
115
116 pub fn is_valid(self) -> bool {
117 self.0.is_valid()
118 }
119
120 #[allow(clippy::absurd_extreme_comparisons)]
121 pub fn to_rad(self) -> f64 {
122 if self.is_valid() {
123 debug_assert!(self.to_raw() >= RAW_COORD_MIN);
124 debug_assert!(self.to_raw() <= RAW_COORD_MAX);
125 let rad = f64::from(self.to_raw()) * Self::TO_RAD;
126 debug_assert!(rad >= Self::RAD_MIN);
127 debug_assert!(rad <= Self::RAD_MAX);
128 rad
129 } else {
130 std::f64::NAN
131 }
132 }
133
134 #[allow(clippy::absurd_extreme_comparisons)]
135 pub fn to_deg(self) -> f64 {
136 if self.is_valid() {
137 debug_assert!(self.to_raw() >= RAW_COORD_MIN);
138 debug_assert!(self.to_raw() <= RAW_COORD_MAX);
139 let deg = f64::from(self.to_raw()) * Self::TO_DEG;
140 debug_assert!(deg >= Self::DEG_MIN);
141 debug_assert!(deg <= Self::DEG_MAX);
142 deg
143 } else {
144 std::f64::NAN
145 }
146 }
147
148 pub fn from_rad<T: Into<f64>>(rad: T) -> Self {
149 let rad = rad.into();
150 debug_assert!(rad >= Self::RAD_MIN);
151 debug_assert!(rad <= Self::RAD_MAX);
152 let raw = f64::round(rad * Self::FROM_RAD) as RawCoord;
153 let res = Self::from_raw(raw);
154 debug_assert!(res.is_valid());
155 res
156 }
157
158 pub fn from_deg<T: Into<f64>>(deg: T) -> Self {
159 let deg = deg.into();
160 debug_assert!(deg >= Self::DEG_MIN);
161 debug_assert!(deg <= Self::DEG_MAX);
162 let raw = f64::round(deg * Self::FROM_DEG) as RawCoord;
163 let res = Self::from_raw(raw);
164 debug_assert!(res.is_valid());
165 res
166 }
167
168 pub fn try_from_deg<T: Into<f64>>(deg: T) -> Result<Self, CoordRangeError> {
169 let deg = deg.into();
170 if (Self::DEG_MIN..=Self::DEG_MAX).contains(°) {
171 Ok(Self::from_deg(deg))
172 } else {
173 Err(CoordRangeError::Degrees(deg))
174 }
175 }
176}
177
178impl Add for LatCoord {
179 type Output = Self;
180
181 fn add(self, rhs: Self) -> Self {
182 let mut deg = self.to_deg() + rhs.to_deg();
183 if deg > Self::DEG_MAX {
184 deg -= Self::DEG_MAX - Self::DEG_MIN;
185 }
186 Self::from_deg(deg)
187 }
188}
189
190impl Sub for LatCoord {
191 type Output = Self;
192
193 fn sub(self, rhs: Self) -> Self {
194 let mut deg = self.to_deg() - rhs.to_deg();
195 if deg < Self::DEG_MIN {
196 deg += Self::DEG_MAX - Self::DEG_MIN;
197 }
198 Self::from_deg(deg)
199 }
200}
201
202impl std::fmt::Display for LatCoord {
203 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
204 write!(f, "{}", self.to_deg())
205 }
206}
207
208#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd)]
209pub struct LngCoord(GeoCoord);
210
211impl LngCoord {
212 const RAD_MAX: f64 = std::f64::consts::PI;
213 const RAD_MIN: f64 = -std::f64::consts::PI;
214 const TO_RAD: f64 =
215 (Self::RAD_MAX - Self::RAD_MIN) / (RAW_COORD_MAX as f64 - RAW_COORD_MIN as f64);
216 const FROM_RAD: f64 =
217 (RAW_COORD_MAX as f64 - RAW_COORD_MIN as f64) / (Self::RAD_MAX - Self::RAD_MIN);
218
219 const DEG_MAX: f64 = 180.0;
220 const DEG_MIN: f64 = -180.0;
221 const TO_DEG: f64 =
222 (Self::DEG_MAX - Self::DEG_MIN) / (RAW_COORD_MAX as f64 - RAW_COORD_MIN as f64);
223 const FROM_DEG: f64 =
224 (RAW_COORD_MAX as f64 - RAW_COORD_MIN as f64) / (Self::DEG_MAX - Self::DEG_MIN);
225
226 pub const fn max() -> Self {
227 Self(GeoCoord::max())
228 }
229
230 pub const fn min() -> Self {
231 Self(GeoCoord::min())
232 }
233
234 pub const fn to_raw(self) -> RawCoord {
235 self.0.to_raw()
236 }
237
238 pub const fn from_raw(raw: RawCoord) -> Self {
239 Self(GeoCoord::from_raw(raw))
240 }
241
242 pub fn is_valid(self) -> bool {
243 self.0.is_valid()
244 }
245
246 #[allow(clippy::absurd_extreme_comparisons)]
247 pub fn to_rad(self) -> f64 {
248 if self.is_valid() {
249 debug_assert!(self.to_raw() >= RAW_COORD_MIN);
250 debug_assert!(self.to_raw() <= RAW_COORD_MAX);
251 let rad = f64::from(self.to_raw()) * Self::TO_RAD;
252 debug_assert!(rad >= Self::RAD_MIN);
253 debug_assert!(rad <= Self::RAD_MAX);
254 rad
255 } else {
256 std::f64::NAN
257 }
258 }
259
260 #[allow(clippy::absurd_extreme_comparisons)]
261 pub fn to_deg(self) -> f64 {
262 if self.is_valid() {
263 debug_assert!(self.to_raw() >= RAW_COORD_MIN);
264 debug_assert!(self.to_raw() <= RAW_COORD_MAX);
265 let deg = f64::from(self.to_raw()) * Self::TO_DEG;
266 debug_assert!(deg >= Self::DEG_MIN);
267 debug_assert!(deg <= Self::DEG_MAX);
268 deg
269 } else {
270 std::f64::NAN
271 }
272 }
273
274 pub fn from_rad<T: Into<f64>>(rad: T) -> Self {
275 let rad = rad.into();
276 debug_assert!(rad >= Self::RAD_MIN);
277 debug_assert!(rad <= Self::RAD_MAX);
278 let raw = f64::round(rad * Self::FROM_RAD) as RawCoord;
279 let res = Self::from_raw(raw);
280 debug_assert!(res.is_valid());
281 res
282 }
283
284 pub fn from_deg<T: Into<f64>>(deg: T) -> Self {
285 let deg = deg.into();
286 debug_assert!(deg >= Self::DEG_MIN);
287 debug_assert!(deg <= Self::DEG_MAX);
288 let raw = f64::round(deg * Self::FROM_DEG) as RawCoord;
289 let res = Self::from_raw(raw);
290 debug_assert!(res.is_valid());
291 res
292 }
293
294 pub fn try_from_deg<T: Into<f64>>(deg: T) -> Result<Self, CoordRangeError> {
295 let deg = deg.into();
296 if (Self::DEG_MIN..=Self::DEG_MAX).contains(°) {
297 Ok(Self::from_deg(deg))
298 } else {
299 Err(CoordRangeError::Degrees(deg))
300 }
301 }
302}
303
304impl Add for LngCoord {
305 type Output = Self;
306
307 fn add(self, rhs: Self) -> Self {
308 let mut deg = self.to_deg() + rhs.to_deg();
309 if deg > Self::DEG_MAX {
310 deg -= Self::DEG_MAX - Self::DEG_MIN;
311 }
312 Self::from_deg(deg)
313 }
314}
315
316impl Sub for LngCoord {
317 type Output = Self;
318
319 fn sub(self, rhs: Self) -> Self {
320 let mut deg = self.to_deg() - rhs.to_deg();
321 if deg < Self::DEG_MIN {
322 deg += Self::DEG_MAX - Self::DEG_MIN;
323 }
324 Self::from_deg(deg)
325 }
326}
327
328impl std::fmt::Display for LngCoord {
329 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
330 write!(f, "{}", self.to_deg())
331 }
332}
333
334#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
336pub struct MapPoint {
337 lat: LatCoord,
338 lng: LngCoord,
339}
340
341impl MapPoint {
342 pub const fn new(lat: LatCoord, lng: LngCoord) -> Self {
343 Self { lat, lng }
344 }
345
346 pub const fn lat(self) -> LatCoord {
347 self.lat
348 }
349
350 pub const fn lng(self) -> LngCoord {
351 self.lng
352 }
353
354 pub fn is_valid(self) -> bool {
355 self.lat.is_valid() && self.lng.is_valid()
356 }
357
358 pub fn to_lat_lng_rad(self) -> (f64, f64) {
359 (self.lat.to_rad(), self.lng.to_rad())
360 }
361
362 pub fn to_lat_lng_deg(self) -> (f64, f64) {
363 (self.lat.to_deg(), self.lng.to_deg())
364 }
365
366 pub fn from_lat_lng_deg<LAT: Into<f64>, LNG: Into<f64>>(lat: LAT, lng: LNG) -> Self {
367 Self::new(LatCoord::from_deg(lat), LngCoord::from_deg(lng))
368 }
369
370 pub fn try_from_lat_lng_deg<LAT: Into<f64>, LNG: Into<f64>>(
371 lat: LAT,
372 lng: LNG,
373 ) -> Result<Self, CoordRangeError> {
374 let lat = LatCoord::try_from_deg(lat)?;
375 let lng = LngCoord::try_from_deg(lng)?;
376 Ok(Self::new(lat, lng))
377 }
378
379 fn parse_lat_lng_deg(lat_deg_str: &str, lng_deg_str: &str) -> Result<Self, MapPointInputError> {
380 let lat_deg = lat_deg_str
381 .parse::<f64>()
382 .map_err(|err| MapPointInputError::Latitude(err.into()))?;
383 let lat = LatCoord::try_from_deg(lat_deg)
384 .map_err(|err| MapPointInputError::Latitude(err.into()))?;
385 debug_assert!(lat.is_valid());
386 let lng_deg = lng_deg_str
387 .parse::<f64>()
388 .map_err(|err| MapPointInputError::Longitude(err.into()))?;
389 let lng = LngCoord::try_from_deg(lng_deg)
390 .map_err(|err| MapPointInputError::Longitude(err.into()))?;
391 debug_assert!(lng.is_valid());
392 Ok(MapPoint::new(lat, lng))
393 }
394}
395
396#[derive(Debug, Error)]
397pub enum MapPointInputError {
398 #[error("latitude: {0}")]
399 Latitude(CoordInputError),
400
401 #[error("longitude: {0}")]
402 Longitude(CoordInputError),
403
404 #[error("invalid format: '{0}'")]
405 Format(String),
406}
407
408impl std::fmt::Display for MapPoint {
409 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
410 write!(f, "{},{}", self.lat, self.lng)
411 }
412}
413
414impl std::str::FromStr for MapPoint {
415 type Err = MapPointInputError;
416
417 fn from_str(s: &str) -> Result<Self, Self::Err> {
418 if let Some((lat_deg_str, lng_deg_str)) = s.split(',').collect_tuple() {
419 MapPoint::parse_lat_lng_deg(lat_deg_str, lng_deg_str)
420 } else {
421 Err(MapPointInputError::Format(s.to_string()))
422 }
423 }
424}
425
426#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
427pub struct Distance(pub f64);
428
429impl Distance {
430 pub const fn infinite() -> Self {
431 Self(std::f64::INFINITY)
432 }
433
434 pub const fn from_meters(meters: f64) -> Self {
435 Self(meters)
436 }
437
438 pub const fn to_meters(self) -> f64 {
439 self.0
440 }
441
442 pub fn is_valid(self) -> bool {
443 self.0 >= 0.0
444 }
445}
446
447const WGS84_MAJOR_SEMIAXIS: Distance = Distance::from_meters(6_378_137.0);
449const WGS84_MINOR_SEMIAXIS: Distance = Distance::from_meters(6_356_752.3);
450
451fn wgs84_earth_radius(lat: LatCoord) -> Distance {
453 let major_n =
454 WGS84_MAJOR_SEMIAXIS.to_meters() * WGS84_MAJOR_SEMIAXIS.to_meters() * lat.to_rad().cos();
455 let minor_n =
456 WGS84_MINOR_SEMIAXIS.to_meters() * WGS84_MINOR_SEMIAXIS.to_meters() * lat.to_rad().sin();
457 let major_d = WGS84_MAJOR_SEMIAXIS.to_meters() * lat.to_rad().cos();
458 let minor_d = WGS84_MINOR_SEMIAXIS.to_meters() * lat.to_rad().sin();
459 Distance::from_meters(
460 ((major_n * major_n + minor_n * minor_n) / (major_d * major_d + minor_d * minor_d)).sqrt(),
461 )
462}
463
464impl MapPoint {
465 pub fn distance(p1: MapPoint, p2: MapPoint) -> Option<Distance> {
470 if !p1.is_valid() || !p2.is_valid() {
471 return None;
472 }
473
474 let (lat1_rad, lng1_rad) = p1.to_lat_lng_rad();
475 let (lat2_rad, lng2_rad) = p2.to_lat_lng_rad();
476
477 let (lat1_sin, lat1_cos) = (lat1_rad.sin(), lat1_rad.cos());
478 let (lat2_sin, lat2_cos) = (lat2_rad.sin(), lat2_rad.cos());
479
480 let dlng = (lng1_rad - lng2_rad).abs();
481 let (dlng_sin, dlng_cos) = (dlng.sin(), dlng.cos());
482
483 let nom1 = lat2_cos * dlng_sin;
484 let nom2 = lat1_cos * lat2_sin - lat1_sin * lat2_cos * dlng_cos;
485
486 let nom = (nom1 * nom1 + nom2 * nom2).sqrt();
487 let denom = lat1_sin * lat2_sin + lat1_cos * lat2_cos * dlng_cos;
488
489 let mean_earth_radius_meters = (wgs84_earth_radius(p1.lat()).to_meters()
490 + wgs84_earth_radius(p2.lat()).to_meters())
491 / 2.0;
492 Some(Distance::from_meters(
493 mean_earth_radius_meters * nom.atan2(denom),
494 ))
495 }
496}
497
498#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
499pub struct MapBbox {
500 sw: MapPoint,
501 ne: MapPoint,
502}
503
504impl MapBbox {
505 pub const fn new(sw: MapPoint, ne: MapPoint) -> Self {
506 Self { sw, ne }
507 }
508
509 pub fn centered_around(center: MapPoint, lat_width: Distance, lng_height: Distance) -> Self {
511 let lat = center.lat();
512 let lng = center.lng();
513
514 let earth_radius = wgs84_earth_radius(lat).to_meters();
516 let parallel_radius = earth_radius * lat.to_rad().cos();
517
518 let lat_delta = LatCoord::from_rad(lat_width.to_meters() / (2.0 * parallel_radius));
519 let lng_delta = LngCoord::from_rad(lng_height.to_meters() / (2.0 * parallel_radius));
520
521 let sw = MapPoint::new(lat - lat_delta, lng - lng_delta);
522 let ne = MapPoint::new(lat + lat_delta, lng + lng_delta);
523
524 Self::new(sw, ne)
525 }
526
527 pub const fn southwest(&self) -> MapPoint {
528 self.sw
529 }
530
531 pub const fn northeast(&self) -> MapPoint {
532 self.ne
533 }
534
535 pub fn is_valid(&self) -> bool {
536 self.sw.is_valid() && self.ne.is_valid() && self.sw.lat() <= self.ne.lat()
537 }
538
539 pub fn is_empty(&self) -> bool {
540 debug_assert!(self.sw.is_valid());
541 debug_assert!(self.ne.is_valid());
542 self.sw.lat() >= self.ne.lat() || self.sw.lng() == self.ne.lng()
543 }
544
545 pub fn contains_point(&self, pt: MapPoint) -> bool {
546 debug_assert!(self.is_valid());
547 debug_assert!(pt.is_valid());
548 if pt.lat() < self.sw.lat() || pt.lat() > self.ne.lat() {
549 return false;
550 }
551 if self.sw.lng() <= self.ne.lng() {
552 pt.lng() >= self.sw.lng() && pt.lng() <= self.ne.lng()
554 } else {
555 !(pt.lng() > self.ne.lng() && pt.lng() < self.sw.lng())
557 }
558 }
559}
560
561impl std::fmt::Display for MapBbox {
562 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
563 write!(f, "{},{}", self.sw, self.ne)
564 }
565}
566
567impl std::str::FromStr for MapBbox {
568 type Err = MapBboxInputError;
569
570 fn from_str(s: &str) -> Result<Self, Self::Err> {
571 if let Some((sw_lat_deg_str, sw_lng_deg_str, ne_lat_deg_str, ne_lng_deg_str)) =
572 s.split(',').collect_tuple()
573 {
574 let sw = MapPoint::parse_lat_lng_deg(sw_lat_deg_str, sw_lng_deg_str)
575 .map_err(MapBboxInputError::Southwest)?;
576 let ne = MapPoint::parse_lat_lng_deg(ne_lat_deg_str, ne_lng_deg_str)
577 .map_err(MapBboxInputError::Northeast)?;
578 Ok(MapBbox::new(sw, ne))
579 } else {
580 Err(MapBboxInputError::Format(s.to_string()))
581 }
582 }
583}
584
585#[derive(Debug, Error)]
586pub enum MapBboxInputError {
587 #[error("southwest point: {0}")]
588 Southwest(MapPointInputError),
589
590 #[error("northeast point: {0}")]
591 Northeast(MapPointInputError),
592
593 #[error("invalid format: '{0}'")]
594 Format(String),
595}
596
597#[cfg(test)]
598#[allow(clippy::unreadable_literal, clippy::float_cmp)]
599mod tests {
600 use super::*;
601
602 #[test]
603 fn latitude() {
604 assert!(!LatCoord::default().is_valid());
605 assert!(LatCoord::default().to_deg().is_nan());
606 assert_eq!(0.0, LatCoord::from_raw(0).to_deg());
607 assert_eq!(RAW_COORD_MIN, LatCoord::min().to_raw());
608 assert_eq!(RAW_COORD_MAX, LatCoord::max().to_raw());
609 assert_eq!(
610 LatCoord::min(),
611 LatCoord::from_raw(LatCoord::min().to_raw())
612 );
613 assert_eq!(
614 LatCoord::max(),
615 LatCoord::from_raw(LatCoord::max().to_raw())
616 );
617 assert_eq!(
618 LatCoord::min(),
619 LatCoord::from_deg(LatCoord::min().to_deg())
620 );
621 assert_eq!(
622 LatCoord::max(),
623 LatCoord::from_deg(LatCoord::max().to_deg())
624 );
625 assert_eq!(LatCoord::min(), LatCoord::from_deg(-90));
626 assert_eq!(LatCoord::max(), LatCoord::from_deg(90));
627 assert!(LatCoord::try_from_deg(-90.000001).is_err());
628 assert!(LatCoord::try_from_deg(90.000001).is_err());
629 }
630
631 #[test]
632 fn longitude() {
633 assert!(!LngCoord::default().is_valid());
634 assert!(LngCoord::default().to_deg().is_nan());
635 assert_eq!(0.0, LngCoord::from_raw(0).to_deg());
636 assert_eq!(RAW_COORD_MIN, LngCoord::min().to_raw());
637 assert_eq!(RAW_COORD_MAX, LngCoord::max().to_raw());
638 assert!(LngCoord::min().is_valid());
639 assert!(LngCoord::max().is_valid());
640 assert_eq!(
641 LngCoord::min(),
642 LngCoord::from_raw(LngCoord::min().to_raw())
643 );
644 assert_eq!(
645 LngCoord::max(),
646 LngCoord::from_raw(LngCoord::max().to_raw())
647 );
648 assert_eq!(
649 LngCoord::min(),
650 LngCoord::from_deg(LngCoord::min().to_deg())
651 );
652 assert_eq!(
653 LngCoord::max(),
654 LngCoord::from_deg(LngCoord::max().to_deg())
655 );
656 assert_eq!(LngCoord::min(), LngCoord::from_deg(-180));
657 assert_eq!(LngCoord::max(), LngCoord::from_deg(180));
658 assert!(LngCoord::try_from_deg(-180.000001).is_err());
659 assert!(LngCoord::try_from_deg(180.000001).is_err());
660 }
661
662 #[test]
663 fn no_distance() {
664 let p1 = MapPoint::from_lat_lng_deg(0.0, 0.0);
665 assert_eq!(MapPoint::distance(p1, p1).unwrap().to_meters(), 0.0);
666
667 let p2 = MapPoint::from_lat_lng_deg(-25.0, 55.0);
668 assert_eq!(MapPoint::distance(p2, p2).unwrap().to_meters(), 0.0);
669
670 let p1 = MapPoint::from_lat_lng_deg(-15.0, -180.0);
671 let p2 = MapPoint::from_lat_lng_deg(-15.0, 180.0);
672 assert!(MapPoint::distance(p1, p2).unwrap().to_meters() < 0.000001);
673 }
674
675 #[test]
676 fn real_distance() {
677 let stuttgart = MapPoint::from_lat_lng_deg(48.7755, 9.1827);
678 let mannheim = MapPoint::from_lat_lng_deg(49.4836, 8.4630);
679 assert!(MapPoint::distance(stuttgart, mannheim).unwrap() > Distance::from_meters(94_000.0));
680 assert!(MapPoint::distance(stuttgart, mannheim).unwrap() < Distance::from_meters(95_000.0));
681
682 let new_york = MapPoint::from_lat_lng_deg(40.714268, -74.005974);
683 let sidney = MapPoint::from_lat_lng_deg(-33.867138, 151.207108);
684 assert!(
685 MapPoint::distance(new_york, sidney).unwrap() > Distance::from_meters(15_985_000.0)
686 );
687 assert!(
688 MapPoint::distance(new_york, sidney).unwrap() < Distance::from_meters(15_995_000.0)
689 );
690 }
691
692 #[test]
693 fn symetric_distance() {
694 let a = MapPoint::from_lat_lng_deg(80.0, 0.0);
695 let b = MapPoint::from_lat_lng_deg(90.0, 20.0);
696 assert_eq!(
697 MapPoint::distance(a, b).unwrap(),
698 MapPoint::distance(b, a).unwrap()
699 );
700 }
701
702 #[test]
703 fn distance_with_invalid_coordinates() {
704 let a = MapPoint::new(LatCoord::from_deg(10.0), Default::default());
705 let b = MapPoint::from_lat_lng_deg(20.0, 20.0);
706 assert_eq!(None, MapPoint::distance(a, b));
707 }
708
709 #[test]
710 fn positive_distance_regressions() {
711 let p1 = MapPoint::from_lat_lng_deg(-81.2281041784343, 77.75747775927069);
712 let p2 = MapPoint::from_lat_lng_deg(40.92116510538438, -93.33303223984923);
713 assert!(MapPoint::distance(p1, p2).unwrap().to_meters() >= 0.0);
714
715 let p1 = MapPoint::from_lat_lng_deg(67.01568147028595, 122.10276824520099);
716 let p2 = MapPoint::from_lat_lng_deg(-87.84709362678561, 132.71691422570353);
717 assert!(MapPoint::distance(p1, p2).unwrap().to_meters() >= 0.0);
718
719 let p1 = MapPoint::from_lat_lng_deg(-37.44489137895633, -124.46758920534867);
720 let p2 = MapPoint::from_lat_lng_deg(29.29724492099939, 0.03218860366949281);
721 assert!(MapPoint::distance(p1, p2).unwrap().to_meters() >= 0.0);
722 }
723
724 #[test]
725 fn bbox_contains_point() {
726 let sw = MapPoint::from_lat_lng_deg(-25.0, -20.0);
727 let ne = MapPoint::from_lat_lng_deg(25.0, 30.0);
728 let bbox = MapBbox::new(sw, ne);
729 assert!(bbox.contains_point(MapPoint::from_lat_lng_deg(-10.0, -15.0)));
730 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(-26.0, -15.0)));
731 assert!(bbox.contains_point(MapPoint::from_lat_lng_deg(10.0, 20.0)));
732 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(26.0, 20.0)));
733 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(-10.0, -21.0)));
734 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(10.0, 31.0)));
735
736 let sw = MapPoint::from_lat_lng_deg(-25.0, 175.0);
737 let ne = MapPoint::from_lat_lng_deg(25.0, -175.0);
738 let bbox = MapBbox::new(sw, ne);
739 assert!(bbox.contains_point(MapPoint::from_lat_lng_deg(-10.0, 177.0)));
740 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(-26.0, 177.0)));
741 assert!(bbox.contains_point(MapPoint::from_lat_lng_deg(10.0, -177.0)));
742 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(26.0, 177.0)));
743 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(-10.0, 174.0)));
744 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(10.0, -174.0)));
745
746 let sw = MapPoint::from_lat_lng_deg(-25.0, 30.0);
747 let ne = MapPoint::from_lat_lng_deg(25.0, 10.0);
748 let bbox = MapBbox::new(sw, ne);
749 assert!(bbox.contains_point(MapPoint::from_lat_lng_deg(-10.0, 5.0)));
750 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(-26.0, 5.0)));
751 assert!(bbox.contains_point(MapPoint::from_lat_lng_deg(10.0, 35.0)));
752 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(26.0, 35.0)));
753 assert!(bbox.contains_point(MapPoint::from_lat_lng_deg(10.0, 180.0)));
754 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(26.0, 180.0)));
755 assert!(bbox.contains_point(MapPoint::from_lat_lng_deg(10.0, -180.0)));
756 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(26.0, -180.0)));
757 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(-10.0, 11.0)));
758 assert!(!bbox.contains_point(MapPoint::from_lat_lng_deg(10.0, 29.0)));
759
760 let bbox1 = MapBbox::new(
761 MapPoint::from_lat_lng_deg(0.0, 0.0),
762 MapPoint::from_lat_lng_deg(10.0, 10.0),
763 );
764 let bbox2 = MapBbox::new(
765 MapPoint::from_lat_lng_deg(-10.0, 0.0),
766 MapPoint::from_lat_lng_deg(0.0, 10.0),
767 );
768 let bbox3 = MapBbox::new(
769 MapPoint::from_lat_lng_deg(-10.0, -10.0),
770 MapPoint::from_lat_lng_deg(0.0, 0.0),
771 );
772 let bbox4 = MapBbox::new(
773 MapPoint::from_lat_lng_deg(0.0, -10.0),
774 MapPoint::from_lat_lng_deg(10.0, 0.0),
775 );
776
777 let lat1 = 5.0;
778 let lng1 = 5.0;
779 let lat2 = -5.0;
780 let lng2 = 5.0;
781 let lat3 = -5.0;
782 let lng3 = -5.0;
783 let lat4 = 5.0;
784 let lng4 = -5.0;
785
786 assert!(bbox1.contains_point(MapPoint::from_lat_lng_deg(lat1, lng1)));
787 assert!(!bbox2.contains_point(MapPoint::from_lat_lng_deg(lat1, lng1)));
788 assert!(!bbox3.contains_point(MapPoint::from_lat_lng_deg(lat1, lng1)));
789 assert!(!bbox4.contains_point(MapPoint::from_lat_lng_deg(lat1, lng1)));
790
791 assert!(!bbox1.contains_point(MapPoint::from_lat_lng_deg(lat2, lng2)));
792 assert!(bbox2.contains_point(MapPoint::from_lat_lng_deg(lat2, lng2)));
793 assert!(!bbox3.contains_point(MapPoint::from_lat_lng_deg(lat2, lng2)));
794 assert!(!bbox4.contains_point(MapPoint::from_lat_lng_deg(lat2, lng2)));
795
796 assert!(!bbox1.contains_point(MapPoint::from_lat_lng_deg(lat3, lng3)));
797 assert!(!bbox2.contains_point(MapPoint::from_lat_lng_deg(lat3, lng3)));
798 assert!(bbox3.contains_point(MapPoint::from_lat_lng_deg(lat3, lng3)));
799 assert!(!bbox4.contains_point(MapPoint::from_lat_lng_deg(lat3, lng3)));
800
801 assert!(!bbox1.contains_point(MapPoint::from_lat_lng_deg(lat4, lng4)));
802 assert!(!bbox2.contains_point(MapPoint::from_lat_lng_deg(lat4, lng4)));
803 assert!(!bbox3.contains_point(MapPoint::from_lat_lng_deg(lat4, lng4)));
804 assert!(bbox4.contains_point(MapPoint::from_lat_lng_deg(lat4, lng4)));
805 }
806
807 }