You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@sedona.apache.org by "sapienza88 (via GitHub)" <gi...@apache.org> on 2026/04/21 20:32:23 UTC
[PR] init commit boilerplate code for st_linesubstring func [sedona-db]
sapienza88 opened a new pull request, #777:
URL: https://github.com/apache/sedona-db/pull/777
(no comment)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "paleolimbot (via GitHub)" <gi...@apache.org>.
paleolimbot commented on PR #777:
URL: https://github.com/apache/sedona-db/pull/777#issuecomment-4492321797
You'll need to solve the merge conflict before it will let me run CI. You can copy/patse the versions of these files from `main` (this is what I did to fix the merge conflict for you)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] ST_LineSubstring function [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3313161543
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,341 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_array::Array;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, exec_datafusion_err, exec_err};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{
+ write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header,
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+ let end_array = args[2]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+ let end_floats = end_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+
+ let mut start_floats_iter = start_floats.iter();
+ let mut end_floats_iter = end_floats.iter();
+
+ let mut wkb_body = Vec::new();
+ let mut cumulative_distances = Vec::new();
+
+ executor.execute_wkb_void(|maybe_wkb| {
+ let (Some(wkb), Some(s_f), Some(e_f)) = (
+ maybe_wkb,
+ start_floats_iter.next().unwrap(),
+ end_floats_iter.next().unwrap(),
+ ) else {
+ builder.append_null();
+ return Ok(());
+ };
+
+ let GeometryType::LineString(line) = wkb.as_type() else {
+ return exec_err!("Can't compute substring of non-line input");
+ };
+
+ if !(0.0..=1.0).contains(&s_f) {
+ return exec_err!("start_fraction must be between 0.0 and 1.0 (got {s_f}");
+ }
+
+ if !(0.0..=1.0).contains(&e_f) {
+ return exec_err!("end_fraction must be between 0.0 and 1.0 (got {e_f}");
+ }
+
+ if e_f < s_f {
+ return exec_err!(
+ "end_fraction must be greater than start_fraction (got {e_f} < {s_f})"
+ );
+ }
+
+ invoke_scalar(
+ line,
+ s_f,
+ e_f,
+ &mut wkb_body,
+ &mut cumulative_distances,
+ &mut builder,
+ )
+ .map_err(|e| exec_datafusion_err!("Error executing st_linesubstring: {e}"))?;
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+
+fn invoke_scalar(
+ line: &impl LineStringTrait<T = f64>,
+ s_f: f64,
+ e_f: f64,
+ wkb_body: &mut Vec<u8>,
+ cumulative_distances: &mut Vec<f64>,
+ builder: &mut BinaryBuilder,
+) -> Result<(), SedonaGeometryError> {
+ let mut point_count = 0u32;
+ wkb_body.clear();
+ cumulative_distances.clear();
+
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ // Empty or degenerate input returns NULL here
+ if num_coords <= 1 {
+ builder.append_null();
+ return Ok(());
+ }
+
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let (p1, p2) = unsafe { (line.coord_unchecked(i), line.coord_unchecked(i + 1)) };
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ // Use binary search to find the segment range to scan.
+ // For start: find the segment containing start_dist (where d[i] <= start_dist < d[i+1])
+ // For end: find the segment containing end_dist (where d[i] < end_dist <= d[i+1])
+ let num_segments = num_coords - 1;
+
+ let first_segment = cumulative_distances
+ .partition_point(|&d| d <= start_dist)
+ .saturating_sub(1)
+ .min(num_segments - 1);
+
+ let last_segment = cumulative_distances
+ .partition_point(|&d| d < end_dist)
+ .saturating_sub(1)
+ .min(num_segments - 1);
Review Comment:
Looks good to me!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3269562570
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,296 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+use arrow_array::Array;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+ let end_array = args[2].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+ let end_floats = end_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+ let mut row_idx = 0;
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+ // Fetch the unique start/end fraction values for THIS specific row
+ let s_f_opt = if start_floats.is_null(row_idx) { None } else { Some(start_floats.value(row_idx)) };
+ let e_f_opt = if end_floats.is_null(row_idx) { None } else { Some(end_floats.value(row_idx)) };
+ row_idx += 1; // Increment for the next iteration
+
+ let (s_f, e_f) = match (s_f_opt, e_f_opt) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+
+ if s_f == e_f {
+ // POINT Result
+ write_wkb_point_header(&mut final_wkb, dim)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result
+ write_wkb_linestring_header(&mut final_wkb, dim, point_count as usize)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+#[cfg(test)]
+mod tests {
+ use arrow_array::{ArrayRef, Float64Array};
+ use arrow_schema::DataType;
+ use datafusion_common::scalar::ScalarValue;
+ use datafusion_expr::{ColumnarValue, ScalarUDF};
+ use rstest::rstest;
+ use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS};
+ use sedona_testing::{
+ compare::{assert_array_equal, assert_scalar_equal},
+ testers::ScalarUdfTester,
+ };
+ use std::sync::Arc;
+
+ use super::*;
+
+ #[test]
+ fn udf_metadata() {
+ let udf: ScalarUDF = st_line_substring_udf().into();
+ assert_eq!(udf.name(), "st_linesubstring");
+ }
+
+ #[rstest]
+ fn udf(
+ #[values(WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS.clone())]
+ sedona_type: SedonaType,
+ ) {
+ let udf = st_line_substring_udf();
+ let tester = ScalarUdfTester::new(
+ udf.into(),
+ vec![
+ sedona_type,
+ SedonaType::Arrow(DataType::Float64),
+ SedonaType::Arrow(DataType::Float64)
+ ]
+ );
+
+ let actual_2d = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.0, 0.5).unwrap();
+ let expected_2d = tester.invoke_wkb_scalar(Some("LINESTRING(0 0, 5 5)")).unwrap();
+ assert_scalar_equal(&actual_2d, &expected_2d);
+
+
+ let actual_z = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 10 20, 10 20 30)", 0.5, 1.0).unwrap();
+ let expected_z = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 15 25, 10 20 30)")).unwrap();
+ assert_scalar_equal(&actual_z, &expected_z);
+
+ let actual_mid = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 0 0, 10 10 10)", 0.5, 0.8).unwrap();
+ let expected_mid = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 5 5, 8 8 8)")).unwrap();
+ assert_scalar_equal(&actual_mid, &expected_mid);
+
+
+ let actual_point = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.5, 0.5).unwrap();
+ let expected_point = tester.invoke_wkb_scalar(Some("POINT(5 5)")).unwrap();
+ assert_scalar_equal(&actual_point, &expected_point);
+
+
+ let geoms_input = tester.invoke_wkb_array(vec![
+ Some("LINESTRING(0 0, 10 10)"),
+ None,
+ Some("LINESTRING(0 0, 10 10)")
+ ]).unwrap();
Review Comment:
done.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3269562570
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,296 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+use arrow_array::Array;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+ let end_array = args[2].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+ let end_floats = end_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+ let mut row_idx = 0;
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+ // Fetch the unique start/end fraction values for THIS specific row
+ let s_f_opt = if start_floats.is_null(row_idx) { None } else { Some(start_floats.value(row_idx)) };
+ let e_f_opt = if end_floats.is_null(row_idx) { None } else { Some(end_floats.value(row_idx)) };
+ row_idx += 1; // Increment for the next iteration
+
+ let (s_f, e_f) = match (s_f_opt, e_f_opt) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+
+ if s_f == e_f {
+ // POINT Result
+ write_wkb_point_header(&mut final_wkb, dim)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result
+ write_wkb_linestring_header(&mut final_wkb, dim, point_count as usize)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+#[cfg(test)]
+mod tests {
+ use arrow_array::{ArrayRef, Float64Array};
+ use arrow_schema::DataType;
+ use datafusion_common::scalar::ScalarValue;
+ use datafusion_expr::{ColumnarValue, ScalarUDF};
+ use rstest::rstest;
+ use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS};
+ use sedona_testing::{
+ compare::{assert_array_equal, assert_scalar_equal},
+ testers::ScalarUdfTester,
+ };
+ use std::sync::Arc;
+
+ use super::*;
+
+ #[test]
+ fn udf_metadata() {
+ let udf: ScalarUDF = st_line_substring_udf().into();
+ assert_eq!(udf.name(), "st_linesubstring");
+ }
+
+ #[rstest]
+ fn udf(
+ #[values(WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS.clone())]
+ sedona_type: SedonaType,
+ ) {
+ let udf = st_line_substring_udf();
+ let tester = ScalarUdfTester::new(
+ udf.into(),
+ vec![
+ sedona_type,
+ SedonaType::Arrow(DataType::Float64),
+ SedonaType::Arrow(DataType::Float64)
+ ]
+ );
+
+ let actual_2d = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.0, 0.5).unwrap();
+ let expected_2d = tester.invoke_wkb_scalar(Some("LINESTRING(0 0, 5 5)")).unwrap();
+ assert_scalar_equal(&actual_2d, &expected_2d);
+
+
+ let actual_z = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 10 20, 10 20 30)", 0.5, 1.0).unwrap();
+ let expected_z = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 15 25, 10 20 30)")).unwrap();
+ assert_scalar_equal(&actual_z, &expected_z);
+
+ let actual_mid = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 0 0, 10 10 10)", 0.5, 0.8).unwrap();
+ let expected_mid = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 5 5, 8 8 8)")).unwrap();
+ assert_scalar_equal(&actual_mid, &expected_mid);
+
+
+ let actual_point = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.5, 0.5).unwrap();
+ let expected_point = tester.invoke_wkb_scalar(Some("POINT(5 5)")).unwrap();
+ assert_scalar_equal(&actual_point, &expected_point);
+
+
+ let geoms_input = tester.invoke_wkb_array(vec![
+ Some("LINESTRING(0 0, 10 10)"),
+ None,
+ Some("LINESTRING(0 0, 10 10)")
+ ]).unwrap();
Review Comment:
done.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] ST_LineSubstring function [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3313185004
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,341 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_array::Array;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, exec_datafusion_err, exec_err};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{
+ write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header,
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+ let end_array = args[2]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+ let end_floats = end_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+
+ let mut start_floats_iter = start_floats.iter();
+ let mut end_floats_iter = end_floats.iter();
+
+ let mut wkb_body = Vec::new();
+ let mut cumulative_distances = Vec::new();
+
+ executor.execute_wkb_void(|maybe_wkb| {
+ let (Some(wkb), Some(s_f), Some(e_f)) = (
+ maybe_wkb,
+ start_floats_iter.next().unwrap(),
+ end_floats_iter.next().unwrap(),
+ ) else {
+ builder.append_null();
+ return Ok(());
+ };
+
+ let GeometryType::LineString(line) = wkb.as_type() else {
+ return exec_err!("Can't compute substring of non-line input");
+ };
+
+ if !(0.0..=1.0).contains(&s_f) {
+ return exec_err!("start_fraction must be between 0.0 and 1.0 (got {s_f}");
+ }
+
+ if !(0.0..=1.0).contains(&e_f) {
+ return exec_err!("end_fraction must be between 0.0 and 1.0 (got {e_f}");
+ }
+
+ if e_f < s_f {
+ return exec_err!(
+ "end_fraction must be greater than start_fraction (got {e_f} < {s_f})"
+ );
+ }
+
+ invoke_scalar(
+ line,
+ s_f,
+ e_f,
+ &mut wkb_body,
+ &mut cumulative_distances,
+ &mut builder,
+ )
+ .map_err(|e| exec_datafusion_err!("Error executing st_linesubstring: {e}"))?;
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+
+fn invoke_scalar(
+ line: &impl LineStringTrait<T = f64>,
+ s_f: f64,
+ e_f: f64,
+ wkb_body: &mut Vec<u8>,
+ cumulative_distances: &mut Vec<f64>,
+ builder: &mut BinaryBuilder,
+) -> Result<(), SedonaGeometryError> {
Review Comment:
Here whats for?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3249463241
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::write_wkb_coord_trait;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
Review Comment:
Done!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3269566067
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,296 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+use arrow_array::Array;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+ let end_array = args[2].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+ let end_floats = end_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+ let mut row_idx = 0;
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+ // Fetch the unique start/end fraction values for THIS specific row
+ let s_f_opt = if start_floats.is_null(row_idx) { None } else { Some(start_floats.value(row_idx)) };
+ let e_f_opt = if end_floats.is_null(row_idx) { None } else { Some(end_floats.value(row_idx)) };
+ row_idx += 1; // Increment for the next iteration
+
+ let (s_f, e_f) = match (s_f_opt, e_f_opt) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+
+ if s_f == e_f {
+ // POINT Result
+ write_wkb_point_header(&mut final_wkb, dim)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result
+ write_wkb_linestring_header(&mut final_wkb, dim, point_count as usize)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+#[cfg(test)]
+mod tests {
+ use arrow_array::{ArrayRef, Float64Array};
+ use arrow_schema::DataType;
+ use datafusion_common::scalar::ScalarValue;
+ use datafusion_expr::{ColumnarValue, ScalarUDF};
+ use rstest::rstest;
+ use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS};
+ use sedona_testing::{
+ compare::{assert_array_equal, assert_scalar_equal},
+ testers::ScalarUdfTester,
+ };
+ use std::sync::Arc;
+
+ use super::*;
+
+ #[test]
+ fn udf_metadata() {
+ let udf: ScalarUDF = st_line_substring_udf().into();
+ assert_eq!(udf.name(), "st_linesubstring");
+ }
+
+ #[rstest]
+ fn udf(
+ #[values(WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS.clone())]
+ sedona_type: SedonaType,
+ ) {
+ let udf = st_line_substring_udf();
+ let tester = ScalarUdfTester::new(
+ udf.into(),
+ vec![
+ sedona_type,
+ SedonaType::Arrow(DataType::Float64),
+ SedonaType::Arrow(DataType::Float64)
+ ]
+ );
+
+ let actual_2d = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.0, 0.5).unwrap();
+ let expected_2d = tester.invoke_wkb_scalar(Some("LINESTRING(0 0, 5 5)")).unwrap();
+ assert_scalar_equal(&actual_2d, &expected_2d);
+
+
+ let actual_z = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 10 20, 10 20 30)", 0.5, 1.0).unwrap();
+ let expected_z = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 15 25, 10 20 30)")).unwrap();
+ assert_scalar_equal(&actual_z, &expected_z);
+
+ let actual_mid = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 0 0, 10 10 10)", 0.5, 0.8).unwrap();
+ let expected_mid = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 5 5, 8 8 8)")).unwrap();
+ assert_scalar_equal(&actual_mid, &expected_mid);
+
+
+ let actual_point = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.5, 0.5).unwrap();
+ let expected_point = tester.invoke_wkb_scalar(Some("POINT(5 5)")).unwrap();
+ assert_scalar_equal(&actual_point, &expected_point);
+
+
+ let geoms_input = tester.invoke_wkb_array(vec![
+ Some("LINESTRING(0 0, 10 10)"),
+ None,
+ Some("LINESTRING(0 0, 10 10)")
+ ]).unwrap();
+
+ let starts_input: ArrayRef = Arc::new(Float64Array::from(vec![Some(0.0), Some(0.0), Some(0.5)]));
+ let ends_input: ArrayRef = Arc::new(Float64Array::from(vec![Some(0.5), Some(1.0), Some(0.5)]));
+
+ let expected_array = tester.invoke_wkb_array(vec![
+ Some("LINESTRING(0 0, 5 5)"),
+ None,
+ Some("POINT(5 5)")
+ ]).unwrap();
+
+ let actual_array = match tester.invoke(vec![
+ ColumnarValue::Array(geoms_input),
+ ColumnarValue::Array(starts_input),
+ ColumnarValue::Array(ends_input)
+ ]).unwrap() {
+ ColumnarValue::Array(arr) => arr,
+ _ => panic!("Expected array block context output"),
+ };
Review Comment:
ok
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3204948963
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_common::sedona_internal_err;
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+use crate::executor::WkbExecutor;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+ fn interpolate<C: CoordTrait<T = f64>>(p1: C, p2: C, fraction: f64) -> (f64, f64) {
+ let x = p1.x() + (p2.x() - p1.x()) * fraction;
+ let y = p1.y() + (p2.y() - p1.y()) * fraction;
+ (x, y)
+ }
+ executor.execute_wkb_void(|maybe_wkb| {
+ let mut new_coords = Vec::new();
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords() as i64;
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords as usize);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+ let (s_frac, e_frac) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+ for i in 0..(num_coords as usize - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+ let start_dist = s_frac * total_length;
+ let end_dist = e_frac * total_length;
+
+ for i in 0..(num_coords as usize - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ new_coords.push(interpolate(p1, p2, fraction));
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ new_coords.push((p1.x(), p1.y()));
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ new_coords.push(interpolate(p1, p2, fraction));
+ }
+ }
+ }
+ }
+ if !new_coords.is_empty() {
+ // write byte order (1 = little endian)
+ builder.write_all(&[1u8])?;
+
+ let type_id: u32 = 2;
+ builder.write_all(&type_id.to_le_bytes())?;
+
+ let num_points = new_coords.len() as u32;
+ builder.write_all(&num_points.to_le_bytes())?;
+
+ for (x, y) in new_coords {
+ builder.write_all(&x.to_le_bytes())?;
+ builder.write_all(&y.to_le_bytes())?;
+ }
Review Comment:
I’m a little rusty when it comes to Rust, but I’ll try to use some AI tools to help make this mergeable.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3252869472
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::write_wkb_coord_trait;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+
+ let (s_f, e_f) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ // 2. Build Header inside the 'line' scope (Fixes "cannot find dim/line")
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+ final_wkb.push(1u8); // Little Endian
+
+ if s_f == e_f {
+ // POINT Result (Fixes point vs line test)
+ let p_type: u32 = if dim == Dimensions::Xyz { 1001 } else { 1 };
+ final_wkb.extend_from_slice(&p_type.to_le_bytes());
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result (Fixes Z-coordinate drop)
+ let l_type: u32 = if dim == Dimensions::Xyz { 1002 } else { 2 };
+ final_wkb.extend_from_slice(&l_type.to_le_bytes());
+ final_wkb.extend_from_slice(&point_count.to_le_bytes());
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
Review Comment:
done! pls check all and merge whenever possible. Thanks.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat(rust/sedona-functions): Aded ST_LineSubstring function [sedona-db]
Posted by "paleolimbot (via GitHub)" <gi...@apache.org>.
paleolimbot merged PR #777:
URL: https://github.com/apache/sedona-db/pull/777
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3269709148
##########
python/sedonadb/tests/functions/test_linesubstring.py:
##########
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from sedonadb.testing import geom_or_null, PostGIS, SedonaDB
+
+
+@pytest.mark.parametrize("eng", [SedonaDB, PostGIS])
+@pytest.mark.parametrize(
+ ("geom", "start", "end", "expected"),
+ [
+ (None, 0.0, 1.0, None),
+ ("LINESTRING(0 0, 10 10)", None, 1.0, None), # Start fraction is NULL -> Output is NULL
+ ("LINESTRING(0 0, 10 10)", 0.0, None, None), # End fraction is NULL -> Output is NULL
+ (None, None, None, None),
+ ("LINESTRING (0 0, 10 0)", 0.2, 0.8, "LINESTRING (2 0, 8 0)"),
Review Comment:
done
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3251080904
##########
python/sedonadb/tests/functions/test_linesubstring.py:
##########
@@ -0,0 +1,45 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from sedonadb.testing import geom_or_null, PostGIS, SedonaDB
+
+
+@pytest.mark.parametrize("eng", [SedonaDB, PostGIS])
+@pytest.mark.parametrize(
+ ("geom", "start", "end", "expected"),
+ [
+ (None, 0.0, 1.0, None),
+ ("LINESTRING (0 0, 10 0)", 0.2, 0.8, "LINESTRING (2 0, 8 0)"),
+ ("LINESTRING (0 0, 10 10)", 0.3, 0.6, "LINESTRING (3 3, 6 6)"),
+ ("LINESTRING (0 0, 10 10)", 0.5, 0.5, "POINT (5 5)"),
+ ("LINESTRING (0 0, 10 0, 10 10)", 0.25, 0.75, "LINESTRING (5 0, 10 0, 10 5)"),
+ (
+ "LINESTRING Z (0 0 0, 10 10 10)",
+ 0.5,
+ 0.8,
+ "LINESTRING Z (5 5 5, 8 8 8)",
+ ),
Review Comment:
done!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3252844585
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::write_wkb_coord_trait;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+
+ let (s_f, e_f) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ // 2. Build Header inside the 'line' scope (Fixes "cannot find dim/line")
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+ final_wkb.push(1u8); // Little Endian
+
+ if s_f == e_f {
+ // POINT Result (Fixes point vs line test)
+ let p_type: u32 = if dim == Dimensions::Xyz { 1001 } else { 1 };
+ final_wkb.extend_from_slice(&p_type.to_le_bytes());
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result (Fixes Z-coordinate drop)
+ let l_type: u32 = if dim == Dimensions::Xyz { 1002 } else { 2 };
+ final_wkb.extend_from_slice(&l_type.to_le_bytes());
+ final_wkb.extend_from_slice(&point_count.to_le_bytes());
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
Review Comment:
for now null should be fine?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "paleolimbot (via GitHub)" <gi...@apache.org>.
paleolimbot commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3175770077
##########
python/sedonadb/tests/functions/test_linesubstring.py:
##########
@@ -0,0 +1,61 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from sedonadb.testing import geom_or_null, PostGIS, SedonaDB
+
+
+@pytest.mark.parametrize("eng", [SedonaDB, PostGIS])
+@pytest.mark.parametrize(
+ ("geom", "start", "end", "expected"),
+ [
+ (None, 0.0, 1.0, None),
+ ("LINESTRING EMPTY", 0.0, 1.0, "LINESTRING EMPTY"),
+
+ ("LINESTRING (0 0, 10 0)", 0.2, 0.8, "LINESTRING (2 0, 8 0)"),
+
+ ("LINESTRING (0 0, 10 10)", 0.3, 0.6, "LINESTRING (3 3, 6 6)"),
+
+ ("LINESTRING (0 0, 10 10)", 0.5, 0.5, "POINT (5 5)"),
+
+ ("LINESTRING (0 0, 10 0, 10 10)", 0.25, 0.75, "LINESTRING (5 0, 10 0, 10 5)"),
+
+ (
+ "LINESTRING Z (0 0 0, 10 10 10)",
+ 0.5,
+ 0.8,
+ "LINESTRING Z (5 5 5, 8 8 8)",
+ ),
+ ],
+)
+def test_st_line_substring(eng, geom, start, end, expected):
+ eng = eng.create_or_skip()
+
+ if expected is not None:
+ expected = expected.replace(", ", ",")
+ expected = expected.replace(" (", "(")
+
+ if isinstance(eng, PostGIS):
+
+ expected = expected.replace("Z(", "Z (")
+ expected = expected.replace("M(", "M (")
+ expected = expected.replace("ZM(", "ZM (")
+
+ eng.assert_query_result(
+ f"SELECT ST_AsText(ST_LineSubstring({geom_or_null(geom)}, {start}, {end}))",
+ expected,
+ )
Review Comment:
You should be able to just feed the WKT directly into the `expected` slot, which should help with the difference between the WKT output:
```suggestion
eng.assert_query_result(
f"SELECT ST_LineSubstring({geom_or_null(geom)}, {start}, {end})",
expected,
)
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on PR #777:
URL: https://github.com/apache/sedona-db/pull/777#issuecomment-4360528601
@paleolimbot I added some tests and the doc for this function, not sure I understand: "This needs cargo fmt to (and all the problems found with cargo clippy to be fixed)."
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "paleolimbot (via GitHub)" <gi...@apache.org>.
paleolimbot commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3269840907
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,296 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+use arrow_array::Array;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+ let end_array = args[2].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+ let end_floats = end_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+ let mut row_idx = 0;
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+ // Fetch the unique start/end fraction values for THIS specific row
+ let s_f_opt = if start_floats.is_null(row_idx) { None } else { Some(start_floats.value(row_idx)) };
+ let e_f_opt = if end_floats.is_null(row_idx) { None } else { Some(end_floats.value(row_idx)) };
+ row_idx += 1; // Increment for the next iteration
+
+ let (s_f, e_f) = match (s_f_opt, e_f_opt) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+
+ if s_f == e_f {
+ // POINT Result
+ write_wkb_point_header(&mut final_wkb, dim)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result
+ write_wkb_linestring_header(&mut final_wkb, dim, point_count as usize)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+#[cfg(test)]
+mod tests {
+ use arrow_array::{ArrayRef, Float64Array};
+ use arrow_schema::DataType;
+ use datafusion_common::scalar::ScalarValue;
+ use datafusion_expr::{ColumnarValue, ScalarUDF};
+ use rstest::rstest;
+ use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS};
+ use sedona_testing::{
+ compare::{assert_array_equal, assert_scalar_equal},
+ testers::ScalarUdfTester,
+ };
+ use std::sync::Arc;
+
+ use super::*;
+
+ #[test]
+ fn udf_metadata() {
+ let udf: ScalarUDF = st_line_substring_udf().into();
+ assert_eq!(udf.name(), "st_linesubstring");
+ }
+
+ #[rstest]
+ fn udf(
+ #[values(WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS.clone())]
+ sedona_type: SedonaType,
+ ) {
+ let udf = st_line_substring_udf();
+ let tester = ScalarUdfTester::new(
+ udf.into(),
+ vec![
+ sedona_type,
+ SedonaType::Arrow(DataType::Float64),
+ SedonaType::Arrow(DataType::Float64)
+ ]
+ );
+
+ let actual_2d = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.0, 0.5).unwrap();
+ let expected_2d = tester.invoke_wkb_scalar(Some("LINESTRING(0 0, 5 5)")).unwrap();
+ assert_scalar_equal(&actual_2d, &expected_2d);
+
+
+ let actual_z = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 10 20, 10 20 30)", 0.5, 1.0).unwrap();
+ let expected_z = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 15 25, 10 20 30)")).unwrap();
+ assert_scalar_equal(&actual_z, &expected_z);
+
+ let actual_mid = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 0 0, 10 10 10)", 0.5, 0.8).unwrap();
+ let expected_mid = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 5 5, 8 8 8)")).unwrap();
+ assert_scalar_equal(&actual_mid, &expected_mid);
+
+
+ let actual_point = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.5, 0.5).unwrap();
+ let expected_point = tester.invoke_wkb_scalar(Some("POINT(5 5)")).unwrap();
+ assert_scalar_equal(&actual_point, &expected_point);
+
+
+ let geoms_input = tester.invoke_wkb_array(vec![
+ Some("LINESTRING(0 0, 10 10)"),
+ None,
+ Some("LINESTRING(0 0, 10 10)")
+ ]).unwrap();
Review Comment:
You'll have to import it from `sedona_testing::create`!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on PR #777:
URL: https://github.com/apache/sedona-db/pull/777#issuecomment-4502083832
@paleolimbot not sure why Git complains about a Cargo.lock conflicting, I removed it from my local code...
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "paleolimbot (via GitHub)" <gi...@apache.org>.
paleolimbot commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3222365532
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::write_wkb_coord_trait;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+
+ let (s_f, e_f) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ // 2. Build Header inside the 'line' scope (Fixes "cannot find dim/line")
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+ final_wkb.push(1u8); // Little Endian
+
+ if s_f == e_f {
+ // POINT Result (Fixes point vs line test)
+ let p_type: u32 = if dim == Dimensions::Xyz { 1001 } else { 1 };
+ final_wkb.extend_from_slice(&p_type.to_le_bytes());
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result (Fixes Z-coordinate drop)
+ let l_type: u32 = if dim == Dimensions::Xyz { 1002 } else { 2 };
+ final_wkb.extend_from_slice(&l_type.to_le_bytes());
+ final_wkb.extend_from_slice(&point_count.to_le_bytes());
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
Review Comment:
Should non-linestrings really return null here or should they error? (You can check by adding a non-linestring case to the Python integration tests where they also run against PostGIS)
##########
Cargo.toml:
##########
@@ -92,6 +92,7 @@ datafusion-datasource-parquet = { version = "52.5.0" }
datafusion-execution = { version = "52.5.0", default-features = false }
datafusion-expr = { version = "52.5.0" }
datafusion-ffi = { version = "52.5.0" }
+datafusion-functions-nested = { version = "52.5.0" }
Review Comment:
Was it necessary to add this?
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::write_wkb_coord_trait;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+
+ let (s_f, e_f) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ // 2. Build Header inside the 'line' scope (Fixes "cannot find dim/line")
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+ final_wkb.push(1u8); // Little Endian
+
+ if s_f == e_f {
+ // POINT Result (Fixes point vs line test)
+ let p_type: u32 = if dim == Dimensions::Xyz { 1001 } else { 1 };
+ final_wkb.extend_from_slice(&p_type.to_le_bytes());
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result (Fixes Z-coordinate drop)
+ let l_type: u32 = if dim == Dimensions::Xyz { 1002 } else { 2 };
+ final_wkb.extend_from_slice(&l_type.to_le_bytes());
+ final_wkb.extend_from_slice(&point_count.to_le_bytes());
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
Review Comment:
Before this merges it will also need a `mod test {}` like those in the other sedona-functions implementations. These include enough pure Rust test coverage to cover the main functionality including Scalar and Array inputs in execution and any errors that should occur. Basically, enough to cover the branches of the implementation.
##########
python/sedonadb/tests/functions/test_linesubstring.py:
##########
@@ -0,0 +1,45 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from sedonadb.testing import geom_or_null, PostGIS, SedonaDB
+
+
+@pytest.mark.parametrize("eng", [SedonaDB, PostGIS])
+@pytest.mark.parametrize(
+ ("geom", "start", "end", "expected"),
+ [
+ (None, 0.0, 1.0, None),
+ ("LINESTRING (0 0, 10 0)", 0.2, 0.8, "LINESTRING (2 0, 8 0)"),
+ ("LINESTRING (0 0, 10 10)", 0.3, 0.6, "LINESTRING (3 3, 6 6)"),
+ ("LINESTRING (0 0, 10 10)", 0.5, 0.5, "POINT (5 5)"),
+ ("LINESTRING (0 0, 10 0, 10 10)", 0.25, 0.75, "LINESTRING (5 0, 10 0, 10 5)"),
+ (
+ "LINESTRING Z (0 0 0, 10 10 10)",
+ 0.5,
+ 0.8,
+ "LINESTRING Z (5 5 5, 8 8 8)",
+ ),
Review Comment:
Can you add:
- Z, M, and ZM cases to ensure dimensions are propagated correctly? You will want to use different numbers for the coordinates to ensure the dimensions are not mixed up (e.g., `LINESTRING Z (0 10 20, 10 20 30)`)
- Cases that cover different values of start/end (for the same 2D linestring)
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::write_wkb_coord_trait;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
Review Comment:
This covers the case where these are constants but doesn't cover the case where you have Array input here. Typically we use something like `let start_frac_arrayref = args[1].cast_to(&DataType::Float64, None)?.to_array(executor.num_iterations());` and get an iterator over the float64s. I think the implementation for `st_pointn()` has an example you can draw from.
##########
Cargo.lock:
##########
Review Comment:
I am not sure these updates are needed but I'm happy to push that commit to fix this when we get that close to merging 🙂
##########
python/sedonadb/tests/functions/test_linesubstring.py:
##########
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from sedonadb.testing import geom_or_null, PostGIS, SedonaDB
+
+
+@pytest.mark.parametrize("eng", [SedonaDB, PostGIS])
+@pytest.mark.parametrize(
+ ("geom", "start", "end", "expected"),
+ [
+ (None, 0.0, 1.0, None),
Review Comment:
Can you add these cases to ensure proper null semantics?
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::write_wkb_coord_trait;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+
+ let (s_f, e_f) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ // 2. Build Header inside the 'line' scope (Fixes "cannot find dim/line")
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+ final_wkb.push(1u8); // Little Endian
+
+ if s_f == e_f {
+ // POINT Result (Fixes point vs line test)
+ let p_type: u32 = if dim == Dimensions::Xyz { 1001 } else { 1 };
+ final_wkb.extend_from_slice(&p_type.to_le_bytes());
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result (Fixes Z-coordinate drop)
+ let l_type: u32 = if dim == Dimensions::Xyz { 1002 } else { 2 };
+ final_wkb.extend_from_slice(&l_type.to_le_bytes());
+ final_wkb.extend_from_slice(&point_count.to_le_bytes());
+ final_wkb.extend_from_slice(&wkb_body);
+ }
Review Comment:
We have utilities in `sedona_geometry::wkb_factory` for this (e.g., write the linestring header or write the point header). This should propagate the dimensions of the input (this section special-cases Z but this could be M or ZM).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3250394265
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::write_wkb_coord_trait;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+
+ let (s_f, e_f) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ // 2. Build Header inside the 'line' scope (Fixes "cannot find dim/line")
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+ final_wkb.push(1u8); // Little Endian
+
+ if s_f == e_f {
+ // POINT Result (Fixes point vs line test)
+ let p_type: u32 = if dim == Dimensions::Xyz { 1001 } else { 1 };
+ final_wkb.extend_from_slice(&p_type.to_le_bytes());
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result (Fixes Z-coordinate drop)
+ let l_type: u32 = if dim == Dimensions::Xyz { 1002 } else { 2 };
+ final_wkb.extend_from_slice(&l_type.to_le_bytes());
+ final_wkb.extend_from_slice(&point_count.to_le_bytes());
+ final_wkb.extend_from_slice(&wkb_body);
+ }
Review Comment:
done!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3251084706
##########
Cargo.toml:
##########
@@ -92,6 +92,7 @@ datafusion-datasource-parquet = { version = "52.5.0" }
datafusion-execution = { version = "52.5.0", default-features = false }
datafusion-expr = { version = "52.5.0" }
datafusion-ffi = { version = "52.5.0" }
+datafusion-functions-nested = { version = "52.5.0" }
Review Comment:
for some reason I remember that was needed
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "paleolimbot (via GitHub)" <gi...@apache.org>.
paleolimbot commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3202392583
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_common::sedona_internal_err;
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+use crate::executor::WkbExecutor;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+ fn interpolate<C: CoordTrait<T = f64>>(p1: C, p2: C, fraction: f64) -> (f64, f64) {
+ let x = p1.x() + (p2.x() - p1.x()) * fraction;
+ let y = p1.y() + (p2.y() - p1.y()) * fraction;
+ (x, y)
+ }
+ executor.execute_wkb_void(|maybe_wkb| {
+ let mut new_coords = Vec::new();
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords() as i64;
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords as usize);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+ let (s_frac, e_frac) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+ for i in 0..(num_coords as usize - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+ let start_dist = s_frac * total_length;
+ let end_dist = e_frac * total_length;
+
+ for i in 0..(num_coords as usize - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ new_coords.push(interpolate(p1, p2, fraction));
Review Comment:
```suggestion
new_coords.push(interpolate(p1, p2, fraction, line.dim(), &mut coords)?);
```
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_common::sedona_internal_err;
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+use crate::executor::WkbExecutor;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+ fn interpolate<C: CoordTrait<T = f64>>(p1: C, p2: C, fraction: f64) -> (f64, f64) {
+ let x = p1.x() + (p2.x() - p1.x()) * fraction;
+ let y = p1.y() + (p2.y() - p1.y()) * fraction;
+ (x, y)
+ }
+ executor.execute_wkb_void(|maybe_wkb| {
+ let mut new_coords = Vec::new();
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords() as i64;
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords as usize);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+ let (s_frac, e_frac) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+ for i in 0..(num_coords as usize - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+ let start_dist = s_frac * total_length;
+ let end_dist = e_frac * total_length;
+
+ for i in 0..(num_coords as usize - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ new_coords.push(interpolate(p1, p2, fraction));
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ new_coords.push((p1.x(), p1.y()));
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ new_coords.push(interpolate(p1, p2, fraction));
+ }
+ }
+ }
+ }
+ if !new_coords.is_empty() {
+ // write byte order (1 = little endian)
+ builder.write_all(&[1u8])?;
+
+ let type_id: u32 = 2;
+ builder.write_all(&type_id.to_le_bytes())?;
+
+ let num_points = new_coords.len() as u32;
+ builder.write_all(&num_points.to_le_bytes())?;
+
+ for (x, y) in new_coords {
+ builder.write_all(&x.to_le_bytes())?;
+ builder.write_all(&y.to_le_bytes())?;
+ }
Review Comment:
You can use `write_wkb_linestring_header(&mut builder, line.dim(), num_output_coords)?` for this (there are other utility functions in sedona_geometry::wkb_factory that might be useful as well. I've added suggestions above about how to accumulate the coordinates as bytes directly in your implementation (which also simplifies supporting Z and M here).
##########
python/sedonadb/tests/functions/test_linesubstring.py:
##########
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from sedonadb.testing import geom_or_null, PostGIS, SedonaDB
+
+
+@pytest.mark.parametrize("eng", [SedonaDB, PostGIS])
+@pytest.mark.parametrize(
+ ("geom", "start", "end", "expected"),
+ [
+ (None, 0.0, 1.0, None),
Review Comment:
```suggestion
(None, 0.0, 1.0, None),
("LINESTRING EMPTY", None, 1.0, None),
("LINESTRING EMPTY, 0.0, None, None),
```
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_common::sedona_internal_err;
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+use crate::executor::WkbExecutor;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+ fn interpolate<C: CoordTrait<T = f64>>(p1: C, p2: C, fraction: f64) -> (f64, f64) {
+ let x = p1.x() + (p2.x() - p1.x()) * fraction;
+ let y = p1.y() + (p2.y() - p1.y()) * fraction;
+ (x, y)
+ }
Review Comment:
You have a test for Z that's failing because this doesn't handle anything except XY coordinates. I think it should be relatively easy to modify this to work with any dimension...maybe:
```rust
fn interpolate<C: CoordTrait<T = f64>>(p1: C, p2: C, fraction: f64, dim: Dimensions, buf: &mut impl Write) -> Result<(), SedonaGeometryError> {
for i in 0..dim.size() {
let v = p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
buf.write_all(v.to_le_bytes())?;
}
Ok(())
}
```
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,164 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_common::sedona_internal_err;
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+use crate::executor::WkbExecutor;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
+ fn interpolate<C: CoordTrait<T = f64>>(p1: C, p2: C, fraction: f64) -> (f64, f64) {
+ let x = p1.x() + (p2.x() - p1.x()) * fraction;
+ let y = p1.y() + (p2.y() - p1.y()) * fraction;
+ (x, y)
+ }
+ executor.execute_wkb_void(|maybe_wkb| {
+ let mut new_coords = Vec::new();
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords() as i64;
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords as usize);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+ let (s_frac, e_frac) = match (start_frac, end_frac) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+ for i in 0..(num_coords as usize - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+ let start_dist = s_frac * total_length;
+ let end_dist = e_frac * total_length;
+
+ for i in 0..(num_coords as usize - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ new_coords.push(interpolate(p1, p2, fraction));
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ new_coords.push((p1.x(), p1.y()));
Review Comment:
```suggestion
write_wkb_coord_trait(p1, &mut coords)?;
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on PR #777:
URL: https://github.com/apache/sedona-db/pull/777#issuecomment-4420166895
@paleolimbot I think now all the tests passes and CI is mostly ok too aside from a datafusion dep versioning prob, can you check it and merge this?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3269637547
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,296 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+use arrow_array::Array;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+ let end_array = args[2].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+ let end_floats = end_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+ let mut row_idx = 0;
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+ // Fetch the unique start/end fraction values for THIS specific row
+ let s_f_opt = if start_floats.is_null(row_idx) { None } else { Some(start_floats.value(row_idx)) };
+ let e_f_opt = if end_floats.is_null(row_idx) { None } else { Some(end_floats.value(row_idx)) };
+ row_idx += 1; // Increment for the next iteration
+
+ let (s_f, e_f) = match (s_f_opt, e_f_opt) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+
+ if s_f == e_f {
+ // POINT Result
+ write_wkb_point_header(&mut final_wkb, dim)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result
+ write_wkb_linestring_header(&mut final_wkb, dim, point_count as usize)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+#[cfg(test)]
+mod tests {
+ use arrow_array::{ArrayRef, Float64Array};
+ use arrow_schema::DataType;
+ use datafusion_common::scalar::ScalarValue;
+ use datafusion_expr::{ColumnarValue, ScalarUDF};
+ use rstest::rstest;
+ use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS};
+ use sedona_testing::{
+ compare::{assert_array_equal, assert_scalar_equal},
+ testers::ScalarUdfTester,
+ };
+ use std::sync::Arc;
+
+ use super::*;
+
+ #[test]
+ fn udf_metadata() {
+ let udf: ScalarUDF = st_line_substring_udf().into();
+ assert_eq!(udf.name(), "st_linesubstring");
+ }
+
+ #[rstest]
+ fn udf(
+ #[values(WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS.clone())]
+ sedona_type: SedonaType,
+ ) {
+ let udf = st_line_substring_udf();
+ let tester = ScalarUdfTester::new(
+ udf.into(),
+ vec![
+ sedona_type,
+ SedonaType::Arrow(DataType::Float64),
+ SedonaType::Arrow(DataType::Float64)
+ ]
+ );
+
+ let actual_2d = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.0, 0.5).unwrap();
+ let expected_2d = tester.invoke_wkb_scalar(Some("LINESTRING(0 0, 5 5)")).unwrap();
+ assert_scalar_equal(&actual_2d, &expected_2d);
+
+
+ let actual_z = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 10 20, 10 20 30)", 0.5, 1.0).unwrap();
+ let expected_z = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 15 25, 10 20 30)")).unwrap();
+ assert_scalar_equal(&actual_z, &expected_z);
+
+ let actual_mid = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 0 0, 10 10 10)", 0.5, 0.8).unwrap();
+ let expected_mid = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 5 5, 8 8 8)")).unwrap();
+ assert_scalar_equal(&actual_mid, &expected_mid);
+
+
+ let actual_point = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.5, 0.5).unwrap();
+ let expected_point = tester.invoke_wkb_scalar(Some("POINT(5 5)")).unwrap();
+ assert_scalar_equal(&actual_point, &expected_point);
+
+
+ let geoms_input = tester.invoke_wkb_array(vec![
+ Some("LINESTRING(0 0, 10 10)"),
+ None,
+ Some("LINESTRING(0 0, 10 10)")
+ ]).unwrap();
Review Comment:
it cannot find create_array in the scope...
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] ST_LineSubstring function [sedona-db]
Posted by "paleolimbot (via GitHub)" <gi...@apache.org>.
paleolimbot commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3313046957
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,341 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_array::Array;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, exec_datafusion_err, exec_err};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{
+ write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header,
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+ let end_array = args[2]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+ let end_floats = end_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+
+ let mut start_floats_iter = start_floats.iter();
+ let mut end_floats_iter = end_floats.iter();
+
+ let mut wkb_body = Vec::new();
+ let mut cumulative_distances = Vec::new();
+
+ executor.execute_wkb_void(|maybe_wkb| {
+ let (Some(wkb), Some(s_f), Some(e_f)) = (
+ maybe_wkb,
+ start_floats_iter.next().unwrap(),
+ end_floats_iter.next().unwrap(),
+ ) else {
+ builder.append_null();
+ return Ok(());
+ };
Review Comment:
I moved these into an iterator + early return for any null, which helps reduce nesting in the implementation
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,341 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_array::Array;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, exec_datafusion_err, exec_err};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{
+ write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header,
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+ let end_array = args[2]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+ let end_floats = end_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+
+ let mut start_floats_iter = start_floats.iter();
+ let mut end_floats_iter = end_floats.iter();
+
+ let mut wkb_body = Vec::new();
+ let mut cumulative_distances = Vec::new();
+
+ executor.execute_wkb_void(|maybe_wkb| {
+ let (Some(wkb), Some(s_f), Some(e_f)) = (
+ maybe_wkb,
+ start_floats_iter.next().unwrap(),
+ end_floats_iter.next().unwrap(),
+ ) else {
+ builder.append_null();
+ return Ok(());
+ };
+
+ let GeometryType::LineString(line) = wkb.as_type() else {
+ return exec_err!("Can't compute substring of non-line input");
+ };
+
+ if !(0.0..=1.0).contains(&s_f) {
+ return exec_err!("start_fraction must be between 0.0 and 1.0 (got {s_f}");
+ }
+
+ if !(0.0..=1.0).contains(&e_f) {
+ return exec_err!("end_fraction must be between 0.0 and 1.0 (got {e_f}");
+ }
+
+ if e_f < s_f {
+ return exec_err!(
+ "end_fraction must be greater than start_fraction (got {e_f} < {s_f})"
+ );
+ }
+
+ invoke_scalar(
+ line,
+ s_f,
+ e_f,
+ &mut wkb_body,
+ &mut cumulative_distances,
+ &mut builder,
+ )
+ .map_err(|e| exec_datafusion_err!("Error executing st_linesubstring: {e}"))?;
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+
+fn invoke_scalar(
+ line: &impl LineStringTrait<T = f64>,
+ s_f: f64,
+ e_f: f64,
+ wkb_body: &mut Vec<u8>,
+ cumulative_distances: &mut Vec<f64>,
+ builder: &mut BinaryBuilder,
+) -> Result<(), SedonaGeometryError> {
Review Comment:
I moved this into a standalone function
##########
python/sedonadb/tests/functions/test_linesubstring.py:
##########
@@ -0,0 +1,122 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from sedonadb.testing import PostGIS, SedonaDB, geom_or_null, val_or_null
+
+
+@pytest.mark.parametrize("eng", [SedonaDB, PostGIS])
+@pytest.mark.parametrize(
+ ("geom", "start", "end", "expected"),
+ [
+ ("LINESTRING EMPTY", 0.0, 1.0, None),
Review Comment:
I added a set of multi-segment cases here that surfaced an error where the endpoint was duplicated in some cases if the proportion landed exactly on a vertex! I also ran these against PostGIS and had to update a few semantics (e.g., EMPTY output is always NULL here)
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,341 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_array::Array;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, exec_datafusion_err, exec_err};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{
+ write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header,
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+ let end_array = args[2]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+ let end_floats = end_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+
+ let mut start_floats_iter = start_floats.iter();
+ let mut end_floats_iter = end_floats.iter();
+
+ let mut wkb_body = Vec::new();
+ let mut cumulative_distances = Vec::new();
Review Comment:
I moved these outside the loop so that the scratch space isn't reallocated for every element (cleared + reused).
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,341 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_array::Array;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, exec_datafusion_err, exec_err};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{
+ write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header,
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+ let end_array = args[2]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+ let end_floats = end_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+
+ let mut start_floats_iter = start_floats.iter();
+ let mut end_floats_iter = end_floats.iter();
+
+ let mut wkb_body = Vec::new();
+ let mut cumulative_distances = Vec::new();
+
+ executor.execute_wkb_void(|maybe_wkb| {
+ let (Some(wkb), Some(s_f), Some(e_f)) = (
+ maybe_wkb,
+ start_floats_iter.next().unwrap(),
+ end_floats_iter.next().unwrap(),
+ ) else {
+ builder.append_null();
+ return Ok(());
+ };
+
+ let GeometryType::LineString(line) = wkb.as_type() else {
+ return exec_err!("Can't compute substring of non-line input");
+ };
+
+ if !(0.0..=1.0).contains(&s_f) {
+ return exec_err!("start_fraction must be between 0.0 and 1.0 (got {s_f}");
+ }
+
+ if !(0.0..=1.0).contains(&e_f) {
+ return exec_err!("end_fraction must be between 0.0 and 1.0 (got {e_f}");
+ }
+
+ if e_f < s_f {
+ return exec_err!(
+ "end_fraction must be greater than start_fraction (got {e_f} < {s_f})"
+ );
+ }
+
+ invoke_scalar(
+ line,
+ s_f,
+ e_f,
+ &mut wkb_body,
+ &mut cumulative_distances,
+ &mut builder,
+ )
+ .map_err(|e| exec_datafusion_err!("Error executing st_linesubstring: {e}"))?;
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+
+fn invoke_scalar(
+ line: &impl LineStringTrait<T = f64>,
+ s_f: f64,
+ e_f: f64,
+ wkb_body: &mut Vec<u8>,
+ cumulative_distances: &mut Vec<f64>,
+ builder: &mut BinaryBuilder,
+) -> Result<(), SedonaGeometryError> {
+ let mut point_count = 0u32;
+ wkb_body.clear();
+ cumulative_distances.clear();
+
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ // Empty or degenerate input returns NULL here
+ if num_coords <= 1 {
+ builder.append_null();
+ return Ok(());
+ }
+
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let (p1, p2) = unsafe { (line.coord_unchecked(i), line.coord_unchecked(i + 1)) };
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ // Use binary search to find the segment range to scan.
+ // For start: find the segment containing start_dist (where d[i] <= start_dist < d[i+1])
+ // For end: find the segment containing end_dist (where d[i] < end_dist <= d[i+1])
+ let num_segments = num_coords - 1;
+
+ let first_segment = cumulative_distances
+ .partition_point(|&d| d <= start_dist)
+ .saturating_sub(1)
+ .min(num_segments - 1);
+
+ let last_segment = cumulative_distances
+ .partition_point(|&d| d < end_dist)
+ .saturating_sub(1)
+ .min(num_segments - 1);
Review Comment:
I added this to limit the subset of the linestring that is considered in the loop (the previous version looped through all vertices, even if only a tiny portion of the linestring is actually needed).
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,341 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_array::Array;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, exec_datafusion_err, exec_err};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{
+ write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header,
+};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+ let end_array = args[2]
+ .cast_to(&DataType::Float64, None)?
+ .to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+ let end_floats = end_array
+ .as_any()
+ .downcast_ref::<arrow_array::Float64Array>()
+ .unwrap();
+
+ let mut start_floats_iter = start_floats.iter();
+ let mut end_floats_iter = end_floats.iter();
+
+ let mut wkb_body = Vec::new();
+ let mut cumulative_distances = Vec::new();
+
+ executor.execute_wkb_void(|maybe_wkb| {
+ let (Some(wkb), Some(s_f), Some(e_f)) = (
+ maybe_wkb,
+ start_floats_iter.next().unwrap(),
+ end_floats_iter.next().unwrap(),
+ ) else {
+ builder.append_null();
+ return Ok(());
+ };
+
+ let GeometryType::LineString(line) = wkb.as_type() else {
+ return exec_err!("Can't compute substring of non-line input");
+ };
+
+ if !(0.0..=1.0).contains(&s_f) {
+ return exec_err!("start_fraction must be between 0.0 and 1.0 (got {s_f}");
+ }
+
+ if !(0.0..=1.0).contains(&e_f) {
+ return exec_err!("end_fraction must be between 0.0 and 1.0 (got {e_f}");
+ }
+
+ if e_f < s_f {
+ return exec_err!(
+ "end_fraction must be greater than start_fraction (got {e_f} < {s_f})"
+ );
+ }
Review Comment:
PostGIS seems to error for all of these cases. We can relax this at some point if there's a good reason to.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "paleolimbot (via GitHub)" <gi...@apache.org>.
paleolimbot commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3262372973
##########
docs/reference/sql/st_linesubstring.qmd:
##########
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+---
+title: ST_LineSubstring
+description: Returns a linestring being a substring of the input one starting and ending at the given fractions of total 2d length. Second and third arguments are double precision values between 0 and 1.
Review Comment:
```suggestion
description: Returns a linestring being a substring of the input one starting and ending at the given fractions of total 2d length.
```
##########
docs/reference/sql/st_linesubstring.qmd:
##########
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+---
+title: ST_LineSubstring
+description: Returns a linestring being a substring of the input one starting and ending at the given fractions of total 2d length. Second and third arguments are double precision values between 0 and 1.
+kernels:
+ - returns: geometry
+ args:
+ - geometry: geometry
+ - start_fraction: double
+ - end_fraction: double
Review Comment:
```suggestion
args:
- geometry
- name: start_fraction
type: double
description: The fraction from which the returned line will start (between 0 and 1)
- name: end_fraction
type: double
description: The fraction from which the returned line will end (between 0 and 1)
```
##########
python/sedonadb/tests/functions/test_linesubstring.py:
##########
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from sedonadb.testing import geom_or_null, PostGIS, SedonaDB
+
+
+@pytest.mark.parametrize("eng", [SedonaDB, PostGIS])
+@pytest.mark.parametrize(
+ ("geom", "start", "end", "expected"),
+ [
+ (None, 0.0, 1.0, None),
+ ("LINESTRING(0 0, 10 10)", None, 1.0, None), # Start fraction is NULL -> Output is NULL
+ ("LINESTRING(0 0, 10 10)", 0.0, None, None), # End fraction is NULL -> Output is NULL
+ (None, None, None, None),
+ ("LINESTRING (0 0, 10 0)", 0.2, 0.8, "LINESTRING (2 0, 8 0)"),
Review Comment:
Can you add a case for LINESTRING EMPTY input?
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,296 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+use arrow_array::Array;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+ let end_array = args[2].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+ let end_floats = end_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+ let mut row_idx = 0;
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+ // Fetch the unique start/end fraction values for THIS specific row
+ let s_f_opt = if start_floats.is_null(row_idx) { None } else { Some(start_floats.value(row_idx)) };
+ let e_f_opt = if end_floats.is_null(row_idx) { None } else { Some(end_floats.value(row_idx)) };
+ row_idx += 1; // Increment for the next iteration
+
+ let (s_f, e_f) = match (s_f_opt, e_f_opt) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+
+ if s_f == e_f {
+ // POINT Result
+ write_wkb_point_header(&mut final_wkb, dim)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result
+ write_wkb_linestring_header(&mut final_wkb, dim, point_count as usize)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+#[cfg(test)]
+mod tests {
+ use arrow_array::{ArrayRef, Float64Array};
+ use arrow_schema::DataType;
+ use datafusion_common::scalar::ScalarValue;
+ use datafusion_expr::{ColumnarValue, ScalarUDF};
+ use rstest::rstest;
+ use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS};
+ use sedona_testing::{
+ compare::{assert_array_equal, assert_scalar_equal},
+ testers::ScalarUdfTester,
+ };
+ use std::sync::Arc;
+
+ use super::*;
+
+ #[test]
+ fn udf_metadata() {
+ let udf: ScalarUDF = st_line_substring_udf().into();
+ assert_eq!(udf.name(), "st_linesubstring");
+ }
+
+ #[rstest]
+ fn udf(
+ #[values(WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS.clone())]
+ sedona_type: SedonaType,
+ ) {
+ let udf = st_line_substring_udf();
+ let tester = ScalarUdfTester::new(
+ udf.into(),
+ vec![
+ sedona_type,
+ SedonaType::Arrow(DataType::Float64),
+ SedonaType::Arrow(DataType::Float64)
+ ]
+ );
+
+ let actual_2d = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.0, 0.5).unwrap();
+ let expected_2d = tester.invoke_wkb_scalar(Some("LINESTRING(0 0, 5 5)")).unwrap();
+ assert_scalar_equal(&actual_2d, &expected_2d);
+
+
+ let actual_z = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 10 20, 10 20 30)", 0.5, 1.0).unwrap();
+ let expected_z = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 15 25, 10 20 30)")).unwrap();
+ assert_scalar_equal(&actual_z, &expected_z);
+
+ let actual_mid = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 0 0, 10 10 10)", 0.5, 0.8).unwrap();
+ let expected_mid = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 5 5, 8 8 8)")).unwrap();
+ assert_scalar_equal(&actual_mid, &expected_mid);
+
+
+ let actual_point = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.5, 0.5).unwrap();
+ let expected_point = tester.invoke_wkb_scalar(Some("POINT(5 5)")).unwrap();
+ assert_scalar_equal(&actual_point, &expected_point);
+
+
+ let geoms_input = tester.invoke_wkb_array(vec![
+ Some("LINESTRING(0 0, 10 10)"),
+ None,
+ Some("LINESTRING(0 0, 10 10)")
+ ]).unwrap();
Review Comment:
I think this may have been what you meant (I'm pretty sure our testing function is called `create_array()` here):
```suggestion
let geoms_input = create_array(&[
Some("LINESTRING(0 0, 10 10)"),
None,
Some("LINESTRING(0 0, 10 10)")
]);
```
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,296 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::{write_wkb_coord_trait, write_wkb_linestring_header, write_wkb_point_header};
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+use arrow_array::Array;
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ // 1. Convert parameters into clean abstract arrays
+ let batch_rows = executor.num_iterations();
+ let start_array = args[1].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+ let end_array = args[2].cast_to(&DataType::Float64, None)?.to_array(batch_rows)?;
+
+ // 2. Downcast them to Arrow Float64Arrays so they can be read by row index
+ let start_floats = start_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+ let end_floats = end_array.as_any().downcast_ref::<arrow_array::Float64Array>().unwrap();
+
+ unsafe fn interpolate<C: CoordTrait<T = f64>>(
+ p1: C,
+ p2: C,
+ fraction: f64,
+ dim: Dimensions,
+ buf: &mut impl Write,
+ ) -> Result<(), SedonaGeometryError> {
+ for i in 0..dim.size() {
+ let v =
+ p1.nth_unchecked(i) + (p2.nth_unchecked(i) - p1.nth_unchecked(i)) * fraction;
+ buf.write_all(&v.to_le_bytes())?;
+ }
+ Ok(())
+ }
+ let mut row_idx = 0;
+ executor.execute_wkb_void(|maybe_wkb| unsafe {
+ let mut wkb_body = Vec::new();
+ let mut point_count = 0u32;
+ // Fetch the unique start/end fraction values for THIS specific row
+ let s_f_opt = if start_floats.is_null(row_idx) { None } else { Some(start_floats.value(row_idx)) };
+ let e_f_opt = if end_floats.is_null(row_idx) { None } else { Some(end_floats.value(row_idx)) };
+ row_idx += 1; // Increment for the next iteration
+
+ let (s_f, e_f) = match (s_f_opt, e_f_opt) {
+ (Some(s), Some(e)) => (s, e),
+ _ => {
+ builder.append_null();
+ return Ok(());
+ }
+ };
+
+ if let Some(wkb) = maybe_wkb {
+ if let GeometryType::LineString(line) = wkb.as_type() {
+ let num_coords = line.num_coords();
+ let dim = line.dim();
+
+ let mut cumulative_distances = Vec::with_capacity(num_coords);
+ let mut total_length = 0.0;
+ cumulative_distances.push(0.0);
+
+ for i in 0..(num_coords - 1) {
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+ let dist = ((p2.x() - p1.x()).powi(2) + (p2.y() - p1.y()).powi(2)).sqrt();
+ total_length += dist;
+ cumulative_distances.push(total_length);
+ }
+
+ let start_dist = s_f * total_length;
+ let end_dist = e_f * total_length;
+
+ for i in 0..(num_coords - 1) {
+ let d1 = cumulative_distances[i];
+ let d2 = cumulative_distances[i + 1];
+ let p1 = line.coord(i).unwrap();
+ let p2 = line.coord(i + 1).unwrap();
+
+ if start_dist >= d1 && start_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (start_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+
+ if d1 > start_dist && d1 < end_dist {
+ write_wkb_coord_trait(&mut wkb_body, &p1).map_err(|e| {
+ DataFusionError::Internal(format!("WKB write failed: {}", e))
+ })?;
+ point_count += 1;
+ }
+
+ if end_dist >= d1 && end_dist <= d2 {
+ let segment_len = d2 - d1;
+ let fraction = if segment_len > 0.0 {
+ (end_dist - d1) / segment_len
+ } else {
+ 0.0
+ };
+ interpolate(p1, p2, fraction, dim, &mut wkb_body).map_err(|e| {
+ DataFusionError::Internal(format!(
+ "Sedona interpolation failed: {}",
+ e
+ ))
+ })?;
+ point_count += 1;
+ }
+ }
+
+ if point_count > 0 {
+ let mut final_wkb = Vec::new();
+
+ if s_f == e_f {
+ // POINT Result
+ write_wkb_point_header(&mut final_wkb, dim)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+ let coord_bytes = dim.size() * 8;
+ if wkb_body.len() >= coord_bytes {
+ final_wkb.extend_from_slice(&wkb_body[..coord_bytes]);
+ }
+ } else {
+ // LINESTRING Result
+ write_wkb_linestring_header(&mut final_wkb, dim, point_count as usize)
+ .map_err(|e| DataFusionError::Internal(e.to_string()))?;
+
+ final_wkb.extend_from_slice(&wkb_body);
+ }
+ builder.append_value(final_wkb);
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+ } else {
+ builder.append_null();
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+#[cfg(test)]
+mod tests {
+ use arrow_array::{ArrayRef, Float64Array};
+ use arrow_schema::DataType;
+ use datafusion_common::scalar::ScalarValue;
+ use datafusion_expr::{ColumnarValue, ScalarUDF};
+ use rstest::rstest;
+ use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS};
+ use sedona_testing::{
+ compare::{assert_array_equal, assert_scalar_equal},
+ testers::ScalarUdfTester,
+ };
+ use std::sync::Arc;
+
+ use super::*;
+
+ #[test]
+ fn udf_metadata() {
+ let udf: ScalarUDF = st_line_substring_udf().into();
+ assert_eq!(udf.name(), "st_linesubstring");
+ }
+
+ #[rstest]
+ fn udf(
+ #[values(WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS.clone())]
+ sedona_type: SedonaType,
+ ) {
+ let udf = st_line_substring_udf();
+ let tester = ScalarUdfTester::new(
+ udf.into(),
+ vec![
+ sedona_type,
+ SedonaType::Arrow(DataType::Float64),
+ SedonaType::Arrow(DataType::Float64)
+ ]
+ );
+
+ let actual_2d = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.0, 0.5).unwrap();
+ let expected_2d = tester.invoke_wkb_scalar(Some("LINESTRING(0 0, 5 5)")).unwrap();
+ assert_scalar_equal(&actual_2d, &expected_2d);
+
+
+ let actual_z = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 10 20, 10 20 30)", 0.5, 1.0).unwrap();
+ let expected_z = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 15 25, 10 20 30)")).unwrap();
+ assert_scalar_equal(&actual_z, &expected_z);
+
+ let actual_mid = tester.invoke_scalar_scalar_scalar("LINESTRING Z (0 0 0, 10 10 10)", 0.5, 0.8).unwrap();
+ let expected_mid = tester.invoke_wkb_scalar(Some("LINESTRING Z (5 5 5, 8 8 8)")).unwrap();
+ assert_scalar_equal(&actual_mid, &expected_mid);
+
+
+ let actual_point = tester.invoke_scalar_scalar_scalar("LINESTRING(0 0, 10 10)", 0.5, 0.5).unwrap();
+ let expected_point = tester.invoke_wkb_scalar(Some("POINT(5 5)")).unwrap();
+ assert_scalar_equal(&actual_point, &expected_point);
+
+
+ let geoms_input = tester.invoke_wkb_array(vec![
+ Some("LINESTRING(0 0, 10 10)"),
+ None,
+ Some("LINESTRING(0 0, 10 10)")
+ ]).unwrap();
+
+ let starts_input: ArrayRef = Arc::new(Float64Array::from(vec![Some(0.0), Some(0.0), Some(0.5)]));
+ let ends_input: ArrayRef = Arc::new(Float64Array::from(vec![Some(0.5), Some(1.0), Some(0.5)]));
+
+ let expected_array = tester.invoke_wkb_array(vec![
+ Some("LINESTRING(0 0, 5 5)"),
+ None,
+ Some("POINT(5 5)")
+ ]).unwrap();
+
+ let actual_array = match tester.invoke(vec![
+ ColumnarValue::Array(geoms_input),
+ ColumnarValue::Array(starts_input),
+ ColumnarValue::Array(ends_input)
+ ]).unwrap() {
+ ColumnarValue::Array(arr) => arr,
+ _ => panic!("Expected array block context output"),
+ };
Review Comment:
I think this works:
```suggestion
let actual_array = tester.invoke_arrays(vec![geoms_input, starts_input, ends_input]).unwrap();
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] st_linesubstring func [sedona-db]
Posted by "sapienza88 (via GitHub)" <gi...@apache.org>.
sapienza88 commented on code in PR #777:
URL: https://github.com/apache/sedona-db/pull/777#discussion_r3249463241
##########
rust/sedona-functions/src/st_linesubstring.rs:
##########
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use crate::executor::WkbExecutor;
+use arrow_array::builder::BinaryBuilder;
+use arrow_schema::DataType;
+use datafusion_common::{error::Result, DataFusionError, ScalarValue};
+use datafusion_expr::{ColumnarValue, Volatility};
+use geo_traits::{CoordTrait, Dimensions, GeometryTrait, GeometryType, LineStringTrait};
+use sedona_expr::{
+ item_crs::ItemCrsKernel,
+ scalar_udf::{SedonaScalarKernel, SedonaScalarUDF},
+};
+use sedona_geometry::error::SedonaGeometryError;
+use sedona_geometry::wkb_factory::write_wkb_coord_trait;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+use std::{io::Write, sync::Arc};
+
+#[derive(Debug)]
+struct STLineSubstring;
+pub fn st_line_substring_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "st_linesubstring",
+ ItemCrsKernel::wrap_impl(vec![Arc::new(STLineSubstring)]),
+ Volatility::Immutable,
+ )
+}
+
+impl SedonaScalarKernel for STLineSubstring {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_geometry(),
+ ArgMatcher::is_numeric(),
+ ArgMatcher::is_numeric(),
+ ],
+ WKB_GEOMETRY,
+ );
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = WkbExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::new();
+
+ let start_frac: Option<f64> = match &args[1].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(s)) => *s,
+ _ => None,
+ };
+
+ let end_frac: Option<f64> = match &args[2].cast_to(&DataType::Float64, None)? {
+ ColumnarValue::Scalar(ScalarValue::Float64(e)) => *e,
+ _ => None,
+ };
Review Comment:
ok but we could always update the function later as it does seem to me it is an essential feature to handle arrays as the input.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@sedona.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org