diff --git a/src/rust/src/bin/read.rs b/src/rust/src/bin/read.rs index 86f31a1..0cf13e0 100644 --- a/src/rust/src/bin/read.rs +++ b/src/rust/src/bin/read.rs @@ -1,4 +1,4 @@ -use flatcitybuf::fcb_deserializer::to_cj_metadata; +use flatcitybuf::deserializer::to_cj_metadata; use flatcitybuf::FcbReader; use std::error::Error; use std::fs::File; diff --git a/src/rust/src/bin/write.rs b/src/rust/src/bin/write.rs index 689a9b1..44dfef3 100644 --- a/src/rust/src/bin/write.rs +++ b/src/rust/src/bin/write.rs @@ -37,9 +37,9 @@ fn write_file() -> Result<(), Box> { } } } - let mut fcb = FcbWriter::new(cj, header_options, features.first(), Some(attr_schema))?; + let mut fcb = FcbWriter::new(cj, header_options, Some(attr_schema))?; fcb.write_feature()?; - for feature in features.iter().skip(1) { + for feature in features.iter() { fcb.add_feature(feature)?; } fcb.write(outputwriter)?; diff --git a/src/rust/src/cj_utils.rs b/src/rust/src/cj_utils.rs index e2afc56..e31fa80 100644 --- a/src/rust/src/cj_utils.rs +++ b/src/rust/src/cj_utils.rs @@ -86,6 +86,20 @@ pub fn read_cityjson_from_reader( parse_cityjson(reader, cj_type) } +/// Tests reading CityJSON data from a memory string +/// +/// # Arguments +/// None +/// +/// # Returns +/// * `Result<()>` - Ok if test passes, Error otherwise +/// +/// # Example +/// ``` +/// let test_data = include_str!("../tests/data/small.city.jsonl"); +/// let result = read_cityjson(test_data, CJTypeKind::Seq)?; +/// ``` + #[cfg(test)] mod tests { use std::{fs::File, path::PathBuf}; @@ -94,10 +108,11 @@ mod tests { #[test] fn test_read_from_memory() -> Result<()> { - let input_file = - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/small.city.jsonl"); - let reader = BufReader::new(File::open(input_file)?); - let result = read_cityjson_from_reader(reader, CJTypeKind::Seq)?; + let input_file = BufReader::new(File::open( + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/small.city.jsonl"), + )?); + let result = read_cityjson_from_reader(input_file, CJTypeKind::Seq)?; + if let CJType::Seq(seq) = result { assert_eq!(seq.features.len(), 3); } else { diff --git a/src/rust/src/fcb_serde/mod.rs b/src/rust/src/fcb_serde/mod.rs deleted file mode 100644 index eb732a6..0000000 --- a/src/rust/src/fcb_serde/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod fcb_deserializer; -pub mod fcb_serializer; diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 12dbc3f..2ff0f9d 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -2,7 +2,6 @@ mod cj_utils; mod error; -mod fcb_serde; #[allow(dead_code, unused_imports, clippy::all, warnings)] mod feature_generated; #[allow(dead_code, unused_imports, clippy::all, warnings)] @@ -11,7 +10,6 @@ mod reader; mod writer; pub use cj_utils::*; -pub use fcb_serde::*; pub use feature_generated::*; pub use header_generated::*; pub use reader::*; diff --git a/src/rust/src/main.rs b/src/rust/src/main.rs index 5baef8d..aaef3df 100644 --- a/src/rust/src/main.rs +++ b/src/rust/src/main.rs @@ -2,7 +2,7 @@ use anyhow::Result; use clap::{Parser, Subcommand}; use flatcitybuf::{ attribute::{AttributeSchema, AttributeSchemaMethods}, - fcb_deserializer, + deserializer, header_writer::{HeaderMetadata, HeaderWriterOptions}, read_cityjson_from_reader, CJType, CJTypeKind, CityJSONSeq, FcbReader, FcbWriter, }; @@ -98,7 +98,7 @@ fn serialize(input: &str, output: &str) -> Result<()> { write_index: false, header_metadata, }); - let mut fcb = FcbWriter::new(cj, header_options, None, attr_schema)?; + let mut fcb = FcbWriter::new(cj, header_options, attr_schema)?; fcb.write_feature()?; for feature in features.iter() { @@ -118,7 +118,7 @@ fn deserialize(input: &str, output: &str) -> Result<()> { let mut fcb_reader = FcbReader::open(reader)?.select_all_seq()?; let header = fcb_reader.header(); - let cj = fcb_deserializer::to_cj_metadata(&header)?; + let cj = deserializer::to_cj_metadata(&header)?; // Write header writeln!(writer, "{}", serde_json::to_string(&cj)?)?; diff --git a/src/rust/src/fcb_serde/fcb_deserializer.rs b/src/rust/src/reader/deserializer.rs similarity index 100% rename from src/rust/src/fcb_serde/fcb_deserializer.rs rename to src/rust/src/reader/deserializer.rs diff --git a/src/rust/src/reader/mod.rs b/src/rust/src/reader/mod.rs index 7d10b12..d44456a 100644 --- a/src/rust/src/reader/mod.rs +++ b/src/rust/src/reader/mod.rs @@ -1,9 +1,9 @@ -mod city_buffer; - +pub mod city_buffer; +pub mod deserializer; use city_buffer::FcbBuffer; use cjseq::CityJSONFeature; +use deserializer::to_cj_feature; -use crate::fcb_deserializer::to_cj_feature; use crate::feature_generated::{size_prefixed_root_as_city_feature, CityFeature}; use crate::header_generated::*; use crate::{check_magic_bytes, HEADER_MAX_BUFFER_SIZE}; diff --git a/src/rust/src/writer/attribute.rs b/src/rust/src/writer/attribute.rs index 0b7ec61..f0712d1 100644 --- a/src/rust/src/writer/attribute.rs +++ b/src/rust/src/writer/attribute.rs @@ -1,7 +1,7 @@ use crate::header_generated::ColumnType; use byteorder::{ByteOrder, LittleEndian}; use serde_json::Value; -use std::{collections::HashMap, u16}; +use std::collections::HashMap; pub type AttributeSchema = HashMap; diff --git a/src/rust/src/writer/feature_writer.rs b/src/rust/src/writer/feature_writer.rs index fed2561..4f9654f 100644 --- a/src/rust/src/writer/feature_writer.rs +++ b/src/rust/src/writer/feature_writer.rs @@ -1,6 +1,6 @@ use cjseq::CityJSONFeature; -use crate::fcb_serde::fcb_serializer::*; +use crate::serializer::*; use super::attribute::AttributeSchema; @@ -43,17 +43,11 @@ impl<'a> FeatureWriter<'a> { /// /// A vector of bytes containing the serialized feature pub fn finish_to_feature(&mut self) -> Vec { - let city_objects_buf: Vec<_> = self - .city_feature - .city_objects - .iter() - .map(|(id, co)| to_fcb_city_object(&mut self.fbb, id, co, &self.attr_schema)) - .collect(); let cf_buf = to_fcb_city_feature( &mut self.fbb, self.city_feature.id.as_str(), - &city_objects_buf, - &self.city_feature.vertices, + self.city_feature, + &self.attr_schema, ); self.fbb.finish_size_prefixed(cf_buf, None); let buf = self.fbb.finished_data().to_vec(); diff --git a/src/rust/src/writer/header_writer.rs b/src/rust/src/writer/header_writer.rs index 685d4c6..edd6dbd 100644 --- a/src/rust/src/writer/header_writer.rs +++ b/src/rust/src/writer/header_writer.rs @@ -1,4 +1,4 @@ -use crate::fcb_serializer::to_fcb_header; +use crate::serializer::to_fcb_header; use cjseq::CityJSON; use flatbuffers::FlatBufferBuilder; diff --git a/src/rust/src/writer/mod.rs b/src/rust/src/writer/mod.rs index fad4d17..4d976f8 100644 --- a/src/rust/src/writer/mod.rs +++ b/src/rust/src/writer/mod.rs @@ -11,6 +11,7 @@ pub mod attribute; pub mod feature_writer; pub mod geometry_encoderdecoder; pub mod header_writer; +pub mod serializer; /// Main writer for FlatCityBuf (FCB) format /// @@ -42,16 +43,14 @@ impl<'a> FcbWriter<'a> { pub fn new( cj: CityJSON, header_option: Option, - first_feature: Option<&'a CityJSONFeature>, attr_schema: Option, ) -> Result { let attr_schema = attr_schema.unwrap_or_default(); let header_writer = HeaderWriter::new(cj, header_option, attr_schema.clone()); - let feat_writer = first_feature.map(|feat| FeatureWriter::new(feat, attr_schema.clone())); Ok(Self { header_writer, - feat_writer, + feat_writer: None, tmpout: BufWriter::new(tempfile::tempfile()?), attr_schema, }) @@ -110,17 +109,12 @@ impl<'a> FcbWriter<'a> { out.write_all(&MAGIC_BYTES)?; let header_buf = self.header_writer.finish_to_header(); - println!("header buf size: {} bytes", header_buf.len()); out.write_all(&header_buf)?; self.tmpout.rewind()?; let mut unsorted_feature_output = self.tmpout.into_inner().map_err(|e| e.into_error())?; let mut feature_buf: Vec = Vec::new(); unsorted_feature_output.read_to_end(&mut feature_buf)?; - println!( - "unsorted_feature_output buf size: {} bytes", - feature_buf.len() - ); out.write_all(&feature_buf)?; Ok(()) diff --git a/src/rust/src/fcb_serde/fcb_serializer.rs b/src/rust/src/writer/serializer.rs similarity index 73% rename from src/rust/src/fcb_serde/fcb_serializer.rs rename to src/rust/src/writer/serializer.rs index 620377e..0203aa5 100644 --- a/src/rust/src/fcb_serde/fcb_serializer.rs +++ b/src/rust/src/writer/serializer.rs @@ -11,8 +11,9 @@ use crate::header_writer::HeaderMetadata; use crate::{Column, ColumnArgs}; use cjseq::{ - CityJSON, CityObject as CjCityObject, Geometry as CjGeometry, GeometryType as CjGeometryType, - Metadata as CjMetadata, Transform as CjTransform, + CityJSON, CityJSONFeature, CityObject as CjCityObject, Geometry as CjGeometry, + GeometryType as CjGeometryType, PointOfContact as CjPointOfContact, + ReferenceSystem as CjReferenceSystem, Transform as CjTransform, }; use flatbuffers::FlatBufferBuilder; use serde_json::Value; @@ -34,100 +35,98 @@ pub fn to_fcb_header<'a>( header_metadata: HeaderMetadata, attr_schema: &AttributeSchema, ) -> flatbuffers::WIPOffset> { - let metadata = cj - .metadata - .as_ref() - .ok_or(anyhow::anyhow!("metadata is missing")) - .unwrap(); - // let metadata = cj - // .metadata - // .as_ref() - // .ok_or(anyhow::anyhow!(Error::MissingField("metadata".to_string()))) - // .unwrap(); - let reference_system = to_fcb_reference_system(fbb, metadata); - let transform = to_fcb_transform(&cj.transform); - let geographical_extent = metadata - .geographical_extent - .as_ref() - .map(to_fcb_geographical_extent); - let header_args = HeaderArgs { - version: Some(fbb.create_string(&cj.version)), - transform: Some(&transform), - columns: Some(to_fcb_columns(fbb, attr_schema)), - features_count: header_metadata.features_count, - geographical_extent: geographical_extent.as_ref(), - reference_system, - identifier: metadata.identifier.as_ref().map(|i| fbb.create_string(i)), - reference_date: metadata - .reference_date - .as_ref() - .map(|r| fbb.create_string(r)), - title: metadata.title.as_ref().map(|t| fbb.create_string(t)), - poc_contact_name: metadata - .point_of_contact - .as_ref() - .map(|poc| fbb.create_string(&poc.contact_name)), - poc_contact_type: metadata - .point_of_contact + let version = Some(fbb.create_string(&cj.version)); + let transform = to_transform(&cj.transform); + let features_count: u64 = header_metadata.features_count; + let columns = Some(to_columns(fbb, attr_schema)); + + if let Some(meta) = cj.metadata.as_ref() { + let reference_system = meta + .reference_system .as_ref() - .and_then(|poc| poc.contact_type.as_ref().map(|ct| fbb.create_string(ct))), - poc_role: metadata - .point_of_contact - .as_ref() - .and_then(|poc| poc.role.as_ref().map(|r| fbb.create_string(r))), - poc_phone: metadata - .point_of_contact + .map(|ref_sys| to_reference_system(fbb, ref_sys)); + let geographical_extent = meta + .geographical_extent .as_ref() - .and_then(|poc| poc.phone.as_ref().map(|p| fbb.create_string(p))), - poc_email: metadata + .map(to_geographical_extent); + let identifier = meta.identifier.as_ref().map(|i| fbb.create_string(i)); + let reference_date = meta.reference_date.as_ref().map(|r| fbb.create_string(r)); + let title = meta.title.as_ref().map(|t| fbb.create_string(t)); + let poc_fields = meta .point_of_contact .as_ref() - .map(|poc| fbb.create_string(&poc.email_address)), - poc_website: metadata - .point_of_contact - .as_ref() - .and_then(|poc| poc.website.as_ref().map(|w| fbb.create_string(w))), - poc_address_thoroughfare_number: metadata.point_of_contact.as_ref().and_then(|poc| { - poc.address - .as_ref() - .map(|a| fbb.create_string(&a.thoroughfare_number.to_string())) - }), - poc_address_thoroughfare_name: metadata.point_of_contact.as_ref().map(|poc| { - fbb.create_string( - &poc.address - .as_ref() - .map(|a| a.thoroughfare_name.clone()) - .unwrap_or_default(), - ) - }), - poc_address_locality: metadata.point_of_contact.as_ref().map(|poc| { - fbb.create_string( - &poc.address - .as_ref() - .map(|a| a.locality.clone()) - .unwrap_or_default(), - ) - }), - poc_address_postcode: metadata.point_of_contact.as_ref().map(|poc| { - fbb.create_string( - &poc.address - .as_ref() - .map(|a| a.postal_code.clone()) - .unwrap_or_default(), - ) - }), - poc_address_country: metadata.point_of_contact.as_ref().map(|poc| { - fbb.create_string( - &poc.address - .as_ref() - .map(|a| a.country.clone()) - .unwrap_or_default(), - ) - }), - attributes: None, - }; - - Header::create(fbb, &header_args) + .map(|poc| to_point_of_contact(fbb, poc)); + + let ( + poc_contact_name, + poc_contact_type, + poc_role, + poc_phone, + poc_email, + poc_website, + poc_address_thoroughfare_number, + poc_address_thoroughfare_name, + poc_address_locality, + poc_address_postcode, + poc_address_country, + ) = poc_fields.map_or( + ( + None, None, None, None, None, None, None, None, None, None, None, + ), + |poc| { + ( + poc.poc_contact_name, + poc.poc_contact_type, + poc.poc_role, + poc.poc_phone, + poc.poc_email, + poc.poc_website, + poc.poc_address_thoroughfare_number, + poc.poc_address_thoroughfare_name, + poc.poc_address_locality, + poc.poc_address_postcode, + poc.poc_address_country, + ) + }, + ); + Header::create( + fbb, + &HeaderArgs { + transform: Some(transform).as_ref(), + columns, + features_count, + geographical_extent: geographical_extent.as_ref(), + reference_system, + identifier, + reference_date, + title, + poc_contact_name, + poc_contact_type, + poc_role, + poc_phone, + poc_email, + poc_website, + poc_address_thoroughfare_number, + poc_address_thoroughfare_name, + poc_address_locality, + poc_address_postcode, + poc_address_country, + attributes: None, + version, + }, + ) + } else { + Header::create( + fbb, + &HeaderArgs { + transform: Some(transform).as_ref(), + columns, + features_count, + version, + ..Default::default() + }, + ) + } } /// Converts CityJSON geographical extent to FlatBuffers format @@ -135,7 +134,7 @@ pub fn to_fcb_header<'a>( /// # Arguments /// /// * `geographical_extent` - Array of 6 values [minx, miny, minz, maxx, maxy, maxz] -pub(crate) fn to_fcb_geographical_extent(geographical_extent: &[f64; 6]) -> GeographicalExtent { +pub(crate) fn to_geographical_extent(geographical_extent: &[f64; 6]) -> GeographicalExtent { let min = Vector::new( geographical_extent[0], geographical_extent[1], @@ -154,7 +153,7 @@ pub(crate) fn to_fcb_geographical_extent(geographical_extent: &[f64; 6]) -> Geog /// # Arguments /// /// * `transform` - CityJSON transform containing scale and translate values -pub(crate) fn to_fcb_transform(transform: &CjTransform) -> Transform { +pub(crate) fn to_transform(transform: &CjTransform) -> Transform { let scale = Vector::new(transform.scale[0], transform.scale[1], transform.scale[2]); let translate = Vector::new( transform.translate[0], @@ -170,107 +169,94 @@ pub(crate) fn to_fcb_transform(transform: &CjTransform) -> Transform { /// /// * `fbb` - FlatBuffers builder instance /// * `metadata` - CityJSON metadata containing reference system information -pub(crate) fn to_fcb_reference_system<'a>( +pub(crate) fn to_reference_system<'a>( fbb: &mut FlatBufferBuilder<'a>, - metadata: &CjMetadata, -) -> Option>> { - metadata.reference_system.as_ref().map(|ref_sys| { - let authority = Some(fbb.create_string(&ref_sys.authority)); - - let version = ref_sys.version.parse::().unwrap_or_else(|e| { - println!("failed to parse version: {}", e); - 0 - }); - let code = ref_sys.code.parse::().unwrap_or_else(|e| { - println!("failed to parse code: {}", e); - 0 - }); - - let code_string = None; // TODO: implement code_string - - ReferenceSystem::create( - fbb, - &ReferenceSystemArgs { - authority, - version, - code, - code_string, - }, - ) - }) -} - -/// ----------------------------------- -/// Serializer for CityJSONFeature -/// ----------------------------------- - -/// Converts CityJSON object type to FlatBuffers enum -/// -/// # Arguments -/// -/// * `co_type` - String representation of CityJSON object type -pub fn to_fcb_city_object_type(co_type: &str) -> CityObjectType { - match co_type { - "Bridge" => CityObjectType::Bridge, - "BridgePart" => CityObjectType::BridgePart, - "BridgeInstallation" => CityObjectType::BridgeInstallation, - "BridgeConstructiveElement" => CityObjectType::BridgeConstructiveElement, - "BridgeRoom" => CityObjectType::BridgeRoom, - "BridgeFurniture" => CityObjectType::BridgeFurniture, - - "Building" => CityObjectType::Building, - "BuildingPart" => CityObjectType::BuildingPart, - "BuildingInstallation" => CityObjectType::BuildingInstallation, - "BuildingConstructiveElement" => CityObjectType::BuildingConstructiveElement, - "BuildingFurniture" => CityObjectType::BuildingFurniture, - "BuildingStorey" => CityObjectType::BuildingStorey, - "BuildingRoom" => CityObjectType::BuildingRoom, - "BuildingUnit" => CityObjectType::BuildingUnit, + ref_system: &CjReferenceSystem, +) -> flatbuffers::WIPOffset> { + let authority = Some(fbb.create_string(&ref_system.authority)); - "CityFurniture" => CityObjectType::CityFurniture, - "CityObjectGroup" => CityObjectType::CityObjectGroup, - "GenericCityObject" => CityObjectType::GenericCityObject, - "LandUse" => CityObjectType::LandUse, - "OtherConstruction" => CityObjectType::OtherConstruction, - "PlantCover" => CityObjectType::PlantCover, - "SolitaryVegetationObject" => CityObjectType::SolitaryVegetationObject, - "TINRelief" => CityObjectType::TINRelief, + let version = ref_system.version.parse::().unwrap_or_else(|e| { + println!("failed to parse version: {}", e); + 0 + }); + let code = ref_system.code.parse::().unwrap_or_else(|e| { + println!("failed to parse code: {}", e); + 0 + }); - "Road" => CityObjectType::Road, - "Railway" => CityObjectType::Railway, - "Waterway" => CityObjectType::Waterway, - "TransportSquare" => CityObjectType::TransportSquare, + let code_string = None; // TODO: implement code_string - "Tunnel" => CityObjectType::Tunnel, - "TunnelPart" => CityObjectType::TunnelPart, - "TunnelInstallation" => CityObjectType::TunnelInstallation, - "TunnelConstructiveElement" => CityObjectType::TunnelConstructiveElement, - "TunnelHollowSpace" => CityObjectType::TunnelHollowSpace, - "TunnelFurniture" => CityObjectType::TunnelFurniture, + ReferenceSystem::create( + fbb, + &ReferenceSystemArgs { + authority, + version, + code, + code_string, + }, + ) +} - "WaterBody" => CityObjectType::WaterBody, - _ => CityObjectType::GenericCityObject, - } +/// Internal struct used only as a return type for `to_point_of_contact` +#[doc(hidden)] +struct FcbPointOfContact<'a> { + poc_contact_name: Option>, + poc_contact_type: Option>, + poc_role: Option>, + poc_phone: Option>, + poc_email: Option>, + poc_website: Option>, + poc_address_thoroughfare_number: Option>, + poc_address_thoroughfare_name: Option>, + poc_address_locality: Option>, + poc_address_postcode: Option>, + poc_address_country: Option>, } -/// Converts CityJSON geometry type to FlatBuffers enum -/// -/// # Arguments -/// -/// * `geometry_type` - CityJSON geometry type -pub(crate) fn to_fcb_geometry_type(geometry_type: &CjGeometryType) -> GeometryType { - match geometry_type { - CjGeometryType::MultiPoint => GeometryType::MultiPoint, - CjGeometryType::MultiLineString => GeometryType::MultiLineString, - CjGeometryType::MultiSurface => GeometryType::MultiSurface, - CjGeometryType::CompositeSurface => GeometryType::CompositeSurface, - CjGeometryType::Solid => GeometryType::Solid, - CjGeometryType::MultiSolid => GeometryType::MultiSolid, - CjGeometryType::CompositeSolid => GeometryType::CompositeSolid, - _ => GeometryType::Solid, +fn to_point_of_contact<'a>( + fbb: &mut FlatBufferBuilder<'a>, + poc: &CjPointOfContact, +) -> FcbPointOfContact<'a> { + let poc_contact_name = Some(fbb.create_string(&poc.contact_name)); + + let poc_contact_type = poc.contact_type.as_ref().map(|ct| fbb.create_string(ct)); + let poc_role = poc.role.as_ref().map(|r| fbb.create_string(r)); + let poc_phone = poc.phone.as_ref().map(|p| fbb.create_string(p)); + let poc_email = Some(fbb.create_string(&poc.email_address)); + let poc_website = poc.website.as_ref().map(|w| fbb.create_string(w)); + let poc_address_thoroughfare_number = poc + .address + .as_ref() + .map(|a| fbb.create_string(&a.thoroughfare_number.to_string())); + let poc_address_thoroughfare_name = poc + .address + .as_ref() + .map(|a| fbb.create_string(&a.thoroughfare_name)); + let poc_address_locality = poc.address.as_ref().map(|a| fbb.create_string(&a.locality)); + let poc_address_postcode = poc + .address + .as_ref() + .map(|a| fbb.create_string(&a.postal_code)); + let poc_address_country = poc.address.as_ref().map(|a| fbb.create_string(&a.country)); + FcbPointOfContact { + poc_contact_name, + poc_contact_type, + poc_role, + poc_phone, + poc_email, + poc_website, + poc_address_thoroughfare_number, + poc_address_thoroughfare_name, + poc_address_locality, + poc_address_postcode, + poc_address_country, } } +/// ----------------------------------- +/// Serializer for CityJSONFeature +/// ----------------------------------- + /// Creates a CityFeature in FlatBuffers format /// /// # Arguments @@ -282,14 +268,20 @@ pub(crate) fn to_fcb_geometry_type(geometry_type: &CjGeometryType) -> GeometryTy pub fn to_fcb_city_feature<'a>( fbb: &mut flatbuffers::FlatBufferBuilder<'a>, id: &str, - objects: &[flatbuffers::WIPOffset>], - vertices: &[Vec], + city_feature: &CityJSONFeature, + attr_schema: &AttributeSchema, ) -> flatbuffers::WIPOffset> { let id = Some(fbb.create_string(id)); - let objects = Some(fbb.create_vector(objects)); + let city_objects: Vec<_> = city_feature + .city_objects + .iter() + .map(|(id, co)| to_city_object(fbb, id, co, attr_schema)) + .collect(); + let objects = Some(fbb.create_vector(&city_objects)); let vertices = Some( fbb.create_vector( - &vertices + &city_feature + .vertices .iter() .map(|v| { Vertex::new( @@ -318,7 +310,7 @@ pub fn to_fcb_city_feature<'a>( /// * `fbb` - FlatBuffers builder instance /// * `id` - Object identifier /// * `co` - CityJSON city object -pub fn to_fcb_city_object<'a>( +pub fn to_city_object<'a>( fbb: &mut flatbuffers::FlatBufferBuilder<'a>, id: &str, co: &CjCityObject, @@ -326,7 +318,7 @@ pub fn to_fcb_city_object<'a>( ) -> flatbuffers::WIPOffset> { let id = Some(fbb.create_string(id)); - let type_ = to_fcb_city_object_type(&co.thetype); + 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]); @@ -336,7 +328,7 @@ pub fn to_fcb_city_object<'a>( let geometries = co.geometry.as_ref().map(|geometries| { geometries .iter() - .map(|g| to_fcb_geometry(fbb, g)) + .map(|g| to_geometry(fbb, g)) .collect::>() }); geometries.map(|geometries| fbb.create_vector(&geometries)) @@ -350,7 +342,7 @@ pub fn to_fcb_city_object<'a>( return (None, None); } let (attr_vec, own_schema) = to_fcb_attribute(fbb, attr, attr_schema); - let columns = own_schema.map(|schema| to_fcb_columns(fbb, &schema)); + let columns = own_schema.map(|schema| to_columns(fbb, &schema)); (Some(attr_vec), columns) }) .unwrap_or((None, None)); @@ -399,12 +391,79 @@ pub fn to_fcb_city_object<'a>( ) } +/// Converts CityJSON object type to FlatBuffers enum +/// +/// # Arguments +/// +/// * `co_type` - String representation of CityJSON object type +pub fn to_co_type(co_type: &str) -> CityObjectType { + match co_type { + "Bridge" => CityObjectType::Bridge, + "BridgePart" => CityObjectType::BridgePart, + "BridgeInstallation" => CityObjectType::BridgeInstallation, + "BridgeConstructiveElement" => CityObjectType::BridgeConstructiveElement, + "BridgeRoom" => CityObjectType::BridgeRoom, + "BridgeFurniture" => CityObjectType::BridgeFurniture, + + "Building" => CityObjectType::Building, + "BuildingPart" => CityObjectType::BuildingPart, + "BuildingInstallation" => CityObjectType::BuildingInstallation, + "BuildingConstructiveElement" => CityObjectType::BuildingConstructiveElement, + "BuildingFurniture" => CityObjectType::BuildingFurniture, + "BuildingStorey" => CityObjectType::BuildingStorey, + "BuildingRoom" => CityObjectType::BuildingRoom, + "BuildingUnit" => CityObjectType::BuildingUnit, + + "CityFurniture" => CityObjectType::CityFurniture, + "CityObjectGroup" => CityObjectType::CityObjectGroup, + "GenericCityObject" => CityObjectType::GenericCityObject, + "LandUse" => CityObjectType::LandUse, + "OtherConstruction" => CityObjectType::OtherConstruction, + "PlantCover" => CityObjectType::PlantCover, + "SolitaryVegetationObject" => CityObjectType::SolitaryVegetationObject, + "TINRelief" => CityObjectType::TINRelief, + + "Road" => CityObjectType::Road, + "Railway" => CityObjectType::Railway, + "Waterway" => CityObjectType::Waterway, + "TransportSquare" => CityObjectType::TransportSquare, + + "Tunnel" => CityObjectType::Tunnel, + "TunnelPart" => CityObjectType::TunnelPart, + "TunnelInstallation" => CityObjectType::TunnelInstallation, + "TunnelConstructiveElement" => CityObjectType::TunnelConstructiveElement, + "TunnelHollowSpace" => CityObjectType::TunnelHollowSpace, + "TunnelFurniture" => CityObjectType::TunnelFurniture, + + "WaterBody" => CityObjectType::WaterBody, + _ => CityObjectType::GenericCityObject, + } +} + +/// Converts CityJSON geometry type to FlatBuffers enum +/// +/// # Arguments +/// +/// * `geometry_type` - CityJSON geometry type +pub(crate) fn to_geom_type(geometry_type: &CjGeometryType) -> GeometryType { + match geometry_type { + CjGeometryType::MultiPoint => GeometryType::MultiPoint, + CjGeometryType::MultiLineString => GeometryType::MultiLineString, + CjGeometryType::MultiSurface => GeometryType::MultiSurface, + CjGeometryType::CompositeSurface => GeometryType::CompositeSurface, + CjGeometryType::Solid => GeometryType::Solid, + CjGeometryType::MultiSolid => GeometryType::MultiSolid, + CjGeometryType::CompositeSolid => GeometryType::CompositeSolid, + _ => GeometryType::Solid, + } +} + /// Converts CityJSON semantic surface type to FlatBuffers enum /// /// # Arguments /// /// * `ss_type` - String representation of semantic surface type -pub(crate) fn to_fcb_semantic_surface_type(ss_type: &str) -> SemanticSurfaceType { +pub(super) fn to_semantic_surface_type(ss_type: &str) -> SemanticSurfaceType { match ss_type { "RoofSurface" => SemanticSurfaceType::RoofSurface, "GroundSurface" => SemanticSurfaceType::GroundSurface, @@ -436,11 +495,11 @@ pub(crate) fn to_fcb_semantic_surface_type(ss_type: &str) -> SemanticSurfaceType /// /// * `fbb` - FlatBuffers builder instance /// * `geometry` - CityJSON geometry object -pub(crate) fn to_fcb_geometry<'a>( +pub fn to_geometry<'a>( fbb: &mut flatbuffers::FlatBufferBuilder<'a>, geometry: &CjGeometry, ) -> flatbuffers::WIPOffset> { - let type_ = to_fcb_geometry_type(&geometry.thetype); + 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(); @@ -459,7 +518,7 @@ pub(crate) fn to_fcb_geometry<'a>( .iter() .map(|s| { let children = s.children.clone().map(|c| fbb.create_vector(&c.to_vec())); - let semantics_type = to_fcb_semantic_surface_type(&s.thetype); + let semantics_type = to_semantic_surface_type(&s.thetype); let semantic_object = SemanticObject::create( fbb, &SemanticObjectArgs { @@ -497,7 +556,7 @@ pub(crate) fn to_fcb_geometry<'a>( ) } -pub fn to_fcb_columns<'a>( +pub fn to_columns<'a>( fbb: &mut FlatBufferBuilder<'a>, attr_schema: &AttributeSchema, ) -> flatbuffers::WIPOffset>>> { @@ -550,8 +609,7 @@ pub fn to_fcb_attribute<'a>( mod tests { use super::*; - use crate::fcb_serde::fcb_deserializer::to_cj_co_type; - use crate::feature_generated::root_as_city_feature; + use crate::{deserializer::to_cj_co_type, feature_generated::root_as_city_feature}; use anyhow::Result; use cjseq::CityJSONFeature; @@ -572,17 +630,8 @@ mod tests { // Create FlatBuffer and encode let mut fbb = FlatBufferBuilder::new(); - let city_objects_buf: Vec<_> = cj_city_feature - .city_objects - .iter() - .map(|(id, co)| to_fcb_city_object(&mut fbb, id, co, &attr_schema)) - .collect(); - let city_feature = to_fcb_city_feature( - &mut fbb, - "test_id", - &city_objects_buf, - &cj_city_feature.vertices, - ); + + let city_feature = to_fcb_city_feature(&mut fbb, "test_id", &cj_city_feature, &attr_schema); fbb.finish(city_feature, None); let buf = fbb.finished_data(); @@ -695,79 +744,4 @@ mod tests { Ok(()) } - - #[test] - fn test_encode_attributes() -> Result<()> { - // let json_data = json!({ - // "attributes": { - // "int": -1, - // "uint": 1, - // "bool": true, - // "float": 1.0, - // "string": "hoge", - // "array": [1, 2, 3], - // "json": { - // "hoge": "fuga" - // }, - // "null": null - // } - // }); - // let attrs = &json_data["attributes"]; - - // // Test case 1: Using common schema - // { - // let mut fbb = FlatBufferBuilder::new(); - // let mut common_schema = AttributeSchema::new(); - // common_schema.add_attributes(attrs); - - // let columns = to_fcb_columns(&mut fbb, &common_schema); - // let header = Header::create( - // &mut fbb, - // &HeaderArgs { - // columns: Some(columns), - // ..Default::default() - // }, - // ); - - // fbb.finish(header, None); - // let finished_data = fbb.finished_data(); - // let header_buf = root_as_header(finished_data).unwrap(); - - // // let feature = - - // let encoded = encode_attributes_with_schema(attrs, &common_schema); - - // // Verify encoded data - // assert!(!encoded.is_empty()); - - // let decoded = decode_attributes(header_buf.columns().unwrap(), encoded.); - // assert_eq!(attrs, &decoded); - // } - - // // Test case 2: Using own schema - // { - // let mut fbb = FlatBufferBuilder::new(); - // let (offset, schema) = to_fcb_attribute(&mut fbb, attrs, &AttributeSchema::new()); - - // // Verify schema is returned for own schema case - // assert!(schema.is_some()); - // let schema = schema.unwrap(); - - // // Verify schema contains expected types - // assert_eq!(schema.get("int"), Some(&ColumnType::Int)); - // assert_eq!(schema.get("uint"), Some(&ColumnType::UInt)); - // assert_eq!(schema.get("bool"), Some(&ColumnType::Bool)); - // assert_eq!(schema.get("float"), Some(&ColumnType::Float)); - // assert_eq!(schema.get("string"), Some(&ColumnType::String)); - // assert_eq!(schema.get("json"), Some(&ColumnType::Json)); - - // // Get the encoded data - // let data = fbb.finished_data(); - // assert!(!data.is_empty()); - // // First 2 bytes should be 1 (true) for own schema - // assert_eq!(&data[0..2], &[1, 0]); - // } - - Ok(()) - } } diff --git a/src/rust/tests/e2e.rs b/src/rust/tests/e2e.rs index 070c14f..684ce9a 100644 --- a/src/rust/tests/e2e.rs +++ b/src/rust/tests/e2e.rs @@ -1,7 +1,7 @@ use anyhow::Result; use flatcitybuf::{ attribute::{AttributeSchema, AttributeSchemaMethods}, - fcb_deserializer, + deserializer, header_writer::{HeaderMetadata, HeaderWriterOptions}, read_cityjson_from_reader, CJType, CJTypeKind, FcbReader, FcbWriter, }; @@ -51,11 +51,10 @@ fn test_cityjson_serialization_cycle() -> Result<()> { write_index: false, header_metadata, }), - original_cj_seq.features.first(), Some(attr_schema), )?; fcb.write_feature()?; - for feature in original_cj_seq.features.iter().skip(1) { + for feature in original_cj_seq.features.iter() { fcb.add_feature(feature)?; } fcb.write(output_writer)?; @@ -68,8 +67,7 @@ fn test_cityjson_serialization_cycle() -> Result<()> { // Get header and convert to CityJSON let header = reader.header(); - let deserialized_cj = fcb_deserializer::to_cj_metadata(&header)?; - + let deserialized_cj = deserializer::to_cj_metadata(&header)?; // Read all features let mut deserialized_features = Vec::new(); let feat_count = header.features_count(); diff --git a/src/rust/tests/serde.rs b/src/rust/tests/serde.rs index 1391f45..00af82c 100644 --- a/src/rust/tests/serde.rs +++ b/src/rust/tests/serde.rs @@ -2,10 +2,10 @@ use anyhow::Result; use flatbuffers::FlatBufferBuilder; use flatcitybuf::{ attribute::{AttributeSchema, AttributeSchemaMethods}, - fcb_deserializer::decode_attributes, - fcb_serializer::{to_fcb_attribute, to_fcb_columns}, - root_as_city_feature, root_as_header, CityFeature, CityFeatureArgs, CityObject, CityObjectArgs, - Header, HeaderArgs, + deserializer::decode_attributes, + root_as_city_feature, root_as_header, + serializer::{to_columns, to_fcb_attribute}, + CityFeature, CityFeatureArgs, CityObject, CityObjectArgs, Header, HeaderArgs, }; use serde_json::json; @@ -108,7 +108,7 @@ fn test_attribute_serialization() -> Result<()> { let mut common_schema = AttributeSchema::new(); common_schema.add_attributes(attr_schema); - let columns = to_fcb_columns(&mut fbb, &common_schema); + let columns = to_columns(&mut fbb, &common_schema); let header = { let version = fbb.create_string("1.0.0"); Header::create(