This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sedona-db.git


The following commit(s) were added to refs/heads/main by this push:
     new 5d3cfba  Add ST_SRID (#31)
5d3cfba is described below

commit 5d3cfba252ffdb0d1a8ac1fd2126fb829ca459df
Author: jp <[email protected]>
AuthorDate: Fri Sep 5 14:25:11 2025 -0700

    Add ST_SRID (#31)
---
 rust/sedona-functions/src/lib.rs      |   1 +
 rust/sedona-functions/src/register.rs |   1 +
 rust/sedona-functions/src/st_srid.rs  | 155 ++++++++++++++++++++++++++++++++++
 rust/sedona-schema/src/crs.rs         |  41 ++++++++-
 4 files changed, 194 insertions(+), 4 deletions(-)

diff --git a/rust/sedona-functions/src/lib.rs b/rust/sedona-functions/src/lib.rs
index c8d95db..1041fb1 100644
--- a/rust/sedona-functions/src/lib.rs
+++ b/rust/sedona-functions/src/lib.rs
@@ -43,6 +43,7 @@ mod st_perimeter;
 mod st_point;
 mod st_pointzm;
 mod st_setsrid;
+mod st_srid;
 mod st_transform;
 pub mod st_union_aggr;
 mod st_xyzm;
diff --git a/rust/sedona-functions/src/register.rs 
b/rust/sedona-functions/src/register.rs
index 3f7f931..b5208ac 100644
--- a/rust/sedona-functions/src/register.rs
+++ b/rust/sedona-functions/src/register.rs
@@ -86,6 +86,7 @@ pub fn default_function_set() -> FunctionSet {
         crate::st_pointzm::st_pointzm_udf,
         crate::st_transform::st_transform_udf,
         crate::st_setsrid::st_set_srid_udf,
+        crate::st_srid::st_srid_udf,
         crate::st_xyzm::st_m_udf,
         crate::st_xyzm::st_x_udf,
         crate::st_xyzm::st_y_udf,
diff --git a/rust/sedona-functions/src/st_srid.rs 
b/rust/sedona-functions/src/st_srid.rs
new file mode 100644
index 0000000..56dfedb
--- /dev/null
+++ b/rust/sedona-functions/src/st_srid.rs
@@ -0,0 +1,155 @@
+// 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::UInt32Builder;
+use std::{sync::Arc, vec};
+
+use crate::executor::WkbExecutor;
+use arrow_schema::DataType;
+use datafusion_common::{DataFusionError, Result};
+use datafusion_expr::{
+    scalar_doc_sections::DOC_SECTION_OTHER, ColumnarValue, Documentation, 
Volatility,
+};
+use sedona_expr::scalar_udf::{ArgMatcher, SedonaScalarKernel, SedonaScalarUDF};
+use sedona_schema::datatypes::SedonaType;
+
+/// ST_Srid() scalar UDF implementation
+///
+/// Scalar function to return the SRID of a geometry or geography
+pub fn st_srid_udf() -> SedonaScalarUDF {
+    SedonaScalarUDF::new(
+        "st_srid",
+        vec![Arc::new(StSrid {})],
+        Volatility::Immutable,
+        Some(st_srid_doc()),
+    )
+}
+
+fn st_srid_doc() -> Documentation {
+    Documentation::builder(
+        DOC_SECTION_OTHER,
+        "Return the spatial reference system identifier (SRID) of the 
geometry.",
+        "ST_SRID (geom: Geometry)",
+    )
+    .with_argument("geom", "geometry: Input geometry or geography")
+    .with_sql_example("SELECT ST_SRID(polygon)".to_string())
+    .build()
+}
+
+#[derive(Debug)]
+struct StSrid {}
+
+impl SedonaScalarKernel for StSrid {
+    fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+        let matcher = ArgMatcher::new(
+            vec![ArgMatcher::is_geometry_or_geography()],
+            SedonaType::Arrow(DataType::UInt32),
+        );
+
+        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 = 
UInt32Builder::with_capacity(executor.num_iterations());
+        let srid_opt = match &arg_types[0] {
+            SedonaType::Wkb(_, Some(crs)) | SedonaType::WkbView(_, Some(crs)) 
=> {
+                match crs.srid()? {
+                    Some(srid) => Some(srid),
+                    None => return Err(DataFusionError::Execution("CRS has no 
SRID".to_string())),
+                }
+            }
+            _ => Some(0),
+        };
+
+        executor.execute_wkb_void(|maybe_wkb| {
+            match maybe_wkb {
+                Some(_wkb) => {
+                    builder.append_option(srid_opt);
+                }
+                _ => builder.append_null(),
+            }
+
+            Ok(())
+        })?;
+
+        executor.finish(Arc::new(builder.finish()))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use datafusion_common::ScalarValue;
+    use datafusion_expr::ScalarUDF;
+    use sedona_schema::crs::deserialize_crs;
+    use sedona_schema::datatypes::Edges;
+    use sedona_testing::testers::ScalarUdfTester;
+    use std::str::FromStr;
+
+    use super::*;
+
+    #[test]
+    fn udf_metadata() {
+        let udf: ScalarUDF = st_srid_udf().into();
+        assert_eq!(udf.name(), "st_srid");
+        assert!(udf.documentation().is_some())
+    }
+
+    #[test]
+    fn udf() {
+        let udf: ScalarUDF = st_srid_udf().into();
+
+        // Test that when no CRS is set, SRID is 0
+        let sedona_type = SedonaType::Wkb(Edges::Planar, None);
+        let tester = ScalarUdfTester::new(udf.clone(), vec![sedona_type]);
+        tester.assert_return_type(DataType::UInt32);
+        let result = tester
+            .invoke_scalar("POLYGON ((0 0, 1 0, 0 1, 0 0))")
+            .unwrap();
+        tester.assert_scalar_result_equals(result, 0_u32);
+
+        // Test that NULL input returns NULL output
+        let result = tester.invoke_scalar(ScalarValue::Null).unwrap();
+        tester.assert_scalar_result_equals(result, ScalarValue::Null);
+
+        // Test with a CRS with an EPSG code
+        let crs_value = serde_json::Value::String("EPSG:4837".to_string());
+        let crs = deserialize_crs(&crs_value).unwrap();
+        let sedona_type = SedonaType::Wkb(Edges::Planar, crs.clone());
+        let tester = ScalarUdfTester::new(udf.clone(), vec![sedona_type]);
+        let result = tester
+            .invoke_scalar("POLYGON ((0 0, 1 0, 0 1, 0 0))")
+            .unwrap();
+        tester.assert_scalar_result_equals(result, 4837_u32);
+
+        // Test with a CRS but null geom
+        let result = tester.invoke_scalar(ScalarValue::Null).unwrap();
+        tester.assert_scalar_result_equals(result, ScalarValue::Null);
+
+        // Call with a CRS with no SRID (should error)
+        let crs_value = serde_json::Value::from_str("{}");
+        let crs = deserialize_crs(&crs_value.unwrap()).unwrap();
+        let sedona_type = SedonaType::Wkb(Edges::Planar, crs.clone());
+        let tester = ScalarUdfTester::new(udf.clone(), vec![sedona_type]);
+        let result = tester.invoke_scalar("POINT (0 1)");
+        assert!(result.is_err());
+        assert!(result.unwrap_err().to_string().contains("CRS has no SRID"));
+    }
+}
diff --git a/rust/sedona-schema/src/crs.rs b/rust/sedona-schema/src/crs.rs
index 80e0884..5c81cb1 100644
--- a/rust/sedona-schema/src/crs.rs
+++ b/rust/sedona-schema/src/crs.rs
@@ -88,6 +88,7 @@ pub trait CoordinateReferenceSystem: Debug {
     fn to_json(&self) -> String;
     fn to_authority_code(&self) -> Result<Option<String>>;
     fn crs_equals(&self, other: &dyn CoordinateReferenceSystem) -> bool;
+    fn srid(&self) -> Result<Option<u32>>;
 }
 
 /// Concrete implementation of a default longitude/latitude coordinate 
reference system
@@ -207,6 +208,15 @@ impl CoordinateReferenceSystem for AuthorityCode {
             (_, _) => false,
         }
     }
+
+    /// Get the SRID if authority is EPSG
+    fn srid(&self) -> Result<Option<u32>> {
+        if self.authority.eq_ignore_ascii_case("EPSG") {
+            Ok(self.code.parse::<u32>().ok())
+        } else {
+            Ok(None)
+        }
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -272,6 +282,20 @@ impl CoordinateReferenceSystem for ProjJSON {
             false
         }
     }
+
+    fn srid(&self) -> Result<Option<u32>> {
+        let authority_code_opt = self.to_authority_code()?;
+        if let Some(authority_code) = authority_code_opt {
+            if LngLat::is_authority_code_lnglat(&authority_code) {
+                return Ok(Some(4326));
+            }
+            if let Some((_, code)) = 
AuthorityCode::split_auth_code(&authority_code) {
+                return Ok(code.parse::<u32>().ok());
+            }
+        }
+
+        Ok(None)
+    }
 }
 
 pub const OGC_CRS84_PROJJSON: &str = 
r#"{"$schema":"https://proj.org/schemas/v0.7/projjson.schema.json","type":"GeographicCRS","name":"WGS
 84 (CRS84)","datum_ensemble":{"name":"World Geodetic System 1984 
ensemble","members":[{"name":"World Geodetic System 1984 
(Transit)","id":{"authority":"EPSG","code":1166}},{"name":"World Geodetic 
System 1984 (G730)","id":{"authority":"EPSG","code":1152}},{"name":"World 
Geodetic System 1984 
(G873)","id":{"authority":"EPSG","code":1153}},{"name":"World  [...]
@@ -279,6 +303,7 @@ pub const OGC_CRS84_PROJJSON: &str = 
r#"{"$schema":"https://proj.org/schemas/v0.
 #[cfg(test)]
 mod test {
     use super::*;
+    const EPSG_6318_PROJJSON: &str = r#"{"$schema": 
"https://proj.org/schemas/v0.4/projjson.schema.json","type": 
"GeographicCRS","name": "NAD83(2011)","datum": {"type": 
"GeodeticReferenceFrame","name": "NAD83 (National Spatial Reference System 
2011)","ellipsoid": {"name": "GRS 1980","semi_major_axis": 
6378137,"inverse_flattening": 298.257222101}},"coordinate_system": {"subtype": 
"ellipsoidal","axis": [{"name": "Geodetic latitude","abbreviation": 
"Lat","direction": "north","unit": "degree [...]
 
     #[test]
     fn deserialize() {
@@ -304,6 +329,7 @@ mod test {
     fn crs_projjson() {
         let projjson = OGC_CRS84_PROJJSON.parse::<ProjJSON>().unwrap();
         assert_eq!(projjson.to_authority_code().unwrap().unwrap(), 
"OGC:CRS84");
+        assert_eq!(projjson.srid().unwrap(), Some(4326));
 
         let json_value: Value = 
serde_json::from_str(OGC_CRS84_PROJJSON).unwrap();
         let json_value_roundtrip: Value = 
serde_json::from_str(&projjson.to_json()).unwrap();
@@ -317,7 +343,11 @@ mod test {
             .to_authority_code()
             .unwrap()
             .is_none());
-        assert!(!projjson.crs_equals(&projjson_without_identifier))
+        assert!(!projjson.crs_equals(&projjson_without_identifier));
+
+        let projjson = EPSG_6318_PROJJSON.parse::<ProjJSON>().unwrap();
+        assert_eq!(projjson.to_authority_code().unwrap().unwrap(), 
"EPSG:6318");
+        assert_eq!(projjson.srid().unwrap(), Some(6318));
     }
 
     #[test]
@@ -328,6 +358,7 @@ mod test {
         };
         assert!(auth_code.crs_equals(&auth_code));
         assert!(!auth_code.crs_equals(LngLat::crs().unwrap().as_ref()));
+        assert_eq!(auth_code.srid().unwrap(), Some(4269));
 
         assert_eq!(
             auth_code.to_authority_code().unwrap(),
@@ -345,18 +376,20 @@ mod test {
         assert!(AuthorityCode::is_authority_code(&auth_code_parsed.unwrap()));
 
         let value: Value = serde_json::from_str("\"EPSG:4269\"").unwrap();
-        let new_crs = deserialize_crs(&value).unwrap();
+        let new_crs = deserialize_crs(&value).unwrap().unwrap();
         assert_eq!(
-            new_crs.unwrap().to_authority_code().unwrap(),
+            new_crs.to_authority_code().unwrap(),
             Some("EPSG:4269".to_string())
         );
+        assert_eq!(new_crs.srid().unwrap(), Some(4269));
 
         // Ensure we can also just pass a code here
         let value: Value = serde_json::from_str("\"4269\"").unwrap();
         let new_crs = deserialize_crs(&value).unwrap();
         assert_eq!(
-            new_crs.unwrap().to_authority_code().unwrap(),
+            new_crs.clone().unwrap().to_authority_code().unwrap(),
             Some("EPSG:4269".to_string())
         );
+        assert_eq!(new_crs.unwrap().srid().unwrap(), Some(4269));
     }
 }

Reply via email to