1use std::collections::hash_map::Entry;
8use std::collections::{BTreeSet, HashMap, HashSet};
9
10use crate::cityjson_types::resources::storage::OwnedStringStorage;
11use crate::cityjson_types::v2_0::attributes::Attributes;
12use crate::cityjson_types::v2_0::geometry::{
13 Geometry, GeometryType, StoredGeometryInstance, StoredGeometryParts,
14};
15use crate::cityjson_types::v2_0::metadata::BBox;
16use crate::cityjson_types::v2_0::{
17 CityObject, CityObjectIdentifier, MaterialMap, Metadata, SemanticMap, TextureMap, Transform,
18 VertexIndex,
19};
20use crate::cityjson_types::{
21 CityModelType,
22 prelude::{
23 CityObjectHandle, GeometryHandle, GeometryTemplateHandle, MaterialHandle, SemanticHandle,
24 TextureHandle,
25 },
26 v2_0::Extensions,
27};
28use crate::{CityModel, Error, Result, json};
29use serde_json::Value;
30
31type OwnedMetadata = Metadata<OwnedStringStorage>;
32type OwnedExtensions = Extensions<OwnedStringStorage>;
33type OwnedCityObject = CityObject<OwnedStringStorage>;
34type OwnedGeometry = Geometry<u32, OwnedStringStorage>;
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37enum CityObjectSelection {
38 Whole,
39 Partial(HashSet<GeometryHandle>),
40}
41
42#[derive(Debug, Clone, Default, PartialEq, Eq)]
44pub struct ModelSelection {
45 cityobjects: HashMap<CityObjectHandle, CityObjectSelection>,
46}
47
48pub struct CityObjectSelectionContext<'a> {
50 model: &'a CityModel,
51 handle: CityObjectHandle,
52 cityobject: &'a CityObject<OwnedStringStorage>,
53}
54
55impl<'a> CityObjectSelectionContext<'a> {
56 pub fn model(&self) -> &'a CityModel {
57 self.model
58 }
59
60 pub fn handle(&self) -> CityObjectHandle {
61 self.handle
62 }
63
64 pub fn cityobject(&self) -> &'a CityObject<OwnedStringStorage> {
65 self.cityobject
66 }
67
68 pub fn id(&self) -> &'a str {
69 self.cityobject.id()
70 }
71}
72
73pub struct GeometrySelectionContext<'a> {
75 model: &'a CityModel,
76 cityobject_handle: CityObjectHandle,
77 cityobject: &'a CityObject<OwnedStringStorage>,
78 geometry_handle: GeometryHandle,
79 geometry: &'a OwnedGeometry,
80 geometry_index: usize,
81}
82
83impl<'a> GeometrySelectionContext<'a> {
84 pub fn model(&self) -> &'a CityModel {
85 self.model
86 }
87
88 pub fn cityobject_handle(&self) -> CityObjectHandle {
89 self.cityobject_handle
90 }
91
92 pub fn cityobject(&self) -> &'a CityObject<OwnedStringStorage> {
93 self.cityobject
94 }
95
96 pub fn cityobject_id(&self) -> &'a str {
97 self.cityobject.id()
98 }
99
100 pub fn geometry_handle(&self) -> GeometryHandle {
101 self.geometry_handle
102 }
103
104 pub fn geometry(&self) -> &'a OwnedGeometry {
105 self.geometry
106 }
107
108 pub fn geometry_index(&self) -> usize {
109 self.geometry_index
110 }
111}
112
113impl ModelSelection {
114 fn select_whole(&mut self, handle: CityObjectHandle) {
115 self.cityobjects.insert(handle, CityObjectSelection::Whole);
116 }
117
118 fn select_geometry(
119 &mut self,
120 cityobject_handle: CityObjectHandle,
121 geometry_handle: GeometryHandle,
122 ) {
123 match self.cityobjects.entry(cityobject_handle) {
124 Entry::Vacant(entry) => {
125 let mut geometries = HashSet::new();
126 geometries.insert(geometry_handle);
127 entry.insert(CityObjectSelection::Partial(geometries));
128 }
129 Entry::Occupied(mut entry) => match entry.get_mut() {
130 CityObjectSelection::Whole => {}
131 CityObjectSelection::Partial(geometries) => {
132 geometries.insert(geometry_handle);
133 }
134 },
135 }
136 }
137
138 pub fn include_relatives(self, model: &CityModel) -> Result<Self> {
140 let mut selection = self;
141 let roots = selection.cityobjects.keys().copied().collect::<Vec<_>>();
142 let relatives = collect_reachable_cityobjects(model, roots, true, true)?;
143
144 for handle in relatives {
145 selection
146 .cityobjects
147 .entry(handle)
148 .or_insert(CityObjectSelection::Whole);
149 }
150
151 Ok(selection)
152 }
153
154 pub fn union(&self, other: &Self) -> Self {
156 let mut selection = self.clone();
157
158 for (handle, state) in &other.cityobjects {
159 match selection.cityobjects.entry(*handle) {
160 Entry::Vacant(entry) => {
161 entry.insert(state.clone());
162 }
163 Entry::Occupied(mut entry) => {
164 let merged = match (entry.get(), state) {
165 (CityObjectSelection::Whole, _) | (_, CityObjectSelection::Whole) => {
166 CityObjectSelection::Whole
167 }
168 (CityObjectSelection::Partial(lhs), CityObjectSelection::Partial(rhs)) => {
169 let geometries =
170 lhs.union(rhs).copied().collect::<HashSet<GeometryHandle>>();
171 CityObjectSelection::Partial(geometries)
172 }
173 };
174 entry.insert(merged);
175 }
176 }
177 }
178
179 selection
180 }
181
182 pub fn intersection(&self, other: &Self) -> Self {
184 let mut cityobjects = HashMap::new();
185
186 for (handle, lhs_state) in &self.cityobjects {
187 let Some(rhs_state) = other.cityobjects.get(handle) else {
188 continue;
189 };
190
191 let merged = match (lhs_state, rhs_state) {
192 (CityObjectSelection::Whole, CityObjectSelection::Whole) => {
193 CityObjectSelection::Whole
194 }
195 (CityObjectSelection::Whole, CityObjectSelection::Partial(geometries))
196 | (CityObjectSelection::Partial(geometries), CityObjectSelection::Whole) => {
197 CityObjectSelection::Partial(geometries.clone())
198 }
199 (CityObjectSelection::Partial(lhs), CityObjectSelection::Partial(rhs)) => {
200 let geometries = lhs.intersection(rhs).copied().collect::<HashSet<_>>();
201 if geometries.is_empty() {
202 continue;
203 }
204 CityObjectSelection::Partial(geometries)
205 }
206 };
207
208 cityobjects.insert(*handle, merged);
209 }
210
211 Self { cityobjects }
212 }
213
214 pub fn is_empty(&self) -> bool {
216 self.cityobjects.is_empty()
217 }
218}
219
220fn import_error(message: impl Into<String>) -> Error {
221 Error::Import(message.into())
222}
223
224#[derive(Debug, Clone, PartialEq)]
225enum TransformMergeState {
226 Empty,
227 Present(Transform),
228 Cleared,
229}
230
231impl TransformMergeState {
232 fn from_model(model: &CityModel) -> Self {
233 match model.transform() {
234 Some(transform) => Self::Present(transform.clone()),
235 None => Self::Empty,
236 }
237 }
238}
239
240fn reconcile_transform_state(
241 current: TransformMergeState,
242 source: Option<&Transform>,
243) -> TransformMergeState {
244 match (current, source) {
245 (TransformMergeState::Empty, None) => TransformMergeState::Empty,
246 (TransformMergeState::Empty, Some(transform)) => {
247 TransformMergeState::Present(transform.clone())
248 }
249 (TransformMergeState::Present(transform), None) => TransformMergeState::Present(transform),
250 (TransformMergeState::Present(transform), Some(source_transform))
251 if transform == *source_transform =>
252 {
253 TransformMergeState::Present(transform)
254 }
255 (TransformMergeState::Cleared, _) | (TransformMergeState::Present(_), Some(_)) => {
256 TransformMergeState::Cleared
257 }
258 }
259}
260
261fn strip_transform(model: &CityModel) -> Result<CityModel> {
262 let mut untransformed = model.clone();
263 untransformed.transform_mut().set_scale([1.0, 1.0, 1.0]);
264 untransformed.transform_mut().set_translate([0.0, 0.0, 0.0]);
265
266 let mut root = serde_json::from_slice::<Value>(&json::to_vec(&untransformed)?)
267 .map_err(|error| import_error(error.to_string()))?;
268 let Value::Object(root) = &mut root else {
269 return Err(import_error("serialized CityJSON root is not an object"));
270 };
271 root.remove("transform");
272 let bytes = serde_json::to_vec(&root).map_err(|error| import_error(error.to_string()))?;
273 match model.type_citymodel() {
274 CityModelType::CityJSON => json::from_slice(&bytes),
275 CityModelType::CityJSONFeature => json::from_feature_slice(&bytes),
276 other => Err(Error::UnsupportedType(other.to_string())),
277 }
278}
279
280fn apply_transform_state(target: &mut CityModel, state: &TransformMergeState) -> Result<()> {
281 match state {
282 TransformMergeState::Empty => {
283 if target.transform().is_some() {
284 *target = strip_transform(target)?;
285 }
286 }
287 TransformMergeState::Present(transform) => {
288 if target.transform().is_none() {
289 *target.transform_mut() = transform.clone();
290 } else if target.transform() != Some(transform) {
291 *target = strip_transform(target)?;
292 *target.transform_mut() = transform.clone();
293 }
294 }
295 TransformMergeState::Cleared => {
296 if target.transform().is_some() {
297 *target = strip_transform(target)?;
298 }
299 }
300 }
301
302 Ok(())
303}
304
305fn append_kind_compatible(target_kind: CityModelType, source_kind: CityModelType) -> bool {
306 target_kind == source_kind
307 || (target_kind == CityModelType::CityJSON && source_kind == CityModelType::CityJSONFeature)
308}
309
310fn union_bbox(lhs: BBox, rhs: BBox) -> BBox {
311 BBox::new(
312 lhs.min_x().min(rhs.min_x()),
313 lhs.min_y().min(rhs.min_y()),
314 lhs.min_z().min(rhs.min_z()),
315 lhs.max_x().max(rhs.max_x()),
316 lhs.max_y().max(rhs.max_y()),
317 lhs.max_z().max(rhs.max_z()),
318 )
319}
320
321fn merge_attributes(
322 target: &mut Attributes<OwnedStringStorage>,
323 source: &Attributes<OwnedStringStorage>,
324) {
325 for (key, value) in source.iter() {
326 target.insert(key.clone(), value.clone());
327 }
328}
329
330fn merge_cityobject_extent(target: &mut OwnedCityObject, source: &OwnedCityObject) {
331 match (
332 target.geographical_extent().copied(),
333 source.geographical_extent().copied(),
334 ) {
335 (None, Some(extent)) => target.set_geographical_extent(Some(extent)),
336 (Some(lhs), Some(rhs)) if lhs != rhs => {
337 target.set_geographical_extent(Some(union_bbox(lhs, rhs)))
338 }
339 _ => {}
340 }
341}
342
343fn merge_metadata(target: &mut OwnedMetadata, source: &OwnedMetadata) {
344 if target.geographical_extent().is_none()
345 && let Some(extent) = source.geographical_extent().copied()
346 {
347 target.set_geographical_extent(extent);
348 } else if let (Some(lhs), Some(rhs)) = (
349 target.geographical_extent().copied(),
350 source.geographical_extent().copied(),
351 ) && lhs != rhs
352 {
353 target.set_geographical_extent(union_bbox(lhs, rhs));
354 }
355
356 if target.identifier().is_none()
357 && let Some(identifier) = source.identifier().cloned()
358 {
359 target.set_identifier(identifier);
360 }
361
362 if target.reference_date().is_none()
363 && let Some(date) = source.reference_date().cloned()
364 {
365 target.set_reference_date(date);
366 }
367
368 if target.reference_system().is_none()
369 && let Some(crs) = source.reference_system().cloned()
370 {
371 target.set_reference_system(crs);
372 }
373
374 if target.title().is_none()
375 && let Some(title) = source.title()
376 {
377 target.set_title(title.to_owned());
378 }
379
380 if target.point_of_contact().is_none()
381 && let Some(contact) = source.point_of_contact().cloned()
382 {
383 target.set_point_of_contact(Some(contact));
384 }
385
386 if let Some(extra) = source.extra() {
387 let target_extra = target.extra_mut();
388 for (key, value) in extra.iter() {
389 target_extra.insert(key.clone(), value.clone());
390 }
391 }
392}
393
394fn merge_root_extensions(target: &mut OwnedExtensions, source: &OwnedExtensions) {
395 for extension in source {
396 target.add(extension.clone());
397 }
398}
399
400fn remap_vertex_indices(
401 boundary: &crate::cityjson_types::v2_0::boundary::Boundary<u32>,
402 vertex_map: &[VertexIndex<u32>],
403) -> Result<crate::cityjson_types::v2_0::boundary::Boundary<u32>> {
404 let mut boundary = boundary.clone();
405 let remapped = boundary.vertices().iter().map(|index| {
406 vertex_map
407 .get(index.to_usize())
408 .copied()
409 .ok_or_else(|| import_error(format!("vertex index {} is out of range", index.value())))
410 });
411 boundary.set_vertices_from_iter(remapped.collect::<Result<Vec<_>>>()?);
412 Ok(boundary)
413}
414
415fn remap_texture_map(
416 map: &crate::cityjson_types::v2_0::geometry::TextureMapView<'_, u32>,
417 uv_map: &[VertexIndex<u32>],
418 texture_map: &HashMap<TextureHandle, TextureHandle>,
419) -> Result<TextureMap<u32>> {
420 let mut remapped = TextureMap::new();
421
422 for vertex in map.vertices() {
423 let mapped = vertex
424 .map(|index| {
425 uv_map.get(index.to_usize()).copied().ok_or_else(|| {
426 import_error(format!("uv vertex index {} is out of range", index.value()))
427 })
428 })
429 .transpose()?;
430 remapped.add_vertex(mapped);
431 }
432
433 for ring in map.rings() {
434 remapped.add_ring(*ring);
435 }
436
437 for texture in map.ring_textures() {
438 remapped.add_ring_texture(
439 texture.map(|handle| texture_map.get(&handle).copied().unwrap_or(handle)),
440 );
441 }
442
443 Ok(remapped)
444}
445
446fn remap_material_map<'a, I, J, K>(
447 points: I,
448 linestrings: J,
449 surfaces: K,
450 material_map: &HashMap<MaterialHandle, MaterialHandle>,
451) -> MaterialMap<u32>
452where
453 I: IntoIterator<Item = &'a Option<MaterialHandle>>,
454 J: IntoIterator<Item = &'a Option<MaterialHandle>>,
455 K: IntoIterator<Item = &'a Option<MaterialHandle>>,
456{
457 let mut remapped = MaterialMap::new();
458
459 for item in points {
460 remapped.add_point(match item {
461 Some(handle) => Some(material_map.get(handle).copied().unwrap_or(*handle)),
462 None => None,
463 });
464 }
465 for item in linestrings {
466 remapped.add_linestring(match item {
467 Some(handle) => Some(material_map.get(handle).copied().unwrap_or(*handle)),
468 None => None,
469 });
470 }
471 for item in surfaces {
472 remapped.add_surface(match item {
473 Some(handle) => Some(material_map.get(handle).copied().unwrap_or(*handle)),
474 None => None,
475 });
476 }
477
478 remapped
479}
480
481fn remap_semantic_map<'a, I, J, K>(
482 points: I,
483 linestrings: J,
484 surfaces: K,
485 semantic_map: &HashMap<SemanticHandle, SemanticHandle>,
486) -> SemanticMap<u32>
487where
488 I: IntoIterator<Item = &'a Option<SemanticHandle>>,
489 J: IntoIterator<Item = &'a Option<SemanticHandle>>,
490 K: IntoIterator<Item = &'a Option<SemanticHandle>>,
491{
492 let mut remapped = SemanticMap::new();
493
494 for item in points {
495 remapped.add_point(match item {
496 Some(handle) => Some(semantic_map.get(handle).copied().unwrap_or(*handle)),
497 None => None,
498 });
499 }
500 for item in linestrings {
501 remapped.add_linestring(match item {
502 Some(handle) => Some(semantic_map.get(handle).copied().unwrap_or(*handle)),
503 None => None,
504 });
505 }
506 for item in surfaces {
507 remapped.add_surface(match item {
508 Some(handle) => Some(semantic_map.get(handle).copied().unwrap_or(*handle)),
509 None => None,
510 });
511 }
512
513 remapped
514}
515
516fn remap_geometry(
517 geometry: &OwnedGeometry,
518 vertex_map: &[VertexIndex<u32>],
519 template_map: &HashMap<GeometryTemplateHandle, GeometryTemplateHandle>,
520 semantic_map: &HashMap<SemanticHandle, SemanticHandle>,
521 material_map: &HashMap<MaterialHandle, MaterialHandle>,
522 texture_map: &HashMap<TextureHandle, TextureHandle>,
523 uv_map: &[VertexIndex<u32>],
524) -> Result<OwnedGeometry> {
525 let stored_parts = if let Some(instance) = geometry.instance() {
526 let template = template_map
527 .get(&instance.template())
528 .copied()
529 .ok_or_else(|| {
530 import_error(format!(
531 "missing remap for geometry template {}",
532 instance.template()
533 ))
534 })?;
535 Geometry::from_stored_parts(StoredGeometryParts {
536 type_geometry: GeometryType::GeometryInstance,
537 lod: None,
538 boundaries: None,
539 semantics: None,
540 materials: None,
541 textures: None,
542 instance: Some(StoredGeometryInstance {
543 template,
544 reference_point: *vertex_map
545 .get(instance.reference_point().to_usize())
546 .ok_or_else(|| {
547 import_error(format!(
548 "vertex index {} is out of range",
549 instance.reference_point().value()
550 ))
551 })?,
552 transformation: instance.transformation(),
553 }),
554 })
555 } else {
556 let boundaries = geometry
557 .boundaries()
558 .map(|boundary| remap_vertex_indices(boundary, vertex_map))
559 .transpose()?;
560
561 let semantics = geometry.semantics().map(|theme| {
562 let points = theme.points();
563 let linestrings = theme.linestrings();
564 let surfaces = theme.surfaces();
565 remap_semantic_map(
566 points.iter(),
567 linestrings.iter(),
568 surfaces.iter(),
569 semantic_map,
570 )
571 });
572
573 let materials = geometry.materials().map(|themes| {
574 themes
575 .iter()
576 .map(|(name, theme)| {
577 let points = theme.points();
578 let linestrings = theme.linestrings();
579 let surfaces = theme.surfaces();
580 (
581 name.clone(),
582 remap_material_map(
583 points.iter(),
584 linestrings.iter(),
585 surfaces.iter(),
586 material_map,
587 ),
588 )
589 })
590 .collect::<Vec<_>>()
591 });
592
593 let textures = geometry
594 .textures()
595 .map(|themes| {
596 themes
597 .iter()
598 .map(|(name, theme)| {
599 remap_texture_map(&theme, uv_map, texture_map)
600 .map(|map| (name.clone(), map))
601 })
602 .collect::<Result<Vec<_>>>()
603 })
604 .transpose()?;
605
606 Geometry::from_stored_parts(StoredGeometryParts {
607 type_geometry: *geometry.type_geometry(),
608 lod: geometry.lod().copied(),
609 boundaries,
610 semantics,
611 materials,
612 textures,
613 instance: None,
614 })
615 };
616
617 Ok(stored_parts)
618}
619
620fn append_vertices(target: &mut CityModel, source: &CityModel) -> Result<Vec<VertexIndex<u32>>> {
621 let mut map = Vec::with_capacity(source.vertices().len());
622 for vertex in source.vertices().as_slice() {
623 map.push(target.add_vertex(*vertex)?);
624 }
625 Ok(map)
626}
627
628fn append_template_vertices(
629 target: &mut CityModel,
630 source: &CityModel,
631) -> Result<Vec<VertexIndex<u32>>> {
632 let mut map = Vec::with_capacity(source.template_vertices().len());
633 for vertex in source.template_vertices().as_slice() {
634 map.push(target.add_template_vertex(*vertex)?);
635 }
636 Ok(map)
637}
638
639fn append_uv_vertices(target: &mut CityModel, source: &CityModel) -> Result<Vec<VertexIndex<u32>>> {
640 let mut map = Vec::with_capacity(source.vertices_texture().len());
641 for uv in source.vertices_texture().as_slice() {
642 map.push(target.add_uv_coordinate((*uv).clone())?);
643 }
644 Ok(map)
645}
646
647fn append_semantics(
648 target: &mut CityModel,
649 source: &CityModel,
650) -> Result<HashMap<SemanticHandle, SemanticHandle>> {
651 let mut map = HashMap::with_capacity(source.semantic_count());
652 for (handle, semantic) in source.iter_semantics() {
653 map.insert(handle, target.add_semantic(semantic.clone())?);
654 }
655 Ok(map)
656}
657
658fn append_materials(
659 target: &mut CityModel,
660 source: &CityModel,
661) -> Result<HashMap<MaterialHandle, MaterialHandle>> {
662 let mut map = HashMap::with_capacity(source.material_count());
663 for (handle, material) in source.iter_materials() {
664 map.insert(handle, target.add_material(material.clone())?);
665 }
666 Ok(map)
667}
668
669fn append_textures(
670 target: &mut CityModel,
671 source: &CityModel,
672) -> Result<HashMap<TextureHandle, TextureHandle>> {
673 let mut map = HashMap::with_capacity(source.texture_count());
674 for (handle, texture) in source.iter_textures() {
675 map.insert(handle, target.add_texture(texture.clone())?);
676 }
677 Ok(map)
678}
679
680fn append_geometry_templates(
681 target: &mut CityModel,
682 source: &CityModel,
683 template_vertex_map: &[VertexIndex<u32>],
684 template_map: &HashMap<GeometryTemplateHandle, GeometryTemplateHandle>,
685 semantic_map: &HashMap<SemanticHandle, SemanticHandle>,
686 material_map: &HashMap<MaterialHandle, MaterialHandle>,
687 texture_map: &HashMap<TextureHandle, TextureHandle>,
688 uv_map: &[VertexIndex<u32>],
689) -> Result<HashMap<GeometryTemplateHandle, GeometryTemplateHandle>> {
690 let mut map = HashMap::with_capacity(source.geometry_template_count());
691 for (handle, geometry) in source.iter_geometry_templates() {
692 let remapped = remap_geometry(
693 geometry,
694 template_vertex_map,
695 template_map,
696 semantic_map,
697 material_map,
698 texture_map,
699 uv_map,
700 )?;
701 map.insert(handle, target.add_geometry_template(remapped)?);
702 }
703 Ok(map)
704}
705
706fn append_geometries(
707 target: &mut CityModel,
708 source: &CityModel,
709 vertex_map: &[VertexIndex<u32>],
710 template_map: &HashMap<GeometryTemplateHandle, GeometryTemplateHandle>,
711 semantic_map: &HashMap<SemanticHandle, SemanticHandle>,
712 material_map: &HashMap<MaterialHandle, MaterialHandle>,
713 texture_map: &HashMap<TextureHandle, TextureHandle>,
714 uv_map: &[VertexIndex<u32>],
715) -> Result<HashMap<GeometryHandle, GeometryHandle>> {
716 let mut map = HashMap::with_capacity(source.geometry_count());
717 for (handle, geometry) in source.iter_geometries() {
718 let remapped = remap_geometry(
719 geometry,
720 vertex_map,
721 template_map,
722 semantic_map,
723 material_map,
724 texture_map,
725 uv_map,
726 )?;
727 map.insert(handle, target.add_geometry(remapped)?);
728 }
729 Ok(map)
730}
731
732fn merge_cityobject(
733 target: &mut OwnedCityObject,
734 source: &OwnedCityObject,
735 cityobject_map: &HashMap<CityObjectHandle, CityObjectHandle>,
736 geometry_map: &HashMap<GeometryHandle, GeometryHandle>,
737) -> Result<()> {
738 if target.type_cityobject() != source.type_cityobject() {
739 return Err(import_error(format!(
740 "conflicting CityObject types for '{}'",
741 target.id()
742 )));
743 }
744
745 if let Some(attributes) = source.attributes() {
746 merge_attributes(target.attributes_mut(), attributes);
747 }
748 merge_cityobject_extent(target, source);
749
750 if let Some(extra) = source.extra() {
751 let target_extra = target.extra_mut();
752 for (key, value) in extra.iter() {
753 target_extra.insert(key.clone(), value.clone());
754 }
755 }
756
757 if let Some(geometry_handles) = source.geometry() {
758 let mut target_geometry = target
759 .geometry()
760 .map(|items| items.to_vec())
761 .unwrap_or_default();
762 for geometry in geometry_handles {
763 let mapped = geometry_map.get(geometry).copied().ok_or_else(|| {
764 import_error(format!(
765 "missing remap for geometry {}",
766 geometry.raw_parts().0
767 ))
768 })?;
769 if !target_geometry.contains(&mapped) {
770 target.add_geometry(mapped);
771 target_geometry.push(mapped);
772 }
773 }
774 }
775
776 if let Some(children) = source.children() {
777 let mut existing = target
778 .children()
779 .map(|items| items.to_vec())
780 .unwrap_or_default();
781 for child in children {
782 let mapped = cityobject_map.get(child).copied().ok_or_else(|| {
783 import_error(format!(
784 "missing remap for cityobject {}",
785 child.raw_parts().0
786 ))
787 })?;
788 if !existing.contains(&mapped) {
789 target.add_child(mapped);
790 existing.push(mapped);
791 }
792 }
793 }
794
795 if let Some(parents) = source.parents() {
796 let mut existing = target
797 .parents()
798 .map(|items| items.to_vec())
799 .unwrap_or_default();
800 for parent in parents {
801 let mapped = cityobject_map.get(parent).copied().ok_or_else(|| {
802 import_error(format!(
803 "missing remap for cityobject {}",
804 parent.raw_parts().0
805 ))
806 })?;
807 if !existing.contains(&mapped) {
808 target.add_parent(mapped);
809 existing.push(mapped);
810 }
811 }
812 }
813
814 Ok(())
815}
816
817fn merge_one(
818 target: &mut CityModel,
819 source: &CityModel,
820 transform_state: &mut TransformMergeState,
821) -> Result<()> {
822 if !append_kind_compatible(target.type_citymodel(), source.type_citymodel()) {
823 return Err(import_error(
824 "model merge currently requires compatible root types",
825 ));
826 }
827
828 *transform_state = reconcile_transform_state(transform_state.clone(), source.transform());
829
830 if target.metadata().is_none() {
831 if let Some(metadata) = source.metadata() {
832 *target.metadata_mut() = metadata.clone();
833 }
834 } else if let Some(source_metadata) = source.metadata() {
835 merge_metadata(target.metadata_mut(), source_metadata);
836 }
837
838 if target.extra().is_none() {
839 if let Some(extra) = source.extra() {
840 *target.extra_mut() = extra.clone();
841 }
842 } else if let Some(extra) = source.extra() {
843 let target_extra = target.extra_mut();
844 for (key, value) in extra.iter() {
845 target_extra.insert(key.clone(), value.clone());
846 }
847 }
848
849 if target.extensions().is_none() {
850 if let Some(extensions) = source.extensions() {
851 *target.extensions_mut() = extensions.clone();
852 }
853 } else if let Some(extensions) = source.extensions() {
854 merge_root_extensions(target.extensions_mut(), extensions);
855 }
856
857 if target.default_material_theme().is_none()
858 && let Some(theme) = source.default_material_theme().cloned()
859 {
860 target.set_default_material_theme(Some(theme));
861 }
862
863 if target.default_texture_theme().is_none()
864 && let Some(theme) = source.default_texture_theme().cloned()
865 {
866 target.set_default_texture_theme(Some(theme));
867 }
868
869 let vertex_map = append_vertices(target, source)?;
870 let template_vertex_map = append_template_vertices(target, source)?;
871 let uv_map = append_uv_vertices(target, source)?;
872 let semantic_map = append_semantics(target, source)?;
873 let material_map = append_materials(target, source)?;
874 let texture_map = append_textures(target, source)?;
875 let empty_template_map: HashMap<GeometryTemplateHandle, GeometryTemplateHandle> =
876 HashMap::new();
877 let template_map = append_geometry_templates(
878 target,
879 source,
880 &template_vertex_map,
881 &empty_template_map,
882 &semantic_map,
883 &material_map,
884 &texture_map,
885 &uv_map,
886 )?;
887 let geometry_map = append_geometries(
888 target,
889 source,
890 &vertex_map,
891 &template_map,
892 &semantic_map,
893 &material_map,
894 &texture_map,
895 &uv_map,
896 )?;
897
898 let mut cityobject_map = HashMap::with_capacity(source.cityobjects().len());
899 for (handle, source_cityobject) in source.cityobjects().iter() {
900 if let Some(existing) = target
901 .cityobjects()
902 .iter()
903 .find(|(_, cityobject)| cityobject.id() == source_cityobject.id())
904 .map(|(handle, _)| handle)
905 {
906 cityobject_map.insert(handle, existing);
907 continue;
908 }
909
910 let placeholder = CityObject::new(
911 CityObjectIdentifier::new(source_cityobject.id().to_owned()),
912 source_cityobject.type_cityobject().clone(),
913 );
914 let new_handle = target.cityobjects_mut().add(placeholder)?;
915 cityobject_map.insert(handle, new_handle);
916 }
917
918 if target.id().is_none()
919 && let Some(source_id) = source.id()
920 && let Some(mapped) = cityobject_map.get(&source_id).copied()
921 {
922 target.set_id(Some(mapped));
923 }
924
925 for (handle, source_cityobject) in source.cityobjects().iter() {
926 let target_handle = cityobject_map.get(&handle).copied().ok_or_else(|| {
927 import_error(format!(
928 "missing remap for cityobject {}",
929 source_cityobject.id()
930 ))
931 })?;
932 let target_cityobject =
933 target
934 .cityobjects_mut()
935 .get_mut(target_handle)
936 .ok_or_else(|| {
937 import_error(format!(
938 "missing target cityobject for {}",
939 source_cityobject.id()
940 ))
941 })?;
942 merge_cityobject(
943 target_cityobject,
944 source_cityobject,
945 &cityobject_map,
946 &geometry_map,
947 )?;
948 }
949
950 Ok(())
951}
952
953pub fn cleanup(model: &CityModel) -> Result<CityModel> {
954 cityjson_json::cleanup(model).map_err(Error::from)
955}
956
957fn collect_reachable_cityobjects<I>(
958 model: &CityModel,
959 roots: I,
960 include_parents: bool,
961 include_children: bool,
962) -> Result<HashSet<CityObjectHandle>>
963where
964 I: IntoIterator<Item = CityObjectHandle>,
965{
966 let mut selected = HashSet::new();
967 let mut stack = roots.into_iter().collect::<Vec<_>>();
968
969 while let Some(handle) = stack.pop() {
970 let cityobject = model.cityobjects().get(handle).ok_or_else(|| {
971 import_error(format!(
972 "missing CityObject handle in traversal: {handle:?}"
973 ))
974 })?;
975 if !selected.insert(handle) {
976 continue;
977 }
978
979 if include_children && let Some(children) = cityobject.children() {
980 stack.extend(children.iter().copied());
981 }
982
983 if include_parents && let Some(parents) = cityobject.parents() {
984 stack.extend(parents.iter().copied());
985 }
986 }
987
988 Ok(selected)
989}
990
991fn selected_geometry_handles(
992 model: &CityModel,
993 cityobject: &OwnedCityObject,
994 state: &CityObjectSelection,
995) -> Result<Vec<GeometryHandle>> {
996 match state {
997 CityObjectSelection::Whole => {
998 let Some(original_geometry) = cityobject.geometry() else {
999 return Ok(Vec::new());
1000 };
1001
1002 let mut selected = Vec::with_capacity(original_geometry.len());
1003 for geometry in original_geometry {
1004 model.get_geometry(*geometry).ok_or_else(|| {
1005 import_error(format!(
1006 "selected geometry handle {:?} is missing from the source model",
1007 geometry
1008 ))
1009 })?;
1010 selected.push(*geometry);
1011 }
1012
1013 Ok(selected)
1014 }
1015 CityObjectSelection::Partial(geometry_handles) => {
1016 let Some(original_geometry) = cityobject.geometry() else {
1017 if geometry_handles.is_empty() {
1018 return Ok(Vec::new());
1019 }
1020
1021 let missing =
1022 geometry_handles.iter().copied().next().expect(
1023 "partial selection with missing geometry requires at least one handle",
1024 );
1025 return Err(import_error(format!(
1026 "selected geometry handle {:?} is missing from CityObject {}",
1027 missing,
1028 cityobject.id()
1029 )));
1030 };
1031
1032 let available = original_geometry.iter().copied().collect::<HashSet<_>>();
1033 for geometry in geometry_handles {
1034 if !available.contains(geometry) {
1035 return Err(import_error(format!(
1036 "selected geometry handle {:?} is missing from CityObject {}",
1037 geometry,
1038 cityobject.id()
1039 )));
1040 }
1041 }
1042
1043 let mut selected = Vec::with_capacity(geometry_handles.len());
1044 for geometry in original_geometry {
1045 if geometry_handles.contains(geometry) {
1046 model.get_geometry(*geometry).ok_or_else(|| {
1047 import_error(format!(
1048 "selected geometry handle {:?} is missing from the source model",
1049 geometry
1050 ))
1051 })?;
1052 selected.push(*geometry);
1053 }
1054 }
1055
1056 Ok(selected)
1057 }
1058 }
1059}
1060
1061fn rebuild_model_with_selection(
1062 model: &CityModel,
1063 selection: &ModelSelection,
1064) -> Result<CityModel> {
1065 let mut result = model.clone();
1066 result.clear_cityobjects();
1067
1068 let mut old_to_new = HashMap::with_capacity(selection.cityobjects.len());
1069 let mut kept = HashSet::with_capacity(selection.cityobjects.len());
1070
1071 for (handle, cityobject) in model.cityobjects().iter() {
1072 let Some(state) = selection.cityobjects.get(&handle) else {
1073 continue;
1074 };
1075
1076 let geometry_handles = selected_geometry_handles(model, cityobject, state)?;
1077 if matches!(state, CityObjectSelection::Partial(_)) && geometry_handles.is_empty() {
1078 continue;
1079 }
1080
1081 let mut cloned = cityobject.clone();
1082 cloned.clear_children();
1083 cloned.clear_parents();
1084 cloned.clear_geometry();
1085 for geometry in &geometry_handles {
1086 cloned.add_geometry(*geometry);
1087 }
1088 let new_handle = result.cityobjects_mut().add(cloned)?;
1089 old_to_new.insert(handle, new_handle);
1090 kept.insert(handle);
1091 }
1092
1093 for (handle, cityobject) in model.cityobjects().iter() {
1094 if !kept.contains(&handle) {
1095 continue;
1096 }
1097
1098 let target_handle = *old_to_new.get(&handle).ok_or_else(|| {
1099 import_error(format!("missing remap for CityObject {}", cityobject.id()))
1100 })?;
1101 let target = result
1102 .cityobjects_mut()
1103 .get_mut(target_handle)
1104 .ok_or_else(|| {
1105 import_error(format!("missing target CityObject {}", cityobject.id()))
1106 })?;
1107
1108 if let Some(children) = cityobject.children() {
1109 for child in children {
1110 model.cityobjects().get(*child).ok_or_else(|| {
1111 import_error(format!("missing child CityObject handle {child:?}"))
1112 })?;
1113 if let Some(mapped) = old_to_new.get(child).copied() {
1114 target.add_child(mapped);
1115 }
1116 }
1117 }
1118
1119 if let Some(parents) = cityobject.parents() {
1120 for parent in parents {
1121 model.cityobjects().get(*parent).ok_or_else(|| {
1122 import_error(format!("missing parent CityObject handle {parent:?}"))
1123 })?;
1124 if let Some(mapped) = old_to_new.get(parent).copied() {
1125 target.add_parent(mapped);
1126 }
1127 }
1128 }
1129 }
1130
1131 if let Some(root) = select_feature_root(model, &kept)? {
1132 let mapped_root = old_to_new.get(&root).copied().ok_or_else(|| {
1133 import_error("feature root selected for rebuild does not exist in the rebuilt model")
1134 })?;
1135 result.set_id(Some(mapped_root));
1136 }
1137
1138 Ok(result)
1139}
1140
1141fn rebuild_model_with_cityobjects(
1142 model: &CityModel,
1143 selected: &HashSet<CityObjectHandle>,
1144) -> Result<CityModel> {
1145 let mut result = model.clone();
1146 result.clear_cityobjects();
1147
1148 let mut old_to_new = HashMap::with_capacity(selected.len());
1149 for (handle, cityobject) in model.cityobjects().iter() {
1150 if !selected.contains(&handle) {
1151 continue;
1152 }
1153
1154 let mut cloned = cityobject.clone();
1155 cloned.clear_children();
1156 cloned.clear_parents();
1157 let new_handle = result.cityobjects_mut().add(cloned)?;
1158 old_to_new.insert(handle, new_handle);
1159 }
1160
1161 for (handle, cityobject) in model.cityobjects().iter() {
1162 if !selected.contains(&handle) {
1163 continue;
1164 }
1165
1166 let target_handle = *old_to_new.get(&handle).ok_or_else(|| {
1167 import_error(format!("missing remap for CityObject {}", cityobject.id()))
1168 })?;
1169 let target = result
1170 .cityobjects_mut()
1171 .get_mut(target_handle)
1172 .ok_or_else(|| {
1173 import_error(format!("missing target CityObject {}", cityobject.id()))
1174 })?;
1175
1176 if let Some(children) = cityobject.children() {
1177 for child in children {
1178 model.cityobjects().get(*child).ok_or_else(|| {
1179 import_error(format!("missing child CityObject handle {child:?}"))
1180 })?;
1181 if let Some(mapped) = old_to_new.get(child).copied() {
1182 target.add_child(mapped);
1183 }
1184 }
1185 }
1186
1187 if let Some(parents) = cityobject.parents() {
1188 for parent in parents {
1189 model.cityobjects().get(*parent).ok_or_else(|| {
1190 import_error(format!("missing parent CityObject handle {parent:?}"))
1191 })?;
1192 if let Some(mapped) = old_to_new.get(parent).copied() {
1193 target.add_parent(mapped);
1194 }
1195 }
1196 }
1197 }
1198
1199 if let Some(root) = select_feature_root(model, selected)? {
1200 let mapped_root = old_to_new.get(&root).copied().ok_or_else(|| {
1201 import_error("feature root selected for rebuild does not exist in the rebuilt model")
1202 })?;
1203 result.set_id(Some(mapped_root));
1204 }
1205
1206 Ok(result)
1207}
1208
1209fn select_feature_root(
1210 model: &CityModel,
1211 selected: &HashSet<CityObjectHandle>,
1212) -> Result<Option<CityObjectHandle>> {
1213 let Some(root) = model.id() else {
1214 return Ok(None);
1215 };
1216
1217 model
1218 .cityobjects()
1219 .get(root)
1220 .ok_or_else(|| import_error("feature root references a missing CityObject"))?;
1221
1222 if selected.contains(&root) {
1223 return Ok(Some(root));
1224 }
1225
1226 for (handle, cityobject) in model.cityobjects().iter() {
1227 if !selected.contains(&handle) {
1228 continue;
1229 }
1230
1231 let is_parentless = cityobject
1232 .parents()
1233 .is_none_or(|parents| parents.iter().all(|parent| !selected.contains(parent)));
1234 if is_parentless {
1235 return Ok(Some(handle));
1236 }
1237 }
1238
1239 Err(import_error(
1240 "feature root was removed and no parentless CityObject remained",
1241 ))
1242}
1243
1244pub fn subset<'a, I>(model: &CityModel, cityobject_ids: I, exclude: bool) -> Result<CityModel>
1245where
1246 I: IntoIterator<Item = &'a str>,
1247{
1248 let ids = cityobject_ids
1249 .into_iter()
1250 .map(str::to_owned)
1251 .collect::<BTreeSet<_>>();
1252 if ids.is_empty() {
1253 return Err(import_error(
1254 "subset requires at least one CityObject identifier",
1255 ));
1256 }
1257
1258 let id_to_handle = model
1259 .cityobjects()
1260 .iter()
1261 .map(|(handle, cityobject)| (cityobject.id().to_owned(), handle))
1262 .collect::<HashMap<_, _>>();
1263
1264 let mut roots = Vec::new();
1265 let mut matched_any = false;
1266
1267 for id in &ids {
1268 if let Some(handle) = id_to_handle.get(id).copied() {
1269 matched_any = true;
1270 roots.push(handle);
1271 }
1272 }
1273
1274 if !matched_any {
1275 return Err(import_error("subset selection matched no CityObjects"));
1276 }
1277
1278 let mut selected = collect_reachable_cityobjects(model, roots, false, true)?;
1279
1280 if exclude {
1281 let excluded = selected;
1282 selected = model
1283 .cityobjects()
1284 .iter()
1285 .map(|(handle, _)| handle)
1286 .filter(|handle| !excluded.contains(handle))
1287 .collect();
1288 }
1289
1290 rebuild_model_with_cityobjects(model, &selected)
1291}
1292
1293pub fn select_cityobjects<F>(model: &CityModel, mut predicate: F) -> Result<ModelSelection>
1295where
1296 F: FnMut(CityObjectSelectionContext<'_>) -> bool,
1297{
1298 let mut selection = ModelSelection::default();
1299
1300 for (handle, cityobject) in model.cityobjects().iter() {
1301 if predicate(CityObjectSelectionContext {
1302 model,
1303 handle,
1304 cityobject,
1305 }) {
1306 selection.select_whole(handle);
1307 }
1308 }
1309
1310 Ok(selection)
1311}
1312
1313pub fn select_geometries<F>(model: &CityModel, mut predicate: F) -> Result<ModelSelection>
1315where
1316 F: FnMut(GeometrySelectionContext<'_>) -> bool,
1317{
1318 let mut selection = ModelSelection::default();
1319
1320 for (cityobject_handle, cityobject) in model.cityobjects().iter() {
1321 let Some(geometry_handles) = cityobject.geometry() else {
1322 continue;
1323 };
1324
1325 for (geometry_index, geometry_handle) in geometry_handles.iter().copied().enumerate() {
1326 let geometry = model.get_geometry(geometry_handle).ok_or_else(|| {
1327 import_error(format!(
1328 "geometry handle {:?} referenced by CityObject {} is missing from the source model",
1329 geometry_handle,
1330 cityobject.id()
1331 ))
1332 })?;
1333
1334 if predicate(GeometrySelectionContext {
1335 model,
1336 cityobject_handle,
1337 cityobject,
1338 geometry_handle,
1339 geometry,
1340 geometry_index,
1341 }) {
1342 selection.select_geometry(cityobject_handle, geometry_handle);
1343 }
1344 }
1345 }
1346
1347 Ok(selection)
1348}
1349
1350pub fn extract(model: &CityModel, selection: &ModelSelection) -> Result<CityModel> {
1352 rebuild_model_with_selection(model, selection)
1353}
1354
1355pub fn append(target: &mut CityModel, source: &CityModel) -> Result<()> {
1356 let mut transform_state = TransformMergeState::from_model(target);
1357 merge_one(target, source, &mut transform_state)?;
1358 apply_transform_state(target, &transform_state)
1359}
1360
1361pub fn merge<I>(models: I) -> Result<CityModel>
1362where
1363 I: IntoIterator<Item = CityModel>,
1364{
1365 let mut models = models.into_iter();
1366 let Some(mut merged) = models.next() else {
1367 return Err(import_error("merge requires at least one model"));
1368 };
1369
1370 let mut transform_state = TransformMergeState::from_model(&merged);
1371
1372 for model in models {
1373 merge_one(&mut merged, &model, &mut transform_state)?;
1374 }
1375
1376 apply_transform_state(&mut merged, &transform_state)?;
1377
1378 Ok(merged)
1379}
1380
1381#[cfg(test)]
1382mod tests {
1383 use super::*;
1384
1385 fn transformed_feature(id: &str, translate_x: f64) -> CityModel {
1386 let bytes = format!(
1387 r#"{{
1388 "type":"CityJSONFeature",
1389 "id":"{id}",
1390 "transform":{{"scale":[1.0,1.0,1.0],"translate":[{translate_x},0.0,0.0]}},
1391 "CityObjects":{{
1392 "{id}":{{
1393 "type":"Building",
1394 "geometry":[{{"type":"MultiSurface","lod":"1","boundaries":[[[0,1,2]]]}}]
1395 }}
1396 }},
1397 "vertices":[[0,0,0],[1,0,0],[0,1,0]]
1398 }}"#
1399 );
1400 json::from_feature_slice(bytes.as_bytes()).expect("feature should parse")
1401 }
1402
1403 #[test]
1404 fn merge_preserves_world_coordinates_when_transforms_differ() {
1405 let merged = merge([
1406 transformed_feature("first", 100.0),
1407 transformed_feature("second", 200.0),
1408 ])
1409 .expect("features should merge");
1410
1411 assert!(merged.transform().is_none());
1412 let world_xs = merged
1413 .vertices()
1414 .as_slice()
1415 .iter()
1416 .map(|vertex| vertex.x())
1417 .collect::<Vec<_>>();
1418
1419 assert!(world_xs.iter().any(|x| (*x - 100.0).abs() < 1e-9));
1420 assert!(world_xs.iter().any(|x| (*x - 200.0).abs() < 1e-9));
1421 }
1422}