Skip to main content

cityjson/v2_0/
citymodel.rs

1//! The root `CityJSON` object.
2//!
3//! [`CityModel`] holds everything in a `CityJSON` document: city objects, vertices, geometries,
4//! and the shared resource pools for semantics, materials, and textures.
5//!
6//! ## City objects and geometry
7//!
8//! City objects are stored in [`CityObjects`] and reference geometries by [`GeometryHandle`].
9//! Add a city object with [`CityModel::cityobjects_mut`], then attach geometries built with
10//! [`GeometryDraft`](super::geometry_draft::GeometryDraft).
11//!
12//! ```rust
13//! use cityjson::CityModelType;
14//! use cityjson::v2_0::{
15//!     CityObject, CityObjectIdentifier, CityObjectType, GeometryDraft, OwnedCityModel, PointDraft,
16//!     RealWorldCoordinate,
17//! };
18//!
19//! let mut model = OwnedCityModel::new(CityModelType::CityJSON);
20//!
21//! // Add a city object.
22//! let mut tree = CityObject::new(
23//!     CityObjectIdentifier::new("tree-1".to_string()),
24//!     CityObjectType::SolitaryVegetationObject,
25//! );
26//!
27//! // Build and attach a geometry.
28//! let geom = GeometryDraft::multi_point(
29//!     None,
30//!     [PointDraft::new(RealWorldCoordinate::new(84710.0, 446900.0, 5.0))],
31//! )
32//! .insert_into(&mut model)
33//! .unwrap();
34//!
35//! tree.add_geometry(geom);
36//! model.cityobjects_mut().add(tree).unwrap();
37//! ```
38//!
39//! ## Resource pools
40//!
41//! Semantics, materials, and textures are stored once in the model and referenced by handle.
42//! Use [`CityModel::add_semantic`], [`CityModel::add_material`], and [`CityModel::add_texture`]
43//! to register resources; use [`CityModel::get_or_insert_semantic`] etc. to deduplicate.
44//!
45//! ## Generics
46//!
47//! `CityModel<VR, SS>` is generic over the vertex index type (`VR: VertexRef`, e.g. `u32`) and
48//! string storage (`SS: StringStorage`). `OwnedCityModel` and `BorrowedCityModel` are the
49//! common aliases.
50use crate::backend::default::citymodel::{CityModelCore, CityModelCoreCapacities};
51use crate::backend::default::geometry_validation::{
52    BoundaryVertexSource, GeometryValidationContext, validate_stored_geometry,
53    validate_stored_geometry_for_boundary_source,
54};
55use crate::cityjson::core::appearance::ThemeName;
56use crate::error::Error;
57use crate::error::Result;
58use crate::raw::{RawAccess, RawPoolView, RawSliceView};
59use crate::resources::handles::{
60    CityObjectHandle, GeometryHandle, GeometryTemplateHandle, MaterialHandle, SemanticHandle,
61    TextureHandle,
62};
63use crate::resources::id::ResourceId32;
64use crate::resources::storage::{BorrowedStringStorage, OwnedStringStorage, StringStorage};
65use crate::v2_0::CityObjects;
66use crate::v2_0::appearance::material::Material;
67use crate::v2_0::appearance::texture::Texture;
68use crate::v2_0::attributes::AttributeValue;
69use crate::v2_0::coordinate::{RealWorldCoordinate, UVCoordinate};
70use crate::v2_0::extension::Extensions;
71use crate::v2_0::geometry::GeometryView;
72use crate::v2_0::geometry::semantic::Semantic;
73use crate::v2_0::geometry::{Geometry, GeometryType};
74use crate::v2_0::metadata::Metadata;
75use crate::v2_0::transform::Transform;
76use crate::v2_0::vertex::{VertexIndex, VertexRef};
77use crate::v2_0::vertices::Vertices;
78use crate::{CityJSONVersion, format_option};
79use std::collections::HashSet;
80use std::fmt;
81
82/// `CityModel` with owned string storage and 32-bit vertex indices.
83pub type OwnedCityModel = CityModel<u32, OwnedStringStorage>;
84
85/// `CityModel` with borrowed string storage and 32-bit vertex indices.
86pub type BorrowedCityModel<'a> = CityModel<u32, BorrowedStringStorage<'a>>;
87
88/// Pre-allocation hints for [`CityModel::with_capacities`].
89///
90/// All fields are optional — set only what you know in advance. Unused fields default to zero.
91#[derive(Debug, Clone, Copy, Default)]
92pub struct CityModelCapacities {
93    pub cityobjects: usize,
94    pub vertices: usize,
95    pub semantics: usize,
96    pub materials: usize,
97    pub textures: usize,
98    pub geometries: usize,
99    pub template_vertices: usize,
100    pub template_geometries: usize,
101    pub uv_coordinates: usize,
102}
103
104impl From<CityModelCapacities> for CityModelCoreCapacities {
105    fn from(value: CityModelCapacities) -> Self {
106        Self {
107            cityobjects: value.cityobjects,
108            vertices: value.vertices,
109            semantics: value.semantics,
110            materials: value.materials,
111            textures: value.textures,
112            geometries: value.geometries,
113            template_vertices: value.template_vertices,
114            template_geometries: value.template_geometries,
115            uv_coordinates: value.uv_coordinates,
116        }
117    }
118}
119
120/// The root `CityJSON` object.
121///
122/// Holds all vertices, city objects, and shared resource pools. See the [module docs](self)
123/// for usage examples.
124#[derive(Debug, Clone)]
125pub struct CityModel<VR: VertexRef = u32, SS: StringStorage = OwnedStringStorage> {
126    #[allow(clippy::type_complexity)]
127    inner: CityModelCore<
128        VR,
129        ResourceId32,
130        SS,
131        Semantic<SS>,
132        Material<SS>,
133        Texture<SS>,
134        Geometry<VR, SS>,
135        Metadata<SS>,
136        Transform,
137        Extensions<SS>,
138        CityObjects<SS>,
139    >,
140}
141
142impl<VR: VertexRef, SS: StringStorage> CityModel<VR, SS> {
143    #[must_use]
144    pub fn new(type_citymodel: crate::CityModelType) -> Self {
145        Self {
146            inner: CityModelCore::new(type_citymodel, Some(CityJSONVersion::V2_0)),
147        }
148    }
149
150    #[must_use]
151    pub fn with_capacities(
152        type_citymodel: crate::CityModelType,
153        capacities: CityModelCapacities,
154    ) -> Self {
155        Self {
156            inner: CityModelCore::with_capacities(
157                type_citymodel,
158                Some(CityJSONVersion::V2_0),
159                capacities.into(),
160                CityObjects::with_capacity,
161            ),
162        }
163    }
164
165    pub fn get_semantic(&self, id: SemanticHandle) -> Option<&Semantic<SS>> {
166        self.inner.get_semantic(id.to_raw())
167    }
168
169    pub fn get_semantic_mut(&mut self, id: SemanticHandle) -> Option<&mut Semantic<SS>> {
170        self.inner.get_semantic_mut(id.to_raw())
171    }
172
173    /// Add a semantic and return its handle.
174    ///
175    /// # Errors
176    ///
177    /// Returns [`crate::error::Error::ResourcePoolFull`] when the semantic pool cannot store
178    /// additional entries for `ResourceId32`.
179    pub fn add_semantic(&mut self, semantic: Semantic<SS>) -> Result<SemanticHandle> {
180        self.inner
181            .add_semantic(semantic)
182            .map(SemanticHandle::from_raw)
183    }
184
185    pub fn semantic_count(&self) -> usize {
186        self.inner.semantic_count()
187    }
188
189    pub fn has_semantics(&self) -> bool {
190        self.inner.has_semantics()
191    }
192
193    pub fn iter_semantics(&self) -> impl Iterator<Item = (SemanticHandle, &Semantic<SS>)> + '_ {
194        self.inner
195            .iter_semantics()
196            .map(|(id, v)| (SemanticHandle::from_raw(id), v))
197    }
198
199    pub fn iter_semantics_mut(
200        &mut self,
201    ) -> impl Iterator<Item = (SemanticHandle, &mut Semantic<SS>)> + '_ {
202        self.inner
203            .iter_semantics_mut()
204            .map(|(id, v)| (SemanticHandle::from_raw(id), v))
205    }
206
207    pub fn find_semantic(&self, semantic: &Semantic<SS>) -> Option<SemanticHandle>
208    where
209        Semantic<SS>: PartialEq,
210    {
211        self.inner
212            .find_semantic(semantic)
213            .map(SemanticHandle::from_raw)
214    }
215
216    #[cfg(test)]
217    pub(crate) fn remove_semantic(&mut self, id: SemanticHandle) -> Option<Semantic<SS>> {
218        self.inner.remove_semantic(id.to_raw())
219    }
220
221    /// Return an existing semantic handle or insert a new semantic.
222    ///
223    /// # Errors
224    ///
225    /// Returns [`crate::error::Error::ResourcePoolFull`] when inserting a new semantic exceeds
226    /// the semantic pool capacity.
227    pub fn get_or_insert_semantic(&mut self, semantic: Semantic<SS>) -> Result<SemanticHandle>
228    where
229        Semantic<SS>: PartialEq,
230    {
231        self.inner
232            .get_or_insert_semantic(semantic)
233            .map(SemanticHandle::from_raw)
234    }
235
236    pub fn get_material(&self, id: MaterialHandle) -> Option<&Material<SS>> {
237        self.inner.get_material(id.to_raw())
238    }
239
240    pub fn get_material_mut(&mut self, id: MaterialHandle) -> Option<&mut Material<SS>> {
241        self.inner.get_material_mut(id.to_raw())
242    }
243
244    /// Add a material and return its handle.
245    ///
246    /// # Errors
247    ///
248    /// Returns [`crate::error::Error::ResourcePoolFull`] when the material pool cannot store
249    /// additional entries for `ResourceId32`.
250    pub fn add_material(&mut self, material: Material<SS>) -> Result<MaterialHandle> {
251        self.inner
252            .add_material(material)
253            .map(MaterialHandle::from_raw)
254    }
255
256    pub fn material_count(&self) -> usize {
257        self.inner.material_count()
258    }
259
260    pub fn iter_materials(&self) -> impl Iterator<Item = (MaterialHandle, &Material<SS>)> + '_ {
261        self.inner
262            .iter_materials()
263            .map(|(id, v)| (MaterialHandle::from_raw(id), v))
264    }
265
266    pub fn iter_materials_mut(
267        &mut self,
268    ) -> impl Iterator<Item = (MaterialHandle, &mut Material<SS>)> + '_ {
269        self.inner
270            .iter_materials_mut()
271            .map(|(id, v)| (MaterialHandle::from_raw(id), v))
272    }
273
274    pub fn find_material(&self, material: &Material<SS>) -> Option<MaterialHandle>
275    where
276        Material<SS>: PartialEq,
277    {
278        self.inner
279            .find_material(material)
280            .map(MaterialHandle::from_raw)
281    }
282
283    #[cfg(test)]
284    pub(crate) fn remove_material(&mut self, id: MaterialHandle) -> Option<Material<SS>> {
285        self.inner.remove_material(id.to_raw())
286    }
287
288    /// Return an existing material handle or insert a new material.
289    ///
290    /// # Errors
291    ///
292    /// Returns [`crate::error::Error::ResourcePoolFull`] when inserting a new material exceeds
293    /// the material pool capacity.
294    pub fn get_or_insert_material(&mut self, material: Material<SS>) -> Result<MaterialHandle>
295    where
296        Material<SS>: PartialEq,
297    {
298        self.inner
299            .get_or_insert_material(material)
300            .map(MaterialHandle::from_raw)
301    }
302
303    pub fn get_texture(&self, id: TextureHandle) -> Option<&Texture<SS>> {
304        self.inner.get_texture(id.to_raw())
305    }
306
307    pub fn get_texture_mut(&mut self, id: TextureHandle) -> Option<&mut Texture<SS>> {
308        self.inner.get_texture_mut(id.to_raw())
309    }
310
311    /// Add a texture and return its handle.
312    ///
313    /// # Errors
314    ///
315    /// Returns [`crate::error::Error::ResourcePoolFull`] when the texture pool cannot store
316    /// additional entries for `ResourceId32`.
317    pub fn add_texture(&mut self, texture: Texture<SS>) -> Result<TextureHandle> {
318        self.inner.add_texture(texture).map(TextureHandle::from_raw)
319    }
320
321    pub fn texture_count(&self) -> usize {
322        self.inner.texture_count()
323    }
324
325    pub fn iter_textures(&self) -> impl Iterator<Item = (TextureHandle, &Texture<SS>)> + '_ {
326        self.inner
327            .iter_textures()
328            .map(|(id, v)| (TextureHandle::from_raw(id), v))
329    }
330
331    pub fn iter_textures_mut(
332        &mut self,
333    ) -> impl Iterator<Item = (TextureHandle, &mut Texture<SS>)> + '_ {
334        self.inner
335            .iter_textures_mut()
336            .map(|(id, v)| (TextureHandle::from_raw(id), v))
337    }
338
339    pub fn find_texture(&self, texture: &Texture<SS>) -> Option<TextureHandle>
340    where
341        Texture<SS>: PartialEq,
342    {
343        self.inner
344            .find_texture(texture)
345            .map(TextureHandle::from_raw)
346    }
347
348    #[cfg(test)]
349    pub(crate) fn remove_texture(&mut self, id: TextureHandle) -> Option<Texture<SS>> {
350        self.inner.remove_texture(id.to_raw())
351    }
352
353    /// Return an existing texture handle or insert a new texture.
354    ///
355    /// # Errors
356    ///
357    /// Returns [`crate::error::Error::ResourcePoolFull`] when inserting a new texture exceeds
358    /// the texture pool capacity.
359    pub fn get_or_insert_texture(&mut self, texture: Texture<SS>) -> Result<TextureHandle>
360    where
361        Texture<SS>: PartialEq,
362    {
363        self.inner
364            .get_or_insert_texture(texture)
365            .map(TextureHandle::from_raw)
366    }
367
368    pub fn get_geometry(&self, id: GeometryHandle) -> Option<&Geometry<VR, SS>> {
369        self.inner.get_geometry(id.to_raw())
370    }
371
372    /// Resolve a geometry handle to the effective geometry view.
373    ///
374    /// # Errors
375    ///
376    /// Returns an error when the geometry handle is invalid or when a
377    /// `GeometryInstance` references a missing geometry template.
378    pub fn resolve_geometry(&self, id: GeometryHandle) -> Result<GeometryView<'_, VR, SS>> {
379        let geometry =
380            self.get_geometry(id)
381                .ok_or_else(|| crate::error::Error::InvalidReference {
382                    element_type: "geometry".to_string(),
383                    index: id.to_raw().index() as usize,
384                    max_index: self.geometry_count().saturating_sub(1),
385                })?;
386
387        if let Some(instance) = geometry.instance() {
388            let template = self
389                .get_geometry_template(instance.template())
390                .ok_or_else(|| crate::error::Error::InvalidReference {
391                    element_type: "template geometry".to_string(),
392                    index: instance.template().to_raw().index() as usize,
393                    max_index: self.geometry_template_count().saturating_sub(1),
394                })?;
395            Ok(GeometryView::from_geometry(template, Some(instance)))
396        } else {
397            Ok(GeometryView::from_geometry(geometry, None))
398        }
399    }
400
401    /// Add a geometry and return its handle.
402    ///
403    /// # Errors
404    ///
405    /// Returns [`crate::error::Error::InvalidGeometry`] when `geometry` violates the stored
406    /// geometry invariants, or [`crate::error::Error::ResourcePoolFull`] when the geometry pool
407    /// cannot store additional entries for `ResourceId32`.
408    pub fn add_geometry(&mut self, geometry: Geometry<VR, SS>) -> Result<GeometryHandle> {
409        validate_stored_geometry(geometry.raw(), self)?;
410        self.add_geometry_unchecked(geometry)
411    }
412
413    /// Add a geometry without validating stored-geometry invariants.
414    ///
415    /// Callers must ensure that `geometry` satisfies the same invariants currently enforced by
416    /// [`CityModel::add_geometry`]. In particular, boundary vertex indices must reference the
417    /// correct vertex pool and the geometry must be valid for regular geometry insertion.
418    ///
419    /// # Errors
420    ///
421    /// Returns [`crate::error::Error::ResourcePoolFull`] when the geometry pool cannot store
422    /// additional entries for `ResourceId32`.
423    pub fn add_geometry_unchecked(&mut self, geometry: Geometry<VR, SS>) -> Result<GeometryHandle> {
424        self.inner
425            .add_geometry(geometry)
426            .map(GeometryHandle::from_raw)
427    }
428
429    pub fn geometry_count(&self) -> usize {
430        self.inner.geometry_count()
431    }
432
433    pub fn iter_geometries(
434        &self,
435    ) -> impl Iterator<Item = (GeometryHandle, &Geometry<VR, SS>)> + '_ {
436        self.inner
437            .iter_geometries()
438            .map(|(id, v)| (GeometryHandle::from_raw(id), v))
439    }
440
441    pub fn vertices(&self) -> &Vertices<VR, RealWorldCoordinate> {
442        self.inner.vertices()
443    }
444
445    /// Add a vertex and return its index.
446    ///
447    /// # Errors
448    ///
449    /// Returns [`crate::error::Error::VerticesContainerFull`] when the vertex
450    /// container cannot represent more vertices for `VR`.
451    pub fn add_vertex(
452        &mut self,
453        coordinate: RealWorldCoordinate,
454    ) -> crate::error::Result<VertexIndex<VR>> {
455        self.inner.add_vertex(coordinate)
456    }
457
458    /// Add many vertices and return the contiguous index range assigned to them.
459    ///
460    /// The returned range is half-open: `start` is the first inserted vertex index and `end` is
461    /// one past the final inserted vertex.
462    ///
463    /// # Errors
464    ///
465    /// Returns [`crate::error::Error::VerticesContainerFull`] when the vertex
466    /// container cannot represent more vertices for `VR`.
467    pub fn add_vertices(
468        &mut self,
469        coordinates: &[RealWorldCoordinate],
470    ) -> crate::error::Result<std::ops::Range<VertexIndex<VR>>> {
471        self.inner.add_vertices(coordinates)
472    }
473
474    pub fn get_vertex(&self, index: VertexIndex<VR>) -> Option<&RealWorldCoordinate> {
475        self.inner.get_vertex(index)
476    }
477
478    pub fn metadata(&self) -> Option<&Metadata<SS>> {
479        self.inner.metadata()
480    }
481
482    pub fn metadata_mut(&mut self) -> &mut Metadata<SS> {
483        self.inner.metadata_mut()
484    }
485
486    /// Typed feature root id.
487    ///
488    /// This is semantically meaningful for `CityJSONFeature`, where it identifies the
489    /// main feature `CityObject`. For plain `CityJSON`, root `id` remains an ordinary
490    /// extra property and this accessor returns `None`.
491    pub fn id(&self) -> Option<CityObjectHandle> {
492        self.inner.id().map(CityObjectHandle::from_raw)
493    }
494
495    pub fn set_id(&mut self, id: Option<CityObjectHandle>) {
496        self.inner.set_id(id.map(CityObjectHandle::to_raw));
497    }
498
499    pub fn extra(&self) -> Option<&crate::v2_0::attributes::Attributes<SS>> {
500        self.inner.extra()
501    }
502
503    pub fn extra_mut(&mut self) -> &mut crate::v2_0::attributes::Attributes<SS> {
504        self.inner.extra_mut()
505    }
506
507    pub fn transform(&self) -> Option<&Transform> {
508        self.inner.transform()
509    }
510
511    pub fn transform_mut(&mut self) -> &mut Transform {
512        self.inner.transform_mut()
513    }
514
515    pub fn extensions(&self) -> Option<&Extensions<SS>> {
516        self.inner.extensions()
517    }
518
519    pub fn extensions_mut(&mut self) -> &mut Extensions<SS> {
520        self.inner.extensions_mut()
521    }
522
523    pub fn cityobjects(&self) -> &CityObjects<SS> {
524        self.inner.cityobjects()
525    }
526
527    /// Returns a raw accessor for zero-copy reads of internal model pools.
528    #[inline]
529    pub fn raw(&self) -> crate::raw::v2_0::CityModelRawAccessor<'_, VR, SS> {
530        crate::raw::v2_0::CityModelRawAccessor::new(self)
531    }
532
533    /// Returns mutable access to the city object collection.
534    ///
535    /// This remains public because object authoring still happens through live
536    /// mutation. It can still create dangling geometry or object references; the
537    /// stricter guarantees in this module apply to stored geometry insertion,
538    /// not to arbitrary city object graph edits.
539    pub fn cityobjects_mut(&mut self) -> &mut CityObjects<SS> {
540        self.inner.cityobjects_mut()
541    }
542
543    pub fn clear_cityobjects(&mut self) {
544        self.inner.cityobjects_mut().clear();
545    }
546
547    /// Add a UV coordinate and return its vertex index.
548    ///
549    /// # Errors
550    ///
551    /// Returns [`crate::error::Error::VerticesContainerFull`] when the UV-coordinate container
552    /// cannot represent more vertices for `VR`.
553    pub fn add_uv_coordinate(
554        &mut self,
555        uvcoordinate: UVCoordinate,
556    ) -> crate::error::Result<VertexIndex<VR>> {
557        self.inner.add_uv_coordinate(uvcoordinate)
558    }
559
560    pub fn get_uv_coordinate(&self, index: VertexIndex<VR>) -> Option<&UVCoordinate> {
561        self.inner.get_uv_coordinate(index)
562    }
563
564    pub fn vertices_texture(&self) -> &Vertices<VR, UVCoordinate> {
565        self.inner.vertices_texture()
566    }
567
568    /// Add a template vertex and return its index.
569    ///
570    /// # Errors
571    ///
572    /// Returns [`crate::error::Error::VerticesContainerFull`] when the template-vertex
573    /// container cannot represent more vertices for `VR`.
574    pub fn add_template_vertex(
575        &mut self,
576        coordinate: RealWorldCoordinate,
577    ) -> crate::error::Result<VertexIndex<VR>> {
578        self.inner.add_template_vertex(coordinate)
579    }
580
581    pub fn get_template_vertex(&self, index: VertexIndex<VR>) -> Option<&RealWorldCoordinate> {
582        self.inner.get_template_vertex(index)
583    }
584
585    pub fn template_vertices(&self) -> &Vertices<VR, RealWorldCoordinate> {
586        self.inner.template_vertices()
587    }
588
589    pub fn get_geometry_template(&self, id: GeometryTemplateHandle) -> Option<&Geometry<VR, SS>> {
590        self.inner.get_template_geometry(id.to_raw())
591    }
592
593    /// Add a template geometry and return its handle.
594    ///
595    /// # Errors
596    ///
597    /// Returns [`crate::error::Error::InvalidGeometry`] when `geometry` violates the stored
598    /// template-geometry invariants, or [`crate::error::Error::ResourcePoolFull`] when the
599    /// template-geometry pool cannot store additional entries for `ResourceId32`.
600    pub fn add_geometry_template(
601        &mut self,
602        geometry: Geometry<VR, SS>,
603    ) -> Result<GeometryTemplateHandle> {
604        if *geometry.type_geometry() == GeometryType::GeometryInstance {
605            return Err(crate::error::Error::InvalidGeometry(
606                "GeometryInstance cannot be inserted into the template geometry pool".to_string(),
607            ));
608        }
609
610        validate_stored_geometry_for_boundary_source(
611            geometry.raw(),
612            self,
613            BoundaryVertexSource::Template,
614        )?;
615        self.add_geometry_template_unchecked(geometry)
616    }
617
618    /// Add a template geometry without validating stored-geometry invariants.
619    ///
620    /// Callers must ensure that `geometry` satisfies the same invariants currently enforced by
621    /// [`CityModel::add_geometry_template`], and that it is not a `GeometryInstance`.
622    /// Boundary vertex indices must reference the template vertex pool.
623    ///
624    /// # Errors
625    ///
626    /// Returns [`crate::error::Error::ResourcePoolFull`] when the template-geometry pool cannot
627    /// store additional entries for `ResourceId32`.
628    pub fn add_geometry_template_unchecked(
629        &mut self,
630        geometry: Geometry<VR, SS>,
631    ) -> Result<GeometryTemplateHandle> {
632        self.inner
633            .add_template_geometry(geometry)
634            .map(GeometryTemplateHandle::from_raw)
635    }
636
637    pub fn geometry_template_count(&self) -> usize {
638        self.inner.template_geometry_count()
639    }
640
641    pub fn iter_geometry_templates(
642        &self,
643    ) -> impl Iterator<Item = (GeometryTemplateHandle, &Geometry<VR, SS>)> + '_ {
644        self.inner
645            .iter_template_geometries()
646            .map(|(id, v)| (GeometryTemplateHandle::from_raw(id), v))
647    }
648
649    pub fn type_citymodel(&self) -> crate::CityModelType {
650        self.inner.type_citymodel()
651    }
652
653    pub fn version(&self) -> Option<crate::CityJSONVersion> {
654        self.inner.version()
655    }
656
657    /// Reserve capacity for bulk import workloads.
658    ///
659    /// This reserves all pools that can be expanded after construction. `cityobjects` uses the
660    /// same runtime reserve path as the other pools, while the remaining fields reserve vertices,
661    /// geometries, templates, and appearance resources.
662    ///
663    /// # Errors
664    ///
665    /// Returns [`crate::error::Error::ResourcePoolFull`] when any pool would exceed the
666    /// representable size for `ResourceId32`.
667    pub fn reserve_import(&mut self, capacities: CityModelCapacities) -> Result<()> {
668        self.cityobjects_mut().reserve(capacities.cityobjects)?;
669        self.inner.reserve_vertex_capacity(capacities.vertices)?;
670        self.inner
671            .reserve_geometry_capacity(capacities.geometries)?;
672        self.inner
673            .reserve_template_vertex_capacity(capacities.template_vertices)?;
674        self.inner
675            .reserve_template_geometry_capacity(capacities.template_geometries)?;
676        self.inner.reserve_semantic_capacity(capacities.semantics)?;
677        self.inner.reserve_material_capacity(capacities.materials)?;
678        self.inner.reserve_texture_capacity(capacities.textures)?;
679        self.inner.reserve_uv_capacity(capacities.uv_coordinates)
680    }
681
682    pub(crate) fn reserve_draft_insert(
683        &mut self,
684        mode: super::geometry_draft::DraftInsertMode,
685        new_vertices: usize,
686        new_uvs: usize,
687    ) -> Result<()> {
688        let capacities = match mode {
689            super::geometry_draft::DraftInsertMode::Regular => CityModelCapacities {
690                geometries: 1,
691                vertices: new_vertices,
692                uv_coordinates: new_uvs,
693                ..CityModelCapacities::default()
694            },
695            super::geometry_draft::DraftInsertMode::Template => CityModelCapacities {
696                template_geometries: 1,
697                template_vertices: new_vertices,
698                uv_coordinates: new_uvs,
699                ..CityModelCapacities::default()
700            },
701        };
702        self.reserve_import(capacities)
703    }
704
705    pub fn default_material_theme(&self) -> Option<&ThemeName<SS>> {
706        self.inner.default_material_theme()
707    }
708
709    pub fn set_default_material_theme(&mut self, theme: Option<ThemeName<SS>>) {
710        self.inner.set_default_material_theme(theme);
711    }
712
713    pub fn default_texture_theme(&self) -> Option<&ThemeName<SS>> {
714        self.inner.default_texture_theme()
715    }
716
717    pub fn set_default_texture_theme(&mut self, theme: Option<ThemeName<SS>>) {
718        self.inner.set_default_texture_theme(theme);
719    }
720
721    pub fn has_material_theme(&self, theme: &str) -> bool {
722        self.iter_geometries()
723            .any(|(_, geometry)| Self::geometry_has_material_theme(geometry, theme))
724            || self
725                .iter_geometry_templates()
726                .any(|(_, geometry)| Self::geometry_has_material_theme(geometry, theme))
727    }
728
729    pub fn has_texture_theme(&self, theme: &str) -> bool {
730        self.iter_geometries()
731            .any(|(_, geometry)| Self::geometry_has_texture_theme(geometry, theme))
732            || self
733                .iter_geometry_templates()
734                .any(|(_, geometry)| Self::geometry_has_texture_theme(geometry, theme))
735    }
736
737    /// Validate that configured default appearance themes exist in the model.
738    ///
739    /// # Errors
740    ///
741    /// Returns [`crate::error::Error::InvalidThemeName`] when a configured default theme name
742    /// does not appear in any geometry material or texture theme map.
743    pub fn validate_default_themes(&self) -> Result<()> {
744        if let Some(theme) = self.default_material_theme()
745            && !self.has_material_theme(theme.as_ref())
746        {
747            return Err(Error::InvalidThemeName {
748                theme_type: "material".to_string(),
749                theme: theme.as_ref().to_string(),
750            });
751        }
752
753        if let Some(theme) = self.default_texture_theme()
754            && !self.has_texture_theme(theme.as_ref())
755        {
756            return Err(Error::InvalidThemeName {
757                theme_type: "texture".to_string(),
758                theme: theme.as_ref().to_string(),
759            });
760        }
761
762        Ok(())
763    }
764
765    /// Extracts a float attribute column from all `CityObjects`.
766    ///
767    /// Returns `(object_refs, values)` where each index in both vectors corresponds.
768    pub fn extract_float_column(&self, key: &str) -> (Vec<CityObjectHandle>, Vec<f64>) {
769        self.extract_attribute_column(key, |value| match value {
770            AttributeValue::Float(value) => Some(*value),
771            _ => None,
772        })
773    }
774
775    /// Extracts an integer attribute column from all `CityObjects`.
776    ///
777    /// Returns `(object_refs, values)` where each index in both vectors corresponds.
778    pub fn extract_integer_column(&self, key: &str) -> (Vec<CityObjectHandle>, Vec<i64>) {
779        self.extract_attribute_column(key, |value| match value {
780            AttributeValue::Integer(value) => Some(*value),
781            _ => None,
782        })
783    }
784
785    /// Extracts a string attribute column from all `CityObjects`.
786    ///
787    /// Returns `(object_refs, values)` where each index in both vectors corresponds.
788    pub fn extract_string_column<'a>(
789        &'a self,
790        key: &str,
791    ) -> (Vec<CityObjectHandle>, Vec<&'a SS::String>) {
792        self.extract_attribute_column(key, |value| match value {
793            AttributeValue::String(value) => Some(value),
794            _ => None,
795        })
796    }
797
798    fn extract_attribute_column<'a, T, F>(
799        &'a self,
800        key: &str,
801        mut select: F,
802    ) -> (Vec<CityObjectHandle>, Vec<T>)
803    where
804        F: FnMut(&'a AttributeValue<SS>) -> Option<T>,
805    {
806        let mut object_refs = Vec::new();
807        let mut values = Vec::new();
808
809        for (id, cityobject) in self.cityobjects().iter() {
810            if let Some(attributes) = cityobject.attributes()
811                && let Some(value) = attributes.get(key).and_then(&mut select)
812            {
813                object_refs.push(id);
814                values.push(value);
815            }
816        }
817
818        (object_refs, values)
819    }
820
821    fn geometry_has_material_theme(geometry: &Geometry<VR, SS>, theme: &str) -> bool {
822        geometry
823            .materials()
824            .is_some_and(|themes| themes.iter().any(|(name, _)| name.as_ref() == theme))
825    }
826
827    fn geometry_has_texture_theme(geometry: &Geometry<VR, SS>, theme: &str) -> bool {
828        geometry
829            .textures()
830            .is_some_and(|themes| themes.iter().any(|(name, _)| name.as_ref() == theme))
831    }
832
833    /// Returns all unique attribute keys from all `CityObjects`.
834    pub fn attribute_keys(&self) -> HashSet<&str> {
835        let mut keys = HashSet::new();
836
837        for (_, cityobject) in self.cityobjects().iter() {
838            if let Some(attributes) = cityobject.attributes() {
839                for key in attributes.keys() {
840                    keys.insert(key.as_ref());
841                }
842            }
843        }
844
845        keys
846    }
847}
848
849impl<VR: VertexRef, SS: StringStorage> RawAccess for CityModel<VR, SS> {
850    type Vertex = RealWorldCoordinate;
851    type Geometry = Geometry<VR, SS>;
852    type Semantic = Semantic<SS>;
853    type Material = Material<SS>;
854    type Texture = Texture<SS>;
855
856    fn vertices_raw(&self) -> RawSliceView<'_, Self::Vertex> {
857        RawSliceView::new(self.vertices().as_slice())
858    }
859
860    fn geometries_raw(&self) -> RawPoolView<'_, Self::Geometry> {
861        self.inner.geometries_raw()
862    }
863
864    fn semantics_raw(&self) -> RawPoolView<'_, Self::Semantic> {
865        self.inner.semantics_raw()
866    }
867
868    fn materials_raw(&self) -> RawPoolView<'_, Self::Material> {
869        self.inner.materials_raw()
870    }
871
872    fn textures_raw(&self) -> RawPoolView<'_, Self::Texture> {
873        self.inner.textures_raw()
874    }
875}
876
877impl<VR: VertexRef, SS: StringStorage> fmt::Display for CityModel<VR, SS> {
878    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
879        writeln!(f, "CityModel {{")?;
880        writeln!(f, "\ttype: {}", self.type_citymodel())?;
881        writeln!(f, "\tversion: {}", format_option(self.version().as_ref()))?;
882        writeln!(
883            f,
884            "\textensions: {{ {} }}",
885            format_option(self.extensions())
886        )?;
887        writeln!(f, "\tid: {}", format_option(self.id().as_ref()))?;
888        writeln!(f, "\ttransform: {{ {} }}", format_option(self.transform()))?;
889        writeln!(f, "\tmetadata: {}", format_option(self.metadata()))?;
890        writeln!(
891            f,
892            "\tCityObjects: {{ nr. cityobjects: {}, nr. geometries: {} }}",
893            self.cityobjects().len(),
894            self.geometry_count()
895        )?;
896        writeln!(
897            f,
898            "\tappearance: {{ nr. materials: {}, nr. textures: {}, nr. vertices-texture: {}, default-theme-texture: {}, default-theme-material: {} }}",
899            self.material_count(),
900            self.texture_count(),
901            self.vertices_texture().len(),
902            format_option(self.default_texture_theme()),
903            format_option(self.default_material_theme())
904        )?;
905        writeln!(f, "\tgeometry-templates: not implemented")?;
906        writeln!(
907            f,
908            "\tvertices: {{ nr. vertices: {}, quantized coordinates: not implemented }}",
909            self.vertices().len()
910        )?;
911        writeln!(f, "\textra: {}", format_option(self.extra()))?;
912        writeln!(f, "}}")
913    }
914}
915
916impl<VR: VertexRef, SS: StringStorage> GeometryValidationContext<VR, ResourceId32>
917    for CityModel<VR, SS>
918{
919    fn semantic_exists(&self, id: ResourceId32) -> bool {
920        self.inner.get_semantic(id).is_some()
921    }
922
923    fn material_exists(&self, id: ResourceId32) -> bool {
924        self.inner.get_material(id).is_some()
925    }
926
927    fn texture_exists(&self, id: ResourceId32) -> bool {
928        self.inner.get_texture(id).is_some()
929    }
930
931    fn uv_exists(&self, id: VertexIndex<VR>) -> bool {
932        self.inner.get_uv_coordinate(id).is_some()
933    }
934
935    fn regular_vertex_exists(&self, id: VertexIndex<VR>) -> bool {
936        self.inner.get_vertex(id).is_some()
937    }
938
939    fn template_vertex_exists(&self, id: VertexIndex<VR>) -> bool {
940        self.inner.get_template_vertex(id).is_some()
941    }
942
943    fn template_geometry_exists(&self, id: ResourceId32) -> bool {
944        self.inner.get_template_geometry(id).is_some()
945    }
946}
947
948#[cfg(test)]
949mod tests {
950    use super::*;
951    use crate::CityModelType;
952    use crate::backend::default::geometry::GeometryInstanceData;
953    use crate::resources::id::ResourceId32;
954    use crate::v2_0::appearance::ImageType;
955    use crate::v2_0::appearance::material::Material;
956    use crate::v2_0::appearance::texture::Texture;
957    use crate::v2_0::boundary::nested::BoundaryNestedMultiPoint32;
958    use crate::v2_0::geometry::{AffineTransform3D, LoD, StoredGeometryParts};
959    use crate::v2_0::{
960        CityObject, CityObjectIdentifier, CityObjectType, GeometryDraft, RingDraft, SurfaceDraft,
961    };
962
963    fn multi_point_geometry(
964        vertices: BoundaryNestedMultiPoint32,
965    ) -> Geometry<u32, OwnedStringStorage> {
966        Geometry::from_raw_parts(
967            GeometryType::MultiPoint,
968            Some(LoD::LoD1),
969            Some(vertices.into()),
970            None,
971            None,
972            None,
973            None,
974        )
975    }
976
977    fn stored_multi_point_geometry(
978        vertices: BoundaryNestedMultiPoint32,
979    ) -> Geometry<u32, OwnedStringStorage> {
980        Geometry::from_stored_parts(StoredGeometryParts {
981            type_geometry: GeometryType::MultiPoint,
982            lod: Some(LoD::LoD1),
983            boundaries: Some(vertices.into()),
984            semantics: None,
985            materials: None,
986            textures: None,
987            instance: None,
988        })
989    }
990
991    #[test]
992    fn add_geometry_rejects_missing_regular_boundary_vertex_even_when_template_vertex_exists() {
993        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
994        model
995            .add_template_vertex(RealWorldCoordinate::new(0.0, 0.0, 0.0))
996            .unwrap();
997
998        let err = model
999            .add_geometry(multi_point_geometry(vec![0u32]))
1000            .unwrap_err();
1001
1002        assert!(format!("{err}").contains("missing regular vertex"));
1003    }
1004
1005    #[test]
1006    fn add_geometry_template_rejects_missing_template_boundary_vertex_even_when_regular_vertex_exists()
1007     {
1008        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
1009        model
1010            .add_vertex(RealWorldCoordinate::new(0.0, 0.0, 0.0))
1011            .unwrap();
1012
1013        let err = model
1014            .add_geometry_template(multi_point_geometry(vec![0u32]))
1015            .unwrap_err();
1016
1017        assert!(format!("{err}").contains("missing template vertex"));
1018    }
1019
1020    #[test]
1021    fn add_geometry_unchecked_accepts_valid_stored_geometry() {
1022        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
1023        model
1024            .add_vertex(RealWorldCoordinate::new(0.0, 0.0, 0.0))
1025            .unwrap();
1026
1027        let geometry = stored_multi_point_geometry(vec![0u32]);
1028        let handle = model.add_geometry_unchecked(geometry).unwrap();
1029
1030        assert_eq!(model.geometry_count(), 1);
1031        assert_eq!(
1032            model.get_geometry(handle).unwrap().type_geometry(),
1033            &GeometryType::MultiPoint
1034        );
1035    }
1036
1037    #[test]
1038    fn add_geometry_template_unchecked_accepts_valid_stored_geometry() {
1039        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
1040        model
1041            .add_template_vertex(RealWorldCoordinate::new(0.0, 0.0, 0.0))
1042            .unwrap();
1043
1044        let geometry = stored_multi_point_geometry(vec![0u32]);
1045        let handle = model.add_geometry_template_unchecked(geometry).unwrap();
1046
1047        assert_eq!(model.geometry_template_count(), 1);
1048        assert_eq!(
1049            model.get_geometry_template(handle).unwrap().type_geometry(),
1050            &GeometryType::MultiPoint
1051        );
1052    }
1053
1054    #[test]
1055    fn add_geometry_template_rejects_geometry_instance() {
1056        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
1057        let geometry = Geometry::from_raw_parts(
1058            GeometryType::GeometryInstance,
1059            None,
1060            None,
1061            None,
1062            None,
1063            None,
1064            Some(GeometryInstanceData::new(
1065                ResourceId32::new(0, 0),
1066                VertexIndex::new(0),
1067                AffineTransform3D::identity(),
1068            )),
1069        );
1070
1071        let err = model.add_geometry_template(geometry).unwrap_err();
1072
1073        assert_eq!(
1074            format!("{err}"),
1075            "GeometryInstance cannot be inserted into the template geometry pool"
1076        );
1077    }
1078
1079    #[test]
1080    fn set_default_material_theme_stores_theme_name_without_validation() {
1081        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
1082        model.set_default_material_theme(Some(ThemeName::new("missing-theme".to_string())));
1083
1084        assert_eq!(
1085            model.default_material_theme().map(AsRef::as_ref),
1086            Some("missing-theme")
1087        );
1088    }
1089
1090    #[test]
1091    fn validate_default_themes_rejects_missing_material_theme() {
1092        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
1093        model.set_default_material_theme(Some(ThemeName::new("missing-theme".to_string())));
1094
1095        let err = model.validate_default_themes().unwrap_err();
1096
1097        assert!(matches!(
1098            err,
1099            Error::InvalidThemeName { ref theme_type, ref theme }
1100                if theme_type == "material" && theme == "missing-theme"
1101        ));
1102    }
1103
1104    #[test]
1105    fn validate_default_themes_rejects_missing_texture_theme() {
1106        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
1107        model.set_default_texture_theme(Some(ThemeName::new("missing-theme".to_string())));
1108
1109        let err = model.validate_default_themes().unwrap_err();
1110
1111        assert!(matches!(
1112            err,
1113            Error::InvalidThemeName { ref theme_type, ref theme }
1114                if theme_type == "texture" && theme == "missing-theme"
1115        ));
1116    }
1117
1118    #[test]
1119    fn validate_default_themes_accepts_present_geometry_themes() {
1120        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
1121        let material = model
1122            .add_material(Material::new("mat-a".to_string()))
1123            .unwrap();
1124        let texture = model
1125            .add_texture(Texture::new("tex-a.png".to_string(), ImageType::Png))
1126            .unwrap();
1127        let theme = ThemeName::new("theme-a".to_string());
1128
1129        GeometryDraft::multi_surface(
1130            None,
1131            [SurfaceDraft::new(
1132                RingDraft::new([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]).with_texture(
1133                    theme.clone(),
1134                    texture,
1135                    [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]],
1136                ),
1137                [],
1138            )
1139            .with_material(theme.clone(), material)],
1140        )
1141        .insert_into(&mut model)
1142        .unwrap();
1143
1144        model.set_default_material_theme(Some(theme.clone()));
1145        model.set_default_texture_theme(Some(theme.clone()));
1146
1147        assert!(model.has_material_theme("theme-a"));
1148        assert!(model.has_texture_theme("theme-a"));
1149        assert!(model.validate_default_themes().is_ok());
1150    }
1151
1152    #[test]
1153    fn cityjsonfeature_root_id_is_stored_as_typed_model_state() {
1154        let mut model = OwnedCityModel::new(CityModelType::CityJSONFeature);
1155        let handle = model
1156            .cityobjects_mut()
1157            .add(CityObject::new(
1158                CityObjectIdentifier::new("feature-1".to_string()),
1159                CityObjectType::Building,
1160            ))
1161            .unwrap();
1162
1163        model.set_id(Some(handle));
1164
1165        assert_eq!(model.id(), Some(handle));
1166        assert!(model.extra().is_none());
1167    }
1168
1169    #[test]
1170    fn cityjson_root_extra_id_remains_independent_from_typed_feature_id() {
1171        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
1172        model.extra_mut().insert(
1173            "id".to_string(),
1174            AttributeValue::String("document-root-id".to_string()),
1175        );
1176
1177        assert_eq!(model.id(), None);
1178        assert_eq!(
1179            model.extra().and_then(|extra| extra.get("id")),
1180            Some(&AttributeValue::String("document-root-id".to_string()))
1181        );
1182    }
1183}