Skip to content

Commit 8c65dc4

Browse files
authored
[GH-2830] Adds Geography dual-dispatch to ST_NumGeometries (#2851)
1 parent b1fd9fc commit 8c65dc4

6 files changed

Lines changed: 104 additions & 6 deletions

File tree

common/src/main/java/org/apache/sedona/common/geography/Functions.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ public static int nPoints(Geography g) {
8585
return toJTS(g).getNumPoints();
8686
}
8787

88+
/** Return the number of sub-geometries in a geography (1 for singles). */
89+
public static int numGeometries(Geography g) {
90+
if (g == null) return 0;
91+
return toJTS(g).getNumGeometries();
92+
}
93+
8894
/** Return the geometry type string of a geography, prefixed with "ST_". */
8995
public static String geometryType(Geography g) {
9096
if (g == null) return null;

common/src/test/java/org/apache/sedona/common/Geography/FunctionTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,37 @@ public void nPoints_polygon() throws ParseException {
148148
assertEquals(5, Functions.nPoints(g));
149149
}
150150

151+
@Test
152+
public void numGeometries_point() throws ParseException {
153+
Geography g = Constructors.geogFromWKT("POINT (1 2)", 4326);
154+
assertEquals(1, Functions.numGeometries(g));
155+
}
156+
157+
@Test
158+
public void numGeometries_polygon() throws ParseException {
159+
Geography g = Constructors.geogFromWKT("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", 4326);
160+
assertEquals(1, Functions.numGeometries(g));
161+
}
162+
163+
@Test
164+
public void numGeometries_multipoint() throws ParseException {
165+
Geography g = Constructors.geogFromWKT("MULTIPOINT ((0 0), (1 1), (2 2))", 4326);
166+
assertEquals(3, Functions.numGeometries(g));
167+
}
168+
169+
@Test
170+
public void numGeometries_multipolygon() throws ParseException {
171+
Geography g =
172+
Constructors.geogFromWKT(
173+
"MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((2 2, 3 2, 3 3, 2 3, 2 2)))", 4326);
174+
assertEquals(2, Functions.numGeometries(g));
175+
}
176+
177+
@Test
178+
public void numGeometries_nullHandling() {
179+
assertEquals(0, Functions.numGeometries(null));
180+
}
181+
151182
@Test
152183
public void geometryType_point() throws ParseException {
153184
Geography g = Constructors.geogFromWKT("POINT (1 2)", 4326);

docs/api/sql/geography/Geography-Functions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ These functions operate on geography type objects.
4646
| [ST_Envelope](Geography-Functions/ST_Envelope.md) | Geography | Return the bounding box (envelope) of a geography. Supports anti-meridian splitting. | v1.8.0 |
4747
| [ST_GeometryType](Geography-Functions/ST_GeometryType.md) | String | Return the type of a geography as a string (e.g., "ST_Point", "ST_Polygon"). | v1.9.1 |
4848
| [ST_NPoints](Geography-Functions/ST_NPoints.md) | Integer | Return the number of points (vertices) in a geography. | v1.9.0 |
49+
| [ST_NumGeometries](Geography-Functions/ST_NumGeometries.md) | Integer | Return the number of sub-geometries in a geography (1 for single geometries). | v1.9.1 |
4950
| [ST_Distance](Geography-Functions/ST_Distance.md) | Double | Return the minimum geodesic distance between two geographies in meters. | v1.9.0 |
5051
| [ST_Contains](Geography-Functions/ST_Contains.md) | Boolean | Test whether geography A fully contains geography B. | v1.9.0 |
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
20+
# ST_NumGeometries
21+
22+
Introduction: Returns the number of sub-geometries in a geography object. Returns 1 for single geometries (Point, LineString, Polygon).
23+
24+
Format:
25+
26+
`ST_NumGeometries (A: Geography)`
27+
28+
Return type: `Integer`
29+
30+
Since: `v1.9.1`
31+
32+
SQL Example
33+
34+
```sql
35+
SELECT ST_NumGeometries(ST_GeogFromWKT('MULTIPOINT ((0 0), (1 1), (2 2))'));
36+
```
37+
38+
Output:
39+
40+
```
41+
3
42+
```

spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,16 +1055,19 @@ private[apache] object ST_IsRing {
10551055
}
10561056

10571057
/**
1058-
* Returns the number of Geometries. If geometry is a GEOMETRYCOLLECTION (or MULTI*) return the
1059-
* number of geometries, for single geometries will return 1
1058+
* Returns the number of sub-geometries. For a GEOMETRYCOLLECTION or MULTI* input, returns the
1059+
* number of component geometries; for single geometries returns 1. Supports both Geometry (JTS)
1060+
* and Geography (S2) inputs via InferredExpression dual dispatch.
10601061
*
1061-
* This method implements the SQL/MM specification. SQL-MM 3: 9.1.4
1062+
* For Geometry inputs this method implements the SQL/MM specification (SQL-MM 3: 9.1.4).
10621063
*
10631064
* @param inputExpressions
1064-
* Geometry
1065+
* Geometry or Geography
10651066
*/
10661067
private[apache] case class ST_NumGeometries(inputExpressions: Seq[Expression])
1067-
extends InferredExpression(Functions.numGeometries _) {
1068+
extends InferredExpression(
1069+
inferrableFunction1(Functions.numGeometries),
1070+
inferrableFunction1(org.apache.sedona.common.geography.Functions.numGeometries)) {
10681071

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

spark/common/src/test/scala/org/apache/sedona/sql/geography/GeographyFunctionTest.scala

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class GeographyFunctionTest extends TestBaseScala {
7878
}
7979
}
8080

81-
// ─── Level 1: ST_NPoints, ST_GeometryType, ST_AsText ───────────────────
81+
// ─── Level 1: ST_NPoints, ST_NumGeometries, ST_GeometryType, ST_AsText ──
8282

8383
describe("Level 1: Structural") {
8484

@@ -89,6 +89,21 @@ class GeographyFunctionTest extends TestBaseScala {
8989
assertEquals(3, row.getInt(0))
9090
}
9191

92+
it("ST_NumGeometries single") {
93+
val row = sparkSession
94+
.sql("SELECT ST_NumGeometries(ST_GeogFromWKT('POINT (1 2)', 4326)) AS n")
95+
.first()
96+
assertEquals(1, row.getInt(0))
97+
}
98+
99+
it("ST_NumGeometries multipoint") {
100+
val row = sparkSession
101+
.sql(
102+
"SELECT ST_NumGeometries(ST_GeogFromWKT('MULTIPOINT ((0 0), (1 1), (2 2))', 4326)) AS n")
103+
.first()
104+
assertEquals(3, row.getInt(0))
105+
}
106+
92107
it("ST_GeometryType point") {
93108
val row = sparkSession
94109
.sql("SELECT ST_GeometryType(ST_GeogFromWKT('POINT (1 2)', 4326)) AS t")

0 commit comments

Comments
 (0)