1use std::sync::Arc;
2
3use geoarrow_array::GeoArrowArray;
4use geoarrow_array::array::from_arrow_array;
5use geoarrow_cast::downcast::NativeType;
6use geoarrow_schema::{
7 BoxType, GeometryCollectionType, LineStringType, MultiLineStringType, MultiPointType,
8 MultiPolygonType, PointType, PolygonType,
9};
10use pyo3::exceptions::PyIndexError;
11use pyo3::intern;
12use pyo3::prelude::*;
13use pyo3::types::{PyCapsule, PyTuple, PyType};
14use pyo3_arrow::PyArray;
15use pyo3_arrow::ffi::to_array_pycapsules;
16
17use crate::PyCoordType;
18use crate::data_type::PyGeoType;
19use crate::error::{PyGeoArrowError, PyGeoArrowResult};
20use crate::scalar::PyGeoScalar;
21use crate::utils::text_repr::text_repr;
22
23#[pyclass(module = "geoarrow.rust.core", name = "GeoArray", subclass, frozen)]
28pub struct PyGeoArray(Arc<dyn GeoArrowArray>);
29
30impl PyGeoArray {
31 pub fn new(array: Arc<dyn GeoArrowArray>) -> Self {
33 Self(array)
34 }
35
36 pub fn from_arrow_pycapsule(
38 schema_capsule: &Bound<PyCapsule>,
39 array_capsule: &Bound<PyCapsule>,
40 ) -> PyGeoArrowResult<Self> {
41 PyArray::from_arrow_pycapsule(schema_capsule, array_capsule)?.try_into()
42 }
43
44 pub fn inner(&self) -> &Arc<dyn GeoArrowArray> {
46 &self.0
47 }
48
49 pub fn into_inner(self) -> Arc<dyn GeoArrowArray> {
51 self.0
52 }
53
54 pub fn to_geoarrow<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
58 let geoarrow_mod = py.import(intern!(py, "geoarrow.rust.core"))?;
59 geoarrow_mod.getattr(intern!(py, "GeoArray"))?.call_method1(
60 intern!(py, "from_arrow_pycapsule"),
61 self.__arrow_c_array__(py, None)?,
62 )
63 }
64
65 pub fn into_geoarrow_py(self, py: Python) -> PyResult<Bound<PyAny>> {
69 let geoarrow_mod = py.import(intern!(py, "geoarrow.rust.core"))?;
70 let array_capsules = to_array_pycapsules(
71 py,
72 self.0.data_type().to_field("", true).into(),
73 &self.0.to_array_ref(),
74 None,
75 )?;
76 geoarrow_mod
77 .getattr(intern!(py, "GeoArray"))?
78 .call_method1(intern!(py, "from_arrow_pycapsule"), array_capsules)
79 }
80}
81
82#[pymethods]
83impl PyGeoArray {
84 #[new]
85 fn py_new(data: Self) -> Self {
86 data
87 }
88
89 #[pyo3(signature = (requested_schema=None))]
90 fn __arrow_c_array__<'py>(
91 &'py self,
92 py: Python<'py>,
93 requested_schema: Option<Bound<'py, PyCapsule>>,
94 ) -> PyGeoArrowResult<Bound<'py, PyTuple>> {
95 let field = Arc::new(self.0.data_type().to_field("", true));
96 let array = self.0.to_array_ref();
97 Ok(to_array_pycapsules(py, field, &array, requested_schema)?)
98 }
99
100 fn __eq__(&self, other: &Bound<PyAny>) -> bool {
101 if let Ok(other) = other.extract::<Self>() {
103 self.0.data_type() == other.0.data_type()
104 && self.0.to_array_ref() == other.0.to_array_ref()
105 } else {
106 false
107 }
108 }
109
110 fn __getitem__(&self, i: isize) -> PyGeoArrowResult<PyGeoScalar> {
129 let i = if i < 0 {
131 let i = self.0.len() as isize + i;
132 if i < 0 {
133 return Err(PyIndexError::new_err("Index out of range").into());
134 }
135 i as usize
136 } else {
137 i as usize
138 };
139 if i >= self.0.len() {
140 return Err(PyIndexError::new_err("Index out of range").into());
141 }
142
143 PyGeoScalar::try_new(self.0.slice(i, 1))
144 }
145
146 fn __len__(&self) -> usize {
147 self.0.len()
148 }
149
150 fn __repr__(&self) -> String {
151 format!("GeoArray({})", text_repr(&self.0.data_type()))
152 }
153
154 #[classmethod]
155 fn from_arrow(_cls: &Bound<PyType>, data: Self) -> Self {
156 data
157 }
158
159 #[classmethod]
160 #[pyo3(name = "from_arrow_pycapsule")]
161 fn from_arrow_pycapsule_py(
162 _cls: &Bound<PyType>,
163 schema_capsule: &Bound<PyCapsule>,
164 array_capsule: &Bound<PyCapsule>,
165 ) -> PyGeoArrowResult<Self> {
166 Self::from_arrow_pycapsule(schema_capsule, array_capsule)
167 }
168
169 #[getter]
170 fn null_count(&self) -> usize {
171 self.0.logical_null_count()
172 }
173
174 #[pyo3(signature = (to_type, /))]
175 fn cast(&self, to_type: PyGeoType) -> PyGeoArrowResult<Self> {
176 let casted = geoarrow_cast::cast::cast(self.0.as_ref(), &to_type.into_inner())?;
177 Ok(Self(casted))
178 }
179
180 #[pyo3(
181 signature = (*, coord_type = PyCoordType::Separated),
182 text_signature = "(*, coord_type='separated')"
183 )]
184 fn downcast(&self, coord_type: PyCoordType) -> PyGeoArrowResult<Self> {
185 if let Some((native_type, dim)) =
186 geoarrow_cast::downcast::infer_downcast_type(std::iter::once(self.0.as_ref()))?
187 {
188 let metadata = self.0.data_type().metadata().clone();
189 let coord_type = coord_type.into();
190 let to_type = match native_type {
191 NativeType::Point => PointType::new(dim, metadata)
192 .with_coord_type(coord_type)
193 .into(),
194 NativeType::LineString => LineStringType::new(dim, metadata)
195 .with_coord_type(coord_type)
196 .into(),
197 NativeType::Polygon => PolygonType::new(dim, metadata)
198 .with_coord_type(coord_type)
199 .into(),
200 NativeType::MultiPoint => MultiPointType::new(dim, metadata)
201 .with_coord_type(coord_type)
202 .into(),
203 NativeType::MultiLineString => MultiLineStringType::new(dim, metadata)
204 .with_coord_type(coord_type)
205 .into(),
206 NativeType::MultiPolygon => MultiPolygonType::new(dim, metadata)
207 .with_coord_type(coord_type)
208 .into(),
209 NativeType::GeometryCollection => GeometryCollectionType::new(dim, metadata)
210 .with_coord_type(coord_type)
211 .into(),
212 NativeType::Rect => BoxType::new(dim, metadata).into(),
213 };
214 self.cast(PyGeoType::new(to_type))
215 } else {
216 Ok(Self::new(self.0.clone()))
217 }
218 }
219
220 #[getter]
221 fn r#type(&self) -> PyGeoType {
222 self.0.data_type().into()
223 }
224}
225
226impl From<Arc<dyn GeoArrowArray>> for PyGeoArray {
227 fn from(value: Arc<dyn GeoArrowArray>) -> Self {
228 Self(value)
229 }
230}
231
232impl From<PyGeoArray> for Arc<dyn GeoArrowArray> {
233 fn from(value: PyGeoArray) -> Self {
234 value.0
235 }
236}
237
238impl<'py> FromPyObject<'_, 'py> for PyGeoArray {
239 type Error = PyErr;
240
241 fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult<Self> {
242 Ok(ob.extract::<PyArray>()?.try_into()?)
243 }
244}
245
246impl TryFrom<PyArray> for PyGeoArray {
247 type Error = PyGeoArrowError;
248
249 fn try_from(value: PyArray) -> Result<Self, Self::Error> {
250 let (array, field) = value.into_inner();
251 let geo_arr = from_arrow_array(&array, &field)?;
252 Ok(Self(geo_arr))
253 }
254}