Skip to content

Commit

Permalink
[SEDONA-348] Implement ST_MakePoint (apache#950)
Browse files Browse the repository at this point in the history
  • Loading branch information
yyy1000 authored and Kontinuation committed Aug 16, 2023
1 parent 3efc022 commit e09da6d
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 2 deletions.
18 changes: 18 additions & 0 deletions common/src/main/java/org/apache/sedona/common/Constructors.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.apache.sedona.common.utils.FormatUtils;
import org.apache.sedona.common.utils.GeoHashDecoder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateXYZM;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.PrecisionModel;
Expand Down Expand Up @@ -104,6 +105,23 @@ public static Geometry point(double x, double y) {
return geometryFactory.createPoint(new Coordinate(x, y));
}

public static Geometry makePoint(Double x, Double y, Double z, Double m){
GeometryFactory geometryFactory = new GeometryFactory();
if (x == null || y == null) {
return null;
}
if (z == null && m == null) {
return geometryFactory.createPoint(new Coordinate(x, y));
}
if (z != null && m == null) {
return geometryFactory.createPoint(new Coordinate(x, y, z));
}
if (z == null) {
return geometryFactory.createPoint(new CoordinateXYZM(x, y, 0, m));
}
return geometryFactory.createPoint(new CoordinateXYZM(x, y, z, m));
}

/**
* Creates a point from the given coordinate.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.apache.sedona.common;

import org.junit.Test;
import org.apache.sedona.common.utils.GeomUtils;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.io.ParseException;
Expand Down Expand Up @@ -106,6 +107,34 @@ public void point() {
assertEquals("POINT (1 2)", point.toText());
}

@Test
public void point2d() {
Geometry point = Constructors.makePoint(1.0d, 2.0d, null, null);

assertTrue(point instanceof Point);
assertEquals(0, point.getSRID());
assertEquals("POINT (1 2)", Functions.asWKT(point));
}

@Test
public void point3DZ() {
Geometry point = Constructors.makePoint(1.0d, 2.0d, 3.0d, null);

assertTrue(point instanceof Point);
assertEquals(0, point.getSRID());
assertEquals("POINT Z(1 2 3)", Functions.asWKT(point));
}

@Test
public void point4DZM() {
Geometry point = Constructors.makePoint(1.0d, 2.0d, 3.0d, 4.0d);

assertTrue(point instanceof Point);
assertTrue(GeomUtils.isMeasuredGeometry(point));
assertEquals(0, point.getSRID());
assertEquals("POINT ZM(1 2 3 4)", Functions.asWKT(point));
}

@Test
public void pointZ() {
Geometry point = Constructors.pointZ(0.0d, 1.0d, 2.0d, 4326);
Expand Down
44 changes: 44 additions & 0 deletions docs/api/flink/Constructor.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,50 @@ Output:
LINESTRING (-74.0428197 40.6867969, -74.0421975 40.6921336, -74.050802 40.6912794)
```

## ST_MakePoint

Introduction: Creates a 2D, 3D Z or 4D ZM Point geometry. Use ST_MakePointM to make points with XYM coordinates. Z and M values are optional.

Format: `ST_MakePoint (X:decimal, Y:decimal, Z:decimal, M:decimal)`

Since: `v1.5.0`

Example:

```sql
SELECT ST_AsText(ST_MakePoint(1.2345, 2.3456));
```

Output:

```
POINT (1.2345 2.3456)
```

Example:

```sql
SELECT ST_AsText(ST_MakePoint(1.2345, 2.3456, 3.4567));
```

Output:

```
POINT Z (1.2345 2.3456 3.4567)
```

Example:

```sql
SELECT ST_AsText(ST_MakePoint(1.2345, 2.3456, 3.4567, 4));
```

Output:

```
POINT ZM (1.2345 2.3456 3.4567 4)
```

## ST_MLineFromText

Introduction: Construct a MultiLineString from Text and Optional SRID
Expand Down
44 changes: 44 additions & 0 deletions docs/api/sql/Constructor.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,50 @@ Output:
LINESTRING (-74.0428197 40.6867969, -74.0421975 40.6921336, -74.050802 40.6912794)
```

## ST_MakePoint

Introduction: Creates a 2D, 3D Z or 4D ZM Point geometry. Use ST_MakePointM to make points with XYM coordinates. Z and M values are optional.

Format: `ST_MakePoint (X:decimal, Y:decimal, Z:decimal, M:decimal)`

Since: `v1.5.0`

Example:

```sql
SELECT ST_AsText(ST_MakePoint(1.2345, 2.3456));
```

Output:

```
POINT (1.2345 2.3456)
```

Example:

```sql
SELECT ST_AsText(ST_MakePoint(1.2345, 2.3456, 3.4567));
```

Output:

```
POINT Z (1.2345 2.3456 3.4567)
```

Example:

```sql
SELECT ST_AsText(ST_MakePoint(1.2345, 2.3456, 3.4567, 4));
```

Output:

```
POINT ZM (1.2345 2.3456 3.4567 4)
```

## ST_MLineFromText

Introduction: Construct a MultiLineString from Wkt. If srid is not set, it defaults to 0 (unknown).
Expand Down
3 changes: 1 addition & 2 deletions flink/src/main/java/org/apache/sedona/flink/Catalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
import org.apache.flink.table.functions.UserDefinedFunction;
import org.apache.sedona.flink.expressions.*;

import scala.Function;

public class Catalog {
public static UserDefinedFunction[] getFuncs() {
return new UserDefinedFunction[]{
Expand All @@ -27,6 +25,7 @@ public static UserDefinedFunction[] getFuncs() {
new Constructors.ST_Point(),
new Constructors.ST_PointZ(),
new Constructors.ST_PointFromText(),
new Constructors.ST_MakePoint(),
new Constructors.ST_LineStringFromText(),
new Constructors.ST_LineFromText(),
new Constructors.ST_PolygonFromText(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ public Geometry eval(@DataTypeHint("String") String s) throws ParseException {
}
}

public static class ST_MakePoint extends ScalarFunction {
@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
public Geometry eval(@DataTypeHint("Double") Double x, @DataTypeHint("Double") Double y) throws ParseException {
return org.apache.sedona.common.Constructors.makePoint(x, y, null, null);
}

@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
public Geometry eval(@DataTypeHint("Double") Double x, @DataTypeHint("Double") Double y, @DataTypeHint("Double") Double z) throws ParseException {
return org.apache.sedona.common.Constructors.makePoint(x, y, z, null);
}

@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
public Geometry eval(@DataTypeHint("Double") Double x, @DataTypeHint("Double") Double y, @DataTypeHint("Double") Double z, @DataTypeHint("Double") Double m) throws ParseException {
return org.apache.sedona.common.Constructors.makePoint(x, y, z, m);
}
}

public static class ST_LineFromText extends ScalarFunction {
@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
public Geometry eval(@DataTypeHint("String") String lineString,
Expand Down
27 changes: 27 additions & 0 deletions flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,33 @@ public void testPointZ() {
assertEquals(5.0, result.getCoordinate().getZ(), 1e-6);
}

@Test
public void testMakePoint() {
List<Row> data = new ArrayList<>();
data.add(Row.of(1.0, 2.0, "point"));
String[] colNames = new String[]{"x", "y", "name_point"};

TypeInformation<?>[] colTypes = {
BasicTypeInfo.DOUBLE_TYPE_INFO,
BasicTypeInfo.DOUBLE_TYPE_INFO,
BasicTypeInfo.STRING_TYPE_INFO};
RowTypeInfo typeInfo = new RowTypeInfo(colTypes, colNames);
DataStream<Row> ds = env.fromCollection(data).returns(typeInfo);
Table pointTable = tableEnv.fromDataStream(ds);

Table geomTable = pointTable
.select(call(Constructors.ST_MakePoint.class.getSimpleName(), $(colNames[0]), $(colNames[1]))
.as(colNames[2]));

String result = first(geomTable)
.getFieldAs(colNames[2])
.toString();

String expected = "POINT (1 2)";

assertEquals(expected, result);
}

@Test
public void testPointFromText() {
List<Row> data = createPointWKT(testDataSize);
Expand Down
24 changes: 24 additions & 0 deletions python/sedona/sql/st_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"ST_LineStringFromText",
"ST_Point",
"ST_PointFromText",
"ST_MakePoint"
"ST_PolygonFromEnvelope",
"ST_PolygonFromText",
"ST_MLineFromText",
Expand Down Expand Up @@ -217,6 +218,29 @@ def ST_PointFromText(coords: ColumnOrName, delimiter: ColumnOrName) -> Column:
"""
return _call_constructor_function("ST_PointFromText", (coords, delimiter))

@validate_argument_types
def ST_MakePoint(x: ColumnOrNameOrNumber, y: ColumnOrNameOrNumber, z: Optional[ColumnOrNameOrNumber] = None, m: Optional[ColumnOrNameOrNumber] = None) -> Column:
"""Generate a 2D, 3D Z or 4D ZM Point geometry. If z is None then a 2D point is generated.
This function doesn't support M coordinates for creating a 4D ZM Point in Dataframe API.
:param x: Either a number or numeric column representing the X coordinate of a point.
:type x: ColumnOrNameOrNumber
:param y: Either a number or numeric column representing the Y coordinate of a point.
:type y: ColumnOrNameOrNumber
:param z: Either a number or numeric column representing the Z coordinate of a point, if None then a 2D point is generated, defaults to None
:type z: ColumnOrNameOrNumber
:param m: Either a number or numeric column representing the M coordinate of a point, if None then a point without M coordinate is generated, defaults to None
:type m: ColumnOrNameOrNumber
:return: Point geometry column generated from the coordinate values.
:rtype: Column
"""
args = (x, y)
if z is not None:
args = args + (z,)
if m is not None:
args = args + (m,)
return _call_constructor_function("ST_MakePoint", (args))


@validate_argument_types
def ST_PolygonFromEnvelope(min_x: ColumnOrNameOrNumber, min_y: ColumnOrNameOrNumber, max_x: ColumnOrNameOrNumber, max_y: ColumnOrNameOrNumber) -> Column:
Expand Down
17 changes: 17 additions & 0 deletions python/tests/sql/test_constructor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ def test_st_point_z(self):
point_df = self.spark.sql("SELECT ST_PointZ(1.2345, 2.3456, 3.4567)")
assert point_df.count() == 1

def test_st_makepoint(self):
point_csv_df = self.spark.read.format("csv").\
option("delimiter", ",").\
option("header", "false").\
load(csv_point_input_location)

point_csv_df.createOrReplaceTempView("pointtable")

point_df = self.spark.sql("select ST_MakePoint(cast(pointtable._c0 as Decimal(24,20)), cast(pointtable._c1 as Decimal(24,20))) as arealandmark from pointtable")
assert point_df.count() == 1000

point_df = self.spark.sql("SELECT ST_AsText(ST_MakePoint(1.2345, 2.3456, 3.4567))")
assert point_df.take(1)[0][0] == "POINT Z(1.2345 2.3456 3.4567)"

point_df = self.spark.sql("SELECT ST_AsText(ST_MakePoint(1.2345, 2.3456, 3.4567, 4))")
assert point_df.take(1)[0][0] == "POINT ZM(1.2345 2.3456 3.4567 4)"

def test_st_point_from_text(self):
point_csv_df = self.spark.read.format("csv").\
option("delimiter", ",").\
Expand Down
2 changes: 2 additions & 0 deletions python/tests/sql/test_dataframe_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
(stc.ST_LineStringFromText, ("multiple_point", lambda: f.lit(',')), "constructor", "", "LINESTRING (0 0, 1 0, 1 1, 0 0)"),
(stc.ST_Point, ("x", "y"), "constructor", "", "POINT (0 1)"),
(stc.ST_PointFromText, ("single_point", lambda: f.lit(',')), "constructor", "", "POINT (0 1)"),
(stc.ST_MakePoint, ("x", "y", "z"), "constructor", "", "POINT Z (0 1 2)"),
(stc.ST_PolygonFromEnvelope, ("minx", "miny", "maxx", "maxy"), "min_max_x_y", "", "POLYGON ((0 1, 0 3, 2 3, 2 1, 0 1))"),
(stc.ST_PolygonFromEnvelope, (0.0, 1.0, 2.0, 3.0), "null", "", "POLYGON ((0 1, 0 3, 2 3, 2 1, 0 1))"),
(stc.ST_PolygonFromText, ("multiple_point", lambda: f.lit(',')), "constructor", "", "POLYGON ((0 0, 1 0, 1 1, 0 0))"),
Expand Down Expand Up @@ -357,6 +358,7 @@ def base_df(self, request):
return TestDataFrameAPI.spark.sql("SELECT null").selectExpr(
"0.0 AS x",
"1.0 AS y",
"2.0 AS z",
"'0.0,1.0' AS single_point",
"'0.0,0.0,1.0,0.0,1.0,1.0,0.0,0.0' AS multiple_point",
f"X'{wkb}' AS wkb",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ object Catalog {
function[ST_GeomFromKML](),
function[ST_CoordDim](),
function[ST_Point](),
function[ST_MakePoint](null, null),
function[ST_PointZ](0),
function[ST_PolygonFromEnvelope](),
function[ST_Contains](),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ case class ST_PointZ(inputExpressions: Seq[Expression])
}
}

case class ST_MakePoint(inputExpressions: Seq[Expression])
extends InferredExpression(nullTolerantInferrableFunction4(Constructors.makePoint)) {

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

/**
* Return a polygon given minX,minY,maxX,maxY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ object st_constructors extends DataFrameAPI {

def ST_PointZ(x: Double, y: Double, z: Double, srid: Int): Column = wrapExpression[ST_PointZ](x, y, z, srid)

def ST_MakePoint(x: Column, y: Column): Column = wrapExpression[ST_MakePoint](x, y, null, null)
def ST_MakePoint(x: String, y: String): Column = wrapExpression[ST_MakePoint](x, y, null, null)
def ST_MakePoint(x: Double, y: Double): Column = wrapExpression[ST_MakePoint](x, y, null, null)

def ST_MakePoint(x: Column, y: Column, z: Column): Column = wrapExpression[ST_MakePoint](x, y, z, null)
def ST_MakePoint(x: String, y: String, z: String): Column = wrapExpression[ST_MakePoint](x, y, z, null)
def ST_MakePoint(x: Double, y: Double, z: Double): Column = wrapExpression[ST_MakePoint](x, y, z, null)

def ST_MakePoint(x: Column, y: Column, z: Column, m: Column): Column = wrapExpression[ST_MakePoint](x, y, z, m)
def ST_MakePoint(x: String, y: String, z: String, m: String): Column = wrapExpression[ST_MakePoint](x, y, z, m)
def ST_MakePoint(x: Double, y: Double, z: Double, m: Double): Column = wrapExpression[ST_MakePoint](x, y, z, m)

def ST_PointFromText(coords: Column, delimiter: Column): Column = wrapExpression[ST_PointFromText](coords, delimiter)
def ST_PointFromText(coords: String, delimiter: String): Column = wrapExpression[ST_PointFromText](coords, delimiter)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@ class constructorTestScala extends TestBaseScala {
assert(pointDf.count() == 1)
}

it("Passed ST_MakePoint") {

var pointCsvDF = sparkSession.read.format("csv").option("delimiter", ",").option("header", "false").load(csvPointInputLocation)

pointCsvDF.createOrReplaceTempView("pointtable")
var pointDf = sparkSession.sql("select ST_MakePoint(cast(pointtable._c0 as Decimal(24,20)), cast(pointtable._c1 as Decimal(24,20))) as arealandmark from pointtable")
assert(pointDf.count() == 1000)

}

it("Passed ST_MakePoint 3D Z and 4D ZM Point") {
val pointDf3D = sparkSession.sql("SELECT ST_AsText(ST_MakePoint(1, 2, 3))")
assert(pointDf3D.take(1)(0).get(0).asInstanceOf[String] == "POINT Z(1 2 3)")
val pointDf4D = sparkSession.sql("SELECT ST_AsText(ST_MakePoint(1, 2, 3, 4))")
assert(pointDf4D.take(1)(0).get(0).asInstanceOf[String] == "POINT ZM(1 2 3 4)")
}

it("Passed ST_MakePoint null safety") {
val pointDf = sparkSession.sql("SELECT ST_MakePoint(null, null)")
assert(pointDf.count() == 1)
}

it("Passed ST_PointZ") {
val pointDf = sparkSession.sql("SELECT ST_PointZ(1.2345, 2.3456, 3.4567)")
assert(pointDf.count() == 1)
Expand Down
Loading

0 comments on commit e09da6d

Please sign in to comment.