Skip to main content

cjseq/
geometry.rs

1use crate::appearance::{MaterialReference, TextureReference};
2use crate::error::{CjseqError, Result};
3use crate::semantics::Semantics;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::HashMap;
7
8/// A closed sequence of vertex indices bounding a face.
9pub type Ring = Vec<usize>;
10/// A face: the exterior ring first, then any interior rings.
11pub type Surface = Vec<Ring>;
12/// A closed collection of surfaces bounding a volume.
13pub type Shell = Vec<Surface>;
14
15/// The tag of a [`Geometry`], for callers that need the kind without matching
16/// on the whole enum.
17#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
18pub enum GeometryType {
19    MultiPoint,
20    MultiLineString,
21    MultiSurface,
22    CompositeSurface,
23    Solid,
24    MultiSolid,
25    CompositeSolid,
26    GeometryInstance,
27}
28
29impl GeometryType {
30    /// The nesting depth `boundaries` must have for this geometry type: the
31    /// number of array levels between the outermost array and a vertex index.
32    pub fn boundary_depth(self) -> usize {
33        match self {
34            GeometryType::MultiPoint | GeometryType::GeometryInstance => 1,
35            GeometryType::MultiLineString => 2,
36            GeometryType::MultiSurface | GeometryType::CompositeSurface => 3,
37            GeometryType::Solid => 4,
38            GeometryType::MultiSolid | GeometryType::CompositeSolid => 5,
39        }
40    }
41}
42
43/// The members every geometry may carry that are neither its type, its lod,
44/// nor its boundaries. The appearance values are depth-typed, keyed by theme.
45#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
46pub struct GeometryCommon {
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub semantics: Option<Semantics>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub material: Option<HashMap<String, MaterialReference>>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub texture: Option<HashMap<String, TextureReference>>,
53}
54
55/// The number of array levels between `v` and the deepest non-array value
56/// inside it. A non-array is 0 deep; `[]` is 1 deep.
57fn nesting_depth(v: &Value) -> usize {
58    match v {
59        Value::Array(a) => 1 + a.iter().map(nesting_depth).max().unwrap_or(0),
60        _ => 0,
61    }
62}
63
64/// A CityJSON geometry object.
65///
66/// The nesting depth of `boundaries` is part of the type: a `MultiSurface`
67/// whose boundaries are nested one level too deep does not deserialize, where
68/// previously it decoded happily into an untyped `serde_json::Value` and left
69/// every consumer to guess the depth.
70#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
71#[serde(tag = "type")]
72pub enum Geometry {
73    MultiPoint {
74        #[serde(skip_serializing_if = "Option::is_none")]
75        lod: Option<String>,
76        boundaries: Ring,
77        #[serde(flatten)]
78        common: GeometryCommon,
79    },
80    MultiLineString {
81        #[serde(skip_serializing_if = "Option::is_none")]
82        lod: Option<String>,
83        boundaries: Vec<Ring>,
84        #[serde(flatten)]
85        common: GeometryCommon,
86    },
87    MultiSurface {
88        #[serde(skip_serializing_if = "Option::is_none")]
89        lod: Option<String>,
90        boundaries: Vec<Surface>,
91        #[serde(flatten)]
92        common: GeometryCommon,
93    },
94    CompositeSurface {
95        #[serde(skip_serializing_if = "Option::is_none")]
96        lod: Option<String>,
97        boundaries: Vec<Surface>,
98        #[serde(flatten)]
99        common: GeometryCommon,
100    },
101    Solid {
102        #[serde(skip_serializing_if = "Option::is_none")]
103        lod: Option<String>,
104        boundaries: Vec<Shell>,
105        #[serde(flatten)]
106        common: GeometryCommon,
107    },
108    MultiSolid {
109        #[serde(skip_serializing_if = "Option::is_none")]
110        lod: Option<String>,
111        boundaries: Vec<Vec<Shell>>,
112        #[serde(flatten)]
113        common: GeometryCommon,
114    },
115    CompositeSolid {
116        #[serde(skip_serializing_if = "Option::is_none")]
117        lod: Option<String>,
118        boundaries: Vec<Vec<Shell>>,
119        #[serde(flatten)]
120        common: GeometryCommon,
121    },
122    GeometryInstance {
123        boundaries: Ring,
124        template: usize,
125        #[serde(rename = "transformationMatrix")]
126        transformation_matrix: [f64; 16],
127    },
128}
129
130impl Geometry {
131    /// Parse a geometry, reporting a wrong `boundaries` depth in terms of the
132    /// geometry type rather than in terms of serde's innards.
133    ///
134    /// `serde_json::from_value::<Geometry>` works just as well and is what
135    /// `#[derive(Deserialize)]` gives every containing type; the only thing
136    /// this adds is a readable error. Deserializing a deeply nested `Vec`
137    /// otherwise fails with "invalid type: integer, expected a sequence",
138    /// which says neither which geometry was at fault nor what depth it
139    /// wanted.
140    pub fn from_json_value(v: Value) -> Result<Geometry> {
141        //-- `&Value` is itself a Deserializer, so the happy path borrows `v`
142        //-- instead of deep-copying every boundary just to keep `v` alive for
143        //-- the error branch below.
144        let err = match Geometry::deserialize(&v) {
145            Ok(g) => return Ok(g),
146            Err(e) => e,
147        };
148        //-- can the failure be pinned on the depth of `boundaries`?
149        let thetype = v
150            .get("type")
151            .and_then(|t| GeometryType::deserialize(t).ok());
152        if let (Some(thetype), Some(boundaries)) = (thetype, v.get("boundaries")) {
153            let expected = thetype.boundary_depth();
154            let found = nesting_depth(boundaries);
155            if found != expected {
156                return Err(CjseqError::GeometryDepth {
157                    geometry_type: thetype,
158                    expected,
159                    found,
160                });
161            }
162        }
163        Err(CjseqError::Json(err))
164    }
165
166    pub fn geometry_type(&self) -> GeometryType {
167        match self {
168            Geometry::MultiPoint { .. } => GeometryType::MultiPoint,
169            Geometry::MultiLineString { .. } => GeometryType::MultiLineString,
170            Geometry::MultiSurface { .. } => GeometryType::MultiSurface,
171            Geometry::CompositeSurface { .. } => GeometryType::CompositeSurface,
172            Geometry::Solid { .. } => GeometryType::Solid,
173            Geometry::MultiSolid { .. } => GeometryType::MultiSolid,
174            Geometry::CompositeSolid { .. } => GeometryType::CompositeSolid,
175            Geometry::GeometryInstance { .. } => GeometryType::GeometryInstance,
176        }
177    }
178
179    /// The lod of this geometry; a [`Geometry::GeometryInstance`] has none.
180    pub fn lod(&self) -> Option<&str> {
181        match self {
182            Geometry::MultiPoint { lod, .. }
183            | Geometry::MultiLineString { lod, .. }
184            | Geometry::MultiSurface { lod, .. }
185            | Geometry::CompositeSurface { lod, .. }
186            | Geometry::Solid { lod, .. }
187            | Geometry::MultiSolid { lod, .. }
188            | Geometry::CompositeSolid { lod, .. } => lod.as_deref(),
189            Geometry::GeometryInstance { .. } => None,
190        }
191    }
192
193    /// The semantics/material/texture members, if this variant has them.
194    /// A [`Geometry::GeometryInstance`] carries none: its appearance lives on
195    /// the template it refers to.
196    pub fn common(&self) -> Option<&GeometryCommon> {
197        match self {
198            Geometry::MultiPoint { common, .. }
199            | Geometry::MultiLineString { common, .. }
200            | Geometry::MultiSurface { common, .. }
201            | Geometry::CompositeSurface { common, .. }
202            | Geometry::Solid { common, .. }
203            | Geometry::MultiSolid { common, .. }
204            | Geometry::CompositeSolid { common, .. } => Some(common),
205            Geometry::GeometryInstance { .. } => None,
206        }
207    }
208
209    pub fn common_mut(&mut self) -> Option<&mut GeometryCommon> {
210        match self {
211            Geometry::MultiPoint { common, .. }
212            | Geometry::MultiLineString { common, .. }
213            | Geometry::MultiSurface { common, .. }
214            | Geometry::CompositeSurface { common, .. }
215            | Geometry::Solid { common, .. }
216            | Geometry::MultiSolid { common, .. }
217            | Geometry::CompositeSolid { common, .. } => Some(common),
218            Geometry::GeometryInstance { .. } => None,
219        }
220    }
221
222    /// Every vertex index in `boundaries`, in document order, whatever the
223    /// nesting depth of this variant. The depth is known statically per
224    /// variant, so no runtime inspection of the boundaries is needed.
225    fn boundary_indices_mut(&mut self) -> Box<dyn Iterator<Item = &mut usize> + '_> {
226        match self {
227            Geometry::MultiPoint { boundaries, .. }
228            | Geometry::GeometryInstance { boundaries, .. } => Box::new(boundaries.iter_mut()),
229            Geometry::MultiLineString { boundaries, .. } => {
230                Box::new(boundaries.iter_mut().flatten())
231            }
232            Geometry::MultiSurface { boundaries, .. }
233            | Geometry::CompositeSurface { boundaries, .. } => {
234                Box::new(boundaries.iter_mut().flatten().flatten())
235            }
236            Geometry::Solid { boundaries, .. } => {
237                Box::new(boundaries.iter_mut().flatten().flatten().flatten())
238            }
239            Geometry::MultiSolid { boundaries, .. }
240            | Geometry::CompositeSolid { boundaries, .. } => Box::new(
241                boundaries
242                    .iter_mut()
243                    .flatten()
244                    .flatten()
245                    .flatten()
246                    .flatten(),
247            ),
248        }
249    }
250
251    pub(crate) fn update_geometry_boundaries(&mut self, violdnew: &mut HashMap<usize, usize>) {
252        for vi in self.boundary_indices_mut() {
253            let next = violdnew.len();
254            *vi = *violdnew.entry(*vi).or_insert(next);
255        }
256    }
257
258    pub(crate) fn offset_geometry_boundaries(&mut self, offset: usize) {
259        for vi in self.boundary_indices_mut() {
260            *vi += offset;
261        }
262    }
263
264    /// Renumber every material index against `m_oldnew`, first-encounter
265    /// order. The depth of `values` comes from its variant, so the geometry
266    /// type is never consulted.
267    pub(crate) fn update_material(&mut self, m_oldnew: &mut HashMap<usize, usize>) {
268        let Some(common) = self.common_mut() else {
269            return;
270        };
271        let Some(materials) = common.material.as_mut() else {
272            return;
273        };
274        for mat in materials.values_mut() {
275            //-- material.value colours the whole object
276            if let Some(thevalue) = mat.value {
277                let l = m_oldnew.len();
278                mat.value = Some(*m_oldnew.entry(thevalue).or_insert(l));
279                continue;
280            }
281            //-- else it's material.values, one index per surface. The outer
282            //-- Option is present-vs-absent, the inner null-vs-array; there is
283            //-- nothing to renumber unless both are Some.
284            let Some(values) = mat.values.as_mut().and_then(|v| v.as_mut()) else {
285                continue;
286            };
287            for x in values.indices_mut() {
288                let l = m_oldnew.len();
289                *x = *m_oldnew.entry(*x).or_insert(l);
290            }
291        }
292    }
293
294    /// Renumber every texture index against `t_oldnew` and every texture
295    /// vertex against `t_v_oldnew`. As with `update_material`, the depth comes
296    /// from the variant, so every geometry that can carry a texture is walked
297    /// the same way.
298    pub(crate) fn update_texture(
299        &mut self,
300        t_oldnew: &mut HashMap<usize, usize>,
301        t_v_oldnew: &mut HashMap<usize, usize>,
302        offset: usize,
303    ) {
304        let Some(common) = self.common_mut() else {
305            return;
306        };
307        let Some(textures) = common.texture.as_mut() else {
308            return;
309        };
310        //-- the first index of the innermost array is a texture, the rest are
311        //-- texture vertices; they are renumbered against different maps.
312        let mut remap = |is_texture: bool, thevalue: usize| -> usize {
313            if is_texture {
314                let l = t_oldnew.len();
315                *t_oldnew.entry(thevalue).or_insert(l)
316            } else {
317                match t_v_oldnew.get(&thevalue) {
318                    Some(y) => *y,
319                    None => {
320                        let l = t_v_oldnew.len();
321                        t_v_oldnew.insert(thevalue, l + offset);
322                        l
323                    }
324                }
325            }
326        };
327        for tex in textures.values_mut() {
328            //-- a theme may legally carry no `values` at all
329            let Some(values) = tex.values.as_mut() else {
330                continue;
331            };
332            for ring in values.rings_mut() {
333                for (k, z) in ring.iter_mut().enumerate() {
334                    if let Some(thevalue) = *z {
335                        *z = Some(remap(k == 0, thevalue));
336                    }
337                }
338            }
339        }
340    }
341}
342
343#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
344pub struct GeometryTemplates {
345    pub templates: Vec<Geometry>,
346    #[serde(rename = "vertices-templates")]
347    pub vertices_templates: Value,
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353
354    fn parse(v: serde_json::Value) -> std::result::Result<Geometry, serde_json::Error> {
355        serde_json::from_value(v)
356    }
357
358    #[test]
359    fn each_variant_has_its_own_depth() {
360        let cases = [
361            (GeometryType::MultiPoint, serde_json::json!([0, 1])),
362            (GeometryType::MultiLineString, serde_json::json!([[0, 1]])),
363            (GeometryType::MultiSurface, serde_json::json!([[[0, 1, 2]]])),
364            (
365                GeometryType::CompositeSurface,
366                serde_json::json!([[[0, 1, 2]]]),
367            ),
368            (GeometryType::Solid, serde_json::json!([[[[0, 1, 2]]]])),
369            (
370                GeometryType::MultiSolid,
371                serde_json::json!([[[[[0, 1, 2]]]]]),
372            ),
373            (
374                GeometryType::CompositeSolid,
375                serde_json::json!([[[[[0, 1, 2]]]]]),
376            ),
377        ];
378        for (thetype, boundaries) in cases {
379            let name = serde_json::to_value(thetype).unwrap();
380            let g = parse(serde_json::json!({
381                "type": name, "lod": "1", "boundaries": boundaries.clone()
382            }))
383            .unwrap_or_else(|e| panic!("{name} must accept its own depth: {e}"));
384            assert_eq!(g.geometry_type(), thetype);
385            assert_eq!(g.lod(), Some("1"));
386            //-- and it must serialize back to exactly the boundaries it was given
387            assert_eq!(
388                serde_json::to_value(&g).unwrap()["boundaries"],
389                boundaries,
390                "{name} must round-trip its boundaries"
391            );
392
393            //-- one level too deep must be rejected
394            let too_deep = serde_json::json!([boundaries]);
395            assert!(
396                parse(serde_json::json!({
397                    "type": name, "lod": "1", "boundaries": too_deep
398                }))
399                .is_err(),
400                "{name} must reject boundaries one level too deep"
401            );
402
403            //-- and one level too shallow must be rejected too
404            let too_shallow = boundaries[0].clone();
405            assert!(
406                parse(serde_json::json!({
407                    "type": name, "lod": "1", "boundaries": too_shallow
408                }))
409                .is_err(),
410                "{name} must reject boundaries one level too shallow"
411            );
412        }
413    }
414
415    #[test]
416    fn geometry_instance_roundtrips() {
417        let input = serde_json::json!({
418            "type": "GeometryInstance",
419            "boundaries": [372],
420            "template": 0,
421            "transformationMatrix": [
422                1.0, 0.0, 0.0, 0.0,
423                0.0, 1.0, 0.0, 0.0,
424                0.0, 0.0, 1.0, 0.0,
425                0.0, 0.0, 0.0, 1.0
426            ]
427        });
428        let g: Geometry = serde_json::from_value(input.clone()).unwrap();
429        assert_eq!(g.geometry_type(), GeometryType::GeometryInstance);
430        assert!(g.common().is_none());
431        assert_eq!(serde_json::to_value(&g).unwrap(), input);
432    }
433
434    #[test]
435    fn offset_walks_every_depth() {
436        let mut g: Geometry = serde_json::from_value(serde_json::json!({
437            "type": "CompositeSolid", "lod": "2",
438            "boundaries": [[[[[0, 1]]]], [[[[2, 3]]]]]
439        }))
440        .unwrap();
441        g.offset_geometry_boundaries(10);
442        assert_eq!(
443            serde_json::to_value(&g).unwrap()["boundaries"],
444            serde_json::json!([[[[[10, 11]]]], [[[[12, 13]]]]])
445        );
446    }
447
448    #[test]
449    fn update_boundaries_compacts_indices() {
450        let mut g: Geometry = serde_json::from_value(serde_json::json!({
451            "type": "MultiSurface", "lod": "2",
452            "boundaries": [[[7, 9, 7]], [[9, 4, 4]]]
453        }))
454        .unwrap();
455        let mut map: HashMap<usize, usize> = HashMap::new();
456        g.update_geometry_boundaries(&mut map);
457        assert_eq!(
458            serde_json::to_value(&g).unwrap()["boundaries"],
459            serde_json::json!([[[0, 1, 0]], [[1, 2, 2]]])
460        );
461        assert_eq!(map.len(), 3);
462    }
463
464    /// The same first-encounter renumbering, but at the deepest traversal the
465    /// enum has, so the flattening order of the 5-level variants is pinned too.
466    /// Expected values produced by the pre-rewrite implementation at a97bc2d.
467    #[test]
468    fn update_boundaries_compacts_indices_at_max_depth() {
469        let mut g: Geometry = serde_json::from_value(serde_json::json!({
470            "type": "CompositeSolid", "lod": "2",
471            "boundaries": [[[[[7, 9, 7]]]], [[[[9, 4, 4]]]]]
472        }))
473        .unwrap();
474        let mut map: HashMap<usize, usize> = HashMap::new();
475        g.update_geometry_boundaries(&mut map);
476        assert_eq!(
477            serde_json::to_value(&g).unwrap()["boundaries"],
478            serde_json::json!([[[[[0, 1, 0]]]], [[[[1, 2, 2]]]]])
479        );
480        let mut entries: Vec<(usize, usize)> = map.into_iter().collect();
481        entries.sort();
482        assert_eq!(entries, vec![(4, 2), (7, 0), (9, 1)]);
483    }
484
485    /// `update_texture` renumbers the first entry of each innermost array
486    /// against the texture map and the rest against the texture-vertex map.
487    ///
488    /// Every expected value below was produced by running the pre-rewrite
489    /// implementation (commit a97bc2d) on this exact input; the test exists to
490    /// prove the rewrite is behaviour-preserving. Note in particular that the
491    /// texture-vertex numbering is asymmetric: the *first* occurrence of a
492    /// vertex is written as `l` while later occurrences are written as the
493    /// stored `l + offset` (see 20 -> 0 then 20 -> 100 below). That is upstream
494    /// behaviour, quirk included, and it is pinned here deliberately.
495    #[test]
496    fn update_texture_renumbers_textures_and_texture_vertices() {
497        let mut g: Geometry = serde_json::from_value(serde_json::json!({
498            "type": "MultiSurface", "lod": "2",
499            "boundaries": [[[0, 1, 2, 3]], [[4, 5, 6, 7]], [[8, 9, 10, 11]]],
500            "texture": {"winter": {"values": [
501                [[5, 20, 21, 22, 20]],
502                [[5, 22, 21, 30, 22]],
503                [[null]]
504            ]}}
505        }))
506        .unwrap();
507        let mut t_oldnew: HashMap<usize, usize> = HashMap::new();
508        let mut t_v_oldnew: HashMap<usize, usize> = HashMap::new();
509        g.update_texture(&mut t_oldnew, &mut t_v_oldnew, 100);
510
511        //-- `values` is now typed, so compare its serialization: the expected
512        //-- values below are untouched from the oracle run at a97bc2d.
513        let values =
514            serde_json::to_value(&g.common().unwrap().texture.as_ref().unwrap()["winter"].values)
515                .unwrap();
516        assert_eq!(
517            values,
518            serde_json::json!([[[0, 0, 1, 2, 100]], [[0, 102, 101, 3, 102]], [[null]]]),
519            "texture values must renumber exactly as the old implementation did"
520        );
521
522        let mut t: Vec<(usize, usize)> = t_oldnew.into_iter().collect();
523        t.sort();
524        assert_eq!(t, vec![(5, 0)], "the repeated texture index 5 maps once");
525
526        let mut tv: Vec<(usize, usize)> = t_v_oldnew.into_iter().collect();
527        tv.sort();
528        assert_eq!(
529            tv,
530            vec![(20, 100), (21, 101), (22, 102), (30, 103)],
531            "texture vertices are stored offset by `offset`"
532        );
533    }
534
535    /// `update_material` renumbers `material.values` at the depth the geometry
536    /// type implies, and `null` holes (surfaces with no material) must survive
537    /// untouched. Expected values produced by the pre-rewrite implementation at
538    /// a97bc2d on this exact input.
539    #[test]
540    fn update_material_preserves_null_holes() {
541        let mut g: Geometry = serde_json::from_value(serde_json::json!({
542            "type": "Solid", "lod": "2",
543            "boundaries": [[[[0, 1, 2, 3]], [[4, 5, 6, 7]], [[8, 9, 10, 11]]]],
544            "material": {"irradiation": {"values": [[0, null, 2], [2, null, 0]]}}
545        }))
546        .unwrap();
547        let mut m_oldnew: HashMap<usize, usize> = HashMap::new();
548        g.update_material(&mut m_oldnew);
549
550        //-- as above: typed `values`, oracle expectations unchanged.
551        let values = serde_json::to_value(
552            g.common().unwrap().material.as_ref().unwrap()["irradiation"]
553                .values
554                .as_ref()
555                .unwrap(),
556        )
557        .unwrap();
558        assert_eq!(
559            values,
560            serde_json::json!([[0, null, 1], [1, null, 0]]),
561            "null holes must survive and indices must compact in first-encounter order"
562        );
563
564        let mut m: Vec<(usize, usize)> = m_oldnew.into_iter().collect();
565        m.sort();
566        assert_eq!(m, vec![(0, 0), (2, 1)]);
567    }
568
569    /// A raw serde error on a deeply nested `Vec` reads "invalid type:
570    /// integer, expected a sequence", which names neither the geometry nor the
571    /// depth it wanted. `from_json_value` must say both.
572    #[test]
573    fn wrong_depth_reports_which_geometry_and_which_depth() {
574        let err = Geometry::from_json_value(serde_json::json!({
575            "type": "MultiSurface", "lod": "2",
576            "boundaries": [[[[0, 1, 2]]]]
577        }))
578        .unwrap_err();
579
580        match &err {
581            CjseqError::GeometryDepth {
582                geometry_type,
583                expected,
584                found,
585            } => {
586                assert_eq!(*geometry_type, GeometryType::MultiSurface);
587                assert_eq!(*expected, 3, "a MultiSurface nests boundaries 3 deep");
588                assert_eq!(
589                    *found, 4,
590                    "the depth actually found must be reported as a number, not prose"
591                );
592            }
593            other => panic!("expected a GeometryDepth error, got {other:?}"),
594        }
595
596        let msg = err.to_string();
597        assert!(
598            msg.contains("MultiSurface") && msg.contains('3'),
599            "the message must name the geometry and the depth it expected, got {msg:?}"
600        );
601    }
602
603    /// A geometry whose boundaries are the right depth but which is malformed
604    /// some other way is not misreported as a depth problem.
605    #[test]
606    fn a_non_depth_failure_stays_a_json_error() {
607        let err = Geometry::from_json_value(serde_json::json!({
608            "type": "MultiSurface", "lod": "2",
609            "boundaries": [[["not an index"]]]
610        }))
611        .unwrap_err();
612        assert!(
613            matches!(err, CjseqError::Json(_)),
614            "expected a Json error, got {err:?}"
615        );
616    }
617
618    #[test]
619    fn from_json_value_accepts_a_good_geometry() {
620        let g = Geometry::from_json_value(serde_json::json!({
621            "type": "Solid", "lod": "2", "boundaries": [[[[0, 1, 2]]]]
622        }))
623        .unwrap();
624        assert_eq!(g.geometry_type(), GeometryType::Solid);
625    }
626
627    #[test]
628    fn absent_members_are_not_serialized_as_null() {
629        let g: Geometry = serde_json::from_value(serde_json::json!({
630            "type": "MultiSurface", "boundaries": [[[0, 1, 2]]]
631        }))
632        .unwrap();
633        assert_eq!(
634            serde_json::to_value(&g).unwrap(),
635            serde_json::json!({"type": "MultiSurface", "boundaries": [[[0, 1, 2]]]})
636        );
637    }
638}