Skip to content

Commit

Permalink
FromWkt trait for reading WKT without exposing the user to the interm…
Browse files Browse the repository at this point in the history
…ediate representation.

Note that this isn't a performance change. It's about (hopefully) making
the library easier to use.

This is a corollary to #89, but for
reading WKT, rather than writing. As we discussed there, probably there
is no reason for the user to care about the Wkt struct.

Note that the intermediate representation is still used (for now!), but
the user is no longer required to interact with it.

The road is open though for having a direct translation from Wkt text to
the geo-types (or whatever) represenation (see geozero for inspiration).
  • Loading branch information
michaelkirk committed Apr 28, 2022
1 parent 4576ece commit ed53073
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 32 deletions.
19 changes: 12 additions & 7 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
### Added
* impl `std::fmt::Display` for `Wkt`.
* <https://github.com/georust/wkt/pull/88>
* added `wkt_string` and `write_wkt` methods to `ToWkt` trait
* <https://github.com/georust/wkt/pull/89>
* impl `ToWkt` on geo_type Geometry variants directly, so you can `point!(x: 1., y: 2.).wkt_string()`
* <https://github.com/georust/wkt/pull/90>
* `ToWkt` is no longer tied to geo-types. You can implement it on your own
custom (non-geo_type) geometry types.
* <https://github.com/georust/wkt/pull/90>
* Additions to ToWkt trait:
* added `wkt_string` and `write_wkt` methods to `ToWkt` trait
* <https://github.com/georust/wkt/pull/89>
* impl `ToWkt` on geo_type Geometry variants directly, so you can `point!(x: 1., y: 2.).wkt_string()`
* <https://github.com/georust/wkt/pull/90>
* `ToWkt` is no longer tied to geo-types. You can implement it on your own
custom (non-geo_type) geometry types.
* <https://github.com/georust/wkt/pull/90>
* New `FromWkt` trait allows a way to convert from a string or reader directly
to geo-types, without exposing you to the intermediate `Wkt` structs.
* <https://github.com/georust/wkt/pull/95>


## 0.10.0 - 2022-02-24
### Changed
Expand Down
5 changes: 3 additions & 2 deletions benches/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extern crate criterion;
extern crate wkt;

use std::str::FromStr;
use wkt::TryFromWkt;

fn bench_parse(c: &mut criterion::Criterion) {
c.bench_function("parse small", |bencher| {
Expand All @@ -24,14 +25,14 @@ fn bench_parse_to_geo(c: &mut criterion::Criterion) {
c.bench_function("parse small to geo", |bencher| {
let s = include_str!("./small.wkt");
bencher.iter(|| {
let _ = geo_types::Geometry::try_from(wkt::Wkt::<f64>::from_str(s).unwrap());
let _ = geo_types::Geometry::<f64>::try_from_wkt_str(s).unwrap();
});
});

c.bench_function("parse big to geo", |bencher| {
let s = include_str!("./big.wkt");
bencher.iter(|| {
let _ = geo_types::Geometry::try_from(wkt::Wkt::<f64>::from_str(s).unwrap());
let _ = geo_types::Geometry::<f64>::try_from_wkt_str(s).unwrap();
});
});
}
Expand Down
31 changes: 31 additions & 0 deletions src/from_wkt.rs
Original file line number Diff line number Diff line change
@@ -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<T>: 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<f64> = Point::try_from_wkt_str("POINT(10 20)").unwrap();
/// assert_eq!(point.y(), 20.0);
/// ```
fn try_from_wkt_str(wkt_str: &str) -> Result<Self, Self::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 fake_file = "POINT(10 20)".as_bytes().to_vec();
/// let point: Point<f64> = 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<Self, Self::Error>;
}
69 changes: 67 additions & 2 deletions src/geo_types_from_wkt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<dyn std::error::Error>),
}
Expand Down Expand Up @@ -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<Wkt<T>>,
/// 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! try_from_wkt_impl {
($($type: ty),*$(,)?) => {
$(
impl<T: WktFloat + FromStr + Default> TryFromWkt<T> for $type {
type Error = Error;
fn try_from_wkt_str(wkt_str: &str) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
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?
try_from_wkt_impl![
geo_types::Geometry<T>,
geo_types::Point<T>,
geo_types::Line<T>,
geo_types::LineString<T>,
geo_types::Polygon<T>,
geo_types::MultiPoint<T>,
geo_types::MultiLineString<T>,
geo_types::MultiPolygon<T>,
geo_types::Triangle<T>,
geo_types::Rect<T>,
];

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -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::<f64>::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::<f64>::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),
}
}
}
60 changes: 39 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,47 +13,62 @@
// 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<f64> = 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<f64> = 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<f64> = Wkt::from_str("POINT(10 20)").unwrap();
//! let g_point: geo_types::Point<f64> = (10., 20.).into();
//! // We can attempt to directly convert the Wkt without having to access its items field
//! let converted: geo_types::Point<f64> = point.try_into().unwrap();
//! assert_eq!(g_point, converted);
//! let point: Point<f64> = 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;
//!
//! let wktls: Wkt<f64> = 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!(),
//! };
Expand Down Expand Up @@ -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};

Expand Down

0 comments on commit ed53073

Please sign in to comment.