Skip to content
Open
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
75 changes: 75 additions & 0 deletions src/h3/th3index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "temporal/temporal.hpp"
#include "geo/tgeompoint.hpp"
#include "geo/tgeogpoint.hpp"
#include "geo_util.hpp"
#include "spatial/spatial_types.hpp"
#include "tydef.hpp"
#include "duckdb/common/types/data_chunk.hpp"
#include "duckdb/main/extension/extension_loader.hpp"
Expand Down Expand Up @@ -74,9 +76,16 @@ LogicalType H3IndexTypes::TH3INDEX() {
return type;
}

LogicalType H3IndexTypes::H3INDEXSET() {
auto type = LogicalType(LogicalTypeId::BLOB);
type.SetAlias("H3INDEXSET");
return type;
}

void H3IndexTypes::RegisterTypes(ExtensionLoader &loader) {
loader.RegisterType("H3INDEX", H3INDEX());
loader.RegisterType("TH3INDEX", TH3INDEX());
loader.RegisterType("H3INDEXSET", H3INDEXSET());
}

void H3IndexTypes::RegisterCastFunctions(ExtensionLoader &loader) {
Expand Down Expand Up @@ -111,6 +120,22 @@ inline string_t TempToBlob(Vector &result, Temporal *t) {
/* TINT → BIGINT result for the int-returning H3 predicates. */
inline bool IntToBool(int r) { return r != 0; }

inline Set *BlobToSet(string_t blob) {
size_t sz = blob.GetSize();
uint8_t *copy = (uint8_t *) malloc(sz);
memcpy(copy, blob.GetData(), sz);
return reinterpret_cast<Set *>(copy);
}

inline string_t SetToBlob(Vector &result, Set *s) {
if (!s) return string_t();
size_t sz = set_mem_size(s);
string_t out = StringVector::AddStringOrBlob(
result, string_t(reinterpret_cast<const char *>(s), sz));
free(s);
return out;
}

} // namespace

/* =====================================================================
Expand Down Expand Up @@ -544,6 +569,45 @@ TH3_T_T_T_TEMP(Tne_th3index_th3index, tne_th3index_th3index)

#undef TH3_T_T_T_TEMP

/* =====================================================================
* Static geometry → h3indexset, and h3indexset × th3index prefilter
* ===================================================================== */

void H3IndexFunctions::Geo_to_h3index_set(DataChunk &args, ExpressionState &state,
Vector &result) {
BinaryExecutor::ExecuteWithNulls<string_t, int32_t, string_t>(
args.data[0], args.data[1], result, args.size(),
[&](string_t geom_blob, int32_t resolution, ValidityMask &mask,
idx_t idx) -> string_t {
/* H3 cells are inherently geographic (WGS84). The DuckDB
* spatial GEOMETRY blob has no embedded SRID, so callers
* must pass a geometry in EPSG:4326 coordinates (e.g.
* `ST_Transform(geom, 'EPSG:4326')`). We mark the
* GSERIALIZED with SRID 4326 explicitly. */
GSERIALIZED *gs = GeometryToGSerialized(geom_blob, 4326);
if (!gs) { mask.SetInvalid(idx); return string_t(); }
Set *s = geo_to_h3index_set(gs, resolution);
free(gs);
if (!s) { mask.SetInvalid(idx); return string_t(); }
return SetToBlob(result, s);
});
}

void H3IndexFunctions::Ever_eq_h3index_set_th3index(DataChunk &args,
ExpressionState &state, Vector &result) {
BinaryExecutor::ExecuteWithNulls<string_t, string_t, bool>(
args.data[0], args.data[1], result, args.size(),
[&](string_t set_blob, string_t temp_blob, ValidityMask &mask,
idx_t idx) -> bool {
Set *s = BlobToSet(set_blob);
Temporal *t = BlobToTemp(temp_blob);
int r = ever_eq_h3indexset_th3index(s, t);
free(s); free(t);
if (r < 0) { mask.SetInvalid(idx); return false; }
return IntToBool(r);
});
}

/* =====================================================================
* Registration
* ===================================================================== */
Expand Down Expand Up @@ -706,6 +770,17 @@ void H3IndexTypes::RegisterScalarFunctions(ExtensionLoader &loader) {
duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction(
"tgeogpointGreatCircleDistance", {TgeogpointType::TGEOGPOINT(), TgeogpointType::TGEOGPOINT(), V},
TemporalTypes::TFLOAT(), H3IndexFunctions::Tgeogpoint_great_circle_distance));

/* --- Static geometry h3 prefilter for trip × static cross-joins --- */
const auto H3SET = H3INDEXSET();
duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction(
"geoToH3IndexSet",
{GeoTypes::GEOMETRY(), LogicalType::INTEGER},
H3SET, H3IndexFunctions::Geo_to_h3index_set));
duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction(
"everEq",
{H3SET, TH3}, LogicalType::BOOLEAN,
H3IndexFunctions::Ever_eq_h3index_set_th3index));
}

} // namespace duckdb
12 changes: 12 additions & 0 deletions src/include/h3/th3index.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ namespace duckdb {
struct H3IndexTypes {
static LogicalType H3INDEX();
static LogicalType TH3INDEX();
/* H3INDEXSET is a Set<H3INDEX>, stored as a serialized Set* blob,
* built from a static geometry by `geoToH3IndexSet`. Used as the
* static side of the trip×static h3 prefilter on Q4 / Q7 / Q11 /
* Q12 / Q15 / Q17. */
static LogicalType H3INDEXSET();

static void RegisterTypes(ExtensionLoader &loader);
static void RegisterCastFunctions(ExtensionLoader &loader);
Expand Down Expand Up @@ -113,6 +118,13 @@ struct H3IndexFunctions {
static void Th3index_cell_area(DataChunk &args, ExpressionState &state, Vector &result);
static void Th3index_edge_length(DataChunk &args, ExpressionState &state, Vector &result);
static void Tgeogpoint_great_circle_distance(DataChunk &args, ExpressionState &state, Vector &result);

/* Static geometry → h3indexset (Set<H3INDEX>) at a given H3 resolution */
static void Geo_to_h3index_set(DataChunk &args, ExpressionState &state, Vector &result);

/* Trip × static h3indexset prefilter: true if any cell of the
* trajectory's th3index ever equals any cell of the static set. */
static void Ever_eq_h3index_set_th3index(DataChunk &args, ExpressionState &state, Vector &result);
};

} // namespace duckdb
29 changes: 29 additions & 0 deletions test/sql/h3_prefilter.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# name: test/sql/h3_prefilter.test
# description: H3INDEXSET prefilter UDFs smoke coverage
# group: [mobilityduck]

require mobilityduck

# geoToH3IndexSet: a POINT input returns an H3INDEXSET blob
query I
SELECT typeof(geoToH3IndexSet(ST_Point(-122.4185, 37.7758), 5));
----
H3INDEXSET

# everEq: trajectory passing through a cell in the set → true
query I
SELECT everEq(
geoToH3IndexSet(ST_Point(-122.4185, 37.7758), 5),
th3index(tgeompoint(ST_Point(-122.4185, 37.7758), timestamptz '2023-01-01', 4326), 5)
);
----
true

# everEq: trajectory at a distant cell → false
query I
SELECT everEq(
geoToH3IndexSet(ST_Point(0.0, 0.0), 5),
th3index(tgeompoint(ST_Point(-122.4185, 37.7758), timestamptz '2023-01-01', 4326), 5)
);
----
false
Loading