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 2021/12/03 15:59:10 UTC
[incubator-sedona] branch master updated: [SEDONA-75] Add support for "3D" geometries (#565)
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/incubator-sedona.git
The following commit(s) were added to refs/heads/master by this push:
new 2e7bbc1 [SEDONA-75] Add support for "3D" geometries (#565)
2e7bbc1 is described below
commit 2e7bbc198a2d50071203293b31fa46c9dd0b7a9c
Author: Kurtis Seebaldt <ks...@gmail.com>
AuthorDate: Fri Dec 3 09:59:03 2021 -0600
[SEDONA-75] Add support for "3D" geometries (#565)
---
docs/api/sql/Constructor.md | 1 +
docs/api/sql/Function.md | 29 ++++++++
python/tests/sql/test_constructor_test.py | 20 ++++++
python/tests/sql/test_function.py | 60 +++++++++++++++-
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 2 +
.../sedona/sql/utils/GeometrySerializer.scala | 6 +-
.../sql/sedona_sql/expressions/Constructors.scala | 16 ++++-
.../sql/sedona_sql/expressions/Functions.scala | 57 ++++++++++++++-
.../apache/sedona/sql/constructorTestScala.scala | 24 +++++++
.../org/apache/sedona/sql/functionTestScala.scala | 84 +++++++++++++++++++++-
10 files changed, 290 insertions(+), 9 deletions(-)
diff --git a/docs/api/sql/Constructor.md b/docs/api/sql/Constructor.md
index a0faeb3..4145593 100644
--- a/docs/api/sql/Constructor.md
+++ b/docs/api/sql/Constructor.md
@@ -107,6 +107,7 @@ System.setProperty("sedona.global.charset", "utf8")
Introduction: Construct a Point from X and Y
Format: `ST_Point (X:decimal, Y:decimal)`
+Format: `ST_Point (X:decimal, Y:decimal, Z:decimal)`
Since: `v1.0.0`
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index d0be0c6..e012305 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -12,6 +12,20 @@ SELECT ST_Distance(polygondf.countyshape, polygondf.countyshape)
FROM polygondf
```
+## ST_3DDistance
+
+Introduction: Return the 3-dimensional minimum cartesian distance between A and B
+
+Format: `ST_3DDistance (A:geometry, B:geometry)`
+
+Since: `v1.2.0`
+
+Spark SQL example:
+```SQL
+SELECT ST_3DDistance(polygondf.countyshape, polygondf.countyshape)
+FROM polygondf
+```
+
## ST_ConvexHull
Introduction: Return the Convex Hull of polgyon A
@@ -405,6 +419,21 @@ SELECT ST_Y(ST_POINT(0.0 25.0))
Output: `25.0`
+## ST_Z
+
+Introduction: Returns Z Coordinate of given Point, null otherwise.
+
+Format: `ST_Z(pointA: Point)`
+
+Since: `v1.2.0`
+
+Spark SQL example:
+```SQL
+SELECT ST_Z(ST_POINT(0.0 25.0 11.0))
+```
+
+Output: `11.0`
+
## ST_StartPoint
Introduction: Returns first point of given linestring.
diff --git a/python/tests/sql/test_constructor_test.py b/python/tests/sql/test_constructor_test.py
index 1639be1..4ea5ee2 100644
--- a/python/tests/sql/test_constructor_test.py
+++ b/python/tests/sql/test_constructor_test.py
@@ -33,6 +33,10 @@ class TestConstructors(TestBase):
point_df = self.spark.sql("select ST_Point(cast(pointtable._c0 as Decimal(24,20)), cast(pointtable._c1 as Decimal(24,20))) as arealandmark from pointtable")
assert point_df.count() == 1000
+ def test_st_point_3d(self):
+ point_df = self.spark.sql("SELECT ST_Point(1.2345, 2.3456, 3.4567)")
+ assert point_df.count() == 1
+
def test_st_point_from_text(self):
point_csv_df = self.spark.read.format("csv").\
option("delimiter", ",").\
@@ -56,6 +60,22 @@ class TestConstructors(TestBase):
polygon_df.show(10)
assert polygon_df.count() == 100
+ def test_st_geom_from_wkt_3d(self):
+ input_df = self.spark.createDataFrame([
+ ("Point(21 52 87)",),
+ ("Polygon((0 0 1, 0 1 1, 1 1 1, 1 0 1, 0 0 1))",),
+ ("Linestring(0 0 1, 1 1 2, 1 0 3)",),
+ ("MULTIPOINT ((10 40 66), (40 30 77), (20 20 88), (30 10 99))",),
+ ("MULTIPOLYGON (((30 20 11, 45 40 11, 10 40 11, 30 20 11)), ((15 5 11, 40 10 11, 10 20 11, 5 10 11, 15 5 11)))",),
+ ("MULTILINESTRING ((10 10 11, 20 20 11, 10 40 11), (40 40 11, 30 30 11, 40 20 11, 30 10 11))",),
+ ("MULTIPOLYGON (((40 40 11, 20 45 11, 45 30 11, 40 40 11)), ((20 35 11, 10 30 11, 10 10 11, 30 5 11, 45 20 11, 20 35 11), (30 20 11, 20 15 11, 20 25 11, 30 20 11)))",),
+ ("POLYGON((0 0 11, 0 5 11, 5 5 11, 5 0 11, 0 0 11), (1 1 11, 2 1 11, 2 2 11, 1 2 11, 1 1 11))",),
+ ], ["wkt"])
+
+ input_df.createOrReplaceTempView("input_wkt")
+ polygon_df = self.spark.sql("select ST_GeomFromWkt(wkt) as geomn from input_wkt")
+ assert polygon_df.count() == 8
+
def test_st_geom_from_text(self):
polygon_wkt_df = self.spark.read.format("csv").\
option("delimiter", "\t").\
diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py
index 08a92aa..ab8d87f 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -155,6 +155,10 @@ class TestPredicateJoin(TestBase):
function_df = self.spark.sql("select ST_Distance(polygondf.countyshape, polygondf.countyshape) from polygondf")
function_df.show()
+ def test_st_3ddistance(self):
+ function_df = self.spark.sql("select ST_3DDistance(ST_Point(0.0, 0.0, 5.0), ST_Point(1.0, 1.0, -6.0))")
+ assert function_df.count() == 1
+
def test_st_transform(self):
polygon_wkt_df = self.spark.read.format("csv"). \
option("delimiter", "\t"). \
@@ -237,6 +241,36 @@ class TestPredicateJoin(TestBase):
wkt_df = self.spark.sql("select ST_AsText(countyshape) as wkt from polygondf")
assert polygon_df.take(1)[0]["countyshape"].wkt == loads(wkt_df.take(1)[0]["wkt"]).wkt
+
+ def test_st_astext_3d(self):
+ input_df = self.spark.createDataFrame([
+ ("Point(21 52 87)",),
+ ("Polygon((0 0 1, 0 1 1, 1 1 1, 1 0 1, 0 0 1))",),
+ ("Linestring(0 0 1, 1 1 2, 1 0 3)",),
+ ("MULTIPOINT ((10 40 66), (40 30 77), (20 20 88), (30 10 99))",),
+ ("MULTIPOLYGON (((30 20 11, 45 40 11, 10 40 11, 30 20 11)), ((15 5 11, 40 10 11, 10 20 11, 5 10 11, 15 5 11)))",),
+ ("MULTILINESTRING ((10 10 11, 20 20 11, 10 40 11), (40 40 11, 30 30 11, 40 20 11, 30 10 11))",),
+ ("MULTIPOLYGON (((40 40 11, 20 45 11, 45 30 11, 40 40 11)), ((20 35 11, 10 30 11, 10 10 11, 30 5 11, 45 20 11, 20 35 11), (30 20 11, 20 15 11, 20 25 11, 30 20 11)))",),
+ ("POLYGON((0 0 11, 0 5 11, 5 5 11, 5 0 11, 0 0 11), (1 1 11, 2 1 11, 2 2 11, 1 2 11, 1 1 11))",),
+ ], ["wkt"])
+
+ input_df.createOrReplaceTempView("input_wkt")
+ polygon_df = self.spark.sql("select ST_AsText(ST_GeomFromWkt(wkt)) as wkt from input_wkt")
+ assert polygon_df.count() == 8
+
+ def test_st_as_text_3d(self):
+ polygon_wkt_df = self.spark.read.format("csv"). \
+ option("delimiter", "\t"). \
+ option("header", "false"). \
+ load(mixed_wkt_geometry_input_location)
+
+ polygon_wkt_df.createOrReplaceTempView("polygontable")
+ polygon_df = self.spark.sql("select ST_GeomFromWKT(polygontable._c0) as countyshape from polygontable")
+ polygon_df.createOrReplaceTempView("polygondf")
+ wkt_df = self.spark.sql("select ST_AsText(countyshape) as wkt from polygondf")
+ assert polygon_df.take(1)[0]["countyshape"].wkt == loads(wkt_df.take(1)[0]["wkt"]).wkt
+
+
def test_st_n_points(self):
test = self.spark.sql("SELECT ST_NPoints(ST_GeomFromText('LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)'))")
@@ -308,6 +342,30 @@ class TestPredicateJoin(TestBase):
assert(not polygons.count())
+ def test_st_z(self):
+ point_df = self.spark.sql(
+ "select ST_GeomFromWKT('POINT Z (1.1 2.2 3.3)') as geom"
+ )
+ polygon_df = self.spark.sql(
+ "select ST_GeomFromWKT('POLYGON Z ((0 0 2, 0 1 2, 1 1 2, 1 0 2, 0 0 2))') as geom"
+ )
+ linestring_df = self.spark.sql(
+ "select ST_GeomFromWKT('LINESTRING Z (0 0 1, 0 1 2)') as geom"
+ )
+
+ points = point_df \
+ .selectExpr("ST_Z(geom)").collect()
+
+ polygons = polygon_df.selectExpr("ST_Z(geom) as z").filter("z IS NOT NULL")
+
+ linestrings = linestring_df.selectExpr("ST_Z(geom) as z").filter("z IS NOT NULL")
+
+ assert([point[0] for point in points] == [3.3])
+
+ assert(not linestrings.count())
+
+ assert(not polygons.count())
+
def test_st_start_point(self):
point_df = create_sample_points_df(self.spark, 5)
@@ -387,7 +445,7 @@ class TestPredicateJoin(TestBase):
def test_st_exterior_ring(self):
polygon_df = create_simple_polygons_df(self.spark, 5)
- additional_wkt = "POLYGON((0 0 1, 1 1 1, 1 2 1, 1 1 1, 0 0 1))"
+ additional_wkt = "POLYGON((0 0, 1 1, 1 2, 1 1, 0 0))"
additional_wkt_df = self.spark.createDataFrame([[wkt.loads(additional_wkt)]], self.geo_schema)
polygons_df = polygon_df.union(additional_wkt_df)
diff --git a/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala b/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 2ce31eb..01d3c6b 100644
--- a/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -41,6 +41,7 @@ object Catalog {
ST_Intersects,
ST_Within,
ST_Distance,
+ ST_3DDistance,
ST_ConvexHull,
ST_NPoints,
ST_Buffer,
@@ -71,6 +72,7 @@ object Catalog {
ST_Azimuth,
ST_X,
ST_Y,
+ ST_Z,
ST_StartPoint,
ST_Boundary,
ST_MinimumBoundingRadius,
diff --git a/sql/src/main/scala/org/apache/sedona/sql/utils/GeometrySerializer.scala b/sql/src/main/scala/org/apache/sedona/sql/utils/GeometrySerializer.scala
index a4f752e..c8321bc 100644
--- a/sql/src/main/scala/org/apache/sedona/sql/utils/GeometrySerializer.scala
+++ b/sql/src/main/scala/org/apache/sedona/sql/utils/GeometrySerializer.scala
@@ -34,7 +34,7 @@ object GeometrySerializer {
* @return Array of bites represents this geometry
*/
def serialize(geometry: Geometry): Array[Byte] = {
- val writer = new WKBWriter(2, 2, true)
+ val writer = new WKBWriter(getDimension(geometry), 2, true)
writer.write(geometry)
}
@@ -48,4 +48,8 @@ object GeometrySerializer {
val reader = new WKBReader()
reader.read(values.toByteArray())
}
+
+ def getDimension(geometry: Geometry): Int = {
+ if (geometry.getCoordinate != null && !java.lang.Double.isNaN(geometry.getCoordinate.getZ)) 3 else 2
+ }
}
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
index 9819ba2..bd99f79 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
@@ -27,7 +27,7 @@ import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
import org.apache.spark.sql.catalyst.util.GenericArrayData
import org.apache.spark.sql.sedona_sql.UDT.GeometryUDT
import org.apache.spark.sql.sedona_sql.expressions.geohash.GeoHashDecoder
-import org.apache.spark.sql.sedona_sql.expressions.implicits.{GeometryEnhancer, InputExpressionEnhancer}
+import org.apache.spark.sql.sedona_sql.expressions.implicits.{GeometryEnhancer, InputExpressionEnhancer, SequenceEnhancer}
import org.apache.spark.sql.types.{DataType, Decimal}
import org.apache.spark.unsafe.types.UTF8String
import org.locationtech.jts.geom.{Coordinate, GeometryFactory}
@@ -258,7 +258,7 @@ case class ST_GeomFromGeoJSON(inputExpressions: Seq[Expression])
*/
case class ST_Point(inputExpressions: Seq[Expression])
extends Expression with CodegenFallback with UserDataGeneratator {
- assert(inputExpressions.length == 2)
+ inputExpressions.betweenLength(2, 3)
override def nullable: Boolean = false
@@ -272,8 +272,18 @@ case class ST_Point(inputExpressions: Seq[Expression])
case b: Decimal => b.toDouble
}
+ val coord = if (inputExpressions.length == 2) {
+ new Coordinate(x, y)
+ } else {
+ val z = inputExpressions(2).eval(inputRow) match {
+ case a: Double => a
+ case b: Decimal => b.toDouble
+ }
+ new Coordinate(x, y, z)
+ }
+
var geometryFactory = new GeometryFactory()
- var geometry = geometryFactory.createPoint(new Coordinate(x, y))
+ var geometry = geometryFactory.createPoint(coord)
new GenericArrayData(GeometrySerializer.serialize(geometry))
}
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index b5894b8..39abeea 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -36,10 +36,11 @@ import org.geotools.geometry.jts.JTS
import org.geotools.referencing.CRS
import org.locationtech.jts.algorithm.MinimumBoundingCircle
import org.locationtech.jts.geom.{PrecisionModel, _}
-import org.locationtech.jts.io.{ByteOrderValues, WKBWriter}
+import org.locationtech.jts.io.{ByteOrderValues, WKBWriter, WKTWriter}
import org.locationtech.jts.linearref.LengthIndexedLine
import org.locationtech.jts.operation.IsSimpleOp
import org.locationtech.jts.operation.buffer.BufferParameters
+import org.locationtech.jts.operation.distance3d.Distance3DOp
import org.locationtech.jts.operation.linemerge.LineMerger
import org.locationtech.jts.operation.valid.IsValidOp
import org.locationtech.jts.precision.GeometryPrecisionReducer
@@ -85,6 +86,33 @@ case class ST_Distance(inputExpressions: Seq[Expression])
}
}
+case class ST_3DDistance(inputExpressions: Seq[Expression])
+ extends Expression with CodegenFallback {
+ assert(inputExpressions.length == 2)
+
+ override def nullable: Boolean = true
+
+ override def toString: String = s" **${ST_3DDistance.getClass.getName}** "
+
+ override def eval(inputRow: InternalRow): Any = {
+ val leftGeometry = inputExpressions(0).toGeometry(inputRow)
+ val rightGeometry = inputExpressions(1).toGeometry(inputRow)
+
+ (leftGeometry, rightGeometry) match {
+ case (leftGeometry: Geometry, rightGeometry: Geometry) => Distance3DOp.distance(leftGeometry, rightGeometry)
+ case _ => null
+ }
+ }
+
+ override def dataType = DoubleType
+
+ override def children: Seq[Expression] = inputExpressions
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+ copy(inputExpressions = newChildren)
+ }
+}
+
/**
* Return the convex hull of a Geometry.
*
@@ -527,7 +555,8 @@ case class ST_AsText(inputExpressions: Seq[Expression])
override def eval(input: InternalRow): Any = {
val geometry = GeometrySerializer.deserialize(inputExpressions.head.eval(input).asInstanceOf[ArrayData])
- UTF8String.fromString(geometry.toText)
+ val writer = new WKTWriter(GeometrySerializer.getDimension(geometry))
+ UTF8String.fromString(writer.write(geometry))
}
override def dataType: DataType = StringType
@@ -795,6 +824,30 @@ case class ST_Y(inputExpressions: Seq[Expression])
}
}
+case class ST_Z(inputExpressions: Seq[Expression])
+ extends Expression with CodegenFallback {
+ assert(inputExpressions.length == 1)
+
+ override def nullable: Boolean = true
+
+ override def eval(input: InternalRow): Any = {
+ val geometry = inputExpressions.head.toGeometry(input)
+
+ geometry match {
+ case point: Point => point.getCoordinate.getZ
+ case _ => null
+ }
+ }
+
+ override def dataType: DataType = DoubleType
+
+ override def children: Seq[Expression] = inputExpressions
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+ copy(inputExpressions = newChildren)
+ }
+}
+
case class ST_StartPoint(inputExpressions: Seq[Expression])
extends Expression with CodegenFallback {
assert(inputExpressions.length == 1)
diff --git a/sql/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala b/sql/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
index 0b6c7bd..fe1f69a 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
@@ -26,6 +26,8 @@ import org.locationtech.jts.geom.Geometry
class constructorTestScala extends TestBaseScala {
+ import sparkSession.implicits._
+
describe("Sedona-SQL Constructor Test") {
it("Passed ST_Point") {
@@ -42,6 +44,11 @@ class constructorTestScala extends TestBaseScala {
assert(pointDf.count() == 1)
}
+ it("Passed ST_Point 3D") {
+ val pointDf = sparkSession.sql("SELECT ST_Point(1.2345, 2.3456, 3.4567)")
+ 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)
@@ -62,6 +69,23 @@ class constructorTestScala extends TestBaseScala {
assert(polygonDf.count() == 100)
}
+ it("Passed ST_GeomFromWKT 3D") {
+ val geometryDf = Seq(
+ "Point(21 52 87)",
+ "Polygon((0 0 1, 0 1 1, 1 1 1, 1 0 1, 0 0 1))",
+ "Linestring(0 0 1, 1 1 2, 1 0 3)",
+ "MULTIPOINT ((10 40 66), (40 30 77), (20 20 88), (30 10 99))",
+ "MULTIPOLYGON (((30 20 11, 45 40 11, 10 40 11, 30 20 11)), ((15 5 11, 40 10 11, 10 20 11, 5 10 11, 15 5 11)))",
+ "MULTILINESTRING ((10 10 11, 20 20 11, 10 40 11), (40 40 11, 30 30 11, 40 20 11, 30 10 11))",
+ "MULTIPOLYGON (((40 40 11, 20 45 11, 45 30 11, 40 40 11)), ((20 35 11, 10 30 11, 10 10 11, 30 5 11, 45 20 11, 20 35 11), (30 20 11, 20 15 11, 20 25 11, 30 20 11)))",
+ "POLYGON((0 0 11, 0 5 11, 5 5 11, 5 0 11, 0 0 11), (1 1 11, 2 1 11, 2 2 11, 1 2 11, 1 1 11))"
+ ).map(wkt => Tuple1(wkt)).toDF("geom")
+
+ geometryDf.createOrReplaceTempView("geometrytable")
+ var polygonDf = sparkSession.sql("select ST_GeomFromWkt(geometrytable.geom) from geometrytable")
+ assert(polygonDf.count() == 8)
+ }
+
it("Passed ST_GeomFromText") {
var polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation)
polygonWktDf.createOrReplaceTempView("polygontable")
diff --git a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index f4a0ad8..7d9918f 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -26,7 +26,9 @@ import org.apache.spark.sql.{DataFrame, Row}
import org.geotools.geometry.jts.WKTReader2
import org.locationtech.jts.algorithm.MinimumBoundingCircle
import org.locationtech.jts.geom.{Geometry, Polygon}
+import org.locationtech.jts.io.WKTWriter
import org.locationtech.jts.linearref.LengthIndexedLine
+import org.locationtech.jts.operation.distance3d.Distance3DOp
import org.scalatest.{GivenWhenThen, Matchers}
class functionTestScala extends TestBaseScala with Matchers with GeometrySample with GivenWhenThen {
@@ -98,6 +100,16 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
assert(functionDf.count() > 0);
}
+ it("Passed ST_3DDistance") {
+ val point1 = wktReader.read("POINT Z (0 0 -5)")
+ val point2 = wktReader.read("POINT Z (1 1 -6)")
+ val pointDf = Seq(Tuple2(point1, point2)).toDF("p1", "p2")
+ pointDf.createOrReplaceTempView("pointdf")
+ var functionDf = sparkSession.sql("select ST_3DDistance(p1, p2) from pointdf")
+ val expected = Distance3DOp.distance(point1, point2)
+ assert(functionDf.take(1)(0).get(0).asInstanceOf[Double].equals(expected))
+ }
+
it("Passed ST_Transform") {
var polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation)
polygonWktDf.createOrReplaceTempView("polygontable")
@@ -266,6 +278,26 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
assert(polygonDf.take(1)(0).getAs[Geometry]("countyshape").toText.equals(wktDf.take(1)(0).getAs[String]("wkt")))
}
+ it("Passed ST_AsText 3D") {
+ val geometryDf = Seq(
+ "Point Z(21 52 87)",
+ "Polygon Z((0 0 1, 0 1 1, 1 1 1, 1 0 1, 0 0 1))",
+ "Linestring Z(0 0 1, 1 1 2, 1 0 3)",
+ "MULTIPOINT Z((10 40 66), (40 30 77), (20 20 88), (30 10 99))",
+ "MULTIPOLYGON Z(((30 20 11, 45 40 11, 10 40 11, 30 20 11)), ((15 5 11, 40 10 11, 10 20 11, 5 10 11, 15 5 11)))",
+ "MULTILINESTRING Z((10 10 11, 20 20 11, 10 40 11), (40 40 11, 30 30 11, 40 20 11, 30 10 11))",
+ "MULTIPOLYGON Z(((40 40 11, 20 45 11, 45 30 11, 40 40 11)), ((20 35 11, 10 30 11, 10 10 11, 30 5 11, 45 20 11, 20 35 11), (30 20 11, 20 15 11, 20 25 11, 30 20 11)))",
+ "POLYGON Z((0 0 11, 0 5 11, 5 5 11, 5 0 11, 0 0 11), (1 1 11, 2 1 11, 2 2 11, 1 2 11, 1 1 11))"
+ ).map(wkt => Tuple1(wktReader.read(wkt))).toDF("geom")
+
+ geometryDf.createOrReplaceTempView("geometrytable")
+ var wktDf = sparkSession.sql("select ST_AsText(geom) as wkt from geometrytable")
+ val wktWriter = new WKTWriter(3)
+ val expected = geometryDf.collect().map(row => wktWriter.write(row.getAs[Geometry]("geom")))
+ val actual = wktDf.collect().map(row => row.getAs[String]("wkt"))
+ actual should contain theSameElementsAs expected
+ }
+
it("Passed ST_AsGeoJSON") {
val df = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON((1 1, 8 1, 8 8, 1 8, 1 1))') AS polygon")
df.createOrReplaceTempView("table")
@@ -434,6 +466,54 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
polygons.length shouldBe 0
}
+
+ it("Should pass ST_Z") {
+
+ Given("Given polygon, point and linestring dataframe")
+ val pointDF = Seq(
+ "POINT Z (1 2 3)"
+ ).map(geom => Tuple1(wktReader.read(geom))).toDF("geom")
+ val polygonDF = Seq(
+ "POLYGON Z ((0 0 2, 0 1 2, 1 1 2, 1 0 2, 0 0 2))"
+ ).map(geom => Tuple1(wktReader.read(geom))).toDF("geom")
+ val lineStringDF = Seq(
+ "LINESTRING Z (0 0 1, 0 1 2)"
+ ).map(geom => Tuple1(wktReader.read(geom))).toDF("geom")
+
+ When("Running ST_Z function on polygon, point and linestring data frames")
+
+ val points = pointDF
+ .selectExpr("ST_Z(geom) as z")
+ .as[Double]
+ .collect()
+ .toList
+
+ val polygons = polygonDF
+ .selectExpr("ST_Z(geom) as z")
+ .filter("z IS NOT NULL")
+ .as[Double]
+ .collect()
+ .toList
+
+ val linestrings = lineStringDF
+ .selectExpr("ST_Z(geom) as z")
+ .filter("z IS NOT NULL")
+ .as[Double]
+ .collect()
+ .toList
+
+ Then("Point z coordinates Should match to expected point coordinates")
+
+ points should contain theSameElementsAs List(3)
+
+ And("LineString count should be 0")
+ linestrings.length shouldBe 0
+
+ And("Polygon count should be 0")
+ polygons.length shouldBe 0
+
+ }
+
it("Should pass ST_StartPoint function") {
Given("Polygon Data Frame, Point DataFrame, LineString Data Frame")
@@ -515,7 +595,7 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
it("Should pass ST_ExteriorRing") {
Given("Polygon DataFrame and other geometries DataFrame")
val polygonDf = createSimplePolygons(5, "geom")
- .union(Seq("POLYGON((0 0 1, 1 1 1, 1 2 1, 1 1 1, 0 0 1))")
+ .union(Seq("POLYGON((0 0, 1 1, 1 2, 1 1, 0 0))")
.map(wkt => Tuple1(wktReader.read(wkt))).toDF("geom")
)
@@ -1067,7 +1147,7 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
Given("Sample geometry dataframe")
val geometryTable = Seq(
"LINESTRING(25 50, 100 125, 150 190)",
- "LINESTRING(1 2 3, 4 5 6, 6 7 8)"
+ "LINESTRING(1 2, 4 5, 6 7)"
).map(geom => Tuple1(wktReader.read(geom))).toDF("geom")
When("Using ST_LineInterpolatePoint")