Validate latitude/longitude coordinates for WGS-84
Implement this for the parser as well as the builder. Also rename `CrsId` to `CoordRefSystem` and the `GeoUri.crs_id` field to `GeoUri.crs`.
This commit is contained in:
parent
a71b9e8a2b
commit
a8a655f388
111
src/lib.rs
111
src/lib.rs
|
@ -36,18 +36,51 @@ const URI_SCHEME_NAME: &str = "geo";
|
||||||
/// [RFC 5870](https://www.rfc-editor.org/rfc/rfc5870).
|
/// [RFC 5870](https://www.rfc-editor.org/rfc/rfc5870).
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum CrsId {
|
pub enum CoordRefSystem {
|
||||||
/// The WGS-84 coordinate reference system.
|
/// The WGS-84 coordinate reference system.
|
||||||
Wgs84,
|
Wgs84,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CrsId {
|
impl CoordRefSystem {
|
||||||
|
/// Validates geo location coordinates against the selected coordinate reference system.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use geo_uri::{CoordRefSystem, ParseError};
|
||||||
|
/// let crs = CoordRefSystem::Wgs84;
|
||||||
|
/// assert_eq!(crs.validate(52.107, 5.134), Ok(()));
|
||||||
|
/// assert_eq!(
|
||||||
|
/// crs.validate(100.0, 5.134), // Latitude not in range `-90.0..=90.0`!
|
||||||
|
/// Err(ParseError::OutOfRangeLatitudeCoord)
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(
|
||||||
|
/// crs.validate(51.107, -200.0), // Longitude not in range `-180.0..=180.0`!
|
||||||
|
/// Err(ParseError::OutOfRangeLongitudeCoord)
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn validate(&self, latitude: f64, longitude: f64) -> Result<(), ParseError> {
|
||||||
|
// This holds only for WGS-84, but it is the only one supported right now!
|
||||||
|
if !(-90.0..=90.0).contains(&latitude) {
|
||||||
|
return Err(ParseError::OutOfRangeLatitudeCoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This holds only for WGS-84, but it is the only one supported right now!
|
||||||
|
if !(-180.0..=180.0).contains(&longitude) {
|
||||||
|
return Err(ParseError::OutOfRangeLongitudeCoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CoordRefSystem {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Wgs84
|
Self::Wgs84
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Possible geo URI parse errors.
|
/// Possible geo URI errors.
|
||||||
#[derive(Debug, Error, Eq, PartialEq)]
|
#[derive(Debug, Error, Eq, PartialEq)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
/// The geo URI is missing a proper scheme, i.e. the prefix `geo:`.
|
/// The geo URI is missing a proper scheme, i.e. the prefix `geo:`.
|
||||||
|
@ -71,6 +104,16 @@ pub enum ParseError {
|
||||||
/// 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),
|
InvalidDistance(ParseIntError),
|
||||||
|
/// The latitude coordinate is out of range of `-90..=90` degrees.
|
||||||
|
///
|
||||||
|
/// This can only fail for the WGS-84 coordinate reference system.
|
||||||
|
#[error("Latitude coordinate is out of range")]
|
||||||
|
OutOfRangeLatitudeCoord,
|
||||||
|
/// The longitude coordinate is out of range of `-180..=180` degrees
|
||||||
|
///
|
||||||
|
/// This can only fail for the WGS-84 coordinate reference system.
|
||||||
|
#[error("Longitude coordinate is out of range")]
|
||||||
|
OutOfRangeLongitudeCoord,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A uniform resource identifier for geographic locations (geo URI).
|
/// A uniform resource identifier for geographic locations (geo URI).
|
||||||
|
@ -133,13 +176,20 @@ pub enum ParseError {
|
||||||
///
|
///
|
||||||
/// For the proposed IEEE standard, see [RFC 5870](https://www.rfc-editor.org/rfc/rfc5870).
|
/// For the proposed IEEE standard, see [RFC 5870](https://www.rfc-editor.org/rfc/rfc5870).
|
||||||
#[derive(Builder, Copy, Clone, Debug, Default)]
|
#[derive(Builder, Copy, Clone, Debug, Default)]
|
||||||
|
#[builder(build_fn(validate = "Self::validate"))]
|
||||||
pub struct GeoUri {
|
pub struct GeoUri {
|
||||||
/// The coordinate reference system used by the coordinates of this URI.
|
/// The coordinate reference system used by the coordinates of this URI.
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub crs_id: CrsId,
|
pub crs: CoordRefSystem,
|
||||||
/// The latitude coordinate of a location.
|
/// The latitude coordinate of a location.
|
||||||
|
///
|
||||||
|
/// For the WGS-84 coordinate reference system, this should be in the range of
|
||||||
|
/// `-90.0` up until including `90.0` degrees.
|
||||||
pub latitude: f64,
|
pub latitude: f64,
|
||||||
/// The longitude coordinate of a location.
|
/// The longitude coordinate of a location.
|
||||||
|
///
|
||||||
|
/// For the WGS-84 coordinate reference system, this should be in the range of
|
||||||
|
/// `-180.0` up until including `180.0` degrees.
|
||||||
pub longitude: f64,
|
pub longitude: f64,
|
||||||
/// The altitude coordinate of a location, if provided.
|
/// The altitude coordinate of a location, if provided.
|
||||||
#[builder(default, setter(strip_option))]
|
#[builder(default, setter(strip_option))]
|
||||||
|
@ -195,7 +245,7 @@ impl GeoUri {
|
||||||
// It can be followed by a "u" parameter or that can be the first one.
|
// It can be followed by a "u" parameter or that can be the first one.
|
||||||
// All other parameters are ignored.
|
// All other parameters are ignored.
|
||||||
let mut param_parts = parts.flat_map(|part| part.split_once('='));
|
let mut param_parts = parts.flat_map(|part| part.split_once('='));
|
||||||
let (crs_id, uncertainty) = match param_parts.next() {
|
let (crs, uncertainty) = match param_parts.next() {
|
||||||
Some(("crs", value)) => {
|
Some(("crs", value)) => {
|
||||||
if value.to_ascii_lowercase() != "wgs84" {
|
if value.to_ascii_lowercase() != "wgs84" {
|
||||||
return Err(ParseError::InvalidCoordRefSystem);
|
return Err(ParseError::InvalidCoordRefSystem);
|
||||||
|
@ -203,21 +253,23 @@ impl GeoUri {
|
||||||
|
|
||||||
match param_parts.next() {
|
match param_parts.next() {
|
||||||
Some(("u", value)) => (
|
Some(("u", value)) => (
|
||||||
CrsId::Wgs84,
|
CoordRefSystem::Wgs84,
|
||||||
Some(value.parse().map_err(ParseError::InvalidDistance)?),
|
Some(value.parse().map_err(ParseError::InvalidDistance)?),
|
||||||
),
|
),
|
||||||
Some(_) | None => (CrsId::Wgs84, None),
|
Some(_) | None => (CoordRefSystem::Wgs84, None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(("u", value)) => (
|
Some(("u", value)) => (
|
||||||
CrsId::default(),
|
CoordRefSystem::default(),
|
||||||
Some(value.parse().map_err(ParseError::InvalidDistance)?),
|
Some(value.parse().map_err(ParseError::InvalidDistance)?),
|
||||||
),
|
),
|
||||||
Some(_) | None => (CrsId::default(), None),
|
Some(_) | None => (CoordRefSystem::default(), None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
crs.validate(latitude, longitude)?;
|
||||||
|
|
||||||
Ok(GeoUri {
|
Ok(GeoUri {
|
||||||
crs_id,
|
crs,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
altitude,
|
altitude,
|
||||||
|
@ -267,9 +319,9 @@ impl TryFrom<&str> for GeoUri {
|
||||||
impl PartialEq for GeoUri {
|
impl PartialEq for GeoUri {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
// In the WGS-84 CRS the the longitude is ignored for the poles.
|
// In the WGS-84 CRS the the longitude is ignored for the poles.
|
||||||
let ignore_longitude = self.crs_id == CrsId::Wgs84 && self.latitude.abs() == 90.0;
|
let ignore_longitude = self.crs == CoordRefSystem::Wgs84 && self.latitude.abs() == 90.0;
|
||||||
|
|
||||||
self.crs_id == other.crs_id
|
self.crs == other.crs
|
||||||
&& self.latitude == other.latitude
|
&& self.latitude == other.latitude
|
||||||
&& (ignore_longitude || self.longitude == other.longitude)
|
&& (ignore_longitude || self.longitude == other.longitude)
|
||||||
&& self.altitude == other.altitude
|
&& self.altitude == other.altitude
|
||||||
|
@ -277,6 +329,19 @@ impl PartialEq for GeoUri {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GeoUriBuilder {
|
||||||
|
/// Validates the coordinates against the
|
||||||
|
fn validate(&self) -> Result<(), String> {
|
||||||
|
self.crs
|
||||||
|
.unwrap_or_default()
|
||||||
|
.validate(
|
||||||
|
self.latitude.unwrap_or_default(),
|
||||||
|
self.longitude.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("{e}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use float_eq::assert_float_eq;
|
use float_eq::assert_float_eq;
|
||||||
|
@ -284,8 +349,22 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn crs_id_default() {
|
fn coord_ref_system_default() {
|
||||||
assert_eq!(CrsId::default(), CrsId::Wgs84);
|
assert_eq!(CoordRefSystem::default(), CoordRefSystem::Wgs84);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn coord_ref_system_validate() {
|
||||||
|
let crs = CoordRefSystem::Wgs84;
|
||||||
|
assert_eq!(crs.validate(52.107, 5.134), Ok(()));
|
||||||
|
assert_eq!(
|
||||||
|
crs.validate(100.0, 5.134),
|
||||||
|
Err(ParseError::OutOfRangeLatitudeCoord)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
crs.validate(51.107, -200.0),
|
||||||
|
Err(ParseError::OutOfRangeLongitudeCoord)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -363,7 +442,7 @@ mod tests {
|
||||||
assert!(matches!(geo_uri, Err(ParseError::InvalidCoordRefSystem)));
|
assert!(matches!(geo_uri, Err(ParseError::InvalidCoordRefSystem)));
|
||||||
|
|
||||||
let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=wgs84")?;
|
let geo_uri = GeoUri::parse("geo:52.107,5.34,3.6;crs=wgs84")?;
|
||||||
assert!(matches!(geo_uri.crs_id, CrsId::Wgs84));
|
assert!(matches!(geo_uri.crs, CoordRefSystem::Wgs84));
|
||||||
|
|
||||||
// TODO: Add exmaples from RFC 5870!
|
// TODO: Add exmaples from RFC 5870!
|
||||||
|
|
||||||
|
@ -373,7 +452,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn geo_uri_display() {
|
fn geo_uri_display() {
|
||||||
let mut geo_uri = GeoUri {
|
let mut geo_uri = GeoUri {
|
||||||
crs_id: CrsId::Wgs84,
|
crs: CoordRefSystem::Wgs84,
|
||||||
latitude: 52.107,
|
latitude: 52.107,
|
||||||
longitude: 5.134,
|
longitude: 5.134,
|
||||||
altitude: None,
|
altitude: None,
|
||||||
|
|
Loading…
Reference in a new issue