diff --git a/CHANGES.md b/CHANGES.md index 74accfc..5813dc1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## 0.12.0 - 2024-11-26 + +* Writing WKT is now up to 50% faster by avoiding extra allocations and writing directly to an underlying buffer. +* Any `geo_traits` input is now supported when writing to WKT. +* Implements `geo_traits` on `Wkt` and all structs in `types`. This allows for easier interoperability when reading WKT data to other representations than `geo-types`. +* BREAKING: removed the `fmt::Display` impl on `wkt::Coord`. + ## 0.11.1 - 2024-10-10 * Add an `infer_type` function to extract only the geometry type and dimension from a WKT string. diff --git a/Cargo.toml b/Cargo.toml index 1c040de..3d17f49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "wkt" description = "Rust read/write support for well-known text (WKT)" -version = "0.11.1" +version = "0.12.0" license = "MIT OR Apache-2.0" repository = "https://github.com/georust/wkt" autobenches = true diff --git a/src/lib.rs b/src/lib.rs index d0c6680..717b0dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ //! geometry format. //! //! Conversions are available via the [`TryFromWkt`] and [`ToWkt`] traits, with implementations for -//! [`geo_types`] primitives enabled by default. +//! [`geo_types`] and [`geo_traits`] primitives enabled by default. //! //! For advanced usage, see the [`types`](crate::types) module for a list of internally used types. //! @@ -57,25 +57,44 @@ //! //! Not using `geo-types` for your geometries? No problem! //! +//! As of `wkt` version 0.12, this crate provides read and write integration with [`geo_traits`], +//! a collection of geometry access traits, to provide zero-copy integration with geometry +//! representations other than `geo-types`. +//! +//! This integration allows you to transparently read data from this crate's intermediate geometry +//! structure, and it allows you to write WKT strings directly from your geometry without any +//! intermediate representation. +//! +//! ### Reading +//! //! 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. +//! structure. `Wkt` (and all structs defined in [types]) implement traits from [geo_traits]. You +//! can write functions in terms of those traits and you'll be able to work with the parsed WKT +//! without any further overhead. //! -//! 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::str::FromStr; //! use wkt::Wkt; +//! use geo_traits::{GeometryTrait, GeometryType}; +//! +//! fn is_line_string(geom: &impl GeometryTrait) { +//! assert!(matches!(geom.as_type(), GeometryType::LineString(_))) +//! } //! //! let wktls: Wkt = Wkt::from_str("LINESTRING(10 20, 20 30)").unwrap(); -//! let ls = match wktls { -//! Wkt::LineString(line_string) => { -//! // you now have access to the `wkt::types::LineString`. -//! assert_eq!(line_string.0[0].x, 10.0); -//! } -//! _ => unreachable!(), -//! }; +//! is_line_string(&wktls); +//! ``` +//! +//! Working with the trait definition is preferable to working with `wkt::Wkt` directly, as the +//! geometry trait will work with many different geometry representations; not just the one from +//! this crate. +//! +//! ### Writing +//! +//! Consult the functions provided in [`to_wkt`]. Those functions will write any `geo_traits` object to WKT without any intermediate overhead. +//! +//! Implement [`geo_traits`] on your own geometry representation and those functions will work out +//! of the box on your data. use std::default::Default; use std::fmt; use std::str::FromStr; diff --git a/src/to_wkt/geo_trait_impl.rs b/src/to_wkt/geo_trait_impl.rs index 7190a5e..7af00d0 100644 --- a/src/to_wkt/geo_trait_impl.rs +++ b/src/to_wkt/geo_trait_impl.rs @@ -22,17 +22,20 @@ enum PhysicalCoordinateDimension { Four, } -impl From for PhysicalCoordinateDimension { - fn from(value: geo_traits::Dimensions) -> Self { +impl TryFrom for PhysicalCoordinateDimension { + type Error = Error; + + fn try_from(value: geo_traits::Dimensions) -> Result { match value.size() { - 2 => Self::Two, - 3 => Self::Three, - 4 => Self::Four, - size => panic!("Unexpected dimension for coordinate: {}", size), + 2 => Ok(Self::Two), + 3 => Ok(Self::Three), + 4 => Ok(Self::Four), + _ => Err(Error::UnknownDimension), } } } +/// Write an object implementing [`PointTrait`] to a WKT string. pub fn write_point( f: &mut impl Write, g: &impl PointTrait, @@ -48,7 +51,7 @@ pub fn write_point( } geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), }?; - let size = PhysicalCoordinateDimension::from(dim); + let size = dim.try_into()?; if let Some(coord) = g.coord() { f.write_char('(')?; write_coord(f, &coord, size)?; @@ -59,6 +62,7 @@ pub fn write_point( } } +/// Write an object implementing [`LineStringTrait`] to a WKT string. pub fn write_linestring( f: &mut impl Write, linestring: &impl LineStringTrait, @@ -78,7 +82,7 @@ pub fn write_linestring( } geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), }?; - let size = PhysicalCoordinateDimension::from(dim); + let size = dim.try_into()?; if linestring.num_coords() == 0 { Ok(f.write_str(" EMPTY")?) } else { @@ -86,6 +90,7 @@ pub fn write_linestring( } } +/// Write an object implementing [`PolygonTrait`] to a WKT string. pub fn write_polygon( f: &mut impl Write, polygon: &impl PolygonTrait, @@ -103,7 +108,7 @@ pub fn write_polygon( } geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), }?; - let size = PhysicalCoordinateDimension::from(dim); + let size = dim.try_into()?; if let Some(exterior) = polygon.exterior() { if exterior.num_coords() != 0 { f.write_str("(")?; @@ -123,6 +128,7 @@ pub fn write_polygon( } } +/// Write an object implementing [`MultiPointTrait`] to a WKT string. pub fn write_multi_point( f: &mut impl Write, multipoint: &impl MultiPointTrait, @@ -142,7 +148,7 @@ pub fn write_multi_point( } geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), }?; - let size = PhysicalCoordinateDimension::from(dim); + let size = dim.try_into()?; let mut points = multipoint.points(); @@ -167,6 +173,7 @@ pub fn write_multi_point( Ok(()) } +/// Write an object implementing [`MultiLineStringTrait`] to a WKT string. pub fn write_multi_linestring( f: &mut impl Write, multilinestring: &impl MultiLineStringTrait, @@ -186,7 +193,7 @@ pub fn write_multi_linestring( } geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), }?; - let size = PhysicalCoordinateDimension::from(dim); + let size = dim.try_into()?; let mut line_strings = multilinestring.line_strings(); if let Some(first_linestring) = line_strings.next() { f.write_str("(")?; @@ -205,6 +212,7 @@ pub fn write_multi_linestring( Ok(()) } +/// Write an object implementing [`MultiPolygonTrait`] to a WKT string. pub fn write_multi_polygon( f: &mut impl Write, multipolygon: &impl MultiPolygonTrait, @@ -224,7 +232,7 @@ pub fn write_multi_polygon( } geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), }?; - let size = PhysicalCoordinateDimension::from(dim); + let size = dim.try_into()?; let mut polygons = multipolygon.polygons(); @@ -255,8 +263,7 @@ pub fn write_multi_polygon( Ok(()) } -/// Create geometry to WKT representation. - +/// Write an object implementing [`GeometryTrait`] to a WKT string. pub fn write_geometry( f: &mut impl Write, geometry: &impl GeometryTrait, @@ -277,6 +284,7 @@ pub fn write_geometry( } } +/// Write an object implementing [`GeometryCollectionTrait`] to a WKT string. pub fn write_geometry_collection( f: &mut impl Write, gc: &impl GeometryCollectionTrait, @@ -314,6 +322,13 @@ pub fn write_geometry_collection( Ok(()) } +/// Write an object implementing [`RectTrait`] to a WKT string. +/// +/// The Rect will written as a Polygon with one exterior ring. +/// +/// Note that only 2D `Rect`s are supported, because it's unclear how to map a higher-dimensional +/// Rect to a Polygon. For higher dimensional `Rect`, transform your data to a Polygon and use +/// [`write_polygon`]. pub fn write_rect( f: &mut impl Write, rect: &impl RectTrait, @@ -366,6 +381,9 @@ pub fn write_rect( Ok(f.write_char(')')?) } +/// Write an object implementing [`TriangleTrait`] to a WKT string. +/// +/// The Triangle will written as a Polygon with one exterior ring. pub fn write_triangle( f: &mut impl Write, triangle: &impl TriangleTrait, @@ -383,7 +401,7 @@ pub fn write_triangle( } geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), }?; - let size = PhysicalCoordinateDimension::from(dim); + let size = dim.try_into()?; f.write_str("(")?; let coords_iter = triangle @@ -395,6 +413,9 @@ pub fn write_triangle( Ok(f.write_char(')')?) } +/// Write an object implementing [`LineTrait`] to a WKT string. +/// +/// The Line will written as a LineString with two coordinates. pub fn write_line( f: &mut impl Write, line: &impl LineTrait, @@ -414,7 +435,7 @@ pub fn write_line( } geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), }?; - let size = PhysicalCoordinateDimension::from(dim); + let size = dim.try_into()?; write_coord_sequence(f, line.coords().into_iter(), size) } diff --git a/src/to_wkt/mod.rs b/src/to_wkt/mod.rs index 7e3e5b6..5938757 100644 --- a/src/to_wkt/mod.rs +++ b/src/to_wkt/mod.rs @@ -1,3 +1,5 @@ +//! Serialize geometries to WKT strings. + use crate::{Wkt, WktNum}; mod geo_trait_impl;