Skip to content

Commit 662cd19

Browse files
authored
Merge pull request duckdb#389 from Maxxen/hilbert
Add new st_hilbert overload not requiring bounds parameter
2 parents b74def2 + e4a72cd commit 662cd19

File tree

1 file changed

+60
-17
lines changed

1 file changed

+60
-17
lines changed

spatial/src/spatial/core/functions/scalar/st_hilbert.cpp

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
#include "duckdb/parser/parsed_data/create_scalar_function_info.hpp"
2-
#include "duckdb/common/vector_operations/generic_executor.hpp"
31
#include "duckdb/common/constants.hpp"
2+
#include "duckdb/common/vector_operations/generic_executor.hpp"
3+
#include "duckdb/parser/parsed_data/create_scalar_function_info.hpp"
44
#include "spatial/common.hpp"
5-
#include "spatial/core/functions/scalar.hpp"
65
#include "spatial/core/functions/common.hpp"
6+
#include "spatial/core/functions/scalar.hpp"
77
#include "spatial/core/geometry/geometry.hpp"
8+
#include "spatial/core/util/math.hpp"
89
#include "spatial/core/types.hpp"
910

1011
#include <cmath>
@@ -76,6 +77,21 @@ inline uint32_t HilbertEncode(uint32_t n, uint32_t x, uint32_t y) {
7677
return ((Interleave(i1) << 1) | Interleave(i0)) >> (32 - 2 * n);
7778
}
7879

80+
static uint32_t FloatToUint32(float f)
81+
{
82+
if (std::isnan(f)) {
83+
return 0xFFFFFFFF;
84+
}
85+
uint32_t res;
86+
memcpy(&res, &f, sizeof(res));
87+
if((res & 0x80000000) != 0) {
88+
res ^= 0xFFFFFFFF;
89+
} else {
90+
res |= 0x80000000;
91+
}
92+
return res;
93+
}
94+
7995
//------------------------------------------------------------------------------
8096
// Coordinates
8197
//------------------------------------------------------------------------------
@@ -120,35 +136,61 @@ static void HilbertEncodeBoundsFunction(DataChunk &args, ExpressionState &state,
120136
using GEOM_TYPE = PrimitiveType<geometry_t>;
121137
using UINT32_TYPE = PrimitiveType<uint32_t>;
122138

123-
ArenaAllocator dummy_allocator(Allocator::DefaultAllocator());
124-
125139
GenericExecutor::ExecuteBinary<GEOM_TYPE, BOX_TYPE, UINT32_TYPE>(
126140
input_vec, bounds_vec, result, count, [&](const GEOM_TYPE &geom_type, const BOX_TYPE &bounds) {
127141
const auto geom = geom_type.val;
128142

129-
if (geom.GetType() != GeometryType::POINT) {
130-
throw InvalidInputException("ST_Hilbert only supports points");
131-
}
132-
const auto point = Geometry::Deserialize(dummy_allocator, geom);
133-
if (Point::IsEmpty(point)) {
134-
throw InvalidInputException("ST_Hilbert does not support empty points");
135-
}
143+
Box2D<double> geom_bounds;
144+
if(!geom.TryGetCachedBounds(geom_bounds)) {
145+
throw InvalidInputException("ST_Hilbert(geom, bounds) requires that all geometries have a bounding box");
146+
}
136147

137-
const auto v = Point::GetVertex(point);
138-
const auto x = v.x;
139-
const auto y = v.y;
148+
const auto dx = geom_bounds.min.x + (geom_bounds.max.x - geom_bounds.min.x) / 2;
149+
const auto dy = geom_bounds.min.y + (geom_bounds.max.y - geom_bounds.min.y) / 2;
140150

141151
const auto hilbert_width = max_hilbert / (bounds.c_val - bounds.a_val);
142152
const auto hilbert_height = max_hilbert / (bounds.d_val - bounds.b_val);
143153
// TODO: Check for overflow
144-
const auto hilbert_x = static_cast<uint32_t>((x - bounds.a_val) * hilbert_width);
145-
const auto hilbert_y = static_cast<uint32_t>((y - bounds.b_val) * hilbert_height);
154+
const auto hilbert_x = static_cast<uint32_t>((dx - bounds.a_val) * hilbert_width);
155+
const auto hilbert_y = static_cast<uint32_t>((dy - bounds.b_val) * hilbert_height);
146156

147157
const auto h = HilbertEncode(16, hilbert_x, hilbert_y);
148158
return UINT32_TYPE {h};
149159
});
150160
}
151161

162+
//------------------------------------------------------------------------------
163+
// GEOMETRY
164+
//------------------------------------------------------------------------------
165+
static void HilbertEncodeGeometryFunction(DataChunk &args, ExpressionState &state, Vector &result) {
166+
const auto count = args.size();
167+
auto &input_vec = args.data[0];
168+
169+
UnaryExecutor::ExecuteWithNulls<geometry_t, uint32_t>(
170+
input_vec, result, count, [&](const geometry_t &geom, ValidityMask &mask, idx_t out_idx) -> uint32_t {
171+
Box2D<double> bounds;
172+
if(!geom.TryGetCachedBounds(bounds)) {
173+
mask.SetInvalid(out_idx);
174+
return 0;
175+
}
176+
177+
Box2D<float> bounds_f;
178+
bounds_f.min.x = MathUtil::DoubleToFloatDown(bounds.min.x);
179+
bounds_f.min.y = MathUtil::DoubleToFloatDown(bounds.min.y);
180+
bounds_f.max.x = MathUtil::DoubleToFloatUp(bounds.max.x);
181+
bounds_f.max.y = MathUtil::DoubleToFloatUp(bounds.max.y);
182+
183+
const auto dx = bounds_f.min.x + (bounds_f.max.x - bounds_f.min.x) / 2;
184+
const auto dy = bounds_f.min.y + (bounds_f.max.y - bounds_f.min.y) / 2;
185+
186+
const auto hx = FloatToUint32(dx);
187+
const auto hy = FloatToUint32(dy);
188+
189+
return HilbertEncode(16, hx, hy);
190+
});
191+
}
192+
193+
152194
//------------------------------------------------------------------------------
153195
// BOX_2D/BOX_2DF
154196
//------------------------------------------------------------------------------
@@ -205,6 +247,7 @@ void CoreScalarFunctions::RegisterStHilbert(DatabaseInstance &db) {
205247
HilbertEncodeBoxFunction<double>));
206248
set.AddFunction(ScalarFunction({GeoTypes::BOX_2DF(), GeoTypes::BOX_2DF()}, LogicalType::UINTEGER,
207249
HilbertEncodeBoxFunction<float>));
250+
set.AddFunction(ScalarFunction({GeoTypes::GEOMETRY()}, LogicalType::UINTEGER, HilbertEncodeGeometryFunction));
208251

209252
ExtensionUtil::RegisterFunction(db, set);
210253
DocUtil::AddDocumentation(db, "ST_Hilbert", DOC_DESCRIPTION, DOC_EXAMPLE, DOC_TAGS);

0 commit comments

Comments
 (0)