Skip to main content

cityjson_lib/
ops.rs

1//! Native workflow helpers for CityJSON models.
2//!
3//! The `subset` and `merge` semantics are ported from `cjio`, and the Rust
4//! implementation here is the crate-owned native rewrite of those workflows.
5//! Selection-driven extraction uses the opaque `ModelSelection` carrier.
6
7use 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/// Opaque selection carrier for selection/extraction workflows.
43#[derive(Debug, Clone, Default, PartialEq, Eq)]
44pub struct ModelSelection {
45    cityobjects: HashMap<CityObjectHandle, CityObjectSelection>,
46}
47
48/// Context passed to cityobject-level selection predicates.
49pub 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
73/// Context passed to geometry-level selection predicates.
74pub 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    /// Expand the selection through parent and child relations.
139    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    /// Combine two selections, preferring whole-cityobject selection.
155    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    /// Keep only the overlap between two selections.
183    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    /// Return `true` when no CityObjects are selected.
215    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
1293/// Build a CityObject selection by evaluating a predicate over each CityObject.
1294pub 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
1313/// Build a geometry selection by evaluating a predicate over every referenced geometry.
1314pub 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
1350/// Rebuild a model from a selection.
1351pub 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}