diff --git a/src/from_wkt.rs b/src/from_wkt.rs new file mode 100644 index 0000000..166cfc0 --- /dev/null +++ b/src/from_wkt.rs @@ -0,0 +1,31 @@ +/// Create geometries from WKT. +/// +/// A default implementation exists for [geo-types](../geo-types), or you can implement this trait +/// for your own types. +pub trait TryFromWkt: Sized { + type Error; + + /// # Examples + #[cfg_attr(feature = "geo-types", doc = "```")] + #[cfg_attr(not(feature = "geo-types"), doc = "```ignore")] + /// // This example requires the geo-types feature (on by default). + /// use wkt::TryFromWkt; + /// use geo_types::Point; + /// let point: Point = Point::try_from_wkt_str("POINT(10 20)").unwrap(); + /// assert_eq!(point.y(), 20.0); + /// ``` + fn try_from_wkt_str(wkt_str: &str) -> Result; + + /// # Examples + #[cfg_attr(feature = "geo-types", doc = "```")] + #[cfg_attr(not(feature = "geo-types"), doc = "```ignore")] + /// // This example requires the geo-types feature (on by default). + /// use wkt::TryFromWkt; + /// use geo_types::Point; + /// + /// let fake_file = "POINT(10 20)".into_bytes().to_vec(); + /// let point: Point = Point::try_from_wkt_reader(&fake_file).unwrap(); + /// assert_eq!(point.y(), 20.0); + /// ``` + fn try_from_wkt_reader(wkt_reader: impl std::io::Read) -> Result; +} diff --git a/src/geo_types_from_wkt.rs b/src/geo_types_from_wkt.rs index a23d3bd..999bfb4 100644 --- a/src/geo_types_from_wkt.rs +++ b/src/geo_types_from_wkt.rs @@ -16,10 +16,11 @@ // limitations under the License. use crate::types::*; -use crate::Geometry; -use crate::Wkt; +use crate::{Geometry, TryFromWkt, Wkt, WktFloat}; use std::convert::{TryFrom, TryInto}; +use std::io::Read; +use std::str::FromStr; use geo_types::{coord, CoordFloat}; use thiserror::Error; @@ -36,6 +37,8 @@ pub enum Error { }, #[error("Wrong number of Geometries: {0}")] WrongNumberOfGeometries(usize), + #[error("Invalid WKT")] + InvalidWKT(&'static str), #[error("External error: {0}")] External(Box), } @@ -305,6 +308,47 @@ where } } +/// Macro for implementing TryFromWkt for all the geo-types. +/// Alternatively, we could try to have a kind of blanket implementation on TryFrom>, +/// but: +/// 1. what would be the type of TryFromWkt::Error? +/// 2. that would preclude ever having a specialized implementation for geo-types as they'd +/// be ambiguous/redundant. +macro_rules! impl_try_from_wkt { + ($($type: ty),*$(,)?) => { + $( + impl TryFromWkt for $type { + type Error = Error; + fn try_from_wkt_str(wkt_str: &str) -> Result { + let wkt = Wkt::from_str(wkt_str).map_err(|e| Error::InvalidWKT(e))?; + Self::try_from(wkt) + } + + fn try_from_wkt_reader(mut wkt_reader: impl Read) -> Result { + let mut bytes = vec![]; + wkt_reader.read_to_end(&mut bytes).map_err(|e| Error::External(Box::new(e)))?; + let wkt_str = String::from_utf8(bytes).map_err(|e| Error::External(Box::new(e)))?; + Self::try_from_wkt_str(&wkt_str) + } + } + )* + } +} + +// FIXME: What about GeometryCollection? +impl_try_from_wkt![ + geo_types::Geometry, + geo_types::Point, + geo_types::Line, + geo_types::LineString, + geo_types::Polygon, + geo_types::MultiPoint, + geo_types::MultiLineString, + geo_types::MultiPolygon, + geo_types::Triangle, + geo_types::Rect, +]; + #[cfg(test)] mod tests { use super::*; @@ -876,4 +920,25 @@ mod tests { w_geometrycollection.try_into().unwrap() ); } + + #[test] + fn from_invalid_wkt_str() { + let a_point_too_many = geo_types::Point::::try_from_wkt_str("PINT(1 2)"); + let err = a_point_too_many.unwrap_err(); + match err { + Error::InvalidWKT(err_text) => assert_eq!(err_text, "Invalid type encountered"), + e => panic!("Not the error we expected. Found: {}", e), + } + } + + #[test] + fn from_other_geom_wkt_str() { + let not_actually_a_line_string = + geo_types::LineString::::try_from_wkt_str("POINT(1 2)"); + let err = not_actually_a_line_string.unwrap_err(); + match err { + Error::MismatchedGeometry { .. } => {} + e => panic!("Not the error we expected. Found: {}", e), + } + } } diff --git a/src/lib.rs b/src/lib.rs index 801ae5a..4534478 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,39 +13,53 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! The `wkt` crate provides conversions to and from [`WKT`](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) primitive types. -//! See the [`types`](crate::types) module for a list of available types. +//! The `wkt` crate provides conversions to and from the [WKT (Well Known Text)](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) +//! geometry format. //! -//! Conversions (using [`std::convert::From`] and [`std::convert::TryFrom`]) to and from [`geo_types`] primitives are enabled by default, but the feature is **optional**. +//! Conversions are available via the [`TryFromWkt`] and [`ToWkt`] traits, with implementations for +//! [`geo_types`] primitives enabled by default. //! -//! Enable the `serde` feature if you need to deserialise data into custom structs containing `WKT` geometry fields. +//! For advanced usage, see the [`types`](crate::types) module for a list of internally used types. +//! +//! Enable the `serde` feature if you need to deserialise data into custom structs containing `WKT` +//! geometry fields. //! //! # Examples //! +//! ## Read `geo_types` from a WKT string +#![cfg_attr(feature = "geo-types", doc = "```")] +#![cfg_attr(not(feature = "geo-types"), doc = "```ignore")] +//! // This example requires the geo-types feature (on by default). +//! use wkt::TryFromWkt; +//! use geo_types::Point; +//! +//! let point: Point = Point::try_from_wkt_str("POINT(10 20)").unwrap(); +//! assert_eq!(point.y(), 20.0); //! ``` -//! use std::str::FromStr; -//! use wkt::Wkt; -//! let point: Wkt = Wkt::from_str("POINT(10 20)").unwrap(); -//! ``` +//! +//! ## Write `geo_types` to a WKT string #![cfg_attr(feature = "geo-types", doc = "```")] #![cfg_attr(not(feature = "geo-types"), doc = "```ignore")] -//! // Convert to a geo_types primitive from a Wkt struct //! // This example requires the geo-types feature (on by default). -//! use std::convert::TryInto; -//! use std::str::FromStr; -//! use wkt::Wkt; +//! use wkt::ToWkt; +//! use geo_types::Point; //! -//! let point: Wkt = Wkt::from_str("POINT(10 20)").unwrap(); -//! let g_point: geo_types::Point = (10., 20.).into(); -//! // We can attempt to directly convert the Wkt without having to access its items field -//! let converted: geo_types::Point = point.try_into().unwrap(); -//! assert_eq!(g_point, converted); +//! let point: Point = Point::new(1.0, 2.0); +//! assert_eq!(point.wkt_string(), "POINT(1 2)"); //! ``` //! -//! ## Direct Access to the `item` Field -//! If you wish to work directly with one of the WKT [`types`] you can match on the `item` field +//! ## Read or write your own geometry types +//! +//! Not using `geo-types` for your geometries? No problem! +//! +//! You can use [`Wkt::from_str`] to parse a WKT string into this crate's intermediate geometry +//! structure. You can use that directly, or if have your own geometry types that you'd prefer to +//! use, utilize that [`Wkt`] struct to implement the [`ToWkt`] or [`TryFromWkt`] traits for your +//! own types. +//! +//! In doing so, you'll likely want to match on one of the WKT [`types`] (Point, Linestring, etc.) +//! stored in its `item` field //! ``` -//! use std::convert::TryInto; //! use std::str::FromStr; //! use wkt::Wkt; //! use wkt::Geometry; @@ -53,7 +67,8 @@ //! let wktls: Wkt = Wkt::from_str("LINESTRING(10 20, 20 30)").unwrap(); //! let ls = match wktls.item { //! Geometry::LineString(line_string) => { -//! // you now have access to the types::LineString +//! // you now have access to the `wkt::types::LineString`. +//! assert_eq!(line_string.0[0].x, 10.0); //! } //! _ => unreachable!(), //! }; @@ -95,6 +110,9 @@ mod geo_types_to_wkt; extern crate serde; #[cfg(feature = "serde")] pub mod deserialize; +mod from_wkt; +pub use from_wkt::TryFromWkt; + #[cfg(all(feature = "serde", feature = "geo-types"))] pub use deserialize::{deserialize_geometry, deserialize_point};