From 04865f63e4814e43bc4cc5c7926df234a88b5fb2 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 14 Feb 2022 16:14:11 -0800 Subject: [PATCH 1/4] cargo fmt --- proj-sys/build.rs | 2 +- src/proj.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/proj-sys/build.rs b/proj-sys/build.rs index d8ac4df1..076f2e47 100644 --- a/proj-sys/build.rs +++ b/proj-sys/build.rs @@ -99,7 +99,7 @@ fn build_from_source() -> Result> config.define("BUILD_PROJSYNC", "OFF"); config.define("ENABLE_CURL", "OFF"); - let enable_tiff = cfg!(feature="network"); + let enable_tiff = cfg!(feature = "network"); if enable_tiff { eprintln!("enabling tiff support"); config.define("ENABLE_TIFF", "ON"); diff --git a/src/proj.rs b/src/proj.rs index 8705ec10..fe529292 100644 --- a/src/proj.rs +++ b/src/proj.rs @@ -11,8 +11,7 @@ use proj_sys::{ PJ_DIRECTION_PJ_INV, PJ_INFO, PJ_LPZT, PJ_XYZT, }; use std::{ - convert, - ffi, + convert, ffi, fmt::{self, Debug}, str, }; From 8fab07ee0833d4da1a64d2678dda6bf280620147 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 14 Feb 2022 16:15:03 -0800 Subject: [PATCH 2/4] use approx for geo-types tests, coalesce versions --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 71f7770c..60b5d5bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,8 @@ pkg_config = [ "proj-sys/pkg_config" ] network = ["reqwest", "proj-sys/network"] [dev-dependencies] -approx = "0.3" +approx = "0.4" +geo-types = { version = "0.7", features = ["approx"] } [package.metadata.docs.rs] features = [ "proj-sys/nobuild", "network", "geo-types" ] From 24158a05e036a1f4f23415bd265626eab8d26412 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 14 Feb 2022 16:15:37 -0800 Subject: [PATCH 3/4] Transform trait in proj and imp for geo-types --- src/geo_types.rs | 342 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/transform.rs | 136 +++++++++++++++++++ 3 files changed, 480 insertions(+) create mode 100644 src/transform.rs diff --git a/src/geo_types.rs b/src/geo_types.rs index 92570437..60f64756 100644 --- a/src/geo_types.rs +++ b/src/geo_types.rs @@ -1,3 +1,6 @@ +use crate::{Proj, ProjError, Transform}; +use geo_types::Geometry; + ///```rust /// # use approx::assert_relative_eq; /// extern crate proj; @@ -51,3 +54,342 @@ impl crate::Coord for geo_types::Point { Self::new(x, y) } } + +impl Transform for geo_types::Geometry +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = self.clone(); + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + match self { + Geometry::Point(g) => g.transform(proj), + Geometry::Line(g) => g.transform(proj), + Geometry::LineString(g) => g.transform(proj), + Geometry::Polygon(g) => g.transform(proj), + Geometry::MultiPoint(g) => g.transform(proj), + Geometry::MultiLineString(g) => g.transform(proj), + Geometry::MultiPolygon(g) => g.transform(proj), + Geometry::GeometryCollection(g) => g.transform(proj), + Geometry::Rect(g) => g.transform(proj), + Geometry::Triangle(g) => g.transform(proj), + } + } +} + +impl Transform for geo_types::Coordinate +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = *self; + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + *self = proj.convert(*self)?; + Ok(()) + } +} + +impl Transform for geo_types::Point +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = *self; + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + self.0.transform(proj) + } +} + +impl Transform for geo_types::Line +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = *self; + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + self.start.transform(proj)?; + self.end.transform(proj)?; + Ok(()) + } +} + +impl Transform for geo_types::LineString +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = self.clone(); + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + proj.convert_array(&mut self.0)?; + Ok(()) + } +} + +impl Transform for geo_types::Polygon +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = self.clone(); + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + let mut exterior_result = Ok(()); + self.exterior_mut(|exterior| { + exterior_result = exterior.transform(proj); + }); + exterior_result?; + + let mut interiors_result = Ok(()); + self.interiors_mut(|interiors| { + interiors_result = interiors + .iter_mut() + .try_for_each(|interior| interior.transform(proj)) + }); + interiors_result?; + + Ok(()) + } +} + +impl Transform for geo_types::MultiPoint +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = self.clone(); + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + proj.convert_array(&mut self.0)?; + Ok(()) + } +} + +impl Transform for geo_types::MultiLineString +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = self.clone(); + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + for line_string in &mut self.0 { + line_string.transform(proj)?; + } + Ok(()) + } +} + +impl Transform for geo_types::MultiPolygon +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = self.clone(); + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + for polygon in &mut self.0 { + polygon.transform(proj)?; + } + Ok(()) + } +} + +impl Transform for geo_types::GeometryCollection +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = self.clone(); + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + for geometry in &mut self.0 { + geometry.transform(proj)?; + } + Ok(()) + } +} + +impl Transform for geo_types::Rect +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = *self; + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + let a = self.min(); + let b = self.max(); + let new = geo_types::Rect::new(proj.convert(a)?, proj.convert(b)?); + *self = new; + Ok(()) + } +} + +impl Transform for geo_types::Triangle +where + T: crate::proj::CoordinateType, +{ + type Output = Self; + + fn transformed(&self, proj: &Proj) -> Result { + let mut output = *self; + output.transform(proj)?; + Ok(output) + } + + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError> { + self.0.transform(proj)?; + self.1.transform(proj)?; + self.2.transform(proj)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use geo_types::{point, MultiPoint, Rect}; + + #[test] + fn test_point() { + let mut subject = point!(x: 4760096.421921f64, y: 3744293.729449f64); + subject + .transform_crs_to_crs("EPSG:2230", "EPSG:26946") + .unwrap(); + let expected = point!(x: 1450880.29f64, y: 1141263.01f64); + assert_relative_eq!(subject, expected, epsilon = 0.2); + } + + #[test] + fn test_rect() { + let mut subject = { + let point_a = point!(x: 4760096.421921f64, y: 3744293.729449f64); + let point_b = point!(x: 4760196.421921f64, y: 3744393.729449f64); + Rect::new(point_a, point_b) + }; + + subject + .transform_crs_to_crs("EPSG:2230", "EPSG:26946") + .unwrap(); + let expected = { + let point_a = point!(x: 1450880.2910605022, y: 1141263.0111604782); + let point_b = point!(x: 1450910.771121464, y: 1141293.4912214363); + Rect::new(point_a, point_b) + }; + assert_relative_eq!(subject, expected, epsilon = 0.2); + } + + #[test] + fn test_multi_point() { + let mut subject = { + let point_a = point!(x: 4760096.421921f64, y: 3744293.729449f64); + let point_b = point!(x: 4760196.421921f64, y: 3744393.729449f64); + MultiPoint(vec![point_a, point_b]) + }; + + subject + .transform_crs_to_crs("EPSG:2230", "EPSG:26946") + .unwrap(); + let expected = { + let point_a = point!(x: 1450880.2910605022, y: 1141263.0111604782); + let point_b = point!(x: 1450910.771121464, y: 1141293.4912214363); + MultiPoint(vec![point_a, point_b]) + }; + assert_relative_eq!(subject, expected, epsilon = 0.2); + } + + #[test] + fn test_geometry_collection() { + let mut subject = { + let multi_point = { + let point_a = point!(x: 4760096.421921f64, y: 3744293.729449f64); + let point_b = point!(x: 4760196.421921f64, y: 3744393.729449f64); + MultiPoint(vec![point_a, point_b]) + }; + let rect = { + let point_a = point!(x: 4760096.421921f64, y: 3744293.729449f64); + let point_b = point!(x: 4760196.421921f64, y: 3744393.729449f64); + Rect::new(point_a, point_b) + }; + geo_types::GeometryCollection(vec![Geometry::from(multi_point), Geometry::from(rect)]) + }; + + subject + .transform_crs_to_crs("EPSG:2230", "EPSG:26946") + .unwrap(); + let expected = { + let multi_point = { + let point_a = point!(x: 1450880.2910605022, y: 1141263.0111604782); + let point_b = point!(x: 1450910.771121464, y: 1141293.4912214363); + MultiPoint(vec![point_a, point_b]) + }; + let rect = { + let point_a = point!(x: 1450880.2910605022, y: 1141263.0111604782); + let point_b = point!(x: 1450910.771121464, y: 1141293.4912214363); + Rect::new(point_a, point_b) + }; + geo_types::GeometryCollection(vec![Geometry::from(multi_point), Geometry::from(rect)]) + }; + assert_relative_eq!(subject, expected, epsilon = 0.2); + } +} diff --git a/src/lib.rs b/src/lib.rs index bf1bf9c8..2dcc0bf1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -211,6 +211,8 @@ mod geo_types; extern crate approx; mod proj; +mod transform; +pub use transform::{Transform, TransformError}; pub use crate::proj::Area; pub use crate::proj::Coord; diff --git a/src/transform.rs b/src/transform.rs new file mode 100644 index 00000000..0a699122 --- /dev/null +++ b/src/transform.rs @@ -0,0 +1,136 @@ +use std::{error::Error, fmt}; + +use crate::{Proj, ProjError}; + +/// Transform a geometry using PROJ. +pub trait Transform { + type Output; + + /// Transform a Geometry by mutating it in place. + /// + /// # Examples + /// + /// Transform a geometry using a PROJ string definition: + /// + /// ``` + /// use geo_types; + /// use proj::{Proj, Transform}; + /// # use approx::assert_relative_eq; + /// + /// let mut point = geo_types::point!(x: -36.508f32, y: -54.2815f32); + /// let proj = Proj::new("+proj=axisswap +order=2,1,3,4").expect("invalid proj string"); + /// point.transform(&proj); + /// + /// assert_relative_eq!( + /// point, + /// geo_types::point!(x: -54.2815f32, y: -36.508f32) + /// ); + /// ``` + fn transform(&mut self, proj: &Proj) -> Result<(), ProjError>; + + /// Immutable flavor of [`Transform::transform`], which allocates a new geometry. + /// + /// # Examples + /// + /// Transform a geometry using a PROJ string definition: + /// + /// ``` + /// use geo_types; + /// use proj::{Proj, Transform}; + /// # use approx::assert_relative_eq; + /// + /// let point = geo_types::point!(x: -36.508f32, y: -54.2815f32); + /// let proj = Proj::new("+proj=axisswap +order=2,1,3,4").expect("invalid proj string"); + /// + /// assert_relative_eq!( + /// point.transformed(&proj).unwrap(), + /// geo_types::point!(x: -54.2815f32, y: -36.508f32) + /// ); + /// + /// // original `point` is untouched + /// assert_relative_eq!( + /// point, + /// geo_types::point!(x: -36.508f32, y: -54.2815f32) + /// ); + /// ``` + fn transformed(&self, proj: &Proj) -> Result; + + /// Transform a geometry from one CRS to another CRS by modifying it in place. + /// + /// # Examples + /// + /// ``` + /// # use approx::assert_relative_eq; + /// use proj::Transform; + /// use geo_types::{point, Point}; + /// + /// let mut point: Point = point!(x: -36.508f32, y: -54.2815f32); + /// point.transform_crs_to_crs("EPSG:4326", "EPSG:3857").unwrap(); + /// + /// assert_relative_eq!(point, point!(x: -4064052.0f32, y: -7223650.5f32)); + /// ``` + /// + fn transform_crs_to_crs( + &mut self, + source_crs: &str, + target_crs: &str, + ) -> Result<(), TransformError> { + let proj = Proj::new_known_crs(source_crs, target_crs, None)?; + Ok(self.transform(&proj)?) + } + + /// Immutable flavor of [`Transform::transform_crs_to_crs`], which allocates a new geometry. + /// + /// # Examples + /// + /// ``` + /// # use approx::assert_relative_eq; + /// use proj::Transform; + /// use geo_types::{point, Point}; + /// + /// let mut point: Point = point!(x: -36.508f32, y: -54.2815f32); + /// + /// assert_relative_eq!( + /// point.transformed_crs_to_crs("EPSG:4326", "EPSG:3857").unwrap(), + /// point!(x: -4064052.0f32, y: -7223650.5f32) + /// ); + /// ``` + /// + fn transformed_crs_to_crs( + &self, + source_crs: &str, + target_crs: &str, + ) -> Result { + let proj = Proj::new_known_crs(source_crs, target_crs, None)?; + Ok(self.transformed(&proj)?) + } +} + +#[derive(Debug)] +pub enum TransformError { + ProjCreateError(crate::ProjCreateError), + ProjError(crate::ProjError), +} + +impl From for TransformError { + fn from(e: crate::ProjError) -> Self { + TransformError::ProjError(e) + } +} + +impl From for TransformError { + fn from(e: crate::ProjCreateError) -> Self { + TransformError::ProjCreateError(e) + } +} + +impl fmt::Display for TransformError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TransformError::ProjCreateError(err) => err.fmt(f), + TransformError::ProjError(err) => err.fmt(f), + } + } +} + +impl Error for TransformError {} From 425edfda2f780ae389ad4af66147b2fd4c6ce177 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 15 Feb 2022 13:46:24 -0800 Subject: [PATCH 4/4] fix doc tests when geo-types feature is disabled --- src/transform.rs | 144 ++++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/src/transform.rs b/src/transform.rs index 0a699122..5691f86a 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -8,68 +8,73 @@ pub trait Transform { /// Transform a Geometry by mutating it in place. /// - /// # Examples - /// - /// Transform a geometry using a PROJ string definition: - /// - /// ``` - /// use geo_types; - /// use proj::{Proj, Transform}; - /// # use approx::assert_relative_eq; - /// - /// let mut point = geo_types::point!(x: -36.508f32, y: -54.2815f32); - /// let proj = Proj::new("+proj=axisswap +order=2,1,3,4").expect("invalid proj string"); - /// point.transform(&proj); - /// - /// assert_relative_eq!( - /// point, - /// geo_types::point!(x: -54.2815f32, y: -36.508f32) - /// ); - /// ``` + #[cfg_attr(feature = "geo-types", doc = r##" +# Examples + +Transform a geometry using a PROJ string definition: + +``` +use geo_types; +use proj::{Proj, Transform}; + # use approx::assert_relative_eq; + +let mut point = geo_types::point!(x: -36.508f32, y: -54.2815f32); +let proj = Proj::new("+proj=axisswap +order=2,1,3,4").expect("invalid proj string"); + point.transform(&proj); + +assert_relative_eq!( + point, + geo_types::point!(x: -54.2815f32, y: -36.508f32) +); +``` +"##)] fn transform(&mut self, proj: &Proj) -> Result<(), ProjError>; /// Immutable flavor of [`Transform::transform`], which allocates a new geometry. /// - /// # Examples - /// - /// Transform a geometry using a PROJ string definition: - /// - /// ``` - /// use geo_types; - /// use proj::{Proj, Transform}; - /// # use approx::assert_relative_eq; - /// - /// let point = geo_types::point!(x: -36.508f32, y: -54.2815f32); - /// let proj = Proj::new("+proj=axisswap +order=2,1,3,4").expect("invalid proj string"); - /// - /// assert_relative_eq!( - /// point.transformed(&proj).unwrap(), - /// geo_types::point!(x: -54.2815f32, y: -36.508f32) - /// ); - /// - /// // original `point` is untouched - /// assert_relative_eq!( - /// point, - /// geo_types::point!(x: -36.508f32, y: -54.2815f32) - /// ); - /// ``` + #[cfg_attr(feature = "geo-types", doc = r##" +# Examples + +Transform a geometry using a PROJ string definition: + +``` +use geo_types; +use proj::{Proj, Transform}; +# use approx::assert_relative_eq; + +let point = geo_types::point!(x: -36.508f32, y: -54.2815f32); +let proj = Proj::new("+proj=axisswap +order=2,1,3,4").expect("invalid proj string"); + +assert_relative_eq!( + point.transformed(&proj).unwrap(), + geo_types::point!(x: -54.2815f32, y: -36.508f32) +); + +// original `point` is untouched +assert_relative_eq!( + point, + geo_types::point!(x: -36.508f32, y: -54.2815f32) +); +``` +"##)] fn transformed(&self, proj: &Proj) -> Result; /// Transform a geometry from one CRS to another CRS by modifying it in place. /// - /// # Examples - /// - /// ``` - /// # use approx::assert_relative_eq; - /// use proj::Transform; - /// use geo_types::{point, Point}; - /// - /// let mut point: Point = point!(x: -36.508f32, y: -54.2815f32); - /// point.transform_crs_to_crs("EPSG:4326", "EPSG:3857").unwrap(); - /// - /// assert_relative_eq!(point, point!(x: -4064052.0f32, y: -7223650.5f32)); - /// ``` - /// + #[cfg_attr(feature = "geo-types", doc = r##" +# Examples + +``` +# use approx::assert_relative_eq; +use proj::Transform; +use geo_types::{point, Point}; + +let mut point: Point = point!(x: -36.508f32, y: -54.2815f32); +point.transform_crs_to_crs("EPSG:4326", "EPSG:3857").unwrap(); + +assert_relative_eq!(point, point!(x: -4064052.0f32, y: -7223650.5f32)); +``` +"##)] fn transform_crs_to_crs( &mut self, source_crs: &str, @@ -81,21 +86,22 @@ pub trait Transform { /// Immutable flavor of [`Transform::transform_crs_to_crs`], which allocates a new geometry. /// - /// # Examples - /// - /// ``` - /// # use approx::assert_relative_eq; - /// use proj::Transform; - /// use geo_types::{point, Point}; - /// - /// let mut point: Point = point!(x: -36.508f32, y: -54.2815f32); - /// - /// assert_relative_eq!( - /// point.transformed_crs_to_crs("EPSG:4326", "EPSG:3857").unwrap(), - /// point!(x: -4064052.0f32, y: -7223650.5f32) - /// ); - /// ``` - /// + #[cfg_attr(feature = "geo-types", doc = r##" +# Examples + +``` +# use approx::assert_relative_eq; +use proj::Transform; +use geo_types::{point, Point}; + +let mut point: Point = point!(x: -36.508f32, y: -54.2815f32); + +assert_relative_eq!( + point.transformed_crs_to_crs("EPSG:4326", "EPSG:3857").unwrap(), + point!(x: -4064052.0f32, y: -7223650.5f32) +); +``` +"##)] fn transformed_crs_to_crs( &self, source_crs: &str,