Skip to content

Commit 2b92889

Browse files
committed
ST_FlipCoords
1 parent 88c5545 commit 2b92889

6 files changed

Lines changed: 491 additions & 0 deletions

File tree

rust/geodatafusion/src/udf/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod geo;
22
pub mod geohash;
33
pub mod native;
4+
pub(crate) mod util;
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
use std::any::Any;
2+
use std::sync::{Arc, OnceLock};
3+
4+
use arrow_schema::{DataType, FieldRef};
5+
use datafusion::error::{DataFusionError, Result};
6+
use datafusion::logical_expr::scalar_doc_sections::DOC_SECTION_OTHER;
7+
use datafusion::logical_expr::{
8+
ColumnarValue, Documentation, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl, Signature,
9+
};
10+
use geo_traits::GeometryTrait;
11+
use geoarrow_array::array::{
12+
CoordBuffer, InterleavedCoordBuffer, LineStringArray, MultiLineStringArray, MultiPointArray,
13+
MultiPolygonArray, PointArray, PolygonArray, RectArray, SeparatedCoordBuffer, from_arrow_array,
14+
};
15+
use geoarrow_array::builder::{GeometryBuilder, InterleavedCoordBufferBuilder};
16+
use geoarrow_array::cast::AsGeoArrowArray;
17+
use geoarrow_array::{GeoArrowArray, GeoArrowArrayAccessor, IntoArrow, downcast_geoarrow_array};
18+
use geoarrow_schema::error::GeoArrowResult;
19+
use geoarrow_schema::{CoordType, Dimension, GeoArrowType, GeometryType};
20+
21+
use crate::data_types::any_single_geometry_type_input;
22+
use crate::error::GeoDataFusionResult;
23+
24+
#[derive(Debug)]
25+
pub struct FlipCoordinates {
26+
signature: Signature,
27+
coord_type: CoordType,
28+
}
29+
30+
impl FlipCoordinates {
31+
pub fn new(coord_type: CoordType) -> Self {
32+
Self {
33+
signature: any_single_geometry_type_input(),
34+
coord_type,
35+
}
36+
}
37+
}
38+
39+
impl Default for FlipCoordinates {
40+
fn default() -> Self {
41+
Self::new(Default::default())
42+
}
43+
}
44+
45+
static DOCUMENTATION: OnceLock<Documentation> = OnceLock::new();
46+
47+
impl ScalarUDFImpl for FlipCoordinates {
48+
fn as_any(&self) -> &dyn Any {
49+
self
50+
}
51+
52+
fn name(&self) -> &str {
53+
"st_flipcoordinates"
54+
}
55+
56+
fn signature(&self) -> &Signature {
57+
&self.signature
58+
}
59+
60+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
61+
Err(DataFusionError::Internal("return_type".to_string()))
62+
}
63+
64+
fn return_field_from_args(&self, args: ReturnFieldArgs) -> Result<FieldRef> {
65+
Ok(return_field_impl(args, self.coord_type)?)
66+
}
67+
68+
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
69+
Ok(invoke_impl(args, self.coord_type)?)
70+
}
71+
72+
fn documentation(&self) -> Option<&Documentation> {
73+
Some(DOCUMENTATION.get_or_init(|| {
74+
Documentation::builder(
75+
DOC_SECTION_OTHER,
76+
"Return the number of points in a geometry. Works for all geometries.",
77+
"ST_FlipCoordinates(geometry)",
78+
)
79+
.with_argument("g1", "geometry")
80+
.build()
81+
}))
82+
}
83+
}
84+
85+
fn return_field_impl(
86+
args: ReturnFieldArgs,
87+
coord_type: CoordType,
88+
) -> GeoDataFusionResult<FieldRef> {
89+
let field = args.arg_fields[0].as_ref();
90+
let geo_type = GeoArrowType::from_arrow_field(field)?;
91+
let new_type = match geo_type {
92+
GeoArrowType::Point(_)
93+
| GeoArrowType::LineString(_)
94+
| GeoArrowType::Polygon(_)
95+
| GeoArrowType::MultiPoint(_)
96+
| GeoArrowType::MultiLineString(_)
97+
| GeoArrowType::MultiPolygon(_)
98+
| GeoArrowType::Rect(_) => geo_type,
99+
_ => GeoArrowType::Geometry(
100+
GeometryType::new(geo_type.metadata().clone()).with_coord_type(coord_type),
101+
),
102+
};
103+
Ok(Arc::new(
104+
new_type.to_field(field.name(), field.is_nullable()),
105+
))
106+
}
107+
108+
fn invoke_impl(
109+
args: ScalarFunctionArgs,
110+
coord_type: CoordType,
111+
) -> GeoDataFusionResult<ColumnarValue> {
112+
let arrays = ColumnarValue::values_to_arrays(&args.args)?;
113+
let geo_array = from_arrow_array(&arrays[0], &args.arg_fields[0])?;
114+
let result = flip_coordinates_impl(&geo_array, coord_type)?;
115+
Ok(ColumnarValue::Array(result.into_array_ref()))
116+
}
117+
118+
fn flip_coordinates_impl(
119+
array: &dyn GeoArrowArray,
120+
coord_type: CoordType,
121+
) -> GeoArrowResult<Arc<dyn GeoArrowArray>> {
122+
match array.data_type() {
123+
GeoArrowType::Point(_) => Ok(Arc::new(flip_point_array(array.as_point()))),
124+
GeoArrowType::LineString(_) => Ok(Arc::new(flip_line_string_array(array.as_line_string()))),
125+
GeoArrowType::Polygon(_) => Ok(Arc::new(flip_polygon_array(array.as_polygon()))),
126+
GeoArrowType::MultiPoint(_) => Ok(Arc::new(flip_multipoint_array(array.as_multi_point()))),
127+
GeoArrowType::MultiLineString(_) => Ok(Arc::new(flip_multi_line_string_array(
128+
array.as_multi_line_string(),
129+
))),
130+
GeoArrowType::MultiPolygon(_) => {
131+
Ok(Arc::new(flip_multi_polygon_array(array.as_multi_polygon())))
132+
}
133+
GeoArrowType::Rect(_) => Ok(Arc::new(flip_rect_array(array.as_rect()))),
134+
_ => downcast_geoarrow_array!(array, flip_generic_array, coord_type),
135+
}
136+
}
137+
138+
fn flip_point_array(array: &PointArray) -> PointArray {
139+
PointArray::new(
140+
flip_coords(array.coords()),
141+
array.logical_nulls(),
142+
array.extension_type().metadata().clone(),
143+
)
144+
}
145+
146+
fn flip_line_string_array(array: &LineStringArray) -> LineStringArray {
147+
LineStringArray::new(
148+
flip_coords(array.coords()),
149+
array.geom_offsets().clone(),
150+
array.logical_nulls(),
151+
array.extension_type().metadata().clone(),
152+
)
153+
}
154+
155+
fn flip_polygon_array(array: &PolygonArray) -> PolygonArray {
156+
PolygonArray::new(
157+
flip_coords(array.coords()),
158+
array.geom_offsets().clone(),
159+
array.ring_offsets().clone(),
160+
array.logical_nulls(),
161+
array.extension_type().metadata().clone(),
162+
)
163+
}
164+
165+
fn flip_multipoint_array(array: &MultiPointArray) -> MultiPointArray {
166+
MultiPointArray::new(
167+
flip_coords(array.coords()),
168+
array.geom_offsets().clone(),
169+
array.logical_nulls(),
170+
array.extension_type().metadata().clone(),
171+
)
172+
}
173+
174+
fn flip_multi_line_string_array(array: &MultiLineStringArray) -> MultiLineStringArray {
175+
MultiLineStringArray::new(
176+
flip_coords(array.coords()),
177+
array.geom_offsets().clone(),
178+
array.ring_offsets().clone(),
179+
array.logical_nulls(),
180+
array.extension_type().metadata().clone(),
181+
)
182+
}
183+
184+
fn flip_multi_polygon_array(array: &MultiPolygonArray) -> MultiPolygonArray {
185+
MultiPolygonArray::new(
186+
flip_coords(array.coords()),
187+
array.geom_offsets().clone(),
188+
array.polygon_offsets().clone(),
189+
array.ring_offsets().clone(),
190+
array.logical_nulls(),
191+
array.extension_type().metadata().clone(),
192+
)
193+
}
194+
195+
fn flip_rect_array(array: &RectArray) -> RectArray {
196+
RectArray::new(
197+
flip_separated_coords(array.lower()),
198+
flip_separated_coords(array.upper()),
199+
array.logical_nulls(),
200+
array.extension_type().metadata().clone(),
201+
)
202+
}
203+
204+
fn flip_generic_array<'a>(
205+
array: &'a impl GeoArrowArrayAccessor<'a>,
206+
coord_type: CoordType,
207+
) -> GeoArrowResult<Arc<dyn GeoArrowArray>> {
208+
let typ = GeometryType::new(array.data_type().metadata().clone()).with_coord_type(coord_type);
209+
let mut output_builder = GeometryBuilder::new(typ);
210+
211+
for geom in array.iter() {
212+
if let Some(g) = geom {
213+
output_builder.push_geometry(Some(&flip_geometry(&g?)?))?;
214+
} else {
215+
output_builder.push_null();
216+
}
217+
}
218+
219+
Ok(Arc::new(output_builder.finish()))
220+
}
221+
222+
fn flip_geometry(geom: &impl GeometryTrait<T = f64>) -> GeoArrowResult<wkt::Wkt> {
223+
todo!()
224+
// match geom.as_type() {
225+
// geo_traits::GeometryType::Point()
226+
// }
227+
}
228+
229+
fn flip_coords(coords: &CoordBuffer) -> CoordBuffer {
230+
match coords {
231+
CoordBuffer::Separated(separated) => {
232+
CoordBuffer::Separated(flip_separated_coords(separated))
233+
}
234+
CoordBuffer::Interleaved(interleaved) => {
235+
CoordBuffer::Interleaved(flip_interleaved_coords(interleaved))
236+
}
237+
}
238+
}
239+
240+
fn flip_separated_coords(coords: &SeparatedCoordBuffer) -> SeparatedCoordBuffer {
241+
let mut buffers = coords.buffers().to_vec();
242+
buffers.swap(0, 1);
243+
SeparatedCoordBuffer::from_vec(buffers, coords.dim()).unwrap()
244+
}
245+
246+
fn flip_interleaved_coords(coords: &InterleavedCoordBuffer) -> InterleavedCoordBuffer {
247+
let mut builder = InterleavedCoordBufferBuilder::with_capacity(coords.len(), coords.dim());
248+
let raw_coord_buffer = coords.coords();
249+
for coord_idx in 0..coords.len() {
250+
match coords.dim() {
251+
Dimension::XY => {
252+
let x = raw_coord_buffer.get(coord_idx * 2).unwrap();
253+
let y = raw_coord_buffer.get(coord_idx * 2 + 1).unwrap();
254+
let flipped = geo::Coord { x: *y, y: *x };
255+
builder.push_coord(&flipped);
256+
}
257+
Dimension::XYZ => {
258+
let x = raw_coord_buffer.get(coord_idx * 3).unwrap();
259+
let y = raw_coord_buffer.get(coord_idx * 3 + 1).unwrap();
260+
let z = raw_coord_buffer.get(coord_idx * 3 + 2).unwrap();
261+
let flipped = wkt::types::Coord {
262+
x: *y,
263+
y: *x,
264+
z: Some(*z),
265+
m: None,
266+
};
267+
builder.push_coord(&flipped);
268+
}
269+
Dimension::XYM => {
270+
let x = raw_coord_buffer.get(coord_idx * 3).unwrap();
271+
let y = raw_coord_buffer.get(coord_idx * 3 + 1).unwrap();
272+
let m = raw_coord_buffer.get(coord_idx * 3 + 2).unwrap();
273+
let flipped = wkt::types::Coord {
274+
x: *y,
275+
y: *x,
276+
z: None,
277+
m: Some(*m),
278+
};
279+
builder.push_coord(&flipped);
280+
}
281+
Dimension::XYZM => {
282+
let x = raw_coord_buffer.get(coord_idx * 4).unwrap();
283+
let y = raw_coord_buffer.get(coord_idx * 4 + 1).unwrap();
284+
let z = raw_coord_buffer.get(coord_idx * 4 + 2).unwrap();
285+
let m = raw_coord_buffer.get(coord_idx * 4 + 3).unwrap();
286+
let flipped = wkt::types::Coord {
287+
x: *y,
288+
y: *x,
289+
z: Some(*z),
290+
m: Some(*m),
291+
};
292+
builder.push_coord(&flipped);
293+
}
294+
}
295+
}
296+
builder.finish()
297+
}
298+
299+
#[cfg(test)]
300+
mod test {
301+
use std::str::FromStr;
302+
303+
use datafusion::prelude::SessionContext;
304+
use geoarrow_array::array::WktArray;
305+
306+
use super::*;
307+
use crate::udf::native::io::{AsText, GeomFromText};
308+
309+
#[tokio::test]
310+
async fn test() {
311+
let ctx = SessionContext::new();
312+
313+
ctx.register_udf(FlipCoordinates::new(Default::default()).into());
314+
ctx.register_udf(GeomFromText::new(Default::default()).into());
315+
ctx.register_udf(AsText::new().into());
316+
317+
let df = ctx
318+
.sql("SELECT ST_AsText(ST_FlipCoordinates(ST_GeomFromText('POINT(1 2)')));")
319+
.await
320+
.unwrap();
321+
let batch = df.collect().await.unwrap().into_iter().next().unwrap();
322+
let wkt_arr =
323+
WktArray::try_from((batch.column(0).as_ref(), batch.schema().field(0))).unwrap();
324+
let val = wkt_arr.value(0).unwrap();
325+
assert_eq!(val, wkt::Wkt::from_str("POINT(2 1)").unwrap());
326+
}
327+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod flip_coordinates;
2+
3+
pub use flip_coordinates::FlipCoordinates;

rust/geodatafusion/src/udf/native/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pub mod accessors;
44
pub mod bounding_box;
55
pub mod constructors;
6+
pub mod editors;
67
pub mod io;
78
// mod processing;
89

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub(crate) mod to_wkt_geometry;

0 commit comments

Comments
 (0)