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