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
21 changes: 21 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 @@ -24,6 +24,7 @@
import javax.xml.parsers.ParserConfigurationException;
import org.apache.sedona.common.enums.FileDataSplitter;
import org.apache.sedona.common.enums.GeometryType;
import org.apache.sedona.common.geometryObjects.Box2D;
import org.apache.sedona.common.utils.FormatUtils;
import org.apache.sedona.common.utils.GeoHashDecoder;
import org.locationtech.jts.geom.*;
Expand Down Expand Up @@ -288,6 +289,26 @@ public static Geometry makeEnvelope(double minX, double minY, double maxX, doubl
return makeEnvelope(minX, minY, maxX, maxY, 0);
}

/**
* Build a {@link Box2D} from two corner points. The corners are taken verbatim — no swapping or
* validation of ordering — so {@code xmin > xmax} or {@code ymin > ymax} are preserved as
* supplied. NULL or empty point inputs return NULL.
*/
public static Box2D makeBox2D(Geometry lowerLeft, Geometry upperRight) {
if (lowerLeft == null || upperRight == null) {
return null;
}
if (!(lowerLeft instanceof Point) || !(upperRight instanceof Point)) {
throw new IllegalArgumentException("ST_MakeBox2D requires two POINT geometries");
}
if (lowerLeft.isEmpty() || upperRight.isEmpty()) {
return null;
}
Point ll = (Point) lowerLeft;
Point ur = (Point) upperRight;
return new Box2D(ll.getX(), ll.getY(), ur.getX(), ur.getY());
}

public static Geometry geomFromGeoHash(String geoHash, Integer precision) {
try {
return GeoHashDecoder.decode(geoHash, precision);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ object Catalog extends AbstractCatalog with Logging {
function[ST_GeomFromGML](),
function[ST_GeomFromKML](),
function[ST_Point](),
function[ST_MakeBox2D](),
function[ST_MakeEnvelope](),
function[ST_MakePoint](null, null),
function[ST_MakePointM](),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,20 @@ private[apache] case class ST_PolygonFromEnvelope(inputExpressions: Seq[Expressi
}
}

/**
* Construct a Box2D from two corner points (lower-left, upper-right). Coordinates are taken
* verbatim; ordering is not validated.
*
* @param inputExpressions
*/
private[apache] case class ST_MakeBox2D(inputExpressions: Seq[Expression])
extends InferredExpression(Constructors.makeBox2D _) {

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

private[apache] trait UserDataGenerator {
def generateUserData(
minInputLength: Integer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.sedona.sql

import org.apache.sedona.common.geometryObjects.Box2D
import org.apache.sedona.core.formatMapper.GeoJsonReader
import org.apache.sedona.core.formatMapper.shapefileParser.ShapefileReader
import org.apache.sedona.sql.utils.Adapter
Expand Down Expand Up @@ -138,6 +139,52 @@ class constructorTestScala extends TestBaseScala with Matchers {
assert(polygonDF.count() == 1)
}

it("Passed ST_MakeBox2D") {
val df = sparkSession.sql("""
SELECT
ST_MakeBox2D(ST_Point(1.0, 2.0), ST_Point(4.0, 5.0)) AS bbox,
ST_MakeBox2D(ST_Point(10.0, 20.0), ST_GeomFromText(NULL)) AS bbox_null,
ST_MakeBox2D(ST_GeomFromText('POINT EMPTY'), ST_Point(1.0, 1.0)) AS bbox_empty
""")
val row = df.collect()(0)
val bbox = row.getAs[Box2D]("bbox")
assert(bbox.getXMin == 1.0)
assert(bbox.getYMin == 2.0)
assert(bbox.getXMax == 4.0)
assert(bbox.getYMax == 5.0)
assert(row.isNullAt(1))
assert(row.isNullAt(2))
}

it("ST_MakeBox2D preserves swapped corners") {
// No swapping or reordering; lower-left/upper-right are taken verbatim.
// This leaves xmin > xmax / ymin > ymax available for future antimeridian semantics.
val df = sparkSession.sql(
"SELECT ST_MakeBox2D(ST_Point(170.0, 10.0), ST_Point(-170.0, 20.0)) AS bbox")
val bbox = df.collect()(0).getAs[Box2D]("bbox")
assert(bbox.getXMin == 170.0)
assert(bbox.getXMax == -170.0)
}
Comment on lines +142 to +167
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added in 16d9137. ST_PointZ inputs round-trip with Z ignored — Box2D gets the supplied X/Y verbatim.


it("ST_MakeBox2D ignores Z on 3D point input") {
val df = sparkSession.sql(
"SELECT ST_MakeBox2D(ST_PointZ(1.0, 2.0, 99.0), ST_PointZ(4.0, 5.0, 99.0)) AS bbox")
val bbox = df.collect()(0).getAs[Box2D]("bbox")
assert(bbox.getXMin == 1.0)
assert(bbox.getYMin == 2.0)
assert(bbox.getXMax == 4.0)
assert(bbox.getYMax == 5.0)
}

it("ST_MakeBox2D rejects non-point input") {
val ex = intercept[Exception] {
sparkSession
.sql("SELECT ST_MakeBox2D(ST_GeomFromText('LINESTRING(0 0, 1 1)'), ST_Point(2.0, 2.0))")
.collect()
}
assert(ex.getMessage.contains("ST_MakeBox2D requires two POINT geometries"))
}

it("Passed ST_PointFromText") {
var pointCsvDF = sparkSession.read
.format("csv")
Expand Down
Loading