From 06b571cac7b0428861eadf4894650ac191a65169 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Fri, 23 Jan 2026 20:42:14 +0900 Subject: [PATCH 1/7] Let ST_Transform() handle 3D coordinates --- c/sedona-proj/src/proj.rs | 11 +++++++++++ c/sedona-proj/src/transform.rs | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/c/sedona-proj/src/proj.rs b/c/sedona-proj/src/proj.rs index c597ec011..053260f3d 100644 --- a/c/sedona-proj/src/proj.rs +++ b/c/sedona-proj/src/proj.rs @@ -371,6 +371,17 @@ impl Proj { Ok((xyzt_out.0, xyzt_out.1)) } + /// Transform XYZ coordinates + pub(crate) fn transform_xyz( + &mut self, + point: (f64, f64, f64), + ) -> Result<(f64, f64, f64), SedonaProjError> { + // Filling extra dimensions with zeroes is what PostGIS does + let xyzt = (point.0, point.1, point.2, 0.0); + let xyzt_out = self.transform(xyzt)?; + Ok((xyzt_out.0, xyzt_out.1, xyzt_out.2)) + } + /// Transform XYZT coordinates pub(crate) fn transform( &mut self, diff --git a/c/sedona-proj/src/transform.rs b/c/sedona-proj/src/transform.rs index 3ed71cbc8..1801b6539 100644 --- a/c/sedona-proj/src/transform.rs +++ b/c/sedona-proj/src/transform.rs @@ -228,6 +228,18 @@ impl CrsTransform for ProjTransform { coord.1 = res.1; Ok(()) } + + fn transform_coord_3d(&self, coord: &mut (f64, f64, f64)) -> Result<(), SedonaGeometryError> { + let res = self.proj.borrow_mut().transform_xyz(*coord).map_err(|e| { + SedonaGeometryError::Invalid(format!( + "PROJ coordinate transformation failed with error: {e}" + )) + })?; + coord.0 = res.0; + coord.1 = res.1; + coord.2 = res.2; + Ok(()) + } } #[cfg(test)] From 0e2a44abd4aedae31804e6bb9bbf02b76f664f32 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Fri, 23 Jan 2026 21:39:16 +0900 Subject: [PATCH 2/7] Add tests for 3D cases --- c/sedona-proj/src/st_transform.rs | 13 +++++++++++++ python/sedonadb/tests/functions/test_transforms.py | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/c/sedona-proj/src/st_transform.rs b/c/sedona-proj/src/st_transform.rs index b7fd90a6c..6fed39fbe 100644 --- a/c/sedona-proj/src/st_transform.rs +++ b/c/sedona-proj/src/st_transform.rs @@ -454,6 +454,7 @@ mod tests { const NAD83ZONE6PROJ: &str = "EPSG:2230"; const WGS84: &str = "EPSG:4326"; + const WGS84GEOCENTRIC: &str = "EPSG:4978"; #[test] fn test_invoke_with_string() { @@ -484,6 +485,18 @@ mod tests { let result = tester.invoke_array_scalar(wkb, NAD83ZONE6PROJ).unwrap(); assert_array_equal(&result, &expected_array); + // 3D case + let expected_array = create_array( + &[ + None, + Some("POINT Z (5177633.352083738 1100539.9429069504 3546477.878583284)"), + ], + &expected_return_type, + ); + let wkb = create_array(&[None, Some("POINT Z (12 34 56)")], &geometry_input); + let result = tester.invoke_array_scalar(wkb, WGS84GEOCENTRIC).unwrap(); + assert_array_equal(&result, &expected_array); + // Invoke with array to argument (returns item CRS) let expected_array = create_array_item_crs( &[None, Some("POINT (-21508577.363421552 34067918.06097863)")], diff --git a/python/sedonadb/tests/functions/test_transforms.py b/python/sedonadb/tests/functions/test_transforms.py index 4f82c5dd2..70b29ddc3 100644 --- a/python/sedonadb/tests/functions/test_transforms.py +++ b/python/sedonadb/tests/functions/test_transforms.py @@ -31,6 +31,16 @@ def test_st_transform(eng): ) +@pytest.mark.parametrize("eng", [SedonaDB, PostGIS]) +def test_st_transform_3d(eng): + eng = eng.create_or_skip() + eng.assert_query_result( + "SELECT ST_Transform(ST_GeomFromText('POINT Z (1 1 1)'), 'EPSG:4326', 'EPSG:4978')", + "POINT Z (6376201.805927448 111297.01651788183 110568.79227697308)", + wkt_precision=9, + ) + + @pytest.mark.parametrize("eng", [SedonaDB, PostGIS]) @pytest.mark.parametrize( ("geom", "srid", "expected_srid"), From b55b4da232a88ccc4f95e4e296a8f31a6b0306f0 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Fri, 23 Jan 2026 22:15:38 +0900 Subject: [PATCH 3/7] Reduce precision --- python/sedonadb/tests/functions/test_transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/sedonadb/tests/functions/test_transforms.py b/python/sedonadb/tests/functions/test_transforms.py index 70b29ddc3..4eab76bc6 100644 --- a/python/sedonadb/tests/functions/test_transforms.py +++ b/python/sedonadb/tests/functions/test_transforms.py @@ -37,7 +37,7 @@ def test_st_transform_3d(eng): eng.assert_query_result( "SELECT ST_Transform(ST_GeomFromText('POINT Z (1 1 1)'), 'EPSG:4326', 'EPSG:4978')", "POINT Z (6376201.805927448 111297.01651788183 110568.79227697308)", - wkt_precision=9, + wkt_precision=8, ) From 1f3e30a979b39727bc66bf4feb84a03c451388e4 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Fri, 23 Jan 2026 22:16:17 +0900 Subject: [PATCH 4/7] Remove Rust test --- c/sedona-proj/src/st_transform.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/c/sedona-proj/src/st_transform.rs b/c/sedona-proj/src/st_transform.rs index 6fed39fbe..a3d874300 100644 --- a/c/sedona-proj/src/st_transform.rs +++ b/c/sedona-proj/src/st_transform.rs @@ -485,18 +485,6 @@ mod tests { let result = tester.invoke_array_scalar(wkb, NAD83ZONE6PROJ).unwrap(); assert_array_equal(&result, &expected_array); - // 3D case - let expected_array = create_array( - &[ - None, - Some("POINT Z (5177633.352083738 1100539.9429069504 3546477.878583284)"), - ], - &expected_return_type, - ); - let wkb = create_array(&[None, Some("POINT Z (12 34 56)")], &geometry_input); - let result = tester.invoke_array_scalar(wkb, WGS84GEOCENTRIC).unwrap(); - assert_array_equal(&result, &expected_array); - // Invoke with array to argument (returns item CRS) let expected_array = create_array_item_crs( &[None, Some("POINT (-21508577.363421552 34067918.06097863)")], From e426742ba2ee17b6a1a0578260721f2367ec1774 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Fri, 23 Jan 2026 22:40:32 +0900 Subject: [PATCH 5/7] Fix clippy warning --- c/sedona-proj/src/st_transform.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/c/sedona-proj/src/st_transform.rs b/c/sedona-proj/src/st_transform.rs index a3d874300..b7fd90a6c 100644 --- a/c/sedona-proj/src/st_transform.rs +++ b/c/sedona-proj/src/st_transform.rs @@ -454,7 +454,6 @@ mod tests { const NAD83ZONE6PROJ: &str = "EPSG:2230"; const WGS84: &str = "EPSG:4326"; - const WGS84GEOCENTRIC: &str = "EPSG:4978"; #[test] fn test_invoke_with_string() { From 3fb1040a517da4325b1e5b1b1945fb45428c4b7e Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Fri, 23 Jan 2026 22:42:44 +0900 Subject: [PATCH 6/7] Tweak --- python/sedonadb/tests/functions/test_transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/sedonadb/tests/functions/test_transforms.py b/python/sedonadb/tests/functions/test_transforms.py index 4eab76bc6..3a404c12b 100644 --- a/python/sedonadb/tests/functions/test_transforms.py +++ b/python/sedonadb/tests/functions/test_transforms.py @@ -36,8 +36,8 @@ def test_st_transform_3d(eng): eng = eng.create_or_skip() eng.assert_query_result( "SELECT ST_Transform(ST_GeomFromText('POINT Z (1 1 1)'), 'EPSG:4326', 'EPSG:4978')", - "POINT Z (6376201.805927448 111297.01651788183 110568.79227697308)", - wkt_precision=8, + "POINT Z (6376201.805927448 111297.016517882 110568.792276973)", + wkt_precision=9, ) From 94246caa43a9fc00d6f93ea211f9777d01d9da16 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 24 Jan 2026 17:07:44 +0900 Subject: [PATCH 7/7] 3D -> 3D --- python/sedonadb/tests/functions/test_transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/sedonadb/tests/functions/test_transforms.py b/python/sedonadb/tests/functions/test_transforms.py index 3a404c12b..22bd0fd96 100644 --- a/python/sedonadb/tests/functions/test_transforms.py +++ b/python/sedonadb/tests/functions/test_transforms.py @@ -35,7 +35,7 @@ def test_st_transform(eng): def test_st_transform_3d(eng): eng = eng.create_or_skip() eng.assert_query_result( - "SELECT ST_Transform(ST_GeomFromText('POINT Z (1 1 1)'), 'EPSG:4326', 'EPSG:4978')", + "SELECT ST_Transform(ST_GeomFromText('POINT Z (1 1 1)'), 'EPSG:4979', 'EPSG:4978')", "POINT Z (6376201.805927448 111297.016517882 110568.792276973)", wkt_precision=9, )