Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SEDONA-239] Implement ST_NumPoints #853

Merged
merged 13 commits into from
Jun 8, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,14 @@ private static Coordinate[] extractCoordinates(Geometry geometry) {
return coordinates;
}

public static int numPoints(Geometry geometry) throws Exception {
String geometryType = geometry.getGeometryType();
if (!(Geometry.TYPENAME_LINESTRING.equalsIgnoreCase(geometryType))) {
throw new IllegalArgumentException("Unsupported geometry type: " + geometryType + ", only LineString geometry is supported.");
}
return geometry.getNumPoints();
}

public static Geometry geometricMedian(Geometry geometry, double tolerance, int maxIter, boolean failIfNotConverged) throws Exception {
String geometryType = geometry.getGeometryType();
if(!(Geometry.TYPENAME_POINT.equals(geometryType) || Geometry.TYPENAME_MULTIPOINT.equals(geometryType))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,4 +565,20 @@ public void spheroidLength() {
GeometryCollection geometryCollection = GEOMETRY_FACTORY.createGeometryCollection(new Geometry[] {point, line, multiLineString});
assertEquals(3.0056262514183864E7, Spheroid.length(geometryCollection), 0.1);
}

@Test
public void numPoints() throws Exception{
LineString line = GEOMETRY_FACTORY.createLineString(coordArray(0, 1, 1, 0, 2, 0));
int expected = 3;
int actual = Functions.numPoints(line);
assertEquals(expected, actual);
}

@Test
public void numPointsUnsupported() throws Exception {
Polygon polygon = GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 0, 90, 0, 0));
String expected = "Unsupported geometry type: " + "Polygon" + ", only LineString geometry is supported.";
Exception e = assertThrows(IllegalArgumentException.class, () -> Functions.numPoints(polygon));
assertEquals(expected, e.getMessage());
}
}
22 changes: 22 additions & 0 deletions docs/api/flink/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,28 @@ SELECT ST_NumInteriorRings(ST_GeomFromText('POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0),

Output: `1`

## ST_NumPoints

Introduction: Returns number of points in a LineString.

!!!note
If any other geometry is provided as an argument, an IllegalArgumentException is thrown.
Example:
`SELECT ST_NumPoints(ST_GeomFromWKT('MULTIPOINT ((0 0), (1 1), (0 1), (2 2))'))`

Output: `IllegalArgumentException: Unsupported geometry type: MultiPoint, only LineString geometry is supported.`

Format: `ST_NumPoints(geom: geometry)`

Since: `v1.4.1`

Example:
```sql
SELECT ST_NumPoints(ST_GeomFromText('LINESTRING(1 2, 1 3)'))
```

Output: `2`

## ST_PointN

Introduction: Return the Nth point in a single linestring or circular linestring in the geometry. Negative values are counted backwards from the end of the LineString, so that -1 is the last point. Returns NULL if there is no linestring in the geometry.
Expand Down
20 changes: 20 additions & 0 deletions docs/api/sql/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,26 @@ SELECT ST_NumInteriorRings(ST_GeomFromText('POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0),

Output: `1`

## ST_NumPoints
Introduction: Returns number of points in a LineString

!!!note
If any other geometry is provided as an argument, an IllegalArgumentException is thrown.
Example:
`SELECT ST_NumPoints(ST_GeomFromWKT('MULTIPOINT ((0 0), (1 1), (0 1), (2 2))'))`

Output: `IllegalArgumentException: Unsupported geometry type: MultiPoint, only LineString geometry is supported.`
Format: `ST_NumPoints(geom: geometry)`

Since: `v1.4.1`

Spark SQL example:
```sql
SELECT ST_NumPoints(ST_GeomFromText('LINESTRING(0 1, 1 0, 2 0)'))
```

Output: `3`

## ST_PointN

Introduction: Return the Nth point in a single linestring or circular linestring in the geometry. Negative values are counted backwards from the end of the LineString, so that -1 is the last point. Returns NULL if there is no linestring in the geometry.
Expand Down
3 changes: 2 additions & 1 deletion flink/src/main/java/org/apache/sedona/flink/Catalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ public static UserDefinedFunction[] getFuncs() {
new Functions.ST_LineFromMultiPoint(),
new Functions.ST_Split(),
new Functions.ST_S2CellIDs(),
new Functions.ST_GeometricMedian()
new Functions.ST_GeometricMedian(),
new Functions.ST_NumPoints()
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,4 +574,12 @@ public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.j

}

public static class ST_NumPoints extends ScalarFunction {
@DataTypeHint(value = "Integer")
public int eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) throws Exception {
Geometry geometry = (Geometry) o;
return org.apache.sedona.common.Functions.numPoints(geometry);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -691,4 +691,12 @@ public void testGeometricMedianParamsFull() throws ParseException {
0, expected.compareTo(actual, COORDINATE_SEQUENCE_COMPARATOR));
}

@Test
public void testNumPoints() {
Integer expected = 3;
Table pointTable = tableEnv.sqlQuery("SELECT ST_NumPoints(ST_GeomFromWKT('LINESTRING(0 1, 1 0, 2 0)'))");
Integer actual = (Integer) first(pointTable).getField(0);
assertEquals(expected, actual);
}

}
10 changes: 10 additions & 0 deletions python/sedona/sql/st_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"ST_Z",
"ST_ZMax",
"ST_ZMin",
"ST_NumPoints"
]


Expand Down Expand Up @@ -1231,3 +1232,12 @@ def ST_ZMin(geometry: ColumnOrName) -> Column:
:rtype: Column
"""
return _call_st_function("ST_ZMin", geometry)

def ST_NumPoints(geometry: ColumnOrName) -> Column:
"""Return the number of points in a LineString
:param geometry: Geometry column to get number of points from.
:type geometry: ColumnOrName
:return: Number of points in a LineString as an integer column
:rtype: Column
"""
return _call_st_function("ST_NumPoints", geometry)
1 change: 1 addition & 0 deletions python/tests/sql/test_dataframe_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
(stf.ST_YMax, ("geom",), "triangle_geom", "", 1.0),
(stf.ST_YMin, ("geom",), "triangle_geom", "", 0.0),
(stf.ST_Z, ("b",), "two_points", "", 4.0),
(stf.ST_NumPoints, ("line",), "linestring_geom", "", 6),

# predicates
(stp.ST_Contains, ("geom", lambda: f.expr("ST_Point(0.5, 0.25)")), "triangle_geom", "", True),
Expand Down
5 changes: 5 additions & 0 deletions python/tests/sql/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,3 +1074,8 @@ def test_st_s2_cell_ids(self):
# test null case
cell_ids = self.spark.sql("select ST_S2CellIDs(null, 6)").take(1)[0][0]
assert cell_ids is None

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]
expected = 3
assert expected == actual
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ object Catalog {
function[ST_DistanceSpheroid](),
function[ST_AreaSpheroid](),
function[ST_LengthSpheroid](),
function[ST_NumPoints](),
// Expression for rasters
function[RS_NormalizedDifference](),
function[RS_Mean](),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -981,3 +981,10 @@ case class ST_LengthSpheroid(inputExpressions: Seq[Expression])
}
}

case class ST_NumPoints(inputExpressions: Seq[Expression])
extends InferredUnaryExpression(Functions.numPoints) with FoldableExpression {
protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
copy(inputExpressions = newChildren)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -301,4 +301,8 @@ object st_functions extends DataFrameAPI {
def ST_LengthSpheroid(a: Column): Column = wrapExpression[ST_LengthSpheroid](a)

def ST_LengthSpheroid(a: String): Column = wrapExpression[ST_LengthSpheroid](a)

def ST_NumPoints(geometry: Column): Column = wrapExpression[ST_NumPoints](geometry)

def ST_NumPoints(geometry: String): Column = wrapExpression[ST_NumPoints](geometry)
}
Original file line number Diff line number Diff line change
Expand Up @@ -949,5 +949,13 @@ class dataFrameAPITestScala extends TestBaseScala {
val expectedResult = 10018754.171394622
assertEquals(expectedResult, actualResult, 0.1)
}

it("Passed ST_NumPoints") {
val lineDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 1, 1 0, 2 0)') AS geom")
val df = lineDf.select(ST_NumPoints("geom"))
val actualResult = df.take(1)(0).getInt(0)
val expectedResult = 3
assert(actualResult == expectedResult)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1909,4 +1909,16 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
assertEquals(expected, actual, 0.1)
}
}

it("Should pass ST_NumPoints") {
val geomTestCases = Map(
("'LINESTRING (0 1, 1 0, 2 0)'") -> "3"
)
for (((geom), expectedResult) <- geomTestCases) {
val df = sparkSession.sql(s"SELECT ST_NumPoints(ST_GeomFromWKT($geom)), " + s"$expectedResult")
val actual = df.take(1)(0).get(0).asInstanceOf[Int]
val expected = df.take(1)(0).get(1).asInstanceOf[java.lang.Integer].intValue()
assertEquals(expected, actual)
}
}
}