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
19mod schema;
20pub use schema::PropertySchema;
21
22#[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 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 #[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#[derive(Debug, Clone, PartialEq, Deserialize)]
114#[serde(from = "FeatureWire")]
115pub struct Feature {
116 pub geometry: Geometry<i32>,
117 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
156impl 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
171mod 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 pub type GeometryWithZ = (Geometry<i32>, Option<Vec<i32>>);
186
187 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 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 #[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#[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#[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#[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#[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#[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#[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#[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#[cfg(feature = "unstable-v2")]
490fn m_value_to_json(values: &MValue) -> Option<Value> {
491 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
533fn 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
557fn 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}