You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by pa...@apache.org on 2026/07/18 02:24:03 UTC
(sedona-db) branch main updated: feat(c/sedona-s2geography): parameterize S2_CoveringCellIds (min_level, max_level, max_cells) (#1023)
This is an automated email from the ASF dual-hosted git repository.
paleolimbot 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 3e63bd7af feat(c/sedona-s2geography): parameterize S2_CoveringCellIds (min_level, max_level, max_cells) (#1023)
3e63bd7af is described below
commit 3e63bd7af12968a3cf69e09f11b94b730e726a11
Author: ajaypadwal73 <51...@users.noreply.github.com>
AuthorDate: Sat Jul 18 07:53:57 2026 +0530
feat(c/sedona-s2geography): parameterize S2_CoveringCellIds (min_level, max_level, max_cells) (#1023)
---
c/sedona-s2geography/s2geography | 2 +-
c/sedona-s2geography/src/kernels.rs | 85 +++++++++++++++++++++++--
docs/reference/sql/s2_coveringcellids.qmd | 28 ++++++++
python/sedonadb/tests/geography/test_geog_s2.py | 29 +++++++++
4 files changed, 138 insertions(+), 6 deletions(-)
diff --git a/c/sedona-s2geography/s2geography b/c/sedona-s2geography/s2geography
index 345295958..ae9864593 160000
--- a/c/sedona-s2geography/s2geography
+++ b/c/sedona-s2geography/s2geography
@@ -1 +1 @@
-Subproject commit 34529595806b0a2420a389efc03c32e841ec9dda
+Subproject commit ae9864593ea40537ffb19d631ba79ce9df9903c3
diff --git a/c/sedona-s2geography/src/kernels.rs b/c/sedona-s2geography/src/kernels.rs
index 06e95b95c..1a56c3193 100644
--- a/c/sedona-s2geography/src/kernels.rs
+++ b/c/sedona-s2geography/src/kernels.rs
@@ -31,6 +31,10 @@ use sedona_schema::{
use crate::{s2geog_check, s2geography_c_bindgen::*};
+fn covering_cell_ids_type() -> SedonaType {
+ SedonaType::Arrow(DataType::new_list(DataType::Int64, true))
+}
+
pub fn s2_scalar_kernels() -> Result<Vec<(String, ScalarKernelRef)>> {
let mut ffi_scalar_kernels = Vec::<SedonaCScalarKernel>::new();
ffi_scalar_kernels.resize_with(unsafe { S2GeogNumKernels() }, Default::default);
@@ -235,14 +239,63 @@ pub fn s2_scalar_kernels() -> Result<Vec<(String, ScalarKernelRef)>> {
"s2_coveringcellids".to_string(),
Arc::new(NullKernelHelper::new(ArgMatcher::new(
vec![ArgMatcher::is_null()],
- SedonaType::Arrow(DataType::List(Arc::new(arrow_schema::Field::new(
- "item",
- DataType::Int64,
- true,
- )))),
+ covering_cell_ids_type(),
))),
));
+ // s2_coveringcellids(NULL, ...), s2_coveringcellids(geography, NULL, ...)
+ for matchers in [
+ vec![ArgMatcher::is_null(), ArgMatcher::is_integer()],
+ vec![ArgMatcher::is_geography(), ArgMatcher::is_null()],
+ vec![
+ ArgMatcher::is_null(),
+ ArgMatcher::is_integer(),
+ ArgMatcher::is_integer(),
+ ],
+ vec![
+ ArgMatcher::is_geography(),
+ ArgMatcher::is_null(),
+ ArgMatcher::is_integer(),
+ ],
+ vec![
+ ArgMatcher::is_geography(),
+ ArgMatcher::is_integer(),
+ ArgMatcher::is_null(),
+ ],
+ vec![
+ ArgMatcher::is_null(),
+ ArgMatcher::is_integer(),
+ ArgMatcher::is_integer(),
+ ArgMatcher::is_integer(),
+ ],
+ vec![
+ ArgMatcher::is_geography(),
+ ArgMatcher::is_null(),
+ ArgMatcher::is_integer(),
+ ArgMatcher::is_integer(),
+ ],
+ vec![
+ ArgMatcher::is_geography(),
+ ArgMatcher::is_integer(),
+ ArgMatcher::is_null(),
+ ArgMatcher::is_integer(),
+ ],
+ vec![
+ ArgMatcher::is_geography(),
+ ArgMatcher::is_integer(),
+ ArgMatcher::is_integer(),
+ ArgMatcher::is_null(),
+ ],
+ ] {
+ kernels.push((
+ "s2_coveringcellids".to_string(),
+ Arc::new(NullKernelHelper::new(ArgMatcher::new(
+ matchers,
+ covering_cell_ids_type(),
+ ))),
+ ));
+ }
+
Ok(kernels)
}
@@ -305,6 +358,28 @@ mod test {
assert!(!kernels.is_empty());
}
+ #[test]
+ fn covering_cell_ids_return_type() {
+ let udf = s2_udf("s2_coveringcellids");
+ let int64 = SedonaType::Arrow(DataType::Int64);
+ let null = SedonaType::Arrow(DataType::Null);
+
+ for arg_types in [
+ vec![WKB_GEOGRAPHY],
+ vec![WKB_GEOGRAPHY, int64.clone()],
+ vec![WKB_GEOGRAPHY, int64.clone(), int64.clone()],
+ vec![WKB_GEOGRAPHY, int64.clone(), int64.clone(), int64.clone()],
+ vec![null.clone()],
+ vec![null.clone(), int64.clone()],
+ vec![WKB_GEOGRAPHY, null.clone(), int64.clone()],
+ vec![WKB_GEOGRAPHY, int64.clone(), null.clone(), int64.clone()],
+ vec![WKB_GEOGRAPHY, int64.clone(), int64.clone(), null.clone()],
+ ] {
+ let tester = ScalarUdfTester::new(udf.clone().into(), arg_types);
+ assert_eq!(tester.return_type().unwrap(), covering_cell_ids_type());
+ }
+ }
+
fn s2_udf(name: &str) -> SedonaScalarUDF {
let mut udf: Option<SedonaScalarUDF> = None;
for (kernel_name, kernel) in s2_scalar_kernels().unwrap() {
diff --git a/docs/reference/sql/s2_coveringcellids.qmd b/docs/reference/sql/s2_coveringcellids.qmd
index 499839e7b..05ecca1ff 100644
--- a/docs/reference/sql/s2_coveringcellids.qmd
+++ b/docs/reference/sql/s2_coveringcellids.qmd
@@ -21,6 +21,30 @@ description: Returns an array of S2 cell IDs that cover the given geography.
kernels:
- returns: array
args: [geography]
+ - returns: array
+ args:
+ - geography
+ - name: min_level
+ type: int64
+ description: Minimum S2 cell level to return. Must be between 0 and 30. Defaults to 0.
+ - returns: array
+ args:
+ - geography
+ - name: min_level
+ type: int64
+ - name: max_level
+ type: int64
+ description: Maximum S2 cell level to return. Must be between 0 and 30. Defaults to 30.
+ - returns: array
+ args:
+ - geography
+ - name: min_level
+ type: int64
+ - name: max_level
+ type: int64
+ - name: max_cells
+ type: int64
+ description: Maximum number of cells to return. Must be greater than 0. Defaults to 8.
---
## Description
@@ -34,3 +58,7 @@ This is useful for spatial indexing and efficient spatial queries. The returned
```sql
SELECT S2_CoveringCellIds(ST_GeogFromWKT('LINESTRING (0 0, 1 1)'));
```
+
+```sql
+SELECT S2_CoveringCellIds(ST_GeogFromWKT('LINESTRING (0 0, 1 1)'), 10, 12, 16);
+```
diff --git a/python/sedonadb/tests/geography/test_geog_s2.py b/python/sedonadb/tests/geography/test_geog_s2.py
index 5b1d170ee..b5c806846 100644
--- a/python/sedonadb/tests/geography/test_geog_s2.py
+++ b/python/sedonadb/tests/geography/test_geog_s2.py
@@ -23,6 +23,12 @@ if "s2geography" not in sedonadb.__features__:
pytest.skip("Python package built without s2geography", allow_module_level=True)
+def _s2_cell_level(cell_id):
+ # The least significant set bit encodes the S2 cell level.
+ marker_bit_index = (int(cell_id) & -int(cell_id)).bit_length() - 1
+ return 30 - (marker_bit_index // 2)
+
+
# S2_CellIdFromPoint tests - returns the S2 cell ID containing a point
@pytest.mark.parametrize("eng", [SedonaDB, BigQuery])
@pytest.mark.parametrize(
@@ -92,3 +98,26 @@ def test_s2_coveringcellids(eng, geog, expected):
)
df = eng.result_to_pandas(result)
assert len(df.iloc[0, 0]) == len(expected)
+
+
+@pytest.mark.parametrize("eng", [SedonaDB])
+def test_s2_coveringcellids_parameters(eng):
+ eng = eng.create_or_skip()
+ geog = "ST_GeogFromText('LINESTRING (0 0, 100 50)')"
+ cases = [
+ (f"S2_CoveringCellIds({geog})", 0, 30, None),
+ (f"S2_CoveringCellIds({geog}, 4)", 4, 30, None),
+ (f"S2_CoveringCellIds({geog}, 4, 6)", 4, 6, None),
+ (f"S2_CoveringCellIds({geog}, 0, 30, 2)", 0, 30, 2),
+ ]
+
+ for expr, min_level, max_level, expected_max_cells in cases:
+ result = eng.execute_and_collect(f"SELECT {expr}")
+ df = eng.result_to_pandas(result)
+ cells = df.iloc[0, 0]
+ levels = [_s2_cell_level(cell_id) for cell_id in cells]
+
+ assert len(cells) > 0
+ if expected_max_cells is not None:
+ assert len(cells) <= expected_max_cells
+ assert all(min_level <= level <= max_level for level in levels)