use crate::errors::ValidationError;
use crate::traits::ValueObject;
use super::Coordinate;
#[derive(Debug, Clone, PartialEq)]
pub struct BoundingBoxInput {
pub sw: Coordinate,
pub ne: Coordinate,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BoundingBox {
sw: Coordinate,
ne: Coordinate,
#[cfg_attr(feature = "serde", serde(skip))]
canonical: String,
}
impl ValueObject for BoundingBox {
type Input = BoundingBoxInput;
type Output = str;
type Error = ValidationError;
fn new(value: Self::Input) -> Result<Self, Self::Error> {
let sw_lat = value.sw.lat().value();
let sw_lng = value.sw.lng().value();
let ne_lat = value.ne.lat().value();
let ne_lng = value.ne.lng().value();
if sw_lat > ne_lat || sw_lng > ne_lng {
return Err(ValidationError::invalid(
"BoundingBox",
"sw must be south-west of ne (lat and lng must be ≤ ne)",
));
}
let canonical = format!("SW: {} / NE: {}", value.sw, value.ne);
Ok(Self {
sw: value.sw,
ne: value.ne,
canonical,
})
}
fn value(&self) -> &Self::Output {
&self.canonical
}
fn into_inner(self) -> Self::Input {
BoundingBoxInput {
sw: self.sw,
ne: self.ne,
}
}
}
impl BoundingBox {
pub fn sw(&self) -> &Coordinate {
&self.sw
}
pub fn ne(&self) -> &Coordinate {
&self.ne
}
}
impl std::fmt::Display for BoundingBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.canonical)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geo::{CoordinateInput, Latitude, Longitude};
fn coord(lat: f64, lng: f64) -> Coordinate {
Coordinate::new(CoordinateInput {
lat: Latitude::new(lat).unwrap(),
lng: Longitude::new(lng).unwrap(),
})
.unwrap()
}
#[test]
fn accepts_valid_bbox() {
let bbox = BoundingBox::new(BoundingBoxInput {
sw: coord(48.0, 14.0),
ne: coord(51.0, 18.0),
})
.unwrap();
assert!(bbox.value().starts_with("SW:"));
assert!(bbox.value().contains("NE:"));
}
#[test]
fn rejects_sw_north_of_ne() {
assert!(
BoundingBox::new(BoundingBoxInput {
sw: coord(52.0, 14.0),
ne: coord(51.0, 18.0),
})
.is_err()
);
}
#[test]
fn rejects_sw_east_of_ne() {
assert!(
BoundingBox::new(BoundingBoxInput {
sw: coord(48.0, 19.0),
ne: coord(51.0, 18.0),
})
.is_err()
);
}
#[test]
fn accepts_equal_corners() {
assert!(
BoundingBox::new(BoundingBoxInput {
sw: coord(50.0, 14.0),
ne: coord(50.0, 14.0),
})
.is_ok()
);
}
#[test]
fn accessors() {
let bbox = BoundingBox::new(BoundingBoxInput {
sw: coord(48.0, 14.0),
ne: coord(51.0, 18.0),
})
.unwrap();
assert_eq!(*bbox.sw().lat().value(), 48.0);
assert_eq!(*bbox.ne().lng().value(), 18.0);
}
#[test]
fn display_matches_value() {
let bbox = BoundingBox::new(BoundingBoxInput {
sw: coord(48.0, 14.0),
ne: coord(51.0, 18.0),
})
.unwrap();
assert_eq!(bbox.to_string(), bbox.value());
}
}