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            _ => return Err(Error::custom("unsupported geometry variant")),
160        };
161        m.serialize_entry("type", ty)?;
162        m.serialize_entry("coordinates", &coords)?;
163        m.end()
164    }
165
166    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Geometry<i32>, D::Error> {
167        fn parse<T: serde::de::DeserializeOwned, E: serde::de::Error>(v: Value) -> Result<T, E> {
168            serde_json::from_value(v).map_err(E::custom)
169        }
170
171        #[derive(Deserialize)]
172        struct Wire {
173            #[serde(rename = "type")]
174            ty: String,
175            coordinates: Value,
176        }
177
178        let Wire { ty, coordinates: c } = Wire::deserialize(d)?;
179        Ok(match ty.as_str() {
180            "Point" => Geometry::Point(Point::from(parse::<Arr, _>(c)?)),
181            "LineString" => Geometry::LineString(arr_ls(parse(c)?)),
182            "Polygon" => Geometry::Polygon(arr_poly(parse(c)?)),
183            "MultiPoint" => {
184                let v: Vec<Arr> = parse(c)?;
185                Geometry::MultiPoint(MultiPoint(v.into_iter().map(Point::from).collect()))
186            }
187            "MultiLineString" => {
188                let v: Vec<Vec<Arr>> = parse(c)?;
189                Geometry::MultiLineString(MultiLineString(v.into_iter().map(arr_ls).collect()))
190            }
191            "MultiPolygon" => {
192                let v: Vec<Vec<Vec<Arr>>> = parse(c)?;
193                Geometry::MultiPolygon(MultiPolygon(v.into_iter().map(arr_poly).collect()))
194            }
195            _ => {
196                return Err(D::Error::unknown_variant(
197                    &ty,
198                    &[
199                        "Point",
200                        "LineString",
201                        "Polygon",
202                        "MultiPoint",
203                        "MultiLineString",
204                        "MultiPolygon",
205                    ],
206                ));
207            }
208        })
209    }
210}
211
212/// Convert f32 to `GeoJSON` value: finite as number, non-finite as string per issue #978.
213#[must_use]
214pub fn f32_to_json(f: f32) -> Value {
215    if f.is_nan() {
216        Value::String("f32::NAN".to_owned())
217    } else if f == f32::INFINITY {
218        Value::String("f32::INFINITY".to_owned())
219    } else if f == f32::NEG_INFINITY {
220        Value::String("f32::NEG_INFINITY".to_owned())
221    } else {
222        Number::from_f64(f64::from(f)).expect("finite f32").into()
223    }
224}
225
226/// Convert f64 to `GeoJSON` value: finite as number, non-finite as string per issue #978.
227#[must_use]
228pub fn f64_to_json(f: f64) -> Value {
229    if f.is_nan() {
230        Value::String("f64::NAN".to_owned())
231    } else if f == f64::INFINITY {
232        Value::String("f64::INFINITY".to_owned())
233    } else if f == f64::NEG_INFINITY {
234        Value::String("f64::NEG_INFINITY".to_owned())
235    } else {
236        Number::from_f64(f).expect("finite f64").into()
237    }
238}
239
240impl From<PropValueRef<'_>> for Value {
241    fn from(v: PropValueRef<'_>) -> Self {
242        match v {
243            PropValueRef::Bool(v) => Self::Bool(v),
244            PropValueRef::I8(v) => Self::from(v),
245            PropValueRef::U8(v) => Self::from(v),
246            PropValueRef::I32(v) => Self::from(v),
247            PropValueRef::U32(v) => Self::from(v),
248            PropValueRef::I64(v) => Self::from(v),
249            PropValueRef::U64(v) => Self::from(v),
250            PropValueRef::F32(v) => f32_to_json(v),
251            PropValueRef::F64(v) => f64_to_json(v),
252            PropValueRef::Str(s) => Self::String(s.to_string()),
253        }
254    }
255}
256
257/// Replace tiny float values (e.g. `1e-40`) with `0.0` to handle codec precision issues.
258fn normalize_tiny_floats(value: Value) -> Value {
259    match value {
260        Value::Number(ref n) => {
261            let eps = f64::from(f32::EPSILON);
262            if let Some(f) = n.as_f64()
263                && f.is_finite()
264                && f.abs() < eps
265            {
266                Value::from(0.0)
267            } else {
268                value
269            }
270        }
271        Value::Array(arr) => Value::Array(arr.into_iter().map(normalize_tiny_floats).collect()),
272        Value::Object(obj) => Value::Object(
273            obj.into_iter()
274                .map(|(k, v)| (k, normalize_tiny_floats(v)))
275                .collect(),
276        ),
277        v => v,
278    }
279}
280
281/// Compare two JSON values for equality. Numbers are compared with float tolerance so that
282/// f32 round-trip (e.g. 3.14 vs 3.140000104904175) and Java minimal decimal (e.g. 3.4028235e+38)
283/// match the Rust decoder output.
284fn json_values_equal(a: &Value, b: &Value) -> bool {
285    match (a, b) {
286        (Value::Number(na), Value::Number(nb)) if na.is_f64() && nb.is_f64() => {
287            let na = na.as_f64().expect("f64");
288            let nb = nb.as_f64().expect("f64");
289            assert!(
290                !na.is_nan() && !nb.is_nan(),
291                "unexpected non-finite numbers"
292            );
293            let abs_diff = (na - nb).abs();
294            let max_abs = na.abs().max(nb.abs()).max(1.0);
295            abs_diff <= f64::from(f32::EPSILON) * max_abs * 2.0
296        }
297        (Value::Array(aa), Value::Array(ab)) => {
298            aa.len() == ab.len()
299                && aa
300                    .iter()
301                    .zip(ab.iter())
302                    .all(|(x, y)| json_values_equal(x, y))
303        }
304        (Value::Object(ao), Value::Object(bo)) => {
305            ao.len() == bo.len()
306                && ao
307                    .iter()
308                    .all(|(k, v)| bo.get(k).is_some_and(|w| json_values_equal(v, w)))
309        }
310        _ => a == b,
311    }
312}