From 02bfe529cb7d689da1e2f1a7c98fd8fd93a00b5f Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Sun, 3 Nov 2024 07:49:42 -0500 Subject: [PATCH 1/2] Converter functions for geo-traits to geo-types --- geo-traits/src/lib.rs | 2 + geo-traits/src/to_geo.rs | 163 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 geo-traits/src/to_geo.rs diff --git a/geo-traits/src/lib.rs b/geo-traits/src/lib.rs index de24bd263..9b63805f3 100644 --- a/geo-traits/src/lib.rs +++ b/geo-traits/src/lib.rs @@ -44,4 +44,6 @@ mod multi_polygon; mod point; mod polygon; mod rect; +#[cfg(feature = "geo-types")] +pub mod to_geo; mod triangle; diff --git a/geo-traits/src/to_geo.rs b/geo-traits/src/to_geo.rs new file mode 100644 index 000000000..e5d641509 --- /dev/null +++ b/geo-traits/src/to_geo.rs @@ -0,0 +1,163 @@ +//! Convert structs that implement geo-traits to [geo-types] objects. + +use geo_types::{ + Coord, CoordNum, Geometry, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, + MultiPolygon, Point, Polygon, Rect, Triangle, +}; + +use crate::{ + CoordTrait, GeometryCollectionTrait, GeometryTrait, GeometryType, LineStringTrait, LineTrait, + MultiLineStringTrait, MultiPointTrait, MultiPolygonTrait, PointTrait, PolygonTrait, RectTrait, + TriangleTrait, +}; + +/// Convert any coordinate to a [`Coord`]. +/// +/// Only the first two dimensions will be kept. +pub fn coord_to_geo(coord: &impl CoordTrait) -> Coord { + Coord { + x: coord.x(), + y: coord.y(), + } +} + +/// Convert any Point to a [`Point`]. +/// +/// Only the first two dimensions will be kept. +pub fn point_to_geo(point: &impl PointTrait) -> Point { + if let Some(coord) = point.coord() { + Point(coord_to_geo(&coord)) + } else { + panic!("converting empty point to geo not implemented") + } +} + +/// Convert any LineString to a [`LineString`]. +/// +/// Only the first two dimensions will be kept. +pub fn line_string_to_geo(line_string: &impl LineStringTrait) -> LineString { + LineString::new( + line_string + .coords() + .map(|coord| coord_to_geo(&coord)) + .collect(), + ) +} + +/// Convert any Polygon to a [`Polygon`]. +/// +/// Only the first two dimensions will be kept. +pub fn polygon_to_geo>(polygon: &P) -> Polygon { + let exterior = if let Some(exterior) = polygon.exterior() { + line_string_to_geo(&exterior) + } else { + LineString::new(vec![]) + }; + let interiors = polygon + .interiors() + .map(|interior| line_string_to_geo(&interior)) + .collect(); + Polygon::new(exterior, interiors) +} + +/// Convert any MultiPoint to a [`MultiPoint`]. +/// +/// Only the first two dimensions will be kept. +pub fn multi_point_to_geo(multi_point: &impl MultiPointTrait) -> MultiPoint { + MultiPoint::new( + multi_point + .points() + .map(|point| point_to_geo(&point)) + .collect(), + ) +} + +/// Convert any MultiLineString to a [`MultiLineString`]. +/// +/// Only the first two dimensions will be kept. +pub fn multi_line_string_to_geo( + multi_line_string: &impl MultiLineStringTrait, +) -> MultiLineString { + MultiLineString::new( + multi_line_string + .line_strings() + .map(|line| line_string_to_geo(&line)) + .collect(), + ) +} + +/// Convert any MultiPolygon to a [`MultiPolygon`]. +/// +/// Only the first two dimensions will be kept. +pub fn multi_polygon_to_geo( + multi_polygon: &impl MultiPolygonTrait, +) -> MultiPolygon { + MultiPolygon::new( + multi_polygon + .polygons() + .map(|polygon| polygon_to_geo(&polygon)) + .collect(), + ) +} + +/// Convert any Rect to a [`Rect`]. +/// +/// Only the first two dimensions will be kept. +pub fn rect_to_geo(rect: &impl RectTrait) -> Rect { + let c1 = coord_to_geo(&rect.min()); + let c2 = coord_to_geo(&rect.max()); + Rect::new(c1, c2) +} + +/// Convert any Line to a [`Line`]. +/// +/// Only the first two dimensions will be kept. +pub fn line_to_geo(line: &impl LineTrait) -> Line { + let start = coord_to_geo(&line.start()); + let end = coord_to_geo(&line.end()); + Line::new(start, end) +} + +/// Convert any Triangle to a [`Triangle`]. +/// +/// Only the first two dimensions will be kept. +pub fn triangle_to_geo(triangle: &impl TriangleTrait) -> Triangle { + let v1 = coord_to_geo(&triangle.first()); + let v2 = coord_to_geo(&triangle.second()); + let v3 = coord_to_geo(&triangle.third()); + Triangle::new(v1, v2, v3) +} + +/// Convert any Geometry to a [`Geometry`]. +/// +/// Only the first two dimensions will be kept. +pub fn geometry_to_geo(geometry: &impl GeometryTrait) -> Geometry { + use GeometryType::*; + + match geometry.as_type() { + Point(geom) => Geometry::Point(point_to_geo(geom)), + LineString(geom) => Geometry::LineString(line_string_to_geo(geom)), + Polygon(geom) => Geometry::Polygon(polygon_to_geo(geom)), + MultiPoint(geom) => Geometry::MultiPoint(multi_point_to_geo(geom)), + MultiLineString(geom) => Geometry::MultiLineString(multi_line_string_to_geo(geom)), + MultiPolygon(geom) => Geometry::MultiPolygon(multi_polygon_to_geo(geom)), + GeometryCollection(geom) => Geometry::GeometryCollection(geometry_collection_to_geo(geom)), + Rect(geom) => Geometry::Rect(rect_to_geo(geom)), + Line(geom) => Geometry::Line(line_to_geo(geom)), + Triangle(geom) => Geometry::Triangle(triangle_to_geo(geom)), + } +} + +/// Convert any GeometryCollection to a [`GeometryCollection`]. +/// +/// Only the first two dimensions will be kept. +pub fn geometry_collection_to_geo( + geometry_collection: &impl GeometryCollectionTrait, +) -> GeometryCollection { + GeometryCollection::new_from( + geometry_collection + .geometries() + .map(|geometry| geometry_to_geo(&geometry)) + .collect(), + ) +} From b55300505b11f429c565d72dbbec22237f1cfa5e Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Thu, 7 Nov 2024 17:21:32 -0500 Subject: [PATCH 2/2] Switch to traits --- geo-traits/src/to_geo.rs | 300 +++++++++++++++++++++++++++------------ 1 file changed, 213 insertions(+), 87 deletions(-) diff --git a/geo-traits/src/to_geo.rs b/geo-traits/src/to_geo.rs index e5d641509..95e77573d 100644 --- a/geo-traits/src/to_geo.rs +++ b/geo-traits/src/to_geo.rs @@ -14,150 +14,276 @@ use crate::{ /// Convert any coordinate to a [`Coord`]. /// /// Only the first two dimensions will be kept. -pub fn coord_to_geo(coord: &impl CoordTrait) -> Coord { - Coord { - x: coord.x(), - y: coord.y(), +pub trait ToGeoCoord { + /// Convert to a geo_types [`Coord`]. + fn to_coord(&self) -> Coord; +} + +impl> ToGeoCoord for G { + fn to_coord(&self) -> Coord { + Coord { + x: self.x(), + y: self.y(), + } } } /// Convert any Point to a [`Point`]. /// /// Only the first two dimensions will be kept. -pub fn point_to_geo(point: &impl PointTrait) -> Point { - if let Some(coord) = point.coord() { - Point(coord_to_geo(&coord)) - } else { - panic!("converting empty point to geo not implemented") +pub trait ToGeoPoint { + /// Convert to a geo_types [`Point`]. + /// + /// # Panics + /// + /// This will panic on an empty point. + fn to_point(&self) -> Point { + self.try_to_point() + .expect("geo-types does not support empty points.") + } + + /// Convert to a geo_types [`Point`]. + /// + /// Empty points will return `None`. + fn try_to_point(&self) -> Option>; +} + +impl> ToGeoPoint for G { + fn try_to_point(&self) -> Option> { + self.coord().map(|coord| Point(coord.to_coord())) } } /// Convert any LineString to a [`LineString`]. /// /// Only the first two dimensions will be kept. -pub fn line_string_to_geo(line_string: &impl LineStringTrait) -> LineString { - LineString::new( - line_string - .coords() - .map(|coord| coord_to_geo(&coord)) - .collect(), - ) +pub trait ToGeoLineString { + /// Convert to a geo_types [`LineString`]. + fn to_line_string(&self) -> LineString; +} + +impl> ToGeoLineString for G { + fn to_line_string(&self) -> LineString { + LineString::new(self.coords().map(|coord| coord.to_coord()).collect()) + } } /// Convert any Polygon to a [`Polygon`]. /// /// Only the first two dimensions will be kept. -pub fn polygon_to_geo>(polygon: &P) -> Polygon { - let exterior = if let Some(exterior) = polygon.exterior() { - line_string_to_geo(&exterior) - } else { - LineString::new(vec![]) - }; - let interiors = polygon - .interiors() - .map(|interior| line_string_to_geo(&interior)) - .collect(); - Polygon::new(exterior, interiors) +pub trait ToGeoPolygon { + /// Convert to a geo_types [`Polygon`]. + fn to_polygon(&self) -> Polygon; +} + +impl> ToGeoPolygon for G { + fn to_polygon(&self) -> Polygon { + let exterior = if let Some(exterior) = self.exterior() { + exterior.to_line_string() + } else { + LineString::new(vec![]) + }; + let interiors = self + .interiors() + .map(|interior| interior.to_line_string()) + .collect(); + Polygon::new(exterior, interiors) + } } /// Convert any MultiPoint to a [`MultiPoint`]. /// /// Only the first two dimensions will be kept. -pub fn multi_point_to_geo(multi_point: &impl MultiPointTrait) -> MultiPoint { - MultiPoint::new( - multi_point - .points() - .map(|point| point_to_geo(&point)) - .collect(), - ) +pub trait ToGeoMultiPoint { + /// Convert to a geo_types [`MultiPoint`]. + /// + /// # Panics + /// + /// This will panic if any of the points contained in the MultiPoint are empty. + fn to_multi_point(&self) -> MultiPoint { + self.try_to_multi_point() + .expect("geo-types does not support MultiPoint containing empty points.") + } + + /// Convert to a geo_types [`MultiPoint`]. + /// + /// `None` will be returned if any of the points contained in the MultiPoint are empty. + fn try_to_multi_point(&self) -> Option>; +} + +impl> ToGeoMultiPoint for G { + fn try_to_multi_point(&self) -> Option> { + let mut geo_points = vec![]; + for point in self.points() { + if let Some(geo_point) = point.try_to_point() { + geo_points.push(geo_point); + } else { + // Return None if any points are empty + return None; + } + } + Some(MultiPoint::new(geo_points)) + } } /// Convert any MultiLineString to a [`MultiLineString`]. /// /// Only the first two dimensions will be kept. -pub fn multi_line_string_to_geo( - multi_line_string: &impl MultiLineStringTrait, -) -> MultiLineString { - MultiLineString::new( - multi_line_string - .line_strings() - .map(|line| line_string_to_geo(&line)) - .collect(), - ) +pub trait ToGeoMultiLineString { + /// Convert to a geo_types [`MultiLineString`]. + fn to_multi_line_string(&self) -> MultiLineString; +} + +impl> ToGeoMultiLineString for G { + fn to_multi_line_string(&self) -> MultiLineString { + MultiLineString::new( + self.line_strings() + .map(|line_string| line_string.to_line_string()) + .collect(), + ) + } } /// Convert any MultiPolygon to a [`MultiPolygon`]. /// /// Only the first two dimensions will be kept. -pub fn multi_polygon_to_geo( - multi_polygon: &impl MultiPolygonTrait, -) -> MultiPolygon { - MultiPolygon::new( - multi_polygon - .polygons() - .map(|polygon| polygon_to_geo(&polygon)) - .collect(), - ) +pub trait ToGeoMultiPolygon { + /// Convert to a geo_types [`MultiPolygon`]. + fn to_multi_polygon(&self) -> MultiPolygon; +} + +impl> ToGeoMultiPolygon for G { + fn to_multi_polygon(&self) -> MultiPolygon { + MultiPolygon::new( + self.polygons() + .map(|polygon| polygon.to_polygon()) + .collect(), + ) + } } /// Convert any Rect to a [`Rect`]. /// /// Only the first two dimensions will be kept. -pub fn rect_to_geo(rect: &impl RectTrait) -> Rect { - let c1 = coord_to_geo(&rect.min()); - let c2 = coord_to_geo(&rect.max()); - Rect::new(c1, c2) +pub trait ToGeoRect { + /// Convert to a geo_types [`Rect`]. + fn to_rect(&self) -> Rect; +} + +impl> ToGeoRect for G { + fn to_rect(&self) -> Rect { + let c1 = self.min().to_coord(); + let c2 = self.max().to_coord(); + Rect::new(c1, c2) + } } /// Convert any Line to a [`Line`]. /// /// Only the first two dimensions will be kept. -pub fn line_to_geo(line: &impl LineTrait) -> Line { - let start = coord_to_geo(&line.start()); - let end = coord_to_geo(&line.end()); - Line::new(start, end) +pub trait ToGeoLine { + /// Convert to a geo_types [`Line`]. + fn to_line(&self) -> Line; +} + +impl> ToGeoLine for G { + fn to_line(&self) -> Line { + let start = self.start().to_coord(); + let end = self.end().to_coord(); + Line::new(start, end) + } } /// Convert any Triangle to a [`Triangle`]. /// /// Only the first two dimensions will be kept. -pub fn triangle_to_geo(triangle: &impl TriangleTrait) -> Triangle { - let v1 = coord_to_geo(&triangle.first()); - let v2 = coord_to_geo(&triangle.second()); - let v3 = coord_to_geo(&triangle.third()); - Triangle::new(v1, v2, v3) +pub trait ToGeoTriangle { + /// Convert to a geo_types [`Triangle`]. + fn to_triangle(&self) -> Triangle; +} + +impl> ToGeoTriangle for G { + fn to_triangle(&self) -> Triangle { + let v1 = self.first().to_coord(); + let v2 = self.second().to_coord(); + let v3 = self.third().to_coord(); + Triangle::new(v1, v2, v3) + } } /// Convert any Geometry to a [`Geometry`]. /// /// Only the first two dimensions will be kept. -pub fn geometry_to_geo(geometry: &impl GeometryTrait) -> Geometry { - use GeometryType::*; +pub trait ToGeoGeometry { + /// Convert to a geo_types [`Geometry`]. + /// + /// # Panics + /// + /// This will panic on an empty point or a MultiPoint containing empty points. + fn to_geometry(&self) -> Geometry { + self.try_to_geometry().expect( + "geo-types does not support empty point or a MultiPoint containing empty points.", + ) + } + + /// Convert to a geo_types [`Geometry`]. + /// + /// Empty Geometrys will return `None`. + fn try_to_geometry(&self) -> Option>; +} + +impl> ToGeoGeometry for G { + fn try_to_geometry(&self) -> Option> { + use GeometryType::*; - match geometry.as_type() { - Point(geom) => Geometry::Point(point_to_geo(geom)), - LineString(geom) => Geometry::LineString(line_string_to_geo(geom)), - Polygon(geom) => Geometry::Polygon(polygon_to_geo(geom)), - MultiPoint(geom) => Geometry::MultiPoint(multi_point_to_geo(geom)), - MultiLineString(geom) => Geometry::MultiLineString(multi_line_string_to_geo(geom)), - MultiPolygon(geom) => Geometry::MultiPolygon(multi_polygon_to_geo(geom)), - GeometryCollection(geom) => Geometry::GeometryCollection(geometry_collection_to_geo(geom)), - Rect(geom) => Geometry::Rect(rect_to_geo(geom)), - Line(geom) => Geometry::Line(line_to_geo(geom)), - Triangle(geom) => Geometry::Triangle(triangle_to_geo(geom)), + match self.as_type() { + Point(geom) => geom.try_to_point().map(Geometry::Point), + LineString(geom) => Some(Geometry::LineString(geom.to_line_string())), + Polygon(geom) => Some(Geometry::Polygon(geom.to_polygon())), + MultiPoint(geom) => geom.try_to_multi_point().map(Geometry::MultiPoint), + MultiLineString(geom) => Some(Geometry::MultiLineString(geom.to_multi_line_string())), + MultiPolygon(geom) => Some(Geometry::MultiPolygon(geom.to_multi_polygon())), + GeometryCollection(geom) => geom + .try_to_geometry_collection() + .map(Geometry::GeometryCollection), + Rect(geom) => Some(Geometry::Rect(geom.to_rect())), + Line(geom) => Some(Geometry::Line(geom.to_line())), + Triangle(geom) => Some(Geometry::Triangle(geom.to_triangle())), + } } } /// Convert any GeometryCollection to a [`GeometryCollection`]. /// /// Only the first two dimensions will be kept. -pub fn geometry_collection_to_geo( - geometry_collection: &impl GeometryCollectionTrait, -) -> GeometryCollection { - GeometryCollection::new_from( - geometry_collection - .geometries() - .map(|geometry| geometry_to_geo(&geometry)) - .collect(), - ) +pub trait ToGeoGeometryCollection { + /// Convert to a geo_types [`GeometryCollection`]. + /// + /// # Panics + /// + /// This will panic on an empty point or a MultiPoint containing empty points. + fn to_geometry_collection(&self) -> GeometryCollection { + self.try_to_geometry_collection() + .expect("geo-types does not support empty GeometryCollections.") + } + + /// Convert to a geo_types [`GeometryCollection`]. + /// + /// This will return `None` for an empty point or a MultiPoint containing empty points. + fn try_to_geometry_collection(&self) -> Option>; +} + +impl> ToGeoGeometryCollection for G { + fn try_to_geometry_collection(&self) -> Option> { + let mut geo_geometries = vec![]; + for geom in self.geometries() { + if let Some(geo_geom) = geom.try_to_geometry() { + geo_geometries.push(geo_geom); + } else { + // Return None if any points are empty + return None; + } + } + Some(GeometryCollection::new_from(geo_geometries)) + } }