You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2024/05/29 03:42:29 UTC
(sedona) branch master updated: [SEDONA-569][SEDONA-570][SEDONA-572] Add ST_PointZM, ST_PointM, ST_PointFromWKB (#1440)
This is an automated email from the ASF dual-hosted git repository.
jiayu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git
The following commit(s) were added to refs/heads/master by this push:
new 7fc3270b7 [SEDONA-569][SEDONA-570][SEDONA-572] Add ST_PointZM, ST_PointM, ST_PointFromWKB (#1440)
7fc3270b7 is described below
commit 7fc3270b71a60c758f9a101db7720b82f7c44766
Author: Jia Yu <ji...@apache.org>
AuthorDate: Tue May 28 20:42:23 2024 -0700
[SEDONA-569][SEDONA-570][SEDONA-572] Add ST_PointZM, ST_PointM, ST_PointFromWKB (#1440)
* [TASK-212] Add ST_PointZM (#143)
* Add ST_PointZM
* fix snowflake test
* fix snowflake test
* Remove snowflake support
* Add flink test
* Add Dataframe api test; remove show()
* fix python test
* Add scala dataframe api tests
* fix test
* Update versions
* [TASK-278] Add ST_PointM (#145)
* init
* Implement ST_PointM
* Update docs
* remove snowflake test
* update snowflake docs
* Remove snowflake registration for ST_PointM
* Add dataframe api tests
* fix test
* fix test
* [TASK-247] Add ST_PointFromWKB (#150)
* init
* init 2
* add flink test
* add scala, python, snowflake tests
* add docs
* Fix scala implementation
* Fix test
* fix test
* fix test
* add tests
* Fix scala implementation; add tests
* Fix docs
* Add test
* Fix test
* Update versions
---------
Co-authored-by: Pranav Toggi <pr...@gmail.com>
---
.../org/apache/sedona/common/Constructors.java | 48 +++++++++-
.../org/apache/sedona/common/ConstructorsTest.java | 18 ++++
.../org/apache/sedona/common/FunctionsTest.java | 19 ++++
docs/api/flink/Constructor.md | 81 ++++++++++++++++
docs/api/snowflake/vector-data/Constructor.md | 31 ++++++-
docs/api/sql/Constructor.md | 81 ++++++++++++++++
.../main/java/org/apache/sedona/flink/Catalog.java | 3 +
.../sedona/flink/expressions/Constructors.java | 57 ++++++++++++
.../org/apache/sedona/flink/ConstructorTest.java | 102 ++++++++++++++++++++-
python/sedona/sql/st_constructors.py | 52 +++++++++++
python/tests/sql/test_constructor_test.py | 4 +
python/tests/sql/test_dataframe_api.py | 16 +++-
.../sedona/snowflake/snowsql/TestConstructors.java | 15 +++
.../org/apache/sedona/snowflake/snowsql/UDFs.java | 14 +++
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 3 +
.../sql/sedona_sql/expressions/Constructors.scala | 66 +++++++++++++
.../sedona_sql/expressions/st_constructors.scala | 26 ++++++
.../apache/sedona/sql/constructorTestScala.scala | 59 ++++++++++++
.../apache/sedona/sql/dataFrameAPITestScala.scala | 31 ++++++-
19 files changed, 711 insertions(+), 15 deletions(-)
diff --git a/common/src/main/java/org/apache/sedona/common/Constructors.java b/common/src/main/java/org/apache/sedona/common/Constructors.java
index f348b2017..6dd348c01 100644
--- a/common/src/main/java/org/apache/sedona/common/Constructors.java
+++ b/common/src/main/java/org/apache/sedona/common/Constructors.java
@@ -17,11 +17,7 @@ import org.apache.sedona.common.enums.FileDataSplitter;
import org.apache.sedona.common.enums.GeometryType;
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;
+import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.io.WKTReader;
@@ -70,6 +66,19 @@ public class Constructors {
return new WKBReader().read(wkb);
}
+ public static Geometry pointFromWKB(byte[] wkb) throws ParseException {
+ return pointFromWKB(wkb, 0);
+ }
+
+ public static Geometry pointFromWKB(byte[] wkb, int srid) throws ParseException {
+ GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
+ Geometry geom = new WKBReader(geometryFactory).read(wkb);
+ if (!(geom instanceof Point)) {
+ return null;
+ }
+ return geom;
+ }
+
public static Geometry mLineFromText(String wkt, int srid) throws ParseException {
if (wkt == null || !wkt.startsWith("MULTILINESTRING")) {
return null;
@@ -136,6 +145,35 @@ public class Constructors {
return geometryFactory.createPoint(new Coordinate(x, y, z));
}
+ /**
+ * Creates a point from the given coordinate.
+ *
+ * @param x the x value
+ * @param y the y value
+ * @param m the m value
+ * @param srid Set to 0 if unknown
+ * @return The point geometry
+ */
+ public static Geometry pointM(double x, double y, double m, int srid) {
+ GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
+ return geometryFactory.createPoint(new CoordinateXYZM(x, y, 0, m));
+ }
+
+ /**
+ * Creates a point from the given coordinate.
+ *
+ * @param x the x value
+ * @param y the y value
+ * @param z the z value
+ * @param m the m value
+ * @param srid Set to 0 if unknown
+ * @return The point geometry
+ */
+ public static Geometry pointZM(double x, double y, double z, double m, int srid) {
+ GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
+ return geometryFactory.createPoint(new CoordinateXYZM(x, y, z, m));
+ }
+
public static Geometry geomFromText(String geomString, String geomFormat, GeometryType geometryType) {
FileDataSplitter fileDataSplitter = FileDataSplitter.getFileDataSplitter(geomFormat);
FormatUtils<Geometry> formatMapper = new FormatUtils<>(fileDataSplitter, false, geometryType);
diff --git a/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java b/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
index d7b92f3a7..7abbcb306 100644
--- a/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
@@ -143,4 +143,22 @@ public class ConstructorsTest {
assertEquals(4326, point.getSRID());
assertEquals("POINT Z(0 1 2)", Functions.asWKT(point));
}
+
+ @Test
+ public void pointM() {
+ Geometry point = Constructors.pointM(0.0d, 1.0d, 2.0d, 4326);
+
+ assertTrue(point instanceof Point);
+ assertEquals(4326, point.getSRID());
+ assertEquals("SRID=4326;POINT ZM(0 1 0 2)", Functions.asEWKT(point));
+ }
+
+ @Test
+ public void pointZM() {
+ Geometry point = Constructors.pointZM(0.0d, 1.0d, 2.0d, 10.0, 4326);
+
+ assertTrue(point instanceof Point);
+ assertEquals(4326, point.getSRID());
+ assertEquals("POINT ZM(0 1 2 10)", Functions.asWKT(point));
+ }
}
diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
index 9107670aa..65a63484b 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -133,6 +133,25 @@ public class FunctionsTest extends TestBase {
assertEquals(expected, geometry);
}
+ @Test
+ public void pointFromWKB() throws Exception{
+ Geometry geometry = GEOMETRY_FACTORY.createPoint(new Coordinate(1.0, 2.0));
+ byte[] wkbGeom = Functions.asWKB(geometry);
+ Geometry result = Constructors.pointFromWKB(wkbGeom);
+ assertEquals(geometry, result);
+
+ geometry = GEOMETRY_FACTORY.createPoint(new Coordinate(1.0, 2.0, 3.0));
+ wkbGeom = Functions.asWKB(geometry);
+ result = Constructors.pointFromWKB(wkbGeom, 4326);
+ assertEquals(geometry, result);
+ assertEquals(4326, Objects.requireNonNull(result).getSRID());
+
+ geometry = GEOMETRY_FACTORY.createLineString(coordArray(0.0, 0.0, 1.5, 1.5, 2.0, 2.0));
+ wkbGeom = Functions.asWKB(geometry);
+ result = Constructors.pointFromWKB(wkbGeom, 0);
+ assertNull(result);
+ }
+
@Test
public void splitLineStringByMultipoint() {
LineString lineString = GEOMETRY_FACTORY.createLineString(coordArray(0.0, 0.0, 1.5, 1.5, 2.0, 2.0));
diff --git a/docs/api/flink/Constructor.md b/docs/api/flink/Constructor.md
index c63a8f84c..3f26e08b7 100644
--- a/docs/api/flink/Constructor.md
+++ b/docs/api/flink/Constructor.md
@@ -480,6 +480,87 @@ Output:
POINT Z(1.2345 2.3456 3.4567)
```
+## ST_PointM
+
+Introduction: Construct a Point from X, Y and M and an optional srid. If srid is not set, it defaults to 0 (unknown).
+Must use ST_AsEWKT function to print the Z and M coordinates.
+
+Format:
+
+`ST_PointM (X: Double, Y: Double, M: Double)`
+
+`ST_PointM (X: Double, Y: Double, M: Double, srid: Integer)`
+
+Since: `v1.6.1`
+
+Example:
+
+```sql
+SELECT ST_AsEWKT(ST_PointM(1.2345, 2.3456, 3.4567))
+```
+
+Output:
+
+```
+POINT ZM(1.2345 2.3456 0 3.4567)
+```
+
+## ST_PointZM
+
+Introduction: Construct a Point from X, Y, Z, M and an optional srid. If srid is not set, it defaults to 0 (unknown).
+Must use ST_AsEWKT function to print the Z and M coordinates.
+
+Format:
+
+`ST_PointZM (X: Double, Y: Double, Z: Double, M: Double)`
+
+`ST_PointZM (X: Double, Y: Double, Z: Double, M: Double, srid: Integer)`
+
+Since: `v1.6.1`
+
+SQL Example
+
+```sql
+SELECT ST_AsEWKT(ST_PointZM(1.2345, 2.3456, 3.4567, 100))
+```
+
+Output:
+
+```
+POINT ZM(1.2345 2.3456 3.4567, 100)
+```
+
+## ST_PointFromWKB
+
+Introduction: Construct a Point geometry from WKB string or Binary and an optional SRID. This function also supports EWKB format.
+
+!!!note
+ Returns null if geometry is not of type Point.
+
+Format:
+
+`ST_PointFromWKB (Wkb: String)`
+
+`ST_PointFromWKB (Wkb: Binary)`
+
+`ST_PointFromWKB (Wkb: String, srid: Integer)`
+
+`ST_PointFromWKB (Wkb: Binary, srid: Integer)`
+
+Since: `v1.6.1`
+
+Example:
+
+```sql
+SELECT ST_PointFromWKB([01 01 00 00 00 00 00 00 00 00 00 24 40 00 00 00 00 00 00 2e 40])
+```
+
+Output:
+
+```
+POINT (10 15)
+```
+
## ST_PolygonFromEnvelope
Introduction: Construct a Polygon from MinX, MinY, MaxX, MaxY.
diff --git a/docs/api/snowflake/vector-data/Constructor.md b/docs/api/snowflake/vector-data/Constructor.md
index e7b98f9dc..796f77994 100644
--- a/docs/api/snowflake/vector-data/Constructor.md
+++ b/docs/api/snowflake/vector-data/Constructor.md
@@ -392,7 +392,7 @@ Introduction: Construct a Point from X and Y
Format: `ST_Point (X:decimal, Y:decimal)`
In `v1.4.0` an optional Z parameter was removed to be more consistent with other spatial SQL implementations.
-If you are upgrading from an older version of Sedona - please use ST_PointZ to create 3D points.
+If you are upgrading from an older version of Sedona - please use ST_PointZ or ST_PointZM to create 3D points.
SQL example:
@@ -443,6 +443,35 @@ Output:
POINT Z(1.2345 2.3456 3.4567)
```
+## ST_PointFromWKB
+
+Introduction: Construct a Point geometry from WKB string or Binary and an optional SRID. This function also supports EWKB format.
+
+!!!note
+ Returns null if geometry is not of type Point.
+
+Format:
+
+`ST_PointFromWKB (Wkb: String)`
+
+`ST_PointFromWKB (Wkb: Binary)`
+
+`ST_PointFromWKB (Wkb: String, srid: Integer)`
+
+`ST_PointFromWKB (Wkb: Binary, srid: Integer)`
+
+Example:
+
+```sql
+SELECT ST_PointFromWKB([01 01 00 00 00 00 00 00 00 00 00 24 40 00 00 00 00 00 00 2e 40])
+```
+
+Output:
+
+```
+POINT (10 15)
+```
+
## ST_PolygonFromEnvelope
Introduction: Construct a Polygon from MinX, MinY, MaxX, MaxY.
diff --git a/docs/api/sql/Constructor.md b/docs/api/sql/Constructor.md
index 9738f7024..0fdd8177d 100644
--- a/docs/api/sql/Constructor.md
+++ b/docs/api/sql/Constructor.md
@@ -542,6 +542,87 @@ Output:
POINT Z(1.2345 2.3456 3.4567)
```
+## ST_PointM
+
+Introduction: Construct a Point from X, Y and M and an optional srid. If srid is not set, it defaults to 0 (unknown).
+Must use ST_AsEWKT function to print the Z and M coordinates.
+
+Format:
+
+`ST_PointM (X: Double, Y: Double, M: Double)`
+
+`ST_PointM (X: Double, Y: Double, M: Double, srid: Integer)`
+
+Since: `v1.6.1`
+
+Example:
+
+```sql
+SELECT ST_AsEWKT(ST_PointM(1.2345, 2.3456, 3.4567))
+```
+
+Output:
+
+```
+POINT ZM(1.2345 2.3456 0 3.4567)
+```
+
+## ST_PointZM
+
+Introduction: Construct a Point from X, Y, Z, M and an optional srid. If srid is not set, it defaults to 0 (unknown).
+Must use ST_AsEWKT function to print the Z and M coordinates.
+
+Format:
+
+`ST_PointZM (X: Double, Y: Double, Z: Double, M: Double)`
+
+`ST_PointZM (X: Double, Y: Double, Z: Double, M: Double, srid: Integer)`
+
+Since: `v1.6.1`
+
+SQL Example
+
+```sql
+SELECT ST_AsEWKT(ST_PointZM(1.2345, 2.3456, 3.4567, 100))
+```
+
+Output:
+
+```
+POINT ZM(1.2345 2.3456 3.4567, 100)
+```
+
+## ST_PointFromWKB
+
+Introduction: Construct a Point geometry from WKB string or Binary and an optional SRID. This function also supports EWKB format.
+
+!!!note
+ Returns null if geometry is not of type Point.
+
+Format:
+
+`ST_PointFromWKB (Wkb: String)`
+
+`ST_PointFromWKB (Wkb: Binary)`
+
+`ST_PointFromWKB (Wkb: String, srid: Integer)`
+
+`ST_PointFromWKB (Wkb: Binary, srid: Integer)`
+
+Since: `v1.6.1`
+
+Example:
+
+```sql
+SELECT ST_PointFromWKB([01 01 00 00 00 00 00 00 00 00 00 24 40 00 00 00 00 00 00 2e 40])
+```
+
+Output:
+
+```
+POINT (10 15)
+```
+
## ST_PolygonFromEnvelope
Introduction: Construct a Polygon from MinX, MinY, MaxX, MaxY.
diff --git a/flink/src/main/java/org/apache/sedona/flink/Catalog.java b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
index bedf26a5c..0f5293337 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -24,7 +24,10 @@ public class Catalog {
new Aggregators.ST_Union_Aggr(),
new Constructors.ST_Point(),
new Constructors.ST_PointZ(),
+ new Constructors.ST_PointM(),
+ new Constructors.ST_PointZM(),
new Constructors.ST_PointFromText(),
+ new Constructors.ST_PointFromWKB(),
new Constructors.ST_MakePoint(),
new Constructors.ST_LineStringFromText(),
new Constructors.ST_LineFromText(),
diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java
index 7054e32d5..1e8f87ded 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java
@@ -22,6 +22,7 @@ import org.apache.sedona.common.utils.GeoHashDecoder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Point;
import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.gml2.GMLReader;
@@ -54,6 +55,30 @@ public class Constructors {
}
}
+ public static class ST_PointM extends ScalarFunction {
+ @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
+ public Geometry eval(@DataTypeHint("Double") Double x, @DataTypeHint("Double") Double y, @DataTypeHint("Double") Double m) throws ParseException {
+ return eval(x, y, m, 0);
+ }
+
+ @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
+ public Geometry eval(@DataTypeHint("Double") Double x, @DataTypeHint("Double") Double y, @DataTypeHint("Double") Double m, @DataTypeHint("Integer") Integer srid) throws ParseException {
+ return org.apache.sedona.common.Constructors.pointM(x, y, m, srid);
+ }
+ }
+
+ public static class ST_PointZM extends ScalarFunction {
+ @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 eval(x, y, z, m,0);
+ }
+
+ @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, @DataTypeHint("Integer") Integer srid) throws ParseException {
+ return org.apache.sedona.common.Constructors.pointZM(x, y, z, m, srid);
+ }
+ }
+
public static class ST_PointFromText extends ScalarFunction {
@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
public Geometry eval(@DataTypeHint("String") String s, @DataTypeHint("String") String inputDelimiter) throws ParseException {
@@ -193,6 +218,38 @@ public class Constructors {
}
+ public static class ST_PointFromWKB extends ScalarFunction {
+ @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
+ public Geometry eval(@DataTypeHint("String") String wkbString) throws ParseException {
+ Geometry geometry = getGeometryByFileData(wkbString, FileDataSplitter.WKB);
+ if (geometry instanceof Point) {
+ geometry.setSRID(0);
+ return geometry;
+ }
+ return null; // Return null if geometry is not a Point
+ }
+
+ @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
+ public Geometry eval(@DataTypeHint("String") String wkbString, int srid) throws ParseException {
+ Geometry geometry = getGeometryByFileData(wkbString, FileDataSplitter.WKB);
+ if (geometry instanceof Point) {
+ geometry.setSRID(srid);
+ return geometry;
+ }
+ return null; // Return null if geometry is not a Point
+ }
+
+ @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
+ public Geometry eval(@DataTypeHint("Bytes") byte[] wkb) throws ParseException {
+ return org.apache.sedona.common.Constructors.pointFromWKB(wkb, 0);
+ }
+
+ @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
+ public Geometry eval(@DataTypeHint("Bytes") byte[] wkb, int srid) throws ParseException {
+ return org.apache.sedona.common.Constructors.pointFromWKB(wkb, srid);
+ }
+ }
+
public static class ST_GeomFromGeoJSON extends ScalarFunction {
@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
public Geometry eval(@DataTypeHint("String") String geoJson) throws ParseException {
diff --git a/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java b/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
index 5470f96d0..7534dc072 100644
--- a/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/ConstructorTest.java
@@ -15,6 +15,7 @@ package org.apache.sedona.flink;
import org.apache.flink.table.api.Table;
import org.apache.flink.types.Row;
+import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.sedona.flink.expressions.Constructors;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -31,14 +32,12 @@ import java.util.List;
import static org.apache.flink.table.api.Expressions.$;
import static org.apache.flink.table.api.Expressions.call;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
import org.apache.flink.api.common.typeinfo.PrimitiveArrayTypeInfo;
import org.apache.flink.api.java.typeutils.RowTypeInfo;
-import org.apache.flink.streaming.api.datastream.DataStream;
public class ConstructorTest extends TestBase{
@@ -99,6 +98,58 @@ public class ConstructorTest extends TestBase{
assertEquals(5.0, result.getCoordinate().getZ(), 1e-6);
}
+ @Test
+ public void testPointM() {
+ List<Row> data = new ArrayList<>();
+ data.add(Row.of(2.0, 2.0, 5.0, "point"));
+ String[] colNames = new String[]{"x", "y", "m", "name_point"};
+
+ TypeInformation<?>[] colTypes = {
+ BasicTypeInfo.DOUBLE_TYPE_INFO,
+ 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_PointM.class.getSimpleName(), $(colNames[0]), $(colNames[1]), $(colNames[2]))
+ .as(colNames[3]));
+
+ Point result = first(geomTable)
+ .getFieldAs(colNames[3]);
+
+ assertEquals(5.0, result.getCoordinate().getM(), 1e-6);
+ }
+
+ @Test
+ public void testPointZM() {
+ List<Row> data = new ArrayList<>();
+ data.add(Row.of(2.0, 2.0, 5.0, 100.0, "point"));
+ String[] colNames = new String[]{"x", "y", "z", "m", "name_point"};
+
+ TypeInformation<?>[] colTypes = {
+ BasicTypeInfo.DOUBLE_TYPE_INFO,
+ BasicTypeInfo.DOUBLE_TYPE_INFO,
+ 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_PointZM.class.getSimpleName(), $(colNames[0]), $(colNames[1]), $(colNames[2]), $(colNames[3]))
+ .as(colNames[4]));
+
+ Point result = first(geomTable)
+ .getFieldAs(colNames[4]);
+
+ assertEquals(5.0, result.getCoordinate().getZ(), 1e-6);
+ assertEquals(100.0, result.getCoordinate().getM(), 1e-6);
+ }
+
@Test
public void testMakePoint() {
List<Row> data = new ArrayList<>();
@@ -266,7 +317,50 @@ public class ConstructorTest extends TestBase{
assertEquals(expectedGeom, result);
- }
+ }
+
+ @Test
+ public void testPointFromWKB() throws Exception {
+ String hexWkb1 = "010100000000000000000000000000000000000000";
+ byte[] wkbPoint1 = org.apache.commons.codec.binary.Hex.decodeHex(hexWkb1);
+ String hexWkb2 = "010100000000000000000024400000000000002e40";
+ byte[] wkbPoint2 = org.apache.commons.codec.binary.Hex.decodeHex(hexWkb2);
+ String hexWkb3 = "01030000000100000005000000000000000000e0bf000000000000e0bf000000000000e0bf000000000000e03f000000000000e03f000000000000e03f000000000000e03f000000000000e0bf000000000000e0bf000000000000e0bf";
+ byte[] wkbPolygon = org.apache.commons.codec.binary.Hex.decodeHex(hexWkb3);
+
+ List<Row> data1 = new ArrayList<>();
+ data1.add(Row.of(wkbPoint1));
+ data1.add(Row.of(wkbPoint2));
+ data1.add(Row.of(wkbPolygon));
+
+ List<Row> data2 = new ArrayList<>();
+ data2.add(Row.of(hexWkb1));
+ data2.add(Row.of(hexWkb2));
+ data2.add(Row.of(hexWkb3));
+
+ TypeInformation<?>[] colTypes1 = {PrimitiveArrayTypeInfo.BYTE_PRIMITIVE_ARRAY_TYPE_INFO};
+ TypeInformation<?>[] colTypes2 = {BasicTypeInfo.STRING_TYPE_INFO};
+ RowTypeInfo typeInfo1 = new RowTypeInfo(colTypes1, new String[]{"wkb"});
+ RowTypeInfo typeInfo2 = new RowTypeInfo(colTypes2, new String[]{"wkb"});
+
+ DataStream<Row> wkbDS1 = env.fromCollection(data1).returns(typeInfo1);
+ DataStream<Row> wkbDS2 = env.fromCollection(data2).returns(typeInfo2);
+ Table wkbTable1 = tableEnv.fromDataStream(wkbDS1, $("wkb"));
+ Table wkbTable2 = tableEnv.fromDataStream(wkbDS2, $("wkb"));
+
+ Table pointTable1 = wkbTable1.select(call(Constructors.ST_PointFromWKB.class.getSimpleName(), $("wkb")).as("point"));
+ Table pointTable2 = wkbTable2.select(call(Constructors.ST_PointFromWKB.class.getSimpleName(), $("wkb")).as("point"));
+
+ List<Row> results1 = TestBase.take(pointTable1, 3);
+ assertEquals("POINT (0 0)", results1.get(0).getField(0).toString());
+ assertEquals("POINT (10 15)", results1.get(1).getField(0).toString());
+ assertNull(results1.get(2).getField(0));
+
+ List<Row> results2 = TestBase.take(pointTable2, 3);
+ assertEquals("POINT (0 0)", results2.get(0).getField(0).toString());
+ assertEquals("POINT (10 15)", results2.get(1).getField(0).toString());
+ assertNull(results2.get(2).getField(0));
+ }
@Test
public void testGeomFromEWKB()
diff --git a/python/sedona/sql/st_constructors.py b/python/sedona/sql/st_constructors.py
index 11b561bee..fae53f49b 100644
--- a/python/sedona/sql/st_constructors.py
+++ b/python/sedona/sql/st_constructors.py
@@ -205,6 +205,44 @@ def ST_PointZ(x: ColumnOrNameOrNumber, y: ColumnOrNameOrNumber, z: ColumnOrNameO
args = (x, y, z) if srid is None else (x, y, z, srid)
return _call_constructor_function("ST_PointZ", args)
+@validate_argument_types
+def ST_PointM(x: ColumnOrNameOrNumber, y: ColumnOrNameOrNumber, m: ColumnOrNameOrNumber, srid: Optional[ColumnOrNameOrNumber] = None) -> Column:
+ """Generates a 3D point geometry column from numeric values.
+
+ :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 srid: The srid of the point. Defaults to 0 (unknown).
+ :type srid: Optional[ColumnOrNameOrNumber], optional
+ :return: Point geometry column generated from the coordinate values.
+ :rtype: Column
+ """
+ args = (x, y, m) if srid is None else (x, y, m, srid)
+ return _call_constructor_function("ST_PointM", args)
+
+@validate_argument_types
+def ST_PointZM(x: ColumnOrNameOrNumber, y: ColumnOrNameOrNumber, z: ColumnOrNameOrNumber, m: ColumnOrNameOrNumber,srid: Optional[ColumnOrNameOrNumber] = None) -> Column:
+ """Generates a 3D point geometry column from numeric values.
+
+ :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 value of a point.
+ :type m: ColumnOrNameOrNumber
+ :param srid: The srid of the point. Defaults to 0 (unknown).
+ :type srid: Optional[ColumnOrNameOrNumber], optional
+ :return: Point geometry column generated from the coordinate values.
+ :rtype: Column
+ """
+ args = (x, y, z, m) if srid is None else (x, y, z, m, srid)
+ return _call_constructor_function("ST_PointZM", args)
+
@validate_argument_types
def ST_PointFromText(coords: ColumnOrName, delimiter: ColumnOrName) -> Column:
"""Generate a point geometry column from coordinates separated by a delimiter and stored
@@ -219,6 +257,20 @@ def ST_PointFromText(coords: ColumnOrName, delimiter: ColumnOrName) -> Column:
"""
return _call_constructor_function("ST_PointFromText", (coords, delimiter))
+@validate_argument_types
+def ST_PointFromWKB(wkb: ColumnOrName, srid: Optional[ColumnOrNameOrNumber] = None) -> Column:
+ """Generate a Point geometry column from a Well-Known Binary (WKB) binary column.
+
+ :param wkb: WKB binary column to generate from.
+ :type wkb: ColumnOrName
+ :param srid: SRID to be set for geometry
+ :type srid: ColumnOrNameOrNumber
+ :return: Point Geometry column representing the WKB binary.
+ :rtype: Column
+ """
+ args = (wkb) if srid is None else (wkb, srid)
+ return _call_constructor_function("ST_PointFromWKB", args)
+
@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.
diff --git a/python/tests/sql/test_constructor_test.py b/python/tests/sql/test_constructor_test.py
index 2180972cd..cdcf114da 100644
--- a/python/tests/sql/test_constructor_test.py
+++ b/python/tests/sql/test_constructor_test.py
@@ -37,6 +37,10 @@ class TestConstructors(TestBase):
point_df = self.spark.sql("SELECT ST_PointZ(1.2345, 2.3456, 3.4567)")
assert point_df.count() == 1
+ def test_st_point_m(self):
+ point_df = self.spark.sql("SELECT ST_PointM(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", ",").\
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
index 977c460ca..be71f77ae 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -44,8 +44,8 @@ test_configurations = [
(stc.ST_GeomFromKML, ("kml",), "constructor", "", "LINESTRING (-71.16 42.26, -71.17 42.26)"),
(stc.ST_GeomFromText, ("wkt",), "linestring_wkt", "", "LINESTRING (1 2, 3 4)"),
(stc.ST_GeomFromText, ("wkt",4326), "linestring_wkt", "", "LINESTRING (1 2, 3 4)"),
- (stc.ST_GeomFromWKB, ("wkb",), "constructor", "ST_ReducePrecision(geom, 2)", "LINESTRING (-2.1 -0.35, -1.5 -0.67)"),
- (stc.ST_GeomFromEWKB, ("wkb",), "constructor", "ST_ReducePrecision(geom, 2)", "LINESTRING (-2.1 -0.35, -1.5 -0.67)"),
+ (stc.ST_GeomFromWKB, ("wkbLine",), "constructor", "ST_ReducePrecision(geom, 2)", "LINESTRING (-2.1 -0.35, -1.5 -0.67)"),
+ (stc.ST_GeomFromEWKB, ("wkbLine",), "constructor", "ST_ReducePrecision(geom, 2)", "LINESTRING (-2.1 -0.35, -1.5 -0.67)"),
(stc.ST_GeomFromWKT, ("wkt",), "linestring_wkt", "", "LINESTRING (1 2, 3 4)"),
(stc.ST_GeomFromWKT, ("wkt",4326), "linestring_wkt", "", "LINESTRING (1 2, 3 4)"),
(stc.ST_GeomFromEWKT, ("ewkt",), "linestring_ewkt", "", "LINESTRING (1 2, 3 4)"),
@@ -58,7 +58,10 @@ test_configurations = [
(stc.ST_MPolyFromText, ("mpoly", 4326), "constructor", "" , "MULTIPOLYGON (((0 0, 20 0, 20 20, 0 20, 0 0), (5 5, 5 7, 7 7, 7 5, 5 5)))"),
(stc.ST_MLineFromText, ("mline", ), "constructor", "" , "MULTILINESTRING ((1 2, 3 4), (4 5, 6 7))"),
(stc.ST_MLineFromText, ("mline", 4326), "constructor", "" , "MULTILINESTRING ((1 2, 3 4), (4 5, 6 7))"),
+ (stc.ST_PointM, ("x", "y", "m", "srid"), "x_y_z_m_srid", "ST_AsEWKT(geom)", "SRID=4326;POINT ZM(1 2 0 100.9)"),
+ (stc.ST_PointZM, ("x", "y", "z", "m", "srid"), "x_y_z_m_srid", "", "POINT Z (1 2 3)"),
(stc.ST_PointFromText, ("single_point", lambda: f.lit(',')), "constructor", "", "POINT (0 1)"),
+ (stc.ST_PointFromWKB, ("wkbPoint",), "constructor", "", "POINT (10 15)"),
(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))"),
@@ -238,6 +241,7 @@ wrong_type_configurations = [
(stc.ST_Point, ("", None)),
(stc.ST_PointFromText, (None, "")),
(stc.ST_PointFromText, ("", None)),
+ (stc.ST_PointFromWKB, (None,)),
(stc.ST_PolygonFromEnvelope, (None, "", "", "")),
(stc.ST_PolygonFromEnvelope, ("", None, "", "")),
(stc.ST_PolygonFromEnvelope, ("", "", None, "")),
@@ -399,9 +403,10 @@ class TestDataFrameAPI(TestBase):
@pytest.fixture
def base_df(self, request):
- wkb = '0102000000020000000000000084d600c00000000080b5d6bf00000060e1eff7bf00000080075de5bf'
mpoly = 'MULTIPOLYGON(((0 0 ,20 0 ,20 20 ,0 20 ,0 0 ),(5 5 ,5 7 ,7 7 ,7 5 ,5 5)))'
mline = 'MULTILINESTRING((1 2, 3 4), (4 5, 6 7))'
+ wkbLine = '0102000000020000000000000084d600c00000000080b5d6bf00000060e1eff7bf00000080075de5bf'
+ wkbPoint = '010100000000000000000024400000000000002e40'
geojson = "{ \"type\": \"Feature\", \"properties\": { \"prop\": \"01\" }, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ 0.0, 1.0 ] }},"
gml_string = "<gml:LineString srsName=\"EPSG:4269\"><gml:coordinates>-71.16,42.25 -71.17,42.25 -71.18,42.25</gml:coordinates></gml:LineString>"
kml_string = "<LineString><coordinates>-71.16,42.26 -71.17,42.26</coordinates></LineString>"
@@ -413,9 +418,10 @@ class TestDataFrameAPI(TestBase):
"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",
f"'{mpoly}' AS mpoly",
f"'{mline}' AS mline",
+ f"X'{wkbLine}' AS wkbLine",
+ f"X'{wkbPoint}' AS wkbPoint",
f"'{geojson}' AS geojson",
"'s00twy01mt' AS geohash",
f"'{gml_string}' AS gml",
@@ -431,6 +437,8 @@ class TestDataFrameAPI(TestBase):
return TestDataFrameAPI.spark.sql("SELECT 'SRID=4269;LINESTRING (1 2, 3 4)' AS ewkt")
elif request.param == "min_max_x_y":
return TestDataFrameAPI.spark.sql("SELECT 0.0 AS minx, 1.0 AS miny, 2.0 AS maxx, 3.0 AS maxy")
+ elif request.param == "x_y_z_m_srid":
+ return TestDataFrameAPI.spark.sql("SELECT 1.0 AS x, 2.0 AS y, 3.0 AS z, 100.9 AS m, 4326 AS srid")
elif request.param == "multipoint_geom":
return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('MULTIPOINT((10 40), (40 30), (20 20), (30 10))') AS multipoint")
elif request.param == "null":
diff --git a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestConstructors.java b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestConstructors.java
index 477f5f0c2..dd6e6bc4f 100644
--- a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestConstructors.java
+++ b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestConstructors.java
@@ -89,6 +89,21 @@ public class TestConstructors extends TestBase{
"POINT (0 1)"
);
}
+
+ @Test
+ public void test_ST_PointFromWKB() {
+ registerUDF("ST_PointFromWKB", byte[].class);
+ registerUDF("ST_AsEWKT", byte[].class);
+ verifySqlSingleRes(
+ "select sedona.ST_AsText(sedona.ST_PointFromWKB(ST_ASWKB(to_geometry('POINT (10.0 15.0)'))))",
+ "POINT (10 15)"
+ );
+ registerUDF("ST_PointFromWKB", byte[].class, int.class);
+ verifySqlSingleRes(
+ "select sedona.ST_AsEWKT(sedona.ST_PointFromWKB(ST_ASWKB(to_geometry('POINT (10.0 15.0)')), 4326))",
+ "SRID=4326;POINT (10 15)"
+ );
+ }
@Test
public void test_ST_GeomFromEWKB() {
registerUDF("ST_GeomFromEWKB", byte[].class);
diff --git a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
index c5f8b362c..14c00a64f 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
@@ -950,6 +950,20 @@ public class UDFs {
);
}
+ @UDFAnnotations.ParamMeta(argNames = {"wkb"})
+ public static byte[] ST_PointFromWKB(byte[] wkb) throws ParseException {
+ return GeometrySerde.serialize(
+ Constructors.pointFromWKB(wkb, 0)
+ );
+ }
+
+ @UDFAnnotations.ParamMeta(argNames = {"wkb", "srid"})
+ public static byte[] ST_PointFromWKB(byte[] wkb, int srid) throws ParseException {
+ return GeometrySerde.serialize(
+ Constructors.pointFromWKB(wkb, srid)
+ );
+ }
+
@UDFAnnotations.ParamMeta(argNames = {"geometry", "srid"})
public static byte[] ST_Polygon(byte[] geometry, int srid) {
return GeometrySerde.serialize(
diff --git a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 988a536cb..0556fb641 100644
--- a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -38,6 +38,7 @@ object Catalog {
// Expression for vectors
function[GeometryType](),
function[ST_PointFromText](),
+ function[ST_PointFromWKB](),
function[ST_PolygonFromText](),
function[ST_LineStringFromText](),
function[ST_GeomFromText](0),
@@ -53,6 +54,8 @@ object Catalog {
function[ST_Point](),
function[ST_MakePoint](null, null),
function[ST_PointZ](0),
+ function[ST_PointM](0),
+ function[ST_PointZM](0),
function[ST_PolygonFromEnvelope](),
function[ST_Contains](),
function[ST_Intersects](),
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
index 6ed6d7335..5ca0a56bc 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
@@ -188,6 +188,51 @@ case class ST_GeomFromEWKB(inputExpressions: Seq[Expression])
}
}
+
+case class ST_PointFromWKB(inputExpressions: Seq[Expression])
+ extends Expression with FoldableExpression with ImplicitCastInputTypes with CodegenFallback with UserDataGeneratator {
+
+ // Validate the number of input expressions (1 or 2)
+ assert(inputExpressions.length >= 1 && inputExpressions.length <= 2)
+
+ override def nullable: Boolean = true
+
+ override def eval(inputRow: InternalRow): Any = {
+ val wkb = inputExpressions.head.eval(inputRow)
+ val srid = if (inputExpressions.length > 1) inputExpressions(1).eval(inputRow) else 0
+
+ wkb match {
+ case geomString: UTF8String =>
+ // Parse UTF-8 encoded WKB string
+ val geom = Constructors.pointFromText(geomString.toString, "wkb")
+ if (geom.getGeometryType == "Point") {
+ geom.setSRID(srid.asInstanceOf[Int])
+ geom.toGenericArrayData
+ } else {
+ null
+ }
+
+ case wkbArray: Array[Byte] =>
+ // Convert raw WKB byte array to geometry
+ Constructors.pointFromWKB(wkbArray, srid.asInstanceOf[Int]).toGenericArrayData
+
+ case _ => null
+ }
+ }
+
+ override def dataType: DataType = GeometryUDT
+
+ override def inputTypes: Seq[AbstractDataType] =
+ if (inputExpressions.length == 1) Seq(TypeCollection(StringType, BinaryType))
+ else Seq(TypeCollection(StringType, BinaryType), IntegerType)
+
+ override def children: Seq[Expression] = inputExpressions
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): ST_PointFromWKB = {
+ copy(inputExpressions = newChildren)
+ }
+}
+
/**
* Return a Geometry from a GeoJSON string
*
@@ -246,6 +291,27 @@ case class ST_PointZ(inputExpressions: Seq[Expression])
}
}
+/**
+ * Return a Point from X, Y, M and srid
+ *
+ * @param inputExpressions This function takes 4 parameter which are point x, y, m and srid (default 0).
+ */
+case class ST_PointM(inputExpressions: Seq[Expression])
+ extends InferredExpression(Constructors.pointM _) {
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+ copy(inputExpressions = newChildren)
+ }
+}
+
+case class ST_PointZM(inputExpressions: Seq[Expression])
+ extends InferredExpression(Constructors.pointZM _) {
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+ copy(inputExpressions = newChildren)
+ }
+}
+
case class ST_MakePoint(inputExpressions: Seq[Expression])
extends InferredExpression(nullTolerantInferrableFunction4(Constructors.makePoint)) {
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
index 02d055a1f..7c99844f4 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
@@ -81,6 +81,32 @@ 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_PointM(x: Column, y: Column, m: Column): Column = wrapExpression[ST_PointM](x, y, m, 0)
+ def ST_PointM(x: String, y: String, m: String): Column = wrapExpression[ST_PointM](x, y, m, 0)
+ def ST_PointM(x: Double, y: Double, m: Double): Column = wrapExpression[ST_PointM](x, y, m, 0)
+
+ def ST_PointM(x: Column, y: Column, m: Column, srid: Column): Column = wrapExpression[ST_PointM](x, y, m, srid)
+
+ def ST_PointM(x: String, y: String, m: String, srid: Column): Column = wrapExpression[ST_PointM](x, y, m, srid)
+
+ def ST_PointM(x: Double, y: Double, m: Double, srid: Int): Column = wrapExpression[ST_PointM](x, y, m, srid)
+
+ def ST_PointZM(x: Column, y: Column, z: Column, m: Column): Column = wrapExpression[ST_PointZM](x, y, z, m, 0)
+ def ST_PointZM(x: String, y: String, z: String, m: String): Column = wrapExpression[ST_PointZM](x, y, z, m, 0)
+ def ST_PointZM(x: Double, y: Double, z: Double, m: Double): Column = wrapExpression[ST_PointZM](x, y, z, m, 0)
+
+ def ST_PointZM(x: Column, y: Column, z: Column, m: Column, srid: Column): Column = wrapExpression[ST_PointZM](x, y, z, m, srid)
+
+ def ST_PointZM(x: String, y: String, z: String, m: String, srid: Column): Column = wrapExpression[ST_PointZM](x, y, z, m, srid)
+
+ def ST_PointZM(x: Double, y: Double, z: Double, m: Double, srid: Int): Column = wrapExpression[ST_PointZM](x, y, z, m, srid)
+
+ def ST_PointFromWKB(wkb: Column): Column = wrapExpression[ST_PointFromWKB](wkb, 0)
+ def ST_PointFromWKB(wkb: String): Column = wrapExpression[ST_PointFromWKB](wkb, 0)
+
+ def ST_PointFromWKB(wkb: Column, srid: Column): Column = wrapExpression[ST_PointFromWKB](wkb, srid)
+ def ST_PointFromWKB(wkb: String, srid: Int): Column = wrapExpression[ST_PointFromWKB](wkb, 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)
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala b/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
index 9d6b416bd..024771e05 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
@@ -76,11 +76,26 @@ class constructorTestScala extends TestBaseScala {
assert(pointDf.count() == 1)
}
+ it("Passed ST_PointZM") {
+ val pointDf = sparkSession.sql("SELECT ST_PointZM(1.2345, 2.3456, 3.4567, 100)")
+ assert(pointDf.count() == 1)
+ }
+
it("Passed ST_PointZ null safety") {
val pointDf = sparkSession.sql("SELECT ST_PointZ(null, null, null)")
assert(pointDf.count() == 1)
}
+ it("Passed ST_PointM") {
+ val pointDf = sparkSession.sql("SELECT ST_PointM(1.2345, 2.3456, 3.4567)")
+ assert(pointDf.count() == 1)
+ }
+
+ it("Passed ST_PointM null safety") {
+ val pointDf = sparkSession.sql("SELECT ST_PointM(null, null, null)")
+ assert(pointDf.count() == 1)
+ }
+
it("Passed ST_PolygonFromEnvelope") {
val polygonDF = sparkSession.sql("select ST_PolygonFromEnvelope(double(1.234),double(2.234),double(3.345),double(3.345))")
assert(polygonDF.count() == 1)
@@ -94,6 +109,50 @@ class constructorTestScala extends TestBaseScala {
assert(pointDf.count() == 121960)
}
+ it("Passed ST_PointFromWKB") {
+ val geometryDf = Seq(
+ "0101000000000000000000F03F0000000000000040",
+ "0102000000020000000000000084d600c00000000080b5d6bf00000060e1eff7bf00000080075de5bf",
+ "010100000000000000000024400000000000002e40",
+ "0103000000010000000500000000000000000000000000000000000000000000000000f03f000000000000f03f0000000000001440000000000000f03f0000000000001440000000000000000000000000000000000000000000000000"
+ ).map(Tuple1.apply).toDF("wkb")
+
+ geometryDf.createOrReplaceTempView("wkbtable")
+
+ var validPointDf = sparkSession.sql("SELECT ST_PointFromWKB(wkbtable.wkb) FROM wkbtable")
+ validPointDf.show()
+ var rows = validPointDf.collect()
+ assert(rows.length == 4)
+
+ var expectedPoints = Seq("POINT (1 2)", null, "POINT (10 15)", null)
+ for (i <- rows.indices) {
+ if (expectedPoints(i) == null) {
+ assert(rows(i).isNullAt(0))
+ } else {
+ assert(rows(i).getAs[Geometry](0).toString == expectedPoints(i))
+ }
+ }
+
+ validPointDf = sparkSession.sql("SELECT ST_AsEWKT(ST_PointFromWKB(wkbtable.wkb, 4326)) FROM wkbtable")
+ validPointDf.show()
+ rows = validPointDf.collect()
+ assert(rows.length == 4)
+
+ expectedPoints = Seq("SRID=4326;POINT (1 2)", null, "SRID=4326;POINT (10 15)", null)
+ for (i <- rows.indices) {
+ if (expectedPoints(i) == null) {
+ assert(rows(i).isNullAt(0))
+ } else {
+ assert(rows(i).get(0).toString == expectedPoints(i))
+ }
+ }
+
+ intercept[Exception] {
+ sparkSession.sql("SELECT ST_PointFromWKB('invalid')").collect()
+ }
+ }
+
+
it("Passed ST_GeomFromWKT") {
var polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation)
polygonWktDf.createOrReplaceTempView("polygontable")
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
index 01129a4fa..5de1f4c73 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala
@@ -25,7 +25,7 @@ import org.apache.spark.sql.sedona_sql.expressions.st_constructors._
import org.apache.spark.sql.sedona_sql.expressions.st_functions._
import org.apache.spark.sql.sedona_sql.expressions.st_predicates._
import org.junit.Assert.{assertEquals, assertFalse, assertTrue}
-import org.locationtech.jts.geom.{Geometry, Polygon}
+import org.locationtech.jts.geom.{Geometry, Point, Polygon}
import org.locationtech.jts.io.WKTWriter
import org.locationtech.jts.operation.buffer.BufferParameters
@@ -58,6 +58,14 @@ class dataFrameAPITestScala extends TestBaseScala {
assert(actual == 40.0)
}
+ it("Passed st_pointm") {
+ val pointDf = sparkSession.sql("SELECT ST_PointM(1,2,100) as point1, ST_PointM(1,2,100,4326) as point2")
+ val point1 = pointDf.select(ST_AsEWKT("point1")).take(1)(0).getString(0)
+ val point2 = pointDf.select(ST_AsEWKT("point2")).take(1)(0).getString(0)
+ assertEquals("POINT ZM(1 2 0 100)", point1)
+ assertEquals("SRID=4326;POINT ZM(1 2 0 100)", point2)
+ }
+
it("passed st_makepoint") {
val df = sparkSession.sql("SELECT 0.0 AS x, 1.0 AS y, 2.0 AS z").select(ST_AsText(ST_MakePoint("x", "y", "z")))
val actualResult = df.take(1)(0).get(0).asInstanceOf[String]
@@ -72,6 +80,19 @@ class dataFrameAPITestScala extends TestBaseScala {
assert(actualResult == expectedResult)
}
+ it("passed st_pointfromwkb") {
+ val wkbSeq = Seq[Array[Byte]](Array[Byte](1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 46, 64))
+ val df = wkbSeq.toDF("wkb").select(ST_PointFromWKB("wkb"))
+ val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+ val expectedResult = "POINT (10 15)"
+ assert(actualResult == expectedResult)
+
+ val wkbStringSeq = Seq("010100000000000000000024400000000000002E40")
+ val dfWithString = wkbStringSeq.toDF("wkb").select(ST_PointFromWKB("wkb"))
+ val actualStringResult = dfWithString.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+ assert(actualStringResult == expectedResult)
+ }
+
it("passed st_polygonfromtext") {
val df = sparkSession.sql("SELECT '0.0,0.0,1.0,0.0,1.0,1.0,0.0,0.0' AS c").select(ST_PolygonFromText($"c", lit(',')))
val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
@@ -1461,5 +1482,13 @@ class dataFrameAPITestScala extends TestBaseScala {
val esriValidityReason = esriValidityTable.take(1)(0).getString(0)
assertEquals("Interior is disconnected at or near point (1.0, 1.0, NaN)", esriValidityReason)
}
+
+ it("Passed ST_PointZM") {
+ val pointDf = sparkSession.sql("SELECT ST_PointZM(1,2,3,100) as point1, ST_PointZM(1,2,3,100,4326) as point2")
+ val point1 = pointDf.select(ST_AsEWKT("point1")).take(1)(0).getString(0)
+ val point2 = pointDf.select(ST_AsEWKT("point2")).take(1)(0).getString(0)
+ assertEquals("POINT ZM(1 2 3 100)", point1)
+ assertEquals("SRID=4326;POINT ZM(1 2 3 100)", point2)
+ }
}
}