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
6use std::collections::{BTreeSet, HashMap, HashSet};
7
8use crate::cityjson::resources::storage::OwnedStringStorage;
9use crate::cityjson::v2_0::attributes::Attributes;
10use crate::cityjson::v2_0::geometry::{
11    Geometry, GeometryType, StoredGeometryInstance, StoredGeometryParts,
12};
13use crate::cityjson::v2_0::metadata::BBox;
14use crate::cityjson::v2_0::{
15    CityObject, CityObjectIdentifier, MaterialMap, Metadata, SemanticMap, TextureMap, VertexIndex,
16};
17use crate::cityjson::{
18    CityModelType,
19    prelude::{
20        CityObjectHandle, GeometryHandle, GeometryTemplateHandle, MaterialHandle, SemanticHandle,
21        TextureHandle,
22    },
23    v2_0::Extensions,
24};
25use crate::{CityModel, Error, Result};
26
27type OwnedMetadata = Metadata<OwnedStringStorage>;
28type OwnedExtensions = Extensions<OwnedStringStorage>;
29type OwnedCityObject = CityObject<OwnedStringStorage>;
30type OwnedGeometry = Geometry<u32, OwnedStringStorage>;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub struct FilterOptions {
34    pub include_relatives: bool,
35}
36
37pub struct FilterContext<'a> {
38    model: &'a CityModel,
39    handle: CityObjectHandle,
40    cityobject: &'a CityObject<OwnedStringStorage>,
41}
42
43impl<'a> FilterContext<'a> {
44    pub fn model(&self) -> &'a CityModel {
45        self.model
46    }
47
48    pub fn handle(&self) -> CityObjectHandle {
49        self.handle
50    }
51
52    pub fn cityobject(&self) -> &'a CityObject<OwnedStringStorage> {
53        self.cityobject
54    }
55
56    pub fn id(&self) -> &'a str {
57        self.cityobject.id()
58    }
59}
60
61fn import_error(message: impl Into<String>) -> Error {
62    Error::Import(message.into())
63}
64
65fn unsupported(message: &'static str) -> Error {
66    Error::UnsupportedFeature(message.to_string())
67}
68
69fn same_transform(target: &CityModel, source: &CityModel) -> bool {
70    match source.transform() {
71        None => true,
72        Some(source_transform) => target.transform() == Some(source_transform),
73    }
74}
75
76fn append_kind_compatible(target_kind: CityModelType, source_kind: CityModelType) -> bool {
77    target_kind == source_kind
78        || (target_kind == CityModelType::CityJSON && source_kind == CityModelType::CityJSONFeature)
79}
80
81fn union_bbox(lhs: BBox, rhs: BBox) -> BBox {
82    BBox::new(
83        lhs.min_x().min(rhs.min_x()),
84        lhs.min_y().min(rhs.min_y()),
85        lhs.min_z().min(rhs.min_z()),
86        lhs.max_x().max(rhs.max_x()),
87        lhs.max_y().max(rhs.max_y()),
88        lhs.max_z().max(rhs.max_z()),
89    )
90}
91
92fn merge_attributes(
93    target: &mut Attributes<OwnedStringStorage>,
94    source: &Attributes<OwnedStringStorage>,
95) {
96    for (key, value) in source.iter() {
97        target.insert(key.clone(), value.clone());
98    }
99}
100
101fn merge_cityobject_extent(target: &mut OwnedCityObject, source: &OwnedCityObject) {
102    match (
103        target.geographical_extent().copied(),
104        source.geographical_extent().copied(),
105    ) {
106        (None, Some(extent)) => target.set_geographical_extent(Some(extent)),
107        (Some(lhs), Some(rhs)) if lhs != rhs => {
108            target.set_geographical_extent(Some(union_bbox(lhs, rhs)))
109        }
110        _ => {}
111    }
112}
113
114fn merge_metadata(target: &mut OwnedMetadata, source: &OwnedMetadata) {
115    if target.geographical_extent().is_none()
116        && let Some(extent) = source.geographical_extent().copied()
117    {
118        target.set_geographical_extent(extent);
119    } else if let (Some(lhs), Some(rhs)) = (
120        target.geographical_extent().copied(),
121        source.geographical_extent().copied(),
122    ) && lhs != rhs
123    {
124        target.set_geographical_extent(union_bbox(lhs, rhs));
125    }
126
127    if target.identifier().is_none()
128        && let Some(identifier) = source.identifier().cloned()
129    {
130        target.set_identifier(identifier);
131    }
132
133    if target.reference_date().is_none()
134        && let Some(date) = source.reference_date().cloned()
135    {
136        target.set_reference_date(date);
137    }
138
139    if target.reference_system().is_none()
140        && let Some(crs) = source.reference_system().cloned()
141    {
142        target.set_reference_system(crs);
143    }
144
145    if target.title().is_none()
146        && let Some(title) = source.title()
147    {
148        target.set_title(title.to_owned());
149    }
150
151    if target.point_of_contact().is_none()
152        && let Some(contact) = source.point_of_contact().cloned()
153    {
154        target.set_point_of_contact(Some(contact));
155    }
156
157    if let Some(extra) = source.extra() {
158        let target_extra = target.extra_mut();
159        for (key, value) in extra.iter() {
160            target_extra.insert(key.clone(), value.clone());
161        }
162    }
163}
164
165fn merge_root_extensions(target: &mut OwnedExtensions, source: &OwnedExtensions) {
166    for extension in source {
167        target.add(extension.clone());
168    }
169}
170
171fn remap_vertex_indices(
172    boundary: &crate::cityjson::v2_0::boundary::Boundary<u32>,
173    vertex_map: &[VertexIndex<u32>],
174) -> Result<crate::cityjson::v2_0::boundary::Boundary<u32>> {
175    let mut boundary = boundary.clone();
176    let remapped = boundary.vertices().iter().map(|index| {
177        vertex_map
178            .get(index.to_usize())
179            .copied()
180            .ok_or_else(|| import_error(format!("vertex index {} is out of range", index.value())))
181    });
182    boundary.set_vertices_from_iter(remapped.collect::<Result<Vec<_>>>()?);
183    Ok(boundary)
184}
185
186fn remap_texture_map(
187    map: &crate::cityjson::v2_0::geometry::TextureMapView<'_, u32>,
188    uv_map: &[VertexIndex<u32>],
189    texture_map: &HashMap<TextureHandle, TextureHandle>,
190) -> Result<TextureMap<u32>> {
191    let mut remapped = TextureMap::new();
192
193    for vertex in map.vertices() {
194        let mapped = vertex
195            .map(|index| {
196                uv_map.get(index.to_usize()).copied().ok_or_else(|| {
197                    import_error(format!("uv vertex index {} is out of range", index.value()))
198                })
199            })
200            .transpose()?;
201        remapped.add_vertex(mapped);
202    }
203
204    for ring in map.rings() {
205        remapped.add_ring(*ring);
206    }
207
208    for texture in map.ring_textures() {
209        remapped.add_ring_texture(
210            texture.map(|handle| texture_map.get(&handle).copied().unwrap_or(handle)),
211        );
212    }
213
214    Ok(remapped)
215}
216
217fn remap_material_map<'a, I, J, K>(
218    points: I,
219    linestrings: J,
220    surfaces: K,
221    material_map: &HashMap<MaterialHandle, MaterialHandle>,
222) -> MaterialMap<u32>
223where
224    I: IntoIterator<Item = &'a Option<MaterialHandle>>,
225    J: IntoIterator<Item = &'a Option<MaterialHandle>>,
226    K: IntoIterator<Item = &'a Option<MaterialHandle>>,
227{
228    let mut remapped = MaterialMap::new();
229
230    for item in points {
231        remapped.add_point(match item {
232            Some(handle) => Some(material_map.get(handle).copied().unwrap_or(*handle)),
233            None => None,
234        });
235    }
236    for item in linestrings {
237        remapped.add_linestring(match item {
238            Some(handle) => Some(material_map.get(handle).copied().unwrap_or(*handle)),
239            None => None,
240        });
241    }
242    for item in surfaces {
243        remapped.add_surface(match item {
244            Some(handle) => Some(material_map.get(handle).copied().unwrap_or(*handle)),
245            None => None,
246        });
247    }
248
249    remapped
250}
251
252fn remap_semantic_map<'a, I, J, K>(
253    points: I,
254    linestrings: J,
255    surfaces: K,
256    semantic_map: &HashMap<SemanticHandle, SemanticHandle>,
257) -> SemanticMap<u32>
258where
259    I: IntoIterator<Item = &'a Option<SemanticHandle>>,
260    J: IntoIterator<Item = &'a Option<SemanticHandle>>,
261    K: IntoIterator<Item = &'a Option<SemanticHandle>>,
262{
263    let mut remapped = SemanticMap::new();
264
265    for item in points {
266        remapped.add_point(match item {
267            Some(handle) => Some(semantic_map.get(handle).copied().unwrap_or(*handle)),
268            None => None,
269        });
270    }
271    for item in linestrings {
272        remapped.add_linestring(match item {
273            Some(handle) => Some(semantic_map.get(handle).copied().unwrap_or(*handle)),
274            None => None,
275        });
276    }
277    for item in surfaces {
278        remapped.add_surface(match item {
279            Some(handle) => Some(semantic_map.get(handle).copied().unwrap_or(*handle)),
280            None => None,
281        });
282    }
283
284    remapped
285}
286
287fn remap_geometry(
288    geometry: &OwnedGeometry,
289    vertex_map: &[VertexIndex<u32>],
290    template_map: &HashMap<GeometryTemplateHandle, GeometryTemplateHandle>,
291    semantic_map: &HashMap<SemanticHandle, SemanticHandle>,
292    material_map: &HashMap<MaterialHandle, MaterialHandle>,
293    texture_map: &HashMap<TextureHandle, TextureHandle>,
294    uv_map: &[VertexIndex<u32>],
295) -> Result<OwnedGeometry> {
296    let stored_parts = if let Some(instance) = geometry.instance() {
297        let template = template_map
298            .get(&instance.template())
299            .copied()
300            .ok_or_else(|| {
301                import_error(format!(
302                    "missing remap for geometry template {}",
303                    instance.template()
304                ))
305            })?;
306        Geometry::from_stored_parts(StoredGeometryParts {
307            type_geometry: GeometryType::GeometryInstance,
308            lod: None,
309            boundaries: None,
310            semantics: None,
311            materials: None,
312            textures: None,
313            instance: Some(StoredGeometryInstance {
314                template,
315                reference_point: *vertex_map
316                    .get(instance.reference_point().to_usize())
317                    .ok_or_else(|| {
318                        import_error(format!(
319                            "vertex index {} is out of range",
320                            instance.reference_point().value()
321                        ))
322                    })?,
323                transformation: instance.transformation(),
324            }),
325        })
326    } else {
327        let boundaries = geometry
328            .boundaries()
329            .map(|boundary| remap_vertex_indices(boundary, vertex_map))
330            .transpose()?;
331
332        let semantics = geometry.semantics().map(|theme| {
333            let points = theme.points();
334            let linestrings = theme.linestrings();
335            let surfaces = theme.surfaces();
336            remap_semantic_map(
337                points.iter(),
338                linestrings.iter(),
339                surfaces.iter(),
340                semantic_map,
341            )
342        });
343
344        let materials = geometry.materials().map(|themes| {
345            themes
346                .iter()
347                .map(|(name, theme)| {
348                    let points = theme.points();
349                    let linestrings = theme.linestrings();
350                    let surfaces = theme.surfaces();
351                    (
352                        name.clone(),
353                        remap_material_map(
354                            points.iter(),
355                            linestrings.iter(),
356                            surfaces.iter(),
357                            material_map,
358                        ),
359                    )
360                })
361                .collect::<Vec<_>>()
362        });
363
364        let textures = geometry
365            .textures()
366            .map(|themes| {
367                themes
368                    .iter()
369                    .map(|(name, theme)| {
370                        remap_texture_map(&theme, uv_map, texture_map)
371                            .map(|map| (name.clone(), map))
372                    })
373                    .collect::<Result<Vec<_>>>()
374            })
375            .transpose()?;
376
377        Geometry::from_stored_parts(StoredGeometryParts {
378            type_geometry: *geometry.type_geometry(),
379            lod: geometry.lod().copied(),
380            boundaries,
381            semantics,
382            materials,
383            textures,
384            instance: None,
385        })
386    };
387
388    Ok(stored_parts)
389}
390
391fn append_vertices(target: &mut CityModel, source: &CityModel) -> Result<Vec<VertexIndex<u32>>> {
392    let mut map = Vec::with_capacity(source.vertices().len());
393    for vertex in source.vertices().as_slice() {
394        map.push(target.add_vertex(*vertex)?);
395    }
396    Ok(map)
397}
398
399fn append_template_vertices(
400    target: &mut CityModel,
401    source: &CityModel,
402) -> Result<Vec<VertexIndex<u32>>> {
403    let mut map = Vec::with_capacity(source.template_vertices().len());
404    for vertex in source.template_vertices().as_slice() {
405        map.push(target.add_template_vertex(*vertex)?);
406    }
407    Ok(map)
408}
409
410fn append_uv_vertices(target: &mut CityModel, source: &CityModel) -> Result<Vec<VertexIndex<u32>>> {
411    let mut map = Vec::with_capacity(source.vertices_texture().len());
412    for uv in source.vertices_texture().as_slice() {
413        map.push(target.add_uv_coordinate((*uv).clone())?);
414    }
415    Ok(map)
416}
417
418fn append_semantics(
419    target: &mut CityModel,
420    source: &CityModel,
421) -> Result<HashMap<SemanticHandle, SemanticHandle>> {
422    let mut map = HashMap::with_capacity(source.semantic_count());
423    for (handle, semantic) in source.iter_semantics() {
424        map.insert(handle, target.add_semantic(semantic.clone())?);
425    }
426    Ok(map)
427}
428
429fn append_materials(
430    target: &mut CityModel,
431    source: &CityModel,
432) -> Result<HashMap<MaterialHandle, MaterialHandle>> {
433    let mut map = HashMap::with_capacity(source.material_count());
434    for (handle, material) in source.iter_materials() {
435        map.insert(handle, target.add_material(material.clone())?);
436    }
437    Ok(map)
438}
439
440fn append_textures(
441    target: &mut CityModel,
442    source: &CityModel,
443) -> Result<HashMap<TextureHandle, TextureHandle>> {
444    let mut map = HashMap::with_capacity(source.texture_count());
445    for (handle, texture) in source.iter_textures() {
446        map.insert(handle, target.add_texture(texture.clone())?);
447    }
448    Ok(map)
449}
450
451fn append_geometry_templates(
452    target: &mut CityModel,
453    source: &CityModel,
454    template_vertex_map: &[VertexIndex<u32>],
455    template_map: &HashMap<GeometryTemplateHandle, GeometryTemplateHandle>,
456    semantic_map: &HashMap<SemanticHandle, SemanticHandle>,
457    material_map: &HashMap<MaterialHandle, MaterialHandle>,
458    texture_map: &HashMap<TextureHandle, TextureHandle>,
459    uv_map: &[VertexIndex<u32>],
460) -> Result<HashMap<GeometryTemplateHandle, GeometryTemplateHandle>> {
461    let mut map = HashMap::with_capacity(source.geometry_template_count());
462    for (handle, geometry) in source.iter_geometry_templates() {
463        let remapped = remap_geometry(
464            geometry,
465            template_vertex_map,
466            template_map,
467            semantic_map,
468            material_map,
469            texture_map,
470            uv_map,
471        )?;
472        map.insert(handle, target.add_geometry_template(remapped)?);
473    }
474    Ok(map)
475}
476
477fn append_geometries(
478    target: &mut CityModel,
479    source: &CityModel,
480    vertex_map: &[VertexIndex<u32>],
481    template_map: &HashMap<GeometryTemplateHandle, GeometryTemplateHandle>,
482    semantic_map: &HashMap<SemanticHandle, SemanticHandle>,
483    material_map: &HashMap<MaterialHandle, MaterialHandle>,
484    texture_map: &HashMap<TextureHandle, TextureHandle>,
485    uv_map: &[VertexIndex<u32>],
486) -> Result<HashMap<GeometryHandle, GeometryHandle>> {
487    let mut map = HashMap::with_capacity(source.geometry_count());
488    for (handle, geometry) in source.iter_geometries() {
489        let remapped = remap_geometry(
490            geometry,
491            vertex_map,
492            template_map,
493            semantic_map,
494            material_map,
495            texture_map,
496            uv_map,
497        )?;
498        map.insert(handle, target.add_geometry(remapped)?);
499    }
500    Ok(map)
501}
502
503fn merge_cityobject(
504    target: &mut OwnedCityObject,
505    source: &OwnedCityObject,
506    cityobject_map: &HashMap<CityObjectHandle, CityObjectHandle>,
507    geometry_map: &HashMap<GeometryHandle, GeometryHandle>,
508) -> Result<()> {
509    if target.type_cityobject() != source.type_cityobject() {
510        return Err(import_error(format!(
511            "conflicting CityObject types for '{}'",
512            target.id()
513        )));
514    }
515
516    if let Some(attributes) = source.attributes() {
517        merge_attributes(target.attributes_mut(), attributes);
518    }
519    merge_cityobject_extent(target, source);
520
521    if let Some(extra) = source.extra() {
522        let target_extra = target.extra_mut();
523        for (key, value) in extra.iter() {
524            target_extra.insert(key.clone(), value.clone());
525        }
526    }
527
528    if let Some(geometry_handles) = source.geometry() {
529        let mut target_geometry = target
530            .geometry()
531            .map(|items| items.to_vec())
532            .unwrap_or_default();
533        for geometry in geometry_handles {
534            let mapped = geometry_map.get(geometry).copied().ok_or_else(|| {
535                import_error(format!(
536                    "missing remap for geometry {}",
537                    geometry.raw_parts().0
538                ))
539            })?;
540            if !target_geometry.contains(&mapped) {
541                target.add_geometry(mapped);
542                target_geometry.push(mapped);
543            }
544        }
545    }
546
547    if let Some(children) = source.children() {
548        let mut existing = target
549            .children()
550            .map(|items| items.to_vec())
551            .unwrap_or_default();
552        for child in children {
553            let mapped = cityobject_map.get(child).copied().ok_or_else(|| {
554                import_error(format!(
555                    "missing remap for cityobject {}",
556                    child.raw_parts().0
557                ))
558            })?;
559            if !existing.contains(&mapped) {
560                target.add_child(mapped);
561                existing.push(mapped);
562            }
563        }
564    }
565
566    if let Some(parents) = source.parents() {
567        let mut existing = target
568            .parents()
569            .map(|items| items.to_vec())
570            .unwrap_or_default();
571        for parent in parents {
572            let mapped = cityobject_map.get(parent).copied().ok_or_else(|| {
573                import_error(format!(
574                    "missing remap for cityobject {}",
575                    parent.raw_parts().0
576                ))
577            })?;
578            if !existing.contains(&mapped) {
579                target.add_parent(mapped);
580                existing.push(mapped);
581            }
582        }
583    }
584
585    Ok(())
586}
587
588fn merge_one(target: &mut CityModel, source: &CityModel) -> Result<()> {
589    if !same_transform(target, source) {
590        return Err(unsupported(
591            "model merge currently requires identical transform objects",
592        ));
593    }
594
595    if !append_kind_compatible(target.type_citymodel(), source.type_citymodel()) {
596        return Err(import_error(
597            "model merge currently requires compatible root types",
598        ));
599    }
600
601    if target.metadata().is_none() {
602        if let Some(metadata) = source.metadata() {
603            *target.metadata_mut() = metadata.clone();
604        }
605    } else if let Some(source_metadata) = source.metadata() {
606        merge_metadata(target.metadata_mut(), source_metadata);
607    }
608
609    if target.extra().is_none() {
610        if let Some(extra) = source.extra() {
611            *target.extra_mut() = extra.clone();
612        }
613    } else if let Some(extra) = source.extra() {
614        let target_extra = target.extra_mut();
615        for (key, value) in extra.iter() {
616            target_extra.insert(key.clone(), value.clone());
617        }
618    }
619
620    if target.extensions().is_none() {
621        if let Some(extensions) = source.extensions() {
622            *target.extensions_mut() = extensions.clone();
623        }
624    } else if let Some(extensions) = source.extensions() {
625        merge_root_extensions(target.extensions_mut(), extensions);
626    }
627
628    if target.transform().is_none() {
629        if let Some(transform) = source.transform() {
630            *target.transform_mut() = transform.clone();
631        }
632    }
633
634    if target.default_material_theme().is_none()
635        && let Some(theme) = source.default_material_theme().cloned()
636    {
637        target.set_default_material_theme(Some(theme));
638    }
639
640    if target.default_texture_theme().is_none()
641        && let Some(theme) = source.default_texture_theme().cloned()
642    {
643        target.set_default_texture_theme(Some(theme));
644    }
645
646    let vertex_map = append_vertices(target, source)?;
647    let template_vertex_map = append_template_vertices(target, source)?;
648    let uv_map = append_uv_vertices(target, source)?;
649    let semantic_map = append_semantics(target, source)?;
650    let material_map = append_materials(target, source)?;
651    let texture_map = append_textures(target, source)?;
652    let empty_template_map: HashMap<GeometryTemplateHandle, GeometryTemplateHandle> =
653        HashMap::new();
654    let template_map = append_geometry_templates(
655        target,
656        source,
657        &template_vertex_map,
658        &empty_template_map,
659        &semantic_map,
660        &material_map,
661        &texture_map,
662        &uv_map,
663    )?;
664    let geometry_map = append_geometries(
665        target,
666        source,
667        &vertex_map,
668        &template_map,
669        &semantic_map,
670        &material_map,
671        &texture_map,
672        &uv_map,
673    )?;
674
675    let mut cityobject_map = HashMap::with_capacity(source.cityobjects().len());
676    for (handle, source_cityobject) in source.cityobjects().iter() {
677        if let Some(existing) = target
678            .cityobjects()
679            .iter()
680            .find(|(_, cityobject)| cityobject.id() == source_cityobject.id())
681            .map(|(handle, _)| handle)
682        {
683            cityobject_map.insert(handle, existing);
684            continue;
685        }
686
687        let placeholder = CityObject::new(
688            CityObjectIdentifier::new(source_cityobject.id().to_owned()),
689            source_cityobject.type_cityobject().clone(),
690        );
691        let new_handle = target.cityobjects_mut().add(placeholder)?;
692        cityobject_map.insert(handle, new_handle);
693    }
694
695    if target.id().is_none()
696        && let Some(source_id) = source.id()
697        && let Some(mapped) = cityobject_map.get(&source_id).copied()
698    {
699        target.set_id(Some(mapped));
700    }
701
702    for (handle, source_cityobject) in source.cityobjects().iter() {
703        let target_handle = cityobject_map.get(&handle).copied().ok_or_else(|| {
704            import_error(format!(
705                "missing remap for cityobject {}",
706                source_cityobject.id()
707            ))
708        })?;
709        let target_cityobject =
710            target
711                .cityobjects_mut()
712                .get_mut(target_handle)
713                .ok_or_else(|| {
714                    import_error(format!(
715                        "missing target cityobject for {}",
716                        source_cityobject.id()
717                    ))
718                })?;
719        merge_cityobject(
720            target_cityobject,
721            source_cityobject,
722            &cityobject_map,
723            &geometry_map,
724        )?;
725    }
726
727    Ok(())
728}
729
730pub fn cleanup(model: &CityModel) -> Result<CityModel> {
731    cityjson_json::cleanup(model).map_err(Error::from)
732}
733
734fn collect_reachable_cityobjects<I>(
735    model: &CityModel,
736    roots: I,
737    include_parents: bool,
738    include_children: bool,
739) -> Result<HashSet<CityObjectHandle>>
740where
741    I: IntoIterator<Item = CityObjectHandle>,
742{
743    let mut selected = HashSet::new();
744    let mut stack = roots.into_iter().collect::<Vec<_>>();
745
746    while let Some(handle) = stack.pop() {
747        let cityobject = model.cityobjects().get(handle).ok_or_else(|| {
748            import_error(format!(
749                "missing CityObject handle in traversal: {handle:?}"
750            ))
751        })?;
752        if !selected.insert(handle) {
753            continue;
754        }
755
756        if include_children && let Some(children) = cityobject.children() {
757            stack.extend(children.iter().copied());
758        }
759
760        if include_parents && let Some(parents) = cityobject.parents() {
761            stack.extend(parents.iter().copied());
762        }
763    }
764
765    Ok(selected)
766}
767
768fn rebuild_model_with_cityobjects(
769    model: &CityModel,
770    selected: &HashSet<CityObjectHandle>,
771) -> Result<CityModel> {
772    let mut result = model.clone();
773    result.clear_cityobjects();
774
775    let mut old_to_new = HashMap::with_capacity(selected.len());
776    for (handle, cityobject) in model.cityobjects().iter() {
777        if !selected.contains(&handle) {
778            continue;
779        }
780
781        let mut cloned = cityobject.clone();
782        cloned.clear_children();
783        cloned.clear_parents();
784        let new_handle = result.cityobjects_mut().add(cloned)?;
785        old_to_new.insert(handle, new_handle);
786    }
787
788    for (handle, cityobject) in model.cityobjects().iter() {
789        if !selected.contains(&handle) {
790            continue;
791        }
792
793        let target_handle = *old_to_new.get(&handle).ok_or_else(|| {
794            import_error(format!("missing remap for CityObject {}", cityobject.id()))
795        })?;
796        let target = result
797            .cityobjects_mut()
798            .get_mut(target_handle)
799            .ok_or_else(|| {
800                import_error(format!("missing target CityObject {}", cityobject.id()))
801            })?;
802
803        if let Some(children) = cityobject.children() {
804            for child in children {
805                model.cityobjects().get(*child).ok_or_else(|| {
806                    import_error(format!("missing child CityObject handle {child:?}"))
807                })?;
808                if let Some(mapped) = old_to_new.get(child).copied() {
809                    target.add_child(mapped);
810                }
811            }
812        }
813
814        if let Some(parents) = cityobject.parents() {
815            for parent in parents {
816                model.cityobjects().get(*parent).ok_or_else(|| {
817                    import_error(format!("missing parent CityObject handle {parent:?}"))
818                })?;
819                if let Some(mapped) = old_to_new.get(parent).copied() {
820                    target.add_parent(mapped);
821                }
822            }
823        }
824    }
825
826    if let Some(root) = model.id() {
827        model
828            .cityobjects()
829            .get(root)
830            .ok_or_else(|| import_error("feature root references a missing CityObject"))?;
831        result.set_id(old_to_new.get(&root).copied());
832    }
833
834    Ok(result)
835}
836
837pub fn subset<'a, I>(model: &CityModel, cityobject_ids: I, exclude: bool) -> Result<CityModel>
838where
839    I: IntoIterator<Item = &'a str>,
840{
841    let ids = cityobject_ids
842        .into_iter()
843        .map(str::to_owned)
844        .collect::<BTreeSet<_>>();
845    if ids.is_empty() {
846        return Err(import_error(
847            "subset requires at least one CityObject identifier",
848        ));
849    }
850
851    let id_to_handle = model
852        .cityobjects()
853        .iter()
854        .map(|(handle, cityobject)| (cityobject.id().to_owned(), handle))
855        .collect::<HashMap<_, _>>();
856
857    let mut roots = Vec::new();
858    let mut matched_any = false;
859
860    for id in &ids {
861        if let Some(handle) = id_to_handle.get(id).copied() {
862            matched_any = true;
863            roots.push(handle);
864        }
865    }
866
867    if !matched_any {
868        return Err(import_error("subset selection matched no CityObjects"));
869    }
870
871    let mut selected = collect_reachable_cityobjects(model, roots, false, true)?;
872
873    if exclude {
874        let excluded = selected;
875        selected = model
876            .cityobjects()
877            .iter()
878            .map(|(handle, _)| handle)
879            .filter(|handle| !excluded.contains(handle))
880            .collect();
881    }
882
883    rebuild_model_with_cityobjects(model, &selected)
884}
885
886pub fn filter<F>(model: &CityModel, predicate: F) -> Result<CityModel>
887where
888    F: FnMut(FilterContext<'_>) -> bool,
889{
890    filter_with_options(model, FilterOptions::default(), predicate)
891}
892
893pub fn filter_with_options<F>(
894    model: &CityModel,
895    options: FilterOptions,
896    mut predicate: F,
897) -> Result<CityModel>
898where
899    F: FnMut(FilterContext<'_>) -> bool,
900{
901    let mut selected = model
902        .cityobjects()
903        .iter()
904        .filter_map(|(handle, cityobject)| {
905            predicate(FilterContext {
906                model,
907                handle,
908                cityobject,
909            })
910            .then_some(handle)
911        })
912        .collect::<HashSet<_>>();
913
914    if options.include_relatives {
915        selected = collect_reachable_cityobjects(model, selected, true, true)?;
916    }
917
918    rebuild_model_with_cityobjects(model, &selected)
919}
920
921pub fn append(target: &mut CityModel, source: &CityModel) -> Result<()> {
922    merge_one(target, source)
923}
924
925pub fn merge<I>(models: I) -> Result<CityModel>
926where
927    I: IntoIterator<Item = CityModel>,
928{
929    let mut models = models.into_iter();
930    let Some(mut merged) = models.next() else {
931        return Err(import_error("merge requires at least one model"));
932    };
933
934    for model in models {
935        merge_one(&mut merged, &model)?;
936    }
937
938    Ok(merged)
939}