Skip to content

Commit 1f062ce

Browse files
authored
Add St_x, st_y, st_srid (#324)
* Add ST_MakePolygon, st_polygon * Add docs * Add ST_dimention * Add license * Add ST_Endpoint, ST_PointN * Reimplement ST_Point to work with snowflake linestring * Add St_x, st_y, st_srid
1 parent cfbd781 commit 1f062ce

File tree

4 files changed

+439
-2
lines changed

4 files changed

+439
-2
lines changed

crates/runtime/src/datafusion/functions/geospatial/accessors/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
mod dim;
1919
mod line_string;
20+
mod point;
21+
mod srid;
2022

2123
use datafusion::prelude::SessionContext;
2224

@@ -26,4 +28,7 @@ pub fn register_udfs(ctx: &SessionContext) {
2628
ctx.register_udf(line_string::StartPoint::new().into());
2729
ctx.register_udf(line_string::EndPoint::new().into());
2830
ctx.register_udf(line_string::PointN::new().into());
31+
ctx.register_udf(srid::Srid::new().into());
32+
ctx.register_udf(point::PointX::new().into());
33+
ctx.register_udf(point::PointY::new().into());
2934
}
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use crate::datafusion::functions::geospatial::data_types::{parse_to_native_array, POINT2D_TYPE};
19+
use crate::datafusion::functions::geospatial::error as geo_error;
20+
use arrow_array::builder::Float64Builder;
21+
use arrow_schema::DataType;
22+
use arrow_schema::DataType::Float64;
23+
use datafusion::logical_expr::scalar_doc_sections::DOC_SECTION_OTHER;
24+
use datafusion::logical_expr::{
25+
ColumnarValue, Documentation, ScalarUDFImpl, Signature, Volatility,
26+
};
27+
use datafusion_common::{DataFusionError, Result};
28+
use geo_traits::{CoordTrait, PointTrait};
29+
use geoarrow::array::AsNativeArray;
30+
use geoarrow::error::GeoArrowError;
31+
use geoarrow::trait_::ArrayAccessor;
32+
use geoarrow::ArrayBase;
33+
use snafu::ResultExt;
34+
use std::any::Any;
35+
use std::sync::{Arc, OnceLock};
36+
37+
#[derive(Debug)]
38+
pub struct PointX {
39+
signature: Signature,
40+
}
41+
42+
impl PointX {
43+
pub fn new() -> Self {
44+
Self {
45+
signature: Signature::exact(vec![POINT2D_TYPE.into()], Volatility::Immutable),
46+
}
47+
}
48+
}
49+
50+
static DOCUMENTATION: OnceLock<Documentation> = OnceLock::new();
51+
52+
impl ScalarUDFImpl for PointX {
53+
fn as_any(&self) -> &dyn Any {
54+
self
55+
}
56+
57+
fn name(&self) -> &'static str {
58+
"st_x"
59+
}
60+
61+
fn signature(&self) -> &Signature {
62+
&self.signature
63+
}
64+
65+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
66+
Ok(Float64)
67+
}
68+
69+
fn invoke(&self, args: &[ColumnarValue]) -> Result<ColumnarValue> {
70+
get_coord(args, 0)
71+
}
72+
73+
fn documentation(&self) -> Option<&Documentation> {
74+
Some(DOCUMENTATION.get_or_init(|| {
75+
Documentation::builder(
76+
DOC_SECTION_OTHER,
77+
"Returns the longitude (X coordinate) of a Point represented by geometry.",
78+
"ST_X(geom)",
79+
)
80+
.with_argument("g1", "geometry")
81+
.build()
82+
}))
83+
}
84+
}
85+
86+
#[derive(Debug)]
87+
pub struct PointY {
88+
signature: Signature,
89+
}
90+
91+
impl PointY {
92+
pub fn new() -> Self {
93+
Self {
94+
signature: Signature::exact(vec![POINT2D_TYPE.into()], Volatility::Immutable),
95+
}
96+
}
97+
}
98+
99+
impl ScalarUDFImpl for PointY {
100+
fn as_any(&self) -> &dyn Any {
101+
self
102+
}
103+
104+
fn name(&self) -> &'static str {
105+
"st_y"
106+
}
107+
108+
fn signature(&self) -> &Signature {
109+
&self.signature
110+
}
111+
112+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
113+
Ok(Float64)
114+
}
115+
116+
fn invoke(&self, args: &[ColumnarValue]) -> Result<ColumnarValue> {
117+
get_coord(args, 1)
118+
}
119+
120+
fn documentation(&self) -> Option<&Documentation> {
121+
Some(DOCUMENTATION.get_or_init(|| {
122+
Documentation::builder(
123+
DOC_SECTION_OTHER,
124+
"Returns the latitude (Y coordinate) of a Point represented by geometry.",
125+
"ST_Y(geom)",
126+
)
127+
.with_argument("g1", "geometry")
128+
.build()
129+
}))
130+
}
131+
}
132+
133+
fn get_coord(args: &[ColumnarValue], n: i64) -> Result<ColumnarValue> {
134+
let array = ColumnarValue::values_to_arrays(args)?
135+
.into_iter()
136+
.next()
137+
.ok_or_else(|| DataFusionError::Execution("Expected at least one argument".to_string()))?;
138+
139+
let native_array = parse_to_native_array(&array)?;
140+
let native_array_ref = native_array.as_ref();
141+
let points_array = native_array_ref
142+
.as_point_opt()
143+
.ok_or(GeoArrowError::General(
144+
"Expected Point-typed array".to_string(),
145+
))
146+
.context(geo_error::GeoArrowSnafu)?;
147+
148+
let mut output_builder = Float64Builder::with_capacity(points_array.len());
149+
150+
for line in points_array.iter() {
151+
if let Some(point) = line {
152+
let coord = point
153+
.coord()
154+
.ok_or_else(|| DataFusionError::Execution("Coordinate is None".to_string()))?;
155+
let value = match n {
156+
0 => coord.x(),
157+
1 => coord.y(),
158+
_ => {
159+
return Err(DataFusionError::Execution(
160+
"Index out of bounds".to_string(),
161+
))
162+
}
163+
};
164+
output_builder.append_value(value);
165+
} else {
166+
output_builder.append_null();
167+
}
168+
}
169+
Ok(ColumnarValue::Array(Arc::new(output_builder.finish())))
170+
}
171+
172+
#[cfg(test)]
173+
mod tests {
174+
use super::*;
175+
use arrow_array::cast::AsArray;
176+
use arrow_array::types::Float64Type;
177+
use datafusion::logical_expr::ColumnarValue;
178+
use geo_types::point;
179+
use geoarrow::array::{CoordType, PointBuilder};
180+
use geoarrow::datatypes::Dimension;
181+
182+
#[test]
183+
#[allow(clippy::unwrap_used, clippy::float_cmp)]
184+
fn test_x() {
185+
let pa = PointBuilder::from_points(
186+
[
187+
point! {x: 4., y: 2.},
188+
point! {x: 1., y: 2.},
189+
point! {x: 2., y: 3.},
190+
]
191+
.iter(),
192+
Dimension::XY,
193+
CoordType::Separated,
194+
Arc::default(),
195+
)
196+
.finish()
197+
.to_array_ref();
198+
199+
let args = vec![ColumnarValue::Array(pa)];
200+
let x = PointX::new();
201+
let result = x.invoke_batch(&args, 3).unwrap();
202+
let result = result.to_array(3).unwrap();
203+
204+
let result = result.as_primitive::<Float64Type>();
205+
assert_eq!(result.value(0), 4.0);
206+
assert_eq!(result.value(1), 1.0);
207+
assert_eq!(result.value(2), 2.0);
208+
}
209+
#[test]
210+
#[allow(clippy::unwrap_used, clippy::float_cmp)]
211+
fn test_y() {
212+
let pa = PointBuilder::from_points(
213+
[
214+
point! {x: 4., y: 0.},
215+
point! {x: 1., y: 2.},
216+
point! {x: 2., y: 3.},
217+
]
218+
.iter(),
219+
Dimension::XY,
220+
CoordType::Separated,
221+
Arc::default(),
222+
)
223+
.finish()
224+
.to_array_ref();
225+
226+
let args = vec![ColumnarValue::Array(pa)];
227+
let y = PointY::new();
228+
let result = y.invoke_batch(&args, 3).unwrap();
229+
let result = result.to_array(3).unwrap();
230+
231+
let result = result.as_primitive::<Float64Type>();
232+
assert_eq!(result.value(0), 0.0);
233+
assert_eq!(result.value(1), 2.0);
234+
assert_eq!(result.value(2), 3.0);
235+
}
236+
}

0 commit comments

Comments
 (0)