You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@sedona.apache.org by "jiayuasu (via GitHub)" <gi...@apache.org> on 2026/03/30 08:30:48 UTC

[PR] [GH-2799] Add ST_OffsetCurve function [sedona]

jiayuasu opened a new pull request, #2811:
URL: https://github.com/apache/sedona/pull/2811

   ## Summary
   
   - Add `ST_OffsetCurve(geom, distance [, quadrantSegments])` function that returns a line offset by a given distance from a linear geometry
   - Uses JTS `org.locationtech.jts.operation.buffer.OffsetCurve` (available since JTS 1.19, Sedona uses 1.20)
   - Positive distance offsets to the left, negative to the right
   - Returns `null` for empty geometries
   
   Closes #2799
   
   ## Changes
   
   ### Implementation (6 layers)
   - **Java core** (`Functions.java`): Two overloads — `offsetCurve(Geometry, double)` and `offsetCurve(Geometry, double, int)` using JTS `OffsetCurve` with `BufferParameters`
   - **Scala Expression** (`Functions.scala`): `ST_OffsetCurve` case class with `inferrableFunction2` and `inferrableFunction3`
   - **Catalog** (`Catalog.scala`): Function registration
   - **Scala API** (`st_functions.scala`): Column/String wrappers with optional quadrantSegments
   - **Python API** (`st_functions.py`): Python wrapper with optional `quadrant_segments` parameter
   - **Flink** (`Functions.java`, `Catalog.java`): Flink scalar function + registration
   
   ### Tests
   - Java unit tests (5 cases: positive offset, negative offset, zero distance, empty geometry, custom quadrant segments)
   - Scala SQL tests + DataFrame API tests (with and without quadrantSegments)
   - SRID preservation test
   - Python SQL test + DataFrame API tests
   - Flink tests (2 cases)
   
   ### Documentation
   - SQL, Flink, and Snowflake docs with examples and SVG visual
   
   ## Test plan
   
   - [x] Java unit tests pass
   - [x] Python SQL tests pass
   - [x] Python DataFrame API tests pass
   - [ ] CI full test suite


-- 
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] [GH-2799] Add ST_OffsetCurve function [sedona]

Posted by "jiayuasu (via GitHub)" <gi...@apache.org>.
jiayuasu merged PR #2811:
URL: https://github.com/apache/sedona/pull/2811


-- 
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] [GH-2799] Add ST_OffsetCurve function [sedona]

Posted by "jiayuasu (via GitHub)" <gi...@apache.org>.
jiayuasu commented on code in PR #2811:
URL: https://github.com/apache/sedona/pull/2811#discussion_r3020985925


##########
docs/api/sql/Geometry-Processing/ST_OffsetCurve.md:
##########
@@ -0,0 +1,54 @@
+<!--
+ 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.
+ -->
+
+# ST_OffsetCurve
+
+![ST_OffsetCurve](../../../image/ST_OffsetCurve/ST_OffsetCurve.svg "ST_OffsetCurve")
+
+Introduction: Returns a line at a given offset distance from a linear geometry. If the distance is positive, the offset is on the left side of the input line; if it is negative, it is on the right side. Returns null for empty geometries.
+
+The optional third parameter `quadrantSegments` controls the number of line segments used to approximate a quarter circle at round joins. The default value is 8.
+
+Format: `ST_OffsetCurve(geometry: Geometry, distance: Double, quadrantSegments: Integer)`
+
+Format: `ST_OffsetCurve(geometry: Geometry, distance: Double)`

Review Comment:
   Consolidated into a single optional-parameter format and switched to fenced output blocks.



##########
docs/api/flink/Geometry-Processing/ST_OffsetCurve.md:
##########
@@ -0,0 +1,52 @@
+<!--
+ 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.
+ -->
+
+# ST_OffsetCurve
+
+Introduction: Returns a line at a given offset distance from a linear geometry. If the distance is positive, the offset is on the left side of the input line; if it is negative, it is on the right side. Returns null for empty geometries.
+
+The optional third parameter `quadrantSegments` controls the number of line segments used to approximate a quarter circle at round joins. The default value is 8.
+
+Format: `ST_OffsetCurve(geometry: Geometry, distance: Double, quadrantSegments: Integer)`
+
+Format: `ST_OffsetCurve(geometry: Geometry, distance: Double)`
+
+Return type: `Geometry`

Review Comment:
   Same fix applied here.



##########
common/src/main/java/org/apache/sedona/common/Functions.java:
##########
@@ -424,6 +425,20 @@ else if (singleParam[0].equalsIgnoreCase(listBufferParameters[5])) {
     return bufferParameters;
   }
 
+  public static Geometry offsetCurve(Geometry geometry, double distance) {
+    return offsetCurve(geometry, distance, BufferParameters.DEFAULT_QUADRANT_SEGMENTS);
+  }
+
+  public static Geometry offsetCurve(Geometry geometry, double distance, int quadrantSegments) {
+    if (geometry.isEmpty()) {
+      return null;
+    }
+    BufferParameters bufferParameters = new BufferParameters();
+    bufferParameters.setQuadrantSegments(quadrantSegments);
+    OffsetCurve oc = new OffsetCurve(geometry, distance, bufferParameters);
+    return oc.getCurve();

Review Comment:
   Spark/Flink expressions null-short-circuit before reaching these Java functions. The existing codebase is inconsistent about this — some functions have the null guard, some don't. Not changing it here to stay consistent with the surrounding code.



##########
snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java:
##########
@@ -768,6 +768,19 @@ public void test_ST_NPoints() {
         "select sedona.ST_NPoints(sedona.ST_GeomFromText('LINESTRING(1 2, 3 4, 5 6)'))", 3);
   }
 
+  @Test
+  public void test_ST_OffsetCurve() {
+    registerUDF("ST_OffsetCurve", byte[].class, double.class);
+    verifySqlSingleRes(
+        "select sedona.ST_AsText(sedona.ST_OffsetCurve(sedona.ST_GeomFromText('LINESTRING(0 0, 10 0)'), 5.0))",
+        "LINESTRING (0 5, 10 5)");
+    registerUDF("ST_OffsetCurve", byte[].class, double.class, int.class);
+    registerUDF("ST_NPoints", byte[].class);
+    verifySqlSingleRes(
+        "select sedona.ST_NPoints(sedona.ST_OffsetCurve(sedona.ST_GeomFromText('LINESTRING(0 0, 10 0, 10 10)'), -3.0, 16))",
+        19);

Review Comment:
   Changed to a relative comparison using Snowflake's `iff()` to assert qs=16 produces more points than the default.



##########
snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java:
##########
@@ -716,6 +716,19 @@ public void test_ST_NPoints() {
         "select sedona.ST_NPoints(ST_GeometryFromWKT('LINESTRING(1 2, 3 4, 5 6)'))", 3);
   }
 
+  @Test
+  public void test_ST_OffsetCurve() {
+    registerUDFV2("ST_OffsetCurve", String.class, double.class);
+    verifySqlSingleRes(
+        "select sedona.ST_AsText(sedona.ST_OffsetCurve(ST_GeometryFromWKT('LINESTRING(0 0, 10 0)'), 5.0))",
+        "LINESTRING (0 5, 10 5)");
+    registerUDFV2("ST_OffsetCurve", String.class, double.class, int.class);
+    registerUDFV2("ST_NPoints", String.class);
+    verifySqlSingleRes(
+        "select sedona.ST_NPoints(sedona.ST_OffsetCurve(ST_GeometryFromWKT('LINESTRING(0 0, 10 0, 10 10)'), -3.0, 16))",
+        19);

Review Comment:
   Same fix applied here.



-- 
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] [GH-2799] Add ST_OffsetCurve function [sedona]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #2811:
URL: https://github.com/apache/sedona/pull/2811#discussion_r3020198670


##########
common/src/main/java/org/apache/sedona/common/Functions.java:
##########
@@ -424,6 +425,20 @@ else if (singleParam[0].equalsIgnoreCase(listBufferParameters[5])) {
     return bufferParameters;
   }
 
+  public static Geometry offsetCurve(Geometry geometry, double distance) {
+    return offsetCurve(geometry, distance, BufferParameters.DEFAULT_QUADRANT_SEGMENTS);
+  }
+
+  public static Geometry offsetCurve(Geometry geometry, double distance, int quadrantSegments) {
+    if (geometry.isEmpty()) {
+      return null;
+    }
+    BufferParameters bufferParameters = new BufferParameters();
+    bufferParameters.setQuadrantSegments(quadrantSegments);
+    OffsetCurve oc = new OffsetCurve(geometry, distance, bufferParameters);
+    return oc.getCurve();

Review Comment:
   `offsetCurve(Geometry, double, int)` calls `geometry.isEmpty()` without a null guard, which will throw `NullPointerException` if this method is invoked directly with a null geometry (unlike Spark SQL expressions which short-circuit on null args). Consider aligning with nearby helpers (e.g., `expand`) by returning null when `geometry == null`, before checking `isEmpty()`.



##########
snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java:
##########
@@ -716,6 +716,19 @@ public void test_ST_NPoints() {
         "select sedona.ST_NPoints(ST_GeometryFromWKT('LINESTRING(1 2, 3 4, 5 6)'))", 3);
   }
 
+  @Test
+  public void test_ST_OffsetCurve() {
+    registerUDFV2("ST_OffsetCurve", String.class, double.class);
+    verifySqlSingleRes(
+        "select sedona.ST_AsText(sedona.ST_OffsetCurve(ST_GeometryFromWKT('LINESTRING(0 0, 10 0)'), 5.0))",
+        "LINESTRING (0 5, 10 5)");
+    registerUDFV2("ST_OffsetCurve", String.class, double.class, int.class);
+    registerUDFV2("ST_NPoints", String.class);
+    verifySqlSingleRes(
+        "select sedona.ST_NPoints(sedona.ST_OffsetCurve(ST_GeometryFromWKT('LINESTRING(0 0, 10 0, 10 10)'), -3.0, 16))",
+        19);

Review Comment:
   This test asserts an exact `ST_NPoints` value (19) for an offset curve with `quadrantSegments=16`. The vertex count of `OffsetCurve` results is algorithm/version dependent and can change across JTS updates, which makes the test fragile. Consider comparing against the default `ST_OffsetCurve(geom, distance)` point count instead (asserting the custom count is greater), or otherwise loosening the assertion to reflect the intended behavior.
   ```suggestion
           "select sedona.ST_NPoints(sedona.ST_OffsetCurve(ST_GeometryFromWKT('LINESTRING(0 0, 10 0, 10 10)'), -3.0, 16)) > "
               + "sedona.ST_NPoints(sedona.ST_OffsetCurve(ST_GeometryFromWKT('LINESTRING(0 0, 10 0, 10 10)'), -3.0))",
           true);
   ```



##########
snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java:
##########
@@ -768,6 +768,19 @@ public void test_ST_NPoints() {
         "select sedona.ST_NPoints(sedona.ST_GeomFromText('LINESTRING(1 2, 3 4, 5 6)'))", 3);
   }
 
+  @Test
+  public void test_ST_OffsetCurve() {
+    registerUDF("ST_OffsetCurve", byte[].class, double.class);
+    verifySqlSingleRes(
+        "select sedona.ST_AsText(sedona.ST_OffsetCurve(sedona.ST_GeomFromText('LINESTRING(0 0, 10 0)'), 5.0))",
+        "LINESTRING (0 5, 10 5)");
+    registerUDF("ST_OffsetCurve", byte[].class, double.class, int.class);
+    registerUDF("ST_NPoints", byte[].class);
+    verifySqlSingleRes(
+        "select sedona.ST_NPoints(sedona.ST_OffsetCurve(sedona.ST_GeomFromText('LINESTRING(0 0, 10 0, 10 10)'), -3.0, 16))",
+        19);

Review Comment:
   This test asserts an exact `ST_NPoints` value (19) for an offset curve with `quadrantSegments=16`. The number of vertices in `OffsetCurve` output can change across JTS/Sedona upgrades even when the geometry is still correct, making the test brittle. Prefer asserting relative behavior (e.g., `NPoints` with `quadrantSegments=16` is greater than the default call) or validating the curve shape in a less version-sensitive way.
   ```suggestion
           "select iff(" +
           "  sedona.ST_NPoints(sedona.ST_OffsetCurve(sedona.ST_GeomFromText('LINESTRING(0 0, 10 0, 10 10)'), -3.0, 16)) > " +
           "  sedona.ST_NPoints(sedona.ST_OffsetCurve(sedona.ST_GeomFromText('LINESTRING(0 0, 10 0, 10 10)'), -3.0))," +
           "  1, 0)",
           1);
   ```



-- 
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] [GH-2799] Add ST_OffsetCurve function [sedona]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #2811:
URL: https://github.com/apache/sedona/pull/2811#discussion_r3012895722


##########
docs/api/sql/Geometry-Processing/ST_OffsetCurve.md:
##########
@@ -0,0 +1,54 @@
+<!--
+ 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.
+ -->
+
+# ST_OffsetCurve
+
+![ST_OffsetCurve](../../../image/ST_OffsetCurve/ST_OffsetCurve.svg "ST_OffsetCurve")
+
+Introduction: Returns a line at a given offset distance from a linear geometry. If the distance is positive, the offset is on the left side of the input line; if it is negative, it is on the right side. Returns null for empty geometries.
+
+The optional third parameter `quadrantSegments` controls the number of line segments used to approximate a quarter circle at round joins. The default value is 8.
+
+Format: `ST_OffsetCurve(geometry: Geometry, distance: Double, quadrantSegments: Integer)`
+
+Format: `ST_OffsetCurve(geometry: Geometry, distance: Double)`

Review Comment:
   The SQL docs format here differs from other SQL geometry-processing pages: overloads are usually documented via a single optional-parameter signature (e.g., `ST_MinimumBoundingCircle`) or via a `Format:` section with code blocks (e.g., `ST_Buffer`), and SQL results are typically shown in a fenced `Output:` block. Consider consolidating the two `Format:` lines into a single optional-signature format and switching the inline `Output: `...`` to the standard fenced output blocks for consistency/readability.



##########
docs/api/flink/Geometry-Processing/ST_OffsetCurve.md:
##########
@@ -0,0 +1,52 @@
+<!--
+ 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.
+ -->
+
+# ST_OffsetCurve
+
+Introduction: Returns a line at a given offset distance from a linear geometry. If the distance is positive, the offset is on the left side of the input line; if it is negative, it is on the right side. Returns null for empty geometries.
+
+The optional third parameter `quadrantSegments` controls the number of line segments used to approximate a quarter circle at round joins. The default value is 8.
+
+Format: `ST_OffsetCurve(geometry: Geometry, distance: Double, quadrantSegments: Integer)`
+
+Format: `ST_OffsetCurve(geometry: Geometry, distance: Double)`
+
+Return type: `Geometry`

Review Comment:
   Other Flink geometry-processing docs typically document overloads as a single signature with an optional parameter and show results under an `Output:` fenced block (e.g., `ST_OrientedEnvelope`). Here the overloads are split across two `Format:` lines and the outputs are inline. Consider consolidating the signature (e.g., `[Optional] quadrantSegments`) and using fenced output blocks to match the rest of the Flink docs.



-- 
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