Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions common/src/main/java/org/apache/sedona/common/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2125,6 +2125,20 @@ public static Long[] h3Uncompact(long[] cells, int resolution) {
.toArray(new Long[0]);
}

/**
* Returns the H3 cell center in longitude/latitude order, with SRID 0 as in h3ToGeom.
*
* @param cell H3 cell index
* @return the center point in degrees
* @throws IllegalArgumentException if cell is not a valid H3 cell index
*/
public static Geometry h3ToPoint(long cell) {
if (!H3Utils.h3.isValidCell(cell)) {
throw new IllegalArgumentException("Invalid H3 cell index: " + cell);
}
return new GeometryFactory().createPoint(H3Utils.cellToCoordinate(cell));
}

/**
* gets the polygon for each h3 index provided
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,41 @@ public void testUnaryUnion() throws ParseException {
assertEquals(expected, actual);
}

@Test
public void h3ToPoint() {
Geometry center = Functions.h3ToPoint(0x8928308280fffffL);
assertTrue(center instanceof Point);
assertEquals(-122.41845932318309, center.getCoordinate().x, 1e-10);
assertEquals(37.776702349435695, center.getCoordinate().y, 1e-10);
assertEquals(0, center.getSRID());
}

@Test
public void h3ToPointAcrossResolutions() {
for (int resolution = 0; resolution <= 15; resolution++) {
long cell = H3Utils.coordinateToCell(new Coordinate(10.75, 59.91), resolution);
Geometry center = Functions.h3ToPoint(cell);
assertEquals(cell, H3Utils.coordinateToCell(center.getCoordinate(), resolution));
for (long pentagon : H3Utils.h3.getPentagons(resolution)) {
Geometry pentagonCenter = Functions.h3ToPoint(pentagon);
assertEquals(
pentagon, H3Utils.coordinateToCell(pentagonCenter.getCoordinate(), resolution));
assertEquals(0, pentagonCenter.getSRID());
}
}
}

@Test
public void h3ToPointUsesH3Center() {
long cell = H3Utils.coordinateToCell(new Coordinate(10.75, 59.91), 0);
Geometry center = Functions.h3ToPoint(cell);
Geometry polygon = Functions.h3ToGeom(new long[] {cell})[0];
assertTrue(center.distance(polygon.getCentroid()) > 1e-6);
assertEquals(polygon.getSRID(), center.getSRID());
assertThrows(IllegalArgumentException.class, () -> Functions.h3ToPoint(0L));
assertThrows(IllegalArgumentException.class, () -> Functions.h3ToPoint(-1L));
}

@Test
public void testH3ToGeom() {
Geometry target =
Expand Down
1 change: 1 addition & 0 deletions docs/api/flink/Geometry-Functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ These functions work with spatial indexing systems including Bing Tiles, H3, Hil
| [ST_H3ToChildren](Spatial-Indexing/ST_H3ToChildren.md) | `Array<Long>` | Returns the children of an H3 cell at the requested resolution. | v2.0.0 |
| [ST_H3ToGeom](Spatial-Indexing/ST_H3ToGeom.md) | `Array<Geometry>` | Return the result of H3 function [cellsToMultiPolygon(cells)](https://h3geo.org/docs/api/regions#cellstolinkedmultipolygon--cellstomultipolygon). | v1.6.0 |
| [ST_H3ToParent](Spatial-Indexing/ST_H3ToParent.md) | Long | Returns the parent of an H3 cell at the requested resolution. | v2.0.0 |
| [ST_H3ToPoint](Spatial-Indexing/ST_H3ToPoint.md) | Geometry | Returns the center of an H3 cell as a point in longitude/latitude order with SRID 0. | v2.0.0 |
| [ST_H3Uncompact](Spatial-Indexing/ST_H3Uncompact.md) | `Array<Long>` | Expands H3 cells to the requested resolution. | v2.0.0 |
| [ST_HilbertDistance](Spatial-Indexing/ST_HilbertDistance.md) | Long | Maps the midpoint of a geometry's envelope to an unsigned Hilbert-curve address within a supplied extent. | v2.0.0 |
| [ST_S2CellIDs](Spatial-Indexing/ST_S2CellIDs.md) | `Array<Long>` | Cover the geometry with Google S2 Cells, return the corresponding cell IDs with the given level. The level indicates the [size of cells](https://s2geometry.io/resources/s2cell_statistics.html). With... | v1.4.0 |
Expand Down
44 changes: 44 additions & 0 deletions docs/api/flink/Spatial-Indexing/ST_H3ToPoint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!--
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_H3ToPoint

Introduction: Returns the center of an H3 cell as a point geometry. This uses H3's cell center, which can differ from the centroid of the cell polygon because of the grid projection.

Coordinates are longitude/latitude (X/Y) in degrees. The result has SRID 0, consistent with `ST_H3ToGeom`. To assign WGS 84, use `ST_SetSRID(ST_H3ToPoint(cell), 4326)`.

Null input returns null. Invalid cell indexes raise an error.

Format: `ST_H3ToPoint(cell: Long)`

Return type: `Geometry` (Point)

Since: `v2.0.0`

SQL Example

```sql
SELECT ST_AsText(ST_H3ToPoint(617700169958293503))
```

Output (coordinates rounded):

```text
POINT (-122.41845932318309 37.776702349435695)
```
1 change: 1 addition & 0 deletions docs/api/sql/Geometry-Functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ These functions work with spatial indexing systems including Bing Tiles, H3, Hil
| [ST_H3ToChildren](Spatial-Indexing/ST_H3ToChildren.md) | `Array<Long>` | Returns the children of an H3 cell at the requested resolution. | v2.0.0 |
| [ST_H3ToGeom](Spatial-Indexing/ST_H3ToGeom.md) | `Array<Geometry>` | Return the result of H3 function [cellsToMultiPolygon(cells)](https://h3geo.org/docs/api/regions#cellstolinkedmultipolygon--cellstomultipolygon). | v1.6.0 |
| [ST_H3ToParent](Spatial-Indexing/ST_H3ToParent.md) | Long | Returns the parent of an H3 cell at the requested resolution. | v2.0.0 |
| [ST_H3ToPoint](Spatial-Indexing/ST_H3ToPoint.md) | Geometry | Returns the center of an H3 cell as a point in longitude/latitude order with SRID 0. | v2.0.0 |
| [ST_H3Uncompact](Spatial-Indexing/ST_H3Uncompact.md) | `Array<Long>` | Expands H3 cells to the requested resolution. | v2.0.0 |
| [ST_HilbertDistance](Spatial-Indexing/ST_HilbertDistance.md) | Long | Maps the midpoint of a geometry's envelope to an unsigned Hilbert-curve address within a supplied extent. | v2.0.0 |
| [ST_S2CellIDs](Spatial-Indexing/ST_S2CellIDs.md) | `Array<Long>` | Cover the geometry with Google S2 Cells, return the corresponding cell IDs with the given level. The level indicates the [size of cells](https://s2geometry.io/resources/s2cell_statistics.html). With... | v1.4.0 |
Expand Down
44 changes: 44 additions & 0 deletions docs/api/sql/Spatial-Indexing/ST_H3ToPoint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!--
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_H3ToPoint

Introduction: Returns the center of an H3 cell as a point geometry. This uses H3's cell center, which can differ from the centroid of the cell polygon because of the grid projection.

Coordinates are longitude/latitude (X/Y) in degrees. The result has SRID 0, consistent with `ST_H3ToGeom`. To assign WGS 84, use `ST_SetSRID(ST_H3ToPoint(cell), 4326)`.

Null input returns null. Invalid cell indexes raise an error.

Format: `ST_H3ToPoint(cell: Long)`

Return type: `Geometry` (Point)

Since: `v2.0.0`

SQL Example

```sql
SELECT ST_AsText(ST_H3ToPoint(617700169958293503))
```

Output (coordinates rounded):

```text
POINT (-122.41845932318309 37.776702349435695)
```
1 change: 1 addition & 0 deletions flink/src/main/java/org/apache/sedona/flink/Catalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public static UserDefinedFunction[] getFuncs() {
new Functions.ST_H3CellDistance(),
new Functions.ST_H3CellIDs(),
new Functions.ST_H3KRing(),
new Functions.ST_H3ToPoint(),
new Functions.ST_H3ToGeom(),
new Functions.ST_H3ToParent(),
new Functions.ST_H3ToChildren(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2941,6 +2941,19 @@ public Long[] eval(
}
}

public static class ST_H3ToPoint extends ScalarFunction {
@DataTypeHint(
value = "RAW",
rawSerializer = GeometryTypeSerializer.class,
bridgedTo = Geometry.class)
public Geometry eval(@DataTypeHint("BIGINT") Long cell) {
if (cell == null) {
return null;
}
return org.apache.sedona.common.Functions.h3ToPoint(cell);
}
}

public static class ST_H3ToGeom extends ScalarFunction {
@DataTypeHint(
value = "RAW",
Expand Down
16 changes: 16 additions & 0 deletions flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2352,6 +2352,22 @@ public void testH3KRing() {
assert full.containsAll(exactRing);
}

@Test
public void testH3ToPoint() {
Geometry center =
(Geometry)
first(
tableEnv.sqlQuery(
"SELECT ST_H3ToPoint(cell) FROM (VALUES (617700169958293503)) AS cells(cell)"))
.getField(0);
assertNotNull(center);
assertEquals("Point", center.getGeometryType());
assertEquals(-122.41845932318309, center.getCoordinate().x, 1e-10);
assertEquals(37.776702349435695, center.getCoordinate().y, 1e-10);
assertEquals(0, center.getSRID());
assertNull(first(tableEnv.sqlQuery("SELECT ST_H3ToPoint(CAST(NULL AS BIGINT))")).getField(0));
}

@Test
public void testH3ToGeom() {
Table pointTable =
Expand Down
11 changes: 11 additions & 0 deletions python/sedona/spark/sql/st_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,17 @@ def ST_H3KRing(
return _call_st_function("ST_H3KRing", args)


@validate_argument_types
def ST_H3ToPoint(cell: Union[ColumnOrName, int]) -> Column:
"""Return an H3 cell's center as a point in longitude/latitude order, with SRID 0.

:param cell: H3 cell index
:return: center point in degrees
:rtype: Geometry
"""
return _call_st_function("ST_H3ToPoint", cell)


@validate_argument_types
def ST_H3ToGeom(cells: Union[ColumnOrName, list]) -> Column:
"""Cover Geometry with H3 Cells and return a List of Long type cell IDs
Expand Down
16 changes: 16 additions & 0 deletions python/tests/sql/test_dataframe_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2004,6 +2004,22 @@ def run_spatial_query():
for future in futures:
future.result()

def test_st_h3_to_point(self):
cell = 617700169958293503
df = self.spark.createDataFrame([(cell,), (None,)], "cell long")
rows = df.select(
stf.ST_H3ToPoint("cell"),
stf.ST_H3ToPoint(f.col("cell")),
stf.ST_H3ToPoint(cell),
).collect()
for point in rows[0]:
assert point.geom_type == "Point"
assert abs(point.x + 122.41845932318309) < 1e-10
assert abs(point.y - 37.776702349435695) < 1e-10
assert rows[1][0] is None
assert rows[1][1] is None
assert rows[1][2] == rows[0][2]

def test_st_h3_to_parent(self):
df = self.spark.createDataFrame([(614552609325318143,)], ["cell"])
parent = df.select(stf.ST_H3ToParent("cell", 5)).first()[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ object Catalog extends AbstractCatalog with Logging {
function[ST_H3CellDistance](),
function[ST_H3CellIDs](),
function[ST_H3KRing](),
function[ST_H3ToPoint](),
function[ST_H3ToGeom](),
function[ST_H3ToParent](),
function[ST_H3ToChildren](),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1718,6 +1718,15 @@ private[apache] case class ST_H3KRing(inputExpressions: Seq[Expression])
}
}

private[apache] case class ST_H3ToPoint(inputExpressions: Seq[Expression])
extends InferredExpression(Functions.h3ToPoint _)
with FoldableExpression {

protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
copy(inputExpressions = newChildren)
}
}

private[apache] case class ST_H3ToGeom(inputExpressions: Seq[Expression])
extends InferredExpression(Functions.h3ToGeom _)
with FoldableExpression {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ object st_functions {
def ST_H3KRing(cell: Long, k: Integer, exactRing: Boolean): Column =
wrapExpression[ST_H3KRing](cell, k, exactRing)

def ST_H3ToPoint(cell: Column): Column = wrapExpression[ST_H3ToPoint](cell)

def ST_H3ToPoint(cell: Long): Column = wrapExpression[ST_H3ToPoint](cell)

def ST_H3ToGeom(cellIds: Column): Column = wrapExpression[ST_H3ToGeom](cellIds)

def ST_H3ToGeom(cellIds: Array[Long]): Column = wrapExpression[ST_H3ToGeom](cellIds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2116,6 +2116,20 @@ class dataFrameAPITestScala extends TestBaseScala {
}
}

it("Passed ST_H3ToPoint") {
val cell = 617700169958293503L
val row = sparkSession
.sql(s"SELECT $cell AS cell")
.select(ST_H3ToPoint(col("cell")), ST_H3ToPoint(cell))
.first()
(0 until 2).foreach { i =>
val point = row.getAs[org.locationtech.jts.geom.Geometry](i)
assert(math.abs(point.getCoordinate.x + 122.41845932318309) < 1e-10)
assert(math.abs(point.getCoordinate.y - 37.776702349435695) < 1e-10)
assert(point.getSRID == 0)
}
}

it("Passed ST_H3ToCenterChild") {
val cell = 601041811137363967L
val result = sparkSession
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,33 @@ import scala.collection.mutable
class STH3Functions extends TestBaseScala with Matchers with GeometrySample with GivenWhenThen {
import sparkSession.implicits._

describe("ST_H3ToPoint") {
it("returns the H3 center as longitude/latitude with SRID 0") {
val row = sparkSession
.sql("""
|SELECT ST_X(ST_H3ToPoint(617700169958293503)),
|ST_Y(ST_H3ToPoint(617700169958293503)),
|ST_SRID(ST_H3ToPoint(617700169958293503)),
|ST_GeometryType(ST_H3ToPoint(617700169958293503))
|""".stripMargin)
.first()
assert(math.abs(row.getDouble(0) + 122.41845932318309) < 1e-10)
assert(math.abs(row.getDouble(1) - 37.776702349435695) < 1e-10)
assert(row.getInt(2) == 0)
assert(row.getString(3) == "ST_Point")
}

it("handles columns and propagates nulls") {
val rows = Seq[java.lang.Long](617700169958293503L, null)
.toDF("cell")
.selectExpr("ST_H3ToPoint(cell)")
.collect()
assert(rows(0).getAs[org.locationtech.jts.geom.Geometry](0).getSRID == 0)
assert(rows(1).isNullAt(0))
assert(sparkSession.sql("SELECT ST_H3ToPoint(CAST(NULL AS BIGINT))").first().isNullAt(0))
}
}

describe("should pass ST_H3CellIDs") {

it("should return null while using ST_H3CellIDs when geometry is empty") {
Expand Down
Loading