From e5c14a9c90e57587bc3f72a6b414c67ec43c3cd0 Mon Sep 17 00:00:00 2001 From: HideBa Date: Sun, 12 Jan 2025 21:45:36 +0100 Subject: [PATCH] wip: refactor geometry encoder decoder --- src/rust/src/reader/mod.rs | 1 + src/rust/src/writer/geom_encoder.rs | 503 ++++++++++++++++++++++++++++ src/rust/src/writer/mod.rs | 2 +- src/rust/src/writer/serializer.rs | 102 +++--- src/rust/tests/e2e.rs | 35 ++ 5 files changed, 590 insertions(+), 53 deletions(-) create mode 100644 src/rust/src/writer/geom_encoder.rs diff --git a/src/rust/src/reader/mod.rs b/src/rust/src/reader/mod.rs index d44456a..f7215a0 100644 --- a/src/rust/src/reader/mod.rs +++ b/src/rust/src/reader/mod.rs @@ -11,6 +11,7 @@ use anyhow::{anyhow, Result}; use fallible_streaming_iterator::FallibleStreamingIterator; use std::io::{Read, Seek, SeekFrom, Write}; +pub mod geom_decoder; use std::marker::PhantomData; pub struct FcbReader { reader: R, diff --git a/src/rust/src/writer/geom_encoder.rs b/src/rust/src/writer/geom_encoder.rs new file mode 100644 index 0000000..ce99984 --- /dev/null +++ b/src/rust/src/writer/geom_encoder.rs @@ -0,0 +1,503 @@ +use cjseq::{ + Boundaries as CjBoundaries, Semantics as CjSemantics, + SemanticsSurface as CjSemanticsSurface, SemanticsValues as CjSemanticsValues, +}; + + +#[derive(Debug, Clone, Default)] +pub(crate) struct GMBoundaries { + pub(crate) solids: Vec, // Number of shells per solid + pub(crate) shells: Vec, // Number of surfaces per shell + pub(crate) surfaces: Vec, // Number of rings per surface + pub(crate) strings: Vec, // Number of indices per ring + pub(crate) indices: Vec, // Flattened list of all indices +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct GMSemantics { + pub(crate) surfaces: Vec, // List of semantic surfaces + pub(crate) values: Vec, // Semantic values corresponding to surfaces +} + +#[derive(Debug, Clone, Default)] +#[doc(hidden)] +pub(crate) struct EncodedGeometry { + pub(crate) boundaries: GMBoundaries, + pub(crate) semantics: Option, +} + +/// Encodes the provided CityJSON boundaries and semantics into flattened arrays. +/// +/// # Arguments +/// +/// * `boundaries` - Reference to the CityJSON boundaries to encode. +/// * `semantics` - Optional reference to the semantics associated with the boundaries. +/// +/// # Returns +/// Nothing. +pub(crate) fn encode( + cj_boundaries: &CjBoundaries, + semantics: Option<&CjSemantics>, +) -> EncodedGeometry { + let mut boundaries = GMBoundaries { + solids: vec![], + shells: vec![], + surfaces: vec![], + strings: vec![], + indices: vec![], + }; + // Encode the geometric boundaries + let _ = encode_boundaries(cj_boundaries, &mut boundaries); + + // Encode semantics if provided + let semantics = semantics.map(encode_semantics); + + EncodedGeometry { + boundaries, + semantics, + } +} + +/// Recursively encodes the CityJSON boundaries into flattened arrays. +/// +/// # Arguments +/// +/// * `boundaries` - Reference to the CityJSON boundaries to encode. +/// +/// # Returns +/// +/// The maximum depth encountered during encoding. +/// +/// # Panics +/// +/// Panics if the `max_depth` is not 1, 2, or 3, indicating an invalid geometry nesting depth. +fn encode_boundaries(boundaries: &CjBoundaries, wip_boundaries: &mut GMBoundaries) -> usize { + match boundaries { + // ------------------ + // (1) Leaf (indices) + // ------------------ + CjBoundaries::Indices(indices) => { + // Extend the flat list of indices with the current ring's indices + wip_boundaries.indices.extend_from_slice(indices); + + // Record the number of indices in the current ring + wip_boundaries.strings.push(indices.len() as u32); + + // Return the current depth level (1 for rings) + 1 // ring-level + } + // ------------------ + // (2) Nested + // ------------------ + CjBoundaries::Nested(sub_boundaries) => { + let mut max_depth = 0; + + // Recursively encode each sub-boundary and track the maximum depth + for sub in sub_boundaries { + let d = encode_boundaries(sub, wip_boundaries); + max_depth = max_depth.max(d); + } + + // Number of sub-boundaries at the current level + let length = sub_boundaries.len(); + + // Interpret the `max_depth` to determine the current geometry type + match max_depth { + // max_depth = 1 indicates the children are rings, so this level represents surfaces + 1 => { + wip_boundaries.surfaces.push(length as u32); + } + // max_depth = 2 indicates the children are surfaces, so this level represents shells + 2 => { + // Push the number of surfaces in this shell + wip_boundaries.shells.push(length as u32); + } + // max_depth = 3 indicates the children are shells, so this level represents solids + 3 => { + // Push the number of shells in this solid + wip_boundaries.solids.push(length as u32); + } + // Any other depth is invalid and should panic + _ => {} + } + + // Return the updated depth level + max_depth + 1 + } + } +} + +/// Encodes the semantic values into the encoder. +/// +/// # Arguments +/// +/// * `semantics_values` - Reference to the `SemanticsValues` to encode. +/// * `flattened` - Mutable reference to a vector where flattened semantics will be stored. +/// +/// # Returns +/// +/// The number of semantic values encoded. +fn encode_semantics_values( + semantics_values: &CjSemanticsValues, + flattened: &mut Vec, +) -> usize { + match semantics_values { + // ------------------ + // (1) Leaf (Indices) + // ------------------ + CjSemanticsValues::Indices(indices) => { + // Flatten the semantic values by converting each index to `Some(u32)` + flattened.extend_from_slice( + &indices + .iter() + .map(|i| if let Some(i) = i { *i } else { u32::MAX }) + .collect::>(), + ); + + flattened.len() + } + // ------------------ + // (2) Nested + // ------------------ + CjSemanticsValues::Nested(nested) => { + // Recursively encode each nested semantics value + for sub in nested { + encode_semantics_values(sub, flattened); + } + + // Return the updated length of the flattened vector + flattened.len() + } + } +} + +/// Encodes semantic surfaces and values from a CityJSON Semantics object. +/// +/// # Arguments +/// +/// * `semantics` - Reference to the CityJSON Semantics object containing surfaces and values +pub fn encode_semantics(semantics: &CjSemantics) -> GMSemantics { + let mut values = Vec::new(); + let _ = encode_semantics_values(&semantics.values, &mut values); + + GMSemantics { + surfaces: semantics.surfaces.to_vec(), + values, + } +} + +#[cfg(test)] +mod tests { + + + use super::*; + use anyhow::Result; + use cjseq::Geometry as CjGeometry; + + use serde_json::json; + + #[test] + fn test_encode_boundaries() -> Result<()> { + // MultiPoint + let boundaries = json!([2, 44, 0, 7]); + let boundaries: CjBoundaries = serde_json::from_value(boundaries)?; + let encoded_boundaries = encode(&boundaries, None); + assert_eq!(vec![2, 44, 0, 7], encoded_boundaries.boundaries.indices); + assert_eq!(vec![4], encoded_boundaries.boundaries.strings); + assert!(encoded_boundaries.boundaries.surfaces.is_empty()); + assert!(encoded_boundaries.boundaries.shells.is_empty()); + assert!(encoded_boundaries.boundaries.solids.is_empty()); + + // MultiLineString + let boundaries = json!([[2, 3, 5], [77, 55, 212]]); + let boundaries: CjBoundaries = serde_json::from_value(boundaries)?; + let encoded_boundaries = encode(&boundaries, None); + + assert_eq!( + vec![2, 3, 5, 77, 55, 212], + encoded_boundaries.boundaries.indices + ); + assert_eq!(vec![3, 3], encoded_boundaries.boundaries.strings); + assert_eq!(vec![2], encoded_boundaries.boundaries.surfaces); + assert!(encoded_boundaries.boundaries.shells.is_empty()); + assert!(encoded_boundaries.boundaries.solids.is_empty()); + + // MultiSurface + let boundaries = json!([[[0, 3, 2, 1]], [[4, 5, 6, 7]], [[0, 1, 5, 4]]]); + let boundaries: CjBoundaries = serde_json::from_value(boundaries)?; + let encoded_boundaries = encode(&boundaries, None); + + assert_eq!( + vec![0, 3, 2, 1, 4, 5, 6, 7, 0, 1, 5, 4], + encoded_boundaries.boundaries.indices + ); + assert_eq!(vec![4, 4, 4], encoded_boundaries.boundaries.strings); + assert_eq!(vec![1, 1, 1], encoded_boundaries.boundaries.surfaces); + assert_eq!(vec![3], encoded_boundaries.boundaries.shells); + assert!(encoded_boundaries.boundaries.solids.is_empty()); + + // Solid + let boundaries = json!([ + [ + [[0, 3, 2, 1, 22], [1, 2, 3, 4]], + [[4, 5, 6, 7]], + [[0, 1, 5, 4]], + [[1, 2, 6, 5]] + ], + [ + [[240, 243, 124]], + [[244, 246, 724]], + [[34, 414, 45]], + [[111, 246, 5]] + ] + ]); + let boundaries: CjBoundaries = serde_json::from_value(boundaries)?; + let encoded_boundaries = encode(&boundaries, None); + + assert_eq!( + vec![ + 0, 3, 2, 1, 22, 1, 2, 3, 4, 4, 5, 6, 7, 0, 1, 5, 4, 1, 2, 6, 5, 240, 243, 124, 244, + 246, 724, 34, 414, 45, 111, 246, 5 + ], + encoded_boundaries.boundaries.indices + ); + assert_eq!( + vec![5, 4, 4, 4, 4, 3, 3, 3, 3], + encoded_boundaries.boundaries.strings + ); + assert_eq!( + vec![2, 1, 1, 1, 1, 1, 1, 1], + encoded_boundaries.boundaries.surfaces + ); + assert_eq!(vec![4, 4], encoded_boundaries.boundaries.shells); + assert_eq!(vec![2], encoded_boundaries.boundaries.solids); + + // CompositeSolid + let boundaries = json!([ + [ + [ + [[0, 3, 2, 1, 22]], + [[4, 5, 6, 7]], + [[0, 1, 5, 4]], + [[1, 2, 6, 5]] + ], + [ + [[240, 243, 124]], + [[244, 246, 724]], + [[34, 414, 45]], + [[111, 246, 5]] + ] + ], + [[ + [[666, 667, 668]], + [[74, 75, 76]], + [[880, 881, 885]], + [[111, 122, 226]] + ]] + ]); + let boundaries: CjBoundaries = serde_json::from_value(boundaries)?; + let encoded_boundaries = encode(&boundaries, None); + assert_eq!( + vec![ + 0, 3, 2, 1, 22, 4, 5, 6, 7, 0, 1, 5, 4, 1, 2, 6, 5, 240, 243, 124, 244, 246, 724, + 34, 414, 45, 111, 246, 5, 666, 667, 668, 74, 75, 76, 880, 881, 885, 111, 122, 226 + ], + encoded_boundaries.boundaries.indices + ); + assert_eq!( + encoded_boundaries.boundaries.strings, + vec![5, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3] + ); + assert_eq!( + encoded_boundaries.boundaries.surfaces, + vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ); + assert_eq!(encoded_boundaries.boundaries.shells, vec![4, 4, 4]); + assert_eq!(encoded_boundaries.boundaries.solids, vec![2, 1]); + + Ok(()) + } + + #[test] + fn test_encode_semantics() -> Result<()> { + //MultiSurface + let multi_surfaces_gem_json = json!({ + "type": "MultiSurface", + "lod": "2", + "boundaries": [ + [ + [ + 0, + 3, + 2, + 1 + ] + ], + [ + [ + 4, + 5, + 6, + 7 + ] + ], + [ + [ + 0, + 1, + 5, + 4 + ] + ], + [ + [ + 0, + 2, + 3, + 8 + ] + ], + [ + [ + 10, + 12, + 23, + 48 + ] + ] + ], + "semantics": { + "surfaces": [ + { + "type": "WallSurface", + "slope": 33.4, + "children": [ + 2 + ] + }, + { + "type": "RoofSurface", + "slope": 66.6 + }, + { + "type": "OuterCeilingSurface", + "parent": 0, + "colour": "blue" + } + ], + "values": [ + 0, + 0, + null, + 1, + 2 + ] + } + }); + let multi_sufaces_geom: CjGeometry = serde_json::from_value(multi_surfaces_gem_json)?; + let CjGeometry { semantics, .. } = multi_sufaces_geom; + + let encoded_semantics = encode_semantics(&semantics.unwrap()); + + let expected_semantics_surfaces = vec![ + CjSemanticsSurface { + thetype: "WallSurface".to_string(), + parent: None, + children: Some(vec![2]), + other: json!({ + "slope": 33.4, + }), + }, + CjSemanticsSurface { + thetype: "RoofSurface".to_string(), + parent: None, + children: None, + other: json!({ + "slope": 66.6, + }), + }, + CjSemanticsSurface { + thetype: "OuterCeilingSurface".to_string(), + parent: Some(0), + children: None, + other: json!({ + "colour": "blue", + }), + }, + ]; + + let expected_semantics_values = vec![0, 0, u32::MAX, 1, 2]; + assert_eq!(expected_semantics_surfaces, encoded_semantics.surfaces); + assert_eq!( + expected_semantics_values, + encoded_semantics.values.as_slice().to_vec() + ); + + //CompositeSolid + let composite_solid_gem_json = json!({ + "type": "CompositeSolid", + "lod": "2.2", + "boundaries": [ + [ + [ + [[0, 3, 2, 1, 22]], + [[4, 5, 6, 7]], + [[0, 1, 5, 4]], + [[1, 2, 6, 5]] + ], + [ + [[240, 243, 124]], + [[244, 246, 724]], + [[34, 414, 45]], + [[111, 246, 5]] + ] + ]], + "semantics": { + "surfaces" : [ + { + "type": "RoofSurface" + }, + { + "type": "WallSurface" + } + ], + "values": [ + [ + [0, 1, 1, null] + ], + [ + [null, null, null] + ] + ] + } + } ); + let composite_solid_geom: CjGeometry = serde_json::from_value(composite_solid_gem_json)?; + let CjGeometry { semantics, .. } = composite_solid_geom; + + let encoded_semantics = encode_semantics(&semantics.unwrap()); + + let expected_semantics_surfaces = vec![ + CjSemanticsSurface { + thetype: "RoofSurface".to_string(), + parent: None, + children: None, + other: json!({}), + }, + CjSemanticsSurface { + thetype: "WallSurface".to_string(), + parent: None, + children: None, + other: json!({}), + }, + ]; + + let expected_semantics_values: Vec = + vec![0, 1, 1, u32::MAX, u32::MAX, u32::MAX, u32::MAX]; + assert_eq!(expected_semantics_surfaces, encoded_semantics.surfaces); + assert_eq!( + expected_semantics_values, + encoded_semantics.values.as_slice().to_vec() + ); + Ok(()) + } +} diff --git a/src/rust/src/writer/mod.rs b/src/rust/src/writer/mod.rs index 4d976f8..7f0deb7 100644 --- a/src/rust/src/writer/mod.rs +++ b/src/rust/src/writer/mod.rs @@ -9,7 +9,7 @@ use std::io::{BufWriter, Read, Seek, Write}; pub mod attribute; pub mod feature_writer; -pub mod geometry_encoderdecoder; +pub mod geom_encoder; pub mod header_writer; pub mod serializer; diff --git a/src/rust/src/writer/serializer.rs b/src/rust/src/writer/serializer.rs index 0203aa5..b647874 100644 --- a/src/rust/src/writer/serializer.rs +++ b/src/rust/src/writer/serializer.rs @@ -3,7 +3,7 @@ use crate::feature_generated::{ CityFeature, CityFeatureArgs, CityObject, CityObjectArgs, CityObjectType, Geometry, GeometryArgs, GeometryType, SemanticObject, SemanticObjectArgs, SemanticSurfaceType, Vertex, }; -use crate::geometry_encoderdecoder::FcbGeometryEncoderDecoder; +use crate::geom_encoder::encode; use crate::header_generated::{ GeographicalExtent, Header, HeaderArgs, ReferenceSystem, ReferenceSystemArgs, Transform, Vector, }; @@ -18,6 +18,8 @@ use cjseq::{ use flatbuffers::FlatBufferBuilder; use serde_json::Value; +use super::geom_encoder::{GMBoundaries, GMSemantics}; + /// ----------------------------------- /// Serializer for Header /// ----------------------------------- @@ -310,7 +312,7 @@ pub fn to_fcb_city_feature<'a>( /// * `fbb` - FlatBuffers builder instance /// * `id` - Object identifier /// * `co` - CityJSON city object -pub fn to_city_object<'a>( +pub(crate) fn to_city_object<'a>( fbb: &mut flatbuffers::FlatBufferBuilder<'a>, id: &str, co: &CjCityObject, @@ -319,18 +321,12 @@ pub fn to_city_object<'a>( let id = Some(fbb.create_string(id)); let type_ = to_co_type(&co.thetype); - let geographical_extent = co.geographical_extent.as_ref().map(|ge| { - let min = Vector::new(ge[0], ge[1], ge[2]); - let max = Vector::new(ge[3], ge[4], ge[5]); - GeographicalExtent::new(&min, &max) - }); + let geographical_extent = co.geographical_extent.as_ref().map(to_geographical_extent); let geometries = { - let geometries = co.geometry.as_ref().map(|geometries| { - geometries - .iter() - .map(|g| to_geometry(fbb, g)) - .collect::>() - }); + let geometries = co + .geometry + .as_ref() + .map(|gs| gs.iter().map(|g| to_geometry(fbb, g)).collect::>()); geometries.map(|geometries| fbb.create_vector(&geometries)) }; @@ -349,13 +345,12 @@ pub fn to_city_object<'a>( let (attributes, columns) = attributes_and_columns; - // todo: check if truncate is needed let children = { - let children_strings = co + let children = co .children .as_ref() .map(|c| c.iter().map(|s| fbb.create_string(s)).collect::>()); - children_strings.map(|children_strings| fbb.create_vector(&children_strings)) + children.map(|c| fbb.create_vector(&c)) }; let children_roles = { @@ -363,16 +358,15 @@ pub fn to_city_object<'a>( .children_roles .as_ref() .map(|c| c.iter().map(|r| fbb.create_string(r)).collect::>()); - children_roles_strings - .map(|children_roles_strings| fbb.create_vector(&children_roles_strings)) + children_roles_strings.map(|c| fbb.create_vector(&c)) }; let parents = { - let parents_strings = co + let parents = co .parents .as_ref() .map(|p| p.iter().map(|s| fbb.create_string(s)).collect::>()); - parents_strings.map(|parents_strings| fbb.create_vector(&parents_strings)) + parents.map(|p| fbb.create_vector(&p)) }; CityObject::create( @@ -396,7 +390,7 @@ pub fn to_city_object<'a>( /// # Arguments /// /// * `co_type` - String representation of CityJSON object type -pub fn to_co_type(co_type: &str) -> CityObjectType { +pub(crate) fn to_co_type(co_type: &str) -> CityObjectType { match co_type { "Bridge" => CityObjectType::Bridge, "BridgePart" => CityObjectType::BridgePart, @@ -502,43 +496,47 @@ pub fn to_geometry<'a>( let type_ = to_geom_type(&geometry.thetype); let lod = geometry.lod.as_ref().map(|lod| fbb.create_string(lod)); - let mut encoder_decoder = FcbGeometryEncoderDecoder::new(); - encoder_decoder.encode(&geometry.boundaries, geometry.semantics.as_ref()); - let (solids, shells, surfaces, strings, boundary_indices) = encoder_decoder.boundaries(); - let (semantics_surfaces, semantics_values) = encoder_decoder.semantics(); + let encoded = encode(&geometry.boundaries, geometry.semantics.as_ref()); + let GMBoundaries { + solids, + shells, + surfaces, + strings, + indices, + } = encoded.boundaries; + let semantics = encoded + .semantics + .map(|GMSemantics { surfaces, values }| (surfaces, values)); let solids = Some(fbb.create_vector(&solids)); let shells = Some(fbb.create_vector(&shells)); let surfaces = Some(fbb.create_vector(&surfaces)); let strings = Some(fbb.create_vector(&strings)); - let boundary_indices = Some(fbb.create_vector(&boundary_indices)); - - let semantics_objects = { - let semantics_objects = semantics_surfaces - .iter() - .map(|s| { - let children = s.children.clone().map(|c| fbb.create_vector(&c.to_vec())); - let semantics_type = to_semantic_surface_type(&s.thetype); - let semantic_object = SemanticObject::create( - fbb, - &SemanticObjectArgs { - type_: semantics_type, - attributes: None, - children, - parent: s.parent, - }, - ); - semantic_object - }) - .collect::>(); - if !semantics_objects.is_empty() { - Some(fbb.create_vector(&semantics_objects)) - } else { - None - } - }; + let boundary_indices = Some(fbb.create_vector(&indices)); - let semantics_values = Some(fbb.create_vector(semantics_values)); + let (semantics_objects, semantics_values) = + semantics.map_or((None, None), |(surface, values)| { + let semantics_objects = surface + .iter() + .map(|s| { + let children = s.children.as_ref().map(|c| fbb.create_vector(c)); + SemanticObject::create( + fbb, + &SemanticObjectArgs { + type_: to_semantic_surface_type(&s.thetype), + attributes: None, + children, + parent: s.parent, + }, + ) + }) + .collect::>(); + + ( + Some(fbb.create_vector(&semantics_objects)), + Some(fbb.create_vector(&values)), + ) + }); Geometry::create( fbb, diff --git a/src/rust/tests/e2e.rs b/src/rust/tests/e2e.rs index 684ce9a..3cefd97 100644 --- a/src/rust/tests/e2e.rs +++ b/src/rust/tests/e2e.rs @@ -111,6 +111,41 @@ fn test_cityjson_serialization_cycle() -> Result<()> { // Compare city objects assert_eq!(orig_feat.city_objects.len(), des_feat.city_objects.len()); for (id, orig_co) in orig_feat.city_objects.iter() { + // ===============remove these lines later================= + println!( + "is CityObject same? {:?}", + orig_co == des_feat.city_objects.get(id).unwrap() + ); + + println!( + "is attribute same======? {:?}", + orig_co.attributes == des_feat.city_objects.get(id).unwrap().attributes + ); + if orig_co.attributes != des_feat.city_objects.get(id).unwrap().attributes { + println!(" attributes======:"); + + let orig_attrs = orig_co.attributes.as_ref().unwrap(); + let des_attrs = des_feat + .city_objects + .get(id) + .unwrap() + .attributes + .as_ref() + .unwrap(); + if orig_attrs.is_object() && des_attrs.is_object() { + for (key, value) in orig_attrs.as_object().unwrap() { + let des_value = des_attrs.get(key); + if des_value.is_none() { + println!(" key not found: {:?}", key); + } else if value != des_value.unwrap() { + println!(" key: {:?}", key); + println!(" original: {:?}", value); + println!(" deserialized: {:?}", des_value.unwrap()); + } + } + } + } + // ===============remove these lines later================= // FIXME: Later, just compare CityObject using "==" let des_co = des_feat.city_objects.get(id).unwrap();