Skip to main content

mlt_core/convert/
geojson.rs

1//! `GeoJSON` -like data to represent decoded MLT data with i32 coordinates
2
3use std::collections::BTreeMap;
4use std::str::FromStr;
5
6use geo_types::Geometry;
7use serde::ser::SerializeMap as _;
8use serde::{Deserialize, Serialize};
9use serde_json::{Number, Value};
10
11use crate::decoder::PropValueRef;
12use crate::{LendingIterator, MltResult, ParsedLayer};
13#[cfg(feature = "unstable-v2")]
14use crate::{
15    ParsedLayer02, ZStep,
16    tile::{MValue, NestedValue},
17};
18
19/// `GeoJSON` [`FeatureCollection`]
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct FeatureCollection {
22    #[serde(rename = "type")]
23    pub ty: String,
24    pub features: Vec<Feature>,
25}
26
27impl FeatureCollection {
28    /// Convert already-decoded layers to a `GeoJSON` [`FeatureCollection`], consuming them.
29    /// Make sure to call `decode_all` on Layer before calling this (won't compile otherwise)
30    pub fn from_layers<'a>(layers: impl IntoIterator<Item = ParsedLayer<'a>>) -> MltResult<Self> {
31        let mut features = Vec::new();
32        for layer in layers {
33            // Read the v2-only columns first: the rest of the loop needs only the
34            // shared ones, which is all `parsed` keeps.
35            #[cfg(feature = "unstable-v2")]
36            let mut m_values = match &layer {
37                ParsedLayer::Tag01(_) | ParsedLayer::Unknown(_) => Vec::new().into_iter(),
38                ParsedLayer::Tag02(l) => m_value_properties(l)?.into_iter(),
39            };
40            #[cfg(feature = "unstable-v2")]
41            let mut nested = match &layer {
42                ParsedLayer::Tag01(_) | ParsedLayer::Unknown(_) => Vec::new().into_iter(),
43                ParsedLayer::Tag02(l) => nested_properties(l)?.into_iter(),
44            };
45            #[cfg(feature = "unstable-v2")]
46            let (z_step, mut z) = match &layer {
47                ParsedLayer::Tag01(_) | ParsedLayer::Unknown(_) => (None, Vec::new().into_iter()),
48                ParsedLayer::Tag02(l) => z_values(l)?,
49            };
50            let parsed = match layer {
51                ParsedLayer::Tag01(l) => l,
52                #[cfg(feature = "unstable-v2")]
53                ParsedLayer::Tag02(l) => l.into_layer(),
54                ParsedLayer::Unknown(_) => continue,
55            };
56            let layer_name = parsed.name();
57            let extent = parsed.extent().get();
58            let mut feat_iter = parsed.iter_features();
59            while let Some(feat) = feat_iter.next() {
60                let feat = feat?;
61                let mut properties = BTreeMap::new();
62                for p in feat.iter_properties() {
63                    properties.insert(p.name().to_string(), p.value().into());
64                }
65                #[cfg(feature = "unstable-v2")]
66                properties.extend(m_values.next().unwrap_or_default());
67                #[cfg(feature = "unstable-v2")]
68                properties.extend(nested.next().unwrap_or_default());
69                #[cfg(feature = "unstable-v2")]
70                if let Some(step) = z_step {
71                    properties.insert("_z_step".into(), step.exponent().into());
72                }
73                #[cfg(feature = "unstable-v2")]
74                let feature_z = z.next();
75                #[cfg(not(feature = "unstable-v2"))]
76                let feature_z = None;
77                properties.insert("_layer".into(), Value::String(layer_name.to_string()));
78                properties.insert("_extent".into(), Value::Number(extent.into()));
79                features.push(Feature {
80                    geometry: feat.geometry().clone(),
81                    z: feature_z,
82                    id: feat.id(),
83                    properties,
84                    ty: "Feature".into(),
85                });
86            }
87        }
88        Ok(Self {
89            features,
90            ty: "FeatureCollection".into(),
91        })
92    }
93
94    pub fn equals(&self, other: &Self) -> Result<bool, serde_json::Error> {
95        let self_val = normalize_tiny_floats(serde_json::to_value(self)?);
96        let other_val = normalize_tiny_floats(serde_json::to_value(other)?);
97        Ok(json_values_equal(&self_val, &other_val))
98    }
99}
100
101impl FromStr for FeatureCollection {
102    type Err = serde_json::Error;
103
104    fn from_str(s: &str) -> Result<Self, Self::Err> {
105        serde_json::from_str(s)
106    }
107}
108
109/// `GeoJSON` [`Feature`]
110#[derive(Debug, Clone, PartialEq, Deserialize)]
111#[serde(from = "FeatureWire")]
112pub struct Feature {
113    pub geometry: Geometry<i32>,
114    /// The z of each stored vertex in vertex order, written as the third value of each position.
115    pub z: Option<Vec<i32>>,
116    pub id: Option<u64>,
117    pub properties: BTreeMap<String, Value>,
118    pub ty: String,
119}
120
121#[derive(Deserialize)]
122struct FeatureWire {
123    #[serde(deserialize_with = "geom_serde::deserialize")]
124    geometry: geom_serde::GeometryWithZ,
125    #[serde(default)]
126    id: Option<u64>,
127    #[serde(default)]
128    properties: BTreeMap<String, Value>,
129    #[serde(rename = "type")]
130    ty: String,
131}
132
133impl From<FeatureWire> for Feature {
134    fn from(wire: FeatureWire) -> Self {
135        let (geometry, z) = wire.geometry;
136        Self {
137            geometry,
138            z,
139            id: wire.id,
140            properties: wire.properties,
141            ty: wire.ty,
142        }
143    }
144}
145
146struct Geom32Wire<'a>(&'a Geometry<i32>, Option<&'a [i32]>);
147impl Serialize for Geom32Wire<'_> {
148    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
149        geom_serde::serialize(self.0, self.1, s)
150    }
151}
152
153/// Serialize with the preferred order of the keys
154impl Serialize for Feature {
155    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
156        let len = 3 + usize::from(self.id.is_some());
157        let mut map = serializer.serialize_map(Some(len))?;
158        map.serialize_entry("type", &self.ty)?;
159        if let Some(id) = self.id {
160            map.serialize_entry("id", &id)?;
161        }
162        map.serialize_entry("properties", &self.properties)?;
163        map.serialize_entry("geometry", &Geom32Wire(&self.geometry, self.z.as_deref()))?;
164        map.end()
165    }
166}
167
168/// Serialize/deserialize [`Geometry<i32>`](geo_types::Geometry) in `GeoJSON` wire format:
169/// `{"type":"…","coordinates":…}` with `[x, y]` integer arrays, or `[x, y, z]` when the feature has z.
170mod geom_serde {
171    use geo_types::{
172        Coord, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
173    };
174    use serde::de::Error as _;
175    use serde::ser::{Error, SerializeMap as _};
176    use serde::{Deserialize, Deserializer, Serializer};
177    use serde_json::Value;
178
179    use crate::tile::stored_ring_len;
180
181    /// A geometry and the z of each stored vertex, when it has any.
182    pub type GeometryWithZ = (Geometry<i32>, Option<Vec<i32>>);
183
184    /// Writes positions, taking the next z for each stored vertex.
185    struct Writer<'a>(Option<std::slice::Iter<'a, i32>>);
186
187    impl Writer<'_> {
188        fn pos(&mut self, c: Coord<i32>) -> Result<Value, &'static str> {
189            let mut pos = vec![Value::from(c.x), Value::from(c.y)];
190            if let Some(z) = &mut self.0 {
191                pos.push(Value::from(*z.next().ok_or("fewer z than vertices")?));
192            }
193            Ok(Value::Array(pos))
194        }
195
196        fn line(&mut self, ls: &LineString<i32>) -> Result<Value, &'static str> {
197            ls.0.iter().map(|&c| self.pos(c)).collect()
198        }
199
200        /// A ring's closing position repeats its first, z included, because MLT does not store it.
201        fn ring(&mut self, ring: &LineString<i32>) -> Result<Value, &'static str> {
202            let stored = stored_ring_len(ring);
203            let mut positions = ring.0[..stored]
204                .iter()
205                .map(|&c| self.pos(c))
206                .collect::<Result<Vec<_>, _>>()?;
207            if stored < ring.0.len() {
208                positions.push(positions[0].clone());
209            }
210            Ok(Value::Array(positions))
211        }
212
213        fn poly(&mut self, poly: &Polygon<i32>) -> Result<Value, &'static str> {
214            std::iter::once(poly.exterior())
215                .chain(poly.interiors())
216                .map(|ring| self.ring(ring))
217                .collect()
218        }
219
220        fn coordinates(
221            &mut self,
222            g: &Geometry<i32>,
223        ) -> Result<(&'static str, Value), &'static str> {
224            Ok(match g {
225                Geometry::Point(p) => ("Point", self.pos(p.0)?),
226                Geometry::LineString(ls) => ("LineString", self.line(ls)?),
227                Geometry::Polygon(poly) => ("Polygon", self.poly(poly)?),
228                Geometry::MultiPoint(mp) => (
229                    "MultiPoint",
230                    mp.0.iter()
231                        .map(|p| self.pos(p.0))
232                        .collect::<Result<_, _>>()?,
233                ),
234                Geometry::MultiLineString(mls) => (
235                    "MultiLineString",
236                    mls.iter()
237                        .map(|ls| self.line(ls))
238                        .collect::<Result<_, _>>()?,
239                ),
240                Geometry::MultiPolygon(mpoly) => (
241                    "MultiPolygon",
242                    mpoly
243                        .iter()
244                        .map(|p| self.poly(p))
245                        .collect::<Result<_, _>>()?,
246                ),
247                Geometry::Line(_)
248                | Geometry::Rect(_)
249                | Geometry::Triangle(_)
250                | Geometry::GeometryCollection(_) => return Err("unsupported geometry variant"),
251            })
252        }
253    }
254
255    pub fn serialize<S: Serializer>(
256        g: &Geometry<i32>,
257        z: Option<&[i32]>,
258        s: S,
259    ) -> Result<S::Ok, S::Error> {
260        let mut writer = Writer(z.map(<[i32]>::iter));
261        let (ty, coords) = writer.coordinates(g).map_err(S::Error::custom)?;
262        if writer.0.is_some_and(|mut z| z.next().is_some()) {
263            return Err(S::Error::custom("more z than vertices"));
264        }
265        let mut m = s.serialize_map(Some(2))?;
266        m.serialize_entry("type", ty)?;
267        m.serialize_entry("coordinates", &coords)?;
268        m.end()
269    }
270
271    /// Reads positions, collecting the z of each stored vertex.
272    #[derive(Default)]
273    struct Reader {
274        z: Vec<i32>,
275        has_z: Option<bool>,
276    }
277
278    impl Reader {
279        fn pos(&mut self, pos: &[i32]) -> Result<Coord<i32>, &'static str> {
280            let (coord, z) = match *pos {
281                [x, y] => (Coord { x, y }, None),
282                [x, y, z] => (Coord { x, y }, Some(z)),
283                _ => return Err("a position needs two or three coordinates"),
284            };
285            if *self.has_z.get_or_insert(z.is_some()) != z.is_some() {
286                return Err("either every position has z or none does");
287            }
288            self.z.extend(z);
289            Ok(coord)
290        }
291
292        fn line(&mut self, positions: &[Vec<i32>]) -> Result<LineString<i32>, &'static str> {
293            positions.iter().map(|p| self.pos(p)).collect()
294        }
295
296        fn ring(&mut self, positions: &[Vec<i32>]) -> Result<LineString<i32>, &'static str> {
297            let ring = self.line(positions)?;
298            if self.has_z == Some(true) && stored_ring_len(&ring) < ring.0.len() {
299                self.z.pop();
300            }
301            Ok(ring)
302        }
303
304        fn poly(&mut self, rings: &[Vec<Vec<i32>>]) -> Result<Polygon<i32>, &'static str> {
305            let Some((ext, interiors)) = rings.split_first() else {
306                return Ok(Polygon::new(LineString(vec![]), vec![]));
307            };
308            let ext = self.ring(ext)?;
309            let interiors = interiors
310                .iter()
311                .map(|ring| self.ring(ring))
312                .collect::<Result<_, _>>()?;
313            Ok(Polygon::new(ext, interiors))
314        }
315
316        fn finish(self) -> Option<Vec<i32>> {
317            self.has_z.unwrap_or(false).then_some(self.z)
318        }
319    }
320
321    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<GeometryWithZ, D::Error> {
322        fn parse<T: serde::de::DeserializeOwned, E: serde::de::Error>(v: Value) -> Result<T, E> {
323            serde_json::from_value(v).map_err(E::custom)
324        }
325
326        #[derive(Deserialize)]
327        struct Wire {
328            #[serde(rename = "type")]
329            ty: String,
330            coordinates: Value,
331        }
332
333        let Wire { ty, coordinates: c } = Wire::deserialize(d)?;
334        let mut r = Reader::default();
335        let geometry = match ty.as_str() {
336            "Point" => r
337                .pos(&parse::<Vec<i32>, _>(c)?)
338                .map(|c| Geometry::Point(Point(c))),
339            "LineString" => r.line(&parse::<Vec<_>, _>(c)?).map(Geometry::LineString),
340            "Polygon" => r.poly(&parse::<Vec<_>, _>(c)?).map(Geometry::Polygon),
341            "MultiPoint" => parse::<Vec<Vec<i32>>, _>(c)?
342                .into_iter()
343                .map(|p| r.pos(&p).map(Point))
344                .collect::<Result<_, _>>()
345                .map(|points| Geometry::MultiPoint(MultiPoint(points))),
346            "MultiLineString" => parse::<Vec<Vec<Vec<i32>>>, _>(c)?
347                .iter()
348                .map(|ls| r.line(ls))
349                .collect::<Result<_, _>>()
350                .map(|lines| Geometry::MultiLineString(MultiLineString(lines))),
351            "MultiPolygon" => parse::<Vec<Vec<Vec<Vec<i32>>>>, _>(c)?
352                .iter()
353                .map(|p| r.poly(p))
354                .collect::<Result<_, _>>()
355                .map(|polys| Geometry::MultiPolygon(MultiPolygon(polys))),
356            _ => {
357                return Err(D::Error::unknown_variant(
358                    &ty,
359                    &[
360                        "Point",
361                        "LineString",
362                        "Polygon",
363                        "MultiPoint",
364                        "MultiLineString",
365                        "MultiPolygon",
366                    ],
367                ));
368            }
369        }
370        .map_err(D::Error::custom)?;
371        Ok((geometry, r.finish()))
372    }
373}
374
375/// Convert f32 to `GeoJSON` value: finite as number, non-finite as string per issue #978.
376#[must_use]
377pub fn f32_to_json(f: f32) -> Value {
378    if f.is_nan() {
379        Value::String("f32::NAN".to_owned())
380    } else if f == f32::INFINITY {
381        Value::String("f32::INFINITY".to_owned())
382    } else if f == f32::NEG_INFINITY {
383        Value::String("f32::NEG_INFINITY".to_owned())
384    } else {
385        Number::from_f64(f64::from(f)).expect("finite f32").into()
386    }
387}
388
389/// Convert f64 to `GeoJSON` value: finite as number, non-finite as string per issue #978.
390#[must_use]
391pub fn f64_to_json(f: f64) -> Value {
392    if f.is_nan() {
393        Value::String("f64::NAN".to_owned())
394    } else if f == f64::INFINITY {
395        Value::String("f64::INFINITY".to_owned())
396    } else if f == f64::NEG_INFINITY {
397        Value::String("f64::NEG_INFINITY".to_owned())
398    } else {
399        Number::from_f64(f).expect("finite f64").into()
400    }
401}
402
403/// Every feature's m-values as `m:<name>` properties, in feature order.
404///
405/// A feature with no values for a column has no property for it, the way a null
406/// property is left out.
407#[cfg(feature = "unstable-v2")]
408fn m_value_properties(layer: &ParsedLayer02<'_>) -> MltResult<Vec<Vec<(String, Value)>>> {
409    let geometry = layer.layer().geometry_values();
410    let mut features = vec![Vec::new(); geometry.feature_count()];
411    for column in layer.m_values() {
412        let key = format!("m:{}", column.name());
413        for (index, span) in column.spans(geometry).enumerate() {
414            let values = column.values().row(column.name(), span?)?;
415            if let Some(value) = m_value_to_json(&values) {
416                features[index].push((key.clone(), value));
417            }
418        }
419    }
420    Ok(features)
421}
422
423/// The layer's z grid and every feature's z coordinates in vertex order.
424#[cfg(feature = "unstable-v2")]
425fn z_values(layer: &ParsedLayer02<'_>) -> MltResult<(Option<ZStep>, std::vec::IntoIter<Vec<i32>>)> {
426    let geometry = layer.layer().geometry_values();
427    let Some(step) = geometry.z_step() else {
428        return Ok((None, Vec::new().into_iter()));
429    };
430    let z = (0..geometry.feature_count())
431        .map(|index| geometry.z(index))
432        .collect::<MltResult<Vec<_>>>()?;
433    Ok((Some(step), z.into_iter()))
434}
435
436/// Every feature's nested columns as JSON, one entry per column under its own name.
437#[cfg(feature = "unstable-v2")]
438fn nested_properties(layer: &ParsedLayer02<'_>) -> MltResult<Vec<Vec<(String, Value)>>> {
439    let mut features = vec![Vec::new(); layer.layer().geometry_values().feature_count()];
440    for column in layer.nested() {
441        for (index, feature) in features.iter_mut().enumerate() {
442            feature.push((
443                column.name().to_string(),
444                nested_to_json(&column.row(index)?),
445            ));
446        }
447    }
448    Ok(features)
449}
450
451/// One nested value as the JSON it stands for: an object, an array, a scalar or null.
452#[cfg(feature = "unstable-v2")]
453fn nested_to_json(value: &NestedValue) -> Value {
454    match value {
455        NestedValue::Leaf(leaf) => prop_to_json(leaf),
456        NestedValue::List(None) | NestedValue::Map(None) => Value::Null,
457        NestedValue::List(Some(items)) => Value::Array(items.iter().map(nested_to_json).collect()),
458        NestedValue::Map(Some(entries)) => Value::Object(
459            entries
460                .iter()
461                .map(|(key, value)| (key.clone(), nested_to_json(value)))
462                .collect(),
463        ),
464    }
465}
466
467/// One scalar as JSON, a null value included.
468#[cfg(feature = "unstable-v2")]
469fn prop_to_json(value: &crate::PropValue) -> Value {
470    use crate::PropValue as P;
471    match value {
472        P::Bool(v) => v.map_or(Value::Null, Value::Bool),
473        P::I8(v) => v.map_or(Value::Null, Value::from),
474        P::U8(v) => v.map_or(Value::Null, Value::from),
475        P::I32(v) => v.map_or(Value::Null, Value::from),
476        P::U32(v) => v.map_or(Value::Null, Value::from),
477        P::I64(v) => v.map_or(Value::Null, Value::from),
478        P::U64(v) => v.map_or(Value::Null, Value::from),
479        P::F32(v) => v.map_or(Value::Null, f32_to_json),
480        P::F64(v) => v.map_or(Value::Null, f64_to_json),
481        P::Str(v) => v.clone().map_or(Value::Null, Value::String),
482    }
483}
484
485/// One feature's m-values as a JSON array, or [`None`] when it carries none.
486#[cfg(feature = "unstable-v2")]
487fn m_value_to_json(values: &MValue) -> Option<Value> {
488    /// A present run, mapped value by value.
489    macro_rules! array {
490        ($values:expr, $to_json:expr) => {
491            Value::Array($values.iter().copied().map($to_json).collect())
492        };
493    }
494    Some(match values {
495        MValue::Bool(v) => array!(v.as_ref()?, Value::Bool),
496        MValue::I8(v) => array!(v.as_ref()?, Value::from),
497        MValue::U8(v) => array!(v.as_ref()?, Value::from),
498        MValue::I32(v) => array!(v.as_ref()?, Value::from),
499        MValue::U32(v) => array!(v.as_ref()?, Value::from),
500        MValue::I64(v) => array!(v.as_ref()?, Value::from),
501        MValue::U64(v) => array!(v.as_ref()?, Value::from),
502        MValue::F32(v) => array!(v.as_ref()?, f32_to_json),
503        MValue::F64(v) => array!(v.as_ref()?, f64_to_json),
504        MValue::Str(v) => Value::Array(
505            v.as_ref()?
506                .iter()
507                .map(|s| Value::String(s.clone()))
508                .collect(),
509        ),
510    })
511}
512
513impl From<PropValueRef<'_>> for Value {
514    fn from(v: PropValueRef<'_>) -> Self {
515        match v {
516            PropValueRef::Bool(v) => Self::Bool(v),
517            PropValueRef::I8(v) => Self::from(v),
518            PropValueRef::U8(v) => Self::from(v),
519            PropValueRef::I32(v) => Self::from(v),
520            PropValueRef::U32(v) => Self::from(v),
521            PropValueRef::I64(v) => Self::from(v),
522            PropValueRef::U64(v) => Self::from(v),
523            PropValueRef::F32(v) => f32_to_json(v),
524            PropValueRef::F64(v) => f64_to_json(v),
525            PropValueRef::Str(s) => Self::String(s.to_string()),
526        }
527    }
528}
529
530/// Replace tiny float values (e.g. `1e-40`) with `0.0` to handle codec precision issues.
531fn normalize_tiny_floats(value: Value) -> Value {
532    match value {
533        Value::Number(ref n) => {
534            let eps = f64::from(f32::EPSILON);
535            if let Some(f) = n.as_f64()
536                && f.is_finite()
537                && f.abs() < eps
538            {
539                Value::from(0.0)
540            } else {
541                value
542            }
543        }
544        Value::Array(arr) => Value::Array(arr.into_iter().map(normalize_tiny_floats).collect()),
545        Value::Object(obj) => Value::Object(
546            obj.into_iter()
547                .map(|(k, v)| (k, normalize_tiny_floats(v)))
548                .collect(),
549        ),
550        Value::Null | Value::Bool(_) | Value::String(_) => value,
551    }
552}
553
554/// Compare two JSON values for equality. Numbers are compared with float tolerance so that
555/// f32 round-trip (e.g. 3.14 vs 3.140000104904175) and Java minimal decimal (e.g. 3.4028235e+38)
556/// match the Rust decoder output.
557fn json_values_equal(a: &Value, b: &Value) -> bool {
558    match (a, b) {
559        (Value::Number(na), Value::Number(nb)) if na.is_f64() && nb.is_f64() => {
560            let na = na.as_f64().expect("f64");
561            let nb = nb.as_f64().expect("f64");
562            assert!(
563                !na.is_nan() && !nb.is_nan(),
564                "unexpected non-finite numbers"
565            );
566            let abs_diff = (na - nb).abs();
567            let max_abs = na.abs().max(nb.abs()).max(1.0);
568            abs_diff <= f64::from(f32::EPSILON) * max_abs * 2.0
569        }
570        (Value::Array(aa), Value::Array(ab)) => {
571            aa.len() == ab.len()
572                && aa
573                    .iter()
574                    .zip(ab.iter())
575                    .all(|(x, y)| json_values_equal(x, y))
576        }
577        (Value::Object(ao), Value::Object(bo)) => {
578            ao.len() == bo.len()
579                && ao
580                    .iter()
581                    .all(|(k, v)| bo.get(k).is_some_and(|w| json_values_equal(v, w)))
582        }
583        _ => a == b,
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use geo_types::{
590        Coord, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, MultiPolygon,
591        Point, Polygon, Rect, Triangle,
592    };
593    use insta::assert_snapshot;
594    use rstest::rstest;
595
596    use super::*;
597
598    fn ring(pts: &[(i32, i32)]) -> LineString<i32> {
599        LineString::from(pts.iter().map(|&(x, y)| Coord { x, y }).collect::<Vec<_>>())
600    }
601
602    fn square_with_hole() -> Polygon<i32> {
603        Polygon::new(
604            ring(&[(0, 0), (8, 0), (8, 8), (0, 8), (0, 0)]),
605            vec![ring(&[(2, 2), (4, 2), (4, 4), (2, 2)])],
606        )
607    }
608
609    fn feature(geometry: Geometry<i32>) -> Feature {
610        Feature {
611            geometry,
612            z: None,
613            id: None,
614            properties: BTreeMap::new(),
615            ty: "Feature".into(),
616        }
617    }
618
619    fn supported_geometries() -> Vec<Geometry<i32>> {
620        vec![
621            Geometry::Point(Point::new(1, -2)),
622            Geometry::LineString(ring(&[(0, 0), (1, 1)])),
623            Geometry::LineString(LineString(vec![])),
624            Geometry::Polygon(square_with_hole()),
625            Geometry::Polygon(Polygon::new(LineString(vec![]), vec![])),
626            Geometry::MultiPoint(MultiPoint(vec![Point::new(1, 2), Point::new(3, 4)])),
627            Geometry::MultiPoint(MultiPoint(vec![])),
628            Geometry::MultiLineString(MultiLineString(vec![ring(&[(0, 0), (1, 1)])])),
629            Geometry::MultiPolygon(MultiPolygon(vec![square_with_hole()])),
630        ]
631    }
632
633    #[test]
634    fn supported_geometries_round_trip_through_geojson() {
635        let mut rendered = Vec::new();
636        for geometry in supported_geometries() {
637            let json = serde_json::to_string(&feature(geometry.clone())).expect("serialize");
638            let back: Feature = serde_json::from_str(&json).expect("deserialize");
639            assert_eq!(back.geometry, geometry);
640            rendered.push(json);
641        }
642
643        assert_snapshot!(rendered.join("\n"), @r#"
644        {"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[1,-2]}}
645        {"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[0,0],[1,1]]}}
646        {"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[]}}
647        {"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[0,0],[8,0],[8,8],[0,8],[0,0]],[[2,2],[4,2],[4,4],[2,2]]]}}
648        {"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[]]}}
649        {"type":"Feature","properties":{},"geometry":{"type":"MultiPoint","coordinates":[[1,2],[3,4]]}}
650        {"type":"Feature","properties":{},"geometry":{"type":"MultiPoint","coordinates":[]}}
651        {"type":"Feature","properties":{},"geometry":{"type":"MultiLineString","coordinates":[[[0,0],[1,1]]]}}
652        {"type":"Feature","properties":{},"geometry":{"type":"MultiPolygon","coordinates":[[[[0,0],[8,0],[8,8],[0,8],[0,0]],[[2,2],[4,2],[4,4],[2,2]]]]}}
653        "#);
654    }
655
656    #[rstest]
657    #[case::line(Geometry::Line(Line::new(Coord { x: 0, y: 0 }, Coord { x: 1, y: 1 })))]
658    #[case::rect(Geometry::Rect(Rect::new(Coord { x: 0, y: 0 }, Coord { x: 1, y: 1 })))]
659    #[case::triangle(Geometry::Triangle(Triangle::new(
660        Coord { x: 0, y: 0 },
661        Coord { x: 1, y: 0 },
662        Coord { x: 0, y: 1 },
663    )))]
664    #[case::collection(Geometry::GeometryCollection(GeometryCollection(vec![])))]
665    fn geometries_outside_geojson_fail_to_serialize(#[case] geometry: Geometry<i32>) {
666        let err = serde_json::to_string(&feature(geometry)).expect_err("must not serialize");
667        assert_eq!(err.to_string(), "unsupported geometry variant");
668    }
669
670    #[rstest]
671    #[case::point(Geometry::Point(Point::new(1, -2)), vec![7])]
672    #[case::polygon_with_hole(Geometry::Polygon(square_with_hole()), (1..=7).collect())]
673    #[case::multi_point(
674        Geometry::MultiPoint(MultiPoint(vec![Point::new(1, 2), Point::new(3, 4)])),
675        vec![-1, 1],
676    )]
677    fn z_round_trips_as_the_third_coordinate(#[case] geometry: Geometry<i32>, #[case] z: Vec<i32>) {
678        let with_z = Feature {
679            z: Some(z),
680            ..feature(geometry)
681        };
682        let json = serde_json::to_string(&with_z).expect("serialize");
683        let back: Feature = serde_json::from_str(&json).expect("deserialize");
684        assert_eq!(back, with_z);
685    }
686
687    #[test]
688    fn a_closing_position_repeats_the_first_z() {
689        let with_z = Feature {
690            z: Some((1..=7).collect()),
691            ..feature(Geometry::Polygon(square_with_hole()))
692        };
693        insta::assert_snapshot!(
694            serde_json::to_string(&with_z).expect("serialize"),
695            @r#"{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[0,0,1],[8,0,2],[8,8,3],[0,8,4],[0,0,1]],[[2,2,5],[4,2,6],[4,4,7],[2,2,5]]]}}"#
696        );
697    }
698
699    #[rstest]
700    #[case::fewer_z(vec![1], "fewer z than vertices")]
701    #[case::more_z(vec![1, 2, 3], "more z than vertices")]
702    fn z_must_match_the_vertex_count(#[case] z: Vec<i32>, #[case] expected: &str) {
703        let with_z = Feature {
704            z: Some(z),
705            ..feature(Geometry::LineString(ring(&[(0, 0), (1, 1)])))
706        };
707        let err = serde_json::to_string(&with_z).expect_err("must not serialize");
708        assert_eq!(err.to_string(), expected);
709    }
710
711    #[rstest]
712    #[case::mixed_z(
713        "[[0,0,1],[1,1]]",
714        "either every position has z or none does at line 1 column 81"
715    )]
716    #[case::four_coordinates(
717        "[[0,0,1,2]]",
718        "a position needs two or three coordinates at line 1 column 77"
719    )]
720    #[case::one_coordinate(
721        "[[0]]",
722        "a position needs two or three coordinates at line 1 column 71"
723    )]
724    fn malformed_positions_fail_to_deserialize(#[case] coordinates: &str, #[case] expected: &str) {
725        let err = serde_json::from_str::<Feature>(&format!(
726            r#"{{"type":"Feature","geometry":{{"type":"LineString","coordinates":{coordinates}}}}}"#
727        ))
728        .expect_err("must not deserialize");
729        assert_eq!(err.to_string(), expected);
730    }
731
732    #[test]
733    fn unknown_geometry_type_fails_to_deserialize() {
734        let err = serde_json::from_str::<Feature>(
735            r#"{"type":"Feature","properties":{},"geometry":{"type":"Circle","coordinates":[0,0]}}"#,
736        )
737        .expect_err("must not deserialize");
738        assert_snapshot!(
739            err,
740            @"unknown variant `Circle`, expected one of `Point`, `LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, `MultiPolygon` at line 1 column 83"
741        );
742    }
743
744    #[test]
745    fn a_feature_collection_round_trips_through_its_text_form() {
746        let collection = FeatureCollection {
747            ty: "FeatureCollection".into(),
748            features: vec![Feature {
749                geometry: Geometry::Point(Point::new(7, 9)),
750                z: None,
751                id: Some(42),
752                properties: BTreeMap::from([
753                    ("name".into(), Value::String("ß".into())),
754                    ("rank".into(), Value::from(3)),
755                ]),
756                ty: "Feature".into(),
757            }],
758        };
759
760        let text = serde_json::to_string(&collection).expect("serialize");
761        assert_snapshot!(
762            text,
763            @r#"{"type":"FeatureCollection","features":[{"type":"Feature","id":42,"properties":{"name":"ß","rank":3},"geometry":{"type":"Point","coordinates":[7,9]}}]}"#
764        );
765        assert_eq!(
766            FeatureCollection::from_str(&text).expect("parse"),
767            collection
768        );
769    }
770
771    #[test]
772    fn floats_outside_the_json_number_range_become_names() {
773        let values = [
774            f32_to_json(1.5),
775            f32_to_json(f32::NAN),
776            f32_to_json(f32::INFINITY),
777            f32_to_json(f32::NEG_INFINITY),
778            f64_to_json(1.5),
779            f64_to_json(f64::NAN),
780            f64_to_json(f64::INFINITY),
781            f64_to_json(f64::NEG_INFINITY),
782        ];
783        assert_snapshot!(
784            Value::Array(values.to_vec()),
785            @r#"[1.5,"f32::NAN","f32::INFINITY","f32::NEG_INFINITY",1.5,"f64::NAN","f64::INFINITY","f64::NEG_INFINITY"]"#
786        );
787    }
788
789    #[test]
790    fn every_property_kind_becomes_json() {
791        let values = [
792            PropValueRef::Bool(true),
793            PropValueRef::I8(-8),
794            PropValueRef::U8(8),
795            PropValueRef::I32(i32::MIN),
796            PropValueRef::U32(u32::MAX),
797            PropValueRef::I64(i64::MIN),
798            PropValueRef::U64(u64::MAX),
799            PropValueRef::F32(0.5),
800            PropValueRef::F64(f64::NAN),
801            PropValueRef::Str("text"),
802        ];
803        let json = Value::Array(values.into_iter().map(Value::from).collect());
804        assert_snapshot!(
805            json,
806            @r#"[true,-8,8,-2147483648,4294967295,-9223372036854775808,18446744073709551615,0.5,"f64::NAN","text"]"#
807        );
808    }
809
810    fn collection_with(value: &str) -> FeatureCollection {
811        FeatureCollection::from_str(&format!(
812            r#"{{"type":"FeatureCollection","features":[{{"type":"Feature","properties":{{"a":{value}}},"geometry":{{"type":"Point","coordinates":[0,0]}}}}]}}"#
813        ))
814        .expect("parse")
815    }
816
817    #[rstest]
818    #[case::identical("3.14", "3.14", true)]
819    #[case::f32_round_trip("3.14", "3.140000104904175", true)]
820    #[case::denormal_against_zero("1e-41", "0.0", true)]
821    #[case::denormal_against_integer_zero("1e-41", "0", true)]
822    #[case::largest_f32("3.4028235e38", "3.4028234663852886e38", true)]
823    #[case::beyond_f32_tolerance("1.0", "1.001", false)]
824    #[case::different_integers("1", "2", false)]
825    #[case::nested_arrays("[1.0, [2.0]]", "[1.0, [2.0]]", true)]
826    #[case::shorter_array("[1.0, 2.0]", "[1.0]", false)]
827    #[case::nan_name(r#""f64::NAN""#, r#""f64::NAN""#, true)]
828    #[case::number_against_string("1.0", r#""1.0""#, false)]
829    fn equals_compares_with_float_tolerance(
830        #[case] left: &str,
831        #[case] right: &str,
832        #[case] expected: bool,
833    ) {
834        let (left, right) = (collection_with(left), collection_with(right));
835        assert_eq!(left.equals(&right).expect("compare"), expected);
836        assert_eq!(right.equals(&left).expect("compare"), expected);
837    }
838
839    #[test]
840    fn equals_is_false_for_a_missing_property() {
841        let with_extra = FeatureCollection::from_str(
842            r#"{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"a":1.0,"b":2.0},"geometry":{"type":"Point","coordinates":[0,0]}}]}"#,
843        )
844        .expect("parse");
845        assert!(!collection_with("1.0").equals(&with_extra).expect("compare"));
846        assert!(!with_extra.equals(&collection_with("1.0")).expect("compare"));
847    }
848}