Skip to main content

cityjson_types/v2_0/
geometry.rs

1//! Geometry read API for `CityJSON` 2.0.
2//!
3//! [`Geometry`] covers the eight geometry types from spec §3 (boundary arrays) plus
4//! `GeometryInstance` (spec §3.4, geometry templates). The table below maps each type to the
5//! nesting depth of its boundary array and its typical `LoD` range:
6//!
7//! | Type | Boundary nesting | Typical `LoD` |
8//! |------|-----------------|-------------|
9//! | `MultiPoint` | `[v, …]` | any |
10//! | `MultiLineString` | `[[v, …], …]` | any |
11//! | `MultiSurface` | `[[[v, …], …], …]` | 0–2 |
12//! | `CompositeSurface` | `[[[v, …], …], …]` | 2–3 |
13//! | `Solid` | `[[[[v, …], …], …], …]` | 1–3 |
14//! | `MultiSolid` | `[[[[[v, …], …], …], …], …]` | 1–3 |
15//! | `CompositeSolid` | `[[[[[v, …], …], …], …], …]` | 3 |
16//! | `GeometryInstance` | — (template reference) | any |
17//!
18//! Boundaries are stored flat internally. Use
19//! [`Boundary::check_type`](super::boundary::Boundary::check_type) and the `to_nested_*`
20//! methods to recover the nested JSON form.
21//!
22//! Semantics, materials, and textures are accessed through map views ([`SemanticMapView`],
23//! [`MaterialMapView`], [`TextureMapView`]) keyed by theme name.
24//!
25//! For `GeometryInstance`, [`CityModel::resolve_geometry`](super::citymodel::CityModel::resolve_geometry) returns a [`GeometryView`] pointing
26//! at the referenced template geometry.
27//!
28//! ```rust
29//! use cityjson_types::CityModelType;
30//! use cityjson_types::error::Result;
31//! use cityjson_types::v2_0::{
32//!     AffineTransform3D, CityModel, GeometryDraft, GeometryType, PointDraft,
33//!     RealWorldCoordinate,
34//! };
35//!
36//! fn read_instance_and_resolve() -> Result<()> {
37//!     let mut model = CityModel::<u32>::new(CityModelType::CityJSON);
38//!
39//!     let template_handle = GeometryDraft::multi_point(
40//!         None,
41//!         [PointDraft::new(RealWorldCoordinate::new(0.0, 0.0, 0.0))],
42//!     )
43//!     .insert_template_into(&mut model)?;
44//!
45//!     let instance_handle = GeometryDraft::instance(
46//!         template_handle,
47//!         RealWorldCoordinate::new(10.0, 20.0, 0.0),
48//!         AffineTransform3D::identity(),
49//!     )
50//!     .insert_into(&mut model)?;
51//!
52//!     let geometry = model.get_geometry(instance_handle).unwrap();
53//!     assert_eq!(geometry.type_geometry(), &GeometryType::GeometryInstance);
54//!     let instance = geometry.instance().unwrap();
55//!     assert_eq!(instance.template(), template_handle);
56//!
57//!     let resolved = model.resolve_geometry(instance_handle)?;
58//!     assert_eq!(resolved.type_geometry(), &GeometryType::MultiPoint);
59//!     Ok(())
60//! }
61//! ```
62use crate::backend::default::geometry::{
63    GeometryCore, GeometryInstanceData, ThemedMaterials, ThemedTextures,
64};
65use crate::cityjson::core::appearance::ThemeName;
66use crate::cityjson::core::coordinate::Coordinate;
67use crate::resources::handles::{
68    GeometryTemplateHandle, MaterialHandle, SemanticHandle, TextureHandle,
69};
70use crate::resources::id::ResourceId32;
71use crate::resources::mapping::textures::TextureMapCore;
72use crate::resources::mapping::{MaterialMap, SemanticMap, SemanticOrMaterialMap, TextureMap};
73use crate::resources::storage::StringStorage;
74use crate::v2_0::Vertices;
75use crate::v2_0::boundary::Boundary;
76use crate::v2_0::boundary::{BoundaryCoordinates, BoundaryUniqueCoordinates};
77use crate::v2_0::vertex::{VertexIndex, VertexRef};
78use std::marker::PhantomData;
79use std::ops::{Deref, Index};
80
81pub mod semantic;
82pub use crate::backend::default::geometry::AffineTransform3D;
83pub use crate::cityjson::core::geometry::{GeometryType, LoD};
84
85/// A stored geometry.
86///
87/// Covers all eight `CityJSON` geometry types. Use [`Geometry::type_geometry`] to determine
88/// the type, then access boundaries, semantics, materials, and textures through the
89/// corresponding methods.
90///
91/// Boundaries are stored in flat offset-encoded form. Use `boundary.to_nested_*` to get
92/// nested arrays compatible with the JSON representation.
93#[derive(Clone, Debug)]
94pub struct Geometry<VR: VertexRef, SS: StringStorage> {
95    inner: GeometryCore<VR, ResourceId32, SS>,
96}
97
98/// Public flat parts for constructing a stored [`Geometry`].
99///
100/// This is the raw write-layer input: callers provide the final flat topology and resource
101/// maps directly, and [`Geometry::from_stored_parts`] packages them into the internal storage
102/// layout without additional validation.
103#[derive(Clone, Debug, PartialEq)]
104pub struct StoredGeometryParts<VR: VertexRef, SS: StringStorage> {
105    pub type_geometry: GeometryType,
106    pub lod: Option<LoD>,
107    pub boundaries: Option<Boundary<VR>>,
108    pub semantics: Option<SemanticMap<VR>>,
109    pub materials: Option<Vec<(ThemeName<SS>, MaterialMap<VR>)>>,
110    pub textures: Option<Vec<(ThemeName<SS>, TextureMap<VR>)>>,
111    pub instance: Option<StoredGeometryInstance<VR>>,
112}
113
114/// Public stored `GeometryInstance` payload used by [`StoredGeometryParts`].
115#[derive(Clone, Copy, Debug, PartialEq)]
116pub struct StoredGeometryInstance<VR: VertexRef> {
117    pub template: GeometryTemplateHandle,
118    pub reference_point: VertexIndex<VR>,
119    pub transformation: AffineTransform3D,
120}
121
122/// Read view over the `GeometryInstance` fields of a geometry.
123///
124/// A `GeometryInstance` references a template geometry and places it at a point
125/// in the model's vertex pool using a 4×4 transformation matrix.
126/// Use [`CityModel::resolve_geometry`](super::citymodel::CityModel::resolve_geometry)
127/// to get a view of the effective (resolved) geometry type.
128#[derive(Clone, Copy, Debug)]
129pub struct GeometryInstanceView<'a, VR: VertexRef> {
130    inner: &'a GeometryInstanceData<VR, ResourceId32>,
131}
132
133impl<VR: VertexRef> GeometryInstanceView<'_, VR> {
134    #[must_use]
135    pub fn template(&self) -> GeometryTemplateHandle {
136        GeometryTemplateHandle::from_raw(*self.inner.template())
137    }
138
139    #[must_use]
140    pub fn reference_point(&self) -> VertexIndex<VR> {
141        *self.inner.reference_point()
142    }
143
144    #[must_use]
145    pub fn transformation(&self) -> AffineTransform3D {
146        *self.inner.transformation()
147    }
148}
149
150/// A read view over a geometry, optionally resolved from a `GeometryInstance`.
151///
152/// When obtained from [`CityModel::resolve_geometry`](super::citymodel::CityModel::resolve_geometry),
153/// this view points at the effective geometry type (e.g. the `MultiSurface` that a
154/// `GeometryInstance` references), with the original instance data available through
155/// [`GeometryView::instance`].
156#[derive(Clone, Copy, Debug)]
157pub struct GeometryView<'a, VR: VertexRef, SS: StringStorage> {
158    geometry: &'a Geometry<VR, SS>,
159    instance: Option<GeometryInstanceView<'a, VR>>,
160}
161
162#[derive(Clone, Copy, Debug)]
163pub struct HandleOptionSlice<'a, H> {
164    raw: &'a [Option<ResourceId32>],
165    _marker: PhantomData<H>,
166}
167
168impl<'a, H> HandleOptionSlice<'a, H> {
169    fn new(raw: &'a [Option<ResourceId32>]) -> Self {
170        Self {
171            raw,
172            _marker: PhantomData,
173        }
174    }
175
176    #[inline]
177    fn as_handle_slice(&self) -> &'a [Option<H>] {
178        const {
179            assert!(
180                std::mem::size_of::<Option<H>>() == std::mem::size_of::<Option<ResourceId32>>()
181            );
182            assert!(
183                std::mem::align_of::<Option<H>>() == std::mem::align_of::<Option<ResourceId32>>()
184            );
185        }
186
187        // SAFETY: handle types are `#[repr(transparent)]` wrappers over `ResourceId32`.
188        // Therefore `Option<Handle>` and `Option<ResourceId32>` have identical layout.
189        unsafe { std::slice::from_raw_parts(self.raw.as_ptr().cast::<Option<H>>(), self.raw.len()) }
190    }
191
192    #[must_use]
193    pub fn len(&self) -> usize {
194        self.raw.len()
195    }
196
197    #[must_use]
198    pub fn is_empty(&self) -> bool {
199        self.raw.is_empty()
200    }
201
202    #[must_use]
203    pub fn get(&self, index: usize) -> Option<&'a Option<H>> {
204        self.as_handle_slice().get(index)
205    }
206
207    pub fn iter(&self) -> std::slice::Iter<'a, Option<H>> {
208        self.as_handle_slice().iter()
209    }
210}
211
212impl<H> Index<usize> for HandleOptionSlice<'_, H> {
213    type Output = Option<H>;
214
215    fn index(&self, index: usize) -> &Self::Output {
216        &self.as_handle_slice()[index]
217    }
218}
219
220impl<'a, H: 'a> IntoIterator for HandleOptionSlice<'a, H> {
221    type Item = &'a Option<H>;
222    type IntoIter = std::slice::Iter<'a, Option<H>>;
223
224    fn into_iter(self) -> Self::IntoIter {
225        self.as_handle_slice().iter()
226    }
227}
228
229impl<'a, H: 'a> IntoIterator for &'_ HandleOptionSlice<'a, H> {
230    type Item = &'a Option<H>;
231    type IntoIter = std::slice::Iter<'a, Option<H>>;
232
233    fn into_iter(self) -> Self::IntoIter {
234        self.as_handle_slice().iter()
235    }
236}
237
238/// Read view over the semantic map of a geometry.
239///
240/// Exposes semantic handle assignments per primitive level:
241/// `points()`, `linestrings()`, and `surfaces()`. Each returns a [`HandleOptionSlice`]
242/// with one optional [`SemanticHandle`] per primitive. `None` means no semantic is assigned
243/// to that primitive.
244#[derive(Clone, Copy, Debug)]
245pub struct SemanticMapView<'a, VR: VertexRef> {
246    inner: &'a SemanticOrMaterialMap<VR, ResourceId32>,
247}
248
249impl<'a, VR: VertexRef> SemanticMapView<'a, VR> {
250    #[allow(clippy::trivially_copy_pass_by_ref)]
251    #[must_use]
252    pub fn points(&self) -> HandleOptionSlice<'a, SemanticHandle> {
253        HandleOptionSlice::new(self.inner.points())
254    }
255
256    #[allow(clippy::trivially_copy_pass_by_ref)]
257    #[must_use]
258    pub fn linestrings(&self) -> HandleOptionSlice<'a, SemanticHandle> {
259        HandleOptionSlice::new(self.inner.linestrings())
260    }
261
262    #[allow(clippy::trivially_copy_pass_by_ref)]
263    #[must_use]
264    pub fn surfaces(&self) -> HandleOptionSlice<'a, SemanticHandle> {
265        HandleOptionSlice::new(self.inner.surfaces())
266    }
267}
268
269/// Read view over a material map for one theme.
270///
271/// Same structure as [`SemanticMapView`] but for material handles.
272#[derive(Clone, Copy, Debug)]
273pub struct MaterialMapView<'a, VR: VertexRef> {
274    inner: &'a SemanticOrMaterialMap<VR, ResourceId32>,
275}
276
277impl<'a, VR: VertexRef> MaterialMapView<'a, VR> {
278    #[allow(clippy::trivially_copy_pass_by_ref)]
279    #[must_use]
280    pub fn points(&self) -> HandleOptionSlice<'a, MaterialHandle> {
281        HandleOptionSlice::new(self.inner.points())
282    }
283
284    #[allow(clippy::trivially_copy_pass_by_ref)]
285    #[must_use]
286    pub fn linestrings(&self) -> HandleOptionSlice<'a, MaterialHandle> {
287        HandleOptionSlice::new(self.inner.linestrings())
288    }
289
290    #[allow(clippy::trivially_copy_pass_by_ref)]
291    #[must_use]
292    pub fn surfaces(&self) -> HandleOptionSlice<'a, MaterialHandle> {
293        HandleOptionSlice::new(self.inner.surfaces())
294    }
295}
296
297/// Read view over all material themes for a geometry.
298///
299/// In `CityJSON`, material assignments are grouped by theme name. Iterate with
300/// [`MaterialThemesView::iter`] to get `(theme_name, MaterialMapView)` pairs.
301#[derive(Clone, Copy, Debug)]
302pub struct MaterialThemesView<'a, VR: VertexRef, SS: StringStorage> {
303    items: &'a [(ThemeName<SS>, SemanticOrMaterialMap<VR, ResourceId32>)],
304}
305
306impl<'a, VR: VertexRef, SS: StringStorage> MaterialThemesView<'a, VR, SS> {
307    #[must_use]
308    pub fn len(&self) -> usize {
309        self.items.len()
310    }
311
312    #[must_use]
313    pub fn is_empty(&self) -> bool {
314        self.items.is_empty()
315    }
316
317    pub fn iter(&self) -> impl Iterator<Item = (&'a ThemeName<SS>, MaterialMapView<'a, VR>)> + 'a {
318        self.items
319            .iter()
320            .map(|(theme, map)| (theme, MaterialMapView { inner: map }))
321    }
322
323    #[must_use]
324    pub fn first(&self) -> Option<(&'a ThemeName<SS>, MaterialMapView<'a, VR>)> {
325        self.items
326            .first()
327            .map(|(theme, map)| (theme, MaterialMapView { inner: map }))
328    }
329}
330
331/// Read view over the texture map for one theme.
332///
333/// Texture assignments in `CityJSON` associate UV coordinates (`vertices-texture`) with rings.
334/// `vertices()` returns the UV index per geometry vertex (`None` = not textured),
335/// `rings()` is the offset array, and `ring_textures()` gives the texture handle per ring.
336#[derive(Clone, Copy, Debug)]
337pub struct TextureMapView<'a, VR: VertexRef> {
338    inner: &'a TextureMapCore<VR, ResourceId32>,
339}
340
341impl<'a, VR: VertexRef> TextureMapView<'a, VR> {
342    #[allow(clippy::trivially_copy_pass_by_ref)]
343    #[must_use]
344    pub fn vertices(&self) -> &'a [Option<VertexIndex<VR>>] {
345        self.inner.vertices()
346    }
347
348    #[allow(clippy::trivially_copy_pass_by_ref)]
349    #[must_use]
350    pub fn rings(&self) -> &'a [VertexIndex<VR>] {
351        self.inner.rings()
352    }
353
354    #[allow(clippy::trivially_copy_pass_by_ref)]
355    #[must_use]
356    pub fn ring_textures(&self) -> HandleOptionSlice<'a, TextureHandle> {
357        HandleOptionSlice::new(self.inner.ring_textures())
358    }
359}
360
361#[derive(Clone, Copy, Debug)]
362pub struct TextureThemesView<'a, VR: VertexRef, SS: StringStorage> {
363    items: &'a [(ThemeName<SS>, TextureMapCore<VR, ResourceId32>)],
364}
365
366impl<'a, VR: VertexRef, SS: StringStorage> TextureThemesView<'a, VR, SS> {
367    #[must_use]
368    pub fn len(&self) -> usize {
369        self.items.len()
370    }
371
372    #[must_use]
373    pub fn is_empty(&self) -> bool {
374        self.items.is_empty()
375    }
376
377    pub fn iter(&self) -> impl Iterator<Item = (&'a ThemeName<SS>, TextureMapView<'a, VR>)> + 'a {
378        self.items
379            .iter()
380            .map(|(theme, map)| (theme, TextureMapView { inner: map }))
381    }
382
383    #[must_use]
384    pub fn first(&self) -> Option<(&'a ThemeName<SS>, TextureMapView<'a, VR>)> {
385        self.items
386            .first()
387            .map(|(theme, map)| (theme, TextureMapView { inner: map }))
388    }
389}
390
391impl<VR: VertexRef, SS: StringStorage> Geometry<VR, SS> {
392    #[must_use]
393    pub fn from_stored_parts(parts: StoredGeometryParts<VR, SS>) -> Self {
394        let semantics = parts.semantics.map(SemanticMap::into_raw);
395        let materials = parts.materials.map(|items| {
396            items
397                .into_iter()
398                .map(|(theme, map)| (theme, map.into_raw()))
399                .collect()
400        });
401        let textures = parts.textures.map(|items| {
402            items
403                .into_iter()
404                .map(|(theme, map)| (theme, map.into_raw()))
405                .collect()
406        });
407        let instance = parts.instance.map(|instance| {
408            GeometryInstanceData::new(
409                instance.template.to_raw(),
410                instance.reference_point,
411                instance.transformation,
412            )
413        });
414
415        Self::from_raw_parts(
416            parts.type_geometry,
417            parts.lod,
418            parts.boundaries,
419            semantics,
420            materials,
421            textures,
422            instance,
423        )
424    }
425
426    pub(crate) fn from_raw_parts(
427        type_geometry: GeometryType,
428        lod: Option<LoD>,
429        boundaries: Option<Boundary<VR>>,
430        semantics: Option<SemanticOrMaterialMap<VR, ResourceId32>>,
431        materials: Option<ThemedMaterials<VR, ResourceId32, SS>>,
432        textures: Option<ThemedTextures<VR, ResourceId32, SS>>,
433        instance: Option<GeometryInstanceData<VR, ResourceId32>>,
434    ) -> Self {
435        Self {
436            inner: GeometryCore::new(
437                type_geometry,
438                lod,
439                boundaries,
440                semantics,
441                materials,
442                textures,
443                instance,
444            ),
445        }
446    }
447
448    pub(crate) fn raw(&self) -> &GeometryCore<VR, ResourceId32, SS> {
449        &self.inner
450    }
451
452    pub fn type_geometry(&self) -> &GeometryType {
453        self.inner.type_geometry()
454    }
455
456    pub fn lod(&self) -> Option<&LoD> {
457        self.inner.lod()
458    }
459
460    pub fn boundaries(&self) -> Option<&Boundary<VR>> {
461        self.inner.boundaries()
462    }
463
464    #[must_use]
465    pub fn coordinates<'a, V: Coordinate>(
466        &'a self,
467        vertices: &'a Vertices<VR, V>,
468    ) -> Option<BoundaryCoordinates<'a, VR, V>> {
469        self.boundaries()
470            .map(|boundary| boundary.coordinates(vertices))
471    }
472
473    pub fn unique_vertex_indices<'a>(
474        &'a self,
475        scratch: &'a mut Vec<VertexIndex<VR>>,
476    ) -> Option<&'a [VertexIndex<VR>]> {
477        self.boundaries()
478            .map(|boundary| boundary.unique_vertex_indices(scratch))
479    }
480
481    #[must_use]
482    pub fn unique_coordinates<'a, V: Coordinate>(
483        &'a self,
484        vertices: &'a Vertices<VR, V>,
485        scratch: &'a mut Vec<VertexIndex<VR>>,
486    ) -> Option<BoundaryUniqueCoordinates<'a, VR, V>> {
487        self.boundaries()
488            .map(|boundary| boundary.unique_coordinates(vertices, scratch))
489    }
490
491    pub fn semantics(&self) -> Option<SemanticMapView<'_, VR>> {
492        self.inner
493            .semantics()
494            .map(|inner| SemanticMapView { inner })
495    }
496
497    pub fn materials(&self) -> Option<MaterialThemesView<'_, VR, SS>> {
498        self.inner
499            .materials()
500            .map(|items| MaterialThemesView { items })
501    }
502
503    pub fn textures(&self) -> Option<TextureThemesView<'_, VR, SS>> {
504        self.inner
505            .textures()
506            .map(|items| TextureThemesView { items })
507    }
508
509    pub fn instance(&self) -> Option<GeometryInstanceView<'_, VR>> {
510        self.inner
511            .instance()
512            .map(|inner| GeometryInstanceView { inner })
513    }
514}
515
516impl<'a, VR: VertexRef, SS: StringStorage> GeometryView<'a, VR, SS> {
517    pub(crate) fn from_geometry(
518        geometry: &'a Geometry<VR, SS>,
519        instance: Option<GeometryInstanceView<'a, VR>>,
520    ) -> Self {
521        Self { geometry, instance }
522    }
523
524    #[must_use]
525    pub fn geometry(&self) -> &'a Geometry<VR, SS> {
526        self.geometry
527    }
528
529    #[must_use]
530    pub fn instance(&self) -> Option<GeometryInstanceView<'a, VR>> {
531        self.instance
532    }
533}
534
535impl<VR: VertexRef, SS: StringStorage> Deref for GeometryView<'_, VR, SS> {
536    type Target = Geometry<VR, SS>;
537
538    fn deref(&self) -> &Self::Target {
539        self.geometry
540    }
541}