From 1ab77dea0708a6bf65aea9892f52ef04338f9500 Mon Sep 17 00:00:00 2001 From: Furqaanahmed Khan Date: Mon, 25 Mar 2024 15:06:36 -0400 Subject: [PATCH 1/2] feat: change ST_H3ToGeom behavior --- .../org/apache/sedona/common/Functions.java | 57 ++++++++++--------- .../apache/sedona/common/FunctionsTest.java | 12 ++++ docs/api/flink/Function.md | 14 ++--- docs/api/sql/Function.md | 17 +++--- .../sedona/flink/expressions/Functions.java | 4 +- .../org/apache/sedona/flink/FunctionTest.java | 9 ++- python/tests/sql/test_function.py | 12 ++-- .../sedona/sql/dataFrameAPITestScala.scala | 2 +- 8 files changed, 74 insertions(+), 53 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 8cf0c88134..fcdddcb41c 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -16,6 +16,7 @@ import com.google.common.geometry.S2CellId; import com.uber.h3core.exceptions.H3Exception; +import com.uber.h3core.util.LatLng; import org.apache.commons.lang3.tuple.Pair; import org.apache.sedona.common.geometryObjects.Circle; import org.apache.sedona.common.sphere.Spheroid; @@ -45,13 +46,8 @@ import org.locationtech.jts.precision.GeometryPrecisionReducer; import org.locationtech.jts.simplify.TopologyPreservingSimplifier; import org.wololo.jts2geojson.GeoJSONWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.LinkedHashSet; -import java.util.Collection; + +import java.util.*; import java.util.stream.Collectors; import static com.google.common.geometry.S2.DBL_EPSILON; @@ -941,28 +937,37 @@ public static Long[] h3KRing(long cell, int k, boolean exactDistance) { } /** - * get the neighbor cells of the input cell by h3.gridDisk function + * gets the polygon for each h3 index provided * @param cells: the set of cells - * @return Multiple Polygons reversed + * @return An Array of Polygons reversed */ - public static Geometry h3ToGeom(long[] cells) { + public static Geometry[] h3ToGeom(long[] cells) { GeometryFactory geomFactory = new GeometryFactory(); - Collection h3 = Arrays.stream(cells).boxed().collect(Collectors.toList()); - return geomFactory.createMultiPolygon( - H3Utils.h3.cellsToMultiPolygon(h3, true).stream().map( - shellHoles -> { - List rings = shellHoles.stream().map( - shell -> geomFactory.createLinearRing(shell.stream().map(latLng -> new Coordinate(latLng.lng, latLng.lat)).toArray(Coordinate[]::new)) - ).collect(Collectors.toList()); - LinearRing shell = rings.remove(0); - if (rings.isEmpty()) { - return geomFactory.createPolygon(shell); - } else { - return geomFactory.createPolygon(shell, rings.toArray(new LinearRing[0])); - } - } - ).toArray(Polygon[]::new) - ); + List h3 = Arrays.stream(cells).boxed().collect(Collectors.toList()); + List polygons = new ArrayList<>(); + + for (int j = 0; j < h3.size(); j++) { + for (List> shellHoles : H3Utils.h3.cellsToMultiPolygon(Collections.singleton(h3.get(j)), true)) { + List rings = new ArrayList<>(); + for (List shell : shellHoles) { + Coordinate[] coordinates = new Coordinate[shell.size()]; + for (int i = 0; i < shell.size(); i++) { + LatLng latLng = shell.get(i); + coordinates[i] = new Coordinate(latLng.lng, latLng.lat); + } + LinearRing ring = geomFactory.createLinearRing(coordinates); + rings.add(ring); + } + + LinearRing shell = rings.remove(0); + if (rings.isEmpty()) { + polygons.add(geomFactory.createPolygon(shell)); + } else { + polygons.add(geomFactory.createPolygon(shell, rings.toArray(new LinearRing[0]))); + } + } + } + return polygons.toArray(new Polygon[0]); } // create static function named simplifyPreserveTopology 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 0d1bd4cef1..44383907fc 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -573,6 +573,18 @@ public void testS2ToGeom() { assertTrue(polygons[100].intersects(target)); } + @Test + public void testH3ToGeom() { + Geometry target = GEOMETRY_FACTORY.createPolygon( + coordArray(0.1, 0.1, 0.5, 0.1, 1.0, 0.3, 1.0, 1.0, 0.1, 1.0, 0.1, 0.1) + ); + Long[] cellIds = Functions.h3CellIDs(target, 4, true); + Geometry[] polygons = Functions.h3ToGeom(Arrays.stream(cellIds).mapToLong(Long::longValue).toArray()); + assertTrue(polygons[0].intersects(target)); + assertTrue(polygons[11].intersects(target)); + assertTrue(polygons[20].intersects(target)); + } + /** * Test H3CellIds: pass in all the types of geometry, test if the function cover */ diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index 66e4247aef..3ee9117d38 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -1446,13 +1446,16 @@ Output: ## ST_H3ToGeom -Introduction: return the result of H3 function [cellsToMultiPolygon(cells)](https://h3geo.org/docs/api/regions#cellstolinkedmultipolygon--cellstomultipolygon). +Introduction: Return the result of H3 function [cellsToMultiPolygon(cells)](https://h3geo.org/docs/api/regions#cellstolinkedmultipolygon--cellstomultipolygon). -Reverse the uber h3 cells to MultiPolygon object composed by the geometry hexagons. +Converts an array of Uber H3 cell indices into an array of Polygon geometries, where each polygon represents a hexagonal H3 cell. + +!!!Hint + To convert a Polygon array to MultiPolygon, use [ST_Collect](#st_collect). However, the result may be an invalid geometry. Apply [ST_MakeValid](#st_makevalid) to the `ST_Collect` output to ensure a valid MultiPolygon. Format: `ST_H3ToGeom(cells: Array[Long])` -Since: `v1.5.0` +Since: `v1.6.0` Example: ```sql @@ -1461,10 +1464,7 @@ SELECT ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromWKT('POINT(1 2)'), 8, true)[0], 1, tr Output: ``` -|st_h3togeom(st_h3cellids(st_geomfromwkt(POINT(1 2), 0), 8, true)) | -+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -|MULTIPOLYGON (((1.0057629565404935 1.9984665139177658, 1.0037116327309032 2.001832524914011, 0.9997277993570498 2.0011632704656668, 0.9977951427833285 1.99712822839324, 0.9998461908217768 1.9937621529331915, 1.0038301712104252 1.9944311839965554, 1.0057629565404935 1.9984665139177658)))| -+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +[POLYGON ((1.0057629565405093 1.9984665139177547, 1.0037116327309097 2.0018325249140068, 0.999727799357053 2.001163270465665, 0.9977951427833316 1.997128228393235, 0.9998461908217928 1.993762152933182, 1.0038301712104316 1.9944311839965523, 1.0057629565405093 1.9984665139177547))] ``` ## ST_HausdorffDistance diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index 9f94e591ac..236c68d761 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -1454,25 +1454,26 @@ Output: ## ST_H3ToGeom -Introduction: return the result of H3 function [cellsToMultiPolygon(cells)](https://h3geo.org/docs/api/regions#cellstolinkedmultipolygon--cellstomultipolygon). +Introduction: Return the result of H3 function [cellsToMultiPolygon(cells)](https://h3geo.org/docs/api/regions#cellstolinkedmultipolygon--cellstomultipolygon). -Reverse the uber h3 cells to MultiPolygon object composed by the geometry hexagons. +Converts an array of Uber H3 cell indices into an array of Polygon geometries, where each polygon represents a hexagonal H3 cell. + + +!!!Hint + To convert a Polygon array to MultiPolygon, use [ST_Collect](#st_collect). However, the result may be an invalid geometry. Apply [ST_MakeValid](#st_makevalid) to the `ST_Collect` output to ensure a valid MultiPolygon. Format: `ST_H3ToGeom(cells: Array[Long])` -Since: `v1.5.0` +Since: `v1.6.0` -SQL Example +Example: ```sql SELECT ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromWKT('POINT(1 2)'), 8, true)[0], 1, true)) ``` Output: ``` -|st_h3togeom(st_h3cellids(st_geomfromwkt(POINT(1 2), 0), 8, true)) | -+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -|MULTIPOLYGON (((1.0057629565404935 1.9984665139177658, 1.0037116327309032 2.001832524914011, 0.9997277993570498 2.0011632704656668, 0.9977951427833285 1.99712822839324, 0.9998461908217768 1.9937621529331915, 1.0038301712104252 1.9944311839965554, 1.0057629565404935 1.9984665139177658)))| -+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +[POLYGON ((1.0057629565405093 1.9984665139177547, 1.0037116327309097 2.0018325249140068, 0.999727799357053 2.001163270465665, 0.9977951427833316 1.997128228393235, 0.9998461908217928 1.993762152933182, 1.0038301712104316 1.9944311839965523, 1.0057629565405093 1.9984665139177547))] ``` ## ST_HausdorffDistance diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java index 34bea764e8..6f01e7d25e 100644 --- a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java +++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java @@ -898,8 +898,8 @@ public Long[] eval(@DataTypeHint("BIGINT") Long cell, } public static class ST_H3ToGeom extends ScalarFunction { - @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) - public Geometry eval(@DataTypeHint(value = "ARRAY") Long[] cells + @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry[].class) + public Geometry[] eval(@DataTypeHint(value = "ARRAY") Long[] cells ) { return org.apache.sedona.common.Functions.h3ToGeom(Arrays.stream(cells).mapToLong(Long::longValue).toArray()); } diff --git a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java index 63f03160b9..71c31b114a 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -1125,9 +1125,12 @@ public void testH3KRing() { @Test public void testH3ToGeom() { - Table pointTable = tableEnv.sqlQuery("select ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromWKT('POINT(1 2)'), 8, true))"); - Geometry exact = (Geometry) Objects.requireNonNull(first(pointTable).getField(0)); - assertEquals(exact.getNumGeometries(), 1); + Table pointTable = tableEnv.sqlQuery("select ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromWKT('POLYGON ((0.1 0.1, 0.5 0.1, 1 0.3, 1 1, 0.1 1, 0.1 0.1))'), 4, true))"); + Geometry target = (Geometry) first(tableEnv.sqlQuery("select ST_GeomFromWKT('POLYGON ((0.1 0.1, 0.5 0.1, 1 0.3, 1 1, 0.1 1, 0.1 0.1))')")).getField(0); + Geometry[] actual = (Geometry[]) first(pointTable).getField(0); + assertTrue(actual[0].intersects(target)); + assertTrue(actual[11].intersects(target)); + assertTrue(actual[20].intersects(target)); } @Test diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index 99ea4e2762..4d2e96ac51 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -1290,21 +1290,21 @@ def test_st_h3_kring(self): def test_st_h3_togeom(self): df = self.spark.sql(""" SELECT - ST_Contains( - ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromText('POLYGON((-1 0, 1 0, 0 0, 0 1, -1 0))'), 6, true)), + ST_Intersects( + ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromText('POLYGON((-1 0, 1 0, 0 0, 0 1, -1 0))'), 6, true))[10], ST_GeomFromText('POLYGON((-1 0, 1 0, 0 0, 0 1, -1 0))') ), - ST_Contains( - ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromText('POLYGON((-1 0, 1 0, 0 0, 0 1, -1 0))'), 6, false)), + ST_Intersects( + ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromText('POLYGON((-1 0, 1 0, 0 0, 0 1, -1 0))'), 6, false))[25], ST_GeomFromText('POLYGON((-1 0, 1 0, 0 0, 0 1, -1 0))') ), ST_Intersects( - ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromText('POLYGON((-1 0, 1 0, 0 0, 0 1, -1 0))'), 6, false)), + ST_H3ToGeom(ST_H3CellIDs(ST_GeomFromText('POLYGON((-1 0, 1 0, 0 0, 0 1, -1 0))'), 6, false))[50], ST_GeomFromText('POLYGON((-1 0, 1 0, 0 0, 0 1, -1 0))') ) """) res1, res2, res3 = df.take(1)[0] - assert res1 and not res2 and res3 + assert res1 and res2 and res3 def test_st_numPoints(self): actual = self.spark.sql("SELECT ST_NumPoints(ST_GeomFromText('LINESTRING(0 1, 1 0, 2 0)'))").take(1)[0][0] 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 95370b7785..5cec6e34a5 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 @@ -1103,7 +1103,7 @@ class dataFrameAPITestScala extends TestBaseScala { it("Passed ST_H3CellIDs") { val baseDF = sparkSession.sql("SELECT ST_GeomFromWKT('Polygon ((0 0, 1 2, 2 2, 3 2, 5 0, 4 0, 3 1, 2 1, 1 0, 0 0))') as geom") - val df = baseDF.select(ST_H3ToGeom(ST_H3CellIDs("geom", 6, true))) + val df = baseDF.select(ST_MakeValid(ST_Collect(ST_H3ToGeom(ST_H3CellIDs("geom", 6, true))))) val actualResult = df.take(1)(0).getAs[Geometry](0) val targetShape = baseDF.take(1)(0).getAs[Polygon](0); assert (actualResult.contains(targetShape)) From 142c784581139e9fea5b4aead0dddb37565a3e44 Mon Sep 17 00:00:00 2001 From: Furqaanahmed Khan Date: Mon, 25 Mar 2024 15:21:19 -0400 Subject: [PATCH 2/2] fix: lint issues --- docs/api/sql/Function.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index 236c68d761..62e598c71f 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -1458,7 +1458,6 @@ Introduction: Return the result of H3 function [cellsToMultiPolygon(cells)](http Converts an array of Uber H3 cell indices into an array of Polygon geometries, where each polygon represents a hexagonal H3 cell. - !!!Hint To convert a Polygon array to MultiPolygon, use [ST_Collect](#st_collect). However, the result may be an invalid geometry. Apply [ST_MakeValid](#st_makevalid) to the `ST_Collect` output to ensure a valid MultiPolygon.