Skip to main content

cityjson_types/backend/default/
geometry.rs

1//! Core geometry storage shared across `CityJSON` versions.
2//!
3//! This is the crate's normalized geometry representation.
4//!
5//! Important invariants:
6//!
7//! - regular geometry and template geometry stay separate in `CityModel`
8//!   storage
9//! - template vertices are distinct from root vertices
10//! - `GeometryInstance` is stored as one explicit value:
11//!   - template handle
12//!   - reference point in the root vertex store
13//!   - transformation matrix
14//! - public APIs can diverge from the `CityJSON` wire format as long as the
15//!   meaning is preserved exactly
16
17use crate::cityjson::core::boundary::Boundary;
18use crate::cityjson::core::vertex::VertexRef;
19use crate::error::Error;
20use crate::resources::id::ResourceId;
21use crate::resources::mapping::SemanticOrMaterialMap;
22use crate::resources::mapping::textures::TextureMapCore;
23use crate::resources::storage::StringStorage;
24use crate::v2_0::vertex::VertexIndex;
25use std::str::FromStr;
26
27pub(crate) type ThemedMaterials<VR, RR, SS> = Vec<(
28    crate::cityjson::core::appearance::ThemeName<SS>,
29    SemanticOrMaterialMap<VR, RR>,
30)>;
31pub(crate) type ThemedTextures<VR, RR, SS> = Vec<(
32    crate::cityjson::core::appearance::ThemeName<SS>,
33    TextureMapCore<VR, RR>,
34)>;
35
36#[derive(Clone, Copy, Debug, PartialEq)]
37pub struct AffineTransform3D([f64; 16]);
38
39impl AffineTransform3D {
40    #[must_use]
41    pub fn new(matrix: [f64; 16]) -> Self {
42        Self(matrix)
43    }
44
45    #[must_use]
46    pub fn identity() -> Self {
47        Self([
48            1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
49        ])
50    }
51
52    #[must_use]
53    pub fn as_array(&self) -> &[f64; 16] {
54        &self.0
55    }
56
57    #[must_use]
58    pub fn into_array(self) -> [f64; 16] {
59        self.0
60    }
61}
62
63impl Default for AffineTransform3D {
64    fn default() -> Self {
65        Self::identity()
66    }
67}
68
69impl From<[f64; 16]> for AffineTransform3D {
70    fn from(value: [f64; 16]) -> Self {
71        Self::new(value)
72    }
73}
74
75impl From<AffineTransform3D> for [f64; 16] {
76    fn from(value: AffineTransform3D) -> Self {
77        value.into_array()
78    }
79}
80
81impl AsRef<[f64; 16]> for AffineTransform3D {
82    fn as_ref(&self) -> &[f64; 16] {
83        self.as_array()
84    }
85}
86
87#[derive(Clone, Copy, Debug)]
88pub(crate) struct GeometryInstanceData<VR: VertexRef, RR: ResourceId> {
89    template: RR,
90    reference_point: VertexIndex<VR>,
91    transformation: AffineTransform3D,
92}
93
94impl<VR: VertexRef, RR: ResourceId> GeometryInstanceData<VR, RR> {
95    #[must_use]
96    pub(crate) fn new(
97        template: RR,
98        reference_point: VertexIndex<VR>,
99        transformation: AffineTransform3D,
100    ) -> Self {
101        Self {
102            template,
103            reference_point,
104            transformation,
105        }
106    }
107
108    pub(crate) fn template(&self) -> &RR {
109        &self.template
110    }
111
112    pub(crate) fn reference_point(&self) -> &VertexIndex<VR> {
113        &self.reference_point
114    }
115
116    pub(crate) fn transformation(&self) -> &AffineTransform3D {
117        &self.transformation
118    }
119}
120
121/// Core geometry structure that contains the data for all `CityJSON` versions.
122/// Version-specific types wrap this core structure and implement methods via macros.
123#[derive(Clone, Debug)]
124pub(crate) struct GeometryCore<VR: VertexRef, RR: ResourceId, SS: StringStorage> {
125    type_geometry: GeometryType,
126    lod: Option<LoD>,
127    boundaries: Option<Boundary<VR>>,
128    semantics: Option<SemanticOrMaterialMap<VR, RR>>,
129    materials: Option<ThemedMaterials<VR, RR, SS>>,
130    textures: Option<ThemedTextures<VR, RR, SS>>,
131    instance: Option<GeometryInstanceData<VR, RR>>,
132}
133
134impl<VR: VertexRef, RR: ResourceId, SS: StringStorage> GeometryCore<VR, RR, SS> {
135    #[allow(clippy::too_many_arguments)]
136    pub(crate) fn new(
137        type_geometry: GeometryType,
138        lod: Option<LoD>,
139        boundaries: Option<Boundary<VR>>,
140        semantics: Option<SemanticOrMaterialMap<VR, RR>>,
141        materials: Option<ThemedMaterials<VR, RR, SS>>,
142        textures: Option<ThemedTextures<VR, RR, SS>>,
143        instance: Option<GeometryInstanceData<VR, RR>>,
144    ) -> Self {
145        Self {
146            type_geometry,
147            lod,
148            boundaries,
149            semantics,
150            materials,
151            textures,
152            instance,
153        }
154    }
155
156    pub fn type_geometry(&self) -> &GeometryType {
157        &self.type_geometry
158    }
159
160    pub fn lod(&self) -> Option<&LoD> {
161        self.lod.as_ref()
162    }
163
164    pub fn boundaries(&self) -> Option<&Boundary<VR>> {
165        self.boundaries.as_ref()
166    }
167
168    pub(crate) fn semantics(&self) -> Option<&SemanticOrMaterialMap<VR, RR>> {
169        self.semantics.as_ref()
170    }
171
172    pub(crate) fn materials(&self) -> Option<&ThemedMaterials<VR, RR, SS>> {
173        self.materials.as_ref()
174    }
175
176    pub(crate) fn textures(&self) -> Option<&ThemedTextures<VR, RR, SS>> {
177        self.textures.as_ref()
178    }
179
180    pub(crate) fn instance(&self) -> Option<&GeometryInstanceData<VR, RR>> {
181        self.instance.as_ref()
182    }
183}
184
185#[repr(C)]
186#[derive(Debug, Clone, Copy, Hash, Ord, PartialOrd, Eq, PartialEq)]
187#[non_exhaustive]
188pub enum GeometryType {
189    MultiPoint,
190    MultiLineString,
191    MultiSurface,
192    CompositeSurface,
193    Solid,
194    MultiSolid,
195    CompositeSolid,
196    GeometryInstance,
197}
198
199impl std::fmt::Display for GeometryType {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        write!(f, "{self:?}")
202    }
203}
204
205impl FromStr for GeometryType {
206    type Err = Error;
207
208    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
209        match s {
210            "MultiPoint" => Ok(GeometryType::MultiPoint),
211            "MultiLineString" => Ok(GeometryType::MultiLineString),
212            "MultiSurface" => Ok(GeometryType::MultiSurface),
213            "CompositeSurface" => Ok(GeometryType::CompositeSurface),
214            "Solid" => Ok(GeometryType::Solid),
215            "MultiSolid" => Ok(GeometryType::MultiSolid),
216            "CompositeSolid" => Ok(GeometryType::CompositeSolid),
217            "GeometryInstance" => Ok(GeometryType::GeometryInstance),
218            &_ => Err(Error::InvalidGeometryType {
219                expected: "one of MultiPoint, MultiLineString, MultiSurface, CompositeSurface, Solid, MultiSolid, CompositeSolid, GeometryInstance"
220                    .to_string(),
221                found: s.to_string(),
222            }),
223        }
224    }
225}
226
227/// Level of Detail (`LoD`) for the geometry
228#[repr(C)]
229#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
230#[non_exhaustive]
231pub enum LoD {
232    LoD0,
233    LoD0_0,
234    LoD0_1,
235    LoD0_2,
236    LoD0_3,
237    LoD1,
238    LoD1_0,
239    LoD1_1,
240    LoD1_2,
241    LoD1_3,
242    LoD2,
243    LoD2_0,
244    LoD2_1,
245    LoD2_2,
246    LoD2_3,
247    LoD3,
248    LoD3_0,
249    LoD3_1,
250    LoD3_2,
251    LoD3_3,
252}
253
254impl std::fmt::Display for LoD {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        match *self {
257            LoD::LoD0 => write!(f, "0"),
258            LoD::LoD0_0 => write!(f, "0.0"),
259            LoD::LoD0_1 => write!(f, "0.1"),
260            LoD::LoD0_2 => write!(f, "0.2"),
261            LoD::LoD0_3 => write!(f, "0.3"),
262            LoD::LoD1 => write!(f, "1"),
263            LoD::LoD1_0 => write!(f, "1.0"),
264            LoD::LoD1_1 => write!(f, "1.1"),
265            LoD::LoD1_2 => write!(f, "1.2"),
266            LoD::LoD1_3 => write!(f, "1.3"),
267            LoD::LoD2 => write!(f, "2"),
268            LoD::LoD2_0 => write!(f, "2.0"),
269            LoD::LoD2_1 => write!(f, "2.1"),
270            LoD::LoD2_2 => write!(f, "2.2"),
271            LoD::LoD2_3 => write!(f, "2.3"),
272            LoD::LoD3 => write!(f, "3"),
273            LoD::LoD3_0 => write!(f, "3.0"),
274            LoD::LoD3_1 => write!(f, "3.1"),
275            LoD::LoD3_2 => write!(f, "3.2"),
276            LoD::LoD3_3 => write!(f, "3.3"),
277        }
278    }
279}