Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions common/src/main/java/org/apache/sedona/common/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,10 @@ public static Double xMin(Box2D box) {
return box == null ? null : box.getXMin();
}

public static Double xMin(Box3D box) {
return box == null ? null : box.getXMin();
}

public static Double xMax(Geometry geometry) {
Coordinate[] points = geometry.getCoordinates();
double max = -Double.MAX_VALUE;
Expand All @@ -743,6 +747,10 @@ public static Double xMax(Box2D box) {
return box == null ? null : box.getXMax();
}

public static Double xMax(Box3D box) {
return box == null ? null : box.getXMax();
}

public static Double yMin(Geometry geometry) {
Coordinate[] points = geometry.getCoordinates();
double min = Double.MAX_VALUE;
Expand All @@ -756,6 +764,10 @@ public static Double yMin(Box2D box) {
return box == null ? null : box.getYMin();
}

public static Double yMin(Box3D box) {
return box == null ? null : box.getYMin();
}

public static Double yMax(Geometry geometry) {
Coordinate[] points = geometry.getCoordinates();
double max = -Double.MAX_VALUE;
Expand All @@ -769,6 +781,10 @@ public static Double yMax(Box2D box) {
return box == null ? null : box.getYMax();
}

public static Double yMax(Box3D box) {
return box == null ? null : box.getYMax();
}

public static Double zMax(Geometry geometry) {
Coordinate[] points = geometry.getCoordinates();
double max = -Double.MAX_VALUE;
Expand All @@ -779,6 +795,10 @@ public static Double zMax(Geometry geometry) {
return max == -Double.MAX_VALUE ? null : max;
}

public static Double zMax(Box3D box) {
return box == null ? null : box.getZMax();
}

public static Double zMin(Geometry geometry) {
Coordinate[] points = geometry.getCoordinates();
double min = Double.MAX_VALUE;
Expand All @@ -789,6 +809,10 @@ public static Double zMin(Geometry geometry) {
return min == Double.MAX_VALUE ? null : min;
}

public static Double zMin(Box3D box) {
return box == null ? null : box.getZMin();
}

public static boolean hasM(Geometry geom) {
Coordinate coord = geom.getCoordinate();
return coord != null && !Double.isNaN(coord.getM());
Expand Down Expand Up @@ -922,6 +946,38 @@ public static String box2dAsText(Box2D box) {
+ ")";
}

/**
* PostGIS-format text for a Box3D: {@code BOX3D(xmin ymin zmin, xmax ymax zmax)}. NULL on null
* input.
*
* <p>Values are emitted exactly as stored on the Box3D — this method does not normalize the
* corners. Sedona's Box3D allows inverted bounds (e.g. {@code xmin > xmax}); that ordering is
* reserved for the same future antimeridian-wraparound semantics noted on {@link
* #box2dAsText(Box2D)}. The text faithfully reflects what {@code ST_XMin} / {@code ST_XMax} /
* etc. would return.
*
* <p>Not WKT (WKT has no {@code BOX3D} type), so like {@link #box2dAsText(Box2D)} this lives
* outside the {@code asWKT} family to keep that API a true geometry serializer.
*/
public static String box3dAsText(Box3D box) {
if (box == null) {
return null;
}
return "BOX3D("
+ box.getXMin()
+ " "
+ box.getYMin()
+ " "
+ box.getZMin()
+ ", "
+ box.getXMax()
+ " "
+ box.getYMax()
+ " "
+ box.getZMax()
+ ")";
}

public static byte[] asEWKB(Geometry geometry) {
return GeomUtils.getEWKB(geometry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ private[apache] case class ST_Distance(inputExpressions: Seq[Expression])
private[apache] case class ST_YMax(inputExpressions: Seq[Expression])
extends InferredExpression(
inferrableFunction1((g: Geometry) => Functions.yMax(g)),
inferrableFunction1((b: Box2D) => Functions.yMax(b))) {
inferrableFunction1((b: Box2D) => Functions.yMax(b)),
inferrableFunction1((b: Box3D) => Functions.yMax(b))) {

protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
copy(inputExpressions = newChildren)
Expand All @@ -82,35 +83,42 @@ private[apache] case class ST_YMax(inputExpressions: Seq[Expression])
private[apache] case class ST_YMin(inputExpressions: Seq[Expression])
extends InferredExpression(
inferrableFunction1((g: Geometry) => Functions.yMin(g)),
inferrableFunction1((b: Box2D) => Functions.yMin(b))) {
inferrableFunction1((b: Box2D) => Functions.yMin(b)),
inferrableFunction1((b: Box3D) => Functions.yMin(b))) {

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

/**
* Return the Z maxima of the geometry.
* Return the Z maxima of a Geometry or a Box3D.
*
* @param inputExpressions
* This function takes a geometry and returns the maximum of all Z-coordinate values.
* For a Geometry, returns the maximum of all Z-coordinate values. For a Box3D, returns the
* stored {@code zmax} bound verbatim (no corner normalization).
*/
private[apache] case class ST_ZMax(inputExpressions: Seq[Expression])
extends InferredExpression(Functions.zMax _) {
extends InferredExpression(
inferrableFunction1((g: Geometry) => Functions.zMax(g)),
inferrableFunction1((b: Box3D) => Functions.zMax(b))) {

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

/**
* Return the Z minima of the geometry.
* Return the Z minima of a Geometry or a Box3D.
*
* @param inputExpressions
* This function takes a geometry and returns the minimum of all Z-coordinate values.
* For a Geometry, returns the minimum of all Z-coordinate values. For a Box3D, returns the
* stored {@code zmin} bound verbatim (no corner normalization).
*/
private[apache] case class ST_ZMin(inputExpressions: Seq[Expression])
extends InferredExpression(Functions.zMin _) {
extends InferredExpression(
inferrableFunction1((g: Geometry) => Functions.zMin(g)),
inferrableFunction1((b: Box3D) => Functions.zMin(b))) {

protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
copy(inputExpressions = newChildren)
Expand Down Expand Up @@ -633,7 +641,8 @@ private[apache] case class ST_AsText(inputExpressions: Seq[Expression])
inferrableFunction1((g: Geometry) => Functions.asWKT(g)),
inferrableFunction1((g: Geography) =>
org.apache.sedona.common.geography.Functions.asText(g)),
inferrableFunction1((b: Box2D) => Functions.box2dAsText(b))) {
inferrableFunction1((b: Box2D) => Functions.box2dAsText(b)),
inferrableFunction1((b: Box3D) => Functions.box3dAsText(b))) {

protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
copy(inputExpressions = newChildren)
Expand Down Expand Up @@ -1516,7 +1525,8 @@ private[apache] case class ST_IsEmpty(inputExpressions: Seq[Expression])
private[apache] case class ST_XMax(inputExpressions: Seq[Expression])
extends InferredExpression(
inferrableFunction1((g: Geometry) => Functions.xMax(g)),
inferrableFunction1((b: Box2D) => Functions.xMax(b))) {
inferrableFunction1((b: Box2D) => Functions.xMax(b)),
inferrableFunction1((b: Box3D) => Functions.xMax(b))) {

protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
copy(inputExpressions = newChildren)
Expand All @@ -1531,7 +1541,8 @@ private[apache] case class ST_XMax(inputExpressions: Seq[Expression])
private[apache] case class ST_XMin(inputExpressions: Seq[Expression])
extends InferredExpression(
inferrableFunction1((g: Geometry) => Functions.xMin(g)),
inferrableFunction1((b: Box2D) => Functions.xMin(b))) {
inferrableFunction1((b: Box2D) => Functions.xMin(b)),
inferrableFunction1((b: Box3D) => Functions.xMin(b))) {

protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
copy(inputExpressions = newChildren)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sedona.sql

class Box3DAccessorSuite extends TestBaseScala {

describe("Box3D accessors + ST_AsText") {

it("ST_XMin/YMin/ZMin/XMax/YMax/ZMax accept Box3D") {
val row = sparkSession
.sql("""
WITH t AS (
SELECT ST_3DMakeBox(ST_PointZ(1, 2, 3), ST_PointZ(4, 5, 6)) AS b
)
SELECT
ST_XMin(b), ST_YMin(b), ST_ZMin(b),
ST_XMax(b), ST_YMax(b), ST_ZMax(b)
FROM t
""")
.collect()(0)
assert(row.getDouble(0) == 1.0)
assert(row.getDouble(1) == 2.0)
assert(row.getDouble(2) == 3.0)
assert(row.getDouble(3) == 4.0)
assert(row.getDouble(4) == 5.0)
assert(row.getDouble(5) == 6.0)
}

it("Accessors propagate NULL on a NULL Box3D input") {
val row = sparkSession
.sql(
"SELECT ST_XMin(b), ST_YMin(b), ST_ZMin(b), " +
"ST_XMax(b), ST_YMax(b), ST_ZMax(b) " +
"FROM (SELECT ST_3DMakeBox(ST_GeomFromText(NULL), ST_PointZ(1,1,1)) AS b)")
.collect()(0)
(0 until 6).foreach(i => assert(row.isNullAt(i), s"column $i should be NULL"))
}

it("ST_AsText(box3d) returns BOX3D(...) text") {
val str = sparkSession
.sql("SELECT ST_AsText(ST_3DMakeBox(ST_PointZ(0, 0, 0), ST_PointZ(10, 20, 30))) AS s")
.collect()(0)
.getString(0)
assert(str == "BOX3D(0.0 0.0 0.0, 10.0 20.0 30.0)")
}

it("ST_AsText(box3d) returns NULL for NULL input") {
val row = sparkSession
.sql("SELECT ST_AsText(ST_3DMakeBox(ST_GeomFromText(NULL), ST_PointZ(1,1,1))) AS s")
.collect()(0)
assert(row.isNullAt(0))
}
}
}
Loading