1use crate::attribute::{encode_attributes_with_schema, AttributeSchema, AttributeSchemaMethods};
2use crate::fb::{
3 Appearance, AppearanceArgs, CityFeature, CityFeatureArgs, CityObject, CityObjectArgs,
4 CityObjectType, Geometry, GeometryArgs, GeometryType, Material, MaterialArgs, SemanticObject,
5 SemanticObjectArgs, SemanticSurfaceType, Texture, TextureArgs, TextureType, Vec2, Vertex,
6 WrapMode,
7};
8use crate::fb::{Column, ColumnArgs};
9use crate::fb::{
10 GeographicalExtent, Header, HeaderArgs, ReferenceSystem, ReferenceSystemArgs, Transform, Vector,
11};
12use crate::geom_encoder::encode;
13use crate::{
14 AttributeIndex, DoubleVertex, Extension, ExtensionArgs, GeometryInstance, GeometryInstanceArgs,
15 MaterialMapping, MaterialMappingArgs, TextureFormat, TextureMapping, TextureMappingArgs,
16 TransformationMatrix,
17};
18use cjseq::{
19 Appearance as CjAppearance, BorderColor as CjBorderColor, CityJSON, CityJSONFeature,
20 CityObject as CjCityObject, CityObjectType as CjCityObjectType, Geometry as CjGeometry,
21 GeometryType as CjGeometryType, PointOfContact as CjPointOfContact,
22 ReferenceSystem as CjReferenceSystem, SemanticSurfaceType as CjSemanticSurfaceType,
23 TextureFormat as CjTextureFormat, TextureType as CjTextureType, Transform as CjTransform,
24 WrapMode as CjWrapMode,
25};
26
27use crate::packed_rtree::NodeItem;
28use flatbuffers::FlatBufferBuilder;
29use serde_json::Value;
30
31use super::geom_encoder::{GMBoundaries, GMSemantics, MaterialMapping as GMMaterialMapping};
32use super::header_writer::HeaderWriterOptions;
33use crate::error::Result;
34
35#[derive(Debug, Clone)]
36pub(super) struct AttributeIndexInfo {
37 pub index: u16,
38 pub length: u32,
39 pub branching_factor: u16,
40 pub num_unique_items: u32,
41}
42pub(super) fn to_fcb_header<'a>(
53 fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
54 cj: &CityJSON,
55 header_options: HeaderWriterOptions,
56 attr_schema: &AttributeSchema,
57 semantic_attr_schema: Option<&AttributeSchema>,
58 attribute_indices_info: Option<&[AttributeIndexInfo]>,
59) -> Result<flatbuffers::WIPOffset<Header<'a>>> {
60 let version = Some(fbb.create_string(&cj.version));
61 let transform = to_transform(&cj.transform);
62 let features_count: u64 = header_options.feature_count;
63 let columns = Some(to_columns(fbb, attr_schema));
64 let semantic_columns = semantic_attr_schema.map(|schema| to_columns(fbb, schema));
65 let index_node_size = header_options.index_node_size;
66 let attribute_index = {
67 if let Some(attribute_indices_info) = attribute_indices_info {
68 let attribute_indices_info_vec = attribute_indices_info
69 .iter()
70 .map(|info| {
71 AttributeIndex::new(
72 info.index,
73 info.length,
74 info.branching_factor,
75 info.num_unique_items,
76 )
77 })
78 .collect::<Vec<_>>();
79 Some(fbb.create_vector(&attribute_indices_info_vec))
80 } else {
81 None
82 }
83 };
84
85 let extensions = cj
90 .extensions
91 .as_ref()
92 .and_then(|e| e.as_object())
93 .map(|extensions| {
94 let extensions = extensions
95 .iter()
96 .map(|(name, ext)| {
97 let url = ext.get("url").and_then(|v| v.as_str()).unwrap_or_default();
98 let version = ext
99 .get("version")
100 .and_then(|v| v.as_str())
101 .unwrap_or_default();
102 to_extension(fbb, name, url, version)
103 })
104 .collect::<Vec<_>>();
105 fbb.create_vector(&extensions)
106 });
107
108 let geographical_extent_from_options = header_options
110 .geographical_extent
111 .as_ref()
112 .map(to_geographical_extent);
113
114 let appearance = cj.appearance.as_ref().map(|app| to_appearance(fbb, app));
115
116 let (templates, templates_vertices) = match &cj.geometry_templates {
117 Some(gm) => {
118 let templates_vertices = to_templates_vertices(fbb, &gm.vertices_templates);
119
120 let gm_vec = gm
121 .templates
122 .iter()
123 .map(|g| to_geometry(fbb, g, semantic_attr_schema))
124 .collect::<Vec<_>>();
125 (Some(fbb.create_vector(&gm_vec)), Some(templates_vertices))
126 }
127 None => (None, None),
128 };
129
130 if let Some(meta) = cj.metadata.as_ref() {
131 let reference_system = meta
132 .reference_system
133 .as_ref()
134 .map(|ref_sys| to_reference_system(fbb, ref_sys));
135 let geographical_extent = geographical_extent_from_options.or_else(|| {
137 meta.geographical_extent
138 .as_ref()
139 .map(to_geographical_extent)
140 });
141 let identifier = meta.identifier.as_ref().map(|i| fbb.create_string(i));
142 let reference_date = meta.reference_date.as_ref().map(|r| fbb.create_string(r));
143 let title = meta.title.as_ref().map(|t| fbb.create_string(t));
144 let poc_fields = meta
145 .point_of_contact
146 .as_ref()
147 .map(|poc| to_point_of_contact(fbb, poc));
148 let (
149 poc_contact_name,
150 poc_contact_type,
151 poc_role,
152 poc_phone,
153 poc_email,
154 poc_website,
155 poc_address_thoroughfare_number,
156 poc_address_thoroughfare_name,
157 poc_address_locality,
158 poc_address_postcode,
159 poc_address_country,
160 ) = poc_fields.map_or(
161 (
162 None, None, None, None, None, None, None, None, None, None, None,
163 ),
164 |poc| {
165 (
166 poc.poc_contact_name,
167 poc.poc_contact_type,
168 poc.poc_role,
169 poc.poc_phone,
170 poc.poc_email,
171 poc.poc_website,
172 poc.poc_address_thoroughfare_number,
173 poc.poc_address_thoroughfare_name,
174 poc.poc_address_locality,
175 poc.poc_address_postcode,
176 poc.poc_address_country,
177 )
178 },
179 );
180
181 Ok(Header::create(
182 fbb,
183 &HeaderArgs {
184 transform: Some(transform).as_ref(),
185 columns,
186 semantic_columns,
187 features_count,
188 index_node_size,
189 geographical_extent: geographical_extent.as_ref(),
190 reference_system,
191 identifier,
192 attribute_index,
193 reference_date,
194 title,
195 poc_contact_name,
196 poc_contact_type,
197 poc_role,
198 poc_phone,
199 poc_email,
200 poc_website,
201 poc_address_thoroughfare_number,
202 poc_address_thoroughfare_name,
203 poc_address_locality,
204 poc_address_postcode,
205 poc_address_country,
206 attributes: None,
207 version,
208 appearance,
209 templates,
210 templates_vertices,
211 extensions,
212 },
213 ))
214 } else {
215 Ok(Header::create(
223 fbb,
224 &HeaderArgs {
225 transform: Some(transform).as_ref(),
226 columns,
227 semantic_columns,
228 features_count,
229 index_node_size,
230 geographical_extent: geographical_extent_from_options.as_ref(),
231 version,
232 attribute_index,
233 appearance,
234 templates,
235 templates_vertices,
236 extensions,
237 ..Default::default()
238 },
239 ))
240 }
241}
242
243pub(super) fn to_geographical_extent(geographical_extent: &[f64; 6]) -> GeographicalExtent {
249 let min = Vector::new(
250 geographical_extent[0],
251 geographical_extent[1],
252 geographical_extent[2],
253 );
254 let max = Vector::new(
255 geographical_extent[3],
256 geographical_extent[4],
257 geographical_extent[5],
258 );
259 GeographicalExtent::new(&min, &max)
260}
261
262pub(super) fn to_transform(transform: &CjTransform) -> Transform {
268 let scale = Vector::new(transform.scale[0], transform.scale[1], transform.scale[2]);
269 let translate = Vector::new(
270 transform.translate[0],
271 transform.translate[1],
272 transform.translate[2],
273 );
274 Transform::new(&scale, &translate)
275}
276
277pub(super) fn to_reference_system<'a>(
284 fbb: &mut FlatBufferBuilder<'a>,
285 ref_system: &CjReferenceSystem,
286) -> flatbuffers::WIPOffset<ReferenceSystem<'a>> {
287 let authority = Some(fbb.create_string(ref_system.authority().unwrap_or_default()));
290
291 let version = ref_system
292 .version()
293 .and_then(|v| v.parse::<i32>().ok())
294 .unwrap_or(0);
295 let code = ref_system
296 .code()
297 .and_then(|c| c.parse::<i32>().ok())
298 .unwrap_or(0);
299
300 let code_string = None; ReferenceSystem::create(
303 fbb,
304 &ReferenceSystemArgs {
305 authority,
306 version,
307 code,
308 code_string,
309 },
310 )
311}
312
313#[doc(hidden)]
315struct FcbPointOfContact<'a> {
316 poc_contact_name: Option<flatbuffers::WIPOffset<&'a str>>,
317 poc_contact_type: Option<flatbuffers::WIPOffset<&'a str>>,
318 poc_role: Option<flatbuffers::WIPOffset<&'a str>>,
319 poc_phone: Option<flatbuffers::WIPOffset<&'a str>>,
320 poc_email: Option<flatbuffers::WIPOffset<&'a str>>,
321 poc_website: Option<flatbuffers::WIPOffset<&'a str>>,
322 poc_address_thoroughfare_number: Option<flatbuffers::WIPOffset<&'a str>>,
323 poc_address_thoroughfare_name: Option<flatbuffers::WIPOffset<&'a str>>,
324 poc_address_locality: Option<flatbuffers::WIPOffset<&'a str>>,
325 poc_address_postcode: Option<flatbuffers::WIPOffset<&'a str>>,
326 poc_address_country: Option<flatbuffers::WIPOffset<&'a str>>,
327}
328
329fn to_point_of_contact<'a>(
330 fbb: &mut FlatBufferBuilder<'a>,
331 poc: &CjPointOfContact,
332) -> FcbPointOfContact<'a> {
333 let poc_contact_name = Some(fbb.create_string(&poc.contact_name));
334
335 let poc_contact_type = poc.contact_type.as_ref().map(|ct| fbb.create_string(ct));
336 let poc_role = poc.role.as_ref().map(|r| fbb.create_string(r));
337 let poc_phone = poc.phone.as_ref().map(|p| fbb.create_string(p));
338 let poc_email = Some(fbb.create_string(&poc.email_address));
339 let poc_website = poc.website.as_ref().map(|w| fbb.create_string(w));
340 let address_member = |key: &str| -> Option<String> {
346 let members = &poc.address.as_ref()?.members;
347 let value = members.get(key)?;
348 match value {
349 Value::String(s) => Some(s.clone()),
350 Value::Null => None,
351 other => Some(other.to_string()),
352 }
353 };
354 let address_either = |a: &str, b: &str| address_member(a).or_else(|| address_member(b));
357
358 let poc_address_thoroughfare_number =
359 address_member("thoroughfareNumber").map(|v| fbb.create_string(&v));
360 let poc_address_thoroughfare_name =
361 address_member("thoroughfareName").map(|v| fbb.create_string(&v));
362 let poc_address_locality = address_member("locality").map(|v| fbb.create_string(&v));
363 let poc_address_postcode =
364 address_either("postcode", "postalCode").map(|v| fbb.create_string(&v));
365 let poc_address_country = address_member("country").map(|v| fbb.create_string(&v));
366 FcbPointOfContact {
367 poc_contact_name,
368 poc_contact_type,
369 poc_role,
370 poc_phone,
371 poc_email,
372 poc_website,
373 poc_address_thoroughfare_number,
374 poc_address_thoroughfare_name,
375 poc_address_locality,
376 poc_address_postcode,
377 poc_address_country,
378 }
379}
380
381pub fn to_extension<'a>(
389 fbb: &mut FlatBufferBuilder<'a>,
390 name: &str,
391 url: &str,
392 version: &str,
393) -> flatbuffers::WIPOffset<Extension<'a>> {
394 let name = fbb.create_string(name);
395 let url = fbb.create_string(url);
396 let version = fbb.create_string(version);
397
398 Extension::create(
399 fbb,
400 &ExtensionArgs {
401 name: Some(name),
402 url: Some(url),
403 version: Some(version),
404 ..Default::default()
405 },
406 )
407}
408
409pub(super) fn to_fcb_city_feature<'a>(
421 fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
422 id: &str,
423 city_feature: &CityJSONFeature,
424 attr_schema: &AttributeSchema,
425 semantic_attr_schema: Option<&AttributeSchema>,
426) -> (flatbuffers::WIPOffset<CityFeature<'a>>, NodeItem) {
427 let id = Some(fbb.create_string(id));
428 let mut object_ids: Vec<&String> = city_feature.city_objects.keys().collect();
433 object_ids.sort_unstable();
434 let city_objects: Vec<_> = object_ids
435 .into_iter()
436 .filter_map(|id| city_feature.city_objects.get(id).map(|co| (id, co)))
437 .map(|(id, co)| to_city_object(fbb, id, co, attr_schema, semantic_attr_schema))
438 .collect();
439 let objects = Some(fbb.create_vector(&city_objects));
440 let vertices = Some(
441 fbb.create_vector(
442 &city_feature
443 .vertices
444 .iter()
445 .map(|v| {
446 Vertex::new(
447 v[0].try_into().unwrap(),
448 v[1].try_into().unwrap(),
449 v[2].try_into().unwrap(),
450 )
451 })
452 .collect::<Vec<_>>(),
453 ),
454 );
455
456 let appearance = city_feature
458 .appearance
459 .as_ref()
460 .map(|app| to_appearance(fbb, app));
461 let min_x = city_feature
462 .vertices
463 .iter()
464 .map(|v| v[0])
465 .min()
466 .unwrap_or(0) as f64;
467 let min_y = city_feature
468 .vertices
469 .iter()
470 .map(|v| v[1])
471 .min()
472 .unwrap_or(0) as f64;
473 let max_x = city_feature
474 .vertices
475 .iter()
476 .map(|v| v[0])
477 .max()
478 .unwrap_or(0) as f64;
479 let max_y = city_feature
480 .vertices
481 .iter()
482 .map(|v| v[1])
483 .max()
484 .unwrap_or(0) as f64;
485
486 let bbox = NodeItem::bounds(min_x, min_y, max_x, max_y);
487 (
488 CityFeature::create(
489 fbb,
490 &CityFeatureArgs {
491 id,
492 objects,
493 vertices,
494 appearance,
495 },
496 ),
497 bbox,
498 )
499}
500
501pub(crate) fn fb_wrap_mode(w: CjWrapMode) -> WrapMode {
511 match w {
512 CjWrapMode::None => WrapMode::None,
513 CjWrapMode::Wrap => WrapMode::Wrap,
514 CjWrapMode::Mirror => WrapMode::Mirror,
515 CjWrapMode::Clamp => WrapMode::Clamp,
516 CjWrapMode::Border => WrapMode::Border,
517 }
518}
519
520pub(crate) fn fb_texture_type(t: CjTextureType) -> TextureType {
522 match t {
523 CjTextureType::Unknown => TextureType::Unknown,
524 CjTextureType::Specific => TextureType::Specific,
525 CjTextureType::Typical => TextureType::Typical,
526 }
527}
528
529pub(crate) fn fb_texture_format(f: Option<CjTextureFormat>) -> TextureFormat {
537 match f {
538 Some(CjTextureFormat::PNG) | None => TextureFormat::PNG,
539 Some(CjTextureFormat::JPG) => TextureFormat::JPG,
540 }
541}
542
543pub(super) fn to_appearance<'a>(
544 fbb: &mut FlatBufferBuilder<'a>,
545 appearance: &CjAppearance,
546) -> flatbuffers::WIPOffset<Appearance<'a>> {
547 let materials = appearance.materials.as_ref().map(|materials| {
553 let material_offsets: Vec<_> = materials
554 .iter()
555 .map(|m| {
556 let name = fbb.create_string(&m.name);
557 let diffuse_color = m.diffuse_color.map(|c| fbb.create_vector(&c));
558 let emissive_color = m.emissive_color.map(|c| fbb.create_vector(&c));
559 let specular_color = m.specular_color.map(|c| fbb.create_vector(&c));
560 Material::create(
561 fbb,
562 &MaterialArgs {
563 name: Some(name),
564 ambient_intensity: m.ambient_intensity,
565 diffuse_color,
566 emissive_color,
567 specular_color,
568 shininess: m.shininess,
569 transparency: m.transparency,
570 is_smooth: m.is_smooth,
571 },
572 )
573 })
574 .collect();
575 fbb.create_vector(&material_offsets)
576 });
577
578 let textures = appearance.textures.as_ref().map(|textures| {
579 let texture_offsets: Vec<_> = textures
580 .iter()
581 .map(|t| {
582 let image = fbb.create_string(t.image.as_deref().unwrap_or_default());
585 let border_color = t.border_color.as_ref().map(|c| match c {
586 CjBorderColor::Rgb(c) => fbb.create_vector(c),
587 CjBorderColor::Rgba(c) => fbb.create_vector(c),
588 });
589 Texture::create(
590 fbb,
591 &TextureArgs {
592 type_: fb_texture_format(t.thetype),
593 image: Some(image),
594 wrap_mode: t.wrap_mode.map(fb_wrap_mode),
595 texture_type: t.texture_type.map(fb_texture_type),
596 border_color,
597 },
598 )
599 })
600 .collect();
601 fbb.create_vector(&texture_offsets)
602 });
603
604 let vertices_texture = appearance.vertices_texture.as_ref().map(|vertices| {
605 fbb.create_vector(
606 &vertices
607 .iter()
608 .map(|v| Vec2::new(v[0], v[1]))
609 .collect::<Vec<_>>(),
610 )
611 });
612
613 let default_theme_texture = appearance
614 .default_theme_texture
615 .as_ref()
616 .map(|t| fbb.create_string(t));
617 let default_theme_material = appearance
618 .default_theme_material
619 .as_ref()
620 .map(|m| fbb.create_string(m));
621
622 Appearance::create(
623 fbb,
624 &AppearanceArgs {
625 materials,
626 textures,
627 vertices_texture,
628 default_theme_texture,
629 default_theme_material,
630 },
631 )
632}
633
634pub(super) fn to_city_object<'a>(
643 fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
644 id: &str,
645 co: &CjCityObject,
646 attr_schema: &AttributeSchema,
647 semantic_attr_schema: Option<&AttributeSchema>,
648) -> flatbuffers::WIPOffset<CityObject<'a>> {
649 let id = Some(fbb.create_string(id));
650
651 let (type_, extension_type) = to_co_type(&co.thetype);
652 let extension_type = extension_type.as_ref().map(|et| fbb.create_string(et));
653 let geographical_extent = co.geographical_extent.as_ref().map(to_geographical_extent);
654 let geometry_without_instances = co.geometry.as_ref().map(|gs| {
655 gs.iter()
656 .filter(|g| g.geometry_type() != CjGeometryType::GeometryInstance)
657 .collect::<Vec<_>>()
658 });
659 let geometry_instances = co.geometry.as_ref().map(|gs| {
660 gs.iter()
661 .filter(|g| g.geometry_type() == CjGeometryType::GeometryInstance)
662 .collect::<Vec<_>>()
663 });
664 let geometries = {
665 let geometries = geometry_without_instances.map(|gs| {
666 gs.iter()
667 .map(|g| to_geometry(fbb, g, semantic_attr_schema))
668 .collect::<Vec<_>>()
669 });
670 geometries.map(|geometries| fbb.create_vector(&geometries))
671 };
672
673 let geometry_instances = {
674 let geometry_instances = geometry_instances.map(|gs| {
675 gs.iter()
676 .map(|g| to_geometry_instance(fbb, g))
677 .collect::<Vec<_>>()
678 });
679 geometry_instances.map(|geometry_instances| fbb.create_vector(&geometry_instances))
680 };
681
682 let attributes_and_columns = co
683 .attributes
684 .as_ref()
685 .map(|attr| {
686 if !attr.is_object() {
687 return (None, None);
688 }
689 let (attr_vec, own_schema) = to_fcb_attribute(fbb, attr, attr_schema);
690 let columns = own_schema.map(|schema| to_columns(fbb, &schema));
691 (Some(attr_vec), columns)
692 })
693 .unwrap_or((None, None));
694
695 let (attributes, columns) = attributes_and_columns;
696
697 let children = {
698 let children = co
699 .children
700 .as_ref()
701 .map(|c| c.iter().map(|s| fbb.create_string(s)).collect::<Vec<_>>());
702 children.map(|c| fbb.create_vector(&c))
703 };
704
705 let children_roles = {
706 let children_roles_strings = co.children_roles.as_ref().map(|c| {
709 c.iter()
710 .map(|r| fbb.create_string(r.as_deref().unwrap_or_default()))
711 .collect::<Vec<_>>()
712 });
713 children_roles_strings.map(|c| fbb.create_vector(&c))
714 };
715
716 let parents = {
717 let parents = co
718 .parents
719 .as_ref()
720 .map(|p| p.iter().map(|s| fbb.create_string(s)).collect::<Vec<_>>());
721 parents.map(|p| fbb.create_vector(&p))
722 };
723
724 CityObject::create(
725 fbb,
726 &CityObjectArgs {
727 id,
728 type_,
729 extension_type,
730 geographical_extent: geographical_extent.as_ref(),
731 geometry: geometries,
732 geometry_instances,
733 attributes,
734 columns,
735 children,
736 children_roles,
737 parents,
738 },
739 )
740}
741
742pub(super) fn to_co_type(co_type: &CjCityObjectType) -> (CityObjectType, Option<String>) {
756 match *co_type {
757 CjCityObjectType::Bridge => (CityObjectType::Bridge, None),
758 CjCityObjectType::BridgePart => (CityObjectType::BridgePart, None),
759 CjCityObjectType::BridgeInstallation => (CityObjectType::BridgeInstallation, None),
760 CjCityObjectType::BridgeConstructiveElement => {
761 (CityObjectType::BridgeConstructiveElement, None)
762 }
763 CjCityObjectType::BridgeRoom => (CityObjectType::BridgeRoom, None),
764 CjCityObjectType::BridgeFurniture => (CityObjectType::BridgeFurniture, None),
765 CjCityObjectType::Building => (CityObjectType::Building, None),
766 CjCityObjectType::BuildingPart => (CityObjectType::BuildingPart, None),
767 CjCityObjectType::BuildingInstallation => (CityObjectType::BuildingInstallation, None),
768 CjCityObjectType::BuildingConstructiveElement => {
769 (CityObjectType::BuildingConstructiveElement, None)
770 }
771 CjCityObjectType::BuildingFurniture => (CityObjectType::BuildingFurniture, None),
772 CjCityObjectType::BuildingStorey => (CityObjectType::BuildingStorey, None),
773 CjCityObjectType::BuildingRoom => (CityObjectType::BuildingRoom, None),
774 CjCityObjectType::BuildingUnit => (CityObjectType::BuildingUnit, None),
775 CjCityObjectType::CityFurniture => (CityObjectType::CityFurniture, None),
776 CjCityObjectType::CityObjectGroup => (CityObjectType::CityObjectGroup, None),
777 CjCityObjectType::GenericCityObject => (CityObjectType::GenericCityObject, None),
778 CjCityObjectType::LandUse => (CityObjectType::LandUse, None),
779 CjCityObjectType::OtherConstruction => (CityObjectType::OtherConstruction, None),
780 CjCityObjectType::PlantCover => (CityObjectType::PlantCover, None),
781 CjCityObjectType::SolitaryVegetationObject => {
782 (CityObjectType::SolitaryVegetationObject, None)
783 }
784 CjCityObjectType::TINRelief => (CityObjectType::TINRelief, None),
785 CjCityObjectType::Road => (CityObjectType::Road, None),
786 CjCityObjectType::Railway => (CityObjectType::Railway, None),
787 CjCityObjectType::Waterway => (CityObjectType::Waterway, None),
788 CjCityObjectType::TransportSquare => (CityObjectType::TransportSquare, None),
789 CjCityObjectType::Tunnel => (CityObjectType::Tunnel, None),
790 CjCityObjectType::TunnelPart => (CityObjectType::TunnelPart, None),
791 CjCityObjectType::TunnelInstallation => (CityObjectType::TunnelInstallation, None),
792 CjCityObjectType::TunnelConstructiveElement => {
793 (CityObjectType::TunnelConstructiveElement, None)
794 }
795 CjCityObjectType::TunnelHollowSpace => (CityObjectType::TunnelHollowSpace, None),
796 CjCityObjectType::TunnelFurniture => (CityObjectType::TunnelFurniture, None),
797 CjCityObjectType::WaterBody => (CityObjectType::WaterBody, None),
798 CjCityObjectType::Extension(ref name) => {
799 (CityObjectType::ExtensionObject, Some(name.clone()))
800 }
801 }
802}
803
804pub(super) fn to_geom_type(geometry_type: &CjGeometryType) -> GeometryType {
810 match geometry_type {
811 CjGeometryType::MultiPoint => GeometryType::MultiPoint,
812 CjGeometryType::MultiLineString => GeometryType::MultiLineString,
813 CjGeometryType::MultiSurface => GeometryType::MultiSurface,
814 CjGeometryType::CompositeSurface => GeometryType::CompositeSurface,
815 CjGeometryType::Solid => GeometryType::Solid,
816 CjGeometryType::MultiSolid => GeometryType::MultiSolid,
817 CjGeometryType::CompositeSolid => GeometryType::CompositeSolid,
818 CjGeometryType::GeometryInstance => GeometryType::GeometryInstance,
819 }
820}
821
822pub(super) struct FcbSemanticSurfaceType {
826 pub(super) type_: SemanticSurfaceType,
827 pub(super) extension_type: Option<String>,
828}
829
830impl FcbSemanticSurfaceType {
831 fn known(type_: SemanticSurfaceType) -> Self {
832 FcbSemanticSurfaceType {
833 type_,
834 extension_type: None,
835 }
836 }
837}
838
839impl From<&CjSemanticSurfaceType> for FcbSemanticSurfaceType {
847 fn from(ss_type: &CjSemanticSurfaceType) -> Self {
848 match *ss_type {
849 CjSemanticSurfaceType::RoofSurface => Self::known(SemanticSurfaceType::RoofSurface),
850 CjSemanticSurfaceType::GroundSurface => Self::known(SemanticSurfaceType::GroundSurface),
851 CjSemanticSurfaceType::WallSurface => Self::known(SemanticSurfaceType::WallSurface),
852 CjSemanticSurfaceType::ClosureSurface => {
853 Self::known(SemanticSurfaceType::ClosureSurface)
854 }
855 CjSemanticSurfaceType::OuterCeilingSurface => {
856 Self::known(SemanticSurfaceType::OuterCeilingSurface)
857 }
858 CjSemanticSurfaceType::OuterFloorSurface => {
859 Self::known(SemanticSurfaceType::OuterFloorSurface)
860 }
861 CjSemanticSurfaceType::Window => Self::known(SemanticSurfaceType::Window),
862 CjSemanticSurfaceType::Door => Self::known(SemanticSurfaceType::Door),
863 CjSemanticSurfaceType::InteriorWallSurface => {
864 Self::known(SemanticSurfaceType::InteriorWallSurface)
865 }
866 CjSemanticSurfaceType::CeilingSurface => {
867 Self::known(SemanticSurfaceType::CeilingSurface)
868 }
869 CjSemanticSurfaceType::FloorSurface => Self::known(SemanticSurfaceType::FloorSurface),
870 CjSemanticSurfaceType::WaterSurface => Self::known(SemanticSurfaceType::WaterSurface),
871 CjSemanticSurfaceType::WaterGroundSurface => {
872 Self::known(SemanticSurfaceType::WaterGroundSurface)
873 }
874 CjSemanticSurfaceType::WaterClosureSurface => {
875 Self::known(SemanticSurfaceType::WaterClosureSurface)
876 }
877 CjSemanticSurfaceType::TrafficArea => Self::known(SemanticSurfaceType::TrafficArea),
878 CjSemanticSurfaceType::AuxiliaryTrafficArea => {
879 Self::known(SemanticSurfaceType::AuxiliaryTrafficArea)
880 }
881 CjSemanticSurfaceType::TransportationMarking => {
882 Self::known(SemanticSurfaceType::TransportationMarking)
883 }
884 CjSemanticSurfaceType::TransportationHole => {
885 Self::known(SemanticSurfaceType::TransportationHole)
886 }
887 CjSemanticSurfaceType::Extension(ref name) => FcbSemanticSurfaceType {
888 type_: SemanticSurfaceType::ExtraSemanticSurface,
889 extension_type: Some(name.clone()),
890 },
891 }
892 }
893}
894
895pub(crate) fn to_geometry<'a>(
902 fbb: &mut flatbuffers::FlatBufferBuilder<'a>,
903 geometry: &CjGeometry,
904 semantic_attr_schema: Option<&AttributeSchema>,
905) -> flatbuffers::WIPOffset<Geometry<'a>> {
906 let type_ = to_geom_type(&geometry.geometry_type());
907 let lod = geometry.lod().map(|lod| fbb.create_string(lod));
908
909 let encoded = encode(geometry);
910 let GMBoundaries {
911 solids,
912 shells,
913 surfaces,
914 strings,
915 indices,
916 } = encoded.boundaries;
917 let semantics = encoded
918 .semantics
919 .map(|GMSemantics { surfaces, values }| (surfaces, values));
920
921 let solids = Some(fbb.create_vector(&solids));
922 let shells = Some(fbb.create_vector(&shells));
923 let surfaces = Some(fbb.create_vector(&surfaces));
924 let strings = Some(fbb.create_vector(&strings));
925 let boundary_indices = Some(fbb.create_vector(&indices));
926
927 let (semantics_objects, semantics_values) =
928 semantics.map_or((None, None), |(surface, values)| {
929 let semantics_objects = surface
930 .iter()
931 .map(|s| {
932 let children = s.children.as_ref().map(|c| {
933 let c = c.iter().map(|&i| i as u32).collect::<Vec<_>>();
934 fbb.create_vector(&c)
935 });
936
937 let FcbSemanticSurfaceType {
938 type_,
939 extension_type,
940 } = FcbSemanticSurfaceType::from(&s.thetype);
941 let extension_type = extension_type.map(|s| fbb.create_string(&s));
942 let attributes = if s.other.is_empty() {
943 None
944 } else {
945 let other = Value::Object(s.other.clone().into_iter().collect());
946 semantic_attr_schema.as_ref().map(|schema| {
947 fbb.create_vector(&encode_attributes_with_schema(&other, schema))
948 })
949 };
950 SemanticObject::create(
951 fbb,
952 &SemanticObjectArgs {
953 type_,
954 extension_type,
955 attributes,
956 children,
957 parent: s.parent.map(|p| p as u32),
958 },
959 )
960 })
961 .collect::<Vec<_>>();
962
963 (
964 Some(fbb.create_vector(&semantics_objects)),
965 values.map(|values| fbb.create_vector(&values)),
969 )
970 });
971
972 let material_mappings = encoded.materials.map(|m| {
973 let mappings = m
974 .iter()
975 .map(|m| match m {
976 GMMaterialMapping::Value(v) => {
977 let theme = Some(fbb.create_string(&v.theme));
978 let value = Some(v.value);
979 MaterialMapping::create(
980 fbb,
981 &MaterialMappingArgs {
982 theme,
983 solids: None,
984 shells: None,
985 vertices: None,
986 value,
987 },
988 )
989 }
990 GMMaterialMapping::Values(v) => {
991 let theme = Some(fbb.create_string(&v.theme));
992 let solids = Some(fbb.create_vector(&v.solids));
993 let shells = Some(fbb.create_vector(&v.shells));
994 let vertices = Some(fbb.create_vector(&v.vertices));
997 let value = None;
998 MaterialMapping::create(
999 fbb,
1000 &MaterialMappingArgs {
1001 theme,
1002 solids,
1003 shells,
1004 vertices,
1005 value,
1006 },
1007 )
1008 }
1009 GMMaterialMapping::NullValues(theme) => {
1011 let theme = Some(fbb.create_string(theme));
1012 MaterialMapping::create(
1013 fbb,
1014 &MaterialMappingArgs {
1015 theme,
1016 ..Default::default()
1017 },
1018 )
1019 }
1020 })
1021 .collect::<Vec<_>>();
1022 fbb.create_vector(&mappings)
1023 });
1024
1025 let texture_mappings = encoded.textures.map(|t| {
1026 let mappings = t
1027 .iter()
1028 .map(|t| {
1029 let theme = Some(fbb.create_string(&t.theme));
1030 let (solids, shells, surfaces, strings, vertices) = if t.has_values {
1033 (
1034 Some(fbb.create_vector(&t.solids)),
1035 Some(fbb.create_vector(&t.shells)),
1036 Some(fbb.create_vector(&t.surfaces)),
1037 Some(fbb.create_vector(&t.strings)),
1038 Some(fbb.create_vector(&t.vertices)),
1039 )
1040 } else {
1041 (None, None, None, None, None)
1042 };
1043 TextureMapping::create(
1044 fbb,
1045 &TextureMappingArgs {
1046 theme,
1047 solids,
1048 shells,
1049 surfaces,
1050 strings,
1051 vertices,
1052 },
1053 )
1054 })
1055 .collect::<Vec<_>>();
1056 fbb.create_vector(&mappings)
1057 });
1058
1059 Geometry::create(
1060 fbb,
1061 &GeometryArgs {
1062 type_,
1063 lod,
1064 solids,
1065 shells,
1066 surfaces,
1067 strings,
1068 boundaries: boundary_indices,
1069 semantics: semantics_values,
1070 semantics_objects,
1071 material: material_mappings,
1072 texture: texture_mappings,
1073 },
1074 )
1075}
1076
1077pub(super) fn to_geometry_instance<'a>(
1078 fbb: &mut FlatBufferBuilder<'a>,
1079 geometry: &CjGeometry,
1080) -> flatbuffers::WIPOffset<GeometryInstance<'a>> {
1081 let CjGeometry::GeometryInstance {
1085 boundaries,
1086 template,
1087 transformation_matrix: m,
1088 } = geometry
1089 else {
1090 panic!(
1091 "to_geometry_instance was given a {:?}",
1092 geometry.geometry_type()
1093 );
1094 };
1096
1097 let template = *template as u32;
1098 let indices = boundaries.iter().map(|&i| i as u32).collect::<Vec<_>>();
1099 let boundaries = Some(fbb.create_vector(&indices));
1100 let transformation = Some(TransformationMatrix::new(
1101 m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], m[12], m[13],
1102 m[14], m[15],
1103 ));
1104 GeometryInstance::create(
1105 fbb,
1106 &GeometryInstanceArgs {
1107 template,
1108 transformation: transformation.as_ref(),
1109 boundaries,
1110 },
1111 )
1112}
1113
1114pub(super) fn to_templates_vertices<'a>(
1118 fbb: &mut FlatBufferBuilder<'a>,
1119 vertices: &Value,
1120) -> flatbuffers::WIPOffset<flatbuffers::Vector<'a, DoubleVertex>> {
1121 let vertices_vec = vertices
1122 .as_array()
1123 .map(|vs| {
1124 vs.iter()
1125 .filter_map(|v| {
1126 let v = v.as_array()?;
1127 let coords: Vec<f64> = v.iter().filter_map(|c| c.as_f64()).collect();
1128 let [x, y, z]: [f64; 3] = coords.try_into().ok()?;
1129 Some(DoubleVertex::new(x, y, z))
1130 })
1131 .collect::<Vec<_>>()
1132 })
1133 .unwrap_or_default();
1134 fbb.create_vector(&vertices_vec)
1135}
1136
1137pub(crate) fn to_columns<'a>(
1138 fbb: &mut FlatBufferBuilder<'a>,
1139 attr_schema: &AttributeSchema,
1140) -> flatbuffers::WIPOffset<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Column<'a>>>> {
1141 let mut sorted_schema: Vec<_> = attr_schema.iter().collect();
1142 sorted_schema.sort_by_key(|(_, (index, _))| *index);
1143 let columns_vec = sorted_schema
1144 .iter()
1145 .map(|(name, (index, column_type))| {
1146 let name = fbb.create_string(name);
1147 Column::create(
1148 fbb,
1149 &ColumnArgs {
1150 name: Some(name),
1151 index: *index,
1152 type_: *column_type,
1153 ..Default::default()
1154 },
1155 )
1156 })
1157 .collect::<Vec<_>>();
1158 fbb.create_vector(&columns_vec)
1159}
1160
1161pub(super) fn to_fcb_attribute<'a>(
1162 fbb: &mut FlatBufferBuilder<'a>,
1163 attr: &Value,
1164 schema: &AttributeSchema,
1165) -> (
1166 flatbuffers::WIPOffset<flatbuffers::Vector<'a, u8>>,
1167 Option<AttributeSchema>,
1168) {
1169 let mut is_own_schema = false;
1170 for (key, _) in attr.as_object().unwrap().iter() {
1171 if !schema.contains_key(key) {
1172 is_own_schema = true;
1173 }
1174 }
1175 if is_own_schema {
1176 let mut own_schema = AttributeSchema::new();
1177 own_schema.add_attributes(attr);
1178 let encoded = encode_attributes_with_schema(attr, &own_schema);
1179 (fbb.create_vector(&encoded), Some(own_schema))
1180 } else {
1181 let encoded = encode_attributes_with_schema(attr, schema);
1182 (fbb.create_vector(&encoded), None)
1183 }
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::*;
1189
1190 use crate::{deserializer::to_cj_co_type, feature_generated::root_as_city_feature};
1191
1192 use anyhow::Result;
1193 use cjseq::CityJSONFeature;
1194 use flatbuffers::FlatBufferBuilder;
1195 use pretty_assertions::assert_eq;
1196
1197 #[test]
1198 fn test_to_fcb_city_feature() -> Result<()> {
1199 let cj_city_feature: CityJSONFeature = CityJSONFeature::from_str(
1200 r#"{"type":"CityJSONFeature","id":"NL.IMBAG.Pand.0503100000005156","CityObjects":{"NL.IMBAG.Pand.0503100000005156-0":{"type":"BuildingPart","attributes":{},"geometry":[{"type":"Solid","lod":"1.2","boundaries":[[[[6,1,0,5,4,3,7,8]],[[9,5,0,10]],[[10,0,1,11]],[[12,3,4,13]],[[13,4,5,9]],[[14,7,3,12]],[[15,8,7,14]],[[16,6,8,15]],[[11,1,6,16]],[[11,16,15,14,12,13,9,10]]]],"semantics":{"surfaces":[{"type":"GroundSurface"},{"type":"RoofSurface"},{"on_footprint_edge":true,"type":"WallSurface"},{"on_footprint_edge":false,"type":"WallSurface"}],"values":[[0,2,2,2,2,2,2,2,2,1]]}},{"type":"Solid","lod":"1.3","boundaries":[[[[3,7,8,6,1,17,0,5,4,18]],[[19,5,0,20]],[[21,22,17,1,23]],[[24,7,3,25]],[[26,8,7,24]],[[20,0,17,43]],[[44,45,43,46]],[[47,4,5,36]],[[48,18,4,47]],[[39,1,6,49]],[[41,3,18,48,50]],[[46,43,17,35,38]],[[49,6,8,42]],[[51,52,45,44]],[[53,54,55]],[[54,53,56]],[[50,48,52,51]],[[53,55,38,39,49,42]],[[54,56,44,46,38,55]],[[50,51,44,56,53,42,40,41]],[[52,48,47,36,37,43,45]]]],"semantics":{"surfaces":[{"type":"GroundSurface"},{"type":"RoofSurface"},{"on_footprint_edge":true,"type":"WallSurface"},{"on_footprint_edge":false,"type":"WallSurface"}],"values":[[0,2,2,2,2,2,3,2,2,2,2,2,3,3,1,1]]}},{"type":"Solid","lod":"2.2","boundaries":[[[[1,35,17,0,5,4,18,3,7,8,6]],[[36,5,0,37]],[[38,35,1,39]],[[40,7,3,41]],[[42,8,7,40]],[[37,0,17,43]],[[44,45,43,46]],[[47,4,5,36]],[[48,18,4,47]],[[39,1,6,49]],[[41,3,18,48,50]],[[46,43,17,35,38]],[[49,6,8,42]],[[51,52,45,44]],[[53,54,55]],[[54,53,56]],[[50,48,52,51]],[[53,55,38,39,49,42]],[[54,56,44,46,38,55]],[[50,51,44,56,53,42,40,41]],[[52,48,47,36,37,43,45]]]],"semantics":{"surfaces":[{"type":"GroundSurface"},{"type":"RoofSurface"},{"on_footprint_edge":true,"type":"WallSurface"},{"on_footprint_edge":false,"type":"WallSurface"}],"values":[[0,2,2,2,2,2,3,2,2,2,2,2,2,3,3,3,3,1,1,1,1]]}}],"parents":["NL.IMBAG.Pand.0503100000005156"]},"NL.IMBAG.Pand.0503100000005156":{"type":"Building","geographicalExtent":[84734.8046875,446636.5625,0.6919999718666077,84746.9453125,446651.0625,11.119057655334473],"attributes":{"b3_bag_bag_overlap":0.0,"b3_bouwlagen":3,"b3_dak_type":"slanted","b3_h_dak_50p":8.609999656677246,"b3_h_dak_70p":9.239999771118164,"b3_h_dak_max":10.970000267028809,"b3_h_dak_min":3.890000104904175,"b3_h_maaiveld":0.6919999718666077,"b3_kas_warenhuis":false,"b3_mutatie_ahn3_ahn4":false,"b3_nodata_fractie_ahn3":0.002518891589716077,"b3_nodata_fractie_ahn4":0.0,"b3_nodata_radius_ahn3":0.359510600566864,"b3_nodata_radius_ahn4":0.34349295496940613,"b3_opp_buitenmuur":165.03,"b3_opp_dak_plat":51.38,"b3_opp_dak_schuin":63.5,"b3_opp_grond":99.21,"b3_opp_scheidingsmuur":129.53,"b3_puntdichtheid_ahn3":16.353534698486328,"b3_puntdichtheid_ahn4":46.19647216796875,"b3_pw_bron":"AHN4","b3_pw_datum":2020,"b3_pw_selectie_reden":"PREFERRED_AND_LATEST","b3_reconstructie_onvolledig":false,"b3_rmse_lod12":3.2317864894866943,"b3_rmse_lod13":0.642620861530304,"b3_rmse_lod22":0.09925124794244766,"b3_val3dity_lod12":"[]","b3_val3dity_lod13":"[]","b3_val3dity_lod22":"[]","b3_volume_lod12":845.0095825195312,"b3_volume_lod13":657.8263549804688,"b3_volume_lod22":636.9927368164062,"begingeldigheid":"1999-04-28","documentdatum":"1999-04-28","documentnummer":"408040.tif","eindgeldigheid":null,"eindregistratie":null,"geconstateerd":false,"identificatie":"NL.IMBAG.Pand.0503100000005156","oorspronkelijkbouwjaar":2000,"status":"Pand in gebruik","tijdstipeindregistratielv":null,"tijdstipinactief":null,"tijdstipinactieflv":null,"tijdstipnietbaglv":null,"tijdstipregistratie":"2010-10-13T12:29:24Z","tijdstipregistratielv":"2010-10-13T12:30:50Z","voorkomenidentificatie":1},"geometry":[{"type":"MultiSurface","lod":"0","boundaries":[[[0,1,2,3,4,5]]]}],"children":["NL.IMBAG.Pand.0503100000005156-0"]}},"vertices":[[-353581,253246,-44957],[-348730,242291,-44957],[-343550,244604,-44957],[-344288,246257,-44957],[-341437,247537,-44957],[-345635,256798,-44957],[-343558,244600,-44957],[-343662,244854,-44957],[-343926,244734,-44957],[-345635,256798,-36439],[-353581,253246,-36439],[-348730,242291,-36439],[-344288,246257,-36439],[-341437,247537,-36439],[-343662,244854,-36439],[-343926,244734,-36439],[-343558,244600,-36439],[-352596,251020,-44957],[-344083,246349,-44957],[-345635,256798,-41490],[-353581,253246,-41490],[-352596,251020,-35952],[-352596,251020,-41490],[-348730,242291,-35952],[-343662,244854,-35952],[-344288,246257,-35952],[-343926,244734,-35952],[-347233,253386,-35952],[-347233,253386,-41490],[-341437,247537,-41490],[-344083,246349,-41490],[-343558,244600,-35952],[-344083,246349,-35952],[-347089,253741,-35952],[-347089,253741,-41490],[-350613,246543,-44957],[-345635,256798,-41507],[-353581,253246,-41516],[-350613,246543,-34688],[-348730,242291,-36953],[-343662,244854,-37089],[-344288,246257,-37099],[-343926,244734,-36944],[-352596,251020,-41514],[-347233,253386,-37262],[-347233,253386,-41508],[-352596,251020,-37264],[-341437,247537,-41498],[-344083,246349,-41501],[-343558,244600,-37083],[-344083,246349,-37212],[-347089,253741,-37402],[-347089,253741,-41508],[-349425,246738,-34864],[-349425,246738,-34529],[-349862,246897,-34699],[-349238,248437,-35307]]}"#,
1201 )?;
1202
1203 let mut attr_schema = AttributeSchema::new();
1204 for (_, co) in cj_city_feature.city_objects.iter() {
1205 if let Some(attr) = &co.attributes {
1206 attr_schema.add_attributes(attr);
1207 }
1208 }
1209
1210 let mut fbb = FlatBufferBuilder::new();
1212
1213 let (city_feature, _) =
1214 to_fcb_city_feature(&mut fbb, "test_id", &cj_city_feature, &attr_schema, None);
1215
1216 fbb.finish(city_feature, None);
1217 let buf = fbb.finished_data();
1218
1219 let fb_city_feature = root_as_city_feature(buf).unwrap();
1221 assert_eq!("test_id", fb_city_feature.id());
1222 assert_eq!(
1223 cj_city_feature.city_objects.len(),
1224 fb_city_feature.objects().unwrap().len()
1225 );
1226
1227 assert_eq!(
1228 cj_city_feature.vertices.len(),
1229 fb_city_feature.vertices().unwrap().len()
1230 );
1231 assert_eq!(
1232 cj_city_feature.vertices[0][0],
1233 fb_city_feature.vertices().unwrap().get(0).x() as i64,
1234 );
1235 assert_eq!(
1236 cj_city_feature.vertices[0][1],
1237 fb_city_feature.vertices().unwrap().get(0).y() as i64,
1238 );
1239 assert_eq!(
1240 cj_city_feature.vertices[0][2],
1241 fb_city_feature.vertices().unwrap().get(0).z() as i64,
1242 );
1243
1244 assert_eq!(
1245 cj_city_feature.vertices[1][0],
1246 fb_city_feature.vertices().unwrap().get(1).x() as i64,
1247 );
1248 assert_eq!(
1249 cj_city_feature.vertices[1][1],
1250 fb_city_feature.vertices().unwrap().get(1).y() as i64,
1251 );
1252 assert_eq!(
1253 cj_city_feature.vertices[1][2],
1254 fb_city_feature.vertices().unwrap().get(1).z() as i64,
1255 );
1256
1257 for (id, cjco) in cj_city_feature.city_objects.iter() {
1259 let fb_city_object = fb_city_feature
1260 .objects()
1261 .unwrap()
1262 .iter()
1263 .find(|co| co.id() == id)
1264 .unwrap();
1265 assert_eq!(id, fb_city_object.id());
1266 assert_eq!(cjco.thetype, to_cj_co_type(fb_city_object.type_(), None));
1267
1268 let fb_geometry = fb_city_object.geometry().unwrap();
1271 for fb_geometry in fb_geometry.iter() {
1272 let cj_geometry = cjco
1273 .geometry
1274 .as_ref()
1275 .unwrap()
1276 .iter()
1277 .find(|g| g.lod() == fb_geometry.lod())
1278 .unwrap();
1279 assert_eq!(
1280 cj_geometry.geometry_type(),
1281 fb_geometry
1282 .type_()
1283 .to_cj()
1284 .expect("a written geometry has a known type")
1285 );
1286 }
1287
1288 if let Some(parents) = cjco.parents.as_ref() {
1289 for parent in fb_city_object.parents().unwrap().iter() {
1290 assert!(parents.contains(&parent.to_string()));
1291 }
1292 }
1293
1294 if let Some(children) = cjco.children.as_ref() {
1295 for child in fb_city_object.children().unwrap().iter() {
1296 assert!(children.contains(&child.to_string()));
1297 }
1298 }
1299
1300 if let Some(ge) = cjco.geographical_extent.as_ref() {
1301 assert_eq!(
1303 ge[0],
1304 fb_city_object.geographical_extent().unwrap().min().x()
1305 );
1306 assert_eq!(
1307 ge[1],
1308 fb_city_object.geographical_extent().unwrap().min().y()
1309 );
1310 assert_eq!(
1311 cjco.geographical_extent.as_ref().unwrap()[2],
1312 fb_city_object.geographical_extent().unwrap().min().z()
1313 );
1314
1315 assert_eq!(
1317 cjco.geographical_extent.as_ref().unwrap()[3],
1318 fb_city_object.geographical_extent().unwrap().max().x()
1319 );
1320 assert_eq!(
1321 cjco.geographical_extent.as_ref().unwrap()[4],
1322 fb_city_object.geographical_extent().unwrap().max().y()
1323 );
1324 assert_eq!(
1325 cjco.geographical_extent.as_ref().unwrap()[5],
1326 fb_city_object.geographical_extent().unwrap().max().z()
1327 );
1328 }
1329 }
1330
1331 Ok(())
1332 }
1333
1334 #[test]
1346 fn either_postcode_spelling_is_accepted_and_both_come_back_as_postcode() -> Result<()> {
1347 let address_after_round_trip = |address: serde_json::Value| -> Result<cjseq::Address> {
1348 let cj: CityJSON = serde_json::from_value(serde_json::json!({
1349 "type": "CityJSON",
1350 "version": "2.0",
1351 "transform": {"scale": [1.0, 1.0, 1.0], "translate": [0.0, 0.0, 0.0]},
1352 "CityObjects": {},
1353 "vertices": [],
1354 "metadata": {
1355 "pointOfContact": {
1356 "contactName": "A Person",
1357 "emailAddress": "a@example.org",
1358 "address": address
1359 }
1360 }
1361 }))?;
1362
1363 let mut fbb = FlatBufferBuilder::new();
1364 let header = to_fcb_header(
1365 &mut fbb,
1366 &cj,
1367 HeaderWriterOptions {
1368 write_index: false,
1369 feature_count: 0,
1370 index_node_size: 16,
1371 attribute_indices: None,
1372 geographical_extent: None,
1373 },
1374 &AttributeSchema::new(),
1375 None,
1376 None,
1377 )?;
1378 fbb.finish(header, None);
1379 let buf = fbb.finished_data().to_vec();
1380 let header = flatbuffers::root::<crate::fb::Header>(&buf).unwrap();
1381 Ok(crate::reader::deserializer::to_cj_address(&header)
1382 .expect("the address has a postcode, so it is not empty"))
1383 };
1384
1385 for spelling in ["postcode", "postalCode"] {
1386 let address = address_after_round_trip(serde_json::json!({
1387 "locality": "Delft",
1388 spelling: "2628 CN"
1389 }))?;
1390 assert_eq!(
1391 address.members.get("postcode"),
1392 Some(&serde_json::json!("2628 CN")),
1393 "`{spelling}` must be accepted and written back as `postcode`"
1394 );
1395 assert_eq!(
1396 address.members.get("postalCode"),
1397 None,
1398 "the header has one postcode slot; `postalCode` is not also emitted"
1399 );
1400 assert_eq!(
1402 address.members.get("locality"),
1403 Some(&serde_json::json!("Delft"))
1404 );
1405 }
1406
1407 let address = address_after_round_trip(serde_json::json!({
1410 "postcode": "2628 CN",
1411 "postalCode": "1234 AB"
1412 }))?;
1413 assert_eq!(
1414 address.members.get("postcode"),
1415 Some(&serde_json::json!("2628 CN"))
1416 );
1417
1418 Ok(())
1419 }
1420}