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