Skip to content

Commit eda79a5

Browse files
authored
[GH-2881] Add ST_Box2D(geom) scalar function (#2890)
1 parent 043b30e commit eda79a5

5 files changed

Lines changed: 52 additions & 1 deletion

File tree

common/src/main/java/org/apache/sedona/common/Functions.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.commons.lang3.tuple.Pair;
3131
import org.apache.sedona.common.S2Geography.Geography;
3232
import org.apache.sedona.common.approximate.StraightSkeleton;
33+
import org.apache.sedona.common.geometryObjects.Box2D;
3334
import org.apache.sedona.common.geometryObjects.Circle;
3435
import org.apache.sedona.common.jts2geojson.GeoJSONWriter;
3536
import org.apache.sedona.common.sphere.Spheroid;
@@ -599,6 +600,10 @@ public static Geometry envelope(Geometry geometry) {
599600
return geometry.getEnvelope();
600601
}
601602

603+
public static Box2D box2D(Geometry geometry) {
604+
return Box2D.fromGeometry(geometry);
605+
}
606+
602607
public static Double distance(Geometry left, Geometry right) {
603608
if (left.isEmpty() || right.isEmpty()) {
604609
return null;

spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ object Catalog extends AbstractCatalog with Logging {
278278
// Bounding-Box-Functions
279279
val boundingBoxExprs: Seq[FunctionDescription] = Seq(
280280
function[ST_BoundingDiagonal](),
281+
function[ST_Box2D](),
281282
function[ST_Envelope](),
282283
function[ST_Expand](),
283284
function[ST_MMax](),

spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,19 @@ private[apache] case class ST_Envelope(inputExpressions: Seq[Expression])
240240
}
241241
}
242242

243+
/**
244+
* Return the planar bounding box (Box2D) of a Geometry. Returns NULL for null or empty input.
245+
*
246+
* @param inputExpressions
247+
*/
248+
private[apache] case class ST_Box2D(inputExpressions: Seq[Expression])
249+
extends InferredExpression(Functions.box2D _) {
250+
251+
protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
252+
copy(inputExpressions = newChildren)
253+
}
254+
}
255+
243256
private[apache] case class ST_Expand(inputExpressions: Seq[Expression])
244257
extends InferredExpression(
245258
inferrableFunction4(Functions.expand),

spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/InferredExpression.scala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ package org.apache.spark.sql.sedona_sql.expressions
2020

2121
import org.apache.commons.lang3.StringUtils
2222
import org.apache.sedona.common.S2Geography.Geography
23+
import org.apache.sedona.common.geometryObjects.Box2D
2324
import org.apache.spark.sql.catalyst.InternalRow
2425
import org.apache.spark.sql.catalyst.expressions.codegen.CodegenFallback
2526
import org.apache.spark.sql.catalyst.expressions.{Expression, ImplicitCastInputTypes}
2627
import org.apache.spark.sql.catalyst.util.ArrayData
27-
import org.apache.spark.sql.sedona_sql.UDT.{GeographyUDT, GeometryUDT}
28+
import org.apache.spark.sql.sedona_sql.UDT.{Box2DUDT, GeographyUDT, GeometryUDT}
2829
import org.apache.spark.sql.sedona_sql.expressions.implicits._
2930
import org.apache.spark.sql.types._
3031
import org.apache.spark.unsafe.types.UTF8String
@@ -164,6 +165,8 @@ object InferrableType {
164165
new InferrableType[Geography] {}
165166
implicit val geographyArrayInstance: InferrableType[Array[Geography]] =
166167
new InferrableType[Array[Geography]] {}
168+
implicit val box2DInstance: InferrableType[Box2D] =
169+
new InferrableType[Box2D] {}
167170
implicit val javaDoubleInstance: InferrableType[java.lang.Double] =
168171
new InferrableType[java.lang.Double] {}
169172
implicit val javaIntegerInstance: InferrableType[java.lang.Integer] =
@@ -266,6 +269,14 @@ object InferredTypes {
266269
} else {
267270
null
268271
}
272+
} else if (t =:= typeOf[Box2D]) {
273+
val udt = Box2DUDT
274+
output =>
275+
if (output != null) {
276+
udt.serialize(output.asInstanceOf[Box2D])
277+
} else {
278+
null
279+
}
269280
} else if (InferredRasterExpression.isRasterType(t)) {
270281
InferredRasterExpression.rasterSerializer
271282
} else if (t =:= typeOf[String]) { output =>
@@ -332,6 +343,8 @@ object InferredTypes {
332343
GeographyUDT()
333344
} else if (t =:= typeOf[Array[Geography]] || t =:= typeOf[java.util.List[Geography]]) {
334345
DataTypes.createArrayType(GeographyUDT())
346+
} else if (t =:= typeOf[Box2D]) {
347+
Box2DUDT()
335348
} else if (InferredRasterExpression.isRasterType(t)) {
336349
InferredRasterExpression.rasterUDT
337350
} else if (InferredRasterExpression.isRasterArrayType(t)) {

spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package org.apache.sedona.sql
2121
import org.apache.commons.codec.binary.Hex
2222
import org.apache.commons.io.FileUtils
2323
import org.apache.sedona.common.FunctionsGeoTools
24+
import org.apache.sedona.common.geometryObjects.Box2D
2425
import org.apache.sedona.sql.implicits._
2526
import org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema
2627
import org.apache.spark.sql.functions._
@@ -192,6 +193,24 @@ class functionTestScala
192193
assert(functionDf.count() > 0)
193194
}
194195

196+
it("Passed ST_Box2D") {
197+
val df = sparkSession
198+
.sql("""
199+
SELECT
200+
ST_Box2D(ST_GeomFromText('POLYGON((1 2, 1 5, 4 5, 4 2, 1 2))')) AS bbox,
201+
ST_Box2D(ST_GeomFromText('POINT EMPTY')) AS bbox_empty,
202+
ST_Box2D(ST_GeomFromText(NULL)) AS bbox_null
203+
""")
204+
val row = df.collect()(0)
205+
val bbox = row.getAs[Box2D]("bbox")
206+
assert(bbox.getXMin == 1.0)
207+
assert(bbox.getYMin == 2.0)
208+
assert(bbox.getXMax == 4.0)
209+
assert(bbox.getYMax == 5.0)
210+
assert(row.isNullAt(1))
211+
assert(row.isNullAt(2))
212+
}
213+
195214
it("Passed ST_Envelope") {
196215
var polygonWktDf = sparkSession.read
197216
.format("csv")

0 commit comments

Comments
 (0)