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-348] Implement ST_MakePoint #950

Merged
merged 15 commits into from
Aug 11, 2023
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
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to include param m?

: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 @@ -353,6 +354,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
Loading