diff --git a/benches/write.rs b/benches/write.rs index 50f5356..f46c0ab 100644 --- a/benches/write.rs +++ b/benches/write.rs @@ -64,5 +64,31 @@ fn geo_write_wkt(c: &mut criterion::Criterion) { }); } -criterion_group!(benches, wkt_to_string, geo_to_wkt_string, geo_write_wkt); +fn geo_write_wkt_as_trait(c: &mut criterion::Criterion) { + c.bench_function("geo: write small wkt using trait", |bencher| { + let s = include_str!("./small.wkt"); + let w = wkt::Wkt::::from_str(s).unwrap(); + let g = geo_types::Geometry::try_from(w).unwrap(); + bencher.iter(|| { + wkt::to_wkt::write_geometry(&mut String::new(), &g).unwrap(); + }); + }); + + c.bench_function("geo: write big wkt using trait", |bencher| { + let s = include_str!("./big.wkt"); + let w = wkt::Wkt::::from_str(s).unwrap(); + let g = geo_types::Geometry::try_from(w).unwrap(); + bencher.iter(|| { + wkt::to_wkt::write_geometry(&mut String::new(), &g).unwrap(); + }); + }); +} + +criterion_group!( + benches, + wkt_to_string, + geo_to_wkt_string, + geo_write_wkt, + geo_write_wkt_as_trait +); criterion_main!(benches); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..d34ee29 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,24 @@ +use std::fmt; + +use thiserror::Error; + +/// Generic errors for WKT writing and reading +#[derive(Error, Debug)] +pub enum Error { + #[error("Only 2D input is supported when writing Rect to WKT.")] + RectUnsupportedDimension, + #[error("Only defined dimensions and undefined dimensions of 2, 3, or 4 are supported.")] + UnknownDimension, + /// Wrapper around `[std::fmt::Error]` + #[error(transparent)] + FmtError(#[from] std::fmt::Error), +} + +impl From for fmt::Error { + fn from(value: Error) -> Self { + match value { + Error::FmtError(err) => err, + _ => std::fmt::Error, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 83787f0..d0c6680 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,15 +86,18 @@ use geo_traits::{ }; use num_traits::{Float, Num, NumCast}; +use crate::to_wkt::write_geometry; use crate::tokenizer::{PeekableTokens, Token, Tokens}; use crate::types::{ Dimension, GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, }; -mod to_wkt; +pub mod to_wkt; mod tokenizer; +/// Error variant for this crate +pub mod error; /// `WKT` primitive types and collections pub mod types; @@ -105,8 +108,6 @@ pub use infer_type::infer_type; #[cfg(feature = "geo-types")] extern crate geo_types; -extern crate thiserror; - pub use crate::to_wkt::ToWkt; #[cfg(feature = "geo-types")] @@ -359,15 +360,7 @@ where T: WktNum + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - Wkt::Point(point) => point.fmt(f), - Wkt::LineString(linestring) => linestring.fmt(f), - Wkt::Polygon(polygon) => polygon.fmt(f), - Wkt::MultiPoint(multipoint) => multipoint.fmt(f), - Wkt::MultiLineString(multilinstring) => multilinstring.fmt(f), - Wkt::MultiPolygon(multipolygon) => multipolygon.fmt(f), - Wkt::GeometryCollection(geometrycollection) => geometrycollection.fmt(f), - } + Ok(write_geometry(f, self)?) } } diff --git a/src/to_wkt/geo_trait_impl.rs b/src/to_wkt/geo_trait_impl.rs new file mode 100644 index 0000000..b6ec9a1 --- /dev/null +++ b/src/to_wkt/geo_trait_impl.rs @@ -0,0 +1,480 @@ +use std::fmt; +use std::fmt::Write; + +use geo_traits::{ + CoordTrait, GeometryCollectionTrait, GeometryTrait, LineStringTrait, LineTrait, + MultiLineStringTrait, MultiPointTrait, MultiPolygonTrait, PointTrait, PolygonTrait, RectTrait, + TriangleTrait, +}; + +use crate::error::Error; +use crate::types::Coord; +use crate::WktNum; + +/// The physical size of the coordinate dimension +/// +/// This is used so that we don't have to call `.dim()` on **every** coordinate. We infer it once +/// from the `geo_traits::Dimensions` and then pass it to each coordinate. +#[derive(Clone, Copy)] +enum PhysicalCoordinateDimension { + Two, + Three, + Four, +} + +impl From for PhysicalCoordinateDimension { + fn from(value: geo_traits::Dimensions) -> Self { + match value.size() { + 2 => Self::Two, + 3 => Self::Three, + 4 => Self::Four, + size => panic!("Unexpected dimension for coordinate: {}", size), + } + } +} + +pub fn write_point>( + f: &mut impl Write, + g: &G, +) -> Result<(), Error> { + let dim = g.dim(); + // Write prefix + match dim { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => f.write_str("POINT"), + geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => f.write_str("POINT Z"), + geo_traits::Dimensions::Xym => f.write_str("POINT M"), + geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => { + f.write_str("POINT ZM") + } + geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), + }?; + let size = PhysicalCoordinateDimension::from(dim); + if let Some(coord) = g.coord() { + f.write_char('(')?; + write_coord(f, &coord, size)?; + f.write_char(')')?; + Ok(()) + } else { + Ok(f.write_str(" EMPTY")?) + } +} + +pub fn write_linestring>( + f: &mut impl Write, + linestring: &G, +) -> Result<(), Error> { + let dim = linestring.dim(); + // Write prefix + match dim { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => { + f.write_str("LINESTRING") + } + geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => { + f.write_str("LINESTRING Z") + } + geo_traits::Dimensions::Xym => f.write_str("LINESTRING M"), + geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => { + f.write_str("LINESTRING ZM") + } + geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), + }?; + let size = PhysicalCoordinateDimension::from(dim); + if linestring.num_coords() == 0 { + Ok(f.write_str(" EMPTY")?) + } else { + write_coord_sequence(f, linestring.coords(), size) + } +} + +pub fn write_polygon>( + f: &mut impl Write, + polygon: &G, +) -> Result<(), Error> { + let dim = polygon.dim(); + // Write prefix + match dim { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => f.write_str("POLYGON"), + geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => { + f.write_str("POLYGON Z") + } + geo_traits::Dimensions::Xym => f.write_str("POLYGON M"), + geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => { + f.write_str("POLYGON ZM") + } + geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), + }?; + let size = PhysicalCoordinateDimension::from(dim); + if let Some(exterior) = polygon.exterior() { + if exterior.num_coords() != 0 { + f.write_str("(")?; + write_coord_sequence(f, exterior.coords(), size)?; + + for interior in polygon.interiors() { + f.write_char(',')?; + write_coord_sequence(f, interior.coords(), size)?; + } + + Ok(f.write_char(')')?) + } else { + Ok(f.write_str(" EMPTY")?) + } + } else { + Ok(f.write_str(" EMPTY")?) + } +} + +pub fn write_multi_point>( + f: &mut impl Write, + multipoint: &G, +) -> Result<(), Error> { + let dim = multipoint.dim(); + // Write prefix + match dim { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => { + f.write_str("MULTIPOINT") + } + geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => { + f.write_str("MULTIPOINT Z") + } + geo_traits::Dimensions::Xym => f.write_str("MULTIPOINT M"), + geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => { + f.write_str("MULTIPOINT ZM") + } + geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), + }?; + let size = PhysicalCoordinateDimension::from(dim); + + let mut points = multipoint.points(); + + // Note: This is largely copied from `write_coord_sequence`, because `multipoint.points()` + // yields a sequence of Point, not Coord. + if let Some(first_point) = points.next() { + f.write_str("((")?; + + // Assume no empty points within this MultiPoint + write_coord(f, &first_point.coord().unwrap(), size)?; + + for point in points { + f.write_str("),(")?; + write_coord(f, &point.coord().unwrap(), size)?; + } + + f.write_str("))")?; + } else { + f.write_str(" EMPTY")?; + } + + Ok(()) +} + +pub fn write_multi_linestring>( + f: &mut impl Write, + multilinestring: &G, +) -> Result<(), Error> { + let dim = multilinestring.dim(); + // Write prefix + match dim { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => { + f.write_str("MULTILINESTRING") + } + geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => { + f.write_str("MULTILINESTRING Z") + } + geo_traits::Dimensions::Xym => f.write_str("MULTILINESTRING M"), + geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => { + f.write_str("MULTILINESTRING ZM") + } + geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), + }?; + let size = PhysicalCoordinateDimension::from(dim); + let mut line_strings = multilinestring.line_strings(); + if let Some(first_linestring) = line_strings.next() { + f.write_str("(")?; + write_coord_sequence(f, first_linestring.coords(), size)?; + + for linestring in line_strings { + f.write_char(',')?; + write_coord_sequence(f, linestring.coords(), size)?; + } + + f.write_char(')')?; + } else { + f.write_str(" EMPTY")?; + }; + + Ok(()) +} + +pub fn write_multi_polygon>( + f: &mut impl Write, + multipolygon: &G, +) -> Result<(), Error> { + let dim = multipolygon.dim(); + // Write prefix + match dim { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => { + f.write_str("MULTIPOLYGON") + } + geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => { + f.write_str("MULTIPOLYGON Z") + } + geo_traits::Dimensions::Xym => f.write_str("MULTIPOLYGON M"), + geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => { + f.write_str("MULTIPOLYGON ZM") + } + geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), + }?; + let size = PhysicalCoordinateDimension::from(dim); + + let mut polygons = multipolygon.polygons(); + + if let Some(first_polygon) = polygons.next() { + f.write_str("((")?; + + write_coord_sequence(f, first_polygon.exterior().unwrap().coords(), size)?; + for interior in first_polygon.interiors() { + f.write_char(',')?; + write_coord_sequence(f, interior.coords(), size)?; + } + + for polygon in polygons { + f.write_str("),(")?; + + write_coord_sequence(f, polygon.exterior().unwrap().coords(), size)?; + for interior in polygon.interiors() { + f.write_char(',')?; + write_coord_sequence(f, interior.coords(), size)?; + } + } + + f.write_str("))")?; + } else { + f.write_str(" EMPTY")?; + }; + + Ok(()) +} + +/// Create geometry to WKT representation. + +pub fn write_geometry>( + f: &mut impl Write, + geometry: &G, +) -> Result<(), Error> { + match geometry.as_type() { + geo_traits::GeometryType::Point(point) => write_point(f, point), + geo_traits::GeometryType::LineString(linestring) => write_linestring(f, linestring), + geo_traits::GeometryType::Polygon(polygon) => write_polygon(f, polygon), + geo_traits::GeometryType::MultiPoint(multi_point) => write_multi_point(f, multi_point), + geo_traits::GeometryType::MultiLineString(mls) => write_multi_linestring(f, mls), + geo_traits::GeometryType::MultiPolygon(multi_polygon) => { + write_multi_polygon(f, multi_polygon) + } + geo_traits::GeometryType::GeometryCollection(gc) => write_geometry_collection(f, gc), + geo_traits::GeometryType::Rect(rect) => write_rect(f, rect), + geo_traits::GeometryType::Triangle(triangle) => write_triangle(f, triangle), + geo_traits::GeometryType::Line(line) => write_line(f, line), + } +} + +pub fn write_geometry_collection>( + f: &mut impl Write, + gc: &G, +) -> Result<(), Error> { + let dim = gc.dim(); + // Write prefix + match dim { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => { + f.write_str("GEOMETRYCOLLECTION") + } + geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => { + f.write_str("GEOMETRYCOLLECTION Z") + } + geo_traits::Dimensions::Xym => f.write_str("GEOMETRYCOLLECTION M"), + geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => { + f.write_str("GEOMETRYCOLLECTION ZM") + } + geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), + }?; + let mut geometries = gc.geometries(); + + if let Some(first_geometry) = geometries.next() { + f.write_str("(")?; + + write_geometry(f, &first_geometry)?; + for geom in geometries { + f.write_char(',')?; + write_geometry(f, &geom)?; + } + + f.write_char(')')?; + } else { + f.write_str(" EMPTY")?; + } + Ok(()) +} + +pub fn write_rect>( + f: &mut impl Write, + rect: &G, +) -> Result<(), Error> { + // Write prefix and error if not 2D + match rect.dim() { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => f.write_str("POLYGON"), + _ => return Err(Error::RectUnsupportedDimension), + }?; + + let min_coord = rect.min(); + let max_coord = rect.max(); + + // We need to construct the five points of the rect that make up the exterior Polygon + let coords = [ + Coord { + x: min_coord.x(), + y: min_coord.y(), + z: None, + m: None, + }, + Coord { + x: min_coord.x(), + y: max_coord.y(), + z: None, + m: None, + }, + Coord { + x: max_coord.x(), + y: max_coord.y(), + z: None, + m: None, + }, + Coord { + x: max_coord.x(), + y: min_coord.y(), + z: None, + m: None, + }, + Coord { + x: min_coord.x(), + y: min_coord.y(), + z: None, + m: None, + }, + ]; + + f.write_str("(")?; + write_coord_sequence(f, coords.iter(), PhysicalCoordinateDimension::Two)?; + Ok(f.write_char(')')?) +} + +pub fn write_triangle>( + f: &mut impl Write, + triangle: &G, +) -> Result<(), Error> { + let dim = triangle.dim(); + // Write prefix + match dim { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => f.write_str("POLYGON"), + geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => { + f.write_str("POLYGON Z") + } + geo_traits::Dimensions::Xym => f.write_str("POLYGON M"), + geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => { + f.write_str("POLYGON ZM") + } + geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), + }?; + let size = PhysicalCoordinateDimension::from(dim); + f.write_str("(")?; + + let coords_iter = triangle + .coords() + .into_iter() + .chain(std::iter::once(triangle.first())); + write_coord_sequence(f, coords_iter, size)?; + + Ok(f.write_char(')')?) +} + +pub fn write_line>( + f: &mut impl Write, + line: &G, +) -> Result<(), Error> { + let dim = line.dim(); + // Write prefix + match dim { + geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => { + f.write_str("LINESTRING") + } + geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => { + f.write_str("LINESTRING Z") + } + geo_traits::Dimensions::Xym => f.write_str("LINESTRING M"), + geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => { + f.write_str("LINESTRING ZM") + } + geo_traits::Dimensions::Unknown(_) => return Err(Error::UnknownDimension), + }?; + let size = PhysicalCoordinateDimension::from(dim); + write_coord_sequence(f, line.coords().into_iter(), size) +} + +/// Write a single coordinate to the writer. +/// +/// Will not include any start or end `()` characters. +fn write_coord>( + f: &mut impl Write, + coord: &G, + size: PhysicalCoordinateDimension, +) -> Result<(), std::fmt::Error> { + match size { + PhysicalCoordinateDimension::Two => write!(f, "{} {}", coord.x(), coord.y()), + PhysicalCoordinateDimension::Three => { + // Safety: + // We've validated that there are three dimensions + write!(f, "{} {} {}", coord.x(), coord.y(), unsafe { + coord.nth_unchecked(2) + }) + } + PhysicalCoordinateDimension::Four => { + write!( + f, + "{} {} {} {}", + coord.x(), + coord.y(), + // Safety: + // We've validated that there are four dimensions + unsafe { coord.nth_unchecked(2) }, + // Safety: + // We've validated that there are four dimensions + unsafe { coord.nth_unchecked(3) } + ) + } + } +} + +/// Includes the `()` characters to start and end this sequence. +/// +/// E.g. it will write: +/// ```notest +/// (1 2, 3 4, 5 6) +/// ``` +/// for a coordinate sequence with three coordinates. +fn write_coord_sequence>( + f: &mut impl Write, + mut coords: impl Iterator, + size: PhysicalCoordinateDimension, +) -> Result<(), Error> { + f.write_char('(')?; + + if let Some(first_coord) = coords.next() { + write_coord(f, &first_coord, size)?; + + for coord in coords { + f.write_char(',')?; + write_coord(f, &coord, size)?; + } + } + + f.write_char(')')?; + Ok(()) +} diff --git a/src/to_wkt.rs b/src/to_wkt/mod.rs similarity index 87% rename from src/to_wkt.rs rename to src/to_wkt/mod.rs index 8219d5c..3581c8b 100644 --- a/src/to_wkt.rs +++ b/src/to_wkt/mod.rs @@ -1,5 +1,13 @@ use crate::{Wkt, WktNum}; +mod geo_trait_impl; + +pub use geo_trait_impl::{ + write_geometry, write_geometry_collection, write_line, write_linestring, + write_multi_linestring, write_multi_point, write_multi_polygon, write_point, write_polygon, + write_rect, write_triangle, +}; + /// A trait for converting values to WKT pub trait ToWkt where diff --git a/src/types/coord.rs b/src/types/coord.rs index 887cdb7..e1033cf 100644 --- a/src/types/coord.rs +++ b/src/types/coord.rs @@ -17,7 +17,6 @@ use geo_traits::CoordTrait; use crate::tokenizer::{PeekableTokens, Token}; use crate::types::Dimension; use crate::{FromTokens, WktNum}; -use std::fmt; use std::str::FromStr; #[derive(Clone, Debug, Default, PartialEq)] @@ -31,22 +30,6 @@ where pub m: Option, } -impl fmt::Display for Coord -where - T: WktNum + fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "{} {}", self.x, self.y)?; - if let Some(z) = self.z { - write!(f, " {}", z)?; - } - if let Some(m) = self.m { - write!(f, " {}", m)?; - } - Ok(()) - } -} - impl FromTokens for Coord where T: WktNum + FromStr + Default, @@ -191,56 +174,3 @@ impl CoordTrait for &Coord { } } } - -#[cfg(test)] -mod tests { - use super::Coord; - - #[test] - fn write_2d_coord() { - let coord = Coord { - x: 10.1, - y: 20.2, - z: None, - m: None, - }; - - assert_eq!("10.1 20.2", format!("{}", coord)); - } - - #[test] - fn write_3d_coord() { - let coord = Coord { - x: 10.1, - y: 20.2, - z: Some(-30.3), - m: None, - }; - - assert_eq!("10.1 20.2 -30.3", format!("{}", coord)); - } - - #[test] - fn write_2d_coord_with_linear_referencing_system() { - let coord = Coord { - x: 10.1, - y: 20.2, - z: None, - m: Some(10.), - }; - - assert_eq!("10.1 20.2 10", format!("{}", coord)); - } - - #[test] - fn write_3d_coord_with_linear_referencing_system() { - let coord = Coord { - x: 10.1, - y: 20.2, - z: Some(-30.3), - m: Some(10.), - }; - - assert_eq!("10.1 20.2 -30.3 10", format!("{}", coord)); - } -} diff --git a/src/types/geometrycollection.rs b/src/types/geometrycollection.rs index 559fd8f..04e3606 100644 --- a/src/types/geometrycollection.rs +++ b/src/types/geometrycollection.rs @@ -14,6 +14,7 @@ use geo_traits::{GeometryCollectionTrait, GeometryTrait}; +use crate::to_wkt::write_geometry_collection; use crate::tokenizer::{PeekableTokens, Token}; use crate::types::Dimension; use crate::{FromTokens, Wkt, WktNum}; @@ -37,18 +38,7 @@ where T: WktNum + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - if self.0.is_empty() { - f.write_str("GEOMETRYCOLLECTION EMPTY") - } else { - let strings = self - .0 - .iter() - .map(|geometry| format!("{}", geometry)) - .collect::>() - .join(","); - - write!(f, "GEOMETRYCOLLECTION({})", strings) - } + Ok(write_geometry_collection(f, self)?) } } diff --git a/src/types/linestring.rs b/src/types/linestring.rs index 6465de4..248771e 100644 --- a/src/types/linestring.rs +++ b/src/types/linestring.rs @@ -14,6 +14,7 @@ use geo_traits::{CoordTrait, LineStringTrait}; +use crate::to_wkt::write_linestring; use crate::tokenizer::PeekableTokens; use crate::types::coord::Coord; use crate::types::Dimension; @@ -48,18 +49,7 @@ where T: WktNum + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - if self.0.is_empty() { - f.write_str("LINESTRING EMPTY") - } else { - let strings = self - .0 - .iter() - .map(|c| format!("{}", c)) - .collect::>() - .join(","); - - write!(f, "LINESTRING({})", strings) - } + Ok(write_linestring(f, self)?) } } diff --git a/src/types/multilinestring.rs b/src/types/multilinestring.rs index 9df1c7f..bad491c 100644 --- a/src/types/multilinestring.rs +++ b/src/types/multilinestring.rs @@ -14,6 +14,7 @@ use geo_traits::{LineStringTrait, MultiLineStringTrait}; +use crate::to_wkt::write_multi_linestring; use crate::tokenizer::PeekableTokens; use crate::types::linestring::LineString; use crate::types::Dimension; @@ -38,23 +39,7 @@ where T: WktNum + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - if self.0.is_empty() { - f.write_str("MULTILINESTRING EMPTY") - } else { - let strings = self - .0 - .iter() - .map(|l| { - l.0.iter() - .map(|c| format!("{} {}", c.x, c.y)) - .collect::>() - .join(",") - }) - .collect::>() - .join("),("); - - write!(f, "MULTILINESTRING(({}))", strings) - } + Ok(write_multi_linestring(f, self)?) } } diff --git a/src/types/multipoint.rs b/src/types/multipoint.rs index bde8613..b189afa 100644 --- a/src/types/multipoint.rs +++ b/src/types/multipoint.rs @@ -14,6 +14,7 @@ use geo_traits::{MultiPointTrait, PointTrait}; +use crate::to_wkt::write_multi_point; use crate::tokenizer::PeekableTokens; use crate::types::point::Point; use crate::types::Dimension; @@ -38,19 +39,7 @@ where T: WktNum + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - if self.0.is_empty() { - f.write_str("MULTIPOINT EMPTY") - } else { - let strings = self - .0 - .iter() - .filter_map(|p| p.0.as_ref()) - .map(|c| format!("({} {})", c.x, c.y)) - .collect::>() - .join(","); - - write!(f, "MULTIPOINT({})", strings) - } + Ok(write_multi_point(f, self)?) } } diff --git a/src/types/multipolygon.rs b/src/types/multipolygon.rs index c9f98ff..e9e2619 100644 --- a/src/types/multipolygon.rs +++ b/src/types/multipolygon.rs @@ -14,6 +14,7 @@ use geo_traits::{MultiPolygonTrait, PolygonTrait}; +use crate::to_wkt::write_multi_polygon; use crate::tokenizer::PeekableTokens; use crate::types::polygon::Polygon; use crate::types::Dimension; @@ -38,28 +39,7 @@ where T: WktNum + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - if self.0.is_empty() { - f.write_str("MULTIPOLYGON EMPTY") - } else { - let strings = self - .0 - .iter() - .map(|p| { - p.0.iter() - .map(|l| { - l.0.iter() - .map(|c| format!("{} {}", c.x, c.y)) - .collect::>() - .join(",") - }) - .collect::>() - .join("),(") - }) - .collect::>() - .join(")),(("); - - write!(f, "MULTIPOLYGON((({})))", strings) - } + Ok(write_multi_polygon(f, self)?) } } diff --git a/src/types/point.rs b/src/types/point.rs index 0d2e5ab..b610612 100644 --- a/src/types/point.rs +++ b/src/types/point.rs @@ -14,6 +14,7 @@ use geo_traits::{CoordTrait, PointTrait}; +use crate::to_wkt::write_point; use crate::tokenizer::PeekableTokens; use crate::types::coord::Coord; use crate::types::Dimension; @@ -38,23 +39,7 @@ where T: WktNum + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self.0 { - Some(ref coord) => { - let mut lrs = String::new(); - if coord.z.is_some() { - lrs += "Z"; - } - if coord.m.is_some() { - lrs += "M"; - } - if !lrs.is_empty() { - lrs = " ".to_string() + &lrs; - } - - write!(f, "POINT{}({})", lrs, coord) - } - None => f.write_str("POINT EMPTY"), - } + Ok(write_point(f, self)?) } } diff --git a/src/types/polygon.rs b/src/types/polygon.rs index dfedf4b..b55a3ec 100644 --- a/src/types/polygon.rs +++ b/src/types/polygon.rs @@ -14,6 +14,7 @@ use geo_traits::{LineStringTrait, PolygonTrait}; +use crate::to_wkt::write_polygon; use crate::tokenizer::PeekableTokens; use crate::types::linestring::LineString; use crate::types::Dimension; @@ -38,23 +39,7 @@ where T: WktNum + fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - if self.0.is_empty() { - f.write_str("POLYGON EMPTY") - } else { - let strings = self - .0 - .iter() - .map(|l| { - l.0.iter() - .map(|c| format!("{} {}", c.x, c.y)) - .collect::>() - .join(",") - }) - .collect::>() - .join("),("); - - write!(f, "POLYGON(({}))", strings) - } + Ok(write_polygon(f, self)?) } }