diff --git a/geozero-shp/tests/reader.rs b/geozero-shp/tests/reader.rs index 55c0e122..d464477d 100644 --- a/geozero-shp/tests/reader.rs +++ b/geozero-shp/tests/reader.rs @@ -1,7 +1,7 @@ use dbase::FieldValue; use geozero::geojson::GeoJsonWriter; use geozero::wkt::WktWriter; -use geozero::{FeatureProperties, ProcessorSink}; +use geozero::{CoordDimensions, FeatureProperties, ProcessorSink}; use std::fs::File; use std::io::BufReader; use std::str::from_utf8; @@ -178,8 +178,7 @@ fn point() -> Result<(), geozero_shp::Error> { fn pointzm() -> Result<(), geozero_shp::Error> { let reader = geozero_shp::Reader::from_path("./tests/data/pointm.shp")?; let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.m = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xym()); reader.iter_geometries(&mut writer).next(); assert_eq!( from_utf8(&wkt_data).unwrap(), @@ -188,8 +187,7 @@ fn pointzm() -> Result<(), geozero_shp::Error> { let reader = geozero_shp::Reader::from_path("./tests/data/pointz.shp")?; let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.z = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyz()); reader.iter_geometries(&mut writer).next(); assert_eq!( from_utf8(&wkt_data).unwrap(), @@ -213,8 +211,7 @@ fn multipoint() -> Result<(), geozero_shp::Error> { fn multipointzm() -> Result<(), geozero_shp::Error> { let reader = geozero_shp::Reader::from_path("./tests/data/multipointz.shp")?; let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.z = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyz()); reader.iter_geometries(&mut writer).next(); assert_eq!( from_utf8(&wkt_data).unwrap(), @@ -241,9 +238,7 @@ fn line() -> Result<(), geozero_shp::Error> { fn linezm() -> Result<(), geozero_shp::Error> { let reader = geozero_shp::Reader::from_path("./tests/data/linez.shp")?; let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.z = true; - writer.dims.m = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyzm()); reader.iter_geometries(&mut writer).next(); assert_eq!( from_utf8(&wkt_data).unwrap(), @@ -252,8 +247,7 @@ fn linezm() -> Result<(), geozero_shp::Error> { let reader = geozero_shp::Reader::from_path("./tests/data/linez.shp")?; let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.z = true; // return XYZ only + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyz()); reader.iter_geometries(&mut writer).next(); assert_eq!( from_utf8(&wkt_data).unwrap(), @@ -272,8 +266,7 @@ fn linezm() -> Result<(), geozero_shp::Error> { let reader = geozero_shp::Reader::from_path("./tests/data/linem.shp")?; let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.m = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xym()); reader.iter_geometries(&mut writer).next(); assert_eq!( from_utf8(&wkt_data).unwrap(), @@ -326,9 +319,7 @@ fn polygon() -> Result<(), geozero_shp::Error> { fn polygonzm() -> Result<(), geozero_shp::Error> { let reader = geozero_shp::Reader::from_path("./tests/data/polygonz.shp")?; let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.z = true; - writer.dims.m = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyzm()); reader.iter_geometries(&mut writer).next(); assert_eq!( from_utf8(&wkt_data).unwrap(), @@ -337,8 +328,7 @@ fn polygonzm() -> Result<(), geozero_shp::Error> { let reader = geozero_shp::Reader::from_path("./tests/data/polygonm.shp")?; let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.m = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xym()); reader.iter_geometries(&mut writer).next(); assert_eq!( from_utf8(&wkt_data).unwrap(), diff --git a/geozero/src/geojson/geojson_reader.rs b/geozero/src/geojson/geojson_reader.rs index ed061213..7e8a4fd1 100644 --- a/geozero/src/geojson/geojson_reader.rs +++ b/geozero/src/geojson/geojson_reader.rs @@ -282,7 +282,7 @@ mod test { use super::*; use crate::geojson::GeoJsonWriter; use crate::wkt::WktWriter; - use crate::{ProcessToSvg, ToJson, ToWkt}; + use crate::{CoordDimensions, ProcessToSvg, ToJson, ToWkt}; use std::fs::File; #[test] @@ -306,16 +306,14 @@ mod test { fn geometries3d() -> Result<()> { let geojson = r#"{"type": "LineString", "coordinates": [[1,1,10],[2,2,20]]}"#; let mut wkt_data: Vec = Vec::new(); - let mut out = WktWriter::new(&mut wkt_data); - out.dims.z = true; + let mut out = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyz()); assert!(read_geojson_geom(&mut geojson.as_bytes(), &mut out).is_ok()); let wkt = std::str::from_utf8(&wkt_data).unwrap(); assert_eq!(wkt, "LINESTRING(1 1 10,2 2 20)"); let geojson = r#"{"type": "LineString", "coordinates": [[1,1],[2,2]]}"#; let mut wkt_data: Vec = Vec::new(); - let mut out = WktWriter::new(&mut wkt_data); - out.dims.z = true; + let mut out = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyz()); assert!(read_geojson_geom(&mut geojson.as_bytes(), &mut out).is_ok()); let wkt = std::str::from_utf8(&wkt_data).unwrap(); assert_eq!(wkt, "LINESTRING(1 1,2 2)"); diff --git a/geozero/src/geos/geos_reader.rs b/geozero/src/geos/geos_reader.rs index a30d3dfa..3760b6a6 100644 --- a/geozero/src/geos/geos_reader.rs +++ b/geozero/src/geos/geos_reader.rs @@ -215,8 +215,7 @@ mod test { let ggeom = GGeometry::new_from_wkt(wkt).unwrap(); let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.z = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyz()); assert!(process_geom(&ggeom, &mut writer).is_ok()); assert_eq!(std::str::from_utf8(&wkt_data).unwrap(), wkt); diff --git a/geozero/src/lib.rs b/geozero/src/lib.rs index bb837a6d..f8a5cbd1 100644 --- a/geozero/src/lib.rs +++ b/geozero/src/lib.rs @@ -29,7 +29,7 @@ //! | MVT | [mvt::tile::Feature] | XY | [mvt::tile::Layer] | [ToMvt] | [MvtWriter](mvt::MvtWriter) | //! | SVG | - | XY | - | [ToSvg] | [SvgWriter](svg::SvgWriter) | //! | WKB | [Wkb](wkb::Wkb), [Ewkb](wkb::Ewkb), [GpkgWkb](wkb::GpkgWkb) | XYZM | - | [ToWkb] | [WkbWriter](wkb::WkbWriter) | -//! | WKT | [wkt::WktStr], [wkt::WktString] | XYZM | [wkt::WktReader], [wkt::WktStr], [wkt::WktString] | [ToWkt] | [WktWriter](wkt::WktWriter) | +//! | WKT | [wkt::WktStr], [wkt::WktString], [wkt::EwktStr], [wkt::EwktString] | XYZM | [wkt::WktReader], [wkt::WktStr], [wkt::WktString] | [ToWkt] | [WktWriter](wkt::WktWriter) | #![warn(clippy::uninlined_format_args)] #![allow( diff --git a/geozero/src/wkb/wkb_reader.rs b/geozero/src/wkb/wkb_reader.rs index bedfa466..36ff71c3 100644 --- a/geozero/src/wkb/wkb_reader.rs +++ b/geozero/src/wkb/wkb_reader.rs @@ -45,18 +45,21 @@ impl GeozeroGeometry for GpkgWkb { /// Process WKB geometry. pub fn process_wkb_geom(raw: &mut R, processor: &mut P) -> Result<()> { let info = read_wkb_header(raw)?; + processor.srid(info.srid)?; process_wkb_geom_n(raw, &info, read_wkb_header, 0, processor) } /// Process EWKB geometry. pub fn process_ewkb_geom(raw: &mut R, processor: &mut P) -> Result<()> { let info = read_ewkb_header(raw)?; + processor.srid(info.srid)?; process_wkb_geom_n(raw, &info, read_ewkb_header, 0, processor) } /// Process GPKG geometry. pub fn process_gpkg_geom(raw: &mut R, processor: &mut P) -> Result<()> { let info = read_gpkg_header(raw)?; + processor.srid(info.srid)?; process_wkb_geom_n(raw, &info, read_wkb_header, 0, processor) } @@ -441,7 +444,7 @@ fn process_curvepolygon( mod test { use super::*; use crate::wkt::WktWriter; - use crate::ToWkt; + use crate::{CoordDimensions, ToWkt}; #[test] fn ewkb_format() { @@ -465,9 +468,7 @@ mod test { // Process all dimensions let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.z = true; - writer.dims.m = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyzm()); assert!(process_ewkb_geom(&mut ewkb.as_slice(), &mut writer).is_ok()); assert_eq!( std::str::from_utf8(&wkt_data).unwrap(), @@ -484,8 +485,7 @@ mod test { assert!(info.has_z); let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.z = true; + let mut writer = WktWriter::with_dims(&mut wkt_data, CoordDimensions::xyz()); assert!(process_ewkb_geom(&mut ewkb.as_slice(), &mut writer).is_ok()); assert_eq!( std::str::from_utf8(&wkt_data).unwrap(), @@ -595,8 +595,12 @@ mod test { fn ewkb_to_wkt(ewkb_str: &str, with_z: bool) -> String { let ewkb = hex::decode(ewkb_str).unwrap(); let mut wkt_data: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut wkt_data); - writer.dims.z = with_z; + let dims = if with_z { + CoordDimensions::xyz() + } else { + CoordDimensions::xy() + }; + let mut writer = WktWriter::with_dims(&mut wkt_data, dims); assert_eq!( process_ewkb_geom(&mut ewkb.as_slice(), &mut writer).map_err(|e| e.to_string()), Ok(()) diff --git a/geozero/src/wkt/mod.rs b/geozero/src/wkt/mod.rs index 9130c608..8b337569 100644 --- a/geozero/src/wkt/mod.rs +++ b/geozero/src/wkt/mod.rs @@ -9,25 +9,47 @@ pub use wkt_writer::*; pub(crate) mod conversion { use crate::error::Result; - use crate::wkt::WktWriter; + use crate::wkt::{WktDialect, WktWriter}; use crate::{CoordDimensions, GeozeroGeometry}; /// Convert to WKT. pub trait ToWkt { /// Convert to 2D WKT String. fn to_wkt(&self) -> Result; + /// Convert to EWKT String. + fn to_ewkt(&self, srid: Option) -> Result; /// Convert to WKT String with dimensions. fn to_wkt_ndim(&self, dims: CoordDimensions) -> Result; + /// Convert to WKT String with srid, dimensions and dialect. + fn to_wkt_with_opts( + &self, + dialect: WktDialect, + dims: CoordDimensions, + srid: Option, + ) -> Result; } impl ToWkt for T { fn to_wkt(&self) -> Result { - self.to_wkt_ndim(CoordDimensions::default()) + self.to_wkt_with_opts(WktDialect::Wkt, CoordDimensions::default(), None) } + + fn to_ewkt(&self, srid: Option) -> Result { + self.to_wkt_with_opts(WktDialect::Ewkt, CoordDimensions::xyzm(), srid) + } + fn to_wkt_ndim(&self, dims: CoordDimensions) -> Result { + self.to_wkt_with_opts(WktDialect::Wkt, dims, None) + } + + fn to_wkt_with_opts( + &self, + dialect: WktDialect, + dims: CoordDimensions, + srid: Option, + ) -> Result { let mut out: Vec = Vec::new(); - let mut writer = WktWriter::new(&mut out); - writer.dims = dims; + let mut writer = WktWriter::with_opts(&mut out, dialect, dims, srid); self.process_geom(&mut writer)?; String::from_utf8(out).map_err(|_| { crate::error::GeozeroError::Geometry("Invalid UTF-8 encoding".to_string()) @@ -40,7 +62,8 @@ pub(crate) mod conversion { mod wkb { use crate::error::Result; use crate::wkb::{FromWkb, WkbDialect}; - use crate::wkt::{WktString, WktWriter}; + use crate::wkt::{EwktString, WktDialect, WktString, WktWriter}; + use crate::CoordDimensions; use std::io::Read; impl FromWkb for WktString { @@ -54,4 +77,25 @@ mod wkb { Ok(WktString(wkt)) } } + + impl FromWkb for EwktString { + fn from_wkb(rdr: &mut R, dialect: WkbDialect) -> Result { + let mut out: Vec = Vec::new(); + let mut writer = + WktWriter::with_opts(&mut out, WktDialect::Ewkt, CoordDimensions::xyzm(), None); + crate::wkb::process_wkb_type_geom(rdr, &mut writer, dialect)?; + let wkt = String::from_utf8(out).map_err(|_| { + crate::error::GeozeroError::Geometry("Invalid UTF-8 encoding".to_string()) + })?; + Ok(EwktString(wkt)) + } + } +} + +/// WKB dialect. +#[derive(Default, PartialEq, Debug, Clone, Copy)] +pub enum WktDialect { + #[default] + Wkt, + Ewkt, } diff --git a/geozero/src/wkt/wkt_reader.rs b/geozero/src/wkt/wkt_reader.rs index da355b15..fa0bf8cb 100644 --- a/geozero/src/wkt/wkt_reader.rs +++ b/geozero/src/wkt/wkt_reader.rs @@ -30,6 +30,12 @@ impl GeozeroDatasource for WktStr<'_> { } } +/// WKT String. +#[derive(Debug)] +pub struct EwktString(pub String); + +pub struct EwktStr<'a>(pub &'a str); + /// Wkt Reader. pub struct WktReader<'a, R: Read>(pub &'a mut R); diff --git a/geozero/src/wkt/wkt_writer.rs b/geozero/src/wkt/wkt_writer.rs index f1b6a947..146b6223 100644 --- a/geozero/src/wkt/wkt_writer.rs +++ b/geozero/src/wkt/wkt_writer.rs @@ -2,19 +2,51 @@ use crate::error::Result; use crate::{CoordDimensions, FeatureProcessor, GeomProcessor, PropertyProcessor}; use std::io::Write; +use super::WktDialect; + /// WKT Writer. pub struct WktWriter<'a, W: Write> { - pub dims: CoordDimensions, + dims: CoordDimensions, + srid: Option, + dialect: WktDialect, + first_header: bool, out: &'a mut W, } impl<'a, W: Write> WktWriter<'a, W> { pub fn new(out: &'a mut W) -> WktWriter<'a, W> { + Self::with_opts(out, WktDialect::Wkt, CoordDimensions::default(), None) + } + + pub fn with_dims(out: &'a mut W, dims: CoordDimensions) -> WktWriter<'a, W> { + Self::with_opts(out, WktDialect::Wkt, dims, None) + } + + pub fn with_opts( + out: &'a mut W, + dialect: WktDialect, + dims: CoordDimensions, + srid: Option, + ) -> WktWriter<'a, W> { WktWriter { - dims: CoordDimensions::default(), + dims, + srid, + dialect, + first_header: true, out, } } + + fn header(&mut self, srid: Option) -> Result<()> { + if self.first_header && self.dialect == WktDialect::Ewkt { + self.first_header = false; + match srid { + None | Some(0) => (), + Some(srid) => self.out.write_all(format!("SRID={srid};").as_bytes())?, + } + } + Ok(()) + } fn comma(&mut self, idx: usize) -> Result<()> { if idx > 0 { self.out.write_all(b",")?; @@ -22,6 +54,7 @@ impl<'a, W: Write> WktWriter<'a, W> { Ok(()) } fn geom_begin(&mut self, idx: usize, tag: &[u8]) -> Result<()> { + self.header(self.srid)?; self.comma(idx)?; self.out.write_all(tag)?; Ok(()) @@ -46,6 +79,11 @@ impl GeomProcessor for WktWriter<'_, W> { self.dims } + fn srid(&mut self, srid: Option) -> Result<()> { + self.srid = self.srid.or(srid); + Ok(()) + } + fn xy(&mut self, x: f64, y: f64, idx: usize) -> Result<()> { self.comma(idx)?; self.out.write_all(format!("{x} {y}").as_bytes())?; @@ -176,6 +214,10 @@ impl FeatureProcessor for WktWriter<'_, W> {} #[cfg(test)] mod test { + #[cfg(feature = "with-wkb")] + use crate::wkb::{FromWkb, WkbDialect}; + #[cfg(feature = "with-wkb")] + use crate::wkt::EwktString; use crate::ToWkt; #[test] @@ -183,5 +225,15 @@ mod test { fn to_wkt() { let geom: geo_types::Geometry = geo_types::Point::new(10.0, 20.0).into(); assert_eq!(&geom.to_wkt().unwrap(), "POINT(10 20)"); + assert_eq!(&geom.to_ewkt(Some(4326)).unwrap(), "SRID=4326;POINT(10 20)"); + } + + #[test] + #[cfg(feature = "with-wkb")] + fn from_wkb() { + let blob = hex::decode("01040000A0E6100000020000000101000080000000000000244000000000000034C0000000000000594001010000800000000000000000000000000000E0BF0000000000405940").unwrap(); + let mut cursor = std::io::Cursor::new(blob); + let ewkt = EwktString::from_wkb(&mut cursor, WkbDialect::Ewkb).unwrap(); + assert_eq!(ewkt.0, "SRID=4326;MULTIPOINT(10 -20 100,0 -0.5 101)") } }