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..95e77573d --- /dev/null +++ b/geo-traits/src/to_geo.rs @@ -0,0 +1,289 @@ +//! 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 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 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 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 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 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 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 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 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 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 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 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 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 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)) + } +}