diff --git a/Cargo.toml b/Cargo.toml index eaa8c16..9f8b575 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,12 @@ serde_json = "~1.0" geo-types = { version = "0.7", features = ["serde"], optional = true } thiserror = "1.0.20" log = "0.4.17" +tinyvec = { version = "1.6.0", features = ["serde", "alloc"] } [dev-dependencies] num-traits = "0.2" criterion = "0.4.0" +pretty_env_logger = "0.4.0" [[bench]] name = "parse" diff --git a/README.md b/README.md index 0ba4ce4..31ec18b 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ let geojson = geojson_str.parse::().unwrap(); use geojson::{Feature, GeoJson, Geometry, Value, JsonObject, JsonValue}; let geometry = Geometry::new( - Value::Point(vec![-120.66029,35.2812]) + Value::Point(Position::from([-120.66029,35.2812])) ); let mut properties = JsonObject::new(); diff --git a/benches/parse.rs b/benches/parse.rs index 3047898..92e9534 100644 --- a/benches/parse.rs +++ b/benches/parse.rs @@ -9,12 +9,10 @@ fn parse_feature_collection_benchmark(c: &mut Criterion) { let geojson_str = include_str!("../tests/fixtures/countries.geojson"); c.bench_function("parse (countries.geojson)", |b| { - b.iter(|| match geojson_str.parse::() { - Ok(GeoJson::FeatureCollection(fc)) => { - assert_eq!(fc.features.len(), 180); - black_box(fc) - } - _ => panic!("unexpected result"), + b.iter(|| { + let fc = geojson_str.parse::().unwrap(); + assert_eq!(fc.features.len(), 180); + black_box(fc); }) }); @@ -32,52 +30,52 @@ fn parse_feature_collection_benchmark(c: &mut Criterion) { }); }); - c.bench_function("FeatureReader::deserialize (countries.geojson)", |b| { - b.iter(|| { - #[allow(unused)] - #[derive(serde::Deserialize)] - struct Country { - geometry: geojson::Geometry, - name: String, - } - let feature_reader = - geojson::FeatureReader::from_reader(BufReader::new(geojson_str.as_bytes())); - - let mut count = 0; - for feature in feature_reader.deserialize::().unwrap() { - let feature = feature.unwrap(); - black_box(feature); - count += 1; - } - assert_eq!(count, 180); - }); - }); - - #[cfg(feature = "geo-types")] - c.bench_function( - "FeatureReader::deserialize to geo-types (countries.geojson)", - |b| { - b.iter(|| { - #[allow(unused)] - #[derive(serde::Deserialize)] - struct Country { - #[serde(deserialize_with = "deserialize_geometry")] - geometry: geo_types::Geometry, - name: String, - } - let feature_reader = - geojson::FeatureReader::from_reader(BufReader::new(geojson_str.as_bytes())); + // c.bench_function("FeatureReader::deserialize (countries.geojson)", |b| { + // b.iter(|| { + // #[allow(unused)] + // #[derive(serde::Deserialize)] + // struct Country { + // geometry: geojson::Geometry, + // name: String, + // } + // let feature_reader = + // geojson::FeatureReader::from_reader(BufReader::new(geojson_str.as_bytes())); + // + // let mut count = 0; + // for feature in feature_reader.deserialize::().unwrap() { + // let feature = feature.unwrap(); + // black_box(feature); + // count += 1; + // } + // assert_eq!(count, 180); + // }); + // }); - let mut count = 0; - for feature in feature_reader.deserialize::().unwrap() { - let feature = feature.unwrap(); - black_box(feature); - count += 1; - } - assert_eq!(count, 180); - }); - }, - ); + // #[cfg(feature = "geo-types")] + // c.bench_function( + // "FeatureReader::deserialize to geo-types (countries.geojson)", + // |b| { + // b.iter(|| { + // #[allow(unused)] + // #[derive(serde::Deserialize)] + // struct Country { + // #[serde(deserialize_with = "deserialize_geometry")] + // geometry: geo_types::Geometry, + // name: String, + // } + // let feature_reader = + // geojson::FeatureReader::from_reader(BufReader::new(geojson_str.as_bytes())); + // + // let mut count = 0; + // for feature in feature_reader.deserialize::().unwrap() { + // let feature = feature.unwrap(); + // black_box(feature); + // count += 1; + // } + // assert_eq!(count, 180); + // }); + // }, + // ); } fn parse_geometry_collection_benchmark(c: &mut Criterion) { diff --git a/benches/serialize.rs b/benches/serialize.rs index 1bb726b..4ecda6d 100644 --- a/benches/serialize.rs +++ b/benches/serialize.rs @@ -18,56 +18,56 @@ fn serialize_feature_collection_benchmark(c: &mut Criterion) { }, ); - c.bench_function("serialize custom struct (countries.geojson)", |b| { - #[derive(serde::Serialize, serde::Deserialize)] - struct Country { - geometry: geojson::Geometry, - name: String, - } - let features = - geojson::de::deserialize_feature_collection_str_to_vec::(geojson_str).unwrap(); - assert_eq!(features.len(), 180); + // c.bench_function("serialize custom struct (countries.geojson)", |b| { + // #[derive(serde::Serialize, serde::Deserialize)] + // struct Country { + // geometry: geojson::Geometry, + // name: String, + // } + // let features = + // geojson::de::deserialize_feature_collection_str_to_vec::(geojson_str).unwrap(); + // assert_eq!(features.len(), 180); + // + // b.iter(|| { + // let geojson_string = geojson::ser::to_feature_collection_string(&features).unwrap(); + // // Sanity check that we serialized a long string of some kind. + // // + // // Note this is slightly shorter than the GeoJson round-trip above because we drop + // // some fields, like foreign members + // assert_eq!(geojson_string.len(), 254908); + // black_box(geojson_string); + // }); + // }); - b.iter(|| { - let geojson_string = geojson::ser::to_feature_collection_string(&features).unwrap(); - // Sanity check that we serialized a long string of some kind. - // - // Note this is slightly shorter than the GeoJson round-trip above because we drop - // some fields, like foreign members - assert_eq!(geojson_string.len(), 254908); - black_box(geojson_string); - }); - }); - - #[cfg(feature = "geo-types")] - c.bench_function( - "serialize custom struct to geo-types (countries.geojson)", - |b| { - #[derive(serde::Serialize, serde::Deserialize)] - struct Country { - #[serde( - serialize_with = "serialize_geometry", - deserialize_with = "deserialize_geometry" - )] - geometry: geo_types::Geometry, - name: String, - } - let features = - geojson::de::deserialize_feature_collection_str_to_vec::(geojson_str) - .unwrap(); - assert_eq!(features.len(), 180); - - b.iter(|| { - let geojson_string = geojson::ser::to_feature_collection_string(&features).unwrap(); - // Sanity check that we serialized a long string of some kind. - // - // Note this is slightly shorter than the GeoJson round-trip above because we drop - // some fields, like foreign members - assert_eq!(geojson_string.len(), 254908); - black_box(geojson_string); - }); - }, - ); + // #[cfg(feature = "geo-types")] + // c.bench_function( + // "serialize custom struct to geo-types (countries.geojson)", + // |b| { + // #[derive(serde::Serialize, serde::Deserialize)] + // struct Country { + // #[serde( + // serialize_with = "serialize_geometry", + // deserialize_with = "deserialize_geometry" + // )] + // geometry: geo_types::Geometry, + // name: String, + // } + // let features = + // geojson::de::deserialize_feature_collection_str_to_vec::(geojson_str) + // .unwrap(); + // assert_eq!(features.len(), 180); + // + // b.iter(|| { + // let geojson_string = geojson::ser::to_feature_collection_string(&features).unwrap(); + // // Sanity check that we serialized a long string of some kind. + // // + // // Note this is slightly shorter than the GeoJson round-trip above because we drop + // // some fields, like foreign members + // assert_eq!(geojson_string.len(), 254908); + // black_box(geojson_string); + // }); + // }, + // ); } criterion_group!(benches, serialize_feature_collection_benchmark); diff --git a/src/conversion/from_geo_types.rs b/src/conversion/from_geo_types.rs index ec05d87..b7a026e 100644 --- a/src/conversion/from_geo_types.rs +++ b/src/conversion/from_geo_types.rs @@ -185,8 +185,7 @@ where { let x: f64 = point.x().to_f64().unwrap(); let y: f64 = point.y().to_f64().unwrap(); - - vec![x, y] + crate::Position::from([x, y]) } fn create_line_string_type(line_string: &geo_types::LineString) -> LineStringType @@ -194,7 +193,7 @@ where T: CoordFloat, { line_string - .points_iter() + .points() .map(|point| create_point_type(&point)) .collect() } @@ -242,7 +241,7 @@ where { let mut coords = vec![polygon .exterior() - .points_iter() + .points() .map(|point| create_point_type(&point)) .collect()]; @@ -271,8 +270,8 @@ where mod tests { use crate::{GeoJson, Geometry, Value}; use geo_types::{ - Coordinate, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, - MultiPolygon, Point, Polygon, Rect, Triangle, + Coord, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, MultiPolygon, + Point, Polygon, Rect, Triangle, }; #[test] @@ -356,9 +355,9 @@ mod tests { #[test] fn geo_triangle_conversion_test() { - let c1 = Coordinate { x: 0., y: 0. }; - let c2 = Coordinate { x: 10., y: 20. }; - let c3 = Coordinate { x: 20., y: -10. }; + let c1 = Coord { x: 0., y: 0. }; + let c2 = Coord { x: 10., y: 20. }; + let c3 = Coord { x: 20., y: -10. }; let triangle = Triangle(c1, c2, c3); @@ -381,8 +380,8 @@ mod tests { #[test] fn geo_rect_conversion_test() { - let c1 = Coordinate { x: 0., y: 0. }; - let c2 = Coordinate { x: 10., y: 20. }; + let c1 = Coord { x: 0., y: 0. }; + let c2 = Coord { x: 10., y: 20. }; let rect = Rect::new(c1, c2); diff --git a/src/conversion/to_geo_types.rs b/src/conversion/to_geo_types.rs index 996a730..e21930a 100644 --- a/src/conversion/to_geo_types.rs +++ b/src/conversion/to_geo_types.rs @@ -273,11 +273,11 @@ where } } -fn create_geo_coordinate(point_type: &PointType) -> geo_types::Coordinate +fn create_geo_coordinate(point_type: &PointType) -> geo_types::Coord where T: CoordFloat, { - geo_types::Coordinate { + geo_types::Coord { x: T::from(point_type[0]).unwrap(), y: T::from(point_type[1]).unwrap(), } @@ -361,15 +361,15 @@ fn mismatch_geom_err(expected_type: &'static str, found: &geometry::Value) -> Er #[cfg(test)] mod tests { - use crate::{Geometry, Value}; + use crate::{Geometry, Position, Value}; use serde_json::json; use std::convert::TryInto; #[test] fn geojson_point_conversion_test() { - let coords = vec![100.0, 0.2]; - let geojson_point = Value::Point(coords.clone()); + let coords = [100.0, 0.2]; + let geojson_point = Value::Point(Position(coords.clone().into())); let geo_point: geo_types::Point = geojson_point.try_into().unwrap(); assert_almost_eq!(geo_point.x(), coords[0], 1e-6); @@ -378,8 +378,8 @@ mod tests { #[test] fn geojson_multi_point_conversion_test() { - let coord1 = vec![100.0, 0.2]; - let coord2 = vec![101.0, 1.0]; + let coord1 = Position([100.0, 0.2].into()); + let coord2 = Position([101.0, 1.0].into()); let geojson_multi_point = Value::MultiPoint(vec![coord1.clone(), coord2.clone()]); let geo_multi_point: geo_types::MultiPoint = geojson_multi_point.try_into().unwrap(); @@ -391,8 +391,8 @@ mod tests { #[test] fn geojson_line_string_conversion_test() { - let coord1 = vec![100.0, 0.2]; - let coord2 = vec![101.0, 1.0]; + let coord1 = Position::from([100.0, 0.2]); + let coord2 = Position::from([101.0, 1.0]); let geojson_line_string = Value::LineString(vec![coord1.clone(), coord2.clone()]); let geo_line_string: geo_types::LineString = geojson_line_string.try_into().unwrap(); @@ -404,9 +404,9 @@ mod tests { #[test] fn geojson_multi_line_string_conversion_test() { - let coord1 = vec![100.0, 0.2]; - let coord2 = vec![101.0, 1.0]; - let coord3 = vec![102.0, 0.8]; + let coord1 = Position::from([100.0, 0.2]); + let coord2 = Position::from([101.0, 1.0]); + let coord3 = Position::from([102.0, 0.8]); let geojson_multi_line_string = Value::MultiLineString(vec![ vec![coord1.clone(), coord2.clone()], vec![coord2.clone(), coord3.clone()], @@ -429,12 +429,12 @@ mod tests { #[test] fn geojson_polygon_conversion_test() { - let coord1 = vec![100.0, 0.0]; - let coord2 = vec![101.0, 1.0]; - let coord3 = vec![101.0, 1.0]; - let coord4 = vec![104.0, 0.2]; - let coord5 = vec![100.9, 0.2]; - let coord6 = vec![100.9, 0.7]; + let coord1 = Position::from([100.0, 0.0]); + let coord2 = Position::from([101.0, 1.0]); + let coord3 = Position::from([101.0, 1.0]); + let coord4 = Position::from([104.0, 0.2]); + let coord5 = Position::from([100.9, 0.2]); + let coord6 = Position::from([100.9, 0.7]); let geojson_multi_line_string_type1 = vec![ vec![ @@ -484,9 +484,9 @@ mod tests { #[test] fn geojson_polygon_without_interiors_conversion_test() { - let coord1 = vec![100.0, 0.0]; - let coord2 = vec![101.0, 1.0]; - let coord3 = vec![101.0, 1.0]; + let coord1 = Position::from([100.0, 0.0]); + let coord2 = Position::from([101.0, 1.0]); + let coord3 = Position::from([101.0, 1.0]); let geojson_multi_line_string_type1 = vec![vec![ coord1.clone(), @@ -512,12 +512,12 @@ mod tests { #[test] fn geojson_multi_polygon_conversion_test() { - let coord1 = vec![100.0, 0.0]; - let coord2 = vec![101.0, 1.0]; - let coord3 = vec![101.0, 1.0]; - let coord4 = vec![104.0, 0.2]; - let coord5 = vec![100.9, 0.2]; - let coord6 = vec![100.9, 0.7]; + let coord1 = Position::from([100.0, 0.0]); + let coord2 = Position::from([101.0, 1.0]); + let coord3 = Position::from([101.0, 1.0]); + let coord4 = Position::from([104.0, 0.2]); + let coord5 = Position::from([100.9, 0.2]); + let coord6 = Position::from([100.9, 0.7]); let geojson_line_string_type1 = vec![ coord1.clone(), @@ -562,11 +562,11 @@ mod tests { #[test] fn geojson_geometry_collection_conversion_test() { - let coord1 = vec![100.0, 0.0]; - let coord2 = vec![100.0, 1.0]; - let coord3 = vec![101.0, 1.0]; - let coord4 = vec![102.0, 0.0]; - let coord5 = vec![101.0, 0.0]; + let coord1 = Position::from([100.0, 0.0]); + let coord2 = Position::from([100.0, 1.0]); + let coord3 = Position::from([101.0, 1.0]); + let coord4 = Position::from([102.0, 0.0]); + let coord5 = Position::from([101.0, 0.0]); let geojson_multi_point = Value::MultiPoint(vec![coord1.clone(), coord2.clone()]); let geojson_multi_line_string = Value::MultiLineString(vec![ @@ -602,7 +602,7 @@ mod tests { #[test] fn geojson_geometry_conversion() { - let coords = vec![100.0, 0.2]; + let coords = Position::from([100.0, 0.2]); let geojson_geometry = Geometry::from(Value::Point(coords.clone())); let geo_geometry: geo_types::Geometry = geojson_geometry .try_into() @@ -615,8 +615,8 @@ mod tests { #[test] fn geojson_mismatch_geometry_conversion_test() { - let coord1 = vec![100.0, 0.2]; - let coord2 = vec![101.0, 1.0]; + let coord1 = Position::from([100.0, 0.2]); + let coord2 = Position::from([101.0, 1.0]); let geojson_line_string = Value::LineString(vec![coord1.clone(), coord2.clone()]); use std::convert::TryFrom; let error = geo_types::Point::::try_from(geojson_line_string).unwrap_err(); @@ -678,10 +678,10 @@ mod tests { #[test] fn borrowed_value_conversions_test() -> crate::Result<()> { - let coord1 = vec![100.0, 0.2]; - let coord2 = vec![101.0, 1.0]; - let coord3 = vec![102.0, 0.8]; - let coord4 = vec![104.0, 0.2]; + let coord1 = Position::from([100.0, 0.2]); + let coord2 = Position::from([101.0, 1.0]); + let coord3 = Position::from([102.0, 0.8]); + let coord4 = Position::from([104.0, 0.2]); let geojson_point = Value::Point(coord1.clone()); let _: geo_types::Point = (&geojson_point).try_into()?; diff --git a/src/de.rs b/src/de.rs index feb318e..eb65667 100644 --- a/src/de.rs +++ b/src/de.rs @@ -82,14 +82,14 @@ //! ... //! } //! ``` -use crate::{Feature, FeatureReader, JsonValue, Result}; +use crate::{Bbox, Feature, FeatureReader, Geometry, JsonObject, JsonValue, Result}; use std::convert::{TryFrom, TryInto}; use std::fmt::Formatter; use std::io::Read; use std::marker::PhantomData; -use serde::de::{Deserialize, Deserializer, Error, IntoDeserializer}; +use serde::de::{Deserialize, DeserializeOwned, Deserializer, Error}; /// Deserialize a GeoJSON FeatureCollection into your custom structs. /// @@ -144,33 +144,22 @@ use serde::de::{Deserialize, Deserializer, Error, IntoDeserializer}; /// } /// } /// ``` -pub fn deserialize_feature_collection<'de, T>( +pub fn deserialize_feature_collection( feature_collection_reader: impl Read, ) -> Result>> where - T: Deserialize<'de>, + T: DeserializeOwned, { #[allow(deprecated)] - let iter = crate::FeatureIterator::new(feature_collection_reader).map( - |feature_value: Result| { - let deserializer = feature_value?.into_deserializer(); - let visitor = FeatureVisitor::new(); - let record: T = deserializer.deserialize_map(visitor)?; - - Ok(record) - }, - ); - Ok(iter) + Ok(crate::FeatureIterator::new(feature_collection_reader)) } /// Build a `Vec` of structs from a GeoJson `&str`. /// /// See [`deserialize_feature_collection`] for more. -pub fn deserialize_feature_collection_str_to_vec<'de, T>( - feature_collection_str: &str, -) -> Result> +pub fn deserialize_feature_collection_str_to_vec(feature_collection_str: &str) -> Result> where - T: Deserialize<'de>, + T: DeserializeOwned, { let feature_collection_reader = feature_collection_str.as_bytes(); deserialize_feature_collection(feature_collection_reader)?.collect() @@ -179,11 +168,12 @@ where /// Build a `Vec` of structs from a GeoJson reader. /// /// See [`deserialize_feature_collection`] for more. -pub fn deserialize_feature_collection_to_vec<'de, T>( +pub fn deserialize_feature_collection_to_vec( feature_collection_reader: impl Read, ) -> Result> where - T: Deserialize<'de>, + // REVIEW: Can we restore the borrowed (Deserialize<'de>) flavor? + T: DeserializeOwned, { deserialize_feature_collection(feature_collection_reader)?.collect() } @@ -281,33 +271,30 @@ pub fn deserialize_features_from_feature_collection( /// assert_eq!(my_struct.name, "Downtown"); /// assert_eq!(my_struct.geometry.x(), 11.1); /// ``` -pub fn deserialize_single_feature<'de, T>(feature_reader: impl Read) -> Result -where - T: Deserialize<'de>, -{ - let feature_value: JsonValue = serde_json::from_reader(feature_reader)?; - let deserializer = feature_value.into_deserializer(); - let visitor = FeatureVisitor::new(); - Ok(deserializer.deserialize_map(visitor)?) -} - -struct FeatureVisitor { +// pub fn deserialize_single_feature<'de, T>(feature_reader: impl Read) -> Result +// where +// T: Deserialize<'de>, +// { +// let feature_value: JsonValue = serde_json::from_reader(feature_reader)?; +// let deserializer = feature_value.into_deserializer(); +// let visitor = FeatureVisitor::new(); +// Ok(deserializer.deserialize_map(visitor)?) +// } + +pub(crate) struct FeatureVisitor { _marker: PhantomData, } impl FeatureVisitor { - fn new() -> Self { + pub fn new() -> Self { Self { _marker: PhantomData, } } } -impl<'de, D> serde::de::Visitor<'de> for FeatureVisitor -where - D: Deserialize<'de>, -{ - type Value = D; +impl<'de> serde::de::Visitor<'de> for FeatureVisitor { + type Value = Feature; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { write!(formatter, "a valid GeoJSON Feature object") @@ -317,49 +304,67 @@ where where A: serde::de::MapAccess<'de>, { - let mut has_feature_type = false; - use std::collections::HashMap; - let mut hash_map: HashMap = HashMap::new(); - - while let Some((key, value)) = map_access.next_entry::()? { - if key == "type" { - if value.as_str() == Some("Feature") { - has_feature_type = true; - } else { - return Err(Error::custom( - "GeoJSON Feature had a `type` other than \"Feature\"", - )); + let mut bbox: Option = None; + let mut geometry: Option = None; + let mut properties: Option = None; + let mut foreign_members: Option = None; + let mut id: Option = None; + + log::debug!("in visit map"); + while let Some(key) = map_access.next_key::()? { + match key.as_str() { + // Note: if we are deserializing a top-level Feature (as opposed to a FeatureCollection) + // we won't encounter the `type` field, as it will already have been consumed in + // determining if this is the proper deserializer. + "type" => { + let type_name = map_access.next_value::()?; + if type_name == "Feature" { + log::debug!("type == Feature"); + } else { + return Err(Error::custom( + "GeoJSON Feature had a `type` other than \"Feature\"", + )); + } + } + "bbox" => { + log::debug!("had bbox"); + bbox = Some(map_access.next_value()?); + } + "geometry" => { + log::debug!("had geometry"); + geometry = map_access.next_value()?; + log::debug!("got geometry"); } - } else if key == "geometry" { - if let JsonValue::Object(_) = value { - hash_map.insert("geometry".to_string(), value); - } else { - return Err(Error::custom("GeoJSON Feature had a unexpected geometry")); + "id" => { + log::debug!("had id"); + id = Some(map_access.next_value()?) } - } else if key == "properties" { - if let JsonValue::Object(properties) = value { - // flatten properties onto struct - for (prop_key, prop_value) in properties { - hash_map.insert(prop_key, prop_value); + "properties" => { + log::debug!("had properties"); + properties = map_access.next_value()?; + } + _ => { + log::debug!("had foreign member \"{key}\""); + let value: JsonValue = map_access.next_value::()?; + if let Some(ref mut foreign_members) = foreign_members { + foreign_members.insert(key, value); + } else { + let mut fm = JsonObject::new(); + fm.insert(key, value); + foreign_members = Some(fm); } - } else { - return Err(Error::custom("GeoJSON Feature had a unexpected geometry")); } - } else { - log::debug!("foreign members are not handled by Feature deserializer") } } - if has_feature_type { - let d2 = hash_map.into_deserializer(); - let result = - Deserialize::deserialize(d2).map_err(|e| Error::custom(format!("{}", e)))?; - Ok(result) - } else { - Err(Error::custom( - "A GeoJSON Feature must have a `type: \"Feature\"` field, but found none.", - )) - } + log::debug!("finishing up in visit_map"); + Ok(Feature { + bbox, + properties, + geometry, + id, + foreign_members, + }) } } @@ -404,6 +409,7 @@ pub(crate) mod tests { #[test] fn test_deserialize_feature_collection() { use crate::Feature; + pretty_env_logger::init(); let feature_collection_string = feature_collection().to_string(); let bytes_reader = feature_collection_string.as_bytes(); diff --git a/src/errors.rs b/src/errors.rs index b5e7c5b..2f9b821 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,7 @@ //! Module for all GeoJSON-related errors use crate::Feature; use serde_json::value::Value; +use std::fmt::Display; use thiserror::Error; /// Errors which can occur when encoding, decoding, and converting GeoJSON @@ -67,6 +68,15 @@ impl From for Error { } } +impl serde::de::Error for Error { + fn custom(msg: T) -> Self + where + T: Display, + { + Self::MalformedJson(serde_json::Error::custom(msg)) + } +} + impl From for Error { fn from(error: std::io::Error) -> Self { Self::Io(error) diff --git a/src/feature.rs b/src/feature.rs index 9e82e32..358562f 100644 --- a/src/feature.rs +++ b/src/feature.rs @@ -15,6 +15,7 @@ use std::convert::TryFrom; use std::str::FromStr; +use crate::de::FeatureVisitor; use crate::errors::{Error, Result}; use crate::{util, Feature, Geometry, Value}; use crate::{JsonObject, JsonValue}; @@ -191,23 +192,22 @@ impl<'de> Deserialize<'de> for Feature { where D: Deserializer<'de>, { - use serde::de::Error as SerdeError; - - let val = JsonObject::deserialize(deserializer)?; - - Feature::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) + deserializer.deserialize_map(FeatureVisitor::new()) } } /// Feature identifier /// /// [GeoJSON Format Specification § 3.2](https://tools.ietf.org/html/rfc7946#section-3.2) -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(untagged)] pub enum Id { String(String), Number(serde_json::Number), } +// REVIEW: test deserializing both numeric and string based ids + impl Serialize for Id { fn serialize(&self, serializer: S) -> std::result::Result where @@ -222,8 +222,7 @@ impl Serialize for Id { #[cfg(test)] mod tests { - use crate::JsonObject; - use crate::{feature, Error, Feature, GeoJson, Geometry, Value}; + use crate::{feature, Error, Feature, GeoJson, Geometry, JsonObject, Position, Value}; use serde_json::json; use std::str::FromStr; @@ -252,7 +251,7 @@ mod tests { } fn value() -> Value { - Value::Point(vec![1.1, 2.1]) + Value::Point(Position::from([1.1, 2.1])) } fn geometry() -> Geometry { @@ -337,8 +336,8 @@ mod tests { fn feature_json_invalid_geometry() { let geojson_str = r#"{"geometry":3.14,"properties":{},"type":"Feature"}"#; match geojson_str.parse::().unwrap_err() { - Error::FeatureInvalidGeometryValue(_) => (), - _ => unreachable!(), + Error::MalformedJson(e) => assert!(e.to_string().contains("expected a valid GeoJSON Geometry object")), + other => panic!("expecting FeatureInvalidGeometryValue, but got: {:?}", other), } } @@ -347,7 +346,7 @@ mod tests { let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":0,\"properties\":{},\"type\":\"Feature\"}"; let feature = crate::Feature { geometry: Some(Geometry { - value: Value::Point(vec![1.1, 2.1]), + value: Value::Point(Position::from([1.1, 2.1])), bbox: None, foreign_members: None, }), @@ -373,7 +372,7 @@ mod tests { let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":\"foo\",\"properties\":{},\"type\":\"Feature\"}"; let feature = crate::Feature { geometry: Some(Geometry { - value: Value::Point(vec![1.1, 2.1]), + value: Value::Point(Position::from([1.1, 2.1])), bbox: None, foreign_members: None, }), @@ -397,19 +396,19 @@ mod tests { #[test] fn decode_feature_with_invalid_id_type_object() { let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":{},\"properties\":{},\"type\":\"Feature\"}"; - assert!(matches!( - feature_json_str.parse::(), - Err(Error::FeatureInvalidIdentifierType(_)) - )); + match feature_json_str.parse::().unwrap_err() { + Error::MalformedJson(e) => assert!(e.to_string().contains("Id")), + other => panic!("expected different error. Got: {:?}", other), + } } #[test] fn decode_feature_with_invalid_id_type_null() { let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":null,\"properties\":{},\"type\":\"Feature\"}"; - assert!(matches!( - feature_json_str.parse::(), - Err(Error::FeatureInvalidIdentifierType(_)) - )); + match feature_json_str.parse::().unwrap_err() { + Error::MalformedJson(e) => assert!(e.to_string().contains("Id")), + other => panic!("expected different error. Got: {:?}", other), + } } #[test] @@ -424,7 +423,7 @@ mod tests { ); let feature = crate::Feature { geometry: Some(Geometry { - value: Value::Point(vec![1.1, 2.1]), + value: Value::Point(Position::from([1.1, 2.1])), bbox: None, foreign_members: None, }), diff --git a/src/feature_collection.rs b/src/feature_collection.rs index ba2c83f..55554fa 100644 --- a/src/feature_collection.rs +++ b/src/feature_collection.rs @@ -19,7 +19,7 @@ use std::str::FromStr; use crate::errors::{Error, Result}; use crate::{util, Bbox, Feature}; use crate::{JsonObject, JsonValue}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; use serde_json::json; /// Feature Collection Objects @@ -51,17 +51,17 @@ use serde_json::json; /// Collect from an iterator: /// /// ```rust -/// use geojson::{Feature, FeatureCollection, Value}; +/// use geojson::{Feature, FeatureCollection, Position, Value}; /// /// let fc: FeatureCollection = (0..10) /// .map(|idx| -> Feature { /// let c = idx as f64; -/// Value::Point(vec![1.0 * c, 2.0 * c, 3.0 * c]).into() +/// Value::Point(Position::from(vec![1.0 * c, 2.0 * c, 3.0 * c])).into() /// }) /// .collect(); /// assert_eq!(fc.features.len(), 10); /// ``` -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct FeatureCollection { /// Bounding Box /// @@ -71,6 +71,7 @@ pub struct FeatureCollection { /// Foreign Members /// /// [GeoJSON Format Specification § 6](https://tools.ietf.org/html/rfc7946#section-6) + #[serde(flatten)] pub foreign_members: Option, } @@ -159,7 +160,7 @@ impl FromStr for FeatureCollection { type Err = Error; fn from_str(s: &str) -> Result { - Self::try_from(crate::GeoJson::from_str(s)?) + Ok(serde_json::from_reader(s.as_bytes())?) } } @@ -172,18 +173,18 @@ impl Serialize for FeatureCollection { } } -impl<'de> Deserialize<'de> for FeatureCollection { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - use serde::de::Error as SerdeError; - - let val = JsonObject::deserialize(deserializer)?; - - FeatureCollection::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) - } -} +// impl<'de> Deserialize<'de> for FeatureCollection { +// fn deserialize(deserializer: D) -> std::result::Result +// where +// D: Deserializer<'de>, +// { +// use serde::de::Error as SerdeError; +// +// let val = JsonObject::deserialize(deserializer)?; +// +// FeatureCollection::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) +// } +// } /// Create a [`FeatureCollection`] using the [`collect`] /// method on an iterator of `Feature`s. If every item @@ -253,22 +254,24 @@ impl FromIterator for FeatureCollection { #[cfg(test)] mod tests { - use crate::{Error, Feature, FeatureCollection, Value}; + use crate::{Error, Feature, FeatureCollection, Position, Value}; use serde_json::json; use std::str::FromStr; - #[test] fn test_fc_from_iterator() { let features: Vec = vec![ { - let mut feat: Feature = Value::Point(vec![0., 0., 0.]).into(); + let mut feat: Feature = Value::Point(Position::from(vec![0., 0., 0.])).into(); feat.bbox = Some(vec![-1., -1., -1., 1., 1., 1.]); feat }, { - let mut feat: Feature = - Value::MultiPoint(vec![vec![10., 10., 10.], vec![11., 11., 11.]]).into(); + let mut feat: Feature = Value::MultiPoint(vec![ + Position::from(vec![10., 10., 10.]), + Position::from(vec![11., 11., 11.]), + ]) + .into(); feat.bbox = Some(vec![10., 10., 10., 11., 11., 11.]); feat }, @@ -339,11 +342,11 @@ mod tests { let actual_failure = FeatureCollection::from_str(&geometry_json).unwrap_err(); match actual_failure { - Error::ExpectedType { actual, expected } => { - assert_eq!(actual, "Geometry"); - assert_eq!(expected, "FeatureCollection"); + Error::MalformedJson(e) => { + assert!(e.to_string().contains("missing field")); + assert!(e.to_string().contains("features")); } - e => panic!("unexpected error: {}", e), - }; + other => panic!("expected other error. Got: {:?}", other) + } } } diff --git a/src/feature_iterator.rs b/src/feature_iterator.rs index 9155067..f403b7d 100644 --- a/src/feature_iterator.rs +++ b/src/feature_iterator.rs @@ -129,7 +129,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{Geometry, Value}; + use crate::{Geometry, Position, Value}; use std::io::BufReader; @@ -187,7 +187,7 @@ mod tests { assert_eq!( Geometry { bbox: None, - value: Value::Point(vec![102.0, 0.5]), + value: Value::Point(Position::from([102.0, 0.5])), foreign_members: None, }, fi.next().unwrap().unwrap().geometry.unwrap() @@ -196,10 +196,10 @@ mod tests { Geometry { bbox: None, value: Value::LineString(vec![ - vec![102.0, 0.0], - vec![103.0, 1.0], - vec![104.0, 0.0], - vec![105.0, 1.0] + Position::from([102.0, 0.0]), + Position::from([103.0, 1.0]), + Position::from([104.0, 0.0]), + Position::from([105.0, 1.0]) ]), foreign_members: None, }, @@ -209,11 +209,11 @@ mod tests { Geometry { bbox: None, value: Value::Polygon(vec![vec![ - vec![100.0, 0.0], - vec![101.0, 0.0], - vec![101.0, 1.0], - vec![100.0, 1.0], - vec![100.0, 0.0] + Position::from([100.0, 0.0]), + Position::from([101.0, 0.0]), + Position::from([101.0, 1.0]), + Position::from([100.0, 1.0]), + Position::from([100.0, 0.0]) ]]), foreign_members: None, }, diff --git a/src/feature_writer.rs b/src/feature_writer.rs index a48c578..b3f484b 100644 --- a/src/feature_writer.rs +++ b/src/feature_writer.rs @@ -226,9 +226,10 @@ impl Drop for FeatureWriter { #[cfg(test)] mod tests { use super::*; - use crate::JsonValue; + use crate::{JsonValue, Position}; use serde_json::json; + use tinyvec::tiny_vec; // an example struct that we want to serialize #[derive(Serialize)] @@ -284,7 +285,9 @@ mod tests { Feature { bbox: None, - geometry: Some(crate::Geometry::from(crate::Value::Point(vec![1.1, 1.2]))), + geometry: Some(crate::Geometry::from(crate::Value::Point(Position::from( + [1.1, 1.2], + )))), id: None, properties: Some(props), foreign_members: None, @@ -298,7 +301,9 @@ mod tests { Feature { bbox: None, - geometry: Some(crate::Geometry::from(crate::Value::Point(vec![2.1, 2.2]))), + geometry: Some(crate::Geometry::from(crate::Value::Point(Position::from( + [2.1, 2.2], + )))), id: None, properties: Some(props), foreign_members: None, @@ -340,12 +345,12 @@ mod tests { { let mut writer = FeatureWriter::from_writer(&mut buffer); let record_1 = MyRecord { - geometry: crate::Geometry::from(crate::Value::Point(vec![1.1, 1.2])), + geometry: crate::Geometry::from(crate::Value::Point(Position::from([1.1, 1.2]))), name: "Mishka".to_string(), age: 12, }; let record_2 = MyRecord { - geometry: crate::Geometry::from(crate::Value::Point(vec![2.1, 2.2])), + geometry: crate::Geometry::from(crate::Value::Point(Position::from([2.1, 2.2]))), name: "Jane".to_string(), age: 22, }; diff --git a/src/geojson.rs b/src/geojson.rs index 862e571..931f194 100644 --- a/src/geojson.rs +++ b/src/geojson.rs @@ -15,7 +15,7 @@ use crate::errors::{Error, Result}; use crate::{Feature, FeatureCollection, Geometry}; use crate::{JsonObject, JsonValue}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; use std::convert::TryFrom; use std::fmt; use std::iter::FromIterator; @@ -43,13 +43,59 @@ use std::str::FromStr; /// let feature2: Feature = geojson.try_into().unwrap(); /// ``` /// [GeoJSON Format Specification § 3](https://tools.ietf.org/html/rfc7946#section-3) -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Deserialize)] +// Tagging is a pickle... we have a "type" field which works like a tag, and FeatureCollection.type and Feature.type are fine, +// but for a Geometry, the type is not "Geometry" rather it's one of the inner variants. +// We could use serdes `untagged` attribute, but if the geojson is invalid, we get an obtuse error like +// "did not match any variant of untagged enum GeoJson" when we really want something more specific like "`id` field had invalid value" +#[serde(from = "TaggedGeoJson")] pub enum GeoJson { + // this "tag" is probably wrong, because Geometry.type is not "Geometry", rather it's the value + // of one of it's Value enum members. Geometry(Geometry), Feature(Feature), FeatureCollection(FeatureCollection), } +#[derive(Debug, Deserialize)] +#[serde(tag = "type")] +enum TaggedGeoJson { + #[serde(deserialize_with = "crate::geometry::deserialize_point")] + Point(Geometry), + #[serde(deserialize_with = "crate::geometry::deserialize_line_string")] + LineString(Geometry), + #[serde(deserialize_with = "crate::geometry::deserialize_polygon")] + Polygon(Geometry), + #[serde(deserialize_with = "crate::geometry::deserialize_multi_point")] + MultiPoint(Geometry), + #[serde(deserialize_with = "crate::geometry::deserialize_multi_line_string")] + MultiLineString(Geometry), + #[serde(deserialize_with = "crate::geometry::deserialize_multi_polygon")] + MultiPolygon(Geometry), + #[serde(deserialize_with = "crate::geometry::deserialize_geometry_collection")] + GeometryCollection(Geometry), + + Feature(Feature), + FeatureCollection(FeatureCollection), +} + +impl From for GeoJson { + fn from(value: TaggedGeoJson) -> Self { + use TaggedGeoJson::*; + match value { + Point(g) + | LineString(g) + | Polygon(g) + | MultiPoint(g) + | MultiLineString(g) + | MultiPolygon(g) + | GeometryCollection(g) => GeoJson::Geometry(g), + Feature(f) => GeoJson::Feature(f), + FeatureCollection(fc) => GeoJson::FeatureCollection(fc), + } + } +} + impl<'a> From<&'a GeoJson> for JsonObject { fn from(geojson: &'a GeoJson) -> JsonObject { match *geojson { @@ -164,7 +210,7 @@ impl GeoJson { /// # Example /// ``` /// use std::convert::TryInto; - /// use geojson::{Feature, GeoJson, Geometry, Value}; + /// use geojson::{Feature, GeoJson, Geometry, Position, Value}; /// use serde_json::json; /// /// let json_value = json!({ @@ -184,7 +230,7 @@ impl GeoJson { /// geojson, /// GeoJson::Feature(Feature { /// bbox: None, - /// geometry: Some(Geometry::new(Value::Point(vec![102.0, 0.5]))), + /// geometry: Some(Geometry::new(Value::Point(Position::from([102.0, 0.5])))), /// id: None, /// properties: None, /// foreign_members: None, @@ -305,18 +351,18 @@ impl Serialize for GeoJson { } } -impl<'de> Deserialize<'de> for GeoJson { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - use serde::de::Error as SerdeError; - - let val = JsonObject::deserialize(deserializer)?; - - GeoJson::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) - } -} +// impl<'de> Deserialize<'de> for GeoJson { +// fn deserialize(deserializer: D) -> std::result::Result +// where +// D: Deserializer<'de>, +// { +// use serde::de::Error as SerdeError; +// +// let val = JsonObject::deserialize(deserializer)?; +// +// GeoJson::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) +// } +// } /// # Example ///``` @@ -351,16 +397,7 @@ impl FromStr for GeoJson { type Err = Error; fn from_str(s: &str) -> Result { - let object = get_object(s)?; - - GeoJson::from_json_object(object) - } -} - -fn get_object(s: &str) -> Result { - match ::serde_json::from_str(s)? { - JsonValue::Object(object) => Ok(object), - other => Err(Error::ExpectedObjectValue(other)), + Ok(serde_json::from_reader(s.as_bytes())?) } } @@ -398,10 +435,11 @@ impl fmt::Display for FeatureCollection { #[cfg(test)] mod tests { - use crate::{Error, Feature, FeatureCollection, GeoJson, Geometry, Value}; + use crate::{Error, Feature, FeatureCollection, GeoJson, Geometry, Position, Value}; use serde_json::json; use std::convert::TryInto; use std::str::FromStr; + use tinyvec::tiny_vec; #[test] fn test_geojson_from_reader() { @@ -449,7 +487,7 @@ mod tests { geojson, GeoJson::Feature(Feature { bbox: None, - geometry: Some(Geometry::new(Value::Point(vec![102.0, 0.5]))), + geometry: Some(Geometry::new(Value::Point(Position::from([102.0, 0.5])))), id: None, properties: None, foreign_members: None, @@ -460,8 +498,8 @@ mod tests { #[test] fn test_geojson_from_features() { let features: Vec = vec![ - Value::Point(vec![0., 0., 0.]).into(), - Value::Point(vec![1., 1., 1.]).into(), + Value::Point(Position::from(vec![0., 0., 0.])).into(), + Value::Point(Position::from(vec![1., 1., 1.])).into(), ]; let geojson: GeoJson = features.into(); @@ -471,14 +509,18 @@ mod tests { features: vec![ Feature { bbox: None, - geometry: Some(Geometry::new(Value::Point(vec![0., 0., 0.]))), + geometry: Some(Geometry::new(Value::Point(Position::from(vec![ + 0., 0., 0. + ])))), id: None, properties: None, foreign_members: None, }, Feature { bbox: None, - geometry: Some(Geometry::new(Value::Point(vec![1., 1., 1.]))), + geometry: Some(Geometry::new(Value::Point(Position::from(vec![ + 1., 1., 1. + ])))), id: None, properties: None, foreign_members: None, @@ -507,7 +549,7 @@ mod tests { geojson, GeoJson::Feature(Feature { bbox: None, - geometry: Some(Geometry::new(Value::Point(vec![102.0, 0.5]))), + geometry: Some(Geometry::new(Value::Point(Position::from([102.0, 0.5])))), id: None, properties: None, foreign_members: None, @@ -538,4 +580,11 @@ mod tests { Err(Error::MalformedJson(_)) )) } + + #[test] + fn countries() { + let geojson_str = include_str!("../tests/fixtures/countries.geojson"); + let fc = geojson_str.parse::().unwrap(); + assert_eq!(fc.features.len(), 180); + } } diff --git a/src/geometry.rs b/src/geometry.rs index a150654..7255792 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -12,13 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::Formatter; use std::str::FromStr; use std::{convert::TryFrom, fmt}; use crate::errors::{Error, Result}; use crate::{util, Bbox, LineStringType, PointType, PolygonType}; use crate::{JsonObject, JsonValue}; +use serde::de::{SeqAccess, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use tinyvec::TinyVec; /// The underlying value for a `Geometry`. /// @@ -34,11 +37,11 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// let genum = geo_types::Geometry::from(point); /// assert_eq!( /// geojson::Value::from(&point), -/// geojson::Value::Point(vec![2., 9.]), +/// geojson::Value::Point(geojson::Position::from([2., 9.])), /// ); /// assert_eq!( /// geojson::Value::from(&genum), -/// geojson::Value::Point(vec![2., 9.]), +/// geojson::Value::Point(geojson::Position::from([2., 9.])), /// ); /// # } /// # #[cfg(not(feature = "geo-types"))] @@ -186,24 +189,24 @@ impl Serialize for Value { /// Constructing a `Geometry`: /// /// ``` -/// use geojson::{Geometry, Value}; +/// use geojson::{Geometry, Position, Value}; /// -/// let geometry = Geometry::new(Value::Point(vec![7.428959, 1.513394])); +/// let geometry = Geometry::new(Value::Point(Position::from([7.428959, 1.513394]))); /// ``` /// /// Geometries can be created from `Value`s. /// ``` -/// # use geojson::{Geometry, Value}; -/// let geometry1: Geometry = Value::Point(vec![7.428959, 1.513394]).into(); +/// # use geojson::{Geometry, Position, Value}; +/// let geometry1: Geometry = Value::Point(Position::from([7.428959, 1.513394])).into(); /// ``` /// /// Serializing a `Geometry` to a GeoJSON string: /// /// ``` -/// use geojson::{GeoJson, Geometry, Value}; +/// use geojson::{GeoJson, Geometry, Position, Value}; /// use serde_json; /// -/// let geometry = Geometry::new(Value::Point(vec![7.428959, 1.513394])); +/// let geometry = Geometry::new(Value::Point(Position::from([7.428959, 1.513394]))); /// /// let geojson_string = geometry.to_string(); /// @@ -216,7 +219,7 @@ impl Serialize for Value { /// Deserializing a GeoJSON string into a `Geometry`: /// /// ``` -/// use geojson::{GeoJson, Geometry, Value}; +/// use geojson::{GeoJson, Geometry, Position, Value}; /// /// let geojson_str = "{\"coordinates\":[7.428959,1.513394],\"type\":\"Point\"}"; /// @@ -226,7 +229,7 @@ impl Serialize for Value { /// }; /// /// assert_eq!( -/// Geometry::new(Value::Point(vec![7.428959, 1.513394]),), +/// Geometry::new(Value::Point(Position::from([7.428959, 1.513394])),), /// geometry, /// ); /// ``` @@ -235,10 +238,10 @@ impl Serialize for Value { /// feature): /// /// ``` -/// use geojson::{Geometry, Value}; +/// use geojson::{Geometry, Position, Value}; /// use std::convert::TryInto; /// -/// let geometry = Geometry::new(Value::Point(vec![7.428959, 1.513394])); +/// let geometry = Geometry::new(Value::Point(Position::from([7.428959, 1.513394]))); /// # #[cfg(feature = "geo-types")] /// let geom: geo_types::Geometry = geometry.try_into().unwrap(); /// ``` @@ -255,6 +258,48 @@ pub struct Geometry { pub foreign_members: Option, } +#[derive(Debug, Clone, Copy)] +pub(crate) enum GeometryType { + Point, + MultiPoint, + LineString, + MultiLineString, + Polygon, + MultiPolygon, + GeometryCollection, +} + +impl FromStr for GeometryType { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + Ok(match s { + "Point" => Self::Point, + "MultiPoint" => Self::MultiPoint, + "LineString" => Self::LineString, + "MultiLineString" => Self::MultiLineString, + "Polygon" => Self::Polygon, + "MultiPolygon" => Self::MultiPolygon, + "GeometryCollection" => Self::GeometryCollection, + other => return Err(Error::GeometryUnknownType(other.to_string())), + }) + } +} + +// impl GeometryType { +// fn as_str(&self) -> &str { +// match self { +// GeometryType::Point => "Point", +// GeometryType::MultiPoint => "MultiPoint", +// GeometryType::LineString => "LineString", +// GeometryType::MultiLineString => "MultiLineString", +// GeometryType::Polygon => "Polygon", +// GeometryType::MultiPolygon => "MultiPolygon", +// GeometryType::GeometryCollection => "GeometryCollection", +// } +// } +// } + impl Geometry { /// Returns a new `Geometry` with the specified `value`. `bbox` and `foreign_members` will be /// set to `None`. @@ -324,7 +369,8 @@ impl FromStr for Geometry { type Err = Error; fn from_str(s: &str) -> Result { - Self::try_from(crate::GeoJson::from_str(s)?) + let mut de = serde_json::Deserializer::new(serde_json::de::StrRead::new(s)); + Geometry::deserialize(&mut de).map_err(Into::into) } } @@ -342,11 +388,440 @@ impl<'de> Deserialize<'de> for Geometry { where D: Deserializer<'de>, { - use serde::de::Error as SerdeError; + deserializer.deserialize_map(GeometryVisitor::default()) + } +} + +pub(crate) fn deserialize_point<'de, D>(deserializer: D) -> std::result::Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_map(GeometryVisitor::known_type(GeometryType::Point)) +} + +pub(crate) fn deserialize_line_string<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_map(GeometryVisitor::known_type(GeometryType::LineString)) +} + +pub(crate) fn deserialize_polygon<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_map(GeometryVisitor::known_type(GeometryType::Polygon)) +} + +pub(crate) fn deserialize_multi_point<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_map(GeometryVisitor::known_type(GeometryType::MultiPoint)) +} + +pub(crate) fn deserialize_multi_line_string<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_map(GeometryVisitor::known_type(GeometryType::MultiLineString)) +} + +pub(crate) fn deserialize_multi_polygon<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_map(GeometryVisitor::known_type(GeometryType::MultiPolygon)) +} - let val = JsonObject::deserialize(deserializer)?; +pub(crate) fn deserialize_geometry_collection<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_map(GeometryVisitor::known_type( + GeometryType::GeometryCollection, + )) +} - Geometry::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) +#[derive(Debug, Default)] +struct GeometryVisitor { + known_geometry_type: Option, +} +impl GeometryVisitor { + fn known_type(geometry_type: GeometryType) -> Self { + Self { + known_geometry_type: Some(geometry_type), + } + } +} + +use serde::de::Error as SerdeError; +impl<'de> serde::de::Visitor<'de> for GeometryVisitor { + type Value = Geometry; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + write!(formatter, "a valid GeoJSON Geometry object") + } + + fn visit_map(self, mut map_access: A) -> std::result::Result + where + A: serde::de::MapAccess<'de>, + { + use crate::Position; + + // Depending on the geometry type, the CoordinateField could have different dimensions + // This might not support mixed dimensionality + #[derive(Debug)] + enum CoordinateField { + ThreeDimensional(Vec>>), // MultiPolygon + TwoDimensional(Vec>), // Polygon, MultiLineString + OneDimensional(Vec), // LineString, MultiPoint + ZeroDimensional(Position), // Point + } + + #[derive(Debug)] + enum CoordinateFieldElement { + // MultiPolygon + TwoDimensional(Vec>), + // Polygon, MultiLineString + OneDimensional(Vec), + // LineString, MultiPoint + ZeroDimensional(Position), + // Point + Scalar(f64), + } + + impl<'de> Deserialize<'de> for CoordinateField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + struct CoordinateFieldVisitor; + impl<'v> Visitor<'v> for CoordinateFieldVisitor { + type Value = CoordinateField; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + write!(formatter, "a valid Geometry `coordinates` field") + } + + fn visit_seq(self, mut seq: A) -> std::result::Result + where + A: SeqAccess<'v>, + { + match seq.next_element::()? { + None => todo!("handle starting with empty sequence"), + Some(next) => match next { + CoordinateFieldElement::TwoDimensional(positions_2d) => { + let mut positions_3d = vec![positions_2d]; + while let Some(next) = + seq.next_element::()? + { + match next { + CoordinateFieldElement::TwoDimensional(positions) => positions_3d.push(positions), + _ => todo!("handle error when encountering {next:?} expecting homogenous element dimensions") + } + } + return Ok(CoordinateField::ThreeDimensional(positions_3d)); + } + CoordinateFieldElement::OneDimensional(positions) => { + let mut positions_2d = vec![positions]; + while let Some(next) = + seq.next_element::()? + { + match next { + CoordinateFieldElement::OneDimensional(positions) => positions_2d.push(positions), + _ => todo!("handle error when encountering {next:?} expecting homogenous element dimensions") + } + } + return Ok(CoordinateField::TwoDimensional(positions_2d)); + } + CoordinateFieldElement::ZeroDimensional(position) => { + let mut positions = vec![position]; + while let Some(next) = + seq.next_element::()? + { + match next { + CoordinateFieldElement::ZeroDimensional(position) => positions.push(position), + _ => todo!("handle error when encountering {next:?} expecting homogenous element dimensions") + } + } + return Ok(CoordinateField::OneDimensional(positions)); + } + CoordinateFieldElement::Scalar(x) => { + // Will this allocate on the stack? + let mut scalars = TinyVec::default(); + scalars.push(x); + while let Some(next) = + seq.next_element::()? + { + match next { + CoordinateFieldElement::Scalar(s) => scalars.push(s), + _ => todo!("handle error when encountering {next:?} expecting homogenous element dimensions") + } + } + return Ok(CoordinateField::ZeroDimensional(Position::from( + scalars, + ))); + } + }, + } + // while let Some(next) = + // { + // log::debug!("1 - visit_seq - next: {next:?}"); + // } + } + } + + impl<'de> Deserialize<'de> for CoordinateFieldElement { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(CoordinateFieldElementVisitor) + } + } + + struct CoordinateFieldElementVisitor; + impl<'v> Visitor<'v> for CoordinateFieldElementVisitor { + type Value = CoordinateFieldElement; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + write!(formatter, "a valid Geometry `coordinates` field") + } + + #[inline] + fn visit_i64(self, v: i64) -> std::result::Result + where + E: SerdeError, + { + log::debug!("2- visited i64: {v}"); + return Ok(CoordinateFieldElement::Scalar(v as f64)); + } + + #[inline] + fn visit_u64(self, v: u64) -> std::result::Result + where + E: SerdeError, + { + log::debug!("2- visited u64: {v}"); + return Ok(CoordinateFieldElement::Scalar(v as f64)); + } + + #[inline] + fn visit_f64(self, v: f64) -> std::result::Result + where + E: SerdeError, + { + log::debug!("2- visited f64: {v}"); + return Ok(CoordinateFieldElement::Scalar(v)); + } + + fn visit_seq(self, mut seq: A) -> std::result::Result + where + A: SeqAccess<'v>, + { + let next = seq.next_element::()?; + log::debug!("2 - visit_seq - next: {next:?}"); + match next { + None => todo!("2. handle none"), + Some(next) => match next { + // CoordinateFieldElement::ThreeDimensional(_) => {} + // CoordinateFieldElement::TwoDimensional(_) => {} + CoordinateFieldElement::OneDimensional(positions) => { + let mut positions_2d = vec![positions]; + while let Some(next) = + seq.next_element::()? + { + match next { + CoordinateFieldElement::OneDimensional(positions) => positions_2d.push(positions), + _ => todo!("handle error when encountering {next:?} expecting homogenous element dimensions") + } + } + return Ok(CoordinateFieldElement::TwoDimensional( + positions_2d, + )); + } + CoordinateFieldElement::ZeroDimensional(pos) => { + let mut positions = vec![pos]; + while let Some(next) = + seq.next_element::()? + { + match next { + CoordinateFieldElement::ZeroDimensional(position) => positions.push(position), + _ => todo!("handle error when encountering {next:?} expecting more ZeroDimensional elements") + } + } + return Ok(CoordinateFieldElement::OneDimensional(positions)); + } + CoordinateFieldElement::Scalar(x) => { + let y = + seq.next_element::()?.expect("TODO: handle missing"); + // TODO: support 3D+ + assert!(seq.next_element::()?.is_none()); + let pos = Position::from([x, y]); + log::debug!("built position: {pos:?}"); + return Ok(CoordinateFieldElement::ZeroDimensional(pos)); + } + _ => todo!("2. visited seq. next: {next:?}"), + }, + } + } + } + + log::debug!("deserializing CoordinateField"); + deserializer.deserialize_any(CoordinateFieldVisitor) + // Ok(match deserializer.deserialize_any(CoordinateFieldVisitor)? { + // CoordinateFieldElement::ThreeDimensional(x) => CoordinateField::ThreeDimensional(x), + // CoordinateFieldElement::TwoDimensional(x) => CoordinateField::TwoDimensional(x), + // CoordinateFieldElement::OneDimensional(x) =>CoordinateField::OneDimensional(x), + // CoordinateFieldElement::ZeroDimensional(x) => CoordinateField::ZeroDimensional(x), + // CoordinateFieldElement::Scalar(x) => panic!("shouldn't get scalar at this point: {}", x) + // }) + } + } + + // impl<'de> Deserialize<'de> for CoordinateField { + // fn deserialize(de: D) -> std::result::Result where D: Deserializer<'de> { + // let dimensions = 0; + // match Position::deserialize(de) { + // Ok(p) => Ok(CoordinateField::ZeroDimensional(p)), + // Err(e) => { + // match Vec::::deserialize(de) { + // Ok(p) => Ok(CoordinateField::OneDimensional(p)), + // Err(e) => { + // dbg!(e); + // todo!("impl deserialize for higher dimension coordinate field") + // } + // } + // } + // } + // } + // } + + fn build_geometry_value( + geometry_type: GeometryType, + coordinates: CoordinateField, + ) -> Result { + match geometry_type { + GeometryType::Point => { + if let CoordinateField::ZeroDimensional(position) = coordinates { + return Ok(Value::Point(position)); + } + } + GeometryType::MultiPoint => { + if let CoordinateField::OneDimensional(position) = coordinates { + return Ok(Value::MultiPoint(position)); + } + } + GeometryType::LineString => { + if let CoordinateField::OneDimensional(position) = coordinates { + return Ok(Value::LineString(position)); + } + } + GeometryType::MultiLineString => { + if let CoordinateField::TwoDimensional(position) = coordinates { + return Ok(Value::MultiLineString(position)); + } + } + GeometryType::Polygon => { + if let CoordinateField::TwoDimensional(position) = coordinates { + return Ok(Value::Polygon(position)); + } + } + GeometryType::MultiPolygon => { + if let CoordinateField::ThreeDimensional(position) = coordinates { + return Ok(Value::MultiPolygon(position)); + } + } + GeometryType::GeometryCollection => { + unreachable!("should not be called for GeometryCollection") + } + } + todo!("handle dimensional mismatch") + } + + let mut child_geometries: Option> = None; + let mut coordinate_field: Option = None; + let mut geometry_type: Option = self.known_geometry_type; + let mut foreign_members: Option = None; + let mut bbox: Option = None; + + while let Some(next_key) = map_access.next_key::()? { + match next_key.as_str() { + "coordinates" => { + if coordinate_field.is_some() { + todo!("handle existing coordinate field error"); + } + if child_geometries.is_some() { + todo!("encountered coordinates field for geoemtry with child geometries - is this a GeometryCollection or not?"); + } + coordinate_field = Some(map_access.next_value()?); + } + "geometries" => { + if coordinate_field.is_some() { + todo!("encountered coordinates field for geoemtry with child geometries - is this a GeometryCollection or not?"); + } + if child_geometries.is_some() { + todo!("handle existing child_geometries error"); + } + child_geometries = Some(map_access.next_value()?); + } + "type" => { + if geometry_type.is_some() { + todo!("handle existing geometry field error"); + } + let geometry_type_string: String = map_access.next_value()?; + let gt = GeometryType::from_str(geometry_type_string.as_str()) + .map_err(A::Error::custom)?; + geometry_type = Some(gt); + } + "bbox" => { + // REVIEW: still need to test this. + bbox = Some(map_access.next_value()?); + } + _ => { + if let Some(ref mut foreign_members) = foreign_members { + foreign_members.insert(next_key, map_access.next_value()?); + } else { + let mut fm = JsonObject::new(); + fm.insert(next_key, map_access.next_value()?); + foreign_members = Some(fm); + } + } + } + } + + let Some(geometry_type) = geometry_type else { + todo!("missing geometry type"); + }; + + let value = match (geometry_type, coordinate_field, child_geometries) { + (GeometryType::GeometryCollection, None, Some(geometries)) => { + Value::GeometryCollection(geometries) + } + (geometry_type, Some(coordinate_field), None) => { + build_geometry_value(geometry_type, coordinate_field).map_err(A::Error::custom)? + } + _ => todo!("handle missing/extra fields"), + }; + + Ok(Geometry { + value, + bbox, + foreign_members, + }) } } @@ -361,7 +836,8 @@ where #[cfg(test)] mod tests { - use crate::{Error, GeoJson, Geometry, JsonObject, Value}; + use super::*; + use crate::{GeoJson, Position}; use serde_json::json; use std::str::FromStr; @@ -376,7 +852,7 @@ mod tests { fn encode_decode_geometry() { let geometry_json_str = "{\"coordinates\":[1.1,2.1],\"type\":\"Point\"}"; let geometry = Geometry { - value: Value::Point(vec![1.1, 2.1]), + value: Value::Point(Position::from([1.1, 2.1])), bbox: None, foreign_members: None, }; @@ -410,7 +886,7 @@ mod tests { assert_eq!( geometry, Geometry { - value: Value::Point(vec![0.0, 0.1]), + value: Value::Point(Position::from([0.0, 0.1])), bbox: None, foreign_members: None, } @@ -419,7 +895,11 @@ mod tests { #[test] fn test_geometry_display() { - let v = Value::LineString(vec![vec![0.0, 0.1], vec![0.1, 0.2], vec![0.2, 0.3]]); + let v = Value::LineString(vec![ + Position::from([0.0, 0.1]), + Position::from([0.1, 0.2]), + Position::from([0.2, 0.3]), + ]); let geometry = Geometry::new(v); assert_eq!( "{\"coordinates\":[[0.0,0.1],[0.1,0.2],[0.2,0.3]],\"type\":\"LineString\"}", @@ -429,7 +909,11 @@ mod tests { #[test] fn test_value_display() { - let v = Value::LineString(vec![vec![0.0, 0.1], vec![0.1, 0.2], vec![0.2, 0.3]]); + let v = Value::LineString(vec![ + Position::from([0.0, 0.1]), + Position::from([0.1, 0.2]), + Position::from([0.2, 0.3]), + ]); assert_eq!( "{\"coordinates\":[[0.0,0.1],[0.1,0.2],[0.2,0.3]],\"type\":\"LineString\"}", v.to_string() @@ -446,7 +930,7 @@ mod tests { serde_json::to_value(true).unwrap(), ); let geometry = Geometry { - value: Value::Point(vec![1.1, 2.1]), + value: Value::Point(Position::from([1.1, 2.1])), bbox: None, foreign_members: Some(foreign_members), }; @@ -470,12 +954,15 @@ mod tests { value: Value::GeometryCollection(vec![ Geometry { bbox: None, - value: Value::Point(vec![100.0, 0.0]), + value: Value::Point(Position::from([100.0, 0.0])), foreign_members: None, }, Geometry { bbox: None, - value: Value::LineString(vec![vec![101.0, 0.0], vec![102.0, 1.0]]), + value: Value::LineString(vec![ + Position::from([101.0, 0.0]), + Position::from([102.0, 1.0]), + ]), foreign_members: None, }, ]), @@ -523,14 +1010,14 @@ mod tests { let actual_failure = Geometry::from_str(&feature_json).unwrap_err(); match actual_failure { - Error::ExpectedType { actual, expected } => { - assert_eq!(actual, "Feature"); - assert_eq!(expected, "Geometry"); + Error::MalformedJson(e) => { + e.to_string().contains("Feature"); } e => panic!("unexpected error: {}", e), }; } + #[test] fn test_reject_too_few_coordinates() { let err = Geometry::from_str(r#"{"type": "Point", "coordinates": []}"#).unwrap_err(); @@ -545,4 +1032,37 @@ mod tests { "A position must contain two or more elements, but got `1`" ); } + + mod deserialize { + use super::*; + use crate::Geometry; + use crate::Value; + + #[test] + fn point() { + let json = json!({ + "type": "Point", + "coordinates": [1.0, 2.0] + }) + .to_string(); + + let geom = Geometry::from_str(&json).unwrap(); + let expected = Value::Point(Position::from([1.0, 2.0])); + assert_eq!(geom.value, expected); + } + + #[test] + fn linestring() { + let json = json!({ + "type": "LineString", + "coordinates": [[1.0, 2.0], [3.0, 4.0]] + }) + .to_string(); + + let geom = Geometry::from_str(&json).unwrap(); + let expected = + Value::LineString(vec![Position::from([1.0, 2.0]), Position::from([3.0, 4.0])]); + assert_eq!(geom.value, expected); + } + } } diff --git a/src/lib.rs b/src/lib.rs index 045faa0..e49075e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ //! // read geometry data //! let geometry: Geometry = feature.geometry.unwrap(); //! if let Value::Point(coords) = geometry.value { -//! assert_eq!(coords, vec![-118.2836, 34.0956]); +//! assert_eq!(coords.as_slice(), &[-118.2836, 34.0956]); //! } //! //! # else { @@ -111,7 +111,7 @@ //! `GeoJson` can be serialized by calling [`to_string`](geojson/enum.GeoJson.html#impl-ToString): //! //! ```rust -//! use geojson::{Feature, GeoJson, Geometry, Value}; +//! use geojson::{Feature, GeoJson, Geometry, Position, Value}; //! # fn get_properties() -> ::geojson::JsonObject { //! # let mut properties = ::geojson::JsonObject::new(); //! # properties.insert( @@ -122,7 +122,7 @@ //! # } //! # fn main() { //! -//! let geometry = Geometry::new(Value::Point(vec![-120.66029, 35.2812])); +//! let geometry = Geometry::new(Value::Point(Position::from([-120.66029, 35.2812]))); //! //! let geojson = GeoJson::Feature(Feature { //! bbox: None, @@ -259,11 +259,11 @@ //! //! assert_eq!( //! geojson::Value::from(&geo_point), -//! geojson::Value::Point(vec![2., 9.]), +//! geojson::Value::Point(geojson::Position::from([2., 9.])), //! ); //! assert_eq!( //! geojson::Value::from(&geo_geometry), -//! geojson::Value::Point(vec![2., 9.]), +//! geojson::Value::Point(geojson::Position::from([2., 9.])), //! ); //! # } //! ``` @@ -421,10 +421,66 @@ /// [GeoJSON Format Specification § 5](https://tools.ietf.org/html/rfc7946#section-5) pub type Bbox = Vec; +use tinyvec::TinyVec; /// Positions /// /// [GeoJSON Format Specification § 3.1.1](https://tools.ietf.org/html/rfc7946#section-3.1.1) -pub type Position = Vec; +#[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +pub struct Position(TinyVec<[f64; 2]>); + +impl Position { + pub fn as_slice_mut(&mut self) -> &mut [f64] { + &mut self.0 + } + + pub fn as_slice(&self) -> &[f64] { + &self.0 + } +} + +impl From> for Position { + fn from(value: TinyVec<[f64; 2]>) -> Self { + Self(value) + } +} + +impl From> for Position { + fn from(value: Vec) -> Self { + Self(TinyVec::Heap(value)) + } +} + +impl From<[f64; 2]> for Position { + fn from(value: [f64; 2]) -> Self { + Self(TinyVec::Inline(value.into())) + } +} + +impl From<(f64, f64)> for Position { + fn from(value: (f64, f64)) -> Self { + Self::from([value.0, value.1]) + } +} + +use std::ops::{Index, IndexMut}; +use std::slice::SliceIndex; + +impl> Index for Position { + type Output = >::Output; + #[inline(always)] + #[must_use] + fn index(&self, index: I) -> &Self::Output { + &self.0[index] + } +} + +impl> IndexMut for Position { + #[inline(always)] + #[must_use] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + &mut self.0[index] + } +} pub type PointType = Position; pub type LineStringType = Vec; diff --git a/src/ser.rs b/src/ser.rs index 1a17e5f..e0de23a 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -362,7 +362,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::JsonValue; + use crate::{JsonValue, Position}; use serde_json::json; @@ -377,7 +377,7 @@ mod tests { } let my_feature = { - let geometry = crate::Geometry::new(crate::Value::Point(vec![0.0, 1.0])); + let geometry = crate::Geometry::new(crate::Value::Point(Position::from([0.0, 1.0]))); let name = "burbs".to_string(); MyStruct { geometry, name } }; @@ -409,7 +409,9 @@ mod tests { #[test] fn with_some_geom() { let my_feature = { - let geometry = Some(crate::Geometry::new(crate::Value::Point(vec![0.0, 1.0]))); + let geometry = Some(crate::Geometry::new(crate::Value::Point(Position::from([ + 0.0, 1.0, + ])))); let name = "burbs".to_string(); MyStruct { geometry, name } }; diff --git a/src/util.rs b/src/util.rs index a1e4897..5234f36 100644 --- a/src/util.rs +++ b/src/util.rs @@ -213,11 +213,11 @@ fn json_to_position(json: &JsonValue) -> Result { if coords_array.len() < 2 { return Err(Error::PositionTooShort(coords_array.len())); } - let mut coords = Vec::with_capacity(coords_array.len()); + let mut coords = tinyvec::TinyVec::with_capacity(coords_array.len()); for position in coords_array { coords.push(expect_f64(position)?); } - Ok(coords) + Ok(Position(coords)) } fn json_to_1d_positions(json: &JsonValue) -> Result> { diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 623d36d..c4a7687 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -52,7 +52,9 @@ mod roundtrip_tests { let _ = file.read_to_string(&mut file_contents); // Read and parse the geojson from the file's contents - let geojson = file_contents.parse::().expect("unable to parse"); + let geojson = file_contents + .parse::() + .expect(&format!("unable to parse: {file_path}")); // Now that we've successfully decoded the geojson, re-encode it and compare to the // original to make sure nothing was lost. @@ -61,6 +63,12 @@ mod roundtrip_tests { let original_json: serde_json::Value = serde_json::from_str(&file_contents).unwrap(); let roundtrip_json: serde_json::Value = serde_json::from_str(&geojson_string).unwrap(); - assert_eq!(original_json, roundtrip_json) + assert_eq!( + original_json, + roundtrip_json, + "inconsistent round trip: {file_path}. \n\n original: {} \n\n roundtrip: {}", + serde_json::to_string_pretty(&original_json).unwrap(), + serde_json::to_string_pretty(&roundtrip_json).unwrap() + ); } }