Skip to main content

egml_core/model/geometry/primitives/
solid.rs

1use crate::error::Error;
2use crate::impl_abstract_solid_traits;
3use crate::model::geometry::primitives::shell_property::ShellProperty;
4use crate::model::geometry::primitives::{AbstractSolid, AsAbstractSolid, AsAbstractSolidMut};
5use crate::model::geometry::{DirectPosition, Envelope};
6use nalgebra::Isometry3;
7
8/// A 3-D geometry bounded by one or more surfaces.
9///
10/// Corresponds to `gml:Solid` in [OGC 07-036 ยง10.6.4](https://docs.ogc.org/is/07-036/07-036.pdf).  The bounding surfaces are
11/// stored as [`SurfaceProperty`] members and may be of any [`SurfaceKind`](crate::model::geometry::primitives::surface_kind::SurfaceKind).
12#[derive(Debug, Clone, PartialEq)]
13pub struct Solid {
14    pub(crate) abstract_solid: AbstractSolid,
15    exterior: Option<ShellProperty>,
16}
17
18impl Solid {
19    /// Creates a new `Solid` from its bounding surfaces.
20    ///
21    /// # Errors
22    ///
23    /// Returns [`Error::TooFewElements`] if `members` is empty.
24    pub fn new(exterior: Option<ShellProperty>) -> Result<Self, Error> {
25        Ok(Self {
26            abstract_solid: AbstractSolid::default(),
27            exterior,
28        })
29    }
30
31    pub fn exterior(&self) -> Option<&ShellProperty> {
32        self.exterior.as_ref()
33    }
34}
35
36impl Solid {
37    pub fn points(&self) -> Vec<&DirectPosition> {
38        if let Some(exterior) = &self.exterior
39            && let Some(object) = &exterior.object
40        {
41            object.points()
42        } else {
43            Vec::new()
44        }
45    }
46
47    /// Returns the volume of this solid.
48    ///
49    /// # Errors
50    ///
51    /// Returns [`Error::MissingExteriorShell`] if the solid has no exterior shell property.
52    /// Returns [`Error::UnresolvedShellReference`] if the shell property carries only an
53    /// xlink:href that has not been resolved into an inline object.
54    /// Propagates any error from triangulating the bounding surfaces.
55    pub fn volume_3d(&self) -> Result<f64, Error> {
56        let shell_property = self.exterior.as_ref().ok_or(Error::MissingExteriorShell)?;
57        let shell =
58            shell_property
59                .object
60                .as_ref()
61                .ok_or_else(|| Error::UnresolvedShellReference {
62                    href: shell_property.href.clone(),
63                })?;
64        shell.volume_3d()
65    }
66
67    pub fn apply_transform(&mut self, m: &Isometry3<f64>) {
68        if let Some(exterior) = self.exterior.as_mut()
69            && let Some(object) = exterior.object.as_mut()
70        {
71            object.apply_transform(m)
72        }
73    }
74
75    /// Returns the union of the bounding boxes of all surface members.
76    pub fn compute_envelope(&self) -> Option<Envelope> {
77        if let Some(exterior) = &self.exterior
78            && let Some(object) = &exterior.object
79        {
80            object.compute_envelope()
81        } else {
82            None
83        }
84    }
85}
86
87impl AsAbstractSolid for Solid {
88    fn abstract_solid(&self) -> &AbstractSolid {
89        &self.abstract_solid
90    }
91}
92
93impl AsAbstractSolidMut for Solid {
94    fn abstract_solid_mut(&mut self) -> &mut AbstractSolid {
95        &mut self.abstract_solid
96    }
97}
98
99impl_abstract_solid_traits!(Solid);
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::model::geometry::Envelope;
105    use crate::model::geometry::primitives::ShellProperty;
106
107    #[test]
108    fn volume_3d_unit_cube() {
109        let solid = Envelope::new(
110            DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
111            DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
112        )
113        .unwrap()
114        .to_solid()
115        .unwrap();
116        assert!((solid.volume_3d().unwrap() - 1.0).abs() < 1e-10);
117    }
118
119    #[test]
120    fn volume_3d_2x3x4_box() {
121        let solid = Envelope::new(
122            DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
123            DirectPosition::new(2.0, 3.0, 4.0).unwrap(),
124        )
125        .unwrap()
126        .to_solid()
127        .unwrap();
128        assert!((solid.volume_3d().unwrap() - 24.0).abs() < 1e-10);
129    }
130
131    #[test]
132    fn volume_3d_missing_exterior_shell() {
133        let solid = Solid::new(None).unwrap();
134        assert_eq!(solid.volume_3d(), Err(Error::MissingExteriorShell));
135    }
136
137    #[test]
138    fn volume_3d_unresolved_shell_reference() {
139        let solid = Solid::new(Some(ShellProperty::new_href(
140            "urn:example:shell-1".to_string(),
141        )))
142        .unwrap();
143        assert_eq!(
144            solid.volume_3d(),
145            Err(Error::UnresolvedShellReference {
146                href: Some("urn:example:shell-1".to_string())
147            })
148        );
149    }
150}