1pub(crate) mod coordinate;
5mod linestring;
6mod multilinestring;
7mod multipoint;
8mod multipolygon;
9mod point;
10mod polygon;
11mod rect;
12mod wkb;
13
14use std::fmt::Display;
15use std::sync::Arc;
16
17use ::wkb::reader::GeometryType;
18use arrow_array::BinaryArray;
19use geo_types::Geometry;
20use geoarrow::array::GenericWkbArray;
21use geoarrow::array::GeoArrowArray;
22use geoarrow::datatypes::CoordType;
23use geoarrow::datatypes::Crs;
24use geoarrow::datatypes::Dimension;
25use geoarrow::datatypes::GeoArrowType;
26use geoarrow::datatypes::LineStringType;
27use geoarrow::datatypes::Metadata;
28use geoarrow::datatypes::MultiLineStringType;
29use geoarrow::datatypes::MultiPointType;
30use geoarrow::datatypes::MultiPolygonType;
31use geoarrow::datatypes::PointType;
32use geoarrow::datatypes::PolygonType;
33use geoarrow::datatypes::WkbType;
34use geoarrow_cast::cast::cast;
35pub use linestring::*;
36pub use multilinestring::*;
37pub use multipoint::*;
38pub use multipolygon::*;
39pub use point::*;
40pub use polygon::*;
41pub use rect::*;
42use vortex_array::ArrayRef;
43use vortex_array::Canonical;
44use vortex_array::ExecutionCtx;
45use vortex_array::IntoArray;
46use vortex_array::arrays::ConstantArray;
47use vortex_array::arrays::ExtensionArray;
48use vortex_array::arrays::StructArray;
49use vortex_array::arrays::extension::ExtensionArrayExt;
50use vortex_array::arrays::listview::ListViewArrayExt;
51use vortex_array::dtype::DType;
52use vortex_array::dtype::extension::ExtDType;
53use vortex_array::dtype::extension::ExtVTable;
54use vortex_array::scalar::Scalar;
55use vortex_arrow::FromArrowArray;
56use vortex_error::VortexResult;
57use vortex_error::vortex_bail;
58use vortex_error::vortex_ensure;
59use vortex_error::vortex_err;
60pub use wkb::*;
61
62pub(crate) fn is_native_geometry(dtype: &DType) -> bool {
64 dtype.as_extension_opt().is_some_and(|ext| {
65 ext.is::<Point>()
66 || ext.is::<LineString>()
67 || ext.is::<MultiPoint>()
68 || ext.is::<Polygon>()
69 || ext.is::<MultiLineString>()
70 || ext.is::<MultiPolygon>()
71 || ext.is::<Rect>()
72 })
73}
74
75pub(crate) fn validate_geometry_operands(dtypes: &[DType]) -> VortexResult<()> {
78 for dtype in dtypes {
79 vortex_ensure!(
80 is_native_geometry(dtype),
81 "geo: operand {dtype} is not a native geometry type"
82 );
83 vortex_ensure!(
84 !dtype.is_nullable(),
85 "geo: nullable operand {dtype} is unsupported"
86 );
87 }
88 Ok(())
89}
90
91pub(crate) fn flatten_coordinates(
94 array: &ArrayRef,
95 ctx: &mut ExecutionCtx,
96) -> VortexResult<StructArray> {
97 if !is_native_geometry(array.dtype()) {
98 vortex_bail!(
99 "geo: operand is not a native geometry extension type, was {}",
100 array.dtype()
101 );
102 }
103 let mut node = array
104 .clone()
105 .execute::<ExtensionArray>(ctx)?
106 .storage_array()
107 .clone();
108 while matches!(node.dtype(), DType::List(..)) {
109 node = node
110 .execute::<Canonical>(ctx)?
111 .into_listview()
112 .elements()
113 .clone();
114 }
115 node.execute::<StructArray>(ctx)
116}
117
118pub(crate) fn geometries(
120 array: &ArrayRef,
121 ctx: &mut ExecutionCtx,
122) -> VortexResult<Vec<Geometry<f64>>> {
123 let Some(ext) = array.dtype().as_extension_opt() else {
124 vortex_bail!(
125 "geo: operand is not a geometry extension type, was {}",
126 array.dtype()
127 );
128 };
129 let storage = array
130 .clone()
131 .execute::<ExtensionArray>(ctx)?
132 .storage_array()
133 .clone();
134 if ext.is::<Point>() {
135 point_geometries(&storage, ctx)
136 } else if ext.is::<LineString>() {
137 linestring_geometries(&storage, ctx)
138 } else if ext.is::<MultiPoint>() {
139 multipoint_geometries(&storage, ctx)
140 } else if ext.is::<Polygon>() {
141 polygon_geometries(&storage, ctx)
142 } else if ext.is::<MultiLineString>() {
143 multilinestring_geometries(&storage, ctx)
144 } else if ext.is::<MultiPolygon>() {
145 multipolygon_geometries(&storage, ctx)
146 } else if ext.is::<Rect>() {
147 rect_geometries(&storage, ctx)
148 } else {
149 vortex_bail!("geo: unsupported geometry extension {}", array.dtype())
150 }
151}
152
153pub(crate) fn single_geometry(
156 scalar: &Scalar,
157 ctx: &mut ExecutionCtx,
158) -> VortexResult<Geometry<f64>> {
159 let array = ConstantArray::new(scalar.clone(), 1).into_array();
160 geometries(&array, ctx)?
161 .pop()
162 .ok_or_else(|| vortex_err!("geo: constant operand decoded to no geometry"))
163}
164
165pub fn native_geometry_scalar_from_wkb(bytes: &[u8]) -> VortexResult<Option<Scalar>> {
168 let metadata = geoarrow_metadata(&GeoMetadata::default());
169 let binary = BinaryArray::from(vec![Some(bytes)]);
170 let wkb = GenericWkbArray::<i32>::try_from((
171 &binary as &dyn arrow_array::Array,
172 WkbType::new(Arc::clone(&metadata)),
173 ))
174 .map_err(|e| vortex_err!("failed to read WKB literal: {e}"))?;
175
176 let to_storage = |target: &GeoArrowType| -> VortexResult<ArrayRef> {
178 let native =
179 cast(&wkb, target).map_err(|e| vortex_err!("failed to cast WKB literal: {e}"))?;
180 ArrayRef::from_arrow(native.to_array_ref().as_ref(), false)
181 };
182
183 let scalar = match Wkb::try_from_bytes(bytes)?.geometry_type() {
184 GeometryType::Point => {
185 let target = GeoArrowType::Point(
186 PointType::new(Dimension::XY, metadata).with_coord_type(CoordType::Separated),
187 );
188 geo_ext_scalar(Point, to_storage(&target)?)?
189 }
190 GeometryType::LineString => {
191 let target = GeoArrowType::LineString(
192 LineStringType::new(Dimension::XY, metadata).with_coord_type(CoordType::Separated),
193 );
194 geo_ext_scalar(LineString, to_storage(&target)?)?
195 }
196 GeometryType::Polygon => {
197 let target = GeoArrowType::Polygon(
198 PolygonType::new(Dimension::XY, metadata).with_coord_type(CoordType::Separated),
199 );
200 geo_ext_scalar(Polygon, to_storage(&target)?)?
201 }
202 GeometryType::MultiPoint => {
203 let target = GeoArrowType::MultiPoint(
204 MultiPointType::new(Dimension::XY, metadata).with_coord_type(CoordType::Separated),
205 );
206 geo_ext_scalar(MultiPoint, to_storage(&target)?)?
207 }
208 GeometryType::MultiLineString => {
209 let target = GeoArrowType::MultiLineString(
210 MultiLineStringType::new(Dimension::XY, metadata)
211 .with_coord_type(CoordType::Separated),
212 );
213 geo_ext_scalar(MultiLineString, to_storage(&target)?)?
214 }
215 GeometryType::MultiPolygon => {
216 let target = GeoArrowType::MultiPolygon(
217 MultiPolygonType::new(Dimension::XY, metadata)
218 .with_coord_type(CoordType::Separated),
219 );
220 geo_ext_scalar(MultiPolygon, to_storage(&target)?)?
221 }
222 _ => return Ok(None),
223 };
224 Ok(Some(scalar))
225}
226
227#[allow(deprecated)]
230fn geo_ext_scalar<V: ExtVTable<Metadata = GeoMetadata>>(
231 vtable: V,
232 storage: ArrayRef,
233) -> VortexResult<Scalar> {
234 let ext = ExtDType::try_with_vtable(vtable, GeoMetadata::default(), storage.dtype().clone())?
235 .erased();
236 ExtensionArray::try_new(ext, storage)?
237 .into_array()
238 .scalar_at(0)
239}
240
241#[derive(Clone, PartialEq, Eq, Hash, prost::Message)]
247pub struct GeoMetadata {
248 #[prost(optional, string, tag = "1")]
249 pub crs: Option<String>,
250}
251
252impl Display for GeoMetadata {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254 match self.crs.as_ref() {
255 Some(crs) => write!(f, "Geometry(crs={crs})"),
256 None => write!(f, "Geometry(unreferenced)"),
257 }
258 }
259}
260
261pub(crate) fn geoarrow_metadata(geo_metadata: &GeoMetadata) -> Arc<Metadata> {
263 Arc::new(Metadata::new(
264 geo_metadata
265 .crs
266 .as_ref()
267 .map(|crs| Crs::from_unknown_crs_type(crs.to_string()))
268 .unwrap_or_default(),
269 None,
270 ))
271}
272
273pub(crate) fn geoarrow_to_wkb(geo_array: &dyn GeoArrowArray) -> VortexResult<ArrayRef> {
276 let wkb_type = GeoArrowType::WkbView(WkbType::new(geoarrow_metadata(&GeoMetadata::default())));
277 let wkb = cast(geo_array, &wkb_type)
278 .map_err(|e| vortex_err!("failed to cast geometry to WKB: {e}"))?;
279 ArrayRef::from_arrow(wkb.to_array_ref().as_ref(), false)
280}
281
282pub(crate) fn geo_metadata_from_arrow(metadata: &Metadata) -> GeoMetadata {
284 let crs = metadata.crs().crs_value().map(|value| {
285 value
289 .as_str()
290 .map(str::to_string)
291 .unwrap_or_else(|| value.to_string())
292 });
293 GeoMetadata { crs }
294}
295
296#[cfg(test)]
297mod tests {
298 use prost::Message;
299 use vortex_array::dtype::DType;
300 use vortex_error::VortexResult;
301 use vortex_error::vortex_err;
302
303 use super::LineString;
304 use super::MultiLineString;
305 use super::MultiPoint;
306 use super::Point;
307 use super::Polygon;
308 use super::native_geometry_scalar_from_wkb;
309 use crate::extension::GeoMetadata;
310
311 #[test]
312 fn test_metadata() {
313 let meta = GeoMetadata {
314 crs: Some("EPSG:4326".to_string()),
315 };
316
317 assert_eq!(meta.to_string(), "Geometry(crs=EPSG:4326)");
318 let bytes = meta.encode_to_vec();
320 let decoded = GeoMetadata::decode(bytes.as_slice()).unwrap();
321 assert_eq!(decoded, meta);
322 }
323
324 #[test]
326 fn decodes_wkb_point_to_native() -> VortexResult<()> {
327 let mut wkb = vec![1u8]; wkb.extend_from_slice(&1u32.to_le_bytes()); wkb.extend_from_slice(&1.0f64.to_le_bytes()); wkb.extend_from_slice(&2.0f64.to_le_bytes()); let scalar = native_geometry_scalar_from_wkb(&wkb)?.expect("a point scalar");
333 let DType::Extension(ext) = scalar.dtype() else {
334 panic!("expected an extension dtype, got {}", scalar.dtype());
335 };
336 assert!(ext.is::<Point>());
337 Ok(())
338 }
339
340 #[test]
342 fn decodes_wkb_polygon_to_native() -> VortexResult<()> {
343 let ring = [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)];
344 let mut wkb = vec![1u8]; wkb.extend_from_slice(&3u32.to_le_bytes()); wkb.extend_from_slice(&1u32.to_le_bytes()); let ring_len = u32::try_from(ring.len()).map_err(|e| vortex_err!("{e}"))?;
348 wkb.extend_from_slice(&ring_len.to_le_bytes());
349 for (x, y) in ring {
350 wkb.extend_from_slice(&f64::to_le_bytes(x));
351 wkb.extend_from_slice(&f64::to_le_bytes(y));
352 }
353
354 let scalar = native_geometry_scalar_from_wkb(&wkb)?.expect("a polygon scalar");
355 let DType::Extension(ext) = scalar.dtype() else {
356 panic!("expected an extension dtype, got {}", scalar.dtype());
357 };
358 assert!(ext.is::<Polygon>());
359 Ok(())
360 }
361
362 #[test]
364 fn decodes_wkb_linestring_to_native() -> VortexResult<()> {
365 let points = [(0.0, 0.0), (1.0, 1.0)];
366 let mut wkb = vec![1u8]; wkb.extend_from_slice(&2u32.to_le_bytes()); let len = u32::try_from(points.len()).map_err(|e| vortex_err!("{e}"))?;
369 wkb.extend_from_slice(&len.to_le_bytes());
370 for (x, y) in points {
371 wkb.extend_from_slice(&f64::to_le_bytes(x));
372 wkb.extend_from_slice(&f64::to_le_bytes(y));
373 }
374
375 let scalar = native_geometry_scalar_from_wkb(&wkb)?.expect("a linestring scalar");
376 let DType::Extension(ext) = scalar.dtype() else {
377 panic!("expected an extension dtype, got {}", scalar.dtype());
378 };
379 assert!(ext.is::<LineString>());
380 Ok(())
381 }
382
383 #[test]
385 fn decodes_wkb_multipoint_to_native() -> VortexResult<()> {
386 let points = [(0.0, 0.0), (1.0, 1.0)];
387 let mut wkb = vec![1u8]; wkb.extend_from_slice(&4u32.to_le_bytes()); let len = u32::try_from(points.len()).map_err(|e| vortex_err!("{e}"))?;
390 wkb.extend_from_slice(&len.to_le_bytes());
391 for (x, y) in points {
392 wkb.push(1u8);
394 wkb.extend_from_slice(&1u32.to_le_bytes());
395 wkb.extend_from_slice(&f64::to_le_bytes(x));
396 wkb.extend_from_slice(&f64::to_le_bytes(y));
397 }
398
399 let scalar = native_geometry_scalar_from_wkb(&wkb)?.expect("a multipoint scalar");
400 let DType::Extension(ext) = scalar.dtype() else {
401 panic!("expected an extension dtype, got {}", scalar.dtype());
402 };
403 assert!(ext.is::<MultiPoint>());
404 Ok(())
405 }
406
407 #[test]
409 fn decodes_wkb_multilinestring_to_native() -> VortexResult<()> {
410 let lines = [[(0.0, 0.0), (1.0, 1.0)], [(2.0, 2.0), (3.0, 3.0)]];
411 let mut wkb = vec![1u8]; wkb.extend_from_slice(&5u32.to_le_bytes()); let num_lines = u32::try_from(lines.len()).map_err(|e| vortex_err!("{e}"))?;
414 wkb.extend_from_slice(&num_lines.to_le_bytes());
415 for line in lines {
416 wkb.push(1u8);
418 wkb.extend_from_slice(&2u32.to_le_bytes());
419 let len = u32::try_from(line.len()).map_err(|e| vortex_err!("{e}"))?;
420 wkb.extend_from_slice(&len.to_le_bytes());
421 for (x, y) in line {
422 wkb.extend_from_slice(&f64::to_le_bytes(x));
423 wkb.extend_from_slice(&f64::to_le_bytes(y));
424 }
425 }
426
427 let scalar = native_geometry_scalar_from_wkb(&wkb)?.expect("a multilinestring scalar");
428 let DType::Extension(ext) = scalar.dtype() else {
429 panic!("expected an extension dtype, got {}", scalar.dtype());
430 };
431 assert!(ext.is::<MultiLineString>());
432 Ok(())
433 }
434}