Skip to main content

cityjson_types/raw/
views.rs

1//! Zero-copy view types for raw data access.
2
3/// A type-safe view over a contiguous slice.
4#[derive(Debug, Clone, Copy)]
5pub struct RawSliceView<'a, T> {
6    data: &'a [T],
7}
8
9impl<'a, T> RawSliceView<'a, T> {
10    #[inline]
11    pub fn new(data: &'a [T]) -> Self {
12        Self { data }
13    }
14
15    #[inline]
16    #[must_use]
17    pub fn as_slice(&self) -> &'a [T] {
18        self.data
19    }
20
21    #[inline]
22    #[must_use]
23    pub fn len(&self) -> usize {
24        self.data.len()
25    }
26
27    #[inline]
28    #[must_use]
29    pub fn is_empty(&self) -> bool {
30        self.data.is_empty()
31    }
32
33    #[inline]
34    #[must_use]
35    pub fn get(&self, index: usize) -> Option<&'a T> {
36        self.data.get(index)
37    }
38
39    #[inline]
40    pub fn iter(&self) -> std::slice::Iter<'a, T> {
41        self.into_iter()
42    }
43}
44
45impl<'a, T> IntoIterator for RawSliceView<'a, T> {
46    type Item = &'a T;
47    type IntoIter = std::slice::Iter<'a, T>;
48
49    fn into_iter(self) -> Self::IntoIter {
50        self.data.iter()
51    }
52}
53
54impl<'a, T> IntoIterator for &'_ RawSliceView<'a, T> {
55    type Item = &'a T;
56    type IntoIter = std::slice::Iter<'a, T>;
57
58    fn into_iter(self) -> Self::IntoIter {
59        self.data.iter()
60    }
61}
62
63/// A raw view over a resource pool.
64///
65/// Includes free slots (`None`) and generation counters.
66#[derive(Debug, Clone, Copy)]
67pub struct RawPoolView<'a, T> {
68    resources: &'a [Option<T>],
69    generations: &'a [u16],
70}
71
72impl<'a, T> RawPoolView<'a, T> {
73    #[inline]
74    pub fn new(resources: &'a [Option<T>], generations: &'a [u16]) -> Self {
75        Self {
76            resources,
77            generations,
78        }
79    }
80
81    #[inline]
82    #[must_use]
83    pub fn resources(&self) -> &'a [Option<T>] {
84        self.resources
85    }
86
87    #[inline]
88    #[must_use]
89    /// Generation counters for each slot.
90    ///
91    /// This is a low-level view intended for serializers and diagnostics.
92    pub fn generations(&self) -> &'a [u16] {
93        self.generations
94    }
95
96    #[inline]
97    #[must_use]
98    pub fn capacity(&self) -> usize {
99        self.resources.len()
100    }
101
102    #[must_use]
103    pub fn len(&self) -> usize {
104        self.resources.iter().filter(|r| r.is_some()).count()
105    }
106
107    #[inline]
108    #[must_use]
109    pub fn is_empty(&self) -> bool {
110        self.len() == 0
111    }
112
113    pub fn iter_occupied(&self) -> impl Iterator<Item = (usize, &'a T)> {
114        self.resources
115            .iter()
116            .enumerate()
117            .filter_map(|(i, opt)| opt.as_ref().map(|value| (i, value)))
118    }
119
120    #[must_use]
121    pub fn dense_index_remap(&self) -> DenseIndexRemap {
122        DenseIndexRemap::from_occupied_indices(
123            self.capacity(),
124            self.iter_occupied().map(|(i, _)| i),
125        )
126    }
127}
128
129/// Remaps sparse stored slot indices to dense export indices.
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct DenseIndexRemap {
132    dense_by_stored_index: Vec<Option<usize>>,
133    dense_len: usize,
134}
135
136impl DenseIndexRemap {
137    #[must_use]
138    pub fn identity(len: usize) -> Self {
139        Self {
140            dense_by_stored_index: (0..len).map(Some).collect(),
141            dense_len: len,
142        }
143    }
144
145    #[must_use]
146    pub fn from_occupied_indices(
147        capacity: usize,
148        occupied_indices: impl IntoIterator<Item = usize>,
149    ) -> Self {
150        let mut dense_by_stored_index = vec![None; capacity];
151        let mut dense_len = 0;
152
153        for stored_index in occupied_indices {
154            if let Some(slot) = dense_by_stored_index.get_mut(stored_index) {
155                *slot = Some(dense_len);
156                dense_len += 1;
157            }
158        }
159
160        Self {
161            dense_by_stored_index,
162            dense_len,
163        }
164    }
165
166    #[must_use]
167    pub fn get(&self, stored_index: usize) -> Option<usize> {
168        self.dense_by_stored_index
169            .get(stored_index)
170            .copied()
171            .flatten()
172    }
173
174    #[must_use]
175    pub fn len(&self) -> usize {
176        self.dense_len
177    }
178
179    #[must_use]
180    pub fn is_empty(&self) -> bool {
181        self.dense_len == 0
182    }
183
184    #[must_use]
185    pub fn as_slice(&self) -> &[Option<usize>] {
186        &self.dense_by_stored_index
187    }
188}
189
190/// Columnar view of quantized coordinates.
191#[derive(Debug, Clone, Copy)]
192pub struct ColumnarCoordinates<'a> {
193    pub x: &'a [i64],
194    pub y: &'a [i64],
195    pub z: &'a [i64],
196}
197
198/// Columnar view of real-world coordinates.
199#[derive(Debug, Clone, Copy)]
200pub struct ColumnarRealCoordinates<'a> {
201    pub x: &'a [f64],
202    pub y: &'a [f64],
203    pub z: &'a [f64],
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::CityModelType;
210    use crate::v2_0::appearance::ImageType;
211    use crate::v2_0::geometry::semantic::SemanticType;
212    use crate::v2_0::{
213        GeometryDraft, Material, OwnedCityModel, RingDraft, Semantic, SurfaceDraft, Texture,
214    };
215
216    #[test]
217    fn dense_index_remap_compacts_sparse_slots() {
218        let remap = DenseIndexRemap::from_occupied_indices(5, [1, 3, 4]);
219        assert_eq!(remap.as_slice(), &[None, Some(0), None, Some(1), Some(2)]);
220        assert_eq!(remap.len(), 3);
221    }
222
223    #[test]
224    fn raw_export_remapping_is_stable_for_geometry_resources_and_uvs() {
225        let mut model = OwnedCityModel::new(CityModelType::CityJSON);
226        let stale_semantic = model
227            .add_semantic(Semantic::new(SemanticType::RoofSurface))
228            .unwrap();
229        let stale_material = model
230            .add_material(Material::new("stale".to_string()))
231            .unwrap();
232        let stale_texture = model
233            .add_texture(Texture::new("stale.png".to_string(), ImageType::Png))
234            .unwrap();
235
236        let live_semantic = model
237            .add_semantic(Semantic::new(SemanticType::WallSurface))
238            .unwrap();
239        let live_material = model
240            .add_material(Material::new("live".to_string()))
241            .unwrap();
242        let live_texture = model
243            .add_texture(Texture::new("live.png".to_string(), ImageType::Png))
244            .unwrap();
245
246        let geometry_handle = GeometryDraft::multi_surface(
247            None,
248            [SurfaceDraft::new(
249                RingDraft::new([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]).with_texture(
250                    "theme".to_string(),
251                    live_texture,
252                    [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]],
253                ),
254                [],
255            )
256            .with_semantic(live_semantic)
257            .with_material("theme".to_string(), live_material)],
258        )
259        .insert_into(&mut model)
260        .unwrap();
261        assert!(model.remove_semantic(stale_semantic).is_some());
262        assert!(model.remove_material(stale_material).is_some());
263        assert!(model.remove_texture(stale_texture).is_some());
264
265        let raw = model.raw();
266        let semantic_remap = raw.semantics().dense_index_remap();
267        let material_remap = raw.materials().dense_index_remap();
268        let texture_remap = raw.textures().dense_index_remap();
269        let uv_remap = DenseIndexRemap::identity(raw.uv_coordinates().len());
270
271        assert_eq!(
272            semantic_remap.as_slice(),
273            raw.semantics().dense_index_remap().as_slice()
274        );
275        assert_eq!(
276            material_remap.as_slice(),
277            raw.materials().dense_index_remap().as_slice()
278        );
279        assert_eq!(
280            texture_remap.as_slice(),
281            raw.textures().dense_index_remap().as_slice()
282        );
283        assert_eq!(
284            uv_remap.as_slice(),
285            DenseIndexRemap::identity(raw.uv_coordinates().len()).as_slice()
286        );
287
288        let geometry = model.get_geometry(geometry_handle).unwrap();
289        let semantic_handle = geometry.semantics().unwrap().surfaces()[0].unwrap();
290        let (_material_theme, material_map) = geometry.materials().unwrap().first().unwrap();
291        let (_texture_theme, texture_map) = geometry.textures().unwrap().first().unwrap();
292
293        assert_eq!(
294            semantic_remap.get(semantic_handle.index() as usize),
295            Some(0)
296        );
297        assert_eq!(
298            material_remap.get(material_map.surfaces()[0].unwrap().index() as usize),
299            Some(0)
300        );
301        assert_eq!(
302            texture_remap.get(texture_map.ring_textures()[0].unwrap().index() as usize),
303            Some(0)
304        );
305
306        let remapped_uvs: Vec<_> = texture_map
307            .vertices()
308            .iter()
309            .map(|uv_ref| uv_remap.get(uv_ref.unwrap().to_usize()).unwrap())
310            .collect();
311        assert_eq!(remapped_uvs, vec![0, 1, 2]);
312    }
313}