Skip to content

Commit b8e6ca2

Browse files
authored
Merge pull request #601 from yutannihilation/fix/box2d-binary-op
Add `&&` operator for `BOX_2D`
2 parents 7fee7e0 + 05b3434 commit b8e6ca2

File tree

5 files changed

+434
-0
lines changed

5 files changed

+434
-0
lines changed

docs/functions.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
| Function | Summary |
77
| --- | --- |
8+
| [`&&`](#&&) | Returns true if the bounding boxes intersects. |
89
| [`DuckDB_PROJ_Compiled_Version`](#duckdb_proj_compiled_version) | Returns a text description of the PROJ library version that that this instance of DuckDB was compiled against. |
910
| [`DuckDB_Proj_Version`](#duckdb_proj_version) | Returns a text description of the PROJ library version that is being used by this instance of DuckDB. |
1011
| [`ST_Affine`](#st_affine) | Applies an affine transformation to a geometry. |
@@ -81,6 +82,7 @@
8182
| [`ST_M`](#st_m) | Returns the M coordinate of a point geometry |
8283
| [`ST_MMax`](#st_mmax) | Returns the maximum M coordinate of a geometry |
8384
| [`ST_MMin`](#st_mmin) | Returns the minimum M coordinate of a geometry |
85+
| [`ST_MakeBox2D`](#st_makebox2d) | Create a BOX2D from two POINT geometries |
8486
| [`ST_MakeEnvelope`](#st_makeenvelope) | Create a rectangular polygon from min/max coordinates |
8587
| [`ST_MakeLine`](#st_makeline) | Create a LINESTRING from a list of POINT geometries |
8688
| [`ST_MakePolygon`](#st_makepolygon) | Create a POLYGON from a LINESTRING shell |
@@ -175,6 +177,36 @@
175177

176178
## Scalar Functions
177179

180+
### &&
181+
182+
183+
#### Signature
184+
185+
```sql
186+
BOOLEAN && (box BOX_2D, geom GEOMETRY)
187+
```
188+
189+
#### Description
190+
191+
Returns true if the bounding boxes intersects.
192+
193+
Note that, this operation is not very accurate; `&&` compares the cached bbox of the geometry using float precision.
194+
If you prefer accuracy, please use some other function like `ST_Intersects()`.
195+
196+
#### Example
197+
198+
```sql
199+
SELECT ST_MakeBox2D('POINT (0 0)'::GEOMETRY, 'POINT (2 2)'::GEOMETRY) && ST_POINT(1, 1);
200+
----
201+
true
202+
203+
SELECT ST_MakeBox2D('POINT (0 0)'::GEOMETRY, 'POINT (2 2)'::GEOMETRY) && ST_POINT(5, 5);
204+
----
205+
false
206+
```
207+
208+
----
209+
178210
### DuckDB_PROJ_Compiled_Version
179211

180212

@@ -1791,6 +1823,29 @@ SELECT ST_MMin(ST_Point(1, 2, 3, 4))
17911823

17921824
----
17931825

1826+
### ST_MakeBox2D
1827+
1828+
1829+
#### Signature
1830+
1831+
```sql
1832+
BOX_2D ST_MakeBox2D (point1 GEOMETRY, point2 GEOMETRY)
1833+
```
1834+
1835+
#### Description
1836+
1837+
Create a BOX2D from two POINT geometries
1838+
1839+
#### Example
1840+
1841+
```sql
1842+
SELECT ST_MakeBox2D(ST_Point(0, 0), ST_Point(1, 1));
1843+
----
1844+
BOX(0 0, 1 1)
1845+
```
1846+
1847+
----
1848+
17941849
### ST_MakeEnvelope
17951850

17961851

generate_function_reference.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
any_value(tags) AS func_tags,
3030
FROM duckdb_functions() as funcs
3131
WHERE function_type = '$FUNCTION_TYPE$'
32+
-- function-specific tweaks
33+
AND CASE function_name
34+
-- TODO: https://github.com/duckdb/duckdb-spatial/pull/601#discussion_r2144753435
35+
WHEN '&&' THEN 'box' IN parameters
36+
ELSE true
37+
END
3238
GROUP BY function_name, function_type
3339
HAVING func_tags['ext'] = 'spatial'
3440
-- TODO: macros cannot have tags

src/spatial/modules/main/spatial_functions_scalar.cpp

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3239,6 +3239,130 @@ struct ST_Extent_Approx {
32393239
}
32403240
};
32413241

3242+
//======================================================================================================================
3243+
// &&
3244+
//======================================================================================================================
3245+
3246+
struct Op_IntersectApprox {
3247+
3248+
//------------------------------------------------------------------------------------------------------------------
3249+
// Execute
3250+
//------------------------------------------------------------------------------------------------------------------
3251+
static void Execute(DataChunk &args, ExpressionState &state, Vector &result) {
3252+
3253+
const auto count = args.size();
3254+
auto &box = args.data[0];
3255+
auto &geom = args.data[1];
3256+
3257+
auto result_data = FlatVector::GetData<bool>(result);
3258+
3259+
// Convert box to unified format
3260+
UnifiedVectorFormat box_vdata;
3261+
box.ToUnifiedFormat(count, box_vdata);
3262+
3263+
// Get the struct entries and convert them to unified format
3264+
const auto &bbox_vec = StructVector::GetEntries(box);
3265+
UnifiedVectorFormat box_min_x_vdata, box_min_y_vdata, box_max_x_vdata, box_max_y_vdata;
3266+
bbox_vec[0]->ToUnifiedFormat(count, box_min_x_vdata);
3267+
bbox_vec[1]->ToUnifiedFormat(count, box_min_y_vdata);
3268+
bbox_vec[2]->ToUnifiedFormat(count, box_max_x_vdata);
3269+
bbox_vec[3]->ToUnifiedFormat(count, box_max_y_vdata);
3270+
3271+
const auto box_min_x_data = UnifiedVectorFormat::GetData<double>(box_min_x_vdata);
3272+
const auto box_min_y_data = UnifiedVectorFormat::GetData<double>(box_min_y_vdata);
3273+
const auto box_max_x_data = UnifiedVectorFormat::GetData<double>(box_max_x_vdata);
3274+
const auto box_max_y_data = UnifiedVectorFormat::GetData<double>(box_max_y_vdata);
3275+
3276+
// Convert geometry to unified format
3277+
UnifiedVectorFormat input_geom_vdata;
3278+
geom.ToUnifiedFormat(count, input_geom_vdata);
3279+
const auto input_geom = UnifiedVectorFormat::GetData<geometry_t>(input_geom_vdata);
3280+
3281+
for (idx_t i = 0; i < count; i++) {
3282+
// Get the actual indices for box and geometry
3283+
const auto box_idx = box_vdata.sel->get_index(i);
3284+
const auto geom_idx = input_geom_vdata.sel->get_index(i);
3285+
3286+
// Check validity of both inputs
3287+
if (!box_vdata.validity.RowIsValid(box_idx) || !input_geom_vdata.validity.RowIsValid(geom_idx)) {
3288+
FlatVector::SetNull(result, i, true);
3289+
continue;
3290+
}
3291+
3292+
// Get box coordinate indices
3293+
const auto box_min_x_idx = box_min_x_vdata.sel->get_index(i);
3294+
const auto box_min_y_idx = box_min_y_vdata.sel->get_index(i);
3295+
const auto box_max_x_idx = box_max_x_vdata.sel->get_index(i);
3296+
const auto box_max_y_idx = box_max_y_vdata.sel->get_index(i);
3297+
3298+
// Check validity of box coordinates
3299+
if (!box_min_x_vdata.validity.RowIsValid(box_min_x_idx) ||
3300+
!box_min_y_vdata.validity.RowIsValid(box_min_y_idx) ||
3301+
!box_max_x_vdata.validity.RowIsValid(box_max_x_idx) ||
3302+
!box_max_y_vdata.validity.RowIsValid(box_max_y_idx)) {
3303+
FlatVector::SetNull(result, i, true);
3304+
continue;
3305+
}
3306+
3307+
auto &geom_blob = input_geom[geom_idx];
3308+
3309+
// Try to get the cached bounding box from the blob
3310+
Box2D<float> geom_bbox;
3311+
if (geom_blob.TryGetCachedBounds(geom_bbox)) {
3312+
const auto box_min_x = box_min_x_data[box_min_x_idx];
3313+
const auto box_min_y = box_min_y_data[box_min_y_idx];
3314+
const auto box_max_x = box_max_x_data[box_max_x_idx];
3315+
const auto box_max_y = box_max_y_data[box_max_y_idx];
3316+
3317+
result_data[i] = (box_min_x <= geom_bbox.max.x && geom_bbox.min.x <= box_max_x) &&
3318+
(box_min_y <= geom_bbox.max.y && geom_bbox.min.y <= box_max_y);
3319+
} else {
3320+
// No bounding box, return null
3321+
FlatVector::SetNull(result, i, true);
3322+
}
3323+
}
3324+
3325+
if (box.GetVectorType() == VectorType::CONSTANT_VECTOR) {
3326+
result.SetVectorType(VectorType::CONSTANT_VECTOR);
3327+
}
3328+
}
3329+
3330+
//------------------------------------------------------------------------------------------------------------------
3331+
// Register
3332+
//------------------------------------------------------------------------------------------------------------------
3333+
static void Register(DatabaseInstance &db) {
3334+
FunctionBuilder::RegisterScalar(db, "&&", [](ScalarFunctionBuilder &func) {
3335+
func.AddVariant([](ScalarFunctionVariantBuilder &variant) {
3336+
variant.AddParameter("box", GeoTypes::BOX_2D());
3337+
variant.AddParameter("geom", GeoTypes::GEOMETRY());
3338+
variant.SetReturnType(LogicalType::BOOLEAN);
3339+
3340+
variant.SetFunction(Execute);
3341+
});
3342+
3343+
func.SetDescription(R"(
3344+
Returns true if the bounding boxes intersects.
3345+
3346+
Note that, this operation is not very accurate; `&&` compares the cached bbox of the geometry using float precision.
3347+
If you prefer accuracy, please use some other function like `ST_Intersects()`.
3348+
)");
3349+
3350+
func.SetExample(R"(
3351+
SELECT ST_MakeBox2D('POINT (0 0)'::GEOMETRY, 'POINT (2 2)'::GEOMETRY) && ST_POINT(1, 1);
3352+
----
3353+
true
3354+
3355+
SELECT ST_MakeBox2D('POINT (0 0)'::GEOMETRY, 'POINT (2 2)'::GEOMETRY) && ST_POINT(5, 5);
3356+
----
3357+
false
3358+
)");
3359+
3360+
func.SetTag("ext", "spatial");
3361+
func.SetTag("category", "property");
3362+
});
3363+
}
3364+
};
3365+
32423366
//======================================================================================================================
32433367
// ST_ExteriorRing
32443368
//======================================================================================================================
@@ -6516,6 +6640,105 @@ struct ST_MakePolygon {
65166640
}
65176641
};
65186642

6643+
//======================================================================================================================
6644+
// ST_MakeBox2D
6645+
//======================================================================================================================
6646+
6647+
struct ST_MakeBox2D {
6648+
//------------------------------------------------------------------------------------------------------------------
6649+
// Execute (GEOMETRY, GEOMETRY)
6650+
//------------------------------------------------------------------------------------------------------------------
6651+
static void ExecuteBinary(DataChunk &args, ExpressionState &state, Vector &result) {
6652+
auto &lstate = LocalState::ResetAndGet(state);
6653+
6654+
const auto &bbox_vec = StructVector::GetEntries(result);
6655+
const auto min_x_data = FlatVector::GetData<double>(*bbox_vec[0]);
6656+
const auto min_y_data = FlatVector::GetData<double>(*bbox_vec[1]);
6657+
const auto max_x_data = FlatVector::GetData<double>(*bbox_vec[2]);
6658+
const auto max_y_data = FlatVector::GetData<double>(*bbox_vec[3]);
6659+
6660+
UnifiedVectorFormat input_vdata1;
6661+
UnifiedVectorFormat input_vdata2;
6662+
args.data[0].ToUnifiedFormat(args.size(), input_vdata1);
6663+
args.data[1].ToUnifiedFormat(args.size(), input_vdata2);
6664+
const auto input_data1 = UnifiedVectorFormat::GetData<string_t>(input_vdata1);
6665+
const auto input_data2 = UnifiedVectorFormat::GetData<string_t>(input_vdata2);
6666+
6667+
const auto count = args.size();
6668+
6669+
for (idx_t out_idx = 0; out_idx < count; out_idx++) {
6670+
const auto row_idx1 = input_vdata1.sel->get_index(out_idx);
6671+
const auto row_idx2 = input_vdata2.sel->get_index(out_idx);
6672+
if (!input_vdata1.validity.RowIsValid(row_idx1) || !input_vdata2.validity.RowIsValid(row_idx2)) {
6673+
FlatVector::SetNull(result, out_idx, true);
6674+
continue;
6675+
}
6676+
6677+
const auto &blob1 = input_data1[row_idx1];
6678+
const auto &blob2 = input_data2[row_idx2];
6679+
sgl::geometry geom1;
6680+
sgl::geometry geom2;
6681+
lstate.Deserialize(blob1, geom1);
6682+
lstate.Deserialize(blob2, geom2);
6683+
6684+
if (geom1.get_type() != sgl::geometry_type::POINT || geom2.get_type() != sgl::geometry_type::POINT) {
6685+
throw InvalidInputException("ST_MakeBox2D only accepts POINT geometries");
6686+
}
6687+
6688+
if (geom1.is_empty() || geom2.is_empty()) {
6689+
FlatVector::SetNull(result, out_idx, true);
6690+
continue;
6691+
}
6692+
6693+
const auto v1 = geom1.get_vertex_xy(0);
6694+
const auto v2 = geom2.get_vertex_xy(0);
6695+
6696+
min_x_data[out_idx] = std::min(v1.x, v2.x);
6697+
min_y_data[out_idx] = std::min(v1.y, v2.y);
6698+
max_x_data[out_idx] = std::max(v1.x, v2.x);
6699+
max_y_data[out_idx] = std::max(v1.y, v2.y);
6700+
}
6701+
6702+
if (args.AllConstant()) {
6703+
result.SetVectorType(VectorType::CONSTANT_VECTOR);
6704+
}
6705+
}
6706+
6707+
//------------------------------------------------------------------------------------------------------------------
6708+
// Documentation
6709+
//------------------------------------------------------------------------------------------------------------------
6710+
static constexpr auto DESCRIPTION_BINARY = R"(
6711+
Create a BOX2D from two POINT geometries
6712+
)";
6713+
static constexpr auto EXAMPLE_BINARY = R"(
6714+
SELECT ST_MakeBox2D(ST_Point(0, 0), ST_Point(1, 1));
6715+
----
6716+
BOX(0 0, 1 1)
6717+
)";
6718+
6719+
//------------------------------------------------------------------------------------------------------------------
6720+
// Register
6721+
//------------------------------------------------------------------------------------------------------------------
6722+
static void Register(DatabaseInstance &db) {
6723+
FunctionBuilder::RegisterScalar(db, "ST_MakeBox2D", [](ScalarFunctionBuilder &func) {
6724+
func.AddVariant([](ScalarFunctionVariantBuilder &variant) {
6725+
variant.AddParameter("point1", GeoTypes::GEOMETRY());
6726+
variant.AddParameter("point2", GeoTypes::GEOMETRY());
6727+
variant.SetReturnType(GeoTypes::BOX_2D());
6728+
6729+
variant.SetInit(LocalState::Init);
6730+
variant.SetFunction(ExecuteBinary);
6731+
6732+
variant.SetDescription(DESCRIPTION_BINARY);
6733+
variant.SetExample(EXAMPLE_BINARY);
6734+
});
6735+
6736+
func.SetTag("ext", "spatial");
6737+
func.SetTag("category", "construction");
6738+
});
6739+
}
6740+
};
6741+
65196742
//======================================================================================================================
65206743
// ST_Multi
65216744
//======================================================================================================================
@@ -8609,6 +8832,7 @@ void RegisterSpatialScalarFunctions(DatabaseInstance &db) {
86098832
ST_EndPoint::Register(db);
86108833
ST_Extent::Register(db);
86118834
ST_Extent_Approx::Register(db);
8835+
Op_IntersectApprox::Register(db);
86128836
ST_ExteriorRing::Register(db);
86138837
ST_FlipCoordinates::Register(db);
86148838
ST_Force2D::Register(db);
@@ -8636,6 +8860,7 @@ void RegisterSpatialScalarFunctions(DatabaseInstance &db) {
86368860
ST_MakeEnvelope::Register(db);
86378861
ST_MakeLine::Register(db);
86388862
ST_MakePolygon::Register(db);
8863+
ST_MakeBox2D::Register(db);
86398864
ST_Multi::Register(db);
86408865
ST_NGeometries::Register(db);
86418866
ST_NInteriorRings::Register(db);

0 commit comments

Comments
 (0)