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

Reply via email to