From c4efa960a1436615796ea57bd1b595728c6c08d4 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 15:39:15 +0900 Subject: [PATCH 01/43] Implement Coord --- geo-traits/Cargo.toml | 2 + geo-traits/src/lib.rs | 2 + geo-traits/src/structs/coord.rs | 145 ++++++++++++++++++++++++++++++++ geo-traits/src/structs/mod.rs | 21 +++++ 4 files changed, 170 insertions(+) create mode 100644 geo-traits/src/structs/coord.rs create mode 100644 geo-traits/src/structs/mod.rs diff --git a/geo-traits/Cargo.toml b/geo-traits/Cargo.toml index 7d6ec086f7..e9cdbb88be 100644 --- a/geo-traits/Cargo.toml +++ b/geo-traits/Cargo.toml @@ -12,6 +12,8 @@ edition = "2021" [features] default = ["geo-types"] +# Provides a set of basic structs that implement the traits +structs = [] [dependencies] geo-types = { version = "0.7.17", optional = true } diff --git a/geo-traits/src/lib.rs b/geo-traits/src/lib.rs index b307b73254..64a362e249 100644 --- a/geo-traits/src/lib.rs +++ b/geo-traits/src/lib.rs @@ -44,6 +44,8 @@ mod multi_polygon; mod point; mod polygon; mod rect; +#[cfg(feature = "structs")] +pub mod structs; #[cfg(feature = "geo-types")] pub mod to_geo; mod triangle; diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs new file mode 100644 index 0000000000..5c97673140 --- /dev/null +++ b/geo-traits/src/structs/coord.rs @@ -0,0 +1,145 @@ +use crate::CoordTrait; + +use crate::dimension::Dimensions; + +/// A parsed coordinate. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct Coord { + /// The x-coordinate. + pub x: T, + /// The y-coordinate. + pub y: T, + /// The z-coordinate. + pub z: Option, + /// The m-coordinate. + pub m: Option, +} + +impl Coord { + /// Creates a new coordinate from a coordinate trait. + pub fn new(coord: impl CoordTrait) -> Self { + let x = coord.x(); + let y = coord.y(); + + match coord.dim() { + Dimensions::Xyzm | Dimensions::Unknown(_) => Self { + x, + y, + z: coord.nth(2), + m: coord.nth(3), + }, + Dimensions::Xyz => Self { + x, + y, + z: coord.nth(2), + m: None, + }, + Dimensions::Xym => Self { + x, + y, + z: None, + m: coord.nth(2), + }, + Dimensions::Xy => Self { + x, + y, + z: None, + m: None, + }, + } + } + + /// Return the [Dimensions] of this coord. + pub fn dimension(&self) -> Dimensions { + match (self.z.is_some(), self.m.is_some()) { + (true, true) => Dimensions::Xyzm, + (true, false) => Dimensions::Xyz, + (false, true) => Dimensions::Xym, + (false, false) => Dimensions::Xy, + } + } +} + +impl CoordTrait for Coord { + type T = T; + + fn dim(&self) -> Dimensions { + self.dimension().into() + } + + 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) -> Dimensions { + self.dimension().into() + } + + 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"), + } + } +} diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs new file mode 100644 index 0000000000..56ac60c860 --- /dev/null +++ b/geo-traits/src/structs/mod.rs @@ -0,0 +1,21 @@ +//! Provides structs that implement the geo-traits crate's traits. + +pub use self::coord::Coord; +// pub use self::geometry_type::GeometryType; +// pub use self::geometrycollection::GeometryCollection; +// pub use self::linestring::LineString; +// pub use self::multilinestring::MultiLineString; +// pub use self::multipoint::MultiPoint; +// pub use self::multipolygon::MultiPolygon; +// pub use self::point::Point; +// pub use self::polygon::Polygon; + +mod coord; +// mod geometry_type; +// mod geometrycollection; +// mod linestring; +// mod multilinestring; +// mod multipoint; +// mod multipolygon; +// mod point; +// mod polygon; From 75ac6d7d5d64c29f3eaf20729c923663c75815bd Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:16:46 +0900 Subject: [PATCH 02/43] Geometry (instead of Wkt) --- geo-traits/src/structs/geometry.rs | 371 +++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 geo-traits/src/structs/geometry.rs diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs new file mode 100644 index 0000000000..891597e1dd --- /dev/null +++ b/geo-traits/src/structs/geometry.rs @@ -0,0 +1,371 @@ +use crate::{ + Dimensions, GeometryTrait, UnimplementedGeometryCollection, UnimplementedLine, + UnimplementedLineString, UnimplementedMultiLineString, UnimplementedMultiPoint, + UnimplementedMultiPolygon, UnimplementedPolygon, UnimplementedRect, UnimplementedTriangle, +}; + +use super::Point; + +#[derive(Clone, Debug, PartialEq)] +/// All supported WKT geometry [`types`] +pub enum Geometry { + Point(Point), + // LineString(LineString), + // Polygon(Polygon), + // MultiPoint(MultiPoint), + // MultiLineString(MultiLineString), + // MultiPolygon(MultiPolygon), + // GeometryCollection(GeometryCollection), +} + +impl Geometry +where + T: Copy, +{ + /// Return the [Dimension] of this geometry. + pub fn dimension(&self) -> Dimensions { + match self { + Self::Point(g) => g.dimension(), + // Self::LineString(g) => g.dimension(), + // Self::Polygon(g) => g.dimension(), + // Self::MultiPoint(g) => g.dimension(), + // Self::MultiLineString(g) => g.dimension(), + // Self::MultiPolygon(g) => g.dimension(), + // Self::GeometryCollection(g) => g.dimension(), + } + } +} + +impl GeometryTrait for Geometry { + type T = T; + type PointType<'b> + = Point + where + Self: 'b; + type LineStringType<'b> + = UnimplementedLineString + // = LineString + where + Self: 'b; + type PolygonType<'b> + = UnimplementedPolygon + // = Polygon + where + Self: 'b; + type MultiPointType<'b> + = UnimplementedMultiPoint + // = MultiPoint + where + Self: 'b; + type MultiLineStringType<'b> + = UnimplementedMultiLineString + // = MultiLineString + where + Self: 'b; + type MultiPolygonType<'b> + = UnimplementedMultiPolygon + // = MultiPolygon + where + Self: 'b; + type GeometryCollectionType<'b> + = UnimplementedGeometryCollection + // = GeometryCollection + where + Self: 'b; + type RectType<'b> + = UnimplementedRect + where + Self: 'b; + type LineType<'b> + = UnimplementedLine + where + Self: 'b; + type TriangleType<'b> + = UnimplementedTriangle + where + Self: 'b; + + fn dim(&self) -> Dimensions { + match self { + Geometry::Point(geom) => geom.dim(), + // Geometry::LineString(geom) => geom.dim(), + // Geometry::Polygon(geom) => geom.dim(), + // Geometry::MultiPoint(geom) => geom.dim(), + // Geometry::MultiLineString(geom) => geom.dim(), + // Geometry::MultiPolygon(geom) => geom.dim(), + // Geometry::GeometryCollection(geom) => geom.dim(), + } + } + + fn as_type( + &self, + ) -> crate::GeometryType< + '_, + Self::PointType<'_>, + Self::LineStringType<'_>, + Self::PolygonType<'_>, + Self::MultiPointType<'_>, + Self::MultiLineStringType<'_>, + Self::MultiPolygonType<'_>, + Self::GeometryCollectionType<'_>, + Self::RectType<'_>, + Self::TriangleType<'_>, + Self::LineType<'_>, + > { + match self { + Geometry::Point(geom) => crate::GeometryType::Point(geom), + // Geometry::LineString(geom) => crate::GeometryType::LineString(geom), + // Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), + // Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), + // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), + // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), + // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), + } + } +} + +impl GeometryTrait for &Geometry { + type T = T; + type PointType<'b> + = Point + where + Self: 'b; + type LineStringType<'b> + = UnimplementedLineString + // = LineString + where + Self: 'b; + type PolygonType<'b> + = UnimplementedPolygon + // = Polygon + where + Self: 'b; + type MultiPointType<'b> + = UnimplementedMultiPoint + // = MultiPoint + where + Self: 'b; + type MultiLineStringType<'b> + = UnimplementedMultiLineString + // = MultiLineString + where + Self: 'b; + type MultiPolygonType<'b> + = UnimplementedMultiPolygon + // = MultiPolygon + where + Self: 'b; + type GeometryCollectionType<'b> + = UnimplementedGeometryCollection + // = GeometryCollection + where + Self: 'b; + type RectType<'b> + = UnimplementedRect + where + Self: 'b; + type LineType<'b> + = UnimplementedLine + where + Self: 'b; + type TriangleType<'b> + = UnimplementedTriangle + where + Self: 'b; + + fn dim(&self) -> Dimensions { + match self { + Geometry::Point(geom) => geom.dim(), + // Geometry::LineString(geom) => geom.dim(), + // Geometry::Polygon(geom) => geom.dim(), + // Geometry::MultiPoint(geom) => geom.dim(), + // Geometry::MultiLineString(geom) => geom.dim(), + // Geometry::MultiPolygon(geom) => geom.dim(), + // Geometry::GeometryCollection(geom) => geom.dim(), + } + } + + fn as_type( + &self, + ) -> crate::GeometryType< + '_, + Self::PointType<'_>, + Self::LineStringType<'_>, + Self::PolygonType<'_>, + Self::MultiPointType<'_>, + Self::MultiLineStringType<'_>, + Self::MultiPolygonType<'_>, + Self::GeometryCollectionType<'_>, + Self::RectType<'_>, + Self::TriangleType<'_>, + Self::LineType<'_>, + > { + match self { + Geometry::Point(geom) => crate::GeometryType::Point(geom), + // Geometry::LineString(geom) => crate::GeometryType::LineString(geom), + // Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), + // Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), + // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), + // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), + // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), + } + } +} + +// Specialized implementations on each Geometry 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> + = UnimplementedLineString + // = LineString + where + Self: 'b; + type PolygonType<'b> + = UnimplementedPolygon + // = Polygon + where + Self: 'b; + type MultiPointType<'b> + = UnimplementedMultiPoint + // = MultiPoint + where + Self: 'b; + type MultiLineStringType<'b> + = UnimplementedMultiLineString + // = MultiLineString + where + Self: 'b; + type MultiPolygonType<'b> + = UnimplementedMultiPolygon + // = MultiPolygon + where + Self: 'b; + type GeometryCollectionType<'b> + = UnimplementedGeometryCollection + // = GeometryCollection + where + Self: 'b; + type RectType<'b> + = UnimplementedRect + where + Self: 'b; + type LineType<'b> + = UnimplementedLine + where + Self: 'b; + type TriangleType<'b> + = UnimplementedTriangle + where + Self: 'b; + + fn dim(&self) -> Dimensions { + self.dim.into() + } + + fn as_type( + &self, + ) -> crate::GeometryType< + '_, + Self::PointType<'_>, + Self::LineStringType<'_>, + Self::PolygonType<'_>, + Self::MultiPointType<'_>, + Self::MultiLineStringType<'_>, + Self::MultiPolygonType<'_>, + Self::GeometryCollectionType<'_>, + Self::RectType<'_>, + Self::TriangleType<'_>, + Self::LineType<'_>, + > { + crate::GeometryType::$geometry_type(self) + } + } + + impl<'a, T: Copy + 'a> GeometryTrait for &'a $geometry_type { + type T = T; + type PointType<'b> + = Point + where + Self: 'b; + type LineStringType<'b> + = UnimplementedLineString + // = LineString + where + Self: 'b; + type PolygonType<'b> + = UnimplementedPolygon + // = Polygon + where + Self: 'b; + type MultiPointType<'b> + = UnimplementedMultiPoint + // = MultiPoint + where + Self: 'b; + type MultiLineStringType<'b> + = UnimplementedMultiLineString + // = MultiLineString + where + Self: 'b; + type MultiPolygonType<'b> + = UnimplementedMultiPolygon + // = MultiPolygon + where + Self: 'b; + type GeometryCollectionType<'b> + = UnimplementedGeometryCollection + // = GeometryCollection + where + Self: 'b; + type RectType<'b> + = UnimplementedRect + where + Self: 'b; + type LineType<'b> + = UnimplementedLine + where + Self: 'b; + type TriangleType<'b> + = UnimplementedTriangle + where + Self: 'b; + + fn dim(&self) -> Dimensions { + self.dim.into() + } + + fn as_type( + &self, + ) -> crate::GeometryType< + '_, + Self::PointType<'_>, + Self::LineStringType<'_>, + Self::PolygonType<'_>, + Self::MultiPointType<'_>, + Self::MultiLineStringType<'_>, + Self::MultiPolygonType<'_>, + Self::GeometryCollectionType<'_>, + Self::RectType<'_>, + Self::TriangleType<'_>, + Self::LineType<'_>, + > { + crate::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); From c28ecfe1cf6a2c001b3c145795ba9b52774d6278 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:17:10 +0900 Subject: [PATCH 03/43] Point --- geo-traits/src/structs/mod.rs | 5 ++- geo-traits/src/structs/point.rs | 78 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 geo-traits/src/structs/point.rs diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs index 56ac60c860..22482ebaf1 100644 --- a/geo-traits/src/structs/mod.rs +++ b/geo-traits/src/structs/mod.rs @@ -7,15 +7,16 @@ pub use self::coord::Coord; // pub use self::multilinestring::MultiLineString; // pub use self::multipoint::MultiPoint; // pub use self::multipolygon::MultiPolygon; -// pub use self::point::Point; +pub use self::point::Point; // pub use self::polygon::Polygon; mod coord; +mod geometry; // mod geometry_type; // mod geometrycollection; // mod linestring; // mod multilinestring; // mod multipoint; // mod multipolygon; -// mod point; +mod point; // mod polygon; diff --git a/geo-traits/src/structs/point.rs b/geo-traits/src/structs/point.rs new file mode 100644 index 0000000000..14f2f97097 --- /dev/null +++ b/geo-traits/src/structs/point.rs @@ -0,0 +1,78 @@ +use crate::dimension::Dimensions; +use crate::structs::Coord; +use crate::PointTrait; + +/// A parsed Point. +#[derive(Clone, Debug, PartialEq)] +pub struct Point { + pub(crate) coord: Option>, + pub(crate) dim: Dimensions, +} + +impl Point { + /// Create a new Point from a coordinate and known [Dimension]. + pub fn new(coord: Option>, dim: Dimensions) -> Self { + Self { coord, dim } + } + + /// Create a new point from a valid [Coord]. + /// + /// This infers the dimension from the coordinate. + pub fn from_coord(coord: Coord) -> Self { + Self { + dim: coord.dimension(), + coord: Some(coord), + } + } + + /// Create a new empty point. + pub fn empty(dim: Dimensions) -> Self { + Self::new(None, dim) + } + + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the coordinate of this point. + pub fn coord(&self) -> Option<&Coord> { + self.coord.as_ref() + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Option>, Dimensions) { + (self.coord, self.dim) + } +} + +impl From> for super::geometry::Geometry +where + T: Copy, +{ + fn from(value: Point) -> Self { + super::geometry::Geometry::Point(value) + } +} + +impl PointTrait for Point { + type CoordType<'a> + = &'a Coord + where + Self: 'a; + + fn coord(&self) -> Option> { + self.coord.as_ref() + } +} + +impl<'a, T: Copy> PointTrait for &'a Point { + type CoordType<'b> + = &'a Coord + where + Self: 'b; + + fn coord(&self) -> Option> { + self.coord.as_ref() + } +} From 2c5f6c2a65a8d261cc0c1be60238c38dde885939 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:17:17 +0900 Subject: [PATCH 04/43] Tweak --- geo-traits/src/structs/coord.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index 5c97673140..ef23b25a00 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -1,6 +1,5 @@ -use crate::CoordTrait; - use crate::dimension::Dimensions; +use crate::CoordTrait; /// A parsed coordinate. #[derive(Clone, Copy, Debug, Default, PartialEq)] From ddffc62b4f518e50e5b415fd82c045b55a654ccb Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:32:03 +0900 Subject: [PATCH 05/43] LineString --- geo-traits/src/structs/coord.rs | 17 ++++++--------- geo-traits/src/structs/geometry.rs | 34 ++++++++++++++---------------- geo-traits/src/structs/mod.rs | 5 +++-- geo-traits/src/structs/point.rs | 4 ++-- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index ef23b25a00..550185fc9d 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -47,9 +47,12 @@ impl Coord { }, } } +} + +impl CoordTrait for Coord { + type T = T; - /// Return the [Dimensions] of this coord. - pub fn dimension(&self) -> Dimensions { + fn dim(&self) -> Dimensions { match (self.z.is_some(), self.m.is_some()) { (true, true) => Dimensions::Xyzm, (true, false) => Dimensions::Xyz, @@ -57,14 +60,6 @@ impl Coord { (false, false) => Dimensions::Xy, } } -} - -impl CoordTrait for Coord { - type T = T; - - fn dim(&self) -> Dimensions { - self.dimension().into() - } fn x(&self) -> Self::T { self.x @@ -105,7 +100,7 @@ impl CoordTrait for &Coord { type T = T; fn dim(&self) -> Dimensions { - self.dimension().into() + (*self).dim() } fn x(&self) -> Self::T { diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index 891597e1dd..3aaac29368 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -1,16 +1,18 @@ use crate::{ Dimensions, GeometryTrait, UnimplementedGeometryCollection, UnimplementedLine, - UnimplementedLineString, UnimplementedMultiLineString, UnimplementedMultiPoint, - UnimplementedMultiPolygon, UnimplementedPolygon, UnimplementedRect, UnimplementedTriangle, + UnimplementedMultiLineString, UnimplementedMultiPoint, UnimplementedMultiPolygon, + UnimplementedPolygon, UnimplementedRect, UnimplementedTriangle, }; -use super::Point; +use super::{LineString, Point}; #[derive(Clone, Debug, PartialEq)] /// All supported WKT geometry [`types`] pub enum Geometry { + /// A point. Point(Point), - // LineString(LineString), + /// A linestring. + LineString(LineString), // Polygon(Polygon), // MultiPoint(MultiPoint), // MultiLineString(MultiLineString), @@ -26,7 +28,7 @@ where pub fn dimension(&self) -> Dimensions { match self { Self::Point(g) => g.dimension(), - // Self::LineString(g) => g.dimension(), + Self::LineString(g) => g.dimension(), // Self::Polygon(g) => g.dimension(), // Self::MultiPoint(g) => g.dimension(), // Self::MultiLineString(g) => g.dimension(), @@ -43,8 +45,7 @@ impl GeometryTrait for Geometry { where Self: 'b; type LineStringType<'b> - = UnimplementedLineString - // = LineString + = LineString where Self: 'b; type PolygonType<'b> @@ -88,7 +89,7 @@ impl GeometryTrait for Geometry { fn dim(&self) -> Dimensions { match self { Geometry::Point(geom) => geom.dim(), - // Geometry::LineString(geom) => geom.dim(), + Geometry::LineString(geom) => geom.dim(), // Geometry::Polygon(geom) => geom.dim(), // Geometry::MultiPoint(geom) => geom.dim(), // Geometry::MultiLineString(geom) => geom.dim(), @@ -114,7 +115,7 @@ impl GeometryTrait for Geometry { > { match self { Geometry::Point(geom) => crate::GeometryType::Point(geom), - // Geometry::LineString(geom) => crate::GeometryType::LineString(geom), + Geometry::LineString(geom) => crate::GeometryType::LineString(geom), // Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), // Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), @@ -131,8 +132,7 @@ impl GeometryTrait for &Geometry { where Self: 'b; type LineStringType<'b> - = UnimplementedLineString - // = LineString + = LineString where Self: 'b; type PolygonType<'b> @@ -176,7 +176,7 @@ impl GeometryTrait for &Geometry { fn dim(&self) -> Dimensions { match self { Geometry::Point(geom) => geom.dim(), - // Geometry::LineString(geom) => geom.dim(), + Geometry::LineString(geom) => geom.dim(), // Geometry::Polygon(geom) => geom.dim(), // Geometry::MultiPoint(geom) => geom.dim(), // Geometry::MultiLineString(geom) => geom.dim(), @@ -202,7 +202,7 @@ impl GeometryTrait for &Geometry { > { match self { Geometry::Point(geom) => crate::GeometryType::Point(geom), - // Geometry::LineString(geom) => crate::GeometryType::LineString(geom), + Geometry::LineString(geom) => crate::GeometryType::LineString(geom), // Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), // Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), @@ -223,8 +223,7 @@ macro_rules! impl_specialization { where Self: 'b; type LineStringType<'b> - = UnimplementedLineString - // = LineString + = LineString where Self: 'b; type PolygonType<'b> @@ -295,8 +294,7 @@ macro_rules! impl_specialization { where Self: 'b; type LineStringType<'b> - = UnimplementedLineString - // = LineString + = LineString where Self: 'b; type PolygonType<'b> @@ -363,7 +361,7 @@ macro_rules! impl_specialization { } impl_specialization!(Point); -// impl_specialization!(LineString); +impl_specialization!(LineString); // impl_specialization!(Polygon); // impl_specialization!(MultiPoint); // impl_specialization!(MultiLineString); diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs index 22482ebaf1..1ca859eaed 100644 --- a/geo-traits/src/structs/mod.rs +++ b/geo-traits/src/structs/mod.rs @@ -1,9 +1,10 @@ //! Provides structs that implement the geo-traits crate's traits. pub use self::coord::Coord; +pub use self::geometry::Geometry; // pub use self::geometry_type::GeometryType; // pub use self::geometrycollection::GeometryCollection; -// pub use self::linestring::LineString; +pub use self::linestring::LineString; // pub use self::multilinestring::MultiLineString; // pub use self::multipoint::MultiPoint; // pub use self::multipolygon::MultiPolygon; @@ -14,7 +15,7 @@ mod coord; mod geometry; // mod geometry_type; // mod geometrycollection; -// mod linestring; +mod linestring; // mod multilinestring; // mod multipoint; // mod multipolygon; diff --git a/geo-traits/src/structs/point.rs b/geo-traits/src/structs/point.rs index 14f2f97097..4ce7081907 100644 --- a/geo-traits/src/structs/point.rs +++ b/geo-traits/src/structs/point.rs @@ -1,6 +1,6 @@ use crate::dimension::Dimensions; use crate::structs::Coord; -use crate::PointTrait; +use crate::{CoordTrait as _, PointTrait}; /// A parsed Point. #[derive(Clone, Debug, PartialEq)] @@ -20,7 +20,7 @@ impl Point { /// This infers the dimension from the coordinate. pub fn from_coord(coord: Coord) -> Self { Self { - dim: coord.dimension(), + dim: coord.dim(), coord: Some(coord), } } From 36aae41e2f1f4e1de1714955da09d50baf20ce3c Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:37:07 +0900 Subject: [PATCH 06/43] Polygon --- geo-traits/src/structs/geometry.rs | 31 ++++----- geo-traits/src/structs/mod.rs | 4 +- geo-traits/src/structs/polygon.rs | 105 +++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 19 deletions(-) create mode 100644 geo-traits/src/structs/polygon.rs diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index 3aaac29368..5aed59a106 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -1,10 +1,10 @@ use crate::{ Dimensions, GeometryTrait, UnimplementedGeometryCollection, UnimplementedLine, UnimplementedMultiLineString, UnimplementedMultiPoint, UnimplementedMultiPolygon, - UnimplementedPolygon, UnimplementedRect, UnimplementedTriangle, + UnimplementedRect, UnimplementedTriangle, }; -use super::{LineString, Point}; +use super::{LineString, Point, Polygon}; #[derive(Clone, Debug, PartialEq)] /// All supported WKT geometry [`types`] @@ -13,7 +13,8 @@ pub enum Geometry { Point(Point), /// A linestring. LineString(LineString), - // Polygon(Polygon), + /// A polygon. + Polygon(Polygon), // MultiPoint(MultiPoint), // MultiLineString(MultiLineString), // MultiPolygon(MultiPolygon), @@ -29,7 +30,7 @@ where match self { Self::Point(g) => g.dimension(), Self::LineString(g) => g.dimension(), - // Self::Polygon(g) => g.dimension(), + Self::Polygon(g) => g.dimension(), // Self::MultiPoint(g) => g.dimension(), // Self::MultiLineString(g) => g.dimension(), // Self::MultiPolygon(g) => g.dimension(), @@ -49,8 +50,7 @@ impl GeometryTrait for Geometry { where Self: 'b; type PolygonType<'b> - = UnimplementedPolygon - // = Polygon + = Polygon where Self: 'b; type MultiPointType<'b> @@ -90,7 +90,7 @@ impl GeometryTrait for Geometry { match self { Geometry::Point(geom) => geom.dim(), Geometry::LineString(geom) => geom.dim(), - // Geometry::Polygon(geom) => geom.dim(), + Geometry::Polygon(geom) => geom.dim(), // Geometry::MultiPoint(geom) => geom.dim(), // Geometry::MultiLineString(geom) => geom.dim(), // Geometry::MultiPolygon(geom) => geom.dim(), @@ -116,7 +116,7 @@ impl GeometryTrait for Geometry { match self { Geometry::Point(geom) => crate::GeometryType::Point(geom), Geometry::LineString(geom) => crate::GeometryType::LineString(geom), - // Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), + Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), // Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), @@ -136,8 +136,7 @@ impl GeometryTrait for &Geometry { where Self: 'b; type PolygonType<'b> - = UnimplementedPolygon - // = Polygon + = Polygon where Self: 'b; type MultiPointType<'b> @@ -177,7 +176,7 @@ impl GeometryTrait for &Geometry { match self { Geometry::Point(geom) => geom.dim(), Geometry::LineString(geom) => geom.dim(), - // Geometry::Polygon(geom) => geom.dim(), + Geometry::Polygon(geom) => geom.dim(), // Geometry::MultiPoint(geom) => geom.dim(), // Geometry::MultiLineString(geom) => geom.dim(), // Geometry::MultiPolygon(geom) => geom.dim(), @@ -203,7 +202,7 @@ impl GeometryTrait for &Geometry { match self { Geometry::Point(geom) => crate::GeometryType::Point(geom), Geometry::LineString(geom) => crate::GeometryType::LineString(geom), - // Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), + Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), // Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), @@ -227,8 +226,7 @@ macro_rules! impl_specialization { where Self: 'b; type PolygonType<'b> - = UnimplementedPolygon - // = Polygon + = Polygon where Self: 'b; type MultiPointType<'b> @@ -298,8 +296,7 @@ macro_rules! impl_specialization { where Self: 'b; type PolygonType<'b> - = UnimplementedPolygon - // = Polygon + = Polygon where Self: 'b; type MultiPointType<'b> @@ -362,7 +359,7 @@ macro_rules! impl_specialization { impl_specialization!(Point); impl_specialization!(LineString); -// impl_specialization!(Polygon); +impl_specialization!(Polygon); // impl_specialization!(MultiPoint); // impl_specialization!(MultiLineString); // impl_specialization!(MultiPolygon); diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs index 1ca859eaed..7ed6482dd0 100644 --- a/geo-traits/src/structs/mod.rs +++ b/geo-traits/src/structs/mod.rs @@ -9,7 +9,7 @@ pub use self::linestring::LineString; // pub use self::multipoint::MultiPoint; // pub use self::multipolygon::MultiPolygon; pub use self::point::Point; -// pub use self::polygon::Polygon; +pub use self::polygon::Polygon; mod coord; mod geometry; @@ -20,4 +20,4 @@ mod linestring; // mod multipoint; // mod multipolygon; mod point; -// mod polygon; +mod polygon; diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs new file mode 100644 index 0000000000..ed33ea32d2 --- /dev/null +++ b/geo-traits/src/structs/polygon.rs @@ -0,0 +1,105 @@ +use crate::{structs::Geometry, Dimensions, PolygonTrait}; + +use super::LineString; + +/// A parsed Polygon. +#[derive(Clone, Debug, PartialEq)] +pub struct Polygon { + pub(crate) rings: Vec>, + pub(crate) dim: Dimensions, +} + +impl Polygon { + /// Create a new Polygon from a sequence of [LineString] and known [Dimensions]. + pub fn new(rings: Vec>, dim: Dimensions) -> Self { + Polygon { dim, rings } + } + + /// Create a new empty polygon. + pub fn empty(dim: Dimensions) -> Self { + Self::new(vec![], dim) + } + + /// Create a new polygon from a non-empty sequence of [LineString]. + /// + /// This will infer the dimension from the first line string, and will not validate that all + /// line strings have the same dimension. + /// + /// Returns `None` if the input iterator is empty. + /// + /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting + /// to an [empty][Self::empty] geometry with specified dimension. + pub fn from_rings(rings: impl IntoIterator>) -> Option { + let rings = rings.into_iter().collect::>(); + if rings.is_empty() { + None + } else { + let dim = rings[0].dimension(); + Some(Self::new(rings, dim)) + } + } + + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the inner rings. + /// + /// The first ring is defined to be the exterior ring, and the rest are interior rings. + pub fn rings(&self) -> &[LineString] { + &self.rings + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.rings, self.dim) + } +} + +impl From> for Geometry +where + T: Copy, +{ + fn from(value: Polygon) -> Self { + Geometry::Polygon(value) + } +} + +impl PolygonTrait for Polygon { + type RingType<'a> + = &'a LineString + where + Self: 'a; + + fn exterior(&self) -> Option> { + self.rings.first() + } + + fn num_interiors(&self) -> usize { + self.rings.len().saturating_sub(1) + } + + unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> { + self.rings.get_unchecked(i + 1) + } +} + +impl PolygonTrait for &Polygon { + type RingType<'a> + = &'a LineString + where + Self: 'a; + + fn exterior(&self) -> Option> { + self.rings.first() + } + + fn num_interiors(&self) -> usize { + self.rings.len().saturating_sub(1) + } + + unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> { + self.rings.get_unchecked(i + 1) + } +} From 86353ddd6d17251213b41d7d1896dfb8280562dc Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:41:36 +0900 Subject: [PATCH 07/43] Multipoint --- geo-traits/src/structs/geometry.rs | 33 +++++----- geo-traits/src/structs/mod.rs | 4 +- geo-traits/src/structs/multipoint.rs | 96 ++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 geo-traits/src/structs/multipoint.rs diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index 5aed59a106..8c8aec0066 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -1,10 +1,10 @@ use crate::{ Dimensions, GeometryTrait, UnimplementedGeometryCollection, UnimplementedLine, - UnimplementedMultiLineString, UnimplementedMultiPoint, UnimplementedMultiPolygon, - UnimplementedRect, UnimplementedTriangle, + UnimplementedMultiLineString, UnimplementedMultiPolygon, UnimplementedRect, + UnimplementedTriangle, }; -use super::{LineString, Point, Polygon}; +use super::{LineString, MultiPoint, Point, Polygon}; #[derive(Clone, Debug, PartialEq)] /// All supported WKT geometry [`types`] @@ -15,7 +15,8 @@ pub enum Geometry { LineString(LineString), /// A polygon. Polygon(Polygon), - // MultiPoint(MultiPoint), + /// A multipoint. + MultiPoint(MultiPoint), // MultiLineString(MultiLineString), // MultiPolygon(MultiPolygon), // GeometryCollection(GeometryCollection), @@ -31,7 +32,7 @@ where Self::Point(g) => g.dimension(), Self::LineString(g) => g.dimension(), Self::Polygon(g) => g.dimension(), - // Self::MultiPoint(g) => g.dimension(), + Self::MultiPoint(g) => g.dimension(), // Self::MultiLineString(g) => g.dimension(), // Self::MultiPolygon(g) => g.dimension(), // Self::GeometryCollection(g) => g.dimension(), @@ -54,8 +55,7 @@ impl GeometryTrait for Geometry { where Self: 'b; type MultiPointType<'b> - = UnimplementedMultiPoint - // = MultiPoint + = MultiPoint where Self: 'b; type MultiLineStringType<'b> @@ -91,7 +91,7 @@ impl GeometryTrait for Geometry { Geometry::Point(geom) => geom.dim(), Geometry::LineString(geom) => geom.dim(), Geometry::Polygon(geom) => geom.dim(), - // Geometry::MultiPoint(geom) => geom.dim(), + Geometry::MultiPoint(geom) => geom.dim(), // Geometry::MultiLineString(geom) => geom.dim(), // Geometry::MultiPolygon(geom) => geom.dim(), // Geometry::GeometryCollection(geom) => geom.dim(), @@ -117,7 +117,7 @@ impl GeometryTrait for Geometry { Geometry::Point(geom) => crate::GeometryType::Point(geom), Geometry::LineString(geom) => crate::GeometryType::LineString(geom), Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), - // Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), + Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), @@ -140,8 +140,7 @@ impl GeometryTrait for &Geometry { where Self: 'b; type MultiPointType<'b> - = UnimplementedMultiPoint - // = MultiPoint + = MultiPoint where Self: 'b; type MultiLineStringType<'b> @@ -177,7 +176,7 @@ impl GeometryTrait for &Geometry { Geometry::Point(geom) => geom.dim(), Geometry::LineString(geom) => geom.dim(), Geometry::Polygon(geom) => geom.dim(), - // Geometry::MultiPoint(geom) => geom.dim(), + Geometry::MultiPoint(geom) => geom.dim(), // Geometry::MultiLineString(geom) => geom.dim(), // Geometry::MultiPolygon(geom) => geom.dim(), // Geometry::GeometryCollection(geom) => geom.dim(), @@ -203,7 +202,7 @@ impl GeometryTrait for &Geometry { Geometry::Point(geom) => crate::GeometryType::Point(geom), Geometry::LineString(geom) => crate::GeometryType::LineString(geom), Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), - // Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), + Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), @@ -230,8 +229,7 @@ macro_rules! impl_specialization { where Self: 'b; type MultiPointType<'b> - = UnimplementedMultiPoint - // = MultiPoint + = MultiPoint where Self: 'b; type MultiLineStringType<'b> @@ -300,8 +298,7 @@ macro_rules! impl_specialization { where Self: 'b; type MultiPointType<'b> - = UnimplementedMultiPoint - // = MultiPoint + = MultiPoint where Self: 'b; type MultiLineStringType<'b> @@ -360,7 +357,7 @@ macro_rules! impl_specialization { impl_specialization!(Point); impl_specialization!(LineString); impl_specialization!(Polygon); -// impl_specialization!(MultiPoint); +impl_specialization!(MultiPoint); // impl_specialization!(MultiLineString); // impl_specialization!(MultiPolygon); // impl_specialization!(GeometryCollection); diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs index 7ed6482dd0..857034aff6 100644 --- a/geo-traits/src/structs/mod.rs +++ b/geo-traits/src/structs/mod.rs @@ -6,7 +6,7 @@ pub use self::geometry::Geometry; // pub use self::geometrycollection::GeometryCollection; pub use self::linestring::LineString; // pub use self::multilinestring::MultiLineString; -// pub use self::multipoint::MultiPoint; +pub use self::multipoint::MultiPoint; // pub use self::multipolygon::MultiPolygon; pub use self::point::Point; pub use self::polygon::Polygon; @@ -17,7 +17,7 @@ mod geometry; // mod geometrycollection; mod linestring; // mod multilinestring; -// mod multipoint; +mod multipoint; // mod multipolygon; mod point; mod polygon; diff --git a/geo-traits/src/structs/multipoint.rs b/geo-traits/src/structs/multipoint.rs new file mode 100644 index 0000000000..bd9f48b7d6 --- /dev/null +++ b/geo-traits/src/structs/multipoint.rs @@ -0,0 +1,96 @@ +use crate::{ + structs::{Geometry, Point}, + Dimensions, MultiPointTrait, +}; + +/// A parsed MultiPoint. +#[derive(Clone, Debug, PartialEq)] +pub struct MultiPoint { + pub(crate) points: Vec>, + pub(crate) dim: Dimensions, +} + +impl MultiPoint { + /// Create a new MultiPoint from a sequence of [Point] and known [Dimensions]. + pub fn new(points: Vec>, dim: Dimensions) -> Self { + MultiPoint { dim, points } + } + + /// Create a new empty MultiPoint. + pub fn empty(dim: Dimensions) -> Self { + Self::new(vec![], dim) + } + + /// Create a new MultiPoint from a non-empty sequence of [Point]. + /// + /// This will infer the dimension from the first point, and will not validate that all + /// points have the same dimension. + /// + /// Returns `None` if the input iterator is empty. + /// + /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting + /// to an [empty][Self::empty] geometry with specified dimension. + pub fn from_points(points: impl IntoIterator>) -> Option { + let points = points.into_iter().collect::>(); + if points.is_empty() { + None + } else { + let dim = points[0].dimension(); + Some(Self::new(points, dim)) + } + } + + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the inner points. + pub fn points(&self) -> &[Point] { + &self.points + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.points, self.dim) + } +} + +impl From> for Geometry +where + T: Copy, +{ + fn from(value: MultiPoint) -> Self { + Geometry::MultiPoint(value) + } +} + +impl MultiPointTrait for MultiPoint { + type InnerPointType<'a> + = &'a Point + where + Self: 'a; + + fn num_points(&self) -> usize { + self.points.len() + } + + unsafe fn point_unchecked(&self, i: usize) -> Self::InnerPointType<'_> { + self.points.get_unchecked(i) + } +} + +impl MultiPointTrait for &MultiPoint { + type InnerPointType<'a> + = &'a Point + where + Self: 'a; + + fn num_points(&self) -> usize { + self.points.len() + } + + unsafe fn point_unchecked(&self, i: usize) -> Self::InnerPointType<'_> { + self.points.get_unchecked(i) + } +} From cf34df7abdad5fd4ec7745ac29550e89d247f998 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:46:56 +0900 Subject: [PATCH 08/43] Multilinestring --- geo-traits/src/structs/geometry.rs | 33 ++++---- geo-traits/src/structs/mod.rs | 4 +- geo-traits/src/structs/multilinestring.rs | 98 +++++++++++++++++++++++ 3 files changed, 114 insertions(+), 21 deletions(-) create mode 100644 geo-traits/src/structs/multilinestring.rs diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index 8c8aec0066..a3325609d7 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -1,11 +1,9 @@ use crate::{ + structs::{LineString, MultiLineString, MultiPoint, Point, Polygon}, Dimensions, GeometryTrait, UnimplementedGeometryCollection, UnimplementedLine, - UnimplementedMultiLineString, UnimplementedMultiPolygon, UnimplementedRect, - UnimplementedTriangle, + UnimplementedMultiPolygon, UnimplementedRect, UnimplementedTriangle, }; -use super::{LineString, MultiPoint, Point, Polygon}; - #[derive(Clone, Debug, PartialEq)] /// All supported WKT geometry [`types`] pub enum Geometry { @@ -17,7 +15,8 @@ pub enum Geometry { Polygon(Polygon), /// A multipoint. MultiPoint(MultiPoint), - // MultiLineString(MultiLineString), + /// A multilinestring. + MultiLineString(MultiLineString), // MultiPolygon(MultiPolygon), // GeometryCollection(GeometryCollection), } @@ -33,7 +32,7 @@ where Self::LineString(g) => g.dimension(), Self::Polygon(g) => g.dimension(), Self::MultiPoint(g) => g.dimension(), - // Self::MultiLineString(g) => g.dimension(), + Self::MultiLineString(g) => g.dimension(), // Self::MultiPolygon(g) => g.dimension(), // Self::GeometryCollection(g) => g.dimension(), } @@ -59,8 +58,7 @@ impl GeometryTrait for Geometry { where Self: 'b; type MultiLineStringType<'b> - = UnimplementedMultiLineString - // = MultiLineString + = MultiLineString where Self: 'b; type MultiPolygonType<'b> @@ -92,7 +90,7 @@ impl GeometryTrait for Geometry { Geometry::LineString(geom) => geom.dim(), Geometry::Polygon(geom) => geom.dim(), Geometry::MultiPoint(geom) => geom.dim(), - // Geometry::MultiLineString(geom) => geom.dim(), + Geometry::MultiLineString(geom) => geom.dim(), // Geometry::MultiPolygon(geom) => geom.dim(), // Geometry::GeometryCollection(geom) => geom.dim(), } @@ -118,7 +116,7 @@ impl GeometryTrait for Geometry { Geometry::LineString(geom) => crate::GeometryType::LineString(geom), Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), - // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), + Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), } @@ -144,8 +142,7 @@ impl GeometryTrait for &Geometry { where Self: 'b; type MultiLineStringType<'b> - = UnimplementedMultiLineString - // = MultiLineString + = MultiLineString where Self: 'b; type MultiPolygonType<'b> @@ -177,7 +174,7 @@ impl GeometryTrait for &Geometry { Geometry::LineString(geom) => geom.dim(), Geometry::Polygon(geom) => geom.dim(), Geometry::MultiPoint(geom) => geom.dim(), - // Geometry::MultiLineString(geom) => geom.dim(), + Geometry::MultiLineString(geom) => geom.dim(), // Geometry::MultiPolygon(geom) => geom.dim(), // Geometry::GeometryCollection(geom) => geom.dim(), } @@ -203,7 +200,7 @@ impl GeometryTrait for &Geometry { Geometry::LineString(geom) => crate::GeometryType::LineString(geom), Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), - // Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), + Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), } @@ -233,8 +230,7 @@ macro_rules! impl_specialization { where Self: 'b; type MultiLineStringType<'b> - = UnimplementedMultiLineString - // = MultiLineString + = MultiLineString where Self: 'b; type MultiPolygonType<'b> @@ -302,8 +298,7 @@ macro_rules! impl_specialization { where Self: 'b; type MultiLineStringType<'b> - = UnimplementedMultiLineString - // = MultiLineString + = MultiLineString where Self: 'b; type MultiPolygonType<'b> @@ -358,6 +353,6 @@ impl_specialization!(Point); impl_specialization!(LineString); impl_specialization!(Polygon); impl_specialization!(MultiPoint); -// impl_specialization!(MultiLineString); +impl_specialization!(MultiLineString); // impl_specialization!(MultiPolygon); // impl_specialization!(GeometryCollection); diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs index 857034aff6..36d7f66cc2 100644 --- a/geo-traits/src/structs/mod.rs +++ b/geo-traits/src/structs/mod.rs @@ -5,7 +5,7 @@ pub use self::geometry::Geometry; // pub use self::geometry_type::GeometryType; // pub use self::geometrycollection::GeometryCollection; pub use self::linestring::LineString; -// pub use self::multilinestring::MultiLineString; +pub use self::multilinestring::MultiLineString; pub use self::multipoint::MultiPoint; // pub use self::multipolygon::MultiPolygon; pub use self::point::Point; @@ -16,7 +16,7 @@ mod geometry; // mod geometry_type; // mod geometrycollection; mod linestring; -// mod multilinestring; +mod multilinestring; mod multipoint; // mod multipolygon; mod point; diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs new file mode 100644 index 0000000000..4836fe07a8 --- /dev/null +++ b/geo-traits/src/structs/multilinestring.rs @@ -0,0 +1,98 @@ +use crate::{ + structs::{Geometry, LineString}, + Dimensions, MultiLineStringTrait, +}; + +/// A parsed MultiLineString. +#[derive(Clone, Debug, PartialEq)] +pub struct MultiLineString { + pub(crate) line_strings: Vec>, + pub(crate) dim: Dimensions, +} + +impl MultiLineString { + /// Create a new LineString from a sequence of [LineString] and known [Dimension]. + pub fn new(line_strings: Vec>, dim: Dimensions) -> Self { + MultiLineString { dim, line_strings } + } + + /// Create a new empty MultiLineString. + pub fn empty(dim: Dimensions) -> Self { + Self::new(vec![], dim) + } + + /// Create a new MultiLineString from a non-empty sequence of [LineString]. + /// + /// This will infer the dimension from the first line string, and will not validate that all + /// line strings have the same dimension. + /// + /// Returns `None` if the input iterator is empty. + /// + /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting + /// to an [empty][Self::empty] geometry with specified dimension. + pub fn from_line_strings( + line_strings: impl IntoIterator>, + ) -> Option { + let line_strings = line_strings.into_iter().collect::>(); + if line_strings.is_empty() { + None + } else { + let dim = line_strings[0].dimension(); + Some(Self::new(line_strings, dim)) + } + } + + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the inner line strings. + pub fn line_strings(&self) -> &[LineString] { + &self.line_strings + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.line_strings, self.dim) + } +} + +impl From> for Geometry +where + T: Copy, +{ + fn from(value: MultiLineString) -> Self { + Geometry::MultiLineString(value) + } +} + +impl MultiLineStringTrait for MultiLineString { + type InnerLineStringType<'a> + = &'a LineString + where + Self: 'a; + + fn num_line_strings(&self) -> usize { + self.line_strings.len() + } + + unsafe fn line_string_unchecked(&self, i: usize) -> Self::InnerLineStringType<'_> { + self.line_strings.get_unchecked(i) + } +} + +impl MultiLineStringTrait for &MultiLineString { + type InnerLineStringType<'a> + = &'a LineString + where + Self: 'a; + + fn num_line_strings(&self) -> usize { + self.line_strings.len() + } + + unsafe fn line_string_unchecked(&self, i: usize) -> Self::InnerLineStringType<'_> { + self.line_strings.get_unchecked(i) + } +} From ab0a9e001f42b8959a54d5fb1b42dcad1fcbea45 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:51:19 +0900 Subject: [PATCH 09/43] Linestring --- geo-traits/src/structs/linestring.rs | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 geo-traits/src/structs/linestring.rs diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs new file mode 100644 index 0000000000..7885bcdba5 --- /dev/null +++ b/geo-traits/src/structs/linestring.rs @@ -0,0 +1,96 @@ +use crate::{ + structs::{Coord, Geometry}, + CoordTrait as _, Dimensions, LineStringTrait, +}; + +/// A parsed LineString. +#[derive(Clone, Debug, PartialEq)] +pub struct LineString { + pub(crate) coords: Vec>, + pub(crate) dim: Dimensions, +} + +impl LineString { + /// Create a new LineString from a sequence of [Coord] and known [Dimensions]. + pub fn new(coords: Vec>, dim: Dimensions) -> Self { + LineString { dim, coords } + } + + /// Create a new empty LineString. + pub fn empty(dim: Dimensions) -> Self { + Self::new(vec![], dim) + } + + /// Create a new LineString from a non-empty sequence of [Coord]. + /// + /// This will infer the dimension from the first coordinate, and will not validate that all + /// coordinates have the same dimension. + /// + /// Returns `None` if the input iterator is empty. + /// + /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting + /// to an [empty][Self::empty] geometry with specified dimension. + pub fn from_coords(coords: impl IntoIterator>) -> Option { + let coords = coords.into_iter().collect::>(); + if coords.is_empty() { + None + } else { + let dim = coords[0].dim(); + Some(Self::new(coords, dim)) + } + } + + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the coordinates of this LineString. + pub fn coords(&self) -> &[Coord] { + &self.coords + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.coords, self.dim) + } +} + +impl From> for Geometry +where + T: Copy, +{ + fn from(value: LineString) -> Self { + Geometry::LineString(value) + } +} + +impl LineStringTrait for LineString { + type CoordType<'a> + = &'a Coord + where + Self: 'a; + + fn num_coords(&self) -> usize { + self.coords.len() + } + + unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> { + self.coords.get_unchecked(i) + } +} + +impl<'a, T: Copy> LineStringTrait for &'a LineString { + type CoordType<'b> + = &'a Coord + where + Self: 'b; + + fn num_coords(&self) -> usize { + self.coords.len() + } + + unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> { + self.coords.get_unchecked(i) + } +} From ba96b9a6693e397743de99abee0c781a3082a408 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:51:36 +0900 Subject: [PATCH 10/43] Multipolygon --- geo-traits/src/structs/geometry.rs | 31 ++++----- geo-traits/src/structs/mod.rs | 4 +- geo-traits/src/structs/multipolygon.rs | 96 ++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 geo-traits/src/structs/multipolygon.rs diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index a3325609d7..d9b2650064 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -1,7 +1,7 @@ use crate::{ - structs::{LineString, MultiLineString, MultiPoint, Point, Polygon}, + structs::{LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon}, Dimensions, GeometryTrait, UnimplementedGeometryCollection, UnimplementedLine, - UnimplementedMultiPolygon, UnimplementedRect, UnimplementedTriangle, + UnimplementedRect, UnimplementedTriangle, }; #[derive(Clone, Debug, PartialEq)] @@ -17,7 +17,8 @@ pub enum Geometry { MultiPoint(MultiPoint), /// A multilinestring. MultiLineString(MultiLineString), - // MultiPolygon(MultiPolygon), + /// A multipolygon. + MultiPolygon(MultiPolygon), // GeometryCollection(GeometryCollection), } @@ -33,7 +34,7 @@ where Self::Polygon(g) => g.dimension(), Self::MultiPoint(g) => g.dimension(), Self::MultiLineString(g) => g.dimension(), - // Self::MultiPolygon(g) => g.dimension(), + Self::MultiPolygon(g) => g.dimension(), // Self::GeometryCollection(g) => g.dimension(), } } @@ -62,8 +63,7 @@ impl GeometryTrait for Geometry { where Self: 'b; type MultiPolygonType<'b> - = UnimplementedMultiPolygon - // = MultiPolygon + = MultiPolygon where Self: 'b; type GeometryCollectionType<'b> @@ -91,7 +91,7 @@ impl GeometryTrait for Geometry { Geometry::Polygon(geom) => geom.dim(), Geometry::MultiPoint(geom) => geom.dim(), Geometry::MultiLineString(geom) => geom.dim(), - // Geometry::MultiPolygon(geom) => geom.dim(), + Geometry::MultiPolygon(geom) => geom.dim(), // Geometry::GeometryCollection(geom) => geom.dim(), } } @@ -117,7 +117,7 @@ impl GeometryTrait for Geometry { Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), - // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), + Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), } } @@ -146,8 +146,7 @@ impl GeometryTrait for &Geometry { where Self: 'b; type MultiPolygonType<'b> - = UnimplementedMultiPolygon - // = MultiPolygon + = MultiPolygon where Self: 'b; type GeometryCollectionType<'b> @@ -175,7 +174,7 @@ impl GeometryTrait for &Geometry { Geometry::Polygon(geom) => geom.dim(), Geometry::MultiPoint(geom) => geom.dim(), Geometry::MultiLineString(geom) => geom.dim(), - // Geometry::MultiPolygon(geom) => geom.dim(), + Geometry::MultiPolygon(geom) => geom.dim(), // Geometry::GeometryCollection(geom) => geom.dim(), } } @@ -201,7 +200,7 @@ impl GeometryTrait for &Geometry { Geometry::Polygon(geom) => crate::GeometryType::Polygon(geom), Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), - // Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), + Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), } } @@ -234,8 +233,7 @@ macro_rules! impl_specialization { where Self: 'b; type MultiPolygonType<'b> - = UnimplementedMultiPolygon - // = MultiPolygon + = MultiPolygon where Self: 'b; type GeometryCollectionType<'b> @@ -302,8 +300,7 @@ macro_rules! impl_specialization { where Self: 'b; type MultiPolygonType<'b> - = UnimplementedMultiPolygon - // = MultiPolygon + = MultiPolygon where Self: 'b; type GeometryCollectionType<'b> @@ -354,5 +351,5 @@ impl_specialization!(LineString); impl_specialization!(Polygon); impl_specialization!(MultiPoint); impl_specialization!(MultiLineString); -// impl_specialization!(MultiPolygon); +impl_specialization!(MultiPolygon); // impl_specialization!(GeometryCollection); diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs index 36d7f66cc2..9ea7ccfd0e 100644 --- a/geo-traits/src/structs/mod.rs +++ b/geo-traits/src/structs/mod.rs @@ -7,7 +7,7 @@ pub use self::geometry::Geometry; pub use self::linestring::LineString; pub use self::multilinestring::MultiLineString; pub use self::multipoint::MultiPoint; -// pub use self::multipolygon::MultiPolygon; +pub use self::multipolygon::MultiPolygon; pub use self::point::Point; pub use self::polygon::Polygon; @@ -18,6 +18,6 @@ mod geometry; mod linestring; mod multilinestring; mod multipoint; -// mod multipolygon; +mod multipolygon; mod point; mod polygon; diff --git a/geo-traits/src/structs/multipolygon.rs b/geo-traits/src/structs/multipolygon.rs new file mode 100644 index 0000000000..c6a2a1927f --- /dev/null +++ b/geo-traits/src/structs/multipolygon.rs @@ -0,0 +1,96 @@ +use crate::{ + structs::{Geometry, Polygon}, + Dimensions, MultiPolygonTrait, +}; + +/// A parsed MultiPolygon. +#[derive(Clone, Debug, PartialEq)] +pub struct MultiPolygon { + pub(crate) polygons: Vec>, + pub(crate) dim: Dimensions, +} + +impl MultiPolygon { + /// Create a new MultiPolygon from a sequence of [Polygon] and known [Dimensions]. + pub fn new(polygons: Vec>, dim: Dimensions) -> Self { + MultiPolygon { dim, polygons } + } + + /// Create a new empty MultiPolygon. + pub fn empty(dim: Dimensions) -> Self { + Self::new(vec![], dim) + } + + /// Create a new MultiPolygon from a non-empty sequence of [Polygon]. + /// + /// This will infer the dimension from the first polygon, and will not validate that all + /// polygons have the same dimension. + /// + /// Returns `None` if the input iterator is empty. + /// + /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting + /// to an [empty][Self::empty] geometry with specified dimension. + pub fn from_polygons(polygons: impl IntoIterator>) -> Option { + let polygons = polygons.into_iter().collect::>(); + if polygons.is_empty() { + None + } else { + let dim = polygons[0].dimension(); + Some(Self::new(polygons, dim)) + } + } + + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the inner polygons. + pub fn polygons(&self) -> &[Polygon] { + &self.polygons + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.polygons, self.dim) + } +} + +impl From> for Geometry +where + T: Copy, +{ + fn from(value: MultiPolygon) -> Self { + Geometry::MultiPolygon(value) + } +} + +impl MultiPolygonTrait for MultiPolygon { + type InnerPolygonType<'a> + = &'a Polygon + where + Self: 'a; + + fn num_polygons(&self) -> usize { + self.polygons.len() + } + + unsafe fn polygon_unchecked(&self, i: usize) -> Self::InnerPolygonType<'_> { + self.polygons.get_unchecked(i) + } +} + +impl MultiPolygonTrait for &MultiPolygon { + type InnerPolygonType<'a> + = &'a Polygon + where + Self: 'a; + + fn num_polygons(&self) -> usize { + self.polygons.len() + } + + unsafe fn polygon_unchecked(&self, i: usize) -> Self::InnerPolygonType<'_> { + self.polygons.get_unchecked(i) + } +} From 7e1206c8d67d1252cc43007228eca1d643952e80 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:56:14 +0900 Subject: [PATCH 11/43] GeometryCollection --- geo-traits/src/structs/geometry.rs | 34 ++++--- geo-traits/src/structs/geometrycollection.rs | 95 ++++++++++++++++++++ geo-traits/src/structs/linestring.rs | 2 +- geo-traits/src/structs/mod.rs | 4 +- geo-traits/src/structs/multilinestring.rs | 2 +- geo-traits/src/structs/multipolygon.rs | 2 +- geo-traits/src/structs/polygon.rs | 2 +- 7 files changed, 117 insertions(+), 24 deletions(-) create mode 100644 geo-traits/src/structs/geometrycollection.rs diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index d9b2650064..afd65907ca 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -1,7 +1,8 @@ use crate::{ - structs::{LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon}, - Dimensions, GeometryTrait, UnimplementedGeometryCollection, UnimplementedLine, - UnimplementedRect, UnimplementedTriangle, + structs::{ + GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, + }, + Dimensions, GeometryTrait, UnimplementedLine, UnimplementedRect, UnimplementedTriangle, }; #[derive(Clone, Debug, PartialEq)] @@ -19,7 +20,8 @@ pub enum Geometry { MultiLineString(MultiLineString), /// A multipolygon. MultiPolygon(MultiPolygon), - // GeometryCollection(GeometryCollection), + /// A geometry collection. + GeometryCollection(GeometryCollection), } impl Geometry @@ -35,7 +37,7 @@ where Self::MultiPoint(g) => g.dimension(), Self::MultiLineString(g) => g.dimension(), Self::MultiPolygon(g) => g.dimension(), - // Self::GeometryCollection(g) => g.dimension(), + Self::GeometryCollection(g) => g.dimension(), } } } @@ -67,8 +69,7 @@ impl GeometryTrait for Geometry { where Self: 'b; type GeometryCollectionType<'b> - = UnimplementedGeometryCollection - // = GeometryCollection + = GeometryCollection where Self: 'b; type RectType<'b> @@ -92,7 +93,7 @@ impl GeometryTrait for Geometry { Geometry::MultiPoint(geom) => geom.dim(), Geometry::MultiLineString(geom) => geom.dim(), Geometry::MultiPolygon(geom) => geom.dim(), - // Geometry::GeometryCollection(geom) => geom.dim(), + Geometry::GeometryCollection(geom) => geom.dim(), } } @@ -118,7 +119,7 @@ impl GeometryTrait for Geometry { Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), - // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), + Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), } } } @@ -150,8 +151,7 @@ impl GeometryTrait for &Geometry { where Self: 'b; type GeometryCollectionType<'b> - = UnimplementedGeometryCollection - // = GeometryCollection + = GeometryCollection where Self: 'b; type RectType<'b> @@ -175,7 +175,7 @@ impl GeometryTrait for &Geometry { Geometry::MultiPoint(geom) => geom.dim(), Geometry::MultiLineString(geom) => geom.dim(), Geometry::MultiPolygon(geom) => geom.dim(), - // Geometry::GeometryCollection(geom) => geom.dim(), + Geometry::GeometryCollection(geom) => geom.dim(), } } @@ -201,7 +201,7 @@ impl GeometryTrait for &Geometry { Geometry::MultiPoint(geom) => crate::GeometryType::MultiPoint(geom), Geometry::MultiLineString(geom) => crate::GeometryType::MultiLineString(geom), Geometry::MultiPolygon(geom) => crate::GeometryType::MultiPolygon(geom), - // Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), + Geometry::GeometryCollection(geom) => crate::GeometryType::GeometryCollection(geom), } } } @@ -237,8 +237,7 @@ macro_rules! impl_specialization { where Self: 'b; type GeometryCollectionType<'b> - = UnimplementedGeometryCollection - // = GeometryCollection + = GeometryCollection where Self: 'b; type RectType<'b> @@ -304,8 +303,7 @@ macro_rules! impl_specialization { where Self: 'b; type GeometryCollectionType<'b> - = UnimplementedGeometryCollection - // = GeometryCollection + = GeometryCollection where Self: 'b; type RectType<'b> @@ -352,4 +350,4 @@ impl_specialization!(Polygon); impl_specialization!(MultiPoint); impl_specialization!(MultiLineString); impl_specialization!(MultiPolygon); -// impl_specialization!(GeometryCollection); +impl_specialization!(GeometryCollection); diff --git a/geo-traits/src/structs/geometrycollection.rs b/geo-traits/src/structs/geometrycollection.rs new file mode 100644 index 0000000000..d0931c44cf --- /dev/null +++ b/geo-traits/src/structs/geometrycollection.rs @@ -0,0 +1,95 @@ +use crate::{structs::Geometry, Dimensions, GeometryCollectionTrait}; + +/// A parsed GeometryCollection. +#[derive(Clone, Debug, PartialEq)] +pub struct GeometryCollection { + pub(crate) geoms: Vec>, + pub(crate) dim: Dimensions, +} + +impl GeometryCollection { + /// Create a new GeometryCollection from a sequence of [Wkt]. + pub fn new(geoms: Vec>, dim: Dimensions) -> Self { + Self { geoms, dim } + } + + /// Create a new empty GeometryCollection. + pub fn empty(dim: Dimensions) -> Self { + Self::new(vec![], dim) + } + + /// Create a new GeometryCollection from a non-empty sequence of [Wkt]. + /// + /// This will infer the dimension from the first geometry, and will not validate that all + /// geometries have the same dimension. + /// + /// ## Errors + /// + /// If the input iterator is empty. + /// + /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting + /// to an [empty][Self::empty] geometry with specified dimension. + pub fn from_geometries(geoms: impl IntoIterator>) -> Option { + let geoms = geoms.into_iter().collect::>(); + if geoms.is_empty() { + None + } else { + let dim = geoms[0].dimension(); + Some(Self::new(geoms, dim)) + } + } + + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the underlying [Wkt] geometries. + pub fn geometries(&self) -> &[Geometry] { + &self.geoms + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.geoms, self.dim) + } +} + +impl From> for Geometry +where + T: Copy, +{ + fn from(value: GeometryCollection) -> Self { + Geometry::GeometryCollection(value) + } +} + +impl GeometryCollectionTrait for GeometryCollection { + type GeometryType<'a> + = &'a Geometry + where + Self: 'a; + + fn num_geometries(&self) -> usize { + self.geoms.len() + } + + unsafe fn geometry_unchecked(&self, i: usize) -> Self::GeometryType<'_> { + self.geoms.get_unchecked(i) + } +} + +impl GeometryCollectionTrait for &GeometryCollection { + type GeometryType<'a> + = &'a Geometry + where + Self: 'a; + + fn num_geometries(&self) -> usize { + self.geoms.len() + } + + unsafe fn geometry_unchecked(&self, i: usize) -> Self::GeometryType<'_> { + self.geoms.get_unchecked(i) + } +} diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs index 7885bcdba5..c6ab358b04 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/linestring.rs @@ -5,7 +5,7 @@ use crate::{ /// A parsed LineString. #[derive(Clone, Debug, PartialEq)] -pub struct LineString { +pub struct LineString { pub(crate) coords: Vec>, pub(crate) dim: Dimensions, } diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs index 9ea7ccfd0e..45407b58fa 100644 --- a/geo-traits/src/structs/mod.rs +++ b/geo-traits/src/structs/mod.rs @@ -3,7 +3,7 @@ pub use self::coord::Coord; pub use self::geometry::Geometry; // pub use self::geometry_type::GeometryType; -// pub use self::geometrycollection::GeometryCollection; +pub use self::geometrycollection::GeometryCollection; pub use self::linestring::LineString; pub use self::multilinestring::MultiLineString; pub use self::multipoint::MultiPoint; @@ -14,7 +14,7 @@ pub use self::polygon::Polygon; mod coord; mod geometry; // mod geometry_type; -// mod geometrycollection; +mod geometrycollection; mod linestring; mod multilinestring; mod multipoint; diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index 4836fe07a8..2fce6a5be2 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -5,7 +5,7 @@ use crate::{ /// A parsed MultiLineString. #[derive(Clone, Debug, PartialEq)] -pub struct MultiLineString { +pub struct MultiLineString { pub(crate) line_strings: Vec>, pub(crate) dim: Dimensions, } diff --git a/geo-traits/src/structs/multipolygon.rs b/geo-traits/src/structs/multipolygon.rs index c6a2a1927f..07753397f1 100644 --- a/geo-traits/src/structs/multipolygon.rs +++ b/geo-traits/src/structs/multipolygon.rs @@ -5,7 +5,7 @@ use crate::{ /// A parsed MultiPolygon. #[derive(Clone, Debug, PartialEq)] -pub struct MultiPolygon { +pub struct MultiPolygon { pub(crate) polygons: Vec>, pub(crate) dim: Dimensions, } diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index ed33ea32d2..4e52aa5c1d 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -4,7 +4,7 @@ use super::LineString; /// A parsed Polygon. #[derive(Clone, Debug, PartialEq)] -pub struct Polygon { +pub struct Polygon { pub(crate) rings: Vec>, pub(crate) dim: Dimensions, } From d72b832e07a376a589a8fe252edbd6b7fb691631 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 16:57:57 +0900 Subject: [PATCH 12/43] Tweak --- geo-traits/src/structs/mod.rs | 2 -- geo-traits/src/structs/polygon.rs | 7 ++++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs index 45407b58fa..d365b60658 100644 --- a/geo-traits/src/structs/mod.rs +++ b/geo-traits/src/structs/mod.rs @@ -2,7 +2,6 @@ pub use self::coord::Coord; pub use self::geometry::Geometry; -// pub use self::geometry_type::GeometryType; pub use self::geometrycollection::GeometryCollection; pub use self::linestring::LineString; pub use self::multilinestring::MultiLineString; @@ -13,7 +12,6 @@ pub use self::polygon::Polygon; mod coord; mod geometry; -// mod geometry_type; mod geometrycollection; mod linestring; mod multilinestring; diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index 4e52aa5c1d..3c90ff03db 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -1,6 +1,7 @@ -use crate::{structs::Geometry, Dimensions, PolygonTrait}; - -use super::LineString; +use crate::{ + structs::{Geometry, LineString}, + Dimensions, PolygonTrait, +}; /// A parsed Polygon. #[derive(Clone, Debug, PartialEq)] From b2c395340a13ff0ba33005cc2b60efd93d577855 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 17:01:55 +0900 Subject: [PATCH 13/43] Tweak documents --- geo-traits/src/structs/geometry.rs | 2 +- geo-traits/src/structs/geometrycollection.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index afd65907ca..ed4f6ea95b 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -6,7 +6,7 @@ use crate::{ }; #[derive(Clone, Debug, PartialEq)] -/// All supported WKT geometry [`types`] +/// All supported geometry pub enum Geometry { /// A point. Point(Point), diff --git a/geo-traits/src/structs/geometrycollection.rs b/geo-traits/src/structs/geometrycollection.rs index d0931c44cf..91b93a1d30 100644 --- a/geo-traits/src/structs/geometrycollection.rs +++ b/geo-traits/src/structs/geometrycollection.rs @@ -8,7 +8,7 @@ pub struct GeometryCollection { } impl GeometryCollection { - /// Create a new GeometryCollection from a sequence of [Wkt]. + /// Create a new GeometryCollection from a sequence of [Geometry]. pub fn new(geoms: Vec>, dim: Dimensions) -> Self { Self { geoms, dim } } @@ -18,7 +18,7 @@ impl GeometryCollection { Self::new(vec![], dim) } - /// Create a new GeometryCollection from a non-empty sequence of [Wkt]. + /// Create a new GeometryCollection from a non-empty sequence of [Geometry]. /// /// This will infer the dimension from the first geometry, and will not validate that all /// geometries have the same dimension. @@ -44,7 +44,7 @@ impl GeometryCollection { self.dim } - /// Access the underlying [Wkt] geometries. + /// Access the underlying geometries. pub fn geometries(&self) -> &[Geometry] { &self.geoms } From f8175b8b3c23e91386667e6098d81e490a698832 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 17:53:39 +0900 Subject: [PATCH 14/43] Add test --- geo-traits/src/structs/coord.rs | 80 +++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index 550185fc9d..470b06d036 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -137,3 +137,83 @@ impl CoordTrait for &Coord { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::CoordTrait; + + #[derive(Clone, Copy)] + struct DummyCoord { + dims: Dimensions, + coords: [T; 4], + } + + impl DummyCoord { + fn new(coords: [T; 4], dims: Dimensions) -> Self { + Self { dims, coords } + } + } + + impl CoordTrait for DummyCoord { + type T = T; + + fn dim(&self) -> Dimensions { + self.dims + } + + fn nth_or_panic(&self, n: usize) -> Self::T { + assert!(n < self.dims.size()); + self.coords[n] + } + + fn x(&self) -> Self::T { + self.coords[0] + } + + fn y(&self) -> Self::T { + self.coords[1] + } + } + + #[test] + fn coord_new_from_tuple_xy() { + let coord = Coord::new((1_i32, 2_i32)); + assert_eq!(coord.x, 1); + assert_eq!(coord.y, 2); + assert_eq!(coord.z, None); + assert_eq!(coord.m, None); + assert_eq!(CoordTrait::dim(&coord), Dimensions::Xy); + } + + #[test] + fn coord_new_from_xyz() { + let source = DummyCoord::new([1.0_f64, 2.0, 3.0, 0.0], Dimensions::Xyz); + let coord = Coord::new(source); + assert_eq!(coord.z, Some(3.0)); + assert_eq!(coord.m, None); + assert_eq!(coord.nth_or_panic(2), 3.0); + assert_eq!(CoordTrait::dim(&coord), Dimensions::Xyz); + } + + #[test] + fn coord_new_from_xym() { + let source = DummyCoord::new([4_u32, 5, 6, 0], Dimensions::Xym); + let coord = Coord::new(source); + assert_eq!(coord.z, None); + assert_eq!(coord.m, Some(6)); + assert_eq!(coord.nth_or_panic(2), 6); + assert_eq!(CoordTrait::dim(&coord), Dimensions::Xym); + } + + #[test] + fn coord_new_from_xyzm() { + let source = DummyCoord::new([7_i16, 8, 9, 10], Dimensions::Xyzm); + let coord = Coord::new(source); + assert_eq!(coord.z, Some(9)); + assert_eq!(coord.m, Some(10)); + assert_eq!(coord.nth_or_panic(2), 9); + assert_eq!(coord.nth_or_panic(3), 10); + assert_eq!(CoordTrait::dim(&coord), Dimensions::Xyzm); + } +} From 2953414866ee2b47d78fc412126061dbf24c9b3f Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 17:54:02 +0900 Subject: [PATCH 15/43] Add conversion --- geo-traits/src/structs/linestring.rs | 44 ++++++++++++++--------- geo-traits/src/structs/point.rs | 33 ++++++++++++------ geo-traits/src/structs/polygon.rs | 52 ++++++++++++++++++---------- 3 files changed, 82 insertions(+), 47 deletions(-) diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs index c6ab358b04..9bbf490204 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/linestring.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Coord, Geometry}, - CoordTrait as _, Dimensions, LineStringTrait, + CoordTrait, Dimensions, LineStringTrait, }; /// A parsed LineString. @@ -21,7 +21,24 @@ impl LineString { Self::new(vec![], dim) } - /// Create a new LineString from a non-empty sequence of [Coord]. + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the coordinates of this LineString. + pub fn coords(&self) -> &[Coord] { + &self.coords + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.coords, self.dim) + } + + // Conversion from geo-traits' traits + + /// Create a new LineString from a non-empty sequence of objects implementing [CoordTrait]. /// /// This will infer the dimension from the first coordinate, and will not validate that all /// coordinates have the same dimension. @@ -30,8 +47,11 @@ impl LineString { /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. - pub fn from_coords(coords: impl IntoIterator>) -> Option { - let coords = coords.into_iter().collect::>(); + pub fn from_coords(coords: impl IntoIterator>) -> Option { + let coords = coords + .into_iter() + .map(|c| Coord::new(c)) + .collect::>(); if coords.is_empty() { None } else { @@ -40,19 +60,9 @@ impl LineString { } } - /// Return the [Dimensions] of this geometry. - pub fn dimension(&self) -> Dimensions { - self.dim - } - - /// Access the coordinates of this LineString. - pub fn coords(&self) -> &[Coord] { - &self.coords - } - - /// Consume self and return the inner parts. - pub fn into_inner(self) -> (Vec>, Dimensions) { - (self.coords, self.dim) + /// Create a new LineString from an objects implementing [LineStringTrait]. + pub fn from_linestring(linestring: impl LineStringTrait) -> Self { + Self::from_coords(linestring.coords()).unwrap() } } diff --git a/geo-traits/src/structs/point.rs b/geo-traits/src/structs/point.rs index 4ce7081907..726aab33f5 100644 --- a/geo-traits/src/structs/point.rs +++ b/geo-traits/src/structs/point.rs @@ -1,6 +1,6 @@ use crate::dimension::Dimensions; use crate::structs::Coord; -use crate::{CoordTrait as _, PointTrait}; +use crate::{CoordTrait, PointTrait}; /// A parsed Point. #[derive(Clone, Debug, PartialEq)] @@ -15,16 +15,6 @@ impl Point { Self { coord, dim } } - /// Create a new point from a valid [Coord]. - /// - /// This infers the dimension from the coordinate. - pub fn from_coord(coord: Coord) -> Self { - Self { - dim: coord.dim(), - coord: Some(coord), - } - } - /// Create a new empty point. pub fn empty(dim: Dimensions) -> Self { Self::new(None, dim) @@ -44,6 +34,27 @@ impl Point { pub fn into_inner(self) -> (Option>, Dimensions) { (self.coord, self.dim) } + + // Conversion from geo-traits' traits + + /// Create a new point from an object implementing [CoordTrait]. + /// + /// This infers the dimension from the coordinate. + pub fn from_coord(coord: impl CoordTrait) -> Self { + Self { + dim: coord.dim(), + coord: Some(Coord::new(coord)), + } + } + + /// Create a new point from an object implementing [PointTrait]. + /// + /// This infers the dimension from the coordinate. + pub fn from_point(point: impl PointTrait) -> Self { + let dim = point.dim(); + let coord = point.coord().map(|c| Coord::new(c)); + Self { coord, dim } + } } impl From> for super::geometry::Geometry diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index 3c90ff03db..13a52eeefa 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Geometry, LineString}, - Dimensions, PolygonTrait, + Dimensions, LineStringTrait, PolygonTrait, }; /// A parsed Polygon. @@ -21,7 +21,26 @@ impl Polygon { Self::new(vec![], dim) } - /// Create a new polygon from a non-empty sequence of [LineString]. + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the inner rings. + /// + /// The first ring is defined to be the exterior ring, and the rest are interior rings. + pub fn rings(&self) -> &[LineString] { + &self.rings + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.rings, self.dim) + } + + // Conversion from geo-traits' traits + + /// Create a new polygon from a non-empty sequence of objects implementing [LineStringTrait]. /// /// This will infer the dimension from the first line string, and will not validate that all /// line strings have the same dimension. @@ -30,8 +49,13 @@ impl Polygon { /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. - pub fn from_rings(rings: impl IntoIterator>) -> Option { - let rings = rings.into_iter().collect::>(); + pub fn from_rings( + rings: impl IntoIterator>, + ) -> Option { + let rings = rings + .into_iter() + .map(|l| LineString::from_linestring(l)) + .collect::>(); if rings.is_empty() { None } else { @@ -40,21 +64,11 @@ impl Polygon { } } - /// Return the [Dimensions] of this geometry. - pub fn dimension(&self) -> Dimensions { - self.dim - } - - /// Access the inner rings. - /// - /// The first ring is defined to be the exterior ring, and the rest are interior rings. - pub fn rings(&self) -> &[LineString] { - &self.rings - } - - /// Consume self and return the inner parts. - pub fn into_inner(self) -> (Vec>, Dimensions) { - (self.rings, self.dim) + /// Create a new polygon from an object implementing [PolygonTrait]. + pub fn from_polygon(polygon: impl PolygonTrait) -> Self { + let exterior = polygon.exterior().into_iter(); + let other = polygon.interiors(); + Self::from_rings(exterior.chain(other)).unwrap() } } From 9c1519e235a42369747bcc7378d8c3aaa484e82f Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 17:58:29 +0900 Subject: [PATCH 16/43] Add tests --- geo-traits/src/structs/linestring.rs | 90 ++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs index 9bbf490204..421170abdf 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/linestring.rs @@ -104,3 +104,93 @@ impl<'a, T: Copy> LineStringTrait for &'a LineString { self.coords.get_unchecked(i) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::LineStringTrait; + + #[test] + fn empty_linestring_preserves_dimension() { + let ls: LineString = LineString::empty(Dimensions::Xym); + assert_eq!(ls.dimension(), Dimensions::Xym); + assert!(ls.coords().is_empty()); + } + + #[test] + fn from_coords_infers_dimension() { + let coords = vec![ + Coord { + x: 1, + y: 2, + z: Some(3), + m: None, + }, + Coord { + x: 4, + y: 5, + z: Some(6), + m: None, + }, + ]; + let ls = LineString::from_coords(coords.clone()).expect("coords are non-empty"); + assert_eq!(ls.dimension(), Dimensions::Xyz); + assert_eq!(ls.coords(), coords.as_slice()); + } + + #[test] + fn from_coords_returns_none_for_empty_iter() { + let empty = std::iter::empty::>(); + assert!(LineString::from_coords(empty).is_none()); + } + + #[test] + fn from_linestring_copies_source() { + let original = LineString::new( + vec![ + Coord { + x: 1, + y: 2, + z: None, + m: Some(7), + }, + Coord { + x: 3, + y: 4, + z: None, + m: Some(8), + }, + ], + Dimensions::Xym, + ); + let converted = LineString::from_linestring(&original); + assert_eq!(converted.dimension(), original.dimension()); + assert_eq!(converted.coords(), original.coords()); + } + + #[test] + fn linestring_trait_coord_access() { + let ls = LineString::new( + vec![ + Coord { + x: 10, + y: 11, + z: None, + m: None, + }, + Coord { + x: 12, + y: 13, + z: None, + m: None, + }, + ], + Dimensions::Xy, + ); + + let ls_ref = &ls; + let second = ls_ref.coord(1).expect("second coord exists"); + assert_eq!(second, &ls.coords()[1]); + assert!(ls_ref.coord(5).is_none()); + } +} From e277d90bace81eb47202aae060d32b750077f2e6 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 18:01:13 +0900 Subject: [PATCH 17/43] Add tests --- geo-traits/src/structs/polygon.rs | 153 ++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index 13a52eeefa..d4baa18214 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -118,3 +118,156 @@ impl PolygonTrait for &Polygon { self.rings.get_unchecked(i + 1) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::structs::Coord; + use crate::PolygonTrait; + + fn square_ring_xy(offset: T, step: T) -> LineString + where + T: From + + Copy + + std::ops::Add + + std::ops::Sub, + { + LineString::new( + vec![ + Coord { + x: offset, + y: offset, + z: None, + m: None, + }, + Coord { + x: offset + step, + y: offset, + z: None, + m: None, + }, + Coord { + x: offset + step, + y: offset + step, + z: None, + m: None, + }, + Coord { + x: offset, + y: offset + step, + z: None, + m: None, + }, + Coord { + x: offset, + y: offset, + z: None, + m: None, + }, + ], + Dimensions::Xy, + ) + } + + #[test] + fn empty_polygon_preserves_dimension() { + let polygon: Polygon = Polygon::empty(Dimensions::Xyzm); + assert_eq!(polygon.dimension(), Dimensions::Xyzm); + assert!(polygon.rings().is_empty()); + } + + #[test] + fn from_rings_infers_dimension() { + let exterior = LineString::new( + vec![ + Coord { + x: 0, + y: 0, + z: None, + m: Some(1), + }, + Coord { + x: 2, + y: 0, + z: None, + m: Some(2), + }, + Coord { + x: 1, + y: 2, + z: None, + m: Some(3), + }, + ], + Dimensions::Xym, + ); + + let polygon = Polygon::from_rings(vec![exterior.clone()]).expect("non-empty rings"); + assert_eq!(polygon.dimension(), Dimensions::Xym); + assert_eq!(polygon.rings(), &[exterior]); + } + + #[test] + fn from_rings_returns_none_for_empty_iter() { + let empty = std::iter::empty::>(); + assert!(Polygon::from_rings(empty).is_none()); + } + + #[test] + fn from_polygon_round_trips_rings() { + let exterior = LineString::new( + vec![ + Coord { + x: 0.0, + y: 0.0, + z: Some(0.0), + m: None, + }, + Coord { + x: 3.0, + y: 0.0, + z: Some(1.0), + m: None, + }, + Coord { + x: 0.0, + y: 4.0, + z: Some(2.0), + m: None, + }, + Coord { + x: 0.0, + y: 0.0, + z: Some(0.0), + m: None, + }, + ], + Dimensions::Xyz, + ); + let interior = square_ring_xy(1.0, 1.0); + let original = Polygon::new(vec![exterior.clone(), interior.clone()], Dimensions::Xyz); + + let converted = Polygon::from_polygon(&original); + assert_eq!(converted, original); + assert_eq!(converted.rings(), &[exterior, interior]); + } + + #[test] + fn polygon_trait_accessors_work_for_owned_and_borrowed() { + let exterior = square_ring_xy(0_i32, 2_i32); + let interior = square_ring_xy(1_i32, 1_i32); + let polygon = Polygon::new(vec![exterior.clone(), interior.clone()], Dimensions::Xy); + + let ext = polygon.exterior().expect("exterior exists"); + assert_eq!(ext, &exterior); + assert_eq!(polygon.num_interiors(), 1); + assert_eq!(polygon.interior(0), Some(&interior)); + assert!(polygon.interior(1).is_none()); + + let borrowed = &polygon; + let borrowed_ext = borrowed.exterior().expect("borrowed exterior exists"); + assert_eq!(borrowed_ext, &exterior); + assert_eq!(borrowed.num_interiors(), 1); + assert_eq!(borrowed.interior(0), Some(&interior)); + } +} From d2bb97d1a811c1f1cb012242763fc3c7a04bf420 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 18:01:24 +0900 Subject: [PATCH 18/43] Add more conversions --- geo-traits/src/structs/multilinestring.rs | 49 +++++++++++++++-------- geo-traits/src/structs/multipoint.rs | 44 ++++++++++++-------- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index 2fce6a5be2..60332422cb 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Geometry, LineString}, - Dimensions, MultiLineStringTrait, + Dimensions, LineStringTrait, MultiLineStringTrait, }; /// A parsed MultiLineString. @@ -21,7 +21,24 @@ impl MultiLineString { Self::new(vec![], dim) } - /// Create a new MultiLineString from a non-empty sequence of [LineString]. + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the inner line strings. + pub fn line_strings(&self) -> &[LineString] { + &self.line_strings + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.line_strings, self.dim) + } + + // Conversion from geo-traits' traits + + /// Create a new MultiLineString from a non-empty sequence of objects implementing [LineStringTrait]. /// /// This will infer the dimension from the first line string, and will not validate that all /// line strings have the same dimension. @@ -31,9 +48,12 @@ impl MultiLineString { /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. pub fn from_line_strings( - line_strings: impl IntoIterator>, + line_strings: impl IntoIterator>, ) -> Option { - let line_strings = line_strings.into_iter().collect::>(); + let line_strings = line_strings + .into_iter() + .map(|l| LineString::from_linestring(l)) + .collect::>(); if line_strings.is_empty() { None } else { @@ -42,19 +62,14 @@ impl MultiLineString { } } - /// Return the [Dimensions] of this geometry. - pub fn dimension(&self) -> Dimensions { - self.dim - } - - /// Access the inner line strings. - pub fn line_strings(&self) -> &[LineString] { - &self.line_strings - } - - /// Consume self and return the inner parts. - pub fn into_inner(self) -> (Vec>, Dimensions) { - (self.line_strings, self.dim) + /// Create a new MultiLineString from an objects implementing [MultiLineStringTrait]. + pub fn from_multilinestring(multilinestring: impl MultiLineStringTrait) -> Self { + let line_strings = multilinestring + .line_strings() + .map(|l| LineString::from_linestring(l)) + .collect::>(); + let dim = line_strings[0].dimension(); + Self::new(line_strings, dim) } } diff --git a/geo-traits/src/structs/multipoint.rs b/geo-traits/src/structs/multipoint.rs index bd9f48b7d6..875c5b36ef 100644 --- a/geo-traits/src/structs/multipoint.rs +++ b/geo-traits/src/structs/multipoint.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Geometry, Point}, - Dimensions, MultiPointTrait, + Dimensions, MultiPointTrait, PointTrait, }; /// A parsed MultiPoint. @@ -21,7 +21,24 @@ impl MultiPoint { Self::new(vec![], dim) } - /// Create a new MultiPoint from a non-empty sequence of [Point]. + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the inner points. + pub fn points(&self) -> &[Point] { + &self.points + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.points, self.dim) + } + + // Conversion from geo-traits' traits + + /// Create a new MultiPoint from a non-empty sequence of objects implementing [PointTrait]. /// /// This will infer the dimension from the first point, and will not validate that all /// points have the same dimension. @@ -30,8 +47,11 @@ impl MultiPoint { /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. - pub fn from_points(points: impl IntoIterator>) -> Option { - let points = points.into_iter().collect::>(); + pub fn from_points(points: impl IntoIterator>) -> Option { + let points = points + .into_iter() + .map(|p| Point::from_point(p)) + .collect::>(); if points.is_empty() { None } else { @@ -40,19 +60,9 @@ impl MultiPoint { } } - /// Return the [Dimensions] of this geometry. - pub fn dimension(&self) -> Dimensions { - self.dim - } - - /// Access the inner points. - pub fn points(&self) -> &[Point] { - &self.points - } - - /// Consume self and return the inner parts. - pub fn into_inner(self) -> (Vec>, Dimensions) { - (self.points, self.dim) + /// Create a new MultiPoint from an objects implementing [MultiPointTrait]. + pub fn from_multipoint(multipoint: impl MultiPointTrait) -> Self { + Self::from_points(multipoint.points()).unwrap() } } From 91ce9bb7f583ae348a874062a4fc2ddbb51c0e8f Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 18:04:28 +0900 Subject: [PATCH 19/43] Update polygon.rs --- geo-traits/src/structs/polygon.rs | 46 ++++--------------------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index d4baa18214..3886bb0a79 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -125,13 +125,7 @@ mod tests { use crate::structs::Coord; use crate::PolygonTrait; - fn square_ring_xy(offset: T, step: T) -> LineString - where - T: From - + Copy - + std::ops::Add - + std::ops::Sub, - { + fn square_ring_xy(offset: i32, step: i32) -> LineString { LineString::new( vec![ Coord { @@ -215,37 +209,9 @@ mod tests { #[test] fn from_polygon_round_trips_rings() { - let exterior = LineString::new( - vec![ - Coord { - x: 0.0, - y: 0.0, - z: Some(0.0), - m: None, - }, - Coord { - x: 3.0, - y: 0.0, - z: Some(1.0), - m: None, - }, - Coord { - x: 0.0, - y: 4.0, - z: Some(2.0), - m: None, - }, - Coord { - x: 0.0, - y: 0.0, - z: Some(0.0), - m: None, - }, - ], - Dimensions::Xyz, - ); - let interior = square_ring_xy(1.0, 1.0); - let original = Polygon::new(vec![exterior.clone(), interior.clone()], Dimensions::Xyz); + let exterior = square_ring_xy(0, 4); + let interior = square_ring_xy(1, 2); + let original = Polygon::new(vec![exterior.clone(), interior.clone()], Dimensions::Xy); let converted = Polygon::from_polygon(&original); assert_eq!(converted, original); @@ -254,8 +220,8 @@ mod tests { #[test] fn polygon_trait_accessors_work_for_owned_and_borrowed() { - let exterior = square_ring_xy(0_i32, 2_i32); - let interior = square_ring_xy(1_i32, 1_i32); + let exterior = square_ring_xy(0, 2); + let interior = square_ring_xy(1, 1); let polygon = Polygon::new(vec![exterior.clone(), interior.clone()], Dimensions::Xy); let ext = polygon.exterior().expect("exterior exists"); From 1353dda1443e7c273eebae7d36d339af0a07f909 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 18:04:58 +0900 Subject: [PATCH 20/43] Multipoint, MultiPolygon --- geo-traits/src/structs/multipoint.rs | 2 + geo-traits/src/structs/multipolygon.rs | 51 +++++++++++++++++--------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/geo-traits/src/structs/multipoint.rs b/geo-traits/src/structs/multipoint.rs index 875c5b36ef..26b94fa0e6 100644 --- a/geo-traits/src/structs/multipoint.rs +++ b/geo-traits/src/structs/multipoint.rs @@ -104,3 +104,5 @@ impl MultiPointTrait for &MultiPoint { self.points.get_unchecked(i) } } + +// TODO: add tests diff --git a/geo-traits/src/structs/multipolygon.rs b/geo-traits/src/structs/multipolygon.rs index 07753397f1..f7560637d4 100644 --- a/geo-traits/src/structs/multipolygon.rs +++ b/geo-traits/src/structs/multipolygon.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Geometry, Polygon}, - Dimensions, MultiPolygonTrait, + Dimensions, MultiPolygonTrait, PolygonTrait, }; /// A parsed MultiPolygon. @@ -21,7 +21,24 @@ impl MultiPolygon { Self::new(vec![], dim) } - /// Create a new MultiPolygon from a non-empty sequence of [Polygon]. + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the inner polygons. + pub fn polygons(&self) -> &[Polygon] { + &self.polygons + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.polygons, self.dim) + } + + // Conversion from geo-traits' traits + + /// Create a new MultiPolygon from a non-empty sequence of objects implementing [PolygonTrait]. /// /// This will infer the dimension from the first polygon, and will not validate that all /// polygons have the same dimension. @@ -30,8 +47,13 @@ impl MultiPolygon { /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. - pub fn from_polygons(polygons: impl IntoIterator>) -> Option { - let polygons = polygons.into_iter().collect::>(); + pub fn from_polygons( + polygons: impl IntoIterator>, + ) -> Option { + let polygons = polygons + .into_iter() + .map(|p| Polygon::from_polygon(p)) + .collect::>(); if polygons.is_empty() { None } else { @@ -40,19 +62,14 @@ impl MultiPolygon { } } - /// Return the [Dimensions] of this geometry. - pub fn dimension(&self) -> Dimensions { - self.dim - } - - /// Access the inner polygons. - pub fn polygons(&self) -> &[Polygon] { - &self.polygons - } - - /// Consume self and return the inner parts. - pub fn into_inner(self) -> (Vec>, Dimensions) { - (self.polygons, self.dim) + /// Create a new MultiPolygon from an objects implementing [MultiPolygonTrait]. + pub fn from_multipolygon(multipolygon: impl MultiPolygonTrait) -> Self { + let polygons = multipolygon + .polygons() + .map(|p| Polygon::from_polygon(p)) + .collect::>(); + let dim = polygons[0].dimension(); + Self::new(polygons, dim) } } From ab35e7628a8caa11547d6650ce71888a5df36c0a Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 18:14:49 +0900 Subject: [PATCH 21/43] Add more tests --- geo-traits/src/structs/multilinestring.rs | 110 ++++++++++++++++++ geo-traits/src/structs/multipoint.rs | 109 +++++++++++++++++- geo-traits/src/structs/multipolygon.rs | 129 ++++++++++++++++++++++ 3 files changed, 347 insertions(+), 1 deletion(-) diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index 60332422cb..e1a2cced1a 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -111,3 +111,113 @@ impl MultiLineStringTrait for &MultiLineString { self.line_strings.get_unchecked(i) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::structs::Coord; + use crate::{LineStringTrait, MultiLineStringTrait}; + + fn line_xy(coords: &[(i32, i32)]) -> LineString { + LineString::new( + coords + .iter() + .map(|&(x, y)| Coord { + x, + y, + z: None, + m: None, + }) + .collect(), + Dimensions::Xy, + ) + } + + #[test] + fn empty_multilinestring_preserves_dimension() { + let mls: MultiLineString = MultiLineString::empty(Dimensions::Xyz); + assert_eq!(mls.dimension(), Dimensions::Xyz); + assert!(mls.line_strings().is_empty()); + } + + #[test] + fn from_line_strings_infers_dimension() { + let lines = vec![ + LineString::new( + vec![ + Coord { + x: 0, + y: 0, + z: Some(1), + m: None, + }, + Coord { + x: 1, + y: 1, + z: Some(2), + m: None, + }, + ], + Dimensions::Xyz, + ), + LineString::new( + vec![ + Coord { + x: 2, + y: 3, + z: Some(4), + m: None, + }, + Coord { + x: 5, + y: 8, + z: Some(9), + m: None, + }, + ], + Dimensions::Xyz, + ), + ]; + + let mls = + MultiLineString::from_line_strings(lines.clone()).expect("line strings are non-empty"); + assert_eq!(mls.dimension(), Dimensions::Xyz); + assert_eq!(mls.line_strings(), lines.as_slice()); + } + + #[test] + fn from_line_strings_returns_none_for_empty_iter() { + let empty = std::iter::empty::>(); + assert!(MultiLineString::from_line_strings(empty).is_none()); + } + + #[test] + fn from_multilinestring_copies_source() { + let lines = vec![ + line_xy(&[(0, 0), (1, 0), (1, 1)]), + line_xy(&[(2, 2), (3, 3), (3, 4)]), + ]; + let original = MultiLineString::new(lines.clone(), Dimensions::Xy); + let converted = MultiLineString::from_multilinestring(&original); + + assert_eq!(converted.dimension(), original.dimension()); + assert_eq!(converted.line_strings(), original.line_strings()); + } + + #[test] + fn multilinestring_trait_accessors_work() { + let lines = vec![ + line_xy(&[(0, 0), (0, 1)]), + line_xy(&[(1, 1), (2, 2)]), + ]; + let mls = MultiLineString::new(lines.clone(), Dimensions::Xy); + + assert_eq!(mls.num_line_strings(), 2); + assert_eq!(mls.line_string(0), Some(&lines[0])); + assert!(mls.line_string(5).is_none()); + + let borrowed = &mls; + let second = borrowed.line_string(1).expect("second line exists"); + assert_eq!(second, &lines[1]); + } +} diff --git a/geo-traits/src/structs/multipoint.rs b/geo-traits/src/structs/multipoint.rs index 26b94fa0e6..f2de3b1100 100644 --- a/geo-traits/src/structs/multipoint.rs +++ b/geo-traits/src/structs/multipoint.rs @@ -105,4 +105,111 @@ impl MultiPointTrait for &MultiPoint { } } -// TODO: add tests +#[cfg(test)] +mod tests { + use super::*; + use crate::structs::Coord; + use crate::MultiPointTrait; + + #[test] + fn empty_multipoint_preserves_dimension() { + let mp: MultiPoint = MultiPoint::empty(Dimensions::Xym); + assert_eq!(mp.dimension(), Dimensions::Xym); + assert!(mp.points().is_empty()); + } + + #[test] + fn from_points_infers_dimension() { + let points = vec![ + Point::new( + Some(Coord { + x: 1, + y: 2, + z: Some(3), + m: None, + }), + Dimensions::Xyz, + ), + Point::new( + Some(Coord { + x: 4, + y: 5, + z: Some(6), + m: None, + }), + Dimensions::Xyz, + ), + ]; + + let mp = MultiPoint::from_points(points.clone()).expect("points are non-empty"); + assert_eq!(mp.dimension(), Dimensions::Xyz); + assert_eq!(mp.points(), points.as_slice()); + } + + #[test] + fn from_points_returns_none_for_empty_iter() { + let empty = std::iter::empty::>(); + assert!(MultiPoint::from_points(empty).is_none()); + } + + #[test] + fn from_multipoint_copies_source() { + let points = vec![ + Point::new( + Some(Coord { + x: 10, + y: 11, + z: None, + m: Some(1), + }), + Dimensions::Xym, + ), + Point::new( + Some(Coord { + x: 12, + y: 13, + z: None, + m: Some(2), + }), + Dimensions::Xym, + ), + ]; + let original = MultiPoint::new(points.clone(), Dimensions::Xym); + let converted = MultiPoint::from_multipoint(&original); + + assert_eq!(converted.dimension(), original.dimension()); + assert_eq!(converted.points(), original.points()); + } + + #[test] + fn multipoint_trait_point_access() { + let mp = MultiPoint::new( + vec![ + Point::new( + Some(Coord { + x: 7, + y: 8, + z: None, + m: None, + }), + Dimensions::Xy, + ), + Point::new( + Some(Coord { + x: 9, + y: 10, + z: None, + m: None, + }), + Dimensions::Xy, + ), + ], + Dimensions::Xy, + ); + + let mp_ref = ∓ + let first = mp_ref.point(0).expect("first point exists"); + assert_eq!(first, &mp.points()[0]); + assert!(mp_ref.point(5).is_none()); + } +} diff --git a/geo-traits/src/structs/multipolygon.rs b/geo-traits/src/structs/multipolygon.rs index f7560637d4..0e5b264bc1 100644 --- a/geo-traits/src/structs/multipolygon.rs +++ b/geo-traits/src/structs/multipolygon.rs @@ -111,3 +111,132 @@ impl MultiPolygonTrait for &MultiPolygon { self.polygons.get_unchecked(i) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::structs::{Coord, LineString}; + use crate::{MultiPolygonTrait, PolygonTrait}; + + fn square_ring_xy(offset: i32, size: i32) -> LineString { + LineString::new( + vec![ + Coord { + x: offset, + y: offset, + z: None, + m: None, + }, + Coord { + x: offset + size, + y: offset, + z: None, + m: None, + }, + Coord { + x: offset + size, + y: offset + size, + z: None, + m: None, + }, + Coord { + x: offset, + y: offset + size, + z: None, + m: None, + }, + Coord { + x: offset, + y: offset, + z: None, + m: None, + }, + ], + Dimensions::Xy, + ) + } + + #[test] + fn empty_multipolygon_preserves_dimension() { + let mp: MultiPolygon = MultiPolygon::empty(Dimensions::Xyzm); + assert_eq!(mp.dimension(), Dimensions::Xyzm); + assert!(mp.polygons().is_empty()); + } + + #[test] + fn from_polygons_infers_dimension() { + let exterior = LineString::new( + vec![ + Coord { + x: 0, + y: 0, + z: None, + m: Some(1), + }, + Coord { + x: 3, + y: 0, + z: None, + m: Some(2), + }, + Coord { + x: 3, + y: 3, + z: None, + m: Some(3), + }, + Coord { + x: 0, + y: 0, + z: None, + m: Some(4), + }, + ], + Dimensions::Xym, + ); + let polygon = Polygon::new(vec![exterior.clone()], Dimensions::Xym); + let mp = + MultiPolygon::from_polygons(vec![polygon.clone()]).expect("polygons are non-empty"); + + assert_eq!(mp.dimension(), Dimensions::Xym); + assert_eq!(mp.polygons(), &[polygon]); + } + + #[test] + fn from_polygons_returns_none_for_empty_iter() { + let empty = std::iter::empty::>(); + assert!(MultiPolygon::from_polygons(empty).is_none()); + } + + #[test] + fn from_multipolygon_copies_source() { + let polygon_a = Polygon::new( + vec![square_ring_xy(0, 2)], + Dimensions::Xy, + ); + let polygon_b = Polygon::new( + vec![square_ring_xy(3, 2)], + Dimensions::Xy, + ); + let original = MultiPolygon::new(vec![polygon_a.clone(), polygon_b.clone()], Dimensions::Xy); + + let converted = MultiPolygon::from_multipolygon(&original); + assert_eq!(converted.dimension(), original.dimension()); + assert_eq!(converted.polygons(), original.polygons()); + } + + #[test] + fn multipolygon_trait_accessors_work() { + let poly_a = Polygon::new(vec![square_ring_xy(0, 3)], Dimensions::Xy); + let poly_b = Polygon::new(vec![square_ring_xy(5, 1)], Dimensions::Xy); + let mp = MultiPolygon::new(vec![poly_a.clone(), poly_b.clone()], Dimensions::Xy); + + assert_eq!(mp.num_polygons(), 2); + assert_eq!(mp.polygon(0), Some(&poly_a)); + assert!(mp.polygon(3).is_none()); + + let borrowed = ∓ + let second = borrowed.polygon(1).expect("second polygon exists"); + assert_eq!(second, &poly_b); + } +} From 0bc3768145059439e712d0bbfbb8b5ad08da97bf Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 21:36:06 +0900 Subject: [PATCH 22/43] Add default type --- .zed/settings.json | 15 +++++++++++++++ geo-traits/src/structs/geometry.rs | 2 +- geo-traits/src/structs/geometrycollection.rs | 2 +- geo-traits/src/structs/linestring.rs | 2 +- geo-traits/src/structs/multilinestring.rs | 7 ++----- geo-traits/src/structs/multipoint.rs | 2 +- geo-traits/src/structs/multipolygon.rs | 15 +++++---------- geo-traits/src/structs/point.rs | 2 +- geo-traits/src/structs/polygon.rs | 2 +- 9 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 .zed/settings.json diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000000..d014f1a0e5 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,15 @@ +// Folder-specific settings +// +// For a full list of overridable settings, and general information on folder-specific settings, +// see the documentation: https://zed.dev/docs/configuring-zed#settings-files +{ + "lsp": { + "rust-analyzer": { + "initialization_options": { + "cargo": { + "features": ["structs"] + } + } + } + } +} diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index ed4f6ea95b..48bbdf8084 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -7,7 +7,7 @@ use crate::{ #[derive(Clone, Debug, PartialEq)] /// All supported geometry -pub enum Geometry { +pub enum Geometry { /// A point. Point(Point), /// A linestring. diff --git a/geo-traits/src/structs/geometrycollection.rs b/geo-traits/src/structs/geometrycollection.rs index 91b93a1d30..078331e506 100644 --- a/geo-traits/src/structs/geometrycollection.rs +++ b/geo-traits/src/structs/geometrycollection.rs @@ -2,7 +2,7 @@ use crate::{structs::Geometry, Dimensions, GeometryCollectionTrait}; /// A parsed GeometryCollection. #[derive(Clone, Debug, PartialEq)] -pub struct GeometryCollection { +pub struct GeometryCollection { pub(crate) geoms: Vec>, pub(crate) dim: Dimensions, } diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs index 421170abdf..f78b09faa4 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/linestring.rs @@ -5,7 +5,7 @@ use crate::{ /// A parsed LineString. #[derive(Clone, Debug, PartialEq)] -pub struct LineString { +pub struct LineString { pub(crate) coords: Vec>, pub(crate) dim: Dimensions, } diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index e1a2cced1a..0273113773 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -5,7 +5,7 @@ use crate::{ /// A parsed MultiLineString. #[derive(Clone, Debug, PartialEq)] -pub struct MultiLineString { +pub struct MultiLineString { pub(crate) line_strings: Vec>, pub(crate) dim: Dimensions, } @@ -206,10 +206,7 @@ mod tests { #[test] fn multilinestring_trait_accessors_work() { - let lines = vec![ - line_xy(&[(0, 0), (0, 1)]), - line_xy(&[(1, 1), (2, 2)]), - ]; + let lines = vec![line_xy(&[(0, 0), (0, 1)]), line_xy(&[(1, 1), (2, 2)])]; let mls = MultiLineString::new(lines.clone(), Dimensions::Xy); assert_eq!(mls.num_line_strings(), 2); diff --git a/geo-traits/src/structs/multipoint.rs b/geo-traits/src/structs/multipoint.rs index f2de3b1100..9e44735e38 100644 --- a/geo-traits/src/structs/multipoint.rs +++ b/geo-traits/src/structs/multipoint.rs @@ -5,7 +5,7 @@ use crate::{ /// A parsed MultiPoint. #[derive(Clone, Debug, PartialEq)] -pub struct MultiPoint { +pub struct MultiPoint { pub(crate) points: Vec>, pub(crate) dim: Dimensions, } diff --git a/geo-traits/src/structs/multipolygon.rs b/geo-traits/src/structs/multipolygon.rs index 0e5b264bc1..643e6470a7 100644 --- a/geo-traits/src/structs/multipolygon.rs +++ b/geo-traits/src/structs/multipolygon.rs @@ -5,7 +5,7 @@ use crate::{ /// A parsed MultiPolygon. #[derive(Clone, Debug, PartialEq)] -pub struct MultiPolygon { +pub struct MultiPolygon { pub(crate) polygons: Vec>, pub(crate) dim: Dimensions, } @@ -210,15 +210,10 @@ mod tests { #[test] fn from_multipolygon_copies_source() { - let polygon_a = Polygon::new( - vec![square_ring_xy(0, 2)], - Dimensions::Xy, - ); - let polygon_b = Polygon::new( - vec![square_ring_xy(3, 2)], - Dimensions::Xy, - ); - let original = MultiPolygon::new(vec![polygon_a.clone(), polygon_b.clone()], Dimensions::Xy); + let polygon_a = Polygon::new(vec![square_ring_xy(0, 2)], Dimensions::Xy); + let polygon_b = Polygon::new(vec![square_ring_xy(3, 2)], Dimensions::Xy); + let original = + MultiPolygon::new(vec![polygon_a.clone(), polygon_b.clone()], Dimensions::Xy); let converted = MultiPolygon::from_multipolygon(&original); assert_eq!(converted.dimension(), original.dimension()); diff --git a/geo-traits/src/structs/point.rs b/geo-traits/src/structs/point.rs index 726aab33f5..c4df179aaf 100644 --- a/geo-traits/src/structs/point.rs +++ b/geo-traits/src/structs/point.rs @@ -4,7 +4,7 @@ use crate::{CoordTrait, PointTrait}; /// A parsed Point. #[derive(Clone, Debug, PartialEq)] -pub struct Point { +pub struct Point { pub(crate) coord: Option>, pub(crate) dim: Dimensions, } diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index 3886bb0a79..eacdebdca3 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -5,7 +5,7 @@ use crate::{ /// A parsed Polygon. #[derive(Clone, Debug, PartialEq)] -pub struct Polygon { +pub struct Polygon { pub(crate) rings: Vec>, pub(crate) dim: Dimensions, } From 5443507edd0ec16b6fbbc83cbb91c294e61f1cce Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 22:00:23 +0900 Subject: [PATCH 23/43] Change the signatures to & --- geo-traits/src/structs/geometry.rs | 28 ++++++++++++ geo-traits/src/structs/geometrycollection.rs | 46 ++++++++++++-------- geo-traits/src/structs/linestring.rs | 2 +- geo-traits/src/structs/multilinestring.rs | 6 +-- geo-traits/src/structs/multipoint.rs | 4 +- geo-traits/src/structs/multipolygon.rs | 6 +-- geo-traits/src/structs/point.rs | 2 +- geo-traits/src/structs/polygon.rs | 4 +- 8 files changed, 69 insertions(+), 29 deletions(-) diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index 48bbdf8084..c0b27f34a7 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -40,6 +40,34 @@ where Self::GeometryCollection(g) => g.dimension(), } } + + /// Create a new Geometry from an objects implementing [GeometryTrait]. + pub fn from_geometry(geometry: &impl GeometryTrait) -> Self { + match geometry.as_type() { + crate::GeometryType::Point(geom) => Self::Point(Point::from_point(geom)), + crate::GeometryType::LineString(geom) => { + Self::LineString(LineString::from_linestring(geom)) + } + crate::GeometryType::Polygon(geom) => Self::Polygon(Polygon::from_polygon(geom)), + crate::GeometryType::MultiPoint(geom) => { + Self::MultiPoint(MultiPoint::from_multipoint(geom)) + } + crate::GeometryType::MultiLineString(geom) => { + Self::MultiLineString(MultiLineString::from_multilinestring(geom)) + } + crate::GeometryType::MultiPolygon(geom) => { + Self::MultiPolygon(MultiPolygon::from_multipolygon(geom)) + } + crate::GeometryType::GeometryCollection(geom) => { + Self::GeometryCollection(GeometryCollection::from_geometry_collection(geom)) + } + _ => unimplemented!(), + // TODO + // crate::GeometryType::Rect(geom) => Self::Rect(Rect::from_rect(geom)), + // crate::GeometryType::Triangle(geom) => Self::Triangle(Triangle::from_triangle(geom)), + // crate::GeometryType::Line(geom) => Self::Line(Line::from_line(geom)), + } + } } impl GeometryTrait for Geometry { diff --git a/geo-traits/src/structs/geometrycollection.rs b/geo-traits/src/structs/geometrycollection.rs index 078331e506..3c32376764 100644 --- a/geo-traits/src/structs/geometrycollection.rs +++ b/geo-traits/src/structs/geometrycollection.rs @@ -1,4 +1,4 @@ -use crate::{structs::Geometry, Dimensions, GeometryCollectionTrait}; +use crate::{structs::Geometry, Dimensions, GeometryCollectionTrait, GeometryTrait}; /// A parsed GeometryCollection. #[derive(Clone, Debug, PartialEq)] @@ -18,7 +18,24 @@ impl GeometryCollection { Self::new(vec![], dim) } - /// Create a new GeometryCollection from a non-empty sequence of [Geometry]. + /// Return the [Dimensions] of this geometry. + pub fn dimension(&self) -> Dimensions { + self.dim + } + + /// Access the underlying geometries. + pub fn geometries(&self) -> &[Geometry] { + &self.geoms + } + + /// Consume self and return the inner parts. + pub fn into_inner(self) -> (Vec>, Dimensions) { + (self.geoms, self.dim) + } + + // Conversion from geo-traits' traits + + /// Create a new GeometryCollection from a non-empty sequence of objects implementing [GeometryTrait]. /// /// This will infer the dimension from the first geometry, and will not validate that all /// geometries have the same dimension. @@ -29,8 +46,13 @@ impl GeometryCollection { /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. - pub fn from_geometries(geoms: impl IntoIterator>) -> Option { - let geoms = geoms.into_iter().collect::>(); + pub fn from_geometries( + geoms: impl IntoIterator>, + ) -> Option { + let geoms = geoms + .into_iter() + .map(|g| Geometry::from_geometry(&g)) + .collect::>(); if geoms.is_empty() { None } else { @@ -39,19 +61,9 @@ impl GeometryCollection { } } - /// Return the [Dimensions] of this geometry. - pub fn dimension(&self) -> Dimensions { - self.dim - } - - /// Access the underlying geometries. - pub fn geometries(&self) -> &[Geometry] { - &self.geoms - } - - /// Consume self and return the inner parts. - pub fn into_inner(self) -> (Vec>, Dimensions) { - (self.geoms, self.dim) + /// Create a new GeometryCollection from an objects implementing [GeometryCollectionTrait]. + pub fn from_geometry_collection(geoms: &impl GeometryCollectionTrait) -> Self { + Self::from_geometries(geoms.geometries()).unwrap() } } diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs index f78b09faa4..50f0abc98c 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/linestring.rs @@ -61,7 +61,7 @@ impl LineString { } /// Create a new LineString from an objects implementing [LineStringTrait]. - pub fn from_linestring(linestring: impl LineStringTrait) -> Self { + pub fn from_linestring(linestring: &impl LineStringTrait) -> Self { Self::from_coords(linestring.coords()).unwrap() } } diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index 0273113773..aabdc2a416 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -52,7 +52,7 @@ impl MultiLineString { ) -> Option { let line_strings = line_strings .into_iter() - .map(|l| LineString::from_linestring(l)) + .map(|l| LineString::from_linestring(&l)) .collect::>(); if line_strings.is_empty() { None @@ -63,10 +63,10 @@ impl MultiLineString { } /// Create a new MultiLineString from an objects implementing [MultiLineStringTrait]. - pub fn from_multilinestring(multilinestring: impl MultiLineStringTrait) -> Self { + pub fn from_multilinestring(multilinestring: &impl MultiLineStringTrait) -> Self { let line_strings = multilinestring .line_strings() - .map(|l| LineString::from_linestring(l)) + .map(|l| LineString::from_linestring(&l)) .collect::>(); let dim = line_strings[0].dimension(); Self::new(line_strings, dim) diff --git a/geo-traits/src/structs/multipoint.rs b/geo-traits/src/structs/multipoint.rs index 9e44735e38..9a099f32b0 100644 --- a/geo-traits/src/structs/multipoint.rs +++ b/geo-traits/src/structs/multipoint.rs @@ -50,7 +50,7 @@ impl MultiPoint { pub fn from_points(points: impl IntoIterator>) -> Option { let points = points .into_iter() - .map(|p| Point::from_point(p)) + .map(|p| Point::from_point(&p)) .collect::>(); if points.is_empty() { None @@ -61,7 +61,7 @@ impl MultiPoint { } /// Create a new MultiPoint from an objects implementing [MultiPointTrait]. - pub fn from_multipoint(multipoint: impl MultiPointTrait) -> Self { + pub fn from_multipoint(multipoint: &impl MultiPointTrait) -> Self { Self::from_points(multipoint.points()).unwrap() } } diff --git a/geo-traits/src/structs/multipolygon.rs b/geo-traits/src/structs/multipolygon.rs index 643e6470a7..fecb71f9b5 100644 --- a/geo-traits/src/structs/multipolygon.rs +++ b/geo-traits/src/structs/multipolygon.rs @@ -52,7 +52,7 @@ impl MultiPolygon { ) -> Option { let polygons = polygons .into_iter() - .map(|p| Polygon::from_polygon(p)) + .map(|p| Polygon::from_polygon(&p)) .collect::>(); if polygons.is_empty() { None @@ -63,10 +63,10 @@ impl MultiPolygon { } /// Create a new MultiPolygon from an objects implementing [MultiPolygonTrait]. - pub fn from_multipolygon(multipolygon: impl MultiPolygonTrait) -> Self { + pub fn from_multipolygon(multipolygon: &impl MultiPolygonTrait) -> Self { let polygons = multipolygon .polygons() - .map(|p| Polygon::from_polygon(p)) + .map(|p| Polygon::from_polygon(&p)) .collect::>(); let dim = polygons[0].dimension(); Self::new(polygons, dim) diff --git a/geo-traits/src/structs/point.rs b/geo-traits/src/structs/point.rs index c4df179aaf..e6fcb8dfba 100644 --- a/geo-traits/src/structs/point.rs +++ b/geo-traits/src/structs/point.rs @@ -50,7 +50,7 @@ impl Point { /// Create a new point from an object implementing [PointTrait]. /// /// This infers the dimension from the coordinate. - pub fn from_point(point: impl PointTrait) -> Self { + pub fn from_point(point: &impl PointTrait) -> Self { let dim = point.dim(); let coord = point.coord().map(|c| Coord::new(c)); Self { coord, dim } diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index eacdebdca3..00f4a90dfa 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -54,7 +54,7 @@ impl Polygon { ) -> Option { let rings = rings .into_iter() - .map(|l| LineString::from_linestring(l)) + .map(|l| LineString::from_linestring(&l)) .collect::>(); if rings.is_empty() { None @@ -65,7 +65,7 @@ impl Polygon { } /// Create a new polygon from an object implementing [PolygonTrait]. - pub fn from_polygon(polygon: impl PolygonTrait) -> Self { + pub fn from_polygon(polygon: &impl PolygonTrait) -> Self { let exterior = polygon.exterior().into_iter(); let other = polygon.interiors(); Self::from_rings(exterior.chain(other)).unwrap() From 02c3c9b670983acdbcd30c1e89d0aa944904e05b Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 22:03:23 +0900 Subject: [PATCH 24/43] Add default type --- geo-traits/src/structs/coord.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index 470b06d036..ea6f263d4b 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -3,7 +3,7 @@ use crate::CoordTrait; /// A parsed coordinate. #[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct Coord { +pub struct Coord { /// The x-coordinate. pub x: T, /// The y-coordinate. From f429652f8cab49e0c99500caf2a8fddc41f73e32 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 23:26:10 +0900 Subject: [PATCH 25/43] Remove unused imports --- geo-traits/src/structs/multilinestring.rs | 2 +- geo-traits/src/structs/multipolygon.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index aabdc2a416..f9efa8339a 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -116,7 +116,7 @@ impl MultiLineStringTrait for &MultiLineString { mod tests { use super::*; use crate::structs::Coord; - use crate::{LineStringTrait, MultiLineStringTrait}; + use crate::MultiLineStringTrait; fn line_xy(coords: &[(i32, i32)]) -> LineString { LineString::new( diff --git a/geo-traits/src/structs/multipolygon.rs b/geo-traits/src/structs/multipolygon.rs index fecb71f9b5..3d10529c47 100644 --- a/geo-traits/src/structs/multipolygon.rs +++ b/geo-traits/src/structs/multipolygon.rs @@ -116,7 +116,7 @@ impl MultiPolygonTrait for &MultiPolygon { mod tests { use super::*; use crate::structs::{Coord, LineString}; - use crate::{MultiPolygonTrait, PolygonTrait}; + use crate::MultiPolygonTrait; fn square_ring_xy(offset: i32, size: i32) -> LineString { LineString::new( From 6d955ce7df4bab39fa4174337c71c42bf513bb1a Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 25 Oct 2025 23:50:37 +0900 Subject: [PATCH 26/43] Add more constructors --- geo-traits/src/structs/coord.rs | 40 ++++++++++++++++++++++++++++ geo-traits/src/structs/multipoint.rs | 16 ++++++++++- geo-traits/src/structs/point.rs | 20 ++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index ea6f263d4b..715f1c0a9e 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -47,6 +47,46 @@ impl Coord { }, } } + + /// Creates a new coordinate from X and Y coordinates. + pub fn from_xy(x: T, y: T) -> Self { + Self { + x, + y, + z: None, + m: None, + } + } + + /// Creates a new coordinate from X, Y and Z coordinates. + pub fn from_xyz(x: T, y: T, z: T) -> Self { + Self { + x, + y, + z: Some(z), + m: None, + } + } + + /// Creates a new coordinate from X, Y, and M coordinates. + pub fn from_xym(x: T, y: T, m: T) -> Self { + Self { + x, + y, + z: None, + m: Some(m), + } + } + + /// Creates a new coordinate from X, Y, Z, and M coordinates. + pub fn from_xyzm(x: T, y: T, z: T, m: T) -> Self { + Self { + x, + y, + z: Some(z), + m: Some(m), + } + } } impl CoordTrait for Coord { diff --git a/geo-traits/src/structs/multipoint.rs b/geo-traits/src/structs/multipoint.rs index 9a099f32b0..3d16f2dcc4 100644 --- a/geo-traits/src/structs/multipoint.rs +++ b/geo-traits/src/structs/multipoint.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Geometry, Point}, - Dimensions, MultiPointTrait, PointTrait, + CoordTrait, Dimensions, MultiPointTrait, PointTrait, }; /// A parsed MultiPoint. @@ -60,6 +60,20 @@ impl MultiPoint { } } + /// Create a new MultiPoint from a non-empty sequence of objects implementing [CoordTrait]. + pub fn from_coords(coords: impl IntoIterator>) -> Option { + let points = coords + .into_iter() + .map(|c| Point::from_coord(c)) + .collect::>(); + if points.is_empty() { + None + } else { + let dim = points[0].dimension(); + Some(Self::new(points, dim)) + } + } + /// Create a new MultiPoint from an objects implementing [MultiPointTrait]. pub fn from_multipoint(multipoint: &impl MultiPointTrait) -> Self { Self::from_points(multipoint.points()).unwrap() diff --git a/geo-traits/src/structs/point.rs b/geo-traits/src/structs/point.rs index e6fcb8dfba..1ff72a57a7 100644 --- a/geo-traits/src/structs/point.rs +++ b/geo-traits/src/structs/point.rs @@ -20,6 +20,26 @@ impl Point { Self::new(None, dim) } + /// Creates a new coordinate from X and Y coordinates. + pub fn from_xy(x: T, y: T) -> Self { + Self::new(Some(Coord::from_xy(x, y)), Dimensions::Xy) + } + + /// Creates a new coordinate from X, Y and Z coordinates. + pub fn from_xyz(x: T, y: T, z: T) -> Self { + Self::new(Some(Coord::from_xyz(x, y, z)), Dimensions::Xyz) + } + + /// Creates a new coordinate from X, Y, and M coordinates. + pub fn from_xym(x: T, y: T, m: T) -> Self { + Self::new(Some(Coord::from_xyz(x, y, m)), Dimensions::Xym) + } + + /// Creates a new coordinate from X, Y, Z, and M coordinates. + pub fn from_xyzm(x: T, y: T, z: T, m: T) -> Self { + Self::new(Some(Coord::from_xyzm(x, y, z, m)), Dimensions::Xyzm) + } + /// Return the [Dimensions] of this geometry. pub fn dimension(&self) -> Dimensions { self.dim From 03c6a73dc11f684c15e411885824f2ccbbd7525b Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sun, 26 Oct 2025 09:30:05 +0900 Subject: [PATCH 27/43] Fix document links --- geo-traits/src/structs/geometry.rs | 2 +- geo-traits/src/structs/multilinestring.rs | 2 +- geo-traits/src/structs/point.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index c0b27f34a7..2237d552ea 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -28,7 +28,7 @@ impl Geometry where T: Copy, { - /// Return the [Dimension] of this geometry. + /// Return the [Dimensions] of this geometry. pub fn dimension(&self) -> Dimensions { match self { Self::Point(g) => g.dimension(), diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index f9efa8339a..9740ece4bf 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -11,7 +11,7 @@ pub struct MultiLineString { } impl MultiLineString { - /// Create a new LineString from a sequence of [LineString] and known [Dimension]. + /// Create a new LineString from a sequence of [LineString] and known [Dimensions]. pub fn new(line_strings: Vec>, dim: Dimensions) -> Self { MultiLineString { dim, line_strings } } diff --git a/geo-traits/src/structs/point.rs b/geo-traits/src/structs/point.rs index 1ff72a57a7..1b2771bb34 100644 --- a/geo-traits/src/structs/point.rs +++ b/geo-traits/src/structs/point.rs @@ -10,7 +10,7 @@ pub struct Point { } impl Point { - /// Create a new Point from a coordinate and known [Dimension]. + /// Create a new Point from a coordinate and known [Dimensions]. pub fn new(coord: Option>, dim: Dimensions) -> Self { Self { coord, dim } } From 3d54e813ff50c8823a9e897ba3736265d3b4ec92 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sun, 26 Oct 2025 11:12:16 +0900 Subject: [PATCH 28/43] Delete mistakenly commited .zed/settings.json --- .zed/settings.json | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .zed/settings.json diff --git a/.zed/settings.json b/.zed/settings.json deleted file mode 100644 index d014f1a0e5..0000000000 --- a/.zed/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -// Folder-specific settings -// -// For a full list of overridable settings, and general information on folder-specific settings, -// see the documentation: https://zed.dev/docs/configuring-zed#settings-files -{ - "lsp": { - "rust-analyzer": { - "initialization_options": { - "cargo": { - "features": ["structs"] - } - } - } - } -} From f025682ea07223f8c017cbcc43557b4806cca6c2 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sun, 26 Oct 2025 11:27:05 +0900 Subject: [PATCH 29/43] Rename --- .zed/settings.json | 15 +++++++++++++++ geo-traits/src/structs/coord.rs | 15 ++++++++++----- geo-traits/src/structs/linestring.rs | 2 +- geo-traits/src/structs/point.rs | 4 ++-- 4 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 .zed/settings.json diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000000..d014f1a0e5 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,15 @@ +// Folder-specific settings +// +// For a full list of overridable settings, and general information on folder-specific settings, +// see the documentation: https://zed.dev/docs/configuring-zed#settings-files +{ + "lsp": { + "rust-analyzer": { + "initialization_options": { + "cargo": { + "features": ["structs"] + } + } + } + } +} diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index 715f1c0a9e..464d9aec42 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -15,8 +15,13 @@ pub struct Coord { } impl Coord { + /// Creates a new coordinate. + pub fn new(x: T, y: T, z: Option, m: Option) -> Self { + Self { x, y, z, m } + } + /// Creates a new coordinate from a coordinate trait. - pub fn new(coord: impl CoordTrait) -> Self { + pub fn from_coord(coord: &impl CoordTrait) -> Self { let x = coord.x(); let y = coord.y(); @@ -218,7 +223,7 @@ mod tests { #[test] fn coord_new_from_tuple_xy() { - let coord = Coord::new((1_i32, 2_i32)); + let coord = Coord::from_coord(&(1_i32, 2_i32)); assert_eq!(coord.x, 1); assert_eq!(coord.y, 2); assert_eq!(coord.z, None); @@ -229,7 +234,7 @@ mod tests { #[test] fn coord_new_from_xyz() { let source = DummyCoord::new([1.0_f64, 2.0, 3.0, 0.0], Dimensions::Xyz); - let coord = Coord::new(source); + let coord = Coord::from_coord(&source); assert_eq!(coord.z, Some(3.0)); assert_eq!(coord.m, None); assert_eq!(coord.nth_or_panic(2), 3.0); @@ -239,7 +244,7 @@ mod tests { #[test] fn coord_new_from_xym() { let source = DummyCoord::new([4_u32, 5, 6, 0], Dimensions::Xym); - let coord = Coord::new(source); + let coord = Coord::from_coord(&source); assert_eq!(coord.z, None); assert_eq!(coord.m, Some(6)); assert_eq!(coord.nth_or_panic(2), 6); @@ -249,7 +254,7 @@ mod tests { #[test] fn coord_new_from_xyzm() { let source = DummyCoord::new([7_i16, 8, 9, 10], Dimensions::Xyzm); - let coord = Coord::new(source); + let coord = Coord::from_coord(&source); assert_eq!(coord.z, Some(9)); assert_eq!(coord.m, Some(10)); assert_eq!(coord.nth_or_panic(2), 9); diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs index 50f0abc98c..1efe1fcc76 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/linestring.rs @@ -50,7 +50,7 @@ impl LineString { pub fn from_coords(coords: impl IntoIterator>) -> Option { let coords = coords .into_iter() - .map(|c| Coord::new(c)) + .map(|c| Coord::from_coord(&c)) .collect::>(); if coords.is_empty() { None diff --git a/geo-traits/src/structs/point.rs b/geo-traits/src/structs/point.rs index 1b2771bb34..19864b22e1 100644 --- a/geo-traits/src/structs/point.rs +++ b/geo-traits/src/structs/point.rs @@ -63,7 +63,7 @@ impl Point { pub fn from_coord(coord: impl CoordTrait) -> Self { Self { dim: coord.dim(), - coord: Some(Coord::new(coord)), + coord: Some(Coord::from_coord(&coord)), } } @@ -72,7 +72,7 @@ impl Point { /// This infers the dimension from the coordinate. pub fn from_point(point: &impl PointTrait) -> Self { let dim = point.dim(); - let coord = point.coord().map(|c| Coord::new(c)); + let coord = point.coord().map(|c| Coord::from_coord(&c)); Self { coord, dim } } } From 4456cbe0a443c4f480c8201be8b6369139d330d0 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sun, 26 Oct 2025 11:42:48 +0900 Subject: [PATCH 30/43] Add conversions --- geo-traits/src/structs/coord.rs | 49 ++++++++++++++++++++++++ geo-traits/src/structs/point.rs | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index 464d9aec42..aa2e7952f9 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -183,6 +183,28 @@ impl CoordTrait for &Coord { } } +/// Convert from a tuple of (X, Y). +impl From<(T, T)> for Coord { + fn from((x, y): (T, T)) -> Self { + Self::new(x, y, None, None) + } +} + +/// Convert from a tuple of (X, Y, Z). If you want to create a `Coord` of +/// `Dimensions::Xym`, use `Coord::from_xym`. +impl From<(T, T, T)> for Coord { + fn from((x, y, z): (T, T, T)) -> Self { + Self::new(x, y, Some(z), None) + } +} + +/// Convert from a tuple of (X, Y, Z, M). +impl From<(T, T, T, T)> for Coord { + fn from((x, y, z, m): (T, T, T, T)) -> Self { + Self::new(x, y, Some(z), Some(m)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -261,4 +283,31 @@ mod tests { assert_eq!(coord.nth_or_panic(3), 10); assert_eq!(CoordTrait::dim(&coord), Dimensions::Xyzm); } + + #[test] + fn coord_from_tuple_xy() { + let coord: Coord = Coord::from((11, 12)); + assert_eq!(coord, Coord::from_xy(11, 12)); + assert_eq!(coord.z, None); + assert_eq!(coord.m, None); + assert_eq!(CoordTrait::dim(&coord), Dimensions::Xy); + } + + #[test] + fn coord_from_tuple_xyz() { + let coord: Coord = Coord::from((1.5, 2.5, 3.5)); + assert_eq!(coord, Coord::from_xyz(1.5, 2.5, 3.5)); + assert_eq!(coord.nth_or_panic(2), 3.5); + assert_eq!(coord.m, None); + assert_eq!(CoordTrait::dim(&coord), Dimensions::Xyz); + } + + #[test] + fn coord_from_tuple_xyzm() { + let coord: Coord = Coord::from((7, 8, 9, 10)); + assert_eq!(coord, Coord::from_xyzm(7, 8, 9, 10)); + assert_eq!(coord.nth_or_panic(2), 9); + assert_eq!(coord.nth_or_panic(3), 10); + assert_eq!(CoordTrait::dim(&coord), Dimensions::Xyzm); + } } diff --git a/geo-traits/src/structs/point.rs b/geo-traits/src/structs/point.rs index 19864b22e1..d0118c91b1 100644 --- a/geo-traits/src/structs/point.rs +++ b/geo-traits/src/structs/point.rs @@ -107,3 +107,69 @@ impl<'a, T: Copy> PointTrait for &'a Point { self.coord.as_ref() } } + +/// Convert from a tuple of (X, Y). +impl From<(T, T)> for Point { + fn from((x, y): (T, T)) -> Self { + Self { + dim: Dimensions::Xy, + coord: Some(Coord::from_xy(x, y)), + } + } +} + +/// Convert from a tuple of (X, Y, Z). If you want to create a `Point` of +/// `Dimensions::Xym`, use `Point::from_xym`. +impl From<(T, T, T)> for Point { + fn from((x, y, z): (T, T, T)) -> Self { + Self { + dim: Dimensions::Xyz, + coord: Some(Coord::from_xyz(x, y, z)), + } + } +} + +/// Convert from a tuple of (X, Y, Z, M). +impl From<(T, T, T, T)> for Point { + fn from((x, y, z, m): (T, T, T, T)) -> Self { + Self { + dim: Dimensions::Xyzm, + coord: Some(Coord::from_xyzm(x, y, z, m)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn point_from_tuple_xy() { + let point: Point = Point::from((3, 4)); + let coord = point.coord().unwrap(); + assert_eq!(*coord, Coord::from_xy(3, 4)); + assert_eq!(coord.z, None); + assert_eq!(coord.m, None); + assert_eq!(point.dimension(), Dimensions::Xy); + } + + #[test] + fn point_from_tuple_xyz() { + let point: Point = Point::from((1.5, 2.5, 3.5)); + let coord = point.coord().unwrap(); + assert_eq!(*coord, Coord::from_xyz(1.5, 2.5, 3.5)); + assert_eq!(coord.z, Some(3.5)); + assert_eq!(coord.m, None); + assert_eq!(point.dimension(), Dimensions::Xyz); + } + + #[test] + fn point_from_tuple_xyzm() { + let point: Point = Point::from((7, 8, 9, 10)); + let coord = point.coord().unwrap(); + assert_eq!(*coord, Coord::from_xyzm(7, 8, 9, 10)); + assert_eq!(coord.z, Some(9)); + assert_eq!(coord.m, Some(10)); + assert_eq!(point.dimension(), Dimensions::Xyzm); + } +} From 8a517a610f69430045df3c90e2f34421c93b6755 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sun, 26 Oct 2025 11:43:22 +0900 Subject: [PATCH 31/43] Delete settings.json --- .zed/settings.json | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .zed/settings.json diff --git a/.zed/settings.json b/.zed/settings.json deleted file mode 100644 index d014f1a0e5..0000000000 --- a/.zed/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -// Folder-specific settings -// -// For a full list of overridable settings, and general information on folder-specific settings, -// see the documentation: https://zed.dev/docs/configuring-zed#settings-files -{ - "lsp": { - "rust-analyzer": { - "initialization_options": { - "cargo": { - "features": ["structs"] - } - } - } - } -} From ca8872cfa5957f509981acc3d29f553dee888d1d Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sun, 26 Oct 2025 12:28:19 +0900 Subject: [PATCH 32/43] Skip Line, Triangle, Rect --- geo-traits/src/structs/geometry.rs | 33 +++++++++----------- geo-traits/src/structs/geometrycollection.rs | 7 +++-- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index 2237d552ea..c1994a2c37 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -41,31 +41,28 @@ where } } - /// Create a new Geometry from an objects implementing [GeometryTrait]. - pub fn from_geometry(geometry: &impl GeometryTrait) -> Self { + /// Create a new Geometry from an objects implementing [GeometryTrait]. The + /// result is `None` if the geometry is `Line`, `Rect`, or `Triangle`. + pub fn from_geometry(geometry: &impl GeometryTrait) -> Option { match geometry.as_type() { - crate::GeometryType::Point(geom) => Self::Point(Point::from_point(geom)), + crate::GeometryType::Point(geom) => Some(Self::Point(Point::from_point(geom))), crate::GeometryType::LineString(geom) => { - Self::LineString(LineString::from_linestring(geom)) + Some(Self::LineString(LineString::from_linestring(geom))) } - crate::GeometryType::Polygon(geom) => Self::Polygon(Polygon::from_polygon(geom)), + crate::GeometryType::Polygon(geom) => Some(Self::Polygon(Polygon::from_polygon(geom))), crate::GeometryType::MultiPoint(geom) => { - Self::MultiPoint(MultiPoint::from_multipoint(geom)) - } - crate::GeometryType::MultiLineString(geom) => { - Self::MultiLineString(MultiLineString::from_multilinestring(geom)) + Some(Self::MultiPoint(MultiPoint::from_multipoint(geom))) } + crate::GeometryType::MultiLineString(geom) => Some(Self::MultiLineString( + MultiLineString::from_multilinestring(geom), + )), crate::GeometryType::MultiPolygon(geom) => { - Self::MultiPolygon(MultiPolygon::from_multipolygon(geom)) - } - crate::GeometryType::GeometryCollection(geom) => { - Self::GeometryCollection(GeometryCollection::from_geometry_collection(geom)) + Some(Self::MultiPolygon(MultiPolygon::from_multipolygon(geom))) } - _ => unimplemented!(), - // TODO - // crate::GeometryType::Rect(geom) => Self::Rect(Rect::from_rect(geom)), - // crate::GeometryType::Triangle(geom) => Self::Triangle(Triangle::from_triangle(geom)), - // crate::GeometryType::Line(geom) => Self::Line(Line::from_line(geom)), + crate::GeometryType::GeometryCollection(geom) => Some(Self::GeometryCollection( + GeometryCollection::from_geometry_collection(geom), + )), + _ => None, } } } diff --git a/geo-traits/src/structs/geometrycollection.rs b/geo-traits/src/structs/geometrycollection.rs index 3c32376764..1a7e9475f1 100644 --- a/geo-traits/src/structs/geometrycollection.rs +++ b/geo-traits/src/structs/geometrycollection.rs @@ -40,9 +40,10 @@ impl GeometryCollection { /// This will infer the dimension from the first geometry, and will not validate that all /// geometries have the same dimension. /// - /// ## Errors + /// This returns `None` when /// - /// If the input iterator is empty. + /// - the input iterator is empty + /// - all the geometries are `Line`, `Triangle`, or `Rect` (these geometries are silently skipped) /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. @@ -51,7 +52,7 @@ impl GeometryCollection { ) -> Option { let geoms = geoms .into_iter() - .map(|g| Geometry::from_geometry(&g)) + .flat_map(|g| Geometry::from_geometry(&g)) .collect::>(); if geoms.is_empty() { None From cc70dd8b2d179874221029922776a4c1825581d1 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 28 Oct 2025 22:32:05 +0900 Subject: [PATCH 33/43] Handle Unknown Dimension --- geo-traits/src/structs/coord.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index aa2e7952f9..67661a6e69 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -26,13 +26,13 @@ impl Coord { let y = coord.y(); match coord.dim() { - Dimensions::Xyzm | Dimensions::Unknown(_) => Self { + Dimensions::Xyzm | Dimensions::Unknown(4) => Self { x, y, z: coord.nth(2), m: coord.nth(3), }, - Dimensions::Xyz => Self { + Dimensions::Xyz | Dimensions::Unknown(3) => Self { x, y, z: coord.nth(2), @@ -44,12 +44,13 @@ impl Coord { z: None, m: coord.nth(2), }, - Dimensions::Xy => Self { + Dimensions::Xy | Dimensions::Unknown(2) => Self { x, y, z: None, m: None, }, + Dimensions::Unknown(_) => todo!(), } } From c0244597c0d506f595832c1bc58e459c13c7247d Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 28 Oct 2025 22:33:14 +0900 Subject: [PATCH 34/43] Remove From conversion on (T,T,T) and (T,T,T,T) --- geo-traits/src/structs/coord.rs | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index 67661a6e69..edbe3fe4e0 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -191,21 +191,6 @@ impl From<(T, T)> for Coord { } } -/// Convert from a tuple of (X, Y, Z). If you want to create a `Coord` of -/// `Dimensions::Xym`, use `Coord::from_xym`. -impl From<(T, T, T)> for Coord { - fn from((x, y, z): (T, T, T)) -> Self { - Self::new(x, y, Some(z), None) - } -} - -/// Convert from a tuple of (X, Y, Z, M). -impl From<(T, T, T, T)> for Coord { - fn from((x, y, z, m): (T, T, T, T)) -> Self { - Self::new(x, y, Some(z), Some(m)) - } -} - #[cfg(test)] mod tests { use super::*; @@ -293,22 +278,4 @@ mod tests { assert_eq!(coord.m, None); assert_eq!(CoordTrait::dim(&coord), Dimensions::Xy); } - - #[test] - fn coord_from_tuple_xyz() { - let coord: Coord = Coord::from((1.5, 2.5, 3.5)); - assert_eq!(coord, Coord::from_xyz(1.5, 2.5, 3.5)); - assert_eq!(coord.nth_or_panic(2), 3.5); - assert_eq!(coord.m, None); - assert_eq!(CoordTrait::dim(&coord), Dimensions::Xyz); - } - - #[test] - fn coord_from_tuple_xyzm() { - let coord: Coord = Coord::from((7, 8, 9, 10)); - assert_eq!(coord, Coord::from_xyzm(7, 8, 9, 10)); - assert_eq!(coord.nth_or_panic(2), 9); - assert_eq!(coord.nth_or_panic(3), 10); - assert_eq!(CoordTrait::dim(&coord), Dimensions::Xyzm); - } } From 7a9cf73fc9c4e1c3b0333a97bd60afdd5cb0ac42 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 28 Oct 2025 23:12:08 +0900 Subject: [PATCH 35/43] Handle Rect, Triangle, Line --- geo-traits/src/structs/geometry.rs | 11 ++++-- geo-traits/src/structs/linestring.rs | 7 +++- geo-traits/src/structs/polygon.rs | 57 +++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index c1994a2c37..a12643e4d8 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -41,8 +41,9 @@ where } } - /// Create a new Geometry from an objects implementing [GeometryTrait]. The - /// result is `None` if the geometry is `Line`, `Rect`, or `Triangle`. + /// Create a new Geometry from an objects implementing [GeometryTrait]. + /// `Line` geometry is converted to a `LineString`, `Rect` and `Triangle` + /// geometries are converted to `Polygon`. pub fn from_geometry(geometry: &impl GeometryTrait) -> Option { match geometry.as_type() { crate::GeometryType::Point(geom) => Some(Self::Point(Point::from_point(geom))), @@ -62,7 +63,11 @@ where crate::GeometryType::GeometryCollection(geom) => Some(Self::GeometryCollection( GeometryCollection::from_geometry_collection(geom), )), - _ => None, + crate::GeometryType::Rect(rect) => Some(Self::Polygon(Polygon::from_rect(rect))), + crate::GeometryType::Triangle(triangle) => { + Some(Self::Polygon(Polygon::from_triangle(triangle))) + } + crate::GeometryType::Line(line) => Some(Self::LineString(LineString::from_line(line))), } } } diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs index 1efe1fcc76..a593c28247 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/linestring.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Coord, Geometry}, - CoordTrait, Dimensions, LineStringTrait, + CoordTrait, Dimensions, LineStringTrait, LineTrait, }; /// A parsed LineString. @@ -64,6 +64,11 @@ impl LineString { pub fn from_linestring(linestring: &impl LineStringTrait) -> Self { Self::from_coords(linestring.coords()).unwrap() } + + /// Create a new LineString from an objects implementing [LineTrait]. + pub fn from_line(line: &impl LineTrait) -> Self { + Self::from_coords(line.coords()).unwrap() + } } impl From> for Geometry diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index 00f4a90dfa..6f4e5a454e 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Geometry, LineString}, - Dimensions, LineStringTrait, PolygonTrait, + CoordTrait, Dimensions, GeometryTrait, LineStringTrait, PolygonTrait, RectTrait, TriangleTrait, }; /// A parsed Polygon. @@ -70,6 +70,61 @@ impl Polygon { let other = polygon.interiors(); Self::from_rings(exterior.chain(other)).unwrap() } + + /// Create a new polygon from an object implementing [TriangleTrait]. + pub fn from_triangle(triangle: &impl TriangleTrait) -> Self { + let ring = super::LineString::from_coords(triangle.coords()).unwrap(); + Self { + dim: ring.dimension(), + rings: vec![ring], + } + } + + /// Create a new polygon from an object implementing [RectTrait]. + pub fn from_rect(rect: &impl RectTrait) -> Self { + let min = rect.min(); + let max = rect.max(); + // Rect should be 2D, so this just uses X and Y coordinates + let ring = super::LineString { + dim: Dimensions::Xy, + coords: vec![ + super::Coord { + x: min.x(), + y: min.y(), + z: None, + m: None, + }, + super::Coord { + x: max.x(), + y: min.y(), + z: None, + m: None, + }, + super::Coord { + x: max.x(), + y: max.y(), + z: None, + m: None, + }, + super::Coord { + x: min.x(), + y: max.y(), + z: None, + m: None, + }, + super::Coord { + x: min.x(), + y: min.y(), + z: None, + m: None, + }, + ], + }; + Self { + rings: vec![ring], + dim: Dimensions::Xy, + } + } } impl From> for Geometry From ed623e2cfd59f747abd11216a225518b44cbf278 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 28 Oct 2025 23:14:40 +0900 Subject: [PATCH 36/43] Make from_geometry() return T not Option --- geo-traits/src/structs/geometry.rs | 30 ++++++++++---------- geo-traits/src/structs/geometrycollection.rs | 2 +- geo-traits/src/structs/polygon.rs | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index a12643e4d8..408db8f347 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -44,30 +44,30 @@ where /// Create a new Geometry from an objects implementing [GeometryTrait]. /// `Line` geometry is converted to a `LineString`, `Rect` and `Triangle` /// geometries are converted to `Polygon`. - pub fn from_geometry(geometry: &impl GeometryTrait) -> Option { + pub fn from_geometry(geometry: &impl GeometryTrait) -> Self { match geometry.as_type() { - crate::GeometryType::Point(geom) => Some(Self::Point(Point::from_point(geom))), + crate::GeometryType::Point(geom) => Self::Point(Point::from_point(geom)), crate::GeometryType::LineString(geom) => { - Some(Self::LineString(LineString::from_linestring(geom))) + Self::LineString(LineString::from_linestring(geom)) } - crate::GeometryType::Polygon(geom) => Some(Self::Polygon(Polygon::from_polygon(geom))), + crate::GeometryType::Polygon(geom) => Self::Polygon(Polygon::from_polygon(geom)), crate::GeometryType::MultiPoint(geom) => { - Some(Self::MultiPoint(MultiPoint::from_multipoint(geom))) + Self::MultiPoint(MultiPoint::from_multipoint(geom)) + } + crate::GeometryType::MultiLineString(geom) => { + Self::MultiLineString(MultiLineString::from_multilinestring(geom)) } - crate::GeometryType::MultiLineString(geom) => Some(Self::MultiLineString( - MultiLineString::from_multilinestring(geom), - )), crate::GeometryType::MultiPolygon(geom) => { - Some(Self::MultiPolygon(MultiPolygon::from_multipolygon(geom))) + Self::MultiPolygon(MultiPolygon::from_multipolygon(geom)) + } + crate::GeometryType::GeometryCollection(geom) => { + Self::GeometryCollection(GeometryCollection::from_geometry_collection(geom)) } - crate::GeometryType::GeometryCollection(geom) => Some(Self::GeometryCollection( - GeometryCollection::from_geometry_collection(geom), - )), - crate::GeometryType::Rect(rect) => Some(Self::Polygon(Polygon::from_rect(rect))), + crate::GeometryType::Rect(rect) => Self::Polygon(Polygon::from_rect(rect)), crate::GeometryType::Triangle(triangle) => { - Some(Self::Polygon(Polygon::from_triangle(triangle))) + Self::Polygon(Polygon::from_triangle(triangle)) } - crate::GeometryType::Line(line) => Some(Self::LineString(LineString::from_line(line))), + crate::GeometryType::Line(line) => Self::LineString(LineString::from_line(line)), } } } diff --git a/geo-traits/src/structs/geometrycollection.rs b/geo-traits/src/structs/geometrycollection.rs index 1a7e9475f1..d013954686 100644 --- a/geo-traits/src/structs/geometrycollection.rs +++ b/geo-traits/src/structs/geometrycollection.rs @@ -52,7 +52,7 @@ impl GeometryCollection { ) -> Option { let geoms = geoms .into_iter() - .flat_map(|g| Geometry::from_geometry(&g)) + .map(|g| Geometry::from_geometry(&g)) .collect::>(); if geoms.is_empty() { None diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index 6f4e5a454e..28a14de981 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -1,6 +1,6 @@ use crate::{ structs::{Geometry, LineString}, - CoordTrait, Dimensions, GeometryTrait, LineStringTrait, PolygonTrait, RectTrait, TriangleTrait, + CoordTrait, Dimensions, LineStringTrait, PolygonTrait, RectTrait, TriangleTrait, }; /// A parsed Polygon. From 3e75bafd02fcd34c40e662781abb86551ec176a0 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 28 Oct 2025 23:28:51 +0900 Subject: [PATCH 37/43] Handle empty linestring --- geo-traits/src/structs/linestring.rs | 20 +++++++++++++++++--- geo-traits/src/structs/multilinestring.rs | 12 +++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs index a593c28247..ac81475cfd 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/linestring.rs @@ -43,7 +43,8 @@ impl LineString { /// This will infer the dimension from the first coordinate, and will not validate that all /// coordinates have the same dimension. /// - /// Returns `None` if the input iterator is empty. + /// Returns `None` if the input iterator is empty; while the empty + /// linestring is valid, the dimension cannot be inferred. /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. @@ -60,14 +61,27 @@ impl LineString { } } + pub(crate) fn from_coords_with_dim( + coords: impl IntoIterator>, + dim: Dimensions, + ) -> Self { + match Self::from_coords(coords) { + Some(line_string) => line_string, + None => Self { + coords: Vec::new(), + dim, + }, + } + } + /// Create a new LineString from an objects implementing [LineStringTrait]. pub fn from_linestring(linestring: &impl LineStringTrait) -> Self { - Self::from_coords(linestring.coords()).unwrap() + Self::from_coords_with_dim(linestring.coords(), linestring.dim()) } /// Create a new LineString from an objects implementing [LineTrait]. pub fn from_line(line: &impl LineTrait) -> Self { - Self::from_coords(line.coords()).unwrap() + Self::from_coords_with_dim(line.coords(), line.dim()) } } diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index 9740ece4bf..5efa603922 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -43,7 +43,8 @@ impl MultiLineString { /// This will infer the dimension from the first line string, and will not validate that all /// line strings have the same dimension. /// - /// Returns `None` if the input iterator is empty. + /// Returns `None` if the input iterator is empty; while the empty + /// linestring is valid, the dimension cannot be inferred. /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. @@ -68,8 +69,13 @@ impl MultiLineString { .line_strings() .map(|l| LineString::from_linestring(&l)) .collect::>(); - let dim = line_strings[0].dimension(); - Self::new(line_strings, dim) + if line_strings.is_empty() { + // How should we infer the dimension? + todo!() + } else { + let dim = line_strings[0].dimension(); + Self::new(line_strings, dim) + } } } From d2f90638a6e046fdff9c8aad108332be3c075723 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 28 Oct 2025 23:46:13 +0900 Subject: [PATCH 38/43] Tweak --- geo-traits/src/structs/multilinestring.rs | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index 5efa603922..03177a482b 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -63,19 +63,22 @@ impl MultiLineString { } } + pub(crate) fn from_line_strings_with_dim( + line_strings: impl IntoIterator>, + dim: Dimensions, + ) -> Self { + match Self::from_line_strings(line_strings) { + Some(multi_line_string) => multi_line_string, + None => Self { + line_strings: Vec::new(), + dim, + }, + } + } + /// Create a new MultiLineString from an objects implementing [MultiLineStringTrait]. pub fn from_multilinestring(multilinestring: &impl MultiLineStringTrait) -> Self { - let line_strings = multilinestring - .line_strings() - .map(|l| LineString::from_linestring(&l)) - .collect::>(); - if line_strings.is_empty() { - // How should we infer the dimension? - todo!() - } else { - let dim = line_strings[0].dimension(); - Self::new(line_strings, dim) - } + Self::from_line_strings_with_dim(multilinestring.line_strings(), multilinestring.dim()) } } From 3036131420796b30d3d57817c080b5d8b9590cfd Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 28 Oct 2025 23:52:14 +0900 Subject: [PATCH 39/43] Rename --- geo-traits/src/structs/geometry.rs | 4 ++-- geo-traits/src/structs/linestring.rs | 4 ++-- geo-traits/src/structs/multilinestring.rs | 6 +++--- geo-traits/src/structs/polygon.rs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index 408db8f347..720f1d4d84 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -48,14 +48,14 @@ where match geometry.as_type() { crate::GeometryType::Point(geom) => Self::Point(Point::from_point(geom)), crate::GeometryType::LineString(geom) => { - Self::LineString(LineString::from_linestring(geom)) + Self::LineString(LineString::from_line_string(geom)) } crate::GeometryType::Polygon(geom) => Self::Polygon(Polygon::from_polygon(geom)), crate::GeometryType::MultiPoint(geom) => { Self::MultiPoint(MultiPoint::from_multipoint(geom)) } crate::GeometryType::MultiLineString(geom) => { - Self::MultiLineString(MultiLineString::from_multilinestring(geom)) + Self::MultiLineString(MultiLineString::from_multi_line_string(geom)) } crate::GeometryType::MultiPolygon(geom) => { Self::MultiPolygon(MultiPolygon::from_multipolygon(geom)) diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/linestring.rs index ac81475cfd..3567d58769 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/linestring.rs @@ -75,7 +75,7 @@ impl LineString { } /// Create a new LineString from an objects implementing [LineStringTrait]. - pub fn from_linestring(linestring: &impl LineStringTrait) -> Self { + pub fn from_line_string(linestring: &impl LineStringTrait) -> Self { Self::from_coords_with_dim(linestring.coords(), linestring.dim()) } @@ -182,7 +182,7 @@ mod tests { ], Dimensions::Xym, ); - let converted = LineString::from_linestring(&original); + let converted = LineString::from_line_string(&original); assert_eq!(converted.dimension(), original.dimension()); assert_eq!(converted.coords(), original.coords()); } diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multilinestring.rs index 03177a482b..9f976b0aea 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multilinestring.rs @@ -53,7 +53,7 @@ impl MultiLineString { ) -> Option { let line_strings = line_strings .into_iter() - .map(|l| LineString::from_linestring(&l)) + .map(|l| LineString::from_line_string(&l)) .collect::>(); if line_strings.is_empty() { None @@ -77,7 +77,7 @@ impl MultiLineString { } /// Create a new MultiLineString from an objects implementing [MultiLineStringTrait]. - pub fn from_multilinestring(multilinestring: &impl MultiLineStringTrait) -> Self { + pub fn from_multi_line_string(multilinestring: &impl MultiLineStringTrait) -> Self { Self::from_line_strings_with_dim(multilinestring.line_strings(), multilinestring.dim()) } } @@ -207,7 +207,7 @@ mod tests { line_xy(&[(2, 2), (3, 3), (3, 4)]), ]; let original = MultiLineString::new(lines.clone(), Dimensions::Xy); - let converted = MultiLineString::from_multilinestring(&original); + let converted = MultiLineString::from_multi_line_string(&original); assert_eq!(converted.dimension(), original.dimension()); assert_eq!(converted.line_strings(), original.line_strings()); diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index 28a14de981..be96ed8c95 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -54,7 +54,7 @@ impl Polygon { ) -> Option { let rings = rings .into_iter() - .map(|l| LineString::from_linestring(&l)) + .map(|l| LineString::from_line_string(&l)) .collect::>(); if rings.is_empty() { None From 5d0e03a1056619dcb7a357366304bf2c81079a4c Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Wed, 29 Oct 2025 00:06:06 +0900 Subject: [PATCH 40/43] Rename and tweak for consistency --- geo-traits/src/structs/geometry.rs | 4 +-- ...rycollection.rs => geometry_collection.rs} | 25 ++++++++++++----- .../structs/{linestring.rs => line_string.rs} | 4 +-- geo-traits/src/structs/mod.rs | 20 +++++++------- ...ultilinestring.rs => multi_line_string.rs} | 8 +++--- .../structs/{multipoint.rs => multi_point.rs} | 22 ++++++++++++--- .../{multipolygon.rs => multi_polygon.rs} | 27 ++++++++++++------- geo-traits/src/structs/polygon.rs | 18 +++++++++++-- 8 files changed, 89 insertions(+), 39 deletions(-) rename geo-traits/src/structs/{geometrycollection.rs => geometry_collection.rs} (79%) rename geo-traits/src/structs/{linestring.rs => line_string.rs} (97%) rename geo-traits/src/structs/{multilinestring.rs => multi_line_string.rs} (95%) rename geo-traits/src/structs/{multipoint.rs => multi_point.rs} (89%) rename geo-traits/src/structs/{multipolygon.rs => multi_polygon.rs} (89%) diff --git a/geo-traits/src/structs/geometry.rs b/geo-traits/src/structs/geometry.rs index 720f1d4d84..957b4dfbf1 100644 --- a/geo-traits/src/structs/geometry.rs +++ b/geo-traits/src/structs/geometry.rs @@ -52,13 +52,13 @@ where } crate::GeometryType::Polygon(geom) => Self::Polygon(Polygon::from_polygon(geom)), crate::GeometryType::MultiPoint(geom) => { - Self::MultiPoint(MultiPoint::from_multipoint(geom)) + Self::MultiPoint(MultiPoint::from_multi_point(geom)) } crate::GeometryType::MultiLineString(geom) => { Self::MultiLineString(MultiLineString::from_multi_line_string(geom)) } crate::GeometryType::MultiPolygon(geom) => { - Self::MultiPolygon(MultiPolygon::from_multipolygon(geom)) + Self::MultiPolygon(MultiPolygon::from_multi_polygon(geom)) } crate::GeometryType::GeometryCollection(geom) => { Self::GeometryCollection(GeometryCollection::from_geometry_collection(geom)) diff --git a/geo-traits/src/structs/geometrycollection.rs b/geo-traits/src/structs/geometry_collection.rs similarity index 79% rename from geo-traits/src/structs/geometrycollection.rs rename to geo-traits/src/structs/geometry_collection.rs index d013954686..3ae61b81d7 100644 --- a/geo-traits/src/structs/geometrycollection.rs +++ b/geo-traits/src/structs/geometry_collection.rs @@ -40,10 +40,8 @@ impl GeometryCollection { /// This will infer the dimension from the first geometry, and will not validate that all /// geometries have the same dimension. /// - /// This returns `None` when - /// - /// - the input iterator is empty - /// - all the geometries are `Line`, `Triangle`, or `Rect` (these geometries are silently skipped) + /// Returns `None` if the input iterator is empty; while an empty GEOMETRYCOLLECTION is valid, the + /// dimension cannot be inferred. /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. @@ -62,9 +60,24 @@ impl GeometryCollection { } } + pub(crate) fn from_geometries_with_dim( + geoms: impl IntoIterator>, + dim: Dimensions, + ) -> Self { + match Self::from_geometries(geoms) { + Some(geometry_collection) => geometry_collection, + None => Self { + geoms: Vec::new(), + dim, + }, + } + } + /// Create a new GeometryCollection from an objects implementing [GeometryCollectionTrait]. - pub fn from_geometry_collection(geoms: &impl GeometryCollectionTrait) -> Self { - Self::from_geometries(geoms.geometries()).unwrap() + pub fn from_geometry_collection( + geometry_collection: &impl GeometryCollectionTrait, + ) -> Self { + Self::from_geometries_with_dim(geometry_collection.geometries(), geometry_collection.dim()) } } diff --git a/geo-traits/src/structs/linestring.rs b/geo-traits/src/structs/line_string.rs similarity index 97% rename from geo-traits/src/structs/linestring.rs rename to geo-traits/src/structs/line_string.rs index 3567d58769..6fef63a244 100644 --- a/geo-traits/src/structs/linestring.rs +++ b/geo-traits/src/structs/line_string.rs @@ -43,8 +43,8 @@ impl LineString { /// This will infer the dimension from the first coordinate, and will not validate that all /// coordinates have the same dimension. /// - /// Returns `None` if the input iterator is empty; while the empty - /// linestring is valid, the dimension cannot be inferred. + /// Returns `None` if the input iterator is empty; while an empty LINESTRING is valid, the + /// dimension cannot be inferred. /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. diff --git a/geo-traits/src/structs/mod.rs b/geo-traits/src/structs/mod.rs index d365b60658..a5792aa3ed 100644 --- a/geo-traits/src/structs/mod.rs +++ b/geo-traits/src/structs/mod.rs @@ -2,20 +2,20 @@ pub use self::coord::Coord; pub use self::geometry::Geometry; -pub use self::geometrycollection::GeometryCollection; -pub use self::linestring::LineString; -pub use self::multilinestring::MultiLineString; -pub use self::multipoint::MultiPoint; -pub use self::multipolygon::MultiPolygon; +pub use self::geometry_collection::GeometryCollection; +pub use self::line_string::LineString; +pub use self::multi_line_string::MultiLineString; +pub use self::multi_point::MultiPoint; +pub use self::multi_polygon::MultiPolygon; pub use self::point::Point; pub use self::polygon::Polygon; mod coord; mod geometry; -mod geometrycollection; -mod linestring; -mod multilinestring; -mod multipoint; -mod multipolygon; +mod geometry_collection; +mod line_string; +mod multi_line_string; +mod multi_point; +mod multi_polygon; mod point; mod polygon; diff --git a/geo-traits/src/structs/multilinestring.rs b/geo-traits/src/structs/multi_line_string.rs similarity index 95% rename from geo-traits/src/structs/multilinestring.rs rename to geo-traits/src/structs/multi_line_string.rs index 9f976b0aea..de30401090 100644 --- a/geo-traits/src/structs/multilinestring.rs +++ b/geo-traits/src/structs/multi_line_string.rs @@ -43,8 +43,8 @@ impl MultiLineString { /// This will infer the dimension from the first line string, and will not validate that all /// line strings have the same dimension. /// - /// Returns `None` if the input iterator is empty; while the empty - /// linestring is valid, the dimension cannot be inferred. + /// Returns `None` if the input iterator is empty; while an empty MULTILINESTRING is valid, the + /// dimension cannot be inferred. /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. @@ -77,8 +77,8 @@ impl MultiLineString { } /// Create a new MultiLineString from an objects implementing [MultiLineStringTrait]. - pub fn from_multi_line_string(multilinestring: &impl MultiLineStringTrait) -> Self { - Self::from_line_strings_with_dim(multilinestring.line_strings(), multilinestring.dim()) + pub fn from_multi_line_string(multi_line_string: &impl MultiLineStringTrait) -> Self { + Self::from_line_strings_with_dim(multi_line_string.line_strings(), multi_line_string.dim()) } } diff --git a/geo-traits/src/structs/multipoint.rs b/geo-traits/src/structs/multi_point.rs similarity index 89% rename from geo-traits/src/structs/multipoint.rs rename to geo-traits/src/structs/multi_point.rs index 3d16f2dcc4..01f2026384 100644 --- a/geo-traits/src/structs/multipoint.rs +++ b/geo-traits/src/structs/multi_point.rs @@ -43,7 +43,8 @@ impl MultiPoint { /// This will infer the dimension from the first point, and will not validate that all /// points have the same dimension. /// - /// Returns `None` if the input iterator is empty. + /// Returns `None` if the input iterator is empty; while an empty MULTIPOINT is valid, the + /// dimension cannot be inferred. /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. @@ -60,6 +61,19 @@ impl MultiPoint { } } + pub(crate) fn from_points_with_dim( + points: impl IntoIterator>, + dim: Dimensions, + ) -> Self { + match Self::from_points(points) { + Some(multi_point) => multi_point, + None => Self { + points: Vec::new(), + dim, + }, + } + } + /// Create a new MultiPoint from a non-empty sequence of objects implementing [CoordTrait]. pub fn from_coords(coords: impl IntoIterator>) -> Option { let points = coords @@ -75,8 +89,8 @@ impl MultiPoint { } /// Create a new MultiPoint from an objects implementing [MultiPointTrait]. - pub fn from_multipoint(multipoint: &impl MultiPointTrait) -> Self { - Self::from_points(multipoint.points()).unwrap() + pub fn from_multi_point(multi_point: &impl MultiPointTrait) -> Self { + Self::from_points_with_dim(multi_point.points(), multi_point.dim()) } } @@ -189,7 +203,7 @@ mod tests { ), ]; let original = MultiPoint::new(points.clone(), Dimensions::Xym); - let converted = MultiPoint::from_multipoint(&original); + let converted = MultiPoint::from_multi_point(&original); assert_eq!(converted.dimension(), original.dimension()); assert_eq!(converted.points(), original.points()); diff --git a/geo-traits/src/structs/multipolygon.rs b/geo-traits/src/structs/multi_polygon.rs similarity index 89% rename from geo-traits/src/structs/multipolygon.rs rename to geo-traits/src/structs/multi_polygon.rs index 3d10529c47..ba40f8ee26 100644 --- a/geo-traits/src/structs/multipolygon.rs +++ b/geo-traits/src/structs/multi_polygon.rs @@ -43,7 +43,8 @@ impl MultiPolygon { /// This will infer the dimension from the first polygon, and will not validate that all /// polygons have the same dimension. /// - /// Returns `None` if the input iterator is empty. + /// Returns `None` if the input iterator is empty; while an empty MULTIPOLYGON is valid, the + /// dimension cannot be inferred. /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. @@ -62,14 +63,22 @@ impl MultiPolygon { } } + pub(crate) fn from_polygons_with_dim( + polygons: impl IntoIterator>, + dim: Dimensions, + ) -> Self { + match Self::from_polygons(polygons) { + Some(multipolygon) => multipolygon, + None => Self { + polygons: Vec::new(), + dim, + }, + } + } + /// Create a new MultiPolygon from an objects implementing [MultiPolygonTrait]. - pub fn from_multipolygon(multipolygon: &impl MultiPolygonTrait) -> Self { - let polygons = multipolygon - .polygons() - .map(|p| Polygon::from_polygon(&p)) - .collect::>(); - let dim = polygons[0].dimension(); - Self::new(polygons, dim) + pub fn from_multi_polygon(multi_polygon: &impl MultiPolygonTrait) -> Self { + Self::from_polygons_with_dim(multi_polygon.polygons(), multi_polygon.dim()) } } @@ -215,7 +224,7 @@ mod tests { let original = MultiPolygon::new(vec![polygon_a.clone(), polygon_b.clone()], Dimensions::Xy); - let converted = MultiPolygon::from_multipolygon(&original); + let converted = MultiPolygon::from_multi_polygon(&original); assert_eq!(converted.dimension(), original.dimension()); assert_eq!(converted.polygons(), original.polygons()); } diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index be96ed8c95..e2a32b36ee 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -45,7 +45,8 @@ impl Polygon { /// This will infer the dimension from the first line string, and will not validate that all /// line strings have the same dimension. /// - /// Returns `None` if the input iterator is empty. + /// Returns `None` if the input iterator is empty; while an empty POLYGON is valid, the + /// dimension cannot be inferred. /// /// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting /// to an [empty][Self::empty] geometry with specified dimension. @@ -64,11 +65,24 @@ impl Polygon { } } + pub(crate) fn from_rings_with_dim( + rings: impl IntoIterator>, + dim: Dimensions, + ) -> Self { + match Self::from_rings(rings) { + Some(polygon) => polygon, + None => Self { + rings: Vec::new(), + dim, + }, + } + } + /// Create a new polygon from an object implementing [PolygonTrait]. pub fn from_polygon(polygon: &impl PolygonTrait) -> Self { let exterior = polygon.exterior().into_iter(); let other = polygon.interiors(); - Self::from_rings(exterior.chain(other)).unwrap() + Self::from_rings_with_dim(exterior.chain(other), polygon.dim()) } /// Create a new polygon from an object implementing [TriangleTrait]. From 755b78902b6723e041717b245cb43ae966d2582a Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Wed, 29 Oct 2025 00:52:48 +0900 Subject: [PATCH 41/43] Add more tests for empty cases --- geo-traits/src/structs/line_string.rs | 13 +++++++++---- geo-traits/src/structs/multi_line_string.rs | 13 +++++++++---- geo-traits/src/structs/multi_point.rs | 13 +++++++++---- geo-traits/src/structs/multi_polygon.rs | 13 +++++++++---- geo-traits/src/structs/polygon.rs | 13 +++++++++---- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/geo-traits/src/structs/line_string.rs b/geo-traits/src/structs/line_string.rs index 6fef63a244..d3667a0ee9 100644 --- a/geo-traits/src/structs/line_string.rs +++ b/geo-traits/src/structs/line_string.rs @@ -67,10 +67,7 @@ impl LineString { ) -> Self { match Self::from_coords(coords) { Some(line_string) => line_string, - None => Self { - coords: Vec::new(), - dim, - }, + None => Self::empty(dim), } } @@ -136,6 +133,14 @@ mod tests { assert!(ls.coords().is_empty()); } + #[test] + fn from_linestring_preserves_dimension_for_empty() { + let source = LineString::::empty(Dimensions::Xyzm); + let converted = LineString::from_line_string(&source); + assert_eq!(converted.dimension(), Dimensions::Xyzm); + assert!(converted.coords().is_empty()); + } + #[test] fn from_coords_infers_dimension() { let coords = vec![ diff --git a/geo-traits/src/structs/multi_line_string.rs b/geo-traits/src/structs/multi_line_string.rs index de30401090..f24b162511 100644 --- a/geo-traits/src/structs/multi_line_string.rs +++ b/geo-traits/src/structs/multi_line_string.rs @@ -69,10 +69,7 @@ impl MultiLineString { ) -> Self { match Self::from_line_strings(line_strings) { Some(multi_line_string) => multi_line_string, - None => Self { - line_strings: Vec::new(), - dim, - }, + None => Self::empty(dim), } } @@ -149,6 +146,14 @@ mod tests { assert!(mls.line_strings().is_empty()); } + #[test] + fn from_multilinestring_preserves_dimension_for_empty() { + let source = MultiLineString::::empty(Dimensions::Xyzm); + let converted = MultiLineString::from_multi_line_string(&source); + assert_eq!(converted.dimension(), Dimensions::Xyzm); + assert!(converted.line_strings().is_empty()); + } + #[test] fn from_line_strings_infers_dimension() { let lines = vec![ diff --git a/geo-traits/src/structs/multi_point.rs b/geo-traits/src/structs/multi_point.rs index 01f2026384..1cca928409 100644 --- a/geo-traits/src/structs/multi_point.rs +++ b/geo-traits/src/structs/multi_point.rs @@ -67,10 +67,7 @@ impl MultiPoint { ) -> Self { match Self::from_points(points) { Some(multi_point) => multi_point, - None => Self { - points: Vec::new(), - dim, - }, + None => Self::empty(dim), } } @@ -146,6 +143,14 @@ mod tests { assert!(mp.points().is_empty()); } + #[test] + fn from_multipoint_preserves_dimension_for_empty() { + let source = MultiPoint::::empty(Dimensions::Xyzm); + let converted = MultiPoint::from_multi_point(&source); + assert_eq!(converted.dimension(), Dimensions::Xyzm); + assert!(converted.points().is_empty()); + } + #[test] fn from_points_infers_dimension() { let points = vec![ diff --git a/geo-traits/src/structs/multi_polygon.rs b/geo-traits/src/structs/multi_polygon.rs index ba40f8ee26..41fed1a3a9 100644 --- a/geo-traits/src/structs/multi_polygon.rs +++ b/geo-traits/src/structs/multi_polygon.rs @@ -69,10 +69,7 @@ impl MultiPolygon { ) -> Self { match Self::from_polygons(polygons) { Some(multipolygon) => multipolygon, - None => Self { - polygons: Vec::new(), - dim, - }, + None => Self::empty(dim), } } @@ -172,6 +169,14 @@ mod tests { assert!(mp.polygons().is_empty()); } + #[test] + fn from_multipolygon_preserves_dimension_for_empty() { + let source = MultiPolygon::::empty(Dimensions::Xyzm); + let converted = MultiPolygon::from_multi_polygon(&source); + assert_eq!(converted.dimension(), Dimensions::Xyzm); + assert!(converted.polygons().is_empty()); + } + #[test] fn from_polygons_infers_dimension() { let exterior = LineString::new( diff --git a/geo-traits/src/structs/polygon.rs b/geo-traits/src/structs/polygon.rs index e2a32b36ee..d1710ef09b 100644 --- a/geo-traits/src/structs/polygon.rs +++ b/geo-traits/src/structs/polygon.rs @@ -71,10 +71,7 @@ impl Polygon { ) -> Self { match Self::from_rings(rings) { Some(polygon) => polygon, - None => Self { - rings: Vec::new(), - dim, - }, + None => Self::empty(dim), } } @@ -239,6 +236,14 @@ mod tests { assert!(polygon.rings().is_empty()); } + #[test] + fn from_polygon_preserves_dimension_for_empty() { + let source = Polygon::::empty(Dimensions::Xyzm); + let converted = Polygon::from_polygon(&source); + assert_eq!(converted.dimension(), Dimensions::Xyzm); + assert!(converted.rings().is_empty()); + } + #[test] fn from_rings_infers_dimension() { let exterior = LineString::new( From 57a6fd8393d585aa29d538fdc0597cbb6a6db4fb Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Wed, 5 Nov 2025 09:09:59 +0900 Subject: [PATCH 42/43] Remove unnecessary .clone() in tests --- geo-traits/src/structs/line_string.rs | 2 +- geo-traits/src/structs/multi_line_string.rs | 3 +-- geo-traits/src/structs/multi_point.rs | 2 +- geo-traits/src/structs/multi_polygon.rs | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/geo-traits/src/structs/line_string.rs b/geo-traits/src/structs/line_string.rs index d3667a0ee9..2f93e0c251 100644 --- a/geo-traits/src/structs/line_string.rs +++ b/geo-traits/src/structs/line_string.rs @@ -157,7 +157,7 @@ mod tests { m: None, }, ]; - let ls = LineString::from_coords(coords.clone()).expect("coords are non-empty"); + let ls = LineString::from_coords(&coords).expect("coords are non-empty"); assert_eq!(ls.dimension(), Dimensions::Xyz); assert_eq!(ls.coords(), coords.as_slice()); } diff --git a/geo-traits/src/structs/multi_line_string.rs b/geo-traits/src/structs/multi_line_string.rs index f24b162511..2980ddd745 100644 --- a/geo-traits/src/structs/multi_line_string.rs +++ b/geo-traits/src/structs/multi_line_string.rs @@ -193,8 +193,7 @@ mod tests { ), ]; - let mls = - MultiLineString::from_line_strings(lines.clone()).expect("line strings are non-empty"); + let mls = MultiLineString::from_line_strings(&lines).expect("line strings are non-empty"); assert_eq!(mls.dimension(), Dimensions::Xyz); assert_eq!(mls.line_strings(), lines.as_slice()); } diff --git a/geo-traits/src/structs/multi_point.rs b/geo-traits/src/structs/multi_point.rs index 1cca928409..cfb9334f58 100644 --- a/geo-traits/src/structs/multi_point.rs +++ b/geo-traits/src/structs/multi_point.rs @@ -174,7 +174,7 @@ mod tests { ), ]; - let mp = MultiPoint::from_points(points.clone()).expect("points are non-empty"); + let mp = MultiPoint::from_points(&points).expect("points are non-empty"); assert_eq!(mp.dimension(), Dimensions::Xyz); assert_eq!(mp.points(), points.as_slice()); } diff --git a/geo-traits/src/structs/multi_polygon.rs b/geo-traits/src/structs/multi_polygon.rs index 41fed1a3a9..aef4dcb463 100644 --- a/geo-traits/src/structs/multi_polygon.rs +++ b/geo-traits/src/structs/multi_polygon.rs @@ -209,8 +209,7 @@ mod tests { Dimensions::Xym, ); let polygon = Polygon::new(vec![exterior.clone()], Dimensions::Xym); - let mp = - MultiPolygon::from_polygons(vec![polygon.clone()]).expect("polygons are non-empty"); + let mp = MultiPolygon::from_polygons(&[polygon.clone()]).expect("polygons are non-empty"); assert_eq!(mp.dimension(), Dimensions::Xym); assert_eq!(mp.polygons(), &[polygon]); From 7c1688f0c56a803b95f2e032e1e500c49e5cbbe7 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 22 Nov 2025 11:33:07 +0900 Subject: [PATCH 43/43] Handle Dimensions::Unknown somehow --- geo-traits/src/structs/coord.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/geo-traits/src/structs/coord.rs b/geo-traits/src/structs/coord.rs index edbe3fe4e0..15669c63a8 100644 --- a/geo-traits/src/structs/coord.rs +++ b/geo-traits/src/structs/coord.rs @@ -26,11 +26,11 @@ impl Coord { let y = coord.y(); match coord.dim() { - Dimensions::Xyzm | Dimensions::Unknown(4) => Self { + Dimensions::Xy | Dimensions::Unknown(2) => Self { x, y, - z: coord.nth(2), - m: coord.nth(3), + z: None, + m: None, }, Dimensions::Xyz | Dimensions::Unknown(3) => Self { x, @@ -44,13 +44,14 @@ impl Coord { z: None, m: coord.nth(2), }, - Dimensions::Xy | Dimensions::Unknown(2) => Self { + // For >4 dimension of Unknown, we don't know how to handle it; + // we simply discard these extra dimensions. + Dimensions::Xyzm | Dimensions::Unknown(_) => Self { x, y, - z: None, - m: None, + z: coord.nth(2), + m: coord.nth(3), }, - Dimensions::Unknown(_) => todo!(), } }