This is an automated email from the ASF dual-hosted git repository.
jiayu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git
The following commit(s) were added to refs/heads/master by this push:
new dc43945b6 [SEDONA-615] Add ST_MaximumInscribedCircle (#1488)
dc43945b6 is described below
commit dc43945b6e5f2bc133af7e77ed5aed2902d1d110
Author: Furqaan Khan <[email protected]>
AuthorDate: Mon Jun 24 02:59:02 2024 -0400
[SEDONA-615] Add ST_MaximumInscribedCircle (#1488)
* feat: add ST_MaximumInscribedCircle
* fix: spotless errors
* fix: spotless errors
---
.../java/org/apache/sedona/common/Functions.java | 29 ++++++++++
.../sedona/common/utils/InscribedCircle.java | 48 +++++++++++++++++
.../org/apache/sedona/common/FunctionsTest.java | 61 ++++++++++++++++++++--
docs/api/snowflake/vector-data/Function.md | 32 ++++++++++++
docs/api/sql/Function.md | 32 ++++++++++++
python/sedona/sql/st_functions.py | 12 +++++
python/tests/sql/test_dataframe_api.py | 4 ++
python/tests/sql/test_function.py | 10 ++++
.../snowflake/snowsql/TestTableFunctions.java | 14 +++++
.../snowflake/snowsql/ddl/UDTFDDLGenerator.java | 1 +
.../snowsql/udtfs/ST_MaximumInscribedCircle.java | 57 ++++++++++++++++++++
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 +
.../sql/sedona_sql/expressions/Functions.scala | 28 +++++++++-
.../sql/sedona_sql/expressions/st_functions.scala | 5 ++
.../apache/sedona/sql/dataFrameAPITestScala.scala | 19 +++++++
.../org/apache/sedona/sql/functionTestScala.scala | 19 +++++++
16 files changed, 367 insertions(+), 5 deletions(-)
diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java
b/common/src/main/java/org/apache/sedona/common/Functions.java
index 58fdcdbbb..4c65c1023 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -33,6 +33,8 @@ import org.apache.sedona.common.subDivide.GeometrySubDivider;
import org.apache.sedona.common.utils.*;
import org.locationtech.jts.algorithm.MinimumBoundingCircle;
import org.locationtech.jts.algorithm.Orientation;
+import org.locationtech.jts.algorithm.construct.LargestEmptyCircle;
+import org.locationtech.jts.algorithm.construct.MaximumInscribedCircle;
import org.locationtech.jts.algorithm.hull.ConcaveHull;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.impl.CoordinateArraySequence;
@@ -941,6 +943,33 @@ public class Functions {
return circle;
}
+ public static InscribedCircle maximumInscribedCircle(Geometry geometry) {
+ // Calculating the tolerance
+ Envelope envelope = geometry.getEnvelopeInternal();
+ double width = envelope.getWidth(), height = envelope.getHeight(), size,
tolerance;
+ size = Math.max(width, height);
+ tolerance = size / 1000.0;
+
+ Geometry center, nearest;
+ double radius;
+
+ // All non-polygonal geometries use LargestEmptyCircle
+ if (!geometry.getClass().getSimpleName().equals("Polygon")
+ && !geometry.getClass().getSimpleName().equals("MultiPolygon")) {
+ LargestEmptyCircle largestEmptyCircle = new LargestEmptyCircle(geometry,
tolerance);
+ center = largestEmptyCircle.getCenter();
+ nearest = largestEmptyCircle.getRadiusPoint();
+ radius = largestEmptyCircle.getRadiusLine().getLength();
+ return new InscribedCircle(center, nearest, radius);
+ }
+
+ MaximumInscribedCircle maximumInscribedCircle = new
MaximumInscribedCircle(geometry, tolerance);
+ center = maximumInscribedCircle.getCenter();
+ nearest = maximumInscribedCircle.getRadiusPoint();
+ radius = maximumInscribedCircle.getRadiusLine().getLength();
+ return new InscribedCircle(center, nearest, radius);
+ }
+
public static Pair<Geometry, Double> minimumBoundingRadius(Geometry
geometry) {
MinimumBoundingCircle minimumBoundingCircle = new
MinimumBoundingCircle(geometry);
Coordinate coods = minimumBoundingCircle.getCentre();
diff --git
a/common/src/main/java/org/apache/sedona/common/utils/InscribedCircle.java
b/common/src/main/java/org/apache/sedona/common/utils/InscribedCircle.java
new file mode 100644
index 000000000..059e97b54
--- /dev/null
+++ b/common/src/main/java/org/apache/sedona/common/utils/InscribedCircle.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+package org.apache.sedona.common.utils;
+
+import org.apache.sedona.common.Functions;
+import org.locationtech.jts.geom.Geometry;
+
+public class InscribedCircle {
+ public final Geometry center;
+ public final Geometry nearest;
+ public final double radius;
+
+ public InscribedCircle(Geometry center, Geometry nearest, double radius) {
+ this.center = center;
+ this.nearest = nearest;
+ this.radius = radius;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "---------------\ncenter: %s\nnearest: %s\nradius:
%.20f\n---------------\n",
+ Functions.asWKT(center), Functions.asWKT(nearest), radius);
+ }
+
+ public boolean equals(InscribedCircle other) {
+ double epsilon = 1e-6;
+ return this.center.equals(other.center)
+ && this.nearest.equals(other.nearest)
+ && Math.abs(this.radius - other.radius) < epsilon;
+ }
+}
diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
index a3aa562b6..1140c0629 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -28,10 +28,7 @@ import java.util.*;
import java.util.stream.Collectors;
import org.apache.sedona.common.sphere.Haversine;
import org.apache.sedona.common.sphere.Spheroid;
-import org.apache.sedona.common.utils.GeomUtils;
-import org.apache.sedona.common.utils.H3Utils;
-import org.apache.sedona.common.utils.S2Utils;
-import org.apache.sedona.common.utils.ValidDetail;
+import org.apache.sedona.common.utils.*;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.projection.ProjectionException;
import org.junit.Test;
@@ -3459,6 +3456,62 @@ public class FunctionsTest extends TestBase {
e.getMessage());
}
+ @Test
+ public void maximumInscribedCircle() throws ParseException {
+ Geometry geom =
+ Constructors.geomFromEWKT(
+ "POLYGON ((40 180, 110 160, 180 180, 180 120, 140 90, 160 40, 80
10, 70 40, 20 50, 40 180),(60 140, 50 90, 90 140, 60 140))");
+ InscribedCircle actual = Functions.maximumInscribedCircle(geom);
+ InscribedCircle expected =
+ new InscribedCircle(
+ Constructors.geomFromEWKT("POINT (96.953125 76.328125)"),
+ Constructors.geomFromEWKT("POINT (140 90)"),
+ 45.165846);
+ assertTrue(expected.equals(actual));
+
+ geom =
+ Constructors.geomFromEWKT(
+ "MULTILINESTRING ((59.5 17, 65 17), (60 16.5, 66 12), (65 12, 69
17))");
+ actual = Functions.maximumInscribedCircle(geom);
+ expected =
+ new InscribedCircle(
+ Constructors.geomFromEWKT("POINT (65.0419921875 15.1005859375)"),
+ Constructors.geomFromEWKT("POINT (65 17)"),
+ 1.8998781);
+ assertTrue(expected.equals(actual));
+
+ geom =
+ Constructors.geomFromEWKT(
+ "MULTIPOINT ((60.8 15.5), (63.2 16.3), (63 14), (67.4 14.8), (66.3
18.4), (65. 13.), (67.5 16.9), (64.2 18))");
+ actual = Functions.maximumInscribedCircle(geom);
+ expected =
+ new InscribedCircle(
+ Constructors.geomFromEWKT("POINT (65.44062499999998
15.953124999999998)"),
+ Constructors.geomFromEWKT("POINT (67.5 16.9)"),
+ 2.2666269);
+ assertTrue(expected.equals(actual));
+
+ geom = Constructors.geomFromEWKT("MULTIPOINT ((60.8 15.5), (63.2 16.3))");
+ actual = Functions.maximumInscribedCircle(geom);
+ expected =
+ new InscribedCircle(
+ Constructors.geomFromEWKT("POINT (60.8 15.5)"),
+ Constructors.geomFromEWKT("POINT (60.8 15.5)"),
+ 0.0);
+ assertTrue(expected.equals(actual));
+
+ geom =
+ Constructors.geomFromEWKT(
+ "GEOMETRYCOLLECTION (POINT (60.8 15.5), POINT (63.2 16.3), POINT
(63 14), POINT (67.4 14.8), POINT (66.3 18.4), POINT (65 13), POINT (67.5
16.9), POINT (64.2 18))");
+ actual = Functions.maximumInscribedCircle(geom);
+ expected =
+ new InscribedCircle(
+ Constructors.geomFromEWKT("POINT (65.44062499999998
15.953124999999998)"),
+ Constructors.geomFromEWKT("POINT (67.5 16.9)"),
+ 2.2666269);
+ assertTrue(expected.equals(actual));
+ }
+
@Test
public void test() throws ParseException {
Geometry geom = Constructors.geomFromEWKT("MULTILINESTRING M ((0 0 1,0 1
2), (0 0 1,0 1 2))");
diff --git a/docs/api/snowflake/vector-data/Function.md
b/docs/api/snowflake/vector-data/Function.md
index 5da53224d..47ada173c 100644
--- a/docs/api/snowflake/vector-data/Function.md
+++ b/docs/api/snowflake/vector-data/Function.md
@@ -1831,6 +1831,38 @@ Result:
The previous implementation only worked for (multi)polygons and had a
different interpretation of the second, boolean, argument.
It would also sometimes return multiple geometries for a single geometry
input.
+## ST_MaximumInscribedCircle
+
+Introduction: Finds the largest circle that is contained within a
(multi)polygon, or which does not overlap any lines and points. Returns a row
with fields:
+
+- `center` - center point of the circle
+- `nearest` - nearest point from the center of the circle
+- `radius` - radius of the circle
+
+For polygonal geometries, the function inscribes the circle within the
boundary rings, treating internal rings as additional constraints. When
processing linear and point inputs, the algorithm inscribes the circle within
the convex hull of the input, utilizing the input lines and points as
additional boundary constraints.
+
+Format: `ST_MaximumInscribedCircle(geometry: Geometry)`
+
+Since: `v1.6.1`
+
+SQL Example:
+
+```sql
+SELECT Sedona.ST_AsText(center) AS center, Sedona.ST_AsText(nearest) AS
nearest, radius FROM table(
+ SELECT ST_MaximumIncribedCircle(ST_GeomFromWKT('POLYGON ((62.11 19.68,
60.79 17.20, 61.30 15.96, 62.11 16.08, 65.93 16.95, 66.20 20.61, 63.08 21.43,
64.48 18.70, 62.11 19.68))'))
+)
+```
+
+Output:
+
+```
++---------------------------------------------+-------------------------------------------+------------------+
+|center |nearest
|radius |
++---------------------------------------------+-------------------------------------------+------------------+
+|POINT (62.794975585937514 17.774780273437496)|POINT (63.36773534817729
19.15992378007859)|1.4988916836219184|
++---------------------------------------------+-------------------------------------------+------------------+
+```
+
## ST_MaxDistance
Introduction: Calculates and returns the length value representing the maximum
distance between any two points across the input geometries. This function is
an alias for `ST_LongestDistance`.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index a7b1891d1..941af5e72 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -2621,6 +2621,38 @@ Result:
Be sure to check you code when upgrading. The previous implementation only
worked for (multi)polygons and had a different interpretation of the second,
boolean, argument.
It would also sometimes return multiple geometries for a single geometry input.
+## ST_MaximumInscribedCircle
+
+Introduction: Finds the largest circle that is contained within a
(multi)polygon, or which does not overlap any lines and points. Returns a row
with fields:
+
+- `center` - center point of the circle
+- `nearest` - nearest point from the center of the circle
+- `radius` - radius of the circle
+
+For polygonal geometries, the function inscribes the circle within the
boundary rings, treating internal rings as additional constraints. When
processing linear and point inputs, the algorithm inscribes the circle within
the convex hull of the input, utilizing the input lines and points as
additional boundary constraints.
+
+Format: `ST_MaximumInscribedCircle(geometry: Geometry)`
+
+Since: `v1.6.1`
+
+SQL Example:
+
+```sql
+SELECT inscribedCircle.* FROM (
+ SELECT ST_MaximumIncribedCircle(ST_GeomFromWKT('POLYGON ((62.11 19.68,
60.79 17.20, 61.30 15.96, 62.11 16.08, 65.93 16.95, 66.20 20.61, 63.08 21.43,
64.48 18.70, 62.11 19.68))')) AS inscribedCircle
+)
+```
+
+Output:
+
+```
++---------------------------------------------+-------------------------------------------+------------------+
+|center |nearest
|radius |
++---------------------------------------------+-------------------------------------------+------------------+
+|POINT (62.794975585937514 17.774780273437496)|POINT (63.36773534817729
19.15992378007859)|1.4988916836219184|
++---------------------------------------------+-------------------------------------------+------------------+
+```
+
## ST_MaxDistance
Introduction: Calculates and returns the length value representing the maximum
distance between any two points across the input geometries. This function is
an alias for `ST_LongestDistance`.
diff --git a/python/sedona/sql/st_functions.py
b/python/sedona/sql/st_functions.py
index 630d6e224..a8ca7aa44 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -1089,6 +1089,18 @@ def ST_MakeValid(geometry: ColumnOrName, keep_collapsed:
Optional[Union[ColumnOr
args = (geometry,) if keep_collapsed is None else (geometry,
keep_collapsed)
return _call_st_function("ST_MakeValid", args)
+
+@validate_argument_types
+def ST_MaximumInscribedCircle(geometry: ColumnOrName) -> Column:
+ """Finds the largest circle that is contained within a geometry, or which
does not overlap any lines and points
+
+ :param geometry:
+ :type geometry: ColumnOrName
+ :return: Row of center point, nearest point and radius
+ :rtype: Column
+ """
+ return _call_st_function("ST_MaximumInscribedCircle", geometry)
+
@validate_argument_types
def ST_MaxDistance(geom1: ColumnOrName, geom2: ColumnOrName) -> Column:
"""Calculate the maximum distance between two furthest points in the
geometries
diff --git a/python/tests/sql/test_dataframe_api.py
b/python/tests/sql/test_dataframe_api.py
index ced18e5d7..c62b6eee2 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -173,6 +173,9 @@ test_configurations = [
(stf.ST_MMax, ("line",), "4D_line", "", 3.0),
(stf.ST_MakeValid, ("geom",), "invalid_geom", "", "MULTIPOLYGON (((1 5, 3
3, 1 1, 1 5)), ((5 3, 7 5, 7 1, 5 3)))"),
(stf.ST_MakeLine, ("line1", "line2"), "two_lines", "", "LINESTRING (0 0, 1
1, 0 0, 3 2)"),
+ (stf.ST_MaximumInscribedCircle, ("geom",), "triangle_geom",
"ST_AsText(geom.center)", "POINT (0.70703125 0.29296875)"),
+ (stf.ST_MaximumInscribedCircle, ("geom",), "triangle_geom",
"ST_AsText(geom.nearest)", "POINT (0.5 0.5)"),
+ (stf.ST_MaximumInscribedCircle, ("geom",), "triangle_geom", "geom.radius",
0.2927864015850548),
(stf.ST_MaxDistance, ("a", "b"), "overlapping_polys", "",
3.1622776601683795),
(stf.ST_Points, ("line",), "linestring_geom", "ST_Normalize(geom)",
"MULTIPOINT (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)"),
(stf.ST_Polygon, ("geom", 4236), "closed_linestring_geom", "", "POLYGON
((0 0, 1 0, 1 1, 0 0))"),
@@ -385,6 +388,7 @@ wrong_type_configurations = [
(stf.ST_MMax, (None,)),
(stf.ST_MakeValid, (None,)),
(stf.ST_MakePolygon, (None,)),
+ (stf.ST_MaximumInscribedCircle, (None,)),
(stf.ST_MaxDistance, (None, None)),
(stf.ST_MaxDistance, (None, "")),
(stf.ST_MaxDistance, ("", None)),
diff --git a/python/tests/sql/test_function.py
b/python/tests/sql/test_function.py
index 05a8f7e08..5fd8dd2b5 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -310,6 +310,16 @@ class TestPredicateJoin(TestBase):
intersects = self.spark.sql("select ST_Intersection(a,b) from
testtable")
assert intersects.take(1)[0][0].wkt == "POLYGON EMPTY"
+ def test_st_maximum_inscribed_circle(self):
+ baseDf = self.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((40 180, 110
160, 180 180, 180 120, 140 90, 160 40, 80 10, 70 40, 20 50, 40 180),(60 140, 50
90, 90 140, 60 140))') AS geom")
+ actual =
baseDf.selectExpr("ST_MaximumInscribedCircle(geom)").take(1)[0][0]
+ center = actual.center.wkt
+ assert center == "POINT (96.953125 76.328125)"
+ nearest = actual.nearest.wkt
+ assert nearest == "POINT (140 90)"
+ radius = actual.radius
+ assert radius == 45.165845650018
+
def test_st_is_valid_detail(self):
baseDf = self.spark.sql("SELECT ST_GeomFromText('POLYGON ((0 0, 2 0, 2
2, 0 2, 1 1, 0 0))') AS geom")
actual = baseDf.selectExpr("ST_IsValidDetail(geom)").first()[0]
diff --git
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestTableFunctions.java
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestTableFunctions.java
index 6522d531d..27e022f45 100644
---
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestTableFunctions.java
+++
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestTableFunctions.java
@@ -48,6 +48,20 @@ public class TestTableFunctions extends TestBase {
Constructors.geomFromWKT("POLYGON ((0.5 1, 1 1, 1 0.5, 0.5 0.5, 0.5
1))", 0));
}
+ @Test
+ public void test_ST_MaximumInscribedCircle() {
+ registerUDTF(ST_MaximumInscribedCircle.class);
+ verifySqlSingleRes(
+ "select sedona.ST_AsText(center) from
table(sedona.ST_MaximumInscribedCircle(sedona.ST_GeomFromText('POLYGON ((40
180, 110 160, 180 180, 180 120, 140 90, 160 40, 80 10, 70 40, 20 50, 40
180),(60 140, 50 90, 90 140, 60 140))')))",
+ "POINT (96.953125 76.328125)");
+ verifySqlSingleRes(
+ "select sedona.ST_AsText(nearest) from
table(sedona.ST_MaximumInscribedCircle(sedona.ST_GeomFromText('POLYGON ((40
180, 110 160, 180 180, 180 120, 140 90, 160 40, 80 10, 70 40, 20 50, 40
180),(60 140, 50 90, 90 140, 60 140))')))",
+ "POINT (140 90)");
+ verifySqlSingleRes(
+ "select radius from
table(sedona.ST_MaximumInscribedCircle(sedona.ST_GeomFromText('POLYGON ((40
180, 110 160, 180 180, 180 120, 140 90, 160 40, 80 10, 70 40, 20 50, 40
180),(60 140, 50 90, 90 140, 60 140))')))",
+ 45.165845650018);
+ }
+
@Test
public void test_ST_IsValidDetail() {
registerUDTF(ST_IsValidDetail.class);
diff --git
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/ddl/UDTFDDLGenerator.java
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/ddl/UDTFDDLGenerator.java
index 43aa6176a..4350d1ade 100644
---
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/ddl/UDTFDDLGenerator.java
+++
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/ddl/UDTFDDLGenerator.java
@@ -29,6 +29,7 @@ import org.apache.sedona.snowflake.snowsql.udtfs.*;
public class UDTFDDLGenerator {
public static final Class[] udtfClz = {
+ ST_MaximumInscribedCircle.class,
ST_MinimumBoundingRadius.class,
ST_Intersection_Aggr.class,
ST_SubDivideExplode.class,
diff --git
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_MaximumInscribedCircle.java
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_MaximumInscribedCircle.java
new file mode 100644
index 000000000..c21ba71b1
--- /dev/null
+++
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/udtfs/ST_MaximumInscribedCircle.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+package org.apache.sedona.snowflake.snowsql.udtfs;
+
+import java.util.stream.Stream;
+import org.apache.sedona.common.Functions;
+import org.apache.sedona.common.utils.InscribedCircle;
+import org.apache.sedona.snowflake.snowsql.GeometrySerde;
+import org.apache.sedona.snowflake.snowsql.annotations.UDTFAnnotations;
+import org.locationtech.jts.io.ParseException;
+
[email protected](
+ name = "ST_MaximumInscribedCircle",
+ argNames = {"geometry"})
+public class ST_MaximumInscribedCircle {
+
+ public static class OutputRow {
+ public final byte[] center;
+ public final byte[] nearest;
+ public final double radius;
+
+ public OutputRow(InscribedCircle inscribedCircle) {
+ this.center = GeometrySerde.serialize(inscribedCircle.center);
+ this.nearest = GeometrySerde.serialize(inscribedCircle.nearest);
+ this.radius = inscribedCircle.radius;
+ }
+ }
+
+ public static Class getOutputClass() {
+ return OutputRow.class;
+ }
+
+ public ST_MaximumInscribedCircle() {}
+
+ public Stream<OutputRow> process(byte[] geometry) throws ParseException {
+ InscribedCircle inscribedCircle =
+ Functions.maximumInscribedCircle(GeometrySerde.deserialize(geometry));
+
+ return Stream.of(new OutputRow(inscribedCircle));
+ }
+}
diff --git
a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 6353e16f2..8107f413d 100644
--- a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -170,6 +170,7 @@ object Catalog {
function[ST_Polygon](),
function[ST_Polygonize](),
function[ST_MakePolygon](null),
+ function[ST_MaximumInscribedCircle](),
function[ST_MaxDistance](),
function[ST_GeoHash](),
function[ST_GeomFromGeoHash](null),
diff --git
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 3f33a66d0..3caadda1b 100644
---
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -20,7 +20,7 @@ package org.apache.spark.sql.sedona_sql.expressions
import org.apache.sedona.common.{Functions, FunctionsGeoTools}
import org.apache.sedona.common.sphere.{Haversine, Spheroid}
-import org.apache.sedona.common.utils.ValidDetail
+import org.apache.sedona.common.utils.{InscribedCircle, ValidDetail}
import org.apache.sedona.sql.utils.GeometrySerializer
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
@@ -974,6 +974,32 @@ case class ST_MakePolygon(inputExpressions:
Seq[Expression])
}
}
+case class ST_MaximumInscribedCircle(children: Seq[Expression])
+ extends Expression
+ with CodegenFallback {
+
+ override def eval(input: InternalRow): Any = {
+ val geometry = children.head.toGeometry(input)
+ var inscribedCircle: InscribedCircle = null
+ inscribedCircle = Functions.maximumInscribedCircle(geometry)
+
+ val serCenter = GeometrySerializer.serialize(inscribedCircle.center)
+ val serNearest = GeometrySerializer.serialize(inscribedCircle.nearest)
+ InternalRow.fromSeq(Seq(serCenter, serNearest, inscribedCircle.radius))
+ }
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]):
Expression = {
+ copy(children = newChildren)
+ }
+
+ override def nullable: Boolean = true
+
+ override def dataType: DataType = new StructType()
+ .add("center", GeometryUDT, nullable = false)
+ .add("nearest", GeometryUDT, nullable = false)
+ .add("radius", DoubleType, nullable = false)
+}
+
case class ST_MaxDistance(inputExpressions: Seq[Expression])
extends InferredExpression(Functions.maxDistance _) {
diff --git
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
index b53a79d06..e2f527021 100644
---
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
+++
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -361,6 +361,11 @@ object st_functions extends DataFrameAPI {
def ST_MakeValid(geometry: String, keepCollapsed: Boolean): Column =
wrapExpression[ST_MakeValid](geometry, keepCollapsed)
+ def ST_MaximumInscribedCircle(geometry: Column): Column =
+ wrapExpression[ST_MaximumInscribedCircle](geometry)
+ def ST_MaximumInscribedCircle(geometry: String): Column =
+ wrapExpression[ST_MaximumInscribedCircle](geometry)
+
def ST_MaxDistance(geom1: Column, geom2: Column): Column =
wrapExpression[ST_MaxDistance](geom1, geom2)
def ST_MaxDistance(geom1: String, geom2: String): Column =
diff --git
a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
index 189733033..44ceee2db 100644
---
a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
+++
b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -2073,6 +2073,25 @@ class dataFrameAPITestScala extends TestBaseScala {
assertTrue(actual)
}
+ it("Should pass ST_MaximumInscribedCircle") {
+ val baseDf = sparkSession.sql(
+ "SELECT ST_GeomFromWKT('POLYGON ((40 180, 110 160, 180 180, 180 120,
140 90, 160 40, 80 10, 70 40, 20 50, 40 180),(60 140, 50 90, 90 140, 60 140))')
AS geom")
+ val actual: Row =
baseDf.select(ST_MaximumInscribedCircle("geom")).first().getAs[Row](0)
+ val expected = Row(
+ sparkSession
+ .sql("SELECT ST_GeomFromWKT('POINT (96.953125 76.328125)')")
+ .first()
+ .get(0)
+ .asInstanceOf[Geometry],
+ sparkSession
+ .sql("SELECT ST_GeomFromWKT('POINT (140 90)')")
+ .first()
+ .get(0)
+ .asInstanceOf[Geometry],
+ 45.165845650018)
+ assertTrue(actual.equals(expected))
+ }
+
it("Should pass ST_IsValidTrajectory") {
val baseDf = sparkSession.sql(
"SELECT ST_GeomFromText('LINESTRING M (0 0 1, 0 1 2)') as geom1,
ST_GeomFromText('LINESTRING M (0 0 1, 0 1 1)') as geom2")
diff --git
a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index 76743f619..4bf957955 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -3230,6 +3230,25 @@ class functionTestScala
}
+ it("Should pass ST_MaximumInscribedCircle") {
+ val baseDf = sparkSession.sql(
+ "SELECT ST_GeomFromWKT('POLYGON ((40 180, 110 160, 180 180, 180 120, 140
90, 160 40, 80 10, 70 40, 20 50, 40 180),(60 140, 50 90, 90 140, 60 140))') AS
geom")
+ val actual: Row =
baseDf.selectExpr("ST_MaximumInscribedCircle(geom)").first().getAs[Row](0)
+ val expected = Row(
+ sparkSession
+ .sql("SELECT ST_GeomFromWKT('POINT (96.953125 76.328125)')")
+ .first()
+ .get(0)
+ .asInstanceOf[Geometry],
+ sparkSession
+ .sql("SELECT ST_GeomFromWKT('POINT (140 90)')")
+ .first()
+ .get(0)
+ .asInstanceOf[Geometry],
+ 45.165845650018)
+ assertTrue(actual.equals(expected))
+ }
+
it("Should pass ST_IsValidTrajectory") {
val baseDf = sparkSession.sql(
"SELECT ST_GeomFromText('LINESTRING M (0 0 1, 0 1 2)') as geom1,
ST_GeomFromText('LINESTRING M (0 0 1, 0 1 1)') as geom2")