1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![warn(
4 clippy::all,
5 missing_copy_implementations,
6 missing_debug_implementations,
7 rust_2018_idioms,
8 rustdoc::broken_intra_doc_links,
9 trivial_casts,
10 trivial_numeric_casts,
11 renamed_and_removed_lints,
12 unsafe_code,
13 unstable_features,
14 unused_import_braces,
15 unused_qualifications
16)]
17#![deny(missing_docs)]
18
19use std::fmt;
20use std::num::ParseFloatError;
21use std::str::FromStr;
22
23#[cfg(feature = "serde")]
24use serde::{
25 de::{Deserialize, Visitor},
26 ser::Serialize,
27};
28use thiserror::Error;
29#[cfg(feature = "url")]
30use url::Url;
31
32const URI_SCHEME_NAME: &str = "geo";
34
35#[derive(Debug, Error, Eq, PartialEq)]
37pub enum Error {
38 #[error("Invalid coordinate in geo URI: {0}")]
40 InvalidCoord(ParseFloatError),
41
42 #[error("Invalid coordinate reference system")]
44 InvalidCoordRefSystem,
45
46 #[error("Invalid distance in geo URI: {0}")]
48 InvalidUncertainty(ParseFloatError),
49
50 #[error("Missing coordinates in geo URI")]
52 MissingCoords,
53
54 #[error("Missing latitude coordinate in geo URI")]
56 MissingLatitude,
57
58 #[error("Missing longitude coordinate in geo URI")]
60 MissingLongitude,
61
62 #[error("Missing geo URI scheme")]
64 MissingScheme,
65
66 #[error("Latitude coordinate is out of range")]
70 OutOfRangeLatitude,
71
72 #[error("Longitude coordinate is out of range")]
76 OutOfRangeLongitude,
77
78 #[error("Uncertainty distance not positive")]
80 OutOfRangeUncertainty,
81}
82
83#[non_exhaustive]
93#[derive(Copy, Clone, Debug, PartialEq, Eq)]
94pub enum CoordRefSystem {
95 Wgs84,
97}
98
99impl CoordRefSystem {
100 pub fn validate(&self, latitude: f64, longitude: f64) -> Result<(), Error> {
123 if !(-90.0..=90.0).contains(&latitude) {
125 return Err(Error::OutOfRangeLatitude);
126 }
127
128 if !(-180.0..=180.0).contains(&longitude) {
130 return Err(Error::OutOfRangeLongitude);
131 }
132
133 Ok(())
134 }
135}
136
137impl Default for CoordRefSystem {
138 fn default() -> Self {
139 Self::Wgs84
140 }
141}
142
143#[derive(Copy, Clone, Debug, Default)]
236pub struct GeoUri {
237 crs: CoordRefSystem,
239
240 latitude: f64,
245
246 longitude: f64,
251
252 altitude: Option<f64>,
254
255 uncertainty: Option<f64>,
259}
260
261impl GeoUri {
262 pub fn builder() -> GeoUriBuilder {
264 GeoUriBuilder::default()
265 }
266
267 pub fn parse(uri: &str) -> Result<Self, Error> {
276 let uri = uri.to_ascii_lowercase();
277 let uri_path = uri.strip_prefix("geo:").ok_or(Error::MissingScheme)?;
278 let mut parts = uri_path.split(';');
279
280 let coords_part = parts.next().expect("Split always yields at least one part");
282 let mut coords = if coords_part.is_empty() {
284 return Err(Error::MissingCoords);
285 } else {
286 coords_part.splitn(3, ',')
287 };
288 let latitude = coords
289 .next()
290 .ok_or(Error::MissingLatitude) .and_then(|lat_s| lat_s.parse().map_err(Error::InvalidCoord))?;
292
293 let longitude = coords
294 .next()
295 .ok_or(Error::MissingLongitude)
296 .and_then(|lon_s| lon_s.parse().map_err(Error::InvalidCoord))?;
297
298 let altitude = coords
299 .next()
300 .map(|alt_s| alt_s.parse().map_err(Error::InvalidCoord))
301 .transpose()?;
302
303 let mut param_parts = parts.flat_map(|part| part.split_once('='));
311 let (crs, uncertainty) = match param_parts.next() {
312 Some(("crs", value)) => {
313 if value != "wgs84" {
314 return Err(Error::InvalidCoordRefSystem);
315 }
316
317 match param_parts.next() {
318 Some(("u", value)) => (
319 CoordRefSystem::Wgs84,
320 Some(value.parse().map_err(Error::InvalidUncertainty)?),
321 ),
322 Some(_) | None => (CoordRefSystem::Wgs84, None),
323 }
324 }
325 Some(("u", value)) => (
326 CoordRefSystem::default(),
327 Some(value.parse().map_err(Error::InvalidUncertainty)?),
328 ),
329 Some(_) | None => (CoordRefSystem::default(), None),
330 };
331
332 let geo_uri = GeoUri {
334 crs,
335 latitude,
336 longitude,
337 altitude,
338 uncertainty,
339 };
340 geo_uri.validate()?;
341
342 Ok(geo_uri)
343 }
344
345 pub fn latitude(&self) -> f64 {
347 self.latitude
348 }
349
350 pub fn set_latitude(&mut self, latitude: f64) -> Result<(), Error> {
357 self.crs.validate(latitude, self.longitude)?;
358 self.latitude = latitude;
359
360 Ok(())
361 }
362
363 pub fn longitude(&self) -> f64 {
365 self.longitude
366 }
367
368 pub fn set_longitude(&mut self, longitude: f64) -> Result<(), Error> {
375 self.crs.validate(self.latitude, longitude)?;
376 self.longitude = longitude;
377
378 Ok(())
379 }
380
381 pub fn altitude(&self) -> Option<f64> {
383 self.altitude
384 }
385
386 pub fn set_altitude(&mut self, altitude: Option<f64>) {
388 self.altitude = altitude;
389 }
390
391 pub fn uncertainty(&self) -> Option<f64> {
393 self.uncertainty
394 }
395
396 pub fn set_uncertainty(&mut self, uncertainty: Option<f64>) -> Result<(), Error> {
402 if let Some(unc) = uncertainty {
403 if unc < 0.0 {
404 return Err(Error::OutOfRangeUncertainty);
405 }
406 }
407 self.uncertainty = uncertainty;
408
409 Ok(())
410 }
411
412 fn validate(&self) -> Result<(), Error> {
422 self.crs.validate(self.latitude, self.longitude)?;
424
425 if let Some(unc) = self.uncertainty {
427 if unc < 0.0 {
428 return Err(Error::OutOfRangeUncertainty);
429 }
430 }
431
432 Ok(())
433 }
434}
435
436#[derive(Clone, Copy, Debug, Default)]
466pub struct GeoUriBuilder {
467 crs: Option<CoordRefSystem>,
469
470 latitude: Option<f64>,
475
476 longitude: Option<f64>,
481
482 altitude: Option<f64>,
484
485 uncertainty: Option<f64>,
489}
490
491impl GeoUriBuilder {
492 pub fn crs(&mut self, value: CoordRefSystem) -> &mut Self {
494 self.crs = Some(value);
495
496 self
497 }
498
499 pub fn latitude(&mut self, value: f64) -> &mut Self {
504 self.latitude = Some(value);
505
506 self
507 }
508
509 pub fn longitude(&mut self, value: f64) -> &mut Self {
514 self.longitude = Some(value);
515
516 self
517 }
518
519 pub fn altitude(&mut self, value: f64) -> &mut Self {
521 self.altitude = Some(value);
522
523 self
524 }
525
526 #[allow(unused_mut)]
531 pub fn uncertainty(&mut self, value: f64) -> &mut Self {
532 self.uncertainty = Some(value);
533
534 self
535 }
536
537 pub fn build(&self) -> Result<GeoUri, GeoUriBuilderError> {
544 self.validate()?;
545
546 Ok(GeoUri {
547 crs: self.crs.unwrap_or_default(),
548 latitude: self
549 .latitude
550 .ok_or(GeoUriBuilderError::UninitializedField("latitude"))?,
551 longitude: self
552 .longitude
553 .ok_or(GeoUriBuilderError::UninitializedField("longitude"))?,
554 altitude: self.altitude,
555 uncertainty: self.uncertainty,
556 })
557 }
558
559 fn validate(&self) -> Result<(), GeoUriBuilderError> {
568 self.crs.unwrap_or_default().validate(
569 self.latitude.unwrap_or_default(),
570 self.longitude.unwrap_or_default(),
571 )?;
572
573 if let Some(unc) = self.uncertainty {
574 if unc < 0.0 {
575 Err(Error::OutOfRangeUncertainty)?;
576 }
577 }
578
579 Ok(())
580 }
581}
582
583#[non_exhaustive]
585#[derive(Debug, Error)]
586pub enum GeoUriBuilderError {
587 #[error("uninitialized field `{0}`")]
589 UninitializedField(&'static str),
590
591 #[error("validation error: {0}")]
593 ValidationError(#[from] Error),
594}
595
596#[cfg(feature = "serde")]
597struct GeoUriVisitor;
598
599#[cfg(feature = "serde")]
600impl<'de> Visitor<'de> for GeoUriVisitor {
601 type Value = GeoUri;
602
603 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
604 write!(formatter, "a string starting with {URI_SCHEME_NAME}:")
605 }
606
607 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
608 where
609 E: serde::de::Error,
610 {
611 GeoUri::parse(v).map_err(E::custom)
612 }
613}
614
615#[cfg(feature = "serde")]
616#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
617impl<'de> Deserialize<'de> for GeoUri {
618 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
619 where
620 D: serde::Deserializer<'de>,
621 {
622 deserializer.deserialize_str(GeoUriVisitor)
623 }
624}
625
626impl fmt::Display for GeoUri {
627 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
628 let Self {
629 latitude,
630 longitude,
631 ..
632 } = self;
633 write!(f, "{URI_SCHEME_NAME}:{latitude},{longitude}")?;
634
635 if let Some(altitude) = self.altitude {
636 write!(f, ",{altitude}")?;
637 }
638
639 if let Some(uncertainty) = self.uncertainty {
641 write!(f, ";u={uncertainty}")?;
642 }
643
644 Ok(())
645 }
646}
647
648#[cfg(feature = "url")]
649#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
650impl From<&GeoUri> for Url {
651 fn from(geo_uri: &GeoUri) -> Self {
652 Url::parse(&geo_uri.to_string()).expect("valid URL")
653 }
654}
655
656#[cfg(feature = "url")]
657#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
658impl From<GeoUri> for Url {
659 fn from(geo_uri: GeoUri) -> Self {
660 Url::from(&geo_uri)
661 }
662}
663
664impl FromStr for GeoUri {
665 type Err = Error;
666
667 fn from_str(s: &str) -> Result<Self, Self::Err> {
668 Self::parse(s)
669 }
670}
671
672#[cfg(feature = "serde")]
673#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
674impl Serialize for GeoUri {
675 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
676 where
677 S: serde::Serializer,
678 {
679 serializer.serialize_str(&self.to_string())
680 }
681}
682
683impl TryFrom<&str> for GeoUri {
684 type Error = Error;
685
686 fn try_from(value: &str) -> Result<Self, Self::Error> {
687 Self::parse(value)
688 }
689}
690
691impl TryFrom<(f64, f64)> for GeoUri {
692 type Error = Error;
693
694 fn try_from((latitude, longitude): (f64, f64)) -> Result<Self, Self::Error> {
695 let geo_uri = GeoUri {
696 latitude,
697 longitude,
698 ..Default::default()
699 };
700 geo_uri.validate()?;
701
702 Ok(geo_uri)
703 }
704}
705
706impl TryFrom<(f64, f64, f64)> for GeoUri {
707 type Error = Error;
708
709 fn try_from((latitude, longitude, altitude): (f64, f64, f64)) -> Result<Self, Self::Error> {
710 let geo_uri = GeoUri {
711 latitude,
712 longitude,
713 altitude: Some(altitude),
714 ..Default::default()
715 };
716 geo_uri.validate()?;
717
718 Ok(geo_uri)
719 }
720}
721
722#[cfg(feature = "url")]
723#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
724impl TryFrom<&Url> for GeoUri {
725 type Error = Error;
726
727 fn try_from(url: &Url) -> Result<Self, Self::Error> {
728 GeoUri::parse(url.as_str())
729 }
730}
731
732#[cfg(feature = "url")]
733#[cfg_attr(docsrs, doc(cfg(feature = "url")))]
734impl TryFrom<Url> for GeoUri {
735 type Error = Error;
736
737 fn try_from(url: Url) -> Result<Self, Self::Error> {
738 GeoUri::try_from(&url)
739 }
740}
741
742impl PartialEq for GeoUri {
743 fn eq(&self, other: &Self) -> bool {
744 let ignore_longitude = self.crs == CoordRefSystem::Wgs84 && self.latitude.abs() == 90.0;
746
747 self.crs == other.crs
748 && self.latitude == other.latitude
749 && (ignore_longitude || self.longitude == other.longitude)
750 && self.altitude == other.altitude
751 && self.uncertainty == other.uncertainty
752 }
753}
754
755#[cfg(test)]
756mod tests {
757 #[cfg(feature = "serde")]
758 use serde_test::{assert_de_tokens_error, assert_tokens, Token};
759
760 use super::*;
761
762 #[test]
763 fn coord_ref_system_default() {
764 assert_eq!(CoordRefSystem::default(), CoordRefSystem::Wgs84);
765 }
766
767 #[test]
768 fn coord_ref_system_validate() {
769 let crs = CoordRefSystem::Wgs84;
770 assert_eq!(crs.validate(52.107, 5.134), Ok(()));
771 assert_eq!(crs.validate(100.0, 5.134), Err(Error::OutOfRangeLatitude));
772 assert_eq!(
773 crs.validate(51.107, -200.0),
774 Err(Error::OutOfRangeLongitude)
775 );
776 }
777
778 #[test]
779 fn geo_uri_builder() -> Result<(), GeoUriBuilderError> {
780 let mut builder = GeoUri::builder();
781 assert!(matches!(
782 builder.build(),
783 Err(GeoUriBuilderError::UninitializedField("latitude"))
784 ));
785
786 builder.latitude(52.107);
787 assert!(matches!(
788 builder.build(),
789 Err(GeoUriBuilderError::UninitializedField("longitude"))
790 ));
791
792 builder.longitude(5.134);
793 let geo_uri = builder.build()?;
794 assert_eq!(geo_uri.latitude, 52.107);
795 assert_eq!(geo_uri.longitude, 5.134);
796 assert_eq!(geo_uri.altitude, None);
797 assert_eq!(geo_uri.uncertainty, None);
798
799 builder.latitude(100.0);
800 assert!(matches!(
801 builder.build(),
802 Err(GeoUriBuilderError::ValidationError(_))
803 ));
804
805 builder.latitude(52.107).longitude(-200.0);
806 assert!(matches!(
807 builder.build(),
808 Err(GeoUriBuilderError::ValidationError(_))
809 ));
810
811 builder.longitude(5.134).uncertainty(-200.0);
812 assert!(matches!(
813 builder.build(),
814 Err(GeoUriBuilderError::ValidationError(_))
815 ));
816
817 Ok(())
818 }
819
820 #[test]
821 fn geo_uri_parse() -> Result<(), Error> {
822 let geo_uri = GeoUri::parse("geo:52.107,5.134")?;
823 assert_eq!(geo_uri.latitude, 52.107);
824 assert_eq!(geo_uri.longitude, 5.134);
825 assert_eq!(geo_uri.altitude, None);
826 assert_eq!(geo_uri.uncertainty, None);
827
828 let geo_uri = GeoUri::parse("52.107,5.134");
829 assert!(matches!(geo_uri, Err(Error::MissingScheme)));
830
831 let geo_uri = GeoUri::parse("geo:100.0,5.134");
832 assert!(matches!(geo_uri, Err(Error::OutOfRangeLatitude)));
833
834 let geo_uri = GeoUri::parse("geo:62.107,-200.0");
835 assert!(matches!(geo_uri, Err(Error::OutOfRangeLongitude)));
836
837 let geo_uri = GeoUri::parse("geo:geo:52.107,5.134");
838 assert!(matches!(geo_uri, Err(Error::InvalidCoord(_))));
839
840 let geo_uri = GeoUri::parse("geo:");
841 assert!(matches!(geo_uri, Err(Error::MissingCoords)));
842
843 let geo_uri = GeoUri::parse("geo:;u=5000");
844 assert!(matches!(geo_uri, Err(Error::MissingCoords)));
845
846 let geo_uri = GeoUri::parse("geo:52.107;u=1000");
847 assert!(matches!(geo_uri, Err(Error::MissingLongitude)));
848
849 let geo_uri = GeoUri::parse("geo:52.107,;u=1000");
850 assert!(matches!(geo_uri, Err(Error::InvalidCoord(_))));
851
852 let geo_uri = GeoUri::parse("geo:52.107,,6.50;u=1000");
853 assert!(matches!(geo_uri, Err(Error::InvalidCoord(_))));
854
855 let geo_uri = GeoUri::parse("geo:52.107,5.134,;u=1000");
856 assert!(matches!(geo_uri, Err(Error::InvalidCoord(_))));
857
858 let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6")?;
859 assert_eq!(geo_uri.latitude, 52.107);
860 assert_eq!(geo_uri.longitude, 5.134);
861 assert_eq!(geo_uri.altitude.unwrap(), 3.6);
862 assert_eq!(geo_uri.uncertainty, None);
863
864 let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=");
865 assert!(matches!(geo_uri, Err(Error::InvalidUncertainty(_))));
866
867 let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=foo");
868 assert!(matches!(geo_uri, Err(Error::InvalidUncertainty(_))));
869
870 let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=wgs84;u=foo");
871 assert!(matches!(geo_uri, Err(Error::InvalidUncertainty(_))));
872
873 let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=-10.0");
874 assert!(matches!(geo_uri, Err(Error::OutOfRangeUncertainty)));
875
876 let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;u=25000")?;
877 assert_eq!(geo_uri.latitude, 52.107);
878 assert_eq!(geo_uri.longitude, 5.134);
879 assert_eq!(geo_uri.altitude.unwrap(), 3.6);
880 assert_eq!(geo_uri.uncertainty, Some(25_000.0));
881
882 let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;crs=wgs84;u=25000")?;
883 assert_eq!(geo_uri.latitude, 52.107);
884 assert_eq!(geo_uri.longitude, 5.134);
885 assert_eq!(geo_uri.altitude.unwrap(), 3.6);
886 assert_eq!(geo_uri.uncertainty, Some(25_000.0));
887
888 let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;CRS=wgs84;U=25000")?;
889 assert_eq!(geo_uri.uncertainty, Some(25_000.0));
890
891 let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;crs=wgs84;u=25000;foo=bar")?;
892 assert_eq!(geo_uri.latitude, 52.107);
893 assert_eq!(geo_uri.longitude, 5.134);
894 assert_eq!(geo_uri.altitude.unwrap(), 3.6);
895 assert_eq!(geo_uri.uncertainty, Some(25_000.0));
896
897 let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=foo");
898 assert!(matches!(geo_uri, Err(Error::InvalidCoordRefSystem)));
899
900 let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=wgs84")?;
901 assert!(matches!(geo_uri.crs, CoordRefSystem::Wgs84));
902
903 let geo_uri = GeoUri::parse("geo:13.4125,103.8667")?;
905 assert_eq!(geo_uri.latitude, 13.4125);
906 assert_eq!(geo_uri.longitude, 103.8667);
907 assert_eq!(geo_uri.altitude, None);
908 assert_eq!(geo_uri.uncertainty, None);
909
910 let geo_uri = GeoUri::parse("geo:48.2010,16.3695,183")?;
911 assert_eq!(geo_uri.latitude, 48.2010);
912 assert_eq!(geo_uri.longitude, 16.3695);
913 assert_eq!(geo_uri.altitude.unwrap(), 183.0);
914 assert_eq!(geo_uri.uncertainty, None);
915
916 let geo_uri = GeoUri::parse("geo:48.198634,16.371648;crs=wgs84;u=40")?;
917 assert_eq!(geo_uri.crs, CoordRefSystem::Wgs84);
918 assert_eq!(geo_uri.latitude, 48.198634);
919 assert_eq!(geo_uri.longitude, 16.371648);
920 assert_eq!(geo_uri.altitude, None);
921 assert_eq!(geo_uri.uncertainty, Some(40.0));
922
923 let geo_uri = GeoUri::parse("geo:94,0");
924 assert_eq!(geo_uri, Err(Error::OutOfRangeLatitude));
925
926 Ok(())
927 }
928
929 #[test]
930 fn geo_uri_validate() {
931 let mut geo_uri = GeoUri {
932 crs: CoordRefSystem::Wgs84,
933 latitude: 52.107,
934 longitude: 5.134,
935 altitude: None,
936 uncertainty: None,
937 };
938 assert_eq!(geo_uri.validate(), Ok(()));
939
940 geo_uri.latitude = 100.0;
941 assert_eq!(geo_uri.validate(), Err(Error::OutOfRangeLatitude));
942
943 geo_uri.latitude = 52.107;
944 geo_uri.longitude = -200.0;
945 assert_eq!(geo_uri.validate(), Err(Error::OutOfRangeLongitude));
946
947 geo_uri.longitude = 5.134;
948 geo_uri.uncertainty = Some(-2000.0);
949 assert_eq!(geo_uri.validate(), Err(Error::OutOfRangeUncertainty));
950 }
951
952 #[test]
953 fn geo_uri_get_set() {
954 let mut geo_uri = GeoUri {
955 crs: CoordRefSystem::Wgs84,
956 latitude: 52.107,
957 longitude: 5.134,
958 altitude: None,
959 uncertainty: None,
960 };
961 assert_eq!(geo_uri.latitude(), 52.107);
962 assert_eq!(geo_uri.longitude(), 5.134);
963 assert_eq!(geo_uri.altitude(), None);
964 assert_eq!(geo_uri.uncertainty(), None);
965
966 assert_eq!(geo_uri.set_latitude(53.107), Ok(()));
967 assert_eq!(geo_uri.set_latitude(100.0), Err(Error::OutOfRangeLatitude));
968 assert_eq!(geo_uri.latitude(), 53.107);
969
970 assert_eq!(geo_uri.set_longitude(6.134), Ok(()));
971 assert_eq!(
972 geo_uri.set_longitude(-200.0),
973 Err(Error::OutOfRangeLongitude)
974 );
975 assert_eq!(geo_uri.longitude(), 6.134);
976
977 geo_uri.set_altitude(Some(3.6));
978 assert_eq!(geo_uri.altitude(), Some(3.6));
979
980 assert_eq!(geo_uri.set_uncertainty(Some(25_000.0)), Ok(()));
981 assert_eq!(
982 geo_uri.set_uncertainty(Some(-100.0)),
983 Err(Error::OutOfRangeUncertainty)
984 );
985 assert_eq!(geo_uri.uncertainty(), Some(25_000.0));
986 }
987
988 #[test]
989 fn geo_uri_display() {
990 let mut geo_uri = GeoUri {
991 crs: CoordRefSystem::Wgs84,
992 latitude: 52.107,
993 longitude: 5.134,
994 altitude: None,
995 uncertainty: None,
996 };
997 assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134");
998
999 geo_uri.altitude = Some(3.6);
1000 assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134,3.6");
1001
1002 geo_uri.uncertainty = Some(25_000.0);
1003 assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134,3.6;u=25000");
1004 }
1005
1006 #[cfg(feature = "url")]
1007 #[test]
1008 fn geo_uri_from() {
1009 let geo_uri = GeoUri {
1010 crs: CoordRefSystem::Wgs84,
1011 latitude: 52.107,
1012 longitude: 5.134,
1013 altitude: Some(3.6),
1014 uncertainty: Some(1000.0),
1015 };
1016 let url = Url::from(&geo_uri);
1017 assert_eq!(url.scheme(), "geo");
1018 assert_eq!(url.path(), "52.107,5.134,3.6;u=1000");
1019
1020 let url = Url::from(geo_uri);
1021 assert_eq!(url.scheme(), "geo");
1022 assert_eq!(url.path(), "52.107,5.134,3.6;u=1000");
1023 }
1024
1025 #[test]
1026 fn geo_uri_from_str() -> Result<(), Error> {
1027 let geo_uri = GeoUri::from_str("geo:52.107,5.134")?;
1028 assert_eq!(geo_uri.latitude, 52.107);
1029 assert_eq!(geo_uri.longitude, 5.134);
1030 assert_eq!(geo_uri.altitude, None);
1031 assert_eq!(geo_uri.uncertainty, None);
1032
1033 Ok(())
1034 }
1035
1036 #[cfg(feature = "serde")]
1037 #[test]
1038 fn geo_uri_serde() {
1039 let geo_uri = GeoUri {
1040 crs: CoordRefSystem::Wgs84,
1041 latitude: 52.107,
1042 longitude: 5.134,
1043 altitude: Some(3.6),
1044 uncertainty: Some(1000.0),
1045 };
1046 assert_tokens(&geo_uri, &[Token::String("geo:52.107,5.134,3.6;u=1000")]);
1047
1048 assert_de_tokens_error::<GeoUri>(
1049 &[Token::I32(0)],
1050 "invalid type: integer `0`, expected a string starting with geo:",
1051 );
1052 assert_de_tokens_error::<GeoUri>(
1053 &[Token::String("geo:100.0,5.134,3.6")],
1054 &format!("{}", Error::OutOfRangeLatitude),
1055 );
1056 }
1057
1058 #[test]
1059 fn geo_uri_try_from() -> Result<(), Error> {
1060 let geo_uri = GeoUri::try_from("geo:52.107,5.134")?;
1062 assert_eq!(geo_uri.latitude, 52.107);
1063 assert_eq!(geo_uri.longitude, 5.134);
1064 assert_eq!(geo_uri.altitude, None);
1065 assert_eq!(geo_uri.uncertainty, None);
1066
1067 let geo_uri = GeoUri::try_from((51.107, 5.134))?;
1069 assert_eq!(geo_uri.latitude, 51.107);
1070 assert_eq!(geo_uri.longitude, 5.134);
1071 assert_eq!(geo_uri.altitude, None);
1072 assert_eq!(geo_uri.uncertainty, None);
1073
1074 assert_eq!(
1075 GeoUri::try_from((100.0, 5.134)),
1076 Err(Error::OutOfRangeLatitude)
1077 );
1078 assert_eq!(
1079 GeoUri::try_from((51.107, -200.0)),
1080 Err(Error::OutOfRangeLongitude)
1081 );
1082
1083 let geo_uri = GeoUri::try_from((51.107, 5.134, 3.6))?;
1085 assert_eq!(geo_uri.latitude, 51.107);
1086 assert_eq!(geo_uri.longitude, 5.134);
1087 assert_eq!(geo_uri.altitude.unwrap(), 3.6);
1088 assert_eq!(geo_uri.uncertainty, None);
1089
1090 assert_eq!(
1091 GeoUri::try_from((100.0, 5.134, 3.6)),
1092 Err(Error::OutOfRangeLatitude)
1093 );
1094 assert_eq!(
1095 GeoUri::try_from((51.107, -200.0, 3.6)),
1096 Err(Error::OutOfRangeLongitude)
1097 );
1098
1099 Ok(())
1100 }
1101
1102 #[cfg(feature = "url")]
1103 #[test]
1104 fn geo_uri_try_from_url() -> Result<(), Error> {
1105 let url = Url::parse("geo:51.107,5.134,3.6;crs=wgs84;u=1000;foo=bar").expect("valid URL");
1107 let geo_uri = GeoUri::try_from(&url)?;
1108 assert_eq!(geo_uri.latitude, 51.107);
1109 assert_eq!(geo_uri.longitude, 5.134);
1110 assert_eq!(geo_uri.altitude.unwrap(), 3.6);
1111 assert_eq!(geo_uri.uncertainty, Some(1000.0));
1112
1113 let geo_uri = GeoUri::try_from(url)?;
1114 assert_eq!(geo_uri.latitude, 51.107);
1115 assert_eq!(geo_uri.longitude, 5.134);
1116 assert_eq!(geo_uri.altitude.unwrap(), 3.6);
1117 assert_eq!(geo_uri.uncertainty, Some(1000.0));
1118
1119 Ok(())
1120 }
1121
1122 #[test]
1123 fn geo_uri_partial_eq() -> Result<(), GeoUriBuilderError> {
1124 let geo_uri = GeoUri::builder()
1125 .latitude(52.107)
1126 .longitude(5.134)
1127 .build()?;
1128 let geo_uri2 = GeoUri::builder()
1129 .latitude(52.107)
1130 .longitude(5.134)
1131 .build()?;
1132 assert_eq!(geo_uri, geo_uri2);
1133 assert_eq!(geo_uri, geo_uri.clone());
1134
1135 let geo_uri = GeoUri::builder().latitude(90.0).longitude(5.134).build()?;
1136 let geo_uri2 = GeoUri::builder().latitude(90.0).longitude(5.134).build()?;
1137 assert_eq!(geo_uri, geo_uri2);
1138
1139 let geo_uri = GeoUri::builder().latitude(-90.0).longitude(5.134).build()?;
1140 let geo_uri2 = GeoUri::builder().latitude(-90.0).longitude(5.134).build()?;
1141 assert_eq!(geo_uri, geo_uri2);
1142
1143 let geo_uri = GeoUri::parse("geo:90,-22.43;crs=WGS84").expect("parsable geo URI");
1145 let geo_uri2 = GeoUri::parse("geo:90,46").expect("parsable geo URI");
1146 assert_eq!(geo_uri, geo_uri2);
1147
1148 let geo_uri = GeoUri::parse("geo:22.300,-118.44").expect("parsable geo URI");
1149 let geo_uri2 = GeoUri::parse("geo:22.3,-118.4400").expect("parsable geo URI");
1150 assert_eq!(geo_uri, geo_uri2);
1151
1152 let geo_uri = GeoUri::parse("geo:66,30;u=6.500;FOo=this%2dthat").expect("parsable geo URI");
1153 let geo_uri2 = GeoUri::parse("geo:66.0,30;u=6.5;foo=this-that").expect("parsable geo URI");
1154 assert_eq!(geo_uri, geo_uri2);
1155
1156 let _geo_uri = GeoUri::parse("geo:70,20;foo=1.00;bar=white").expect("parsable geo URI");
1157 let _geo_uri2 = GeoUri::parse("geo:70,20;foo=1;bar=white").expect("parsable geo URI");
1158 let geo_uri = GeoUri::parse("geo:47,11;foo=blue;bar=white").expect("parsable geo URI");
1162 let geo_uri2 = GeoUri::parse("geo:47,11;bar=white;foo=blue").expect("parsable geo URI");
1163 assert_eq!(geo_uri, geo_uri2);
1164
1165 let _geo_uri = GeoUri::parse("geo:22,0;bar=Blue").expect("parsable geo URI");
1166 let _geo_uri2 = GeoUri::parse("geo:22,0;BAR=blue").expect("parsable geo URI");
1167 Ok(())
1171 }
1172}