use crate::error::Error;
use crate::model::geometry::DirectPosition;
use crate::model::geometry::primitives::{
AbstractRing, AbstractSolid, AbstractSurface, LinearRing, Polygon, RingPropertyKind, Solid,
SurfaceKind, SurfaceProperty, TriangulatedSurface,
};
use nalgebra::{Isometry3, Point3, Vector3};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Envelope {
lower_corner: DirectPosition,
upper_corner: DirectPosition,
srs_name: Option<String>,
srs_dimension: Option<u8>,
}
impl Envelope {
pub fn new(lower_corner: DirectPosition, upper_corner: DirectPosition) -> Result<Self, Error> {
if lower_corner.x() > upper_corner.x() {
return Err(Error::InvalidEnvelopeBounds {
axis: "x",
lower: lower_corner.x(),
upper: upper_corner.x(),
});
}
if lower_corner.y() > upper_corner.y() {
return Err(Error::InvalidEnvelopeBounds {
axis: "y",
lower: lower_corner.y(),
upper: upper_corner.y(),
});
}
if lower_corner.z() > upper_corner.z() {
return Err(Error::InvalidEnvelopeBounds {
axis: "z",
lower: lower_corner.z(),
upper: upper_corner.z(),
});
}
Ok(Self {
lower_corner,
upper_corner,
srs_name: None,
srs_dimension: None,
})
}
pub(crate) fn new_unchecked(
lower_corner: DirectPosition,
upper_corner: DirectPosition,
) -> Self {
debug_assert!(
{
let lc: Point3<f64> = lower_corner.into();
let uc: Point3<f64> = upper_corner.into();
lc <= uc
},
"lower_corner must be <= upper_corner"
);
Self {
lower_corner,
upper_corner,
srs_name: None,
srs_dimension: None,
}
}
pub fn lower_corner(&self) -> &DirectPosition {
&self.lower_corner
}
pub fn upper_corner(&self) -> &DirectPosition {
&self.upper_corner
}
pub fn size(&self) -> Vector3<f64> {
let lower_corner_point: Point3<f64> = self.lower_corner.into();
let upper_corner_point: Point3<f64> = self.upper_corner.into();
upper_corner_point - lower_corner_point
}
pub fn size_x(&self) -> f64 {
self.upper_corner.x() - self.lower_corner.x()
}
pub fn size_y(&self) -> f64 {
self.upper_corner.y() - self.lower_corner.y()
}
pub fn size_z(&self) -> f64 {
self.upper_corner.z() - self.lower_corner.z()
}
pub fn volume(&self) -> f64 {
self.size_x() * self.size_y() * self.size_z()
}
pub fn is_point(&self) -> bool {
self.lower_corner == self.upper_corner
}
#[allow(clippy::nonminimal_bool)]
pub fn is_linear(&self) -> bool {
let nx = self.size_x() > 0.0;
let ny = self.size_y() > 0.0;
let nz = self.size_z() > 0.0;
(nx && !ny && !nz) || (!nx && ny && !nz) || (!nx && !ny && nz)
}
#[allow(clippy::nonminimal_bool)]
pub fn is_surface(&self) -> bool {
let nx = self.size_x() > 0.0;
let ny = self.size_y() > 0.0;
let nz = self.size_z() > 0.0;
(nx && ny && !nz) || (nx && !ny && nz) || (!nx && ny && nz)
}
pub fn is_volume(&self) -> bool {
self.size_x() > 0.0 && self.size_y() > 0.0 && self.size_z() > 0.0
}
fn non_zero_extents(&self) -> u8 {
[self.size_x(), self.size_y(), self.size_z()]
.iter()
.filter(|&&s| s > 0.0)
.count() as u8
}
pub fn center(&self) -> DirectPosition {
DirectPosition::new(
self.lower_corner.x() + self.size_x() / 2.0,
self.lower_corner.y() + self.size_y() / 2.0,
self.lower_corner.z() + self.size_z() / 2.0,
)
.expect("envelope corners are finite")
}
pub fn contains(&self, point: &DirectPosition) -> bool {
let lower_corner: Point3<f64> = self.lower_corner.into();
let upper_corner: Point3<f64> = self.upper_corner.into();
let point: Point3<f64> = (*point).into();
lower_corner <= point && point <= upper_corner
}
pub fn contains_envelope(&self, envelope: &Envelope) -> bool {
self.contains(&envelope.lower_corner) && self.contains(&envelope.upper_corner)
}
pub fn contains_envelope_partially(&self, envelope: &Envelope) -> bool {
self.contains(&envelope.lower_corner) || self.contains(&envelope.upper_corner)
}
pub fn enlarge(&self, distance: f64) -> Result<Envelope, Error> {
let lower_corner = DirectPosition::new(
self.lower_corner.x() - distance,
self.lower_corner.y() - distance,
self.lower_corner.z() - distance,
)?;
let upper_corner = DirectPosition::new(
self.upper_corner.x() + distance,
self.upper_corner.y() + distance,
self.upper_corner.z() + distance,
)?;
Envelope::new(lower_corner, upper_corner)
}
pub fn apply_transform(&mut self, m: &Isometry3<f64>) {
let transformed_lower_corner: Point3<f64> = m * Point3::from(self.lower_corner);
let transformed_upper_corner: Point3<f64> = m * Point3::from(self.upper_corner);
self.lower_corner = DirectPosition::new(
transformed_lower_corner.x.min(transformed_upper_corner.x),
transformed_lower_corner.y.min(transformed_upper_corner.y),
transformed_lower_corner.z.min(transformed_upper_corner.z),
)
.expect("envelope corners are finite");
self.upper_corner = DirectPosition::new(
transformed_lower_corner.x.max(transformed_upper_corner.x),
transformed_lower_corner.y.max(transformed_upper_corner.y),
transformed_lower_corner.z.max(transformed_upper_corner.z),
)
.expect("envelope corners are finite");
}
}
impl Envelope {
pub fn from_envelopes(envelopes: &[Self]) -> Option<Self> {
let first = envelopes.first()?;
let (lower, upper) = envelopes.iter().skip(1).fold(
(first.lower_corner, first.upper_corner),
|(lo, hi), e| {
let new_lo = DirectPosition::new(
lo.x().min(e.lower_corner.x()),
lo.y().min(e.lower_corner.y()),
lo.z().min(e.lower_corner.z()),
)
.unwrap();
let new_hi = DirectPosition::new(
hi.x().max(e.upper_corner.x()),
hi.y().max(e.upper_corner.y()),
hi.z().max(e.upper_corner.z()),
)
.unwrap();
(new_lo, new_hi)
},
);
Some(Envelope::new_unchecked(lower, upper))
}
pub fn from_points(points: &[DirectPosition]) -> Result<Self, Error> {
if points.is_empty() {
return Err(Error::TooFewElements {
geometry: "Envelope::from_points",
minimum: 1,
spec: None,
id: None,
detail: None,
});
}
let first = &points[0];
let (mut min_x, mut min_y, mut min_z) = (first.x(), first.y(), first.z());
let (mut max_x, mut max_y, mut max_z) = (first.x(), first.y(), first.z());
for point in points.iter().skip(1) {
min_x = min_x.min(point.x());
min_y = min_y.min(point.y());
min_z = min_z.min(point.z());
max_x = max_x.max(point.x());
max_y = max_y.max(point.y());
max_z = max_z.max(point.z());
}
let lower_corner = DirectPosition::new(min_x, min_y, min_z)?;
let upper_corner = DirectPosition::new(max_x, max_y, max_z)?;
Ok(Self::new_unchecked(lower_corner, upper_corner))
}
}
impl Envelope {
pub fn to_solid(&self) -> Result<Solid, Error> {
if !self.is_volume() {
return Err(Error::NotAVolume {
non_zero_extents: self.non_zero_extents(),
});
}
let (lx, ly, lz) = (
self.lower_corner.x(),
self.lower_corner.y(),
self.lower_corner.z(),
);
let (hx, hy, hz) = (
self.upper_corner.x(),
self.upper_corner.y(),
self.upper_corner.z(),
);
let p000 = DirectPosition::new(lx, ly, lz).expect("envelope corners are finite");
let p100 = DirectPosition::new(hx, ly, lz).expect("envelope corners are finite");
let p110 = DirectPosition::new(hx, hy, lz).expect("envelope corners are finite");
let p010 = DirectPosition::new(lx, hy, lz).expect("envelope corners are finite");
let p001 = DirectPosition::new(lx, ly, hz).expect("envelope corners are finite");
let p101 = DirectPosition::new(hx, ly, hz).expect("envelope corners are finite");
let p111 = DirectPosition::new(hx, hy, hz).expect("envelope corners are finite");
let p011 = DirectPosition::new(lx, hy, hz).expect("envelope corners are finite");
let face_rings: [Vec<DirectPosition>; 6] = [
vec![p000, p010, p110, p100], vec![p001, p101, p111, p011], vec![p000, p100, p101, p001], vec![p010, p011, p111, p110], vec![p000, p001, p011, p010], vec![p100, p110, p111, p101], ];
let members: Vec<SurfaceProperty> = face_rings
.into_iter()
.map(|points| {
let ring = LinearRing::new(AbstractRing::default(), points).ok()?;
let polygon = Polygon::new(
AbstractSurface::default(),
Some(RingPropertyKind::LinearRing(ring)),
vec![],
)
.ok()?;
Some(SurfaceProperty::new(SurfaceKind::Polygon(polygon)))
})
.collect::<Option<_>>()
.expect("envelope corners are finite and valid");
let solid = Solid::new(AbstractSolid::default(), members).expect("envelope is valid");
Ok(solid)
}
pub fn to_polygon(&self) -> Result<Polygon, Error> {
if !self.is_surface() {
return Err(Error::NotASurface {
non_zero_extents: self.non_zero_extents(),
});
}
let (lx, ly, lz) = (
self.lower_corner.x(),
self.lower_corner.y(),
self.lower_corner.z(),
);
let (hx, hy, hz) = (
self.upper_corner.x(),
self.upper_corner.y(),
self.upper_corner.z(),
);
let points = if self.size_z() == 0.0 {
vec![
DirectPosition::new(lx, ly, lz).expect("envelope corners are finite"),
DirectPosition::new(hx, ly, lz).expect("envelope corners are finite"),
DirectPosition::new(hx, hy, lz).expect("envelope corners are finite"),
DirectPosition::new(lx, hy, lz).expect("envelope corners are finite"),
]
} else if self.size_y() == 0.0 {
vec![
DirectPosition::new(lx, ly, lz).expect("envelope corners are finite"),
DirectPosition::new(lx, ly, hz).expect("envelope corners are finite"),
DirectPosition::new(hx, ly, hz).expect("envelope corners are finite"),
DirectPosition::new(hx, ly, lz).expect("envelope corners are finite"),
]
} else {
vec![
DirectPosition::new(lx, ly, lz).expect("envelope corners are finite"),
DirectPosition::new(lx, hy, lz).expect("envelope corners are finite"),
DirectPosition::new(lx, hy, hz).expect("envelope corners are finite"),
DirectPosition::new(lx, ly, hz).expect("envelope corners are finite"),
]
};
let ring = LinearRing::new(AbstractRing::default(), points)
.expect("envelope corners are finite and valid");
Polygon::new(
AbstractSurface::default(),
Some(RingPropertyKind::LinearRing(ring)),
vec![],
)
.map_err(|_| Error::NotASurface {
non_zero_extents: self.non_zero_extents(),
})
}
pub fn to_triangulated_surface(&self) -> Result<TriangulatedSurface, Error> {
if self.is_surface() {
self.to_polygon()?.triangulate()
} else if self.is_volume() {
self.to_solid()?.triangulate()
} else {
Err(Error::NotSurfaceOrVolume {
non_zero_extents: self.non_zero_extents(),
})
}
}
}
impl fmt::Display for Envelope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Envelope[{}, {}, {} -> {}, {}, {}]",
self.lower_corner.x(),
self.lower_corner.y(),
self.lower_corner.z(),
self.upper_corner.x(),
self.upper_corner.y(),
self.upper_corner.z()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pos(x: f64, y: f64, z: f64) -> DirectPosition {
DirectPosition::new(x, y, z).unwrap()
}
fn env(lx: f64, ly: f64, lz: f64, ux: f64, uy: f64, uz: f64) -> Envelope {
Envelope::new(pos(lx, ly, lz), pos(ux, uy, uz)).unwrap()
}
#[test]
fn from_envelopes_empty_returns_none() {
let result = Envelope::from_envelopes(&[]);
assert!(result.is_none());
}
#[test]
fn from_envelopes_single_returns_same_envelope() {
let e = env(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
let result = Envelope::from_envelopes(&[e.clone()]).unwrap();
assert_eq!(result, e);
}
#[test]
fn from_envelopes_two_disjoint() {
let a = env(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
let b = env(5.0, 6.0, 7.0, 8.0, 9.0, 10.0);
let result = Envelope::from_envelopes(&[a, b]).unwrap();
assert_eq!(result, env(0.0, 0.0, 0.0, 8.0, 9.0, 10.0));
}
#[test]
fn from_envelopes_overlapping() {
let a = env(0.0, 0.0, 0.0, 5.0, 5.0, 5.0);
let b = env(3.0, 3.0, 3.0, 7.0, 7.0, 7.0);
let result = Envelope::from_envelopes(&[a, b]).unwrap();
assert_eq!(result, env(0.0, 0.0, 0.0, 7.0, 7.0, 7.0));
}
#[test]
fn from_envelopes_one_contains_the_other() {
let outer = env(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
let inner = env(2.0, 3.0, 4.0, 5.0, 6.0, 7.0);
let result = Envelope::from_envelopes(&[outer.clone(), inner]).unwrap();
assert_eq!(result, outer);
}
#[test]
fn from_envelopes_multiple() {
let a = env(0.0, 10.0, 20.0, 1.0, 11.0, 21.0);
let b = env(-5.0, 8.0, 25.0, 2.0, 12.0, 30.0);
let c = env(1.0, 9.0, 18.0, 3.0, 15.0, 22.0);
let result = Envelope::from_envelopes(&[a, b, c]).unwrap();
assert_eq!(result, env(-5.0, 8.0, 18.0, 3.0, 15.0, 30.0));
}
#[test]
fn from_envelopes_with_negative_coords() {
let a = env(-10.0, -20.0, -30.0, -1.0, -2.0, -3.0);
let b = env(-5.0, -25.0, -15.0, 0.0, -1.0, 0.0);
let result = Envelope::from_envelopes(&[a, b]).unwrap();
assert_eq!(result, env(-10.0, -25.0, -30.0, 0.0, -1.0, 0.0));
}
#[test]
fn from_envelopes_zero_volume_envelopes() {
let a = env(1.0, 1.0, 1.0, 1.0, 1.0, 1.0); let b = env(3.0, 3.0, 3.0, 3.0, 3.0, 3.0); let result = Envelope::from_envelopes(&[a, b]).unwrap();
assert_eq!(result, env(1.0, 1.0, 1.0, 3.0, 3.0, 3.0));
}
#[test]
fn is_point_when_corners_equal() {
let e = env(1.0, 2.0, 3.0, 1.0, 2.0, 3.0);
assert!(e.is_point());
assert!(!e.is_linear());
assert!(!e.is_surface());
assert!(!e.is_volume());
}
#[test]
fn is_linear_along_x() {
let e = env(0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
assert!(!e.is_point());
assert!(e.is_linear());
assert!(!e.is_surface());
assert!(!e.is_volume());
}
#[test]
fn is_linear_along_y() {
let e = env(0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
assert!(e.is_linear());
}
#[test]
fn is_linear_along_z() {
let e = env(0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
assert!(e.is_linear());
}
#[test]
fn is_surface_xy_plane() {
let e = env(0.0, 0.0, 0.0, 1.0, 1.0, 0.0);
assert!(!e.is_point());
assert!(!e.is_linear());
assert!(e.is_surface());
assert!(!e.is_volume());
}
#[test]
fn is_surface_xz_plane() {
let e = env(0.0, 0.0, 0.0, 1.0, 0.0, 1.0);
assert!(e.is_surface());
}
#[test]
fn is_surface_yz_plane() {
let e = env(0.0, 0.0, 0.0, 0.0, 1.0, 1.0);
assert!(e.is_surface());
}
#[test]
fn is_volume_all_extents_nonzero() {
let e = env(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
assert!(!e.is_point());
assert!(!e.is_linear());
assert!(!e.is_surface());
assert!(e.is_volume());
}
#[test]
fn to_polygon_returns_err_for_point() {
assert_eq!(
env(1.0, 1.0, 1.0, 1.0, 1.0, 1.0).to_polygon(),
Err(Error::NotASurface {
non_zero_extents: 0
})
);
}
#[test]
fn to_polygon_returns_err_for_linear() {
assert_eq!(
env(0.0, 0.0, 0.0, 1.0, 0.0, 0.0).to_polygon(),
Err(Error::NotASurface {
non_zero_extents: 1
})
);
}
#[test]
fn to_polygon_returns_err_for_volume() {
assert_eq!(
env(0.0, 0.0, 0.0, 1.0, 1.0, 1.0).to_polygon(),
Err(Error::NotASurface {
non_zero_extents: 3
})
);
}
#[test]
fn to_polygon_xy_plane() {
assert!(env(0.0, 0.0, 0.0, 2.0, 3.0, 0.0).to_polygon().is_ok());
}
#[test]
fn to_polygon_xz_plane() {
assert!(env(0.0, 0.0, 0.0, 2.0, 0.0, 3.0).to_polygon().is_ok());
}
#[test]
fn to_polygon_yz_plane() {
assert!(env(0.0, 0.0, 0.0, 0.0, 2.0, 3.0).to_polygon().is_ok());
}
#[test]
fn to_triangulated_surface_returns_err_for_point() {
assert_eq!(
env(0.0, 0.0, 0.0, 0.0, 0.0, 0.0).to_triangulated_surface(),
Err(Error::NotSurfaceOrVolume {
non_zero_extents: 0
})
);
}
#[test]
fn to_triangulated_surface_returns_err_for_linear() {
assert_eq!(
env(0.0, 0.0, 0.0, 1.0, 0.0, 0.0).to_triangulated_surface(),
Err(Error::NotSurfaceOrVolume {
non_zero_extents: 1
})
);
}
#[test]
fn to_triangulated_surface_surface_has_two_triangles() {
let result = env(0.0, 0.0, 0.0, 2.0, 3.0, 0.0)
.to_triangulated_surface()
.unwrap();
assert_eq!(result.triangles().len(), 2);
}
#[test]
fn to_triangulated_surface_volume_has_twelve_triangles() {
let result = env(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)
.to_triangulated_surface()
.unwrap();
assert_eq!(result.triangles().len(), 12);
}
#[test]
fn envelope_contains() {
let lower_corner = DirectPosition::new(1.0, 2.0, 3.0).unwrap();
let upper_corner = DirectPosition::new(2.0, 3.0, 4.0).unwrap();
let envelope = Envelope::new(lower_corner, upper_corner).unwrap();
let point_a = DirectPosition::new(1.5, 2.5, 3.5).unwrap();
let point_b = DirectPosition::new(2.5, 3.5, 4.5).unwrap();
assert!(envelope.contains(&point_a));
assert!(!envelope.contains(&point_b));
}
}