1use 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#[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 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 #[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#[derive(Debug, Clone, PartialEq, Deserialize)]
111#[serde(from = "FeatureWire")]
112pub struct Feature {
113 pub geometry: Geometry<i32>,
114 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
153impl 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
168mod 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 pub type GeometryWithZ = (Geometry<i32>, Option<Vec<i32>>);
183
184 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 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 #[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#[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#[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#[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#[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#[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#[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#[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#[cfg(feature = "unstable-v2")]
487fn m_value_to_json(values: &MValue) -> Option<Value> {
488 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
530fn 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
554fn 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}