1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4
5#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
11pub enum KnownSemanticSurfaceType {
12 RoofSurface,
13 GroundSurface,
14 WallSurface,
15 ClosureSurface,
16 OuterCeilingSurface,
17 OuterFloorSurface,
18 Window,
19 Door,
20 InteriorWallSurface,
21 CeilingSurface,
22 FloorSurface,
23 WaterSurface,
24 WaterGroundSurface,
25 WaterClosureSurface,
26 TrafficArea,
27 AuxiliaryTrafficArea,
28 TransportationMarking,
29 TransportationHole,
30}
31
32#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
72#[serde(untagged)]
73pub enum SemanticSurfaceType {
74 Known(KnownSemanticSurfaceType),
75 Extension(String),
78}
79
80#[allow(non_upper_case_globals)]
87impl SemanticSurfaceType {
88 pub const RoofSurface: SemanticSurfaceType =
89 SemanticSurfaceType::Known(KnownSemanticSurfaceType::RoofSurface);
90 pub const GroundSurface: SemanticSurfaceType =
91 SemanticSurfaceType::Known(KnownSemanticSurfaceType::GroundSurface);
92 pub const WallSurface: SemanticSurfaceType =
93 SemanticSurfaceType::Known(KnownSemanticSurfaceType::WallSurface);
94 pub const ClosureSurface: SemanticSurfaceType =
95 SemanticSurfaceType::Known(KnownSemanticSurfaceType::ClosureSurface);
96 pub const OuterCeilingSurface: SemanticSurfaceType =
97 SemanticSurfaceType::Known(KnownSemanticSurfaceType::OuterCeilingSurface);
98 pub const OuterFloorSurface: SemanticSurfaceType =
99 SemanticSurfaceType::Known(KnownSemanticSurfaceType::OuterFloorSurface);
100 pub const Window: SemanticSurfaceType =
101 SemanticSurfaceType::Known(KnownSemanticSurfaceType::Window);
102 pub const Door: SemanticSurfaceType =
103 SemanticSurfaceType::Known(KnownSemanticSurfaceType::Door);
104 pub const InteriorWallSurface: SemanticSurfaceType =
105 SemanticSurfaceType::Known(KnownSemanticSurfaceType::InteriorWallSurface);
106 pub const CeilingSurface: SemanticSurfaceType =
107 SemanticSurfaceType::Known(KnownSemanticSurfaceType::CeilingSurface);
108 pub const FloorSurface: SemanticSurfaceType =
109 SemanticSurfaceType::Known(KnownSemanticSurfaceType::FloorSurface);
110 pub const WaterSurface: SemanticSurfaceType =
111 SemanticSurfaceType::Known(KnownSemanticSurfaceType::WaterSurface);
112 pub const WaterGroundSurface: SemanticSurfaceType =
113 SemanticSurfaceType::Known(KnownSemanticSurfaceType::WaterGroundSurface);
114 pub const WaterClosureSurface: SemanticSurfaceType =
115 SemanticSurfaceType::Known(KnownSemanticSurfaceType::WaterClosureSurface);
116 pub const TrafficArea: SemanticSurfaceType =
117 SemanticSurfaceType::Known(KnownSemanticSurfaceType::TrafficArea);
118 pub const AuxiliaryTrafficArea: SemanticSurfaceType =
119 SemanticSurfaceType::Known(KnownSemanticSurfaceType::AuxiliaryTrafficArea);
120 pub const TransportationMarking: SemanticSurfaceType =
121 SemanticSurfaceType::Known(KnownSemanticSurfaceType::TransportationMarking);
122 pub const TransportationHole: SemanticSurfaceType =
123 SemanticSurfaceType::Known(KnownSemanticSurfaceType::TransportationHole);
124}
125
126#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
129pub struct SemanticsSurface {
130 #[serde(rename = "type")]
131 pub thetype: SemanticSurfaceType,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub parent: Option<usize>,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub children: Option<Vec<usize>>,
136 #[serde(flatten)]
140 pub other: HashMap<String, Value>,
141}
142
143pub type SemanticsShell = Vec<Option<usize>>;
148pub type SemanticsSolid = Vec<Option<SemanticsShell>>;
150
151#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
163#[serde(untagged)]
164pub enum SemanticsValues {
165 Surfaces(Vec<Option<usize>>),
167 Shells(Vec<Option<SemanticsShell>>),
169 Solids(Vec<Option<SemanticsSolid>>),
172}
173
174#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
188pub struct Semantics {
189 pub surfaces: Vec<SemanticsSurface>,
190 pub values: Option<SemanticsValues>,
191 #[serde(flatten)]
192 pub other: HashMap<String, Value>,
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
205 fn semantics_keeps_members_the_schema_does_not_name() {
206 let input = serde_json::json!({
207 "surfaces": [{"type": "RoofSurface"}],
208 "values": [0],
209 "vendorData": {"x": 1}
210 });
211 let s: Semantics = serde_json::from_value(input.clone()).unwrap();
212 assert_eq!(s.other["vendorData"], serde_json::json!({"x": 1}));
213 assert_eq!(serde_json::to_value(&s).unwrap(), input);
214 }
215
216 #[test]
218 fn semantics_without_extras_serializes_unchanged() {
219 let input = serde_json::json!({
220 "surfaces": [{"type": "RoofSurface"}],
221 "values": null
222 });
223 let s: Semantics = serde_json::from_value(input.clone()).unwrap();
224 assert!(s.other.is_empty());
225 assert_eq!(serde_json::to_value(&s).unwrap(), input);
226 }
227
228 #[test]
229 fn solid_semantics_values_are_one_per_surface_per_shell() {
230 let s: Semantics = serde_json::from_value(serde_json::json!({
231 "surfaces": [{"type": "RoofSurface"}, {"type": "WallSurface"}],
232 "values": [[0, 1, null]]
233 }))
234 .unwrap();
235 assert_eq!(
236 serde_json::to_value(&s).unwrap()["values"],
237 serde_json::json!([[0, 1, null]])
238 );
239 }
240
241 #[test]
242 fn null_semantics_index_stays_null() {
243 let s: Semantics = serde_json::from_value(serde_json::json!({
244 "surfaces": [{"type": "RoofSurface"}], "values": [null, 0]
245 }))
246 .unwrap();
247 assert_eq!(
249 serde_json::to_value(&s).unwrap()["values"],
250 serde_json::json!([null, 0])
251 );
252 }
253
254 #[test]
262 fn semantics_surface_and_semantics_implement_eq() {
263 fn assert_impls_eq<T: Eq>() {}
264 assert_impls_eq::<SemanticsSurface>();
265 assert_impls_eq::<SemanticsValues>();
266 assert_impls_eq::<Semantics>();
267 }
268
269 #[test]
272 fn surface_extras_and_hierarchy_round_trip() {
273 let input = serde_json::json!({
274 "surfaces": [
275 {"type": "RoofSurface", "slope": 33.4, "children": [1]},
276 {"type": "Window", "parent": 0, "direction": "north"}
277 ],
278 "values": [0, 1]
279 });
280 let s: Semantics = serde_json::from_value(input.clone()).unwrap();
281 assert_eq!(s.surfaces[0].children, Some(vec![1]));
282 assert_eq!(s.surfaces[1].parent, Some(0));
283 assert_eq!(
284 s.surfaces[1].other["direction"],
285 serde_json::json!("north"),
286 "extra members must be kept, not dropped"
287 );
288 assert_eq!(serde_json::to_value(&s).unwrap(), input);
289 }
290
291 #[test]
299 fn surface_with_no_extras_round_trips_without_spurious_members() {
300 let input = serde_json::json!({
301 "surfaces": [{"type": "RoofSurface"}],
302 "values": [0]
303 });
304 let s: Semantics = serde_json::from_value(input.clone()).unwrap();
305 assert!(s.surfaces[0].other.is_empty());
306 assert_eq!(serde_json::to_value(&s).unwrap(), input);
307 }
308
309 #[test]
312 fn each_shape_lands_in_the_variant_its_depth_implies() {
313 let parse =
314 |v: serde_json::Value| -> SemanticsValues { serde_json::from_value(v).unwrap() };
315
316 assert!(matches!(
317 parse(serde_json::json!([0, 1, null])),
318 SemanticsValues::Surfaces(_)
319 ));
320 assert!(matches!(
321 parse(serde_json::json!([[0, 1, null]])),
322 SemanticsValues::Shells(_)
323 ));
324 assert!(matches!(
325 parse(serde_json::json!([[[0, 1, null]]])),
326 SemanticsValues::Solids(_)
327 ));
328 let empty = parse(serde_json::json!([]));
331 assert!(matches!(empty, SemanticsValues::Surfaces(_)));
332 assert_eq!(serde_json::to_value(&empty).unwrap(), serde_json::json!([]));
333
334 assert!(serde_json::from_value::<SemanticsValues>(serde_json::json!([[[[0]]]])).is_err());
336 }
337
338 #[test]
343 fn a_null_semantics_sub_array_round_trips_as_null_at_every_level() {
344 for input in [
345 serde_json::json!([0, null, 2]),
346 serde_json::json!([[0, 1], null]),
347 serde_json::json!([null, [0, 1]]),
348 serde_json::json!([[[0, 1]], null]),
349 serde_json::json!([[[0, 1], null], null]),
350 serde_json::json!([null, null]),
351 serde_json::json!([[null], null]),
352 ] {
353 let v: SemanticsValues = serde_json::from_value(input.clone())
354 .unwrap_or_else(|e| panic!("{input} is schema-valid CityJSON: {e}"));
355 assert_eq!(
356 serde_json::to_value(&v).unwrap(),
357 input,
358 "{input} must round-trip with its nulls intact, never as []"
359 );
360 }
361 }
362
363 #[test]
367 fn a_null_semantics_values_member_round_trips() {
368 let input = serde_json::json!({
369 "surfaces": [{"type": "RoofSurface"}], "values": null
370 });
371 let s: Semantics =
372 serde_json::from_value(input.clone()).expect("`values: null` is schema-valid CityJSON");
373 assert_eq!(s.values, None);
374 assert_eq!(serde_json::to_value(&s).unwrap(), input);
375 }
376
377 #[test]
380 fn null_sub_arrays_do_not_disturb_the_semantics_ladder() {
381 let parse =
382 |v: serde_json::Value| -> SemanticsValues { serde_json::from_value(v).unwrap() };
383
384 assert!(matches!(
385 parse(serde_json::json!([null, 1])),
386 SemanticsValues::Surfaces(_)
387 ));
388 assert!(matches!(
389 parse(serde_json::json!([[0, 1], null])),
390 SemanticsValues::Shells(_)
391 ));
392 assert!(matches!(
393 parse(serde_json::json!([[[0, 1]], null])),
394 SemanticsValues::Solids(_)
395 ));
396
397 for input in [
398 serde_json::json!([]),
399 serde_json::json!([[]]),
400 serde_json::json!([[[]]]),
401 ] {
402 let v = parse(input.clone());
403 assert_eq!(serde_json::to_value(&v).unwrap(), input, "{input}");
404 }
405 assert!(matches!(
406 parse(serde_json::json!([])),
407 SemanticsValues::Surfaces(_)
408 ));
409 assert!(matches!(
410 parse(serde_json::json!([[]])),
411 SemanticsValues::Shells(_)
412 ));
413 assert!(matches!(
414 parse(serde_json::json!([[[]]])),
415 SemanticsValues::Solids(_)
416 ));
417 }
418
419 #[test]
423 fn extension_semantic_surface_type_roundtrips_with_its_plus() {
424 let t: SemanticSurfaceType =
425 serde_json::from_value(serde_json::json!("+ThermalSurface")).unwrap();
426 assert_eq!(t, SemanticSurfaceType::Extension("+ThermalSurface".into()));
427 assert_eq!(
428 serde_json::to_value(&t).unwrap(),
429 serde_json::json!("+ThermalSurface")
430 );
431 }
432
433 #[test]
434 fn plus_prefixed_known_surface_name_lands_in_extension() {
435 let t: SemanticSurfaceType =
436 serde_json::from_value(serde_json::json!("+RoofSurface")).unwrap();
437 assert_eq!(t, SemanticSurfaceType::Extension("+RoofSurface".into()));
438 assert_ne!(t, SemanticSurfaceType::RoofSurface);
439 }
440
441 #[test]
455 fn extension_typed_semantics_surface_roundtrips_byte_for_byte_inside_a_geometry() {
456 let input = concat!(
457 r#"{"type":"MultiSurface","boundaries":[[[0,1,2]]],"#,
458 r#""semantics":{"surfaces":[{"type":"+ThermalSurface","azimuth":30.5}],"values":[0]}}"#
459 );
460 let parsed: crate::geometry::Geometry = serde_json::from_str(input).unwrap();
461 let reserialized = serde_json::to_string(&parsed).unwrap();
462 assert_eq!(
463 reserialized, input,
464 "an Extension-typed SemanticsSurface with an extra flattened member inside a \
465 Geometry must round-trip byte-for-byte"
466 );
467 }
468
469 #[test]
472 fn every_known_semantic_surface_type_round_trips_as_its_unit_variant() {
473 let known: &[(&str, SemanticSurfaceType)] = &[
474 ("RoofSurface", SemanticSurfaceType::RoofSurface),
475 ("GroundSurface", SemanticSurfaceType::GroundSurface),
476 ("WallSurface", SemanticSurfaceType::WallSurface),
477 ("ClosureSurface", SemanticSurfaceType::ClosureSurface),
478 (
479 "OuterCeilingSurface",
480 SemanticSurfaceType::OuterCeilingSurface,
481 ),
482 ("OuterFloorSurface", SemanticSurfaceType::OuterFloorSurface),
483 ("Window", SemanticSurfaceType::Window),
484 ("Door", SemanticSurfaceType::Door),
485 (
486 "InteriorWallSurface",
487 SemanticSurfaceType::InteriorWallSurface,
488 ),
489 ("CeilingSurface", SemanticSurfaceType::CeilingSurface),
490 ("FloorSurface", SemanticSurfaceType::FloorSurface),
491 ("WaterSurface", SemanticSurfaceType::WaterSurface),
492 (
493 "WaterGroundSurface",
494 SemanticSurfaceType::WaterGroundSurface,
495 ),
496 (
497 "WaterClosureSurface",
498 SemanticSurfaceType::WaterClosureSurface,
499 ),
500 ("TrafficArea", SemanticSurfaceType::TrafficArea),
501 (
502 "AuxiliaryTrafficArea",
503 SemanticSurfaceType::AuxiliaryTrafficArea,
504 ),
505 (
506 "TransportationMarking",
507 SemanticSurfaceType::TransportationMarking,
508 ),
509 (
510 "TransportationHole",
511 SemanticSurfaceType::TransportationHole,
512 ),
513 ];
514 for (name, expected) in known {
515 let parsed: SemanticSurfaceType =
516 serde_json::from_value(serde_json::json!(name)).unwrap();
517 assert_eq!(&parsed, expected, "{name} did not parse to its own variant");
518 assert_eq!(
519 serde_json::to_value(&parsed).unwrap(),
520 serde_json::json!(name),
521 "{name} did not round-trip back to its own string"
522 );
523 }
524 }
525}