Change uncertainty distance to be of type f64

The uncertainty distance is a positive floating number.

* Validate this while parsing/building/using the setter
* Add the error variant `Error::OutOfRangeUncertainty` and change
  `Error::InvalidDistance` to `Error::InvalidUncertainty`
* Update the tests
This commit is contained in:
Paul van Tilburg 2022-09-29 22:15:28 +02:00
parent 2efdddc209
commit 6a9c13d099
Signed by: paul
GPG Key ID: C6DE073EDA9EEC4D
2 changed files with 73 additions and 30 deletions

View File

@ -33,7 +33,7 @@ let geo_uri = GeoUri::try_from("geo:52.107,5.134,3.6;u=1000");
assert!(geo_uri.is_ok()); assert!(geo_uri.is_ok());
use std::str::FromStr; use std::str::FromStr;
let geo_uri2 = GeoUri::from_str("geo:52.107,5.134;u=2000"); let geo_uri2 = GeoUri::from_str("geo:52.107,5.134;u=2000.0");
assert!(geo_uri2.is_ok()); assert!(geo_uri2.is_ok());
``` ```
@ -55,11 +55,11 @@ Use either the [`ToString`](std::string::ToString) or
use geo_uri::GeoUri; use geo_uri::GeoUri;
let geo_uri = GeoUri::builder() let geo_uri = GeoUri::builder()
.latitude(52.107) .latitude(52.107)
.longitude(5.134) .longitude(5.134)
.uncertainty(1_000) .uncertainty(1_000.0)
.build() .build()
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
geo_uri.to_string(), geo_uri.to_string(),
String::from("geo:52.107,5.134;u=1000") String::from("geo:52.107,5.134;u=1000")

View File

@ -16,7 +16,7 @@
#![deny(missing_docs)] #![deny(missing_docs)]
use std::fmt; use std::fmt;
use std::num::{ParseFloatError, ParseIntError}; use std::num::ParseFloatError;
use std::str::FromStr; use std::str::FromStr;
use derive_builder::Builder; use derive_builder::Builder;
@ -36,9 +36,9 @@ pub enum Error {
#[error("Invalid coordinate reference system")] #[error("Invalid coordinate reference system")]
InvalidCoordRefSystem, InvalidCoordRefSystem,
/// The geo URI contains an unparsable/invalid (uncertainty) distance. /// The geo URI contains an unparsable/invalid uncertainty distance.
#[error("Invalid distance in geo URI: {0}")] #[error("Invalid distance in geo URI: {0}")]
InvalidDistance(ParseIntError), InvalidUncertainty(ParseFloatError),
/// The geo URI contains no coordinates. /// The geo URI contains no coordinates.
#[error("Missing coordinates in geo URI")] #[error("Missing coordinates in geo URI")]
@ -62,11 +62,15 @@ pub enum Error {
#[error("Latitude coordinate is out of range")] #[error("Latitude coordinate is out of range")]
OutOfRangeLatitude, OutOfRangeLatitude,
/// The longitude coordinate is out of range of `-180.0..=180.0` degrees /// The longitude coordinate is out of range of `-180.0..=180.0` degrees.
/// ///
/// This can only fail for the WGS-84 coordinate reference system. /// This can only fail for the WGS-84 coordinate reference system.
#[error("Longitude coordinate is out of range")] #[error("Longitude coordinate is out of range")]
OutOfRangeLongitude, OutOfRangeLongitude,
/// The uncertainty distance is not positive.
#[error("Uncertainty distance not positive")]
OutOfRangeUncertainty,
} }
/// The reference system of the provided coordinates. /// The reference system of the provided coordinates.
@ -139,7 +143,7 @@ impl Default for CoordRefSystem {
/// assert_eq!(geo_uri.latitude(), 52.107); /// assert_eq!(geo_uri.latitude(), 52.107);
/// assert_eq!(geo_uri.longitude(), 5.134); /// assert_eq!(geo_uri.longitude(), 5.134);
/// assert_eq!(geo_uri.altitude(), Some(3.6)); /// assert_eq!(geo_uri.altitude(), Some(3.6));
/// assert_eq!(geo_uri.uncertainty(), Some(1000)); /// assert_eq!(geo_uri.uncertainty(), Some(1000.0));
/// ``` /// ```
/// ///
/// or by using the [`TryFrom`] trait: /// or by using the [`TryFrom`] trait:
@ -147,11 +151,11 @@ impl Default for CoordRefSystem {
/// use geo_uri::GeoUri; /// use geo_uri::GeoUri;
/// use std::str::FromStr; /// use std::str::FromStr;
/// ///
/// let geo_uri = GeoUri::from_str("geo:52.107,5.134;u=2000").expect("valid geo URI"); /// let geo_uri = GeoUri::from_str("geo:52.107,5.134;u=2000.0").expect("valid geo URI");
/// assert_eq!(geo_uri.latitude(), 52.107); /// assert_eq!(geo_uri.latitude(), 52.107);
/// assert_eq!(geo_uri.longitude(), 5.134); /// assert_eq!(geo_uri.longitude(), 5.134);
/// assert_eq!(geo_uri.altitude(), None); /// assert_eq!(geo_uri.altitude(), None);
/// assert_eq!(geo_uri.uncertainty(), Some(2000)); /// assert_eq!(geo_uri.uncertainty(), Some(2000.0));
/// ``` /// ```
/// ///
/// It is also possible to call the parse function directly: /// It is also possible to call the parse function directly:
@ -176,7 +180,7 @@ impl Default for CoordRefSystem {
/// let geo_uri = GeoUri::builder() /// let geo_uri = GeoUri::builder()
/// .latitude(52.107) /// .latitude(52.107)
/// .longitude(5.134) /// .longitude(5.134)
/// .uncertainty(1_000) /// .uncertainty(1_000.0)
/// .build() /// .build()
/// .unwrap(); /// .unwrap();
/// assert_eq!( /// assert_eq!(
@ -217,7 +221,9 @@ pub struct GeoUri {
#[builder(default, setter(strip_option))] #[builder(default, setter(strip_option))]
/// The uncertainty around the location as a radius (distance) in meters. /// The uncertainty around the location as a radius (distance) in meters.
uncertainty: Option<u32>, ///
/// This distance needs to be positive.
uncertainty: Option<f64>,
} }
impl GeoUri { impl GeoUri {
@ -275,19 +281,26 @@ impl GeoUri {
match param_parts.next() { match param_parts.next() {
Some(("u", value)) => ( Some(("u", value)) => (
CoordRefSystem::Wgs84, CoordRefSystem::Wgs84,
Some(value.parse().map_err(Error::InvalidDistance)?), Some(value.parse().map_err(Error::InvalidUncertainty)?),
), ),
Some(_) | None => (CoordRefSystem::Wgs84, None), Some(_) | None => (CoordRefSystem::Wgs84, None),
} }
} }
Some(("u", value)) => ( Some(("u", value)) => (
CoordRefSystem::default(), CoordRefSystem::default(),
Some(value.parse().map_err(Error::InvalidDistance)?), Some(value.parse().map_err(Error::InvalidUncertainty)?),
), ),
Some(_) | None => (CoordRefSystem::default(), None), Some(_) | None => (CoordRefSystem::default(), None),
}; };
// Validate the parsed values.
crs.validate(latitude, longitude)?; crs.validate(latitude, longitude)?;
// FIXME: Move this into the validator? This code is duplicate now.
if let Some(unc) = uncertainty {
if unc < 0.0 {
return Err(Error::OutOfRangeUncertainty);
}
}
Ok(GeoUri { Ok(GeoUri {
crs, crs,
@ -339,13 +352,22 @@ impl GeoUri {
} }
/// Returns the uncertainty around the location. /// Returns the uncertainty around the location.
pub fn uncertainty(&self) -> Option<u32> { pub fn uncertainty(&self) -> Option<f64> {
self.uncertainty self.uncertainty
} }
/// Changes the uncertainty around the location. /// Changes the uncertainty around the location.
pub fn set_uncertainty(&mut self, uncertainty: Option<u32>) { ///
/// The uncertainty distance must be positive.
pub fn set_uncertainty(&mut self, uncertainty: Option<f64>) -> Result<(), Error> {
if let Some(unc) = uncertainty {
if unc < 0.0 {
return Err(Error::OutOfRangeUncertainty);
}
}
self.uncertainty = uncertainty; self.uncertainty = uncertainty;
Ok(())
} }
} }
@ -409,7 +431,15 @@ impl GeoUriBuilder {
self.latitude.unwrap_or_default(), self.latitude.unwrap_or_default(),
self.longitude.unwrap_or_default(), self.longitude.unwrap_or_default(),
) )
.map_err(|e| format!("{e}")) .map_err(|e| format!("{e}"))?;
if let Some(unc) = self.uncertainty.unwrap_or_default() {
if unc < 0.0 {
return Err(format!("{}", Error::OutOfRangeUncertainty));
}
}
Ok(())
} }
} }
@ -482,6 +512,12 @@ mod tests {
let geo_uri = GeoUri::parse("52.107,5.134"); let geo_uri = GeoUri::parse("52.107,5.134");
assert!(matches!(geo_uri, Err(Error::MissingScheme))); assert!(matches!(geo_uri, Err(Error::MissingScheme)));
let geo_uri = GeoUri::parse("geo:100.0,5.134");
assert!(matches!(geo_uri, Err(Error::OutOfRangeLatitude)));
let geo_uri = GeoUri::parse("geo:62.107,-200.0");
assert!(matches!(geo_uri, Err(Error::OutOfRangeLongitude)));
let geo_uri = GeoUri::parse("geo:geo:52.107,5.134"); let geo_uri = GeoUri::parse("geo:geo:52.107,5.134");
assert!(matches!(geo_uri, Err(Error::InvalidCoord(_)))); assert!(matches!(geo_uri, Err(Error::InvalidCoord(_))));
@ -510,34 +546,37 @@ mod tests {
assert_eq!(geo_uri.uncertainty, None); assert_eq!(geo_uri.uncertainty, None);
let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u="); let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=");
assert!(matches!(geo_uri, Err(Error::InvalidDistance(_)))); assert!(matches!(geo_uri, Err(Error::InvalidUncertainty(_))));
let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=foo"); let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=foo");
assert!(matches!(geo_uri, Err(Error::InvalidDistance(_)))); assert!(matches!(geo_uri, Err(Error::InvalidUncertainty(_))));
let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=wgs84;u=foo"); let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=wgs84;u=foo");
assert!(matches!(geo_uri, Err(Error::InvalidDistance(_)))); assert!(matches!(geo_uri, Err(Error::InvalidUncertainty(_))));
let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;u=-10.0");
assert!(matches!(geo_uri, Err(Error::OutOfRangeUncertainty)));
let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;u=25000")?; let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;u=25000")?;
assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001); assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001);
assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001); assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001);
assert_float_eq!(geo_uri.altitude.unwrap(), 3.6, abs <= 0.001); assert_float_eq!(geo_uri.altitude.unwrap(), 3.6, abs <= 0.001);
assert_eq!(geo_uri.uncertainty, Some(25_000)); assert_eq!(geo_uri.uncertainty, Some(25_000.0));
let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;crs=wgs84;u=25000")?; let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;crs=wgs84;u=25000")?;
assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001); assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001);
assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001); assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001);
assert_float_eq!(geo_uri.altitude.unwrap(), 3.6, abs <= 0.001); assert_float_eq!(geo_uri.altitude.unwrap(), 3.6, abs <= 0.001);
assert_eq!(geo_uri.uncertainty, Some(25_000)); assert_eq!(geo_uri.uncertainty, Some(25_000.0));
let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;CRS=wgs84;U=25000")?; let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;CRS=wgs84;U=25000")?;
assert_eq!(geo_uri.uncertainty, Some(25_000)); assert_eq!(geo_uri.uncertainty, Some(25_000.0));
let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;crs=wgs84;u=25000;foo=bar")?; let geo_uri = GeoUri::parse("geo:52.107,5.134,3.6;crs=wgs84;u=25000;foo=bar")?;
assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001); assert_float_eq!(geo_uri.latitude, 52.107, abs <= 0.001);
assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001); assert_float_eq!(geo_uri.longitude, 5.134, abs <= 0.001);
assert_float_eq!(geo_uri.altitude.unwrap(), 3.6, abs <= 0.001); assert_float_eq!(geo_uri.altitude.unwrap(), 3.6, abs <= 0.001);
assert_eq!(geo_uri.uncertainty, Some(25_000)); assert_eq!(geo_uri.uncertainty, Some(25_000.0));
let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=foo"); let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=foo");
assert!(matches!(geo_uri, Err(Error::InvalidCoordRefSystem))); assert!(matches!(geo_uri, Err(Error::InvalidCoordRefSystem)));
@ -578,8 +617,12 @@ mod tests {
geo_uri.set_altitude(Some(3.6)); geo_uri.set_altitude(Some(3.6));
assert_eq!(geo_uri.altitude(), Some(3.6)); assert_eq!(geo_uri.altitude(), Some(3.6));
geo_uri.set_uncertainty(Some(25_000)); assert_eq!(geo_uri.set_uncertainty(Some(25_000.0)), Ok(()));
assert_eq!(geo_uri.uncertainty(), Some(25_000)); assert_eq!(
geo_uri.set_uncertainty(Some(-100.0)),
Err(Error::OutOfRangeUncertainty)
);
assert_eq!(geo_uri.uncertainty(), Some(25_000.0));
} }
#[test] #[test]
@ -596,7 +639,7 @@ mod tests {
geo_uri.altitude = Some(3.6); geo_uri.altitude = Some(3.6);
assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134,3.6"); assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134,3.6");
geo_uri.uncertainty = Some(25_000); geo_uri.uncertainty = Some(25_000.0);
assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134,3.6;u=25000"); assert_eq!(&geo_uri.to_string(), "geo:52.107,5.134,3.6;u=25000");
} }