diff --git a/python/core/src/algorithm/geo/area.rs b/python/core/src/algorithm/geo/area.rs index 9c202b2d0..bbc76947e 100644 --- a/python/core/src/algorithm/geo/area.rs +++ b/python/core/src/algorithm/geo/area.rs @@ -1,26 +1,23 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use crate::ffi::from_python::import_arrow_c_array; use geoarrow::algorithm::geo::Area; use geoarrow::array::from_arrow_array; use pyo3::prelude::*; #[pyfunction] -pub fn area(ob: &PyAny) -> PyResult { +pub fn area(ob: &PyAny) -> PyGeoArrowResult { let (array, field) = import_arrow_c_array(ob)?; - // TODO: need to improve crate's error handling - let array = from_arrow_array(&array, &field).unwrap(); - // TODO: fix error handling - Ok(array.as_ref().unsigned_area().unwrap().into()) + let array = from_arrow_array(&array, &field)?; + Ok(array.as_ref().unsigned_area()?.into()) } #[pyfunction] -pub fn signed_area(ob: &PyAny) -> PyResult { +pub fn signed_area(ob: &PyAny) -> PyGeoArrowResult { let (array, field) = import_arrow_c_array(ob)?; - // TODO: need to improve crate's error handling - let array = from_arrow_array(&array, &field).unwrap(); - // TODO: fix error handling - Ok(array.as_ref().signed_area().unwrap().into()) + let array = from_arrow_array(&array, &field)?; + Ok(array.as_ref().signed_area()?.into()) } macro_rules! impl_area { @@ -56,15 +53,15 @@ macro_rules! impl_chunked { #[pymethods] impl $struct_name { /// Unsigned planar area of a geometry. - pub fn area(&self) -> ChunkedFloat64Array { + pub fn area(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::Area; - Area::unsigned_area(&self.0).unwrap().into() + Ok(Area::unsigned_area(&self.0)?.into()) } /// Signed planar area of a geometry. - pub fn signed_area(&self) -> ChunkedFloat64Array { + pub fn signed_area(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::Area; - Area::signed_area(&self.0).unwrap().into() + Ok(Area::signed_area(&self.0)?.into()) } } }; diff --git a/python/core/src/algorithm/geo/bounding_rect.rs b/python/core/src/algorithm/geo/bounding_rect.rs index f57e1d83a..e4e8f709f 100644 --- a/python/core/src/algorithm/geo/bounding_rect.rs +++ b/python/core/src/algorithm/geo/bounding_rect.rs @@ -1,5 +1,6 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use pyo3::prelude::*; macro_rules! impl_bounding_rect { @@ -29,11 +30,9 @@ macro_rules! impl_vector { #[pymethods] impl $struct_name { /// Return the bounding rectangle of a geometry - pub fn bounding_rect(&self) -> PyResult { + pub fn bounding_rect(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::BoundingRect; - Ok(ChunkedRectArray( - BoundingRect::bounding_rect(&self.0).unwrap(), - )) + Ok(ChunkedRectArray(BoundingRect::bounding_rect(&self.0)?)) } } }; diff --git a/python/core/src/algorithm/geo/center.rs b/python/core/src/algorithm/geo/center.rs index 049c3a468..4a19e7dd7 100644 --- a/python/core/src/algorithm/geo/center.rs +++ b/python/core/src/algorithm/geo/center.rs @@ -1,17 +1,16 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use crate::ffi::from_python::import_arrow_c_array; use geoarrow::algorithm::geo::Center; use geoarrow::array::from_arrow_array; use pyo3::prelude::*; #[pyfunction] -pub fn center(ob: &PyAny) -> PyResult { +pub fn center(ob: &PyAny) -> PyGeoArrowResult { let (array, field) = import_arrow_c_array(ob)?; - // TODO: need to improve crate's error handling - let array = from_arrow_array(&array, &field).unwrap(); - // TODO: fix error handling - Ok(array.as_ref().center().unwrap().into()) + let array = from_arrow_array(&array, &field)?; + Ok(array.as_ref().center()?.into()) } macro_rules! impl_center { @@ -47,9 +46,9 @@ macro_rules! impl_chunked { /// /// This first computes the axis-aligned bounding rectangle, then takes the center of /// that box - pub fn center(&self) -> ChunkedPointArray { + pub fn center(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::Center; - ChunkedPointArray(Center::center(&self.0).unwrap()) + Ok(ChunkedPointArray(Center::center(&self.0)?)) } } }; diff --git a/python/core/src/algorithm/geo/centroid.rs b/python/core/src/algorithm/geo/centroid.rs index abeb2f587..52fb53343 100644 --- a/python/core/src/algorithm/geo/centroid.rs +++ b/python/core/src/algorithm/geo/centroid.rs @@ -1,17 +1,16 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use crate::ffi::from_python::import_arrow_c_array; use geoarrow::algorithm::geo::Centroid; use geoarrow::array::from_arrow_array; use pyo3::prelude::*; #[pyfunction] -pub fn centroid(ob: &PyAny) -> PyResult { +pub fn centroid(ob: &PyAny) -> PyGeoArrowResult { let (array, field) = import_arrow_c_array(ob)?; - // TODO: need to improve crate's error handling - let array = from_arrow_array(&array, &field).unwrap(); - // TODO: fix error handling - Ok(array.as_ref().centroid().unwrap().into()) + let array = from_arrow_array(&array, &field)?; + Ok(array.as_ref().centroid()?.into()) } macro_rules! impl_centroid { @@ -55,9 +54,9 @@ macro_rules! impl_chunked { /// /// The geometric centroid of a convex object always lies in the object. /// A non-convex object might have a centroid that _is outside the object itself_. - pub fn centroid(&self) -> ChunkedPointArray { + pub fn centroid(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::Centroid; - ChunkedPointArray(Centroid::centroid(&self.0).unwrap()) + Ok(ChunkedPointArray(Centroid::centroid(&self.0)?)) } } }; diff --git a/python/core/src/algorithm/geo/chamberlain_duquette_area.rs b/python/core/src/algorithm/geo/chamberlain_duquette_area.rs index e204c6f83..adcfea5bf 100644 --- a/python/core/src/algorithm/geo/chamberlain_duquette_area.rs +++ b/python/core/src/algorithm/geo/chamberlain_duquette_area.rs @@ -1,5 +1,6 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use pyo3::prelude::*; macro_rules! impl_alg { @@ -35,19 +36,19 @@ macro_rules! impl_chunked { #[pymethods] impl $struct_name { /// Calculate the unsigned approximate geodesic area of a `Geometry`. - pub fn chamberlain_duquette_unsigned_area(&self) -> ChunkedFloat64Array { + pub fn chamberlain_duquette_unsigned_area( + &self, + ) -> PyGeoArrowResult { use geoarrow::algorithm::geo::ChamberlainDuquetteArea; - ChamberlainDuquetteArea::chamberlain_duquette_unsigned_area(&self.0) - .unwrap() - .into() + Ok(ChamberlainDuquetteArea::chamberlain_duquette_unsigned_area(&self.0)?.into()) } /// Calculate the signed approximate geodesic area of a `Geometry`. - pub fn chamberlain_duquette_signed_area(&self) -> ChunkedFloat64Array { + pub fn chamberlain_duquette_signed_area( + &self, + ) -> PyGeoArrowResult { use geoarrow::algorithm::geo::ChamberlainDuquetteArea; - ChamberlainDuquetteArea::chamberlain_duquette_signed_area(&self.0) - .unwrap() - .into() + Ok(ChamberlainDuquetteArea::chamberlain_duquette_signed_area(&self.0)?.into()) } } }; diff --git a/python/core/src/algorithm/geo/convex_hull.rs b/python/core/src/algorithm/geo/convex_hull.rs index ad76414b8..1383b7cfe 100644 --- a/python/core/src/algorithm/geo/convex_hull.rs +++ b/python/core/src/algorithm/geo/convex_hull.rs @@ -1,17 +1,16 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use crate::ffi::from_python::import_arrow_c_array; use geoarrow::algorithm::geo::ConvexHull; use geoarrow::array::from_arrow_array; use pyo3::prelude::*; #[pyfunction] -pub fn convex_hull(ob: &PyAny) -> PyResult { +pub fn convex_hull(ob: &PyAny) -> PyGeoArrowResult { let (array, field) = import_arrow_c_array(ob)?; - // TODO: need to improve crate's error handling - let array = from_arrow_array(&array, &field).unwrap(); - // TODO: fix error handling - Ok(array.as_ref().convex_hull().unwrap().into()) + let array = from_arrow_array(&array, &field)?; + Ok(array.as_ref().convex_hull()?.into()) } macro_rules! impl_alg { @@ -53,9 +52,9 @@ macro_rules! impl_chunked { /// Dobkin, David P.; Huhdanpaa, Hannu (1 December /// 1996)](https://dx.doi.org/10.1145%2F235815.235821) Original paper here: /// - pub fn convex_hull(&self) -> ChunkedPolygonArray { + pub fn convex_hull(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::ConvexHull; - ChunkedPolygonArray(ConvexHull::convex_hull(&self.0).unwrap()) + Ok(ChunkedPolygonArray(ConvexHull::convex_hull(&self.0)?)) } } }; diff --git a/python/core/src/algorithm/geo/dimensions.rs b/python/core/src/algorithm/geo/dimensions.rs index f01bb27d8..352e2e707 100644 --- a/python/core/src/algorithm/geo/dimensions.rs +++ b/python/core/src/algorithm/geo/dimensions.rs @@ -1,5 +1,6 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use pyo3::prelude::*; macro_rules! impl_alg { @@ -37,9 +38,9 @@ macro_rules! impl_chunked { /// /// Types like `Point`, which have at least one coordinate by construction, can never /// be considered empty. - pub fn is_empty(&self) -> ChunkedBooleanArray { + pub fn is_empty(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::HasDimensions; - HasDimensions::is_empty(&self.0).unwrap().into() + Ok(HasDimensions::is_empty(&self.0)?.into()) } } }; diff --git a/python/core/src/algorithm/geo/euclidean_length.rs b/python/core/src/algorithm/geo/euclidean_length.rs index 16b84980d..498a9badc 100644 --- a/python/core/src/algorithm/geo/euclidean_length.rs +++ b/python/core/src/algorithm/geo/euclidean_length.rs @@ -1,5 +1,6 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use pyo3::prelude::*; macro_rules! impl_euclidean_length { @@ -25,9 +26,9 @@ macro_rules! impl_chunked { #[pymethods] impl $struct_name { /// (Euclidean) Calculation of the length of a Line - pub fn length(&self) -> ChunkedFloat64Array { + pub fn length(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::EuclideanLength; - EuclideanLength::euclidean_length(&self.0).unwrap().into() + Ok(EuclideanLength::euclidean_length(&self.0)?.into()) } } }; diff --git a/python/core/src/algorithm/geo/geodesic_area.rs b/python/core/src/algorithm/geo/geodesic_area.rs index 073bac95a..cdc1e4dbe 100644 --- a/python/core/src/algorithm/geo/geodesic_area.rs +++ b/python/core/src/algorithm/geo/geodesic_area.rs @@ -1,5 +1,6 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use pyo3::prelude::*; macro_rules! impl_geodesic_area { @@ -110,9 +111,9 @@ macro_rules! impl_chunked { /// 2. The polygon is larger than half the planet. In this case, the returned area of the polygon is not correct. If you expect to be dealing with very large polygons, please use the `unsigned` methods. /// /// [Karney (2013)]: https://arxiv.org/pdf/1109.4448.pdf - pub fn geodesic_area_signed(&self) -> ChunkedFloat64Array { + pub fn geodesic_area_signed(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::GeodesicArea; - GeodesicArea::geodesic_area_signed(&self.0).unwrap().into() + Ok(GeodesicArea::geodesic_area_signed(&self.0)?.into()) } /// Determine the area of a geometry on an ellipsoidal model of the earth. Supports very large geometries that cover a significant portion of the earth. @@ -130,11 +131,9 @@ macro_rules! impl_chunked { /// - return value: meter² /// /// [Karney (2013)]: https://arxiv.org/pdf/1109.4448.pdf - pub fn geodesic_area_unsigned(&self) -> ChunkedFloat64Array { + pub fn geodesic_area_unsigned(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::GeodesicArea; - GeodesicArea::geodesic_area_unsigned(&self.0) - .unwrap() - .into() + Ok(GeodesicArea::geodesic_area_unsigned(&self.0)?.into()) } /// Determine the perimeter of a geometry on an ellipsoidal model of the earth. @@ -149,9 +148,9 @@ macro_rules! impl_chunked { /// - return value: meter /// /// [Karney (2013)]: https://arxiv.org/pdf/1109.4448.pdf - pub fn geodesic_perimeter(&self) -> ChunkedFloat64Array { + pub fn geodesic_perimeter(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::GeodesicArea; - GeodesicArea::geodesic_perimeter(&self.0).unwrap().into() + Ok(GeodesicArea::geodesic_perimeter(&self.0)?.into()) } } }; diff --git a/python/core/src/algorithm/geo/vincenty_length.rs b/python/core/src/algorithm/geo/vincenty_length.rs index e68ef4ca8..4b05d49f9 100644 --- a/python/core/src/algorithm/geo/vincenty_length.rs +++ b/python/core/src/algorithm/geo/vincenty_length.rs @@ -1,4 +1,5 @@ use crate::array::*; +use crate::error::PyGeoArrowResult; use pyo3::prelude::*; macro_rules! impl_vincenty_length { @@ -8,10 +9,9 @@ macro_rules! impl_vincenty_length { /// Determine the length of a geometry using [Vincenty’s formulae]. /// /// [Vincenty’s formulae]: https://en.wikipedia.org/wiki/Vincenty%27s_formulae - pub fn vincenty_length(&self) -> PyResult { + pub fn vincenty_length(&self) -> PyGeoArrowResult { use geoarrow::algorithm::geo::VincentyLength; - let result = VincentyLength::vincenty_length(&self.0).unwrap(); - Ok(result.into()) + Ok(VincentyLength::vincenty_length(&self.0)?.into()) } } }; diff --git a/python/core/src/algorithm/native/concatenate.rs b/python/core/src/algorithm/native/concatenate.rs index 25fa24670..356f8a0be 100644 --- a/python/core/src/algorithm/native/concatenate.rs +++ b/python/core/src/algorithm/native/concatenate.rs @@ -1,5 +1,6 @@ use crate::array::*; use crate::chunked_array::*; +use crate::error::PyGeoArrowResult; use geoarrow::algorithm::native::Concatenate; use pyo3::prelude::*; @@ -8,8 +9,8 @@ macro_rules! impl_len { #[pymethods] impl $struct_name { /// Concatenate a chunked array into a contiguous array. - pub fn concatenate(&self) -> $return_type { - self.0.concatenate().unwrap().into() + pub fn concatenate(&self) -> PyGeoArrowResult<$return_type> { + Ok(self.0.concatenate()?.into()) } } }; diff --git a/python/core/src/array/mod.rs b/python/core/src/array/mod.rs index 1405fc217..7a929fc9e 100644 --- a/python/core/src/array/mod.rs +++ b/python/core/src/array/mod.rs @@ -1,5 +1,6 @@ pub mod primitive; +use crate::error::PyGeoArrowResult; pub use primitive::{ BooleanArray, Float16Array, Float32Array, Float64Array, Int16Array, Int32Array, Int64Array, Int8Array, LargeStringArray, StringArray, UInt16Array, UInt32Array, UInt64Array, UInt8Array, @@ -74,27 +75,27 @@ impl_array! { #[pymethods] impl WKBArray { - fn to_point_array(&self) -> Result { - Ok(PointArray(self.0.clone().try_into().unwrap())) + fn to_point_array(&self) -> PyGeoArrowResult { + Ok(PointArray(self.0.clone().try_into()?)) } - fn to_line_string_array(&self) -> Result { - Ok(LineStringArray(self.0.clone().try_into().unwrap())) + fn to_line_string_array(&self) -> PyGeoArrowResult { + Ok(LineStringArray(self.0.clone().try_into()?)) } - fn to_polygon_array(&self) -> Result { - Ok(PolygonArray(self.0.clone().try_into().unwrap())) + fn to_polygon_array(&self) -> PyGeoArrowResult { + Ok(PolygonArray(self.0.clone().try_into()?)) } - fn to_multi_point_array(&self) -> Result { - Ok(MultiPointArray(self.0.clone().try_into().unwrap())) + fn to_multi_point_array(&self) -> PyGeoArrowResult { + Ok(MultiPointArray(self.0.clone().try_into()?)) } - fn to_multi_line_string_array(&self) -> Result { - Ok(MultiLineStringArray(self.0.clone().try_into().unwrap())) + fn to_multi_line_string_array(&self) -> PyGeoArrowResult { + Ok(MultiLineStringArray(self.0.clone().try_into()?)) } - fn to_multi_polygon_array(&self) -> Result { - Ok(MultiPolygonArray(self.0.clone().try_into().unwrap())) + fn to_multi_polygon_array(&self) -> PyGeoArrowResult { + Ok(MultiPolygonArray(self.0.clone().try_into()?)) } } diff --git a/python/core/src/error.rs b/python/core/src/error.rs new file mode 100644 index 000000000..590bb53dd --- /dev/null +++ b/python/core/src/error.rs @@ -0,0 +1,48 @@ +use pyo3::exceptions::{PyException, PyTypeError, PyValueError}; +use pyo3::prelude::*; + +pub enum PyGeoArrowError { + GeoArrowError(geoarrow::error::GeoArrowError), + PyErr(PyErr), +} + +impl From for PyErr { + fn from(error: PyGeoArrowError) -> Self { + match error { + PyGeoArrowError::GeoArrowError(err) => PyException::new_err(err.to_string()), + PyGeoArrowError::PyErr(err) => err, + } + } +} + +impl From for PyGeoArrowError { + fn from(other: geoarrow::error::GeoArrowError) -> Self { + Self::GeoArrowError(other) + } +} + +impl From for PyGeoArrowError { + fn from(other: PyTypeError) -> Self { + Self::PyErr((&other).into()) + } +} + +impl From for PyGeoArrowError { + fn from(other: PyValueError) -> Self { + Self::PyErr((&other).into()) + } +} + +impl From for PyGeoArrowError { + fn from(other: PyErr) -> Self { + Self::PyErr(other) + } +} + +impl From for PyGeoArrowError { + fn from(value: arrow::error::ArrowError) -> Self { + PyGeoArrowError::GeoArrowError(value.into()) + } +} + +pub type PyGeoArrowResult = Result; diff --git a/python/core/src/ffi/from_python.rs b/python/core/src/ffi/from_python.rs index 7c082d738..ab97dbe1b 100644 --- a/python/core/src/ffi/from_python.rs +++ b/python/core/src/ffi/from_python.rs @@ -1,6 +1,5 @@ use crate::array::*; use arrow::datatypes::Field; -use arrow::error::ArrowError; use arrow::ffi::{FFI_ArrowArray, FFI_ArrowSchema}; use arrow_array::{make_array, ArrayRef}; use pyo3::exceptions::{PyTypeError, PyValueError}; @@ -9,26 +8,43 @@ use pyo3::types::{PyCapsule, PyTuple, PyType}; use pyo3::{PyAny, PyResult}; macro_rules! impl_from_py_object { - ($struct_name:ident) => { + ($struct_name:ident, $geoarrow_arr:ty) => { impl<'a> FromPyObject<'a> for $struct_name { fn extract(ob: &'a PyAny) -> PyResult { let (array, _field) = import_arrow_c_array(ob)?; - Ok(Self(array.as_ref().try_into().unwrap())) + let geo_array = <$geoarrow_arr>::try_from(array.as_ref()) + .map_err(|err| PyTypeError::new_err(err.to_string()))?; + Ok(geo_array.into()) } } }; } -impl_from_py_object!(WKBArray); -impl_from_py_object!(PointArray); -impl_from_py_object!(LineStringArray); -impl_from_py_object!(PolygonArray); -impl_from_py_object!(MultiPointArray); -impl_from_py_object!(MultiLineStringArray); -impl_from_py_object!(MultiPolygonArray); -impl_from_py_object!(MixedGeometryArray); +// impl<'a> FromPyObject<'a> for PointArray { +// fn extract(ob: &'a PyAny) -> PyResult { +// let (array, _field) = import_arrow_c_array(ob)?; +// let geo_array = geoarrow::array::PointArray::try_from(array.as_ref()) +// .map_err(|err| PyTypeError::new_err(err.to_string()))?; +// Ok(geo_array.into()) +// } +// } + +impl_from_py_object!(WKBArray, geoarrow::array::WKBArray); +impl_from_py_object!(PointArray, geoarrow::array::PointArray); +impl_from_py_object!(LineStringArray, geoarrow::array::LineStringArray); +impl_from_py_object!(PolygonArray, geoarrow::array::PolygonArray); +impl_from_py_object!(MultiPointArray, geoarrow::array::MultiPointArray); +impl_from_py_object!( + MultiLineStringArray, + geoarrow::array::MultiLineStringArray +); +impl_from_py_object!(MultiPolygonArray, geoarrow::array::MultiPolygonArray); +impl_from_py_object!(MixedGeometryArray, geoarrow::array::MixedGeometryArray); // impl_from_py_object!(RectArray); -impl_from_py_object!(GeometryCollectionArray); +impl_from_py_object!( + GeometryCollectionArray, + geoarrow::array::GeometryCollectionArray +); macro_rules! impl_from_arrow { ($struct_name:ident) => { @@ -53,26 +69,22 @@ impl_from_arrow!(MixedGeometryArray); // impl_from_arrow!(RectArray); impl_from_arrow!(GeometryCollectionArray); -fn to_py_err(err: ArrowError) -> PyErr { - PyValueError::new_err(err.to_string()) -} - fn validate_pycapsule(capsule: &PyCapsule, expected_name: &str) -> PyResult<()> { let capsule_name = capsule.name()?; - if capsule_name.is_none() { + if let Some(capsule_name) = capsule_name { + let capsule_name = capsule_name.to_str()?; + if capsule_name != expected_name { + return Err(PyValueError::new_err(format!( + "Expected name '{}' in PyCapsule, instead got '{}'", + expected_name, capsule_name + ))); + } + } else { return Err(PyValueError::new_err( "Expected schema PyCapsule to have name set.", )); } - let capsule_name = capsule_name.unwrap().to_str()?; - if capsule_name != expected_name { - return Err(PyValueError::new_err(format!( - "Expected name '{}' in PyCapsule, instead got '{}'", - expected_name, capsule_name - ))); - } - Ok(()) } @@ -100,7 +112,8 @@ pub(crate) fn import_arrow_c_array(ob: &PyAny) -> PyResult<(ArrayRef, Field)> { let schema_ptr = unsafe { schema_capsule.reference::() }; let array = unsafe { FFI_ArrowArray::from_raw(array_capsule.pointer() as _) }; - let array_data = unsafe { arrow::ffi::from_ffi(array, schema_ptr) }.map_err(to_py_err)?; - let field = Field::try_from(schema_ptr).map_err(to_py_err)?; + let array_data = unsafe { arrow::ffi::from_ffi(array, schema_ptr) } + .map_err(|err| PyTypeError::new_err(err.to_string()))?; + let field = Field::try_from(schema_ptr).map_err(|err| PyTypeError::new_err(err.to_string()))?; Ok((make_array(array_data), field)) } diff --git a/python/core/src/ffi/to_python.rs b/python/core/src/ffi/to_python.rs index 6cf3b07de..8e5809f3c 100644 --- a/python/core/src/ffi/to_python.rs +++ b/python/core/src/ffi/to_python.rs @@ -1,12 +1,14 @@ use crate::array::*; +use crate::error::PyGeoArrowResult; use arrow::array::Array; use arrow::ffi::{FFI_ArrowArray, FFI_ArrowSchema}; +use geoarrow::array::AsGeometryArray; use geoarrow::datatypes::GeoDataType; +use geoarrow::error::GeoArrowError; use geoarrow::GeometryArrayTrait; -use pyo3::exceptions::PyTypeError; + use pyo3::prelude::*; use pyo3::types::{PyCapsule, PyTuple}; -use pyo3::PyResult; use std::ffi::CString; use std::sync::Arc; @@ -16,9 +18,12 @@ macro_rules! impl_arrow_c_array_geometry_array { #[pymethods] impl $struct_name { /// An implementation of the Arrow PyCapsule Interface - fn __arrow_c_array__(&self, _requested_schema: Option) -> PyResult { + fn __arrow_c_array__( + &self, + _requested_schema: Option, + ) -> PyGeoArrowResult { let field = self.0.extension_field(); - let ffi_schema = FFI_ArrowSchema::try_from(&*field).unwrap(); + let ffi_schema = FFI_ArrowSchema::try_from(&*field)?; let ffi_array = FFI_ArrowArray::new(&self.0.clone().into_array_ref().to_data()); let schema_capsule_name = CString::new("arrow_schema").unwrap(); @@ -35,7 +40,27 @@ macro_rules! impl_arrow_c_array_geometry_array { }; } -impl_arrow_c_array_geometry_array!(PointArray); +#[pymethods] +impl PointArray { + /// An implementation of the Arrow PyCapsule Interface + fn __arrow_c_array__(&self, _requested_schema: Option) -> PyGeoArrowResult { + let field = self.0.extension_field(); + let ffi_schema = FFI_ArrowSchema::try_from(&*field)?; + let ffi_array = FFI_ArrowArray::new(&self.0.clone().into_array_ref().to_data()); + + let schema_capsule_name = CString::new("arrow_schema").unwrap(); + let array_capsule_name = CString::new("arrow_array").unwrap(); + + Python::with_gil(|py| { + let schema_capsule = PyCapsule::new(py, ffi_schema, Some(schema_capsule_name))?; + let array_capsule = PyCapsule::new(py, ffi_array, Some(array_capsule_name))?; + let tuple = PyTuple::new(py, vec![schema_capsule, array_capsule]); + Ok(tuple.to_object(py)) + }) + } +} + +// impl_arrow_c_array_geometry_array!(PointArray); impl_arrow_c_array_geometry_array!(LineStringArray); impl_arrow_c_array_geometry_array!(PolygonArray); impl_arrow_c_array_geometry_array!(MultiPointArray); @@ -49,76 +74,32 @@ impl_arrow_c_array_geometry_array!(RectArray); pub fn geometry_array_to_pyobject( py: Python, arr: Arc, -) -> PyResult { +) -> PyGeoArrowResult { let py_obj = match arr.data_type() { - GeoDataType::Point(_) => PointArray( - arr.as_any() - .downcast_ref::() - .unwrap() - .clone(), - ) - .into_py(py), - GeoDataType::LineString(_) => LineStringArray( - arr.as_any() - .downcast_ref::>() - .unwrap() - .clone(), - ) - .into_py(py), - GeoDataType::Polygon(_) => PolygonArray( - arr.as_any() - .downcast_ref::>() - .unwrap() - .clone(), - ) - .into_py(py), - GeoDataType::MultiPoint(_) => MultiPointArray( - arr.as_any() - .downcast_ref::>() - .unwrap() - .clone(), - ) - .into_py(py), - GeoDataType::MultiLineString(_) => MultiLineStringArray( - arr.as_any() - .downcast_ref::>() - .unwrap() - .clone(), - ) - .into_py(py), - GeoDataType::MultiPolygon(_) => MultiPolygonArray( - arr.as_any() - .downcast_ref::>() - .unwrap() - .clone(), - ) - .into_py(py), - GeoDataType::Mixed(_) => MixedGeometryArray( - arr.as_any() - .downcast_ref::>() - .unwrap() - .clone(), - ) - .into_py(py), - GeoDataType::GeometryCollection(_) => GeometryCollectionArray( - arr.as_any() - .downcast_ref::>() - .unwrap() - .clone(), - ) - .into_py(py), - GeoDataType::WKB => WKBArray( - arr.as_any() - .downcast_ref::>() - .unwrap() - .clone(), - ) - .into_py(py), + GeoDataType::Point(_) => PointArray(arr.as_ref().as_point().clone()).into_py(py), + GeoDataType::LineString(_) => { + LineStringArray(arr.as_ref().as_line_string().clone()).into_py(py) + } + GeoDataType::Polygon(_) => PolygonArray(arr.as_ref().as_polygon().clone()).into_py(py), + GeoDataType::MultiPoint(_) => { + MultiPointArray(arr.as_ref().as_multi_point().clone()).into_py(py) + } + GeoDataType::MultiLineString(_) => { + MultiLineStringArray(arr.as_ref().as_multi_line_string().clone()).into_py(py) + } + GeoDataType::MultiPolygon(_) => { + MultiPolygonArray(arr.as_ref().as_multi_polygon().clone()).into_py(py) + } + GeoDataType::Mixed(_) => MixedGeometryArray(arr.as_ref().as_mixed().clone()).into_py(py), + GeoDataType::GeometryCollection(_) => { + GeometryCollectionArray(arr.as_ref().as_geometry_collection().clone()).into_py(py) + } + GeoDataType::WKB => WKBArray(arr.as_ref().as_wkb().clone()).into_py(py), other => { - return Err(PyTypeError::new_err(format!( - "Unexpected parsed geometry array type {:?}", - other - ))) + return Err(GeoArrowError::IncorrectType( + format!("Unexpected array type {:?}", other).into(), + ) + .into()) } }; @@ -130,8 +111,11 @@ macro_rules! impl_arrow_c_array_primitive { #[pymethods] impl $struct_name { /// An implementation of the Arrow PyCapsule Interface - fn __arrow_c_array__(&self, _requested_schema: Option) -> PyResult { - let ffi_schema = FFI_ArrowSchema::try_from(self.0.data_type()).unwrap(); + fn __arrow_c_array__( + &self, + _requested_schema: Option, + ) -> PyGeoArrowResult { + let ffi_schema = FFI_ArrowSchema::try_from(self.0.data_type())?; let ffi_array = FFI_ArrowArray::new(&self.0.to_data()); let schema_capsule_name = CString::new("arrow_schema").unwrap(); diff --git a/python/core/src/io/csv.rs b/python/core/src/io/csv.rs index af8269e36..2ce5bb694 100644 --- a/python/core/src/io/csv.rs +++ b/python/core/src/io/csv.rs @@ -1,14 +1,16 @@ use std::fs::File; use std::io::BufReader; +use crate::error::PyGeoArrowResult; use crate::table::GeoTable; use geoarrow::io::csv::read_csv as _read_csv; +use pyo3::exceptions::PyFileNotFoundError; use pyo3::prelude::*; #[pyfunction] -pub fn read_csv(path: String, geometry_column_name: &str) -> GeoTable { - let f = File::open(path).unwrap(); +pub fn read_csv(path: String, geometry_column_name: &str) -> PyGeoArrowResult { + let f = File::open(path).map_err(|err| PyFileNotFoundError::new_err(err.to_string()))?; let mut reader = BufReader::new(f); - let table = _read_csv(&mut reader, geometry_column_name).unwrap(); - GeoTable(table) + let table = _read_csv(&mut reader, geometry_column_name)?; + Ok(GeoTable(table)) } diff --git a/python/core/src/io/ewkb.rs b/python/core/src/io/ewkb.rs index 76f172a3f..130b9ba4f 100644 --- a/python/core/src/io/ewkb.rs +++ b/python/core/src/io/ewkb.rs @@ -9,28 +9,23 @@ use pyo3::prelude::*; use pyo3::types::PyType; use crate::array::*; +use crate::error::PyGeoArrowResult; use crate::ffi::from_python::import_arrow_c_array; use crate::ffi::to_python::geometry_array_to_pyobject; /// Convert an Arrow BinaryArray from EWKB to its GeoArrow-native counterpart. #[pyfunction] -pub fn from_ewkb(ob: &PyAny) -> PyResult { +pub fn from_ewkb(ob: &PyAny) -> PyGeoArrowResult { let (array, field) = import_arrow_c_array(ob)?; - // TODO: need to improve crate's error handling - let array = from_arrow_array(&array, &field).unwrap(); + let array = from_arrow_array(&array, &field)?; let ref_array = array.as_ref(); let geo_array: Arc = match array.data_type() { - GeoDataType::WKB => { - FromEWKB::from_ewkb(ref_array.as_wkb(), CoordType::Interleaved).unwrap() - } + GeoDataType::WKB => FromEWKB::from_ewkb(ref_array.as_wkb(), CoordType::Interleaved)?, GeoDataType::LargeWKB => { - FromEWKB::from_ewkb(ref_array.as_large_wkb(), CoordType::Interleaved).unwrap() + FromEWKB::from_ewkb(ref_array.as_large_wkb(), CoordType::Interleaved)? } other => { - return Err(PyTypeError::new_err(format!( - "Unexpected array type {:?}", - other - ))) + return Err(PyTypeError::new_err(format!("Unexpected array type {:?}", other)).into()) } }; Python::with_gil(|py| geometry_array_to_pyobject(py, geo_array)) @@ -42,27 +37,26 @@ macro_rules! impl_from_ewkb { impl $py_array { /// Parse from EWKB #[classmethod] - pub fn from_ewkb(_cls: &PyType, ob: &PyAny) -> PyResult<$py_array> { + pub fn from_ewkb(_cls: &PyType, ob: &PyAny) -> PyGeoArrowResult<$py_array> { let (array, field) = import_arrow_c_array(ob)?; - let array = from_arrow_array(&array, &field).unwrap(); + let array = from_arrow_array(&array, &field)?; let ref_array = array.as_ref(); match array.data_type() { GeoDataType::WKB => Ok(<$geoarrow_array>::from_ewkb( ref_array.as_wkb(), CoordType::Interleaved, - ) - .unwrap() + )? .into()), GeoDataType::LargeWKB => Ok(<$geoarrow_array>::from_ewkb( ref_array.as_large_wkb(), CoordType::Interleaved, - ) - .unwrap() + )? .into()), other => Err(PyTypeError::new_err(format!( "Unexpected array type {:?}", other - ))), + )) + .into()), } } } diff --git a/python/core/src/io/flatgeobuf.rs b/python/core/src/io/flatgeobuf.rs index f92f252ca..5c0455fbe 100644 --- a/python/core/src/io/flatgeobuf.rs +++ b/python/core/src/io/flatgeobuf.rs @@ -1,14 +1,17 @@ use std::fs::File; use std::io::BufReader; +use crate::error::PyGeoArrowResult; use crate::table::GeoTable; use geoarrow::io::flatgeobuf::read_flatgeobuf as _read_flatgeobuf; +use pyo3::exceptions::PyFileNotFoundError; use pyo3::prelude::*; +/// Read FlatGeobuf from a path on disk into a GeoTable #[pyfunction] -pub fn read_flatgeobuf(path: String) -> GeoTable { - let f = File::open(path).unwrap(); +pub fn read_flatgeobuf(path: String) -> PyGeoArrowResult { + let f = File::open(path).map_err(|err| PyFileNotFoundError::new_err(err.to_string()))?; let mut reader = BufReader::new(f); - let table = _read_flatgeobuf(&mut reader).unwrap(); - GeoTable(table) + let table = _read_flatgeobuf(&mut reader)?; + Ok(GeoTable(table)) } diff --git a/python/core/src/io/geojson.rs b/python/core/src/io/geojson.rs index c48fd786f..89824c259 100644 --- a/python/core/src/io/geojson.rs +++ b/python/core/src/io/geojson.rs @@ -1,14 +1,16 @@ use std::fs::File; use std::io::BufReader; +use crate::error::PyGeoArrowResult; use crate::table::GeoTable; use geoarrow::io::geojson::read_geojson as _read_geojson; +use pyo3::exceptions::PyFileNotFoundError; use pyo3::prelude::*; #[pyfunction] -pub fn read_geojson(path: String) -> GeoTable { - let f = File::open(path).unwrap(); +pub fn read_geojson(path: String) -> PyGeoArrowResult { + let f = File::open(path).map_err(|err| PyFileNotFoundError::new_err(err.to_string()))?; let mut reader = BufReader::new(f); - let table = _read_geojson(&mut reader).unwrap(); - GeoTable(table) + let table = _read_geojson(&mut reader)?; + Ok(GeoTable(table)) } diff --git a/python/core/src/io/wkb.rs b/python/core/src/io/wkb.rs index f980605c0..6bb586fa7 100644 --- a/python/core/src/io/wkb.rs +++ b/python/core/src/io/wkb.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use geoarrow::array::{from_arrow_array, AsGeometryArray, CoordType}; use geoarrow::datatypes::GeoDataType; +use geoarrow::error::GeoArrowError; use geoarrow::io::wkb::{to_wkb as _to_wkb, FromWKB}; use geoarrow::GeometryArrayTrait; use pyo3::exceptions::PyTypeError; @@ -9,26 +10,27 @@ use pyo3::prelude::*; use pyo3::types::PyType; use crate::array::*; +use crate::error::PyGeoArrowResult; use crate::ffi::from_python::import_arrow_c_array; use crate::ffi::to_python::geometry_array_to_pyobject; /// Convert an Arrow BinaryArray from WKB to its GeoArrow-native counterpart. #[pyfunction] -pub fn from_wkb(ob: &PyAny) -> PyResult { +pub fn from_wkb(ob: &PyAny) -> PyGeoArrowResult { let (array, field) = import_arrow_c_array(ob)?; // TODO: need to improve crate's error handling - let array = from_arrow_array(&array, &field).unwrap(); + let array = from_arrow_array(&array, &field)?; let ref_array = array.as_ref(); let geo_array: Arc = match array.data_type() { - GeoDataType::WKB => FromWKB::from_wkb(ref_array.as_wkb(), CoordType::Interleaved).unwrap(), + GeoDataType::WKB => FromWKB::from_wkb(ref_array.as_wkb(), CoordType::Interleaved)?, GeoDataType::LargeWKB => { - FromWKB::from_wkb(ref_array.as_large_wkb(), CoordType::Interleaved).unwrap() + FromWKB::from_wkb(ref_array.as_large_wkb(), CoordType::Interleaved)? } other => { - return Err(PyTypeError::new_err(format!( - "Unexpected array type {:?}", - other - ))) + return Err(GeoArrowError::IncorrectType( + format!("Unexpected array type {:?}", other).into(), + ) + .into()) } }; Python::with_gil(|py| geometry_array_to_pyobject(py, geo_array)) @@ -36,10 +38,9 @@ pub fn from_wkb(ob: &PyAny) -> PyResult { /// Convert a GeoArrow-native geometry array to a WKBArray. #[pyfunction] -pub fn to_wkb(ob: &PyAny) -> PyResult { +pub fn to_wkb(ob: &PyAny) -> PyGeoArrowResult { let (array, field) = import_arrow_c_array(ob)?; - // TODO: need to improve crate's error handling - let array = from_arrow_array(&array, &field).unwrap(); + let array = from_arrow_array(&array, &field)?; Ok(WKBArray(_to_wkb(array.as_ref()))) } @@ -49,27 +50,26 @@ macro_rules! impl_from_wkb { impl $py_array { /// Parse from WKB #[classmethod] - pub fn from_wkb(_cls: &PyType, ob: &PyAny) -> PyResult<$py_array> { + pub fn from_wkb(_cls: &PyType, ob: &PyAny) -> PyGeoArrowResult<$py_array> { let (array, field) = import_arrow_c_array(ob)?; - let array = from_arrow_array(&array, &field).unwrap(); + let array = from_arrow_array(&array, &field)?; let ref_array = array.as_ref(); match array.data_type() { GeoDataType::WKB => Ok(<$geoarrow_array>::from_wkb( ref_array.as_wkb(), CoordType::Interleaved, - ) - .unwrap() + )? .into()), GeoDataType::LargeWKB => Ok(<$geoarrow_array>::from_wkb( ref_array.as_large_wkb(), CoordType::Interleaved, - ) - .unwrap() + )? .into()), other => Err(PyTypeError::new_err(format!( "Unexpected array type {:?}", other - ))), + )) + .into()), } } } diff --git a/python/core/src/io/wkt.rs b/python/core/src/io/wkt.rs index df2ca78ac..e4bd408f0 100644 --- a/python/core/src/io/wkt.rs +++ b/python/core/src/io/wkt.rs @@ -10,25 +10,19 @@ use pyo3::prelude::*; use pyo3::types::PyType; use crate::array::*; +use crate::error::PyGeoArrowResult; use crate::ffi::from_python::import_arrow_c_array; use crate::ffi::to_python::geometry_array_to_pyobject; /// Convert an Arrow StringArray from WKT to its GeoArrow-native counterpart. #[pyfunction] -pub fn from_wkt(ob: &PyAny) -> PyResult { +pub fn from_wkt(ob: &PyAny) -> PyGeoArrowResult { let (array, _field) = import_arrow_c_array(ob)?; let geo_array: Arc = match array.data_type() { - DataType::Utf8 => { - FromWKT::from_wkt(array.as_string::(), CoordType::Interleaved).unwrap() - } - DataType::LargeUtf8 => { - FromWKT::from_wkt(array.as_string::(), CoordType::Interleaved).unwrap() - } + DataType::Utf8 => FromWKT::from_wkt(array.as_string::(), CoordType::Interleaved)?, + DataType::LargeUtf8 => FromWKT::from_wkt(array.as_string::(), CoordType::Interleaved)?, other => { - return Err(PyTypeError::new_err(format!( - "Unexpected array type {:?}", - other - ))) + return Err(PyTypeError::new_err(format!("Unexpected array type {:?}", other)).into()) } }; Python::with_gil(|py| geometry_array_to_pyobject(py, geo_array)) @@ -40,25 +34,24 @@ macro_rules! impl_from_wkt { impl $py_array { /// Parse from WKT #[classmethod] - pub fn from_wkt(_cls: &PyType, ob: &PyAny) -> PyResult<$py_array> { + pub fn from_wkt(_cls: &PyType, ob: &PyAny) -> PyGeoArrowResult<$py_array> { let (array, _field) = import_arrow_c_array(ob)?; match array.data_type() { DataType::Utf8 => Ok(<$geoarrow_array>::from_wkt( array.as_string::(), CoordType::Interleaved, - ) - .unwrap() + )? .into()), DataType::LargeUtf8 => Ok(<$geoarrow_array>::from_wkt( array.as_string::(), CoordType::Interleaved, - ) - .unwrap() + )? .into()), other => Err(PyTypeError::new_err(format!( "Unexpected array type {:?}", other - ))), + )) + .into()), } } } diff --git a/python/core/src/lib.rs b/python/core/src/lib.rs index 51c3dce6e..3e44b8d00 100644 --- a/python/core/src/lib.rs +++ b/python/core/src/lib.rs @@ -3,6 +3,7 @@ pub mod algorithm; pub mod array; pub mod broadcasting; pub mod chunked_array; +pub mod error; pub mod ffi; pub mod io; pub mod table; @@ -84,12 +85,18 @@ fn rust(_py: Python, m: &PyModule) -> PyResult<()> { )?)?; // IO - m.add_function(wrap_pyfunction!(crate::io::wkb::to_wkb, m)?)?; + m.add_function(wrap_pyfunction!(crate::io::ewkb::from_ewkb, m)?)?; m.add_function(wrap_pyfunction!(crate::io::wkb::from_wkb, m)?)?; + m.add_function(wrap_pyfunction!(crate::io::wkb::to_wkb, m)?)?; + m.add_function(wrap_pyfunction!(crate::io::wkt::from_wkt, m)?)?; m.add_function(wrap_pyfunction!(crate::io::csv::read_csv, m)?)?; m.add_function(wrap_pyfunction!(crate::io::flatgeobuf::read_flatgeobuf, m)?)?; m.add_function(wrap_pyfunction!(crate::io::geojson::read_geojson, m)?)?; + // Exceptions + // create_exception!(m, GeoArrowException, pyo3::exceptions::PyException); + // m.add("GeoArrowException", py.get_type::())?; + Ok(()) }