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
14/// `GeoJSON` [`FeatureCollection`]
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct FeatureCollection {
17    #[serde(rename = "type")]
18    pub ty: String,
19    pub features: Vec<Feature>,
20}
21
22impl FeatureCollection {
23    /// Convert already-decoded layers to a `GeoJSON` [`FeatureCollection`], consuming them.
24    /// Make sure to call `decode_all` on Layer before calling this (won't compile otherwise)
25    pub fn from_layers<'a>(layers: impl IntoIterator<Item = ParsedLayer<'a>>) -> MltResult<Self> {
26        let mut features = Vec::new();
27        for layer in layers {
28            let Some(parsed) = layer.into_layer01() else {
29                continue;
30            };
31            let layer_name = parsed.name();
32            let extent = parsed.extent().get();
33            let mut feat_iter = parsed.iter_features();
34            while let Some(feat) = feat_iter.next() {
35                let feat = feat?;
36                let mut properties = BTreeMap::new();
37                for p in feat.iter_properties() {
38                    properties.insert(p.name().to_string(), p.value().into());
39                }
40                properties.insert("_layer".into(), Value::String(layer_name.to_string()));
41                properties.insert("_extent".into(), Value::Number(extent.into()));
42                features.push(Feature {
43                    geometry: feat.geometry().clone(),
44                    id: feat.id(),
45                    properties,
46                    ty: "Feature".into(),
47                });
48            }
49        }
50        Ok(Self {
51            features,
52            ty: "FeatureCollection".into(),
53        })
54    }
55
56    pub fn equals(&self, other: &Self) -> Result<bool, serde_json::Error> {
57        let self_val = normalize_tiny_floats(serde_json::to_value(self)?);
58        let other_val = normalize_tiny_floats(serde_json::to_value(other)?);
59        Ok(json_values_equal(&self_val, &other_val))
60    }
61}
62
63impl FromStr for FeatureCollection {
64    type Err = serde_json::Error;
65
66    fn from_str(s: &str) -> Result<Self, Self::Err> {
67        serde_json::from_str(s)
68    }
69}
70
71/// `GeoJSON` [`Feature`]
72#[derive(Debug, Clone, PartialEq, Deserialize)]
73pub struct Feature {
74    #[serde(with = "geom_serde")]
75    pub geometry: Geometry<i32>,
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub id: Option<u64>,
78    #[serde(default)]
79    pub properties: BTreeMap<String, Value>,
80    #[serde(rename = "type")]
81    pub ty: String,
82}
83
84struct Geom32Wire<'a>(&'a Geometry<i32>);
85impl Serialize for Geom32Wire<'_> {
86    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
87        geom_serde::serialize(self.0, s)
88    }
89}
90
91/// Serialize with the preferred order of the keys
92impl Serialize for Feature {
93    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
94        let len = 3 + usize::from(self.id.is_some());
95        let mut map = serializer.serialize_map(Some(len))?;
96        map.serialize_entry("type", &self.ty)?;
97        if let Some(id) = self.id {
98            map.serialize_entry("id", &id)?;
99        }
100        map.serialize_entry("properties", &self.properties)?;
101        map.serialize_entry("geometry", &Geom32Wire(&self.geometry))?;
102        map.end()
103    }
104}
105
106/// Serialize/deserialize [`Geometry<i32>`](geo_types::Geometry) in `GeoJSON` wire format:
107/// `{"type":"…","coordinates":…}` with `[x, y]` integer arrays.
108mod geom_serde {
109    use geo_types::{
110        Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
111    };
112    use serde::de::Error as _;
113    use serde::ser::{Error, SerializeMap as _};
114    use serde::{Deserialize, Deserializer, Serializer};
115    use serde_json::Value;
116
117    type Arr = [i32; 2];
118
119    fn ls_arr(ls: &LineString<i32>) -> Vec<Arr> {
120        ls.0.iter().copied().map(Into::into).collect()
121    }
122
123    fn poly_arr(poly: &Polygon<i32>) -> Vec<Vec<Arr>> {
124        std::iter::once(poly.exterior())
125            .chain(poly.interiors())
126            .map(ls_arr)
127            .collect()
128    }
129
130    fn arr_ls(v: Vec<Arr>) -> LineString<i32> {
131        LineString::from(v)
132    }
133
134    fn arr_poly(rings: Vec<Vec<Arr>>) -> Polygon<i32> {
135        let mut it = rings.into_iter();
136        let ext = it.next().map_or_else(|| LineString(vec![]), arr_ls);
137        Polygon::new(ext, it.map(arr_ls).collect())
138    }
139
140    pub fn serialize<S: Serializer>(g: &Geometry<i32>, s: S) -> Result<S::Ok, S::Error> {
141        let mut m = s.serialize_map(Some(2))?;
142        let (ty, coords): (&str, Value) = match g {
143            Geometry::Point(p) => ("Point", serde_json::to_value(Arr::from(*p)).unwrap()),
144            Geometry::LineString(ls) => ("LineString", serde_json::to_value(ls_arr(ls)).unwrap()),
145            Geometry::Polygon(poly) => ("Polygon", serde_json::to_value(poly_arr(poly)).unwrap()),
146            Geometry::MultiPoint(mp) => (
147                "MultiPoint",
148                serde_json::to_value(mp.0.iter().copied().map(Arr::from).collect::<Vec<_>>())
149                    .unwrap(),
150            ),
151            Geometry::MultiLineString(mls) => (
152                "MultiLineString",
153                serde_json::to_value(mls.iter().map(ls_arr).collect::<Vec<_>>()).unwrap(),
154            ),
155            Geometry::MultiPolygon(mpoly) => (
156                "MultiPolygon",
157                serde_json::to_value(mpoly.iter().map(poly_arr).collect::<Vec<_>>()).unwrap(),
158            ),
159            Geometry::Line(_)
160            | Geometry::Rect(_)
161            | Geometry::Triangle(_)
162            | Geometry::GeometryCollection(_) => {
163                return Err(Error::custom("unsupported geometry variant"));
164            }
165        };
166        m.serialize_entry("type", ty)?;
167        m.serialize_entry("coordinates", &coords)?;
168        m.end()
169    }
170
171    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Geometry<i32>, D::Error> {
172        fn parse<T: serde::de::DeserializeOwned, E: serde::de::Error>(v: Value) -> Result<T, E> {
173            serde_json::from_value(v).map_err(E::custom)
174        }
175
176        #[derive(Deserialize)]
177        struct Wire {
178            #[serde(rename = "type")]
179            ty: String,
180            coordinates: Value,
181        }
182
183        let Wire { ty, coordinates: c } = Wire::deserialize(d)?;
184        Ok(match ty.as_str() {
185            "Point" => Geometry::Point(Point::from(parse::<Arr, _>(c)?)),
186            "LineString" => Geometry::LineString(arr_ls(parse(c)?)),
187            "Polygon" => Geometry::Polygon(arr_poly(parse(c)?)),
188            "MultiPoint" => {
189                let v: Vec<Arr> = parse(c)?;
190                Geometry::MultiPoint(MultiPoint(v.into_iter().map(Point::from).collect()))
191            }
192            "MultiLineString" => {
193                let v: Vec<Vec<Arr>> = parse(c)?;
194                Geometry::MultiLineString(MultiLineString(v.into_iter().map(arr_ls).collect()))
195            }
196            "MultiPolygon" => {
197                let v: Vec<Vec<Vec<Arr>>> = parse(c)?;
198                Geometry::MultiPolygon(MultiPolygon(v.into_iter().map(arr_poly).collect()))
199            }
200            _ => {
201                return Err(D::Error::unknown_variant(
202                    &ty,
203                    &[
204                        "Point",
205                        "LineString",
206                        "Polygon",
207                        "MultiPoint",
208                        "MultiLineString",
209                        "MultiPolygon",
210                    ],
211                ));
212            }
213        })
214    }
215}
216
217/// Convert f32 to `GeoJSON` value: finite as number, non-finite as string per issue #978.
218#[must_use]
219pub fn f32_to_json(f: f32) -> Value {
220    if f.is_nan() {
221        Value::String("f32::NAN".to_owned())
222    } else if f == f32::INFINITY {
223        Value::String("f32::INFINITY".to_owned())
224    } else if f == f32::NEG_INFINITY {
225        Value::String("f32::NEG_INFINITY".to_owned())
226    } else {
227        Number::from_f64(f64::from(f)).expect("finite f32").into()
228    }
229}
230
231/// Convert f64 to `GeoJSON` value: finite as number, non-finite as string per issue #978.
232#[must_use]
233pub fn f64_to_json(f: f64) -> Value {
234    if f.is_nan() {
235        Value::String("f64::NAN".to_owned())
236    } else if f == f64::INFINITY {
237        Value::String("f64::INFINITY".to_owned())
238    } else if f == f64::NEG_INFINITY {
239        Value::String("f64::NEG_INFINITY".to_owned())
240    } else {
241        Number::from_f64(f).expect("finite f64").into()
242    }
243}
244
245impl From<PropValueRef<'_>> for Value {
246    fn from(v: PropValueRef<'_>) -> Self {
247        match v {
248            PropValueRef::Bool(v) => Self::Bool(v),
249            PropValueRef::I8(v) => Self::from(v),
250            PropValueRef::U8(v) => Self::from(v),
251            PropValueRef::I32(v) => Self::from(v),
252            PropValueRef::U32(v) => Self::from(v),
253            PropValueRef::I64(v) => Self::from(v),
254            PropValueRef::U64(v) => Self::from(v),
255            PropValueRef::F32(v) => f32_to_json(v),
256            PropValueRef::F64(v) => f64_to_json(v),
257            PropValueRef::Str(s) => Self::String(s.to_string()),
258        }
259    }
260}
261
262/// Replace tiny float values (e.g. `1e-40`) with `0.0` to handle codec precision issues.
263fn normalize_tiny_floats(value: Value) -> Value {
264    match value {
265        Value::Number(ref n) => {
266            let eps = f64::from(f32::EPSILON);
267            if let Some(f) = n.as_f64()
268                && f.is_finite()
269                && f.abs() < eps
270            {
271                Value::from(0.0)
272            } else {
273                value
274            }
275        }
276        Value::Array(arr) => Value::Array(arr.into_iter().map(normalize_tiny_floats).collect()),
277        Value::Object(obj) => Value::Object(
278            obj.into_iter()
279                .map(|(k, v)| (k, normalize_tiny_floats(v)))
280                .collect(),
281        ),
282        Value::Null | Value::Bool(_) | Value::String(_) => value,
283    }
284}
285
286/// Compare two JSON values for equality. Numbers are compared with float tolerance so that
287/// f32 round-trip (e.g. 3.14 vs 3.140000104904175) and Java minimal decimal (e.g. 3.4028235e+38)
288/// match the Rust decoder output.
289fn json_values_equal(a: &Value, b: &Value) -> bool {
290    match (a, b) {
291        (Value::Number(na), Value::Number(nb)) if na.is_f64() && nb.is_f64() => {
292            let na = na.as_f64().expect("f64");
293            let nb = nb.as_f64().expect("f64");
294            assert!(
295                !na.is_nan() && !nb.is_nan(),
296                "unexpected non-finite numbers"
297            );
298            let abs_diff = (na - nb).abs();
299            let max_abs = na.abs().max(nb.abs()).max(1.0);
300            abs_diff <= f64::from(f32::EPSILON) * max_abs * 2.0
301        }
302        (Value::Array(aa), Value::Array(ab)) => {
303            aa.len() == ab.len()
304                && aa
305                    .iter()
306                    .zip(ab.iter())
307                    .all(|(x, y)| json_values_equal(x, y))
308        }
309        (Value::Object(ao), Value::Object(bo)) => {
310            ao.len() == bo.len()
311                && ao
312                    .iter()
313                    .all(|(k, v)| bo.get(k).is_some_and(|w| json_values_equal(v, w)))
314        }
315        _ => a == b,
316    }
317}