diff --git a/Cargo.toml b/Cargo.toml index 7c4fdf4..1c040de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ edition = "2021" [dependencies] geo-types = { version = "0.7.8", optional = true } +geo-traits = "0.2" num-traits = "0.2" serde = { version = "1.0", default-features = false, optional = true } thiserror = "1.0.23" diff --git a/src/lib.rs b/src/lib.rs index ee6d881..83787f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,10 @@ use std::default::Default; use std::fmt; use std::str::FromStr; +use geo_traits::{ + GeometryCollectionTrait, GeometryTrait, LineStringTrait, MultiLineStringTrait, MultiPointTrait, + MultiPolygonTrait, PointTrait, PolygonTrait, +}; use num_traits::{Float, Num, NumCast}; use crate::tokenizer::{PeekableTokens, Token, Tokens}; @@ -397,6 +401,196 @@ where } } +impl GeometryTrait for Wkt { + type T = T; + type PointType<'b> = Point where Self: 'b; + type LineStringType<'b> = LineString where Self: 'b; + type PolygonType<'b> = Polygon where Self: 'b; + type MultiPointType<'b> = MultiPoint where Self: 'b; + type MultiLineStringType<'b> = MultiLineString where Self: 'b; + type MultiPolygonType<'b> = MultiPolygon where Self: 'b; + type GeometryCollectionType<'b> = GeometryCollection where Self: 'b; + type RectType<'b> = geo_traits::UnimplementedRect where Self: 'b; + type LineType<'b> = geo_traits::UnimplementedLine where Self: 'b; + type TriangleType<'b> = geo_traits::UnimplementedTriangle where Self: 'b; + + fn dim(&self) -> geo_traits::Dimensions { + match self { + Wkt::Point(geom) => PointTrait::dim(geom), + Wkt::LineString(geom) => LineStringTrait::dim(geom), + Wkt::Polygon(geom) => PolygonTrait::dim(geom), + Wkt::MultiPoint(geom) => MultiPointTrait::dim(geom), + Wkt::MultiLineString(geom) => MultiLineStringTrait::dim(geom), + Wkt::MultiPolygon(geom) => MultiPolygonTrait::dim(geom), + Wkt::GeometryCollection(geom) => GeometryCollectionTrait::dim(geom), + } + } + + fn as_type( + &self, + ) -> geo_traits::GeometryType< + '_, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + GeometryCollection, + Self::RectType<'_>, + Self::TriangleType<'_>, + Self::LineType<'_>, + > { + match self { + Wkt::Point(geom) => geo_traits::GeometryType::Point(geom), + Wkt::LineString(geom) => geo_traits::GeometryType::LineString(geom), + Wkt::Polygon(geom) => geo_traits::GeometryType::Polygon(geom), + Wkt::MultiPoint(geom) => geo_traits::GeometryType::MultiPoint(geom), + Wkt::MultiLineString(geom) => geo_traits::GeometryType::MultiLineString(geom), + Wkt::MultiPolygon(geom) => geo_traits::GeometryType::MultiPolygon(geom), + Wkt::GeometryCollection(geom) => geo_traits::GeometryType::GeometryCollection(geom), + } + } +} + +impl GeometryTrait for &Wkt { + type T = T; + type PointType<'b> = Point where Self: 'b; + type LineStringType<'b> = LineString where Self: 'b; + type PolygonType<'b> = Polygon where Self: 'b; + type MultiPointType<'b> = MultiPoint where Self: 'b; + type MultiLineStringType<'b> = MultiLineString where Self: 'b; + type MultiPolygonType<'b> = MultiPolygon where Self: 'b; + type GeometryCollectionType<'b> = GeometryCollection where Self: 'b; + type RectType<'b> = geo_traits::UnimplementedRect where Self: 'b; + type LineType<'b> = geo_traits::UnimplementedLine where Self: 'b; + type TriangleType<'b> = geo_traits::UnimplementedTriangle where Self: 'b; + + fn dim(&self) -> geo_traits::Dimensions { + match self { + Wkt::Point(geom) => PointTrait::dim(geom), + Wkt::LineString(geom) => LineStringTrait::dim(geom), + Wkt::Polygon(geom) => PolygonTrait::dim(geom), + Wkt::MultiPoint(geom) => MultiPointTrait::dim(geom), + Wkt::MultiLineString(geom) => MultiLineStringTrait::dim(geom), + Wkt::MultiPolygon(geom) => MultiPolygonTrait::dim(geom), + Wkt::GeometryCollection(geom) => GeometryCollectionTrait::dim(geom), + } + } + + fn as_type( + &self, + ) -> geo_traits::GeometryType< + '_, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + GeometryCollection, + Self::RectType<'_>, + Self::TriangleType<'_>, + Self::LineType<'_>, + > { + match self { + Wkt::Point(geom) => geo_traits::GeometryType::Point(geom), + Wkt::LineString(geom) => geo_traits::GeometryType::LineString(geom), + Wkt::Polygon(geom) => geo_traits::GeometryType::Polygon(geom), + Wkt::MultiPoint(geom) => geo_traits::GeometryType::MultiPoint(geom), + Wkt::MultiLineString(geom) => geo_traits::GeometryType::MultiLineString(geom), + Wkt::MultiPolygon(geom) => geo_traits::GeometryType::MultiPolygon(geom), + Wkt::GeometryCollection(geom) => geo_traits::GeometryType::GeometryCollection(geom), + } + } +} + +// Specialized implementations on each WKT concrete type. + +macro_rules! impl_specialization { + ($geometry_type:ident) => { + impl GeometryTrait for $geometry_type { + type T = T; + type PointType<'b> = Point where Self: 'b; + type LineStringType<'b> = LineString where Self: 'b; + type PolygonType<'b> = Polygon where Self: 'b; + type MultiPointType<'b> = MultiPoint where Self: 'b; + type MultiLineStringType<'b> = MultiLineString where Self: 'b; + type MultiPolygonType<'b> = MultiPolygon where Self: 'b; + type GeometryCollectionType<'b> = GeometryCollection where Self: 'b; + type RectType<'b> = geo_traits::UnimplementedRect where Self: 'b; + type LineType<'b> = geo_traits::UnimplementedLine where Self: 'b; + type TriangleType<'b> = geo_traits::UnimplementedTriangle where Self: 'b; + + fn dim(&self) -> geo_traits::Dimensions { + geo_traits::Dimensions::Xy + } + + fn as_type( + &self, + ) -> geo_traits::GeometryType< + '_, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + GeometryCollection, + Self::RectType<'_>, + Self::TriangleType<'_>, + Self::LineType<'_>, + > { + geo_traits::GeometryType::$geometry_type(self) + } + } + + impl<'a, T: WktNum + 'a> GeometryTrait for &'a $geometry_type { + type T = T; + type PointType<'b> = Point where Self: 'b; + type LineStringType<'b> = LineString where Self: 'b; + type PolygonType<'b> = Polygon where Self: 'b; + type MultiPointType<'b> = MultiPoint where Self: 'b; + type MultiLineStringType<'b> = MultiLineString where Self: 'b; + type MultiPolygonType<'b> = MultiPolygon where Self: 'b; + type GeometryCollectionType<'b> = GeometryCollection where Self: 'b; + type RectType<'b> = geo_traits::UnimplementedRect where Self: 'b; + type LineType<'b> = geo_traits::UnimplementedLine where Self: 'b; + type TriangleType<'b> = geo_traits::UnimplementedTriangle where Self: 'b; + + fn dim(&self) -> geo_traits::Dimensions { + geo_traits::Dimensions::Xy + } + + fn as_type( + &self, + ) -> geo_traits::GeometryType< + '_, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + GeometryCollection, + Self::RectType<'_>, + Self::TriangleType<'_>, + Self::LineType<'_>, + > { + geo_traits::GeometryType::$geometry_type(self) + } + } + }; +} + +impl_specialization!(Point); +impl_specialization!(LineString); +impl_specialization!(Polygon); +impl_specialization!(MultiPoint); +impl_specialization!(MultiLineString); +impl_specialization!(MultiPolygon); +impl_specialization!(GeometryCollection); + fn infer_geom_dimension( tokens: &mut PeekableTokens, ) -> Result { diff --git a/src/types/coord.rs b/src/types/coord.rs index 358490e..887cdb7 100644 --- a/src/types/coord.rs +++ b/src/types/coord.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use geo_traits::CoordTrait; + use crate::tokenizer::{PeekableTokens, Token}; use crate::types::Dimension; use crate::{FromTokens, WktNum}; @@ -96,6 +98,100 @@ where } } +impl CoordTrait for Coord { + type T = T; + + fn dim(&self) -> geo_traits::Dimensions { + match (self.z.is_some(), self.m.is_some()) { + (true, true) => geo_traits::Dimensions::Xyzm, + (true, false) => geo_traits::Dimensions::Xyz, + (false, true) => geo_traits::Dimensions::Xym, + (false, false) => geo_traits::Dimensions::Xy, + } + } + + fn x(&self) -> Self::T { + self.x + } + + fn y(&self) -> Self::T { + self.y + } + + fn nth_or_panic(&self, n: usize) -> Self::T { + let has_z = self.z.is_some(); + let has_m = self.m.is_some(); + match n { + 0 => self.x, + 1 => self.y, + 2 => { + if has_z { + self.z.unwrap() + } else if has_m { + self.m.unwrap() + } else { + panic!("n out of range") + } + } + 3 => { + if has_z && has_m { + self.m.unwrap() + } else { + panic!("n out of range") + } + } + _ => panic!("n out of range"), + } + } +} + +impl CoordTrait for &Coord { + type T = T; + + fn dim(&self) -> geo_traits::Dimensions { + match (self.z.is_some(), self.m.is_some()) { + (true, true) => geo_traits::Dimensions::Xyzm, + (true, false) => geo_traits::Dimensions::Xyz, + (false, true) => geo_traits::Dimensions::Xym, + (false, false) => geo_traits::Dimensions::Xy, + } + } + + fn x(&self) -> Self::T { + self.x + } + + fn y(&self) -> Self::T { + self.y + } + + fn nth_or_panic(&self, n: usize) -> Self::T { + let has_z = self.z.is_some(); + let has_m = self.m.is_some(); + match n { + 0 => self.x, + 1 => self.y, + 2 => { + if has_z { + self.z.unwrap() + } else if has_m { + self.m.unwrap() + } else { + panic!("n out of range") + } + } + 3 => { + if has_z && has_m { + self.m.unwrap() + } else { + panic!("n out of range") + } + } + _ => panic!("n out of range"), + } + } +} + #[cfg(test)] mod tests { use super::Coord; diff --git a/src/types/geometrycollection.rs b/src/types/geometrycollection.rs index 8aa3d8a..559fd8f 100644 --- a/src/types/geometrycollection.rs +++ b/src/types/geometrycollection.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use geo_traits::{GeometryCollectionTrait, GeometryTrait}; + use crate::tokenizer::{PeekableTokens, Token}; use crate::types::Dimension; use crate::{FromTokens, Wkt, WktNum}; @@ -84,6 +86,28 @@ where } } +impl GeometryCollectionTrait for GeometryCollection { + type T = T; + type GeometryType<'a> = &'a Wkt where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn num_geometries(&self) -> usize { + self.0.len() + } + + unsafe fn geometry_unchecked(&self, i: usize) -> Self::GeometryType<'_> { + self.0.get_unchecked(i) + } +} + #[cfg(test)] mod tests { use super::GeometryCollection; diff --git a/src/types/linestring.rs b/src/types/linestring.rs index ff84bcf..6465de4 100644 --- a/src/types/linestring.rs +++ b/src/types/linestring.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use geo_traits::{CoordTrait, LineStringTrait}; + use crate::tokenizer::PeekableTokens; use crate::types::coord::Coord; use crate::types::Dimension; @@ -61,6 +63,50 @@ where } } +impl LineStringTrait for LineString { + type T = T; + type CoordType<'a> = &'a Coord where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn num_coords(&self) -> usize { + self.0.len() + } + + unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> { + self.0.get_unchecked(i) + } +} + +impl LineStringTrait for &LineString { + type T = T; + type CoordType<'a> = &'a Coord where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn num_coords(&self) -> usize { + self.0.len() + } + + unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> { + self.0.get_unchecked(i) + } +} + #[cfg(test)] mod tests { use super::{Coord, LineString}; diff --git a/src/types/multilinestring.rs b/src/types/multilinestring.rs index dae4532..9df1c7f 100644 --- a/src/types/multilinestring.rs +++ b/src/types/multilinestring.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use geo_traits::{LineStringTrait, MultiLineStringTrait}; + use crate::tokenizer::PeekableTokens; use crate::types::linestring::LineString; use crate::types::Dimension; @@ -70,6 +72,50 @@ where } } +impl MultiLineStringTrait for MultiLineString { + type T = T; + type LineStringType<'a> = &'a LineString where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn num_line_strings(&self) -> usize { + self.0.len() + } + + unsafe fn line_string_unchecked(&self, i: usize) -> Self::LineStringType<'_> { + self.0.get_unchecked(i) + } +} + +impl MultiLineStringTrait for &MultiLineString { + type T = T; + type LineStringType<'a> = &'a LineString where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn num_line_strings(&self) -> usize { + self.0.len() + } + + unsafe fn line_string_unchecked(&self, i: usize) -> Self::LineStringType<'_> { + self.0.get_unchecked(i) + } +} + #[cfg(test)] mod tests { use super::{LineString, MultiLineString}; diff --git a/src/types/multipoint.rs b/src/types/multipoint.rs index b0aa431..bde8613 100644 --- a/src/types/multipoint.rs +++ b/src/types/multipoint.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use geo_traits::{MultiPointTrait, PointTrait}; + use crate::tokenizer::PeekableTokens; use crate::types::point::Point; use crate::types::Dimension; @@ -66,6 +68,50 @@ where } } +impl MultiPointTrait for MultiPoint { + type T = T; + type PointType<'a> = &'a Point where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn num_points(&self) -> usize { + self.0.len() + } + + unsafe fn point_unchecked(&self, i: usize) -> Self::PointType<'_> { + self.0.get_unchecked(i) + } +} + +impl MultiPointTrait for &MultiPoint { + type T = T; + type PointType<'a> = &'a Point where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn num_points(&self) -> usize { + self.0.len() + } + + unsafe fn point_unchecked(&self, i: usize) -> Self::PointType<'_> { + self.0.get_unchecked(i) + } +} + #[cfg(test)] mod tests { use super::{MultiPoint, Point}; diff --git a/src/types/multipolygon.rs b/src/types/multipolygon.rs index 7c21a4b..c9f98ff 100644 --- a/src/types/multipolygon.rs +++ b/src/types/multipolygon.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use geo_traits::{MultiPolygonTrait, PolygonTrait}; + use crate::tokenizer::PeekableTokens; use crate::types::polygon::Polygon; use crate::types::Dimension; @@ -75,6 +77,50 @@ where } } +impl MultiPolygonTrait for MultiPolygon { + type T = T; + type PolygonType<'a> = &'a Polygon where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn num_polygons(&self) -> usize { + self.0.len() + } + + unsafe fn polygon_unchecked(&self, i: usize) -> Self::PolygonType<'_> { + self.0.get_unchecked(i) + } +} + +impl MultiPolygonTrait for &MultiPolygon { + type T = T; + type PolygonType<'a> = &'a Polygon where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn num_polygons(&self) -> usize { + self.0.len() + } + + unsafe fn polygon_unchecked(&self, i: usize) -> Self::PolygonType<'_> { + self.0.get_unchecked(i) + } +} + #[cfg(test)] mod tests { use super::{MultiPolygon, Polygon}; diff --git a/src/types/point.rs b/src/types/point.rs index a4d6123..0d2e5ab 100644 --- a/src/types/point.rs +++ b/src/types/point.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use geo_traits::{CoordTrait, PointTrait}; + use crate::tokenizer::PeekableTokens; use crate::types::coord::Coord; use crate::types::Dimension; @@ -66,6 +68,41 @@ where } } +impl PointTrait for Point { + type T = T; + type CoordType<'a> = &'a Coord where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + if let Some(coord) = &self.0 { + coord.dim() + } else { + // TODO: infer dimension from empty WKT + geo_traits::Dimensions::Xy + } + } + + fn coord(&self) -> Option> { + self.0.as_ref() + } +} + +impl PointTrait for &Point { + type T = T; + type CoordType<'a> = &'a Coord where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + if let Some(coord) = &self.0 { + coord.dim() + } else { + // TODO: infer dimension from empty WKT + geo_traits::Dimensions::Xy + } + } + + fn coord(&self) -> Option> { + self.0.as_ref() + } +} #[cfg(test)] mod tests { use super::{Coord, Point}; diff --git a/src/types/polygon.rs b/src/types/polygon.rs index fd2d5de..dfedf4b 100644 --- a/src/types/polygon.rs +++ b/src/types/polygon.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use geo_traits::{LineStringTrait, PolygonTrait}; + use crate::tokenizer::PeekableTokens; use crate::types::linestring::LineString; use crate::types::Dimension; @@ -70,6 +72,58 @@ where } } +impl PolygonTrait for Polygon { + type T = T; + type RingType<'a> = &'a LineString where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn exterior(&self) -> Option> { + self.0.first() + } + + fn num_interiors(&self) -> usize { + self.0.len().saturating_sub(1) + } + + unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> { + self.0.get_unchecked(i + 1) + } +} + +impl PolygonTrait for &Polygon { + type T = T; + type RingType<'a> = &'a LineString where Self: 'a; + + fn dim(&self) -> geo_traits::Dimensions { + // TODO: infer dimension from empty WKT + if self.0.is_empty() { + geo_traits::Dimensions::Xy + } else { + self.0[0].dim() + } + } + + fn exterior(&self) -> Option> { + self.0.first() + } + + fn num_interiors(&self) -> usize { + self.0.len().saturating_sub(1) + } + + unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> { + self.0.get_unchecked(i + 1) + } +} + #[cfg(test)] mod tests { use super::{LineString, Polygon};