1use 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
82pub type OwnedCityModel = CityModel<u32, OwnedStringStorage>;
84
85pub type BorrowedCityModel<'a> = CityModel<u32, BorrowedStringStorage<'a>>;
87
88#[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#[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 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 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 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 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 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 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 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 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 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 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 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 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 #[inline]
529 pub fn raw(&self) -> crate::raw::v2_0::CityModelRawAccessor<'_, VR, SS> {
530 crate::raw::v2_0::CityModelRawAccessor::new(self)
531 }
532
533 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 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 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 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 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 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 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 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 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 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 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}