diff --git a/CMakeLists.txt b/CMakeLists.txt index 47640ba4..1cc6f8ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,25 @@ endif() # MEOS from overlay port find_package(MEOS CONFIG REQUIRED) +# h3 — transitively needed because `meos_h3.h` includes ``. +# vcpkg's h3 port installs the header at `include/h3/h3api.h` (under +# a subdirectory). `find_package(h3 CONFIG)` finds the import target +# but its `INTERFACE_INCLUDE_DIRECTORIES` only contains `include/`, +# not `include/h3/`. Probe the header directly with `find_path` and +# add the resolved directory to the global include path so the +# `#include ` in `meos_h3.h` resolves. +find_package(h3 CONFIG) +if(h3_FOUND) + message(STATUS "Found h3 (CONFIG): version ${h3_VERSION}") +endif() +find_path(H3_INCLUDE_DIR NAMES h3api.h PATH_SUFFIXES h3) +if(H3_INCLUDE_DIR) + include_directories(${H3_INCLUDE_DIR}) + message(STATUS "Found h3 include dir: ${H3_INCLUDE_DIR}") +else() + message(WARNING "h3api.h not found; the th3index extension surface will fail to compile") +endif() + if(TARGET GEOS::geos_c) set(GEOS_TGT GEOS::geos_c) elseif(TARGET GEOS::geos) @@ -89,6 +108,7 @@ set(EXTENSION_SOURCES src/geo/tgeogpoint.cpp src/geo/tgeogpoint_in_out.cpp src/geo/tgeogpoint_ops.cpp + src/h3/th3index.cpp src/index/rtree_module.cpp src/single_tile_getters.cpp src/index/rtree_index_create_physical.cpp @@ -113,6 +133,10 @@ endif() # ----------------------------- # Link libraries # ----------------------------- +if(TARGET h3::h3) + set(H3_TGT h3::h3) +endif() + target_link_libraries(${EXTENSION_NAME} MEOS::meos ${GEOS_TGT} @@ -120,6 +144,7 @@ target_link_libraries(${EXTENSION_NAME} GSL::gsl GSL::gslcblas ${JSONC_TGT} + ${H3_TGT} OpenSSL::SSL OpenSSL::Crypto ) @@ -131,6 +156,7 @@ target_link_libraries(${LOADABLE_EXTENSION_NAME} GSL::gsl GSL::gslcblas ${JSONC_TGT} + ${H3_TGT} OpenSSL::SSL OpenSSL::Crypto ) diff --git a/src/h3/th3index.cpp b/src/h3/th3index.cpp new file mode 100644 index 00000000..ee6a03e1 --- /dev/null +++ b/src/h3/th3index.cpp @@ -0,0 +1,711 @@ +/* MobilityDuck binding for the MEOS H3 cell index types (h3index + + * th3index). Wraps every export from `meos_h3.h` so DuckDB SQL can + * call the full H3 surface — primarily for the cross-platform + * BerlinMOD benchmark prefilter (matching MobilitySpark PR #9). + * + * H3INDEX is surfaced as BIGINT (the 64-bit cell id reinterprets + * losslessly). TH3INDEX is a Temporal* blob stored as BLOB. + */ + +#include "h3/th3index.hpp" +#include "temporal/temporal.hpp" +#include "geo/tgeompoint.hpp" +#include "geo/tgeogpoint.hpp" +#include "tydef.hpp" +#include "duckdb/common/types/data_chunk.hpp" +#include "duckdb/main/extension/extension_loader.hpp" +#include "duckdb/common/extension_type_info.hpp" +#include "duckdb/function/scalar_function.hpp" +#include "mobilityduck/meos_exec_serial.hpp" +#include "time_util.hpp" + +extern "C" { + #include + #include + #include + #include +} + +namespace { + +/* MEOS commit beddae670 declares `h3index_in` and `h3index_out` in + * `meos_h3.h` but does not define them in the source tree. They are + * thin wrappers around h3's `stringToH3` / `h3ToString` — implement + * locally so MobilityDuck's H3INDEX cast / text-output paths link. + * + * Drop these definitions once upstream MEOS ships its own versions. + */ +extern "C" H3Index h3index_in(const char *str) { + H3Index out = 0; + H3Error err = stringToH3(str, &out); + if (err != E_SUCCESS) { + return 0; + } + return out; +} + +extern "C" char *h3index_out(H3Index cell) { + /* H3's textual form is "xxxxxxxxxxxxxxxx" — 16 hex digits + + * NUL. Allocate slightly more for safety. */ + char *buf = (char *) malloc(32); + if (!buf) return nullptr; + H3Error err = h3ToString(cell, buf, 32); + if (err != E_SUCCESS) { + buf[0] = '\0'; + } + return buf; +} + +} + +namespace duckdb { + +LogicalType H3IndexTypes::H3INDEX() { + /* 64-bit unsigned cell id; surface as BIGINT (signed reinterpretation + * is safe — equality / ordering care only about the bit pattern). */ + LogicalType type = LogicalType::BIGINT; + type.SetAlias("H3INDEX"); + return type; +} + +LogicalType H3IndexTypes::TH3INDEX() { + auto type = LogicalType(LogicalTypeId::BLOB); + type.SetAlias("TH3INDEX"); + return type; +} + +void H3IndexTypes::RegisterTypes(ExtensionLoader &loader) { + loader.RegisterType("H3INDEX", H3INDEX()); + loader.RegisterType("TH3INDEX", TH3INDEX()); +} + +void H3IndexTypes::RegisterCastFunctions(ExtensionLoader &loader) { + loader.RegisterCastFunction(LogicalType::VARCHAR, H3INDEX(), + H3IndexFunctions::H3index_in_cast); + loader.RegisterCastFunction(H3INDEX(), LogicalType::VARCHAR, + H3IndexFunctions::H3index_out_cast); + loader.RegisterCastFunction(LogicalType::VARCHAR, TH3INDEX(), + H3IndexFunctions::Th3index_in_cast); + loader.RegisterCastFunction(TH3INDEX(), LogicalType::VARCHAR, + H3IndexFunctions::Th3index_out_cast); +} + +namespace { + +inline Temporal *BlobToTemp(string_t blob) { + size_t sz = blob.GetSize(); + uint8_t *copy = (uint8_t *) malloc(sz); + memcpy(copy, blob.GetData(), sz); + return reinterpret_cast(copy); +} + +inline string_t TempToBlob(Vector &result, Temporal *t) { + if (!t) return string_t(); + size_t sz = temporal_mem_size(t); + string_t out = StringVector::AddStringOrBlob( + result, string_t(reinterpret_cast(t), sz)); + free(t); + return out; +} + +/* TINT → BIGINT result for the int-returning H3 predicates. */ +inline bool IntToBool(int r) { return r != 0; } + +} // namespace + +/* ===================================================================== + * In / out — H3 cell scalar (BIGINT bit-pattern of uint64 H3Index) + * ===================================================================== */ + +bool H3IndexFunctions::H3index_in_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + UnaryExecutor::Execute( + source, result, count, + [&](string_t s) -> int64_t { + std::string str(s.GetData(), s.GetSize()); + H3Index h = h3index_in(str.c_str()); + return static_cast(h); + }); + return true; +} + +bool H3IndexFunctions::H3index_out_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + UnaryExecutor::Execute( + source, result, count, + [&](int64_t v) -> string_t { + char *s = h3index_out(static_cast(v)); + std::string copy(s); + free(s); + return StringVector::AddString(result, copy); + }); + return true; +} + +void H3IndexFunctions::H3index_from_text(DataChunk &args, ExpressionState &state, Vector &result) { + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](string_t s) -> int64_t { + std::string str(s.GetData(), s.GetSize()); + H3Index h = h3index_in(str.c_str()); + return static_cast(h); + }); +} + +void H3IndexFunctions::H3index_as_text(DataChunk &args, ExpressionState &state, Vector &result) { + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](int64_t v) -> string_t { + char *s = h3index_out(static_cast(v)); + std::string copy(s); + free(s); + return StringVector::AddString(result, copy); + }); +} + +/* ===================================================================== + * In / out — TH3INDEX temporal blob + * ===================================================================== */ + +bool H3IndexFunctions::Th3index_in_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + UnaryExecutor::Execute( + source, result, count, + [&](string_t s) -> string_t { + std::string str(s.GetData(), s.GetSize()); + Temporal *t = th3index_in(str.c_str()); + return TempToBlob(result, t); + }); + return true; +} + +bool H3IndexFunctions::Th3index_out_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + UnaryExecutor::Execute( + source, result, count, + [&](string_t blob) -> string_t { + Temporal *t = BlobToTemp(blob); + char *str = temporal_out(t, OUT_DEFAULT_DECIMAL_DIGITS); + free(t); + std::string copy(str); + free(str); + return StringVector::AddString(result, copy); + }); + return true; +} + +/* ===================================================================== + * Constructor — th3indexinst_make wrapped as `th3index(cell, t)` + * ===================================================================== */ + +void H3IndexFunctions::Th3index_make(DataChunk &args, ExpressionState &state, Vector &result) { + BinaryExecutor::Execute( + args.data[0], args.data[1], result, args.size(), + [&](int64_t cell, timestamp_tz_t t) -> string_t { + TInstant *inst = th3indexinst_make(static_cast(cell), ToMeosTimestamp(t)); + return TempToBlob(result, reinterpret_cast(inst)); + }); +} + +/* ===================================================================== + * Accessors + * ===================================================================== */ + +void H3IndexFunctions::Th3index_start_value(DataChunk &args, ExpressionState &state, Vector &result) { + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](string_t blob) -> int64_t { + Temporal *t = BlobToTemp(blob); + H3Index v = th3index_start_value(t); + free(t); + return static_cast(v); + }); +} + +void H3IndexFunctions::Th3index_end_value(DataChunk &args, ExpressionState &state, Vector &result) { + UnaryExecutor::Execute( + args.data[0], result, args.size(), + [&](string_t blob) -> int64_t { + Temporal *t = BlobToTemp(blob); + H3Index v = th3index_end_value(t); + free(t); + return static_cast(v); + }); +} + +void H3IndexFunctions::Th3index_value_n(DataChunk &args, ExpressionState &state, Vector &result) { + BinaryExecutor::ExecuteWithNulls( + args.data[0], args.data[1], result, args.size(), + [&](string_t blob, int32_t n, ValidityMask &mask, idx_t idx) -> int64_t { + Temporal *t = BlobToTemp(blob); + H3Index v; + bool ok = th3index_value_n(t, n, &v); + free(t); + if (!ok) { mask.SetInvalid(idx); return 0; } + return static_cast(v); + }); +} + +void H3IndexFunctions::Th3index_values(DataChunk &args, ExpressionState &state, Vector &result) { + /* H3Index[] → LIST; surface as a list of cell ids. */ + auto &input = args.data[0]; + input.Flatten(args.size()); + auto in_data = FlatVector::GetData(input); + auto list_entries = FlatVector::GetData(result); + auto &out_validity = FlatVector::Validity(result); + idx_t total = 0; + for (idx_t row = 0; row < args.size(); row++) { + if (!FlatVector::Validity(input).RowIsValid(row)) { + out_validity.SetInvalid(row); + list_entries[row] = list_entry_t{total, 0}; + continue; + } + Temporal *t = BlobToTemp(in_data[row]); + int n = 0; + H3Index *vals = th3index_values(t, &n); + free(t); + if (!vals || n <= 0) { + if (vals) free(vals); + list_entries[row] = list_entry_t{total, 0}; + continue; + } + ListVector::Reserve(result, total + n); + ListVector::SetListSize(result, total + n); + list_entries[row] = list_entry_t{total, static_cast(n)}; + auto child = FlatVector::GetData(ListVector::GetEntry(result)); + for (int i = 0; i < n; i++) { + child[total + i] = static_cast(vals[i]); + } + total += n; + free(vals); + } +} + +void H3IndexFunctions::Th3index_value_at_timestamptz(DataChunk &args, ExpressionState &state, Vector &result) { + BinaryExecutor::ExecuteWithNulls( + args.data[0], args.data[1], result, args.size(), + [&](string_t blob, timestamp_tz_t t, ValidityMask &mask, idx_t idx) -> int64_t { + Temporal *temp = BlobToTemp(blob); + H3Index v; + bool ok = th3index_value_at_timestamptz(temp, ToMeosTimestamp(t), true, &v); + free(temp); + if (!ok) { mask.SetInvalid(idx); return 0; } + return static_cast(v); + }); +} + +/* ===================================================================== + * Casts to/from other temporal types — all `Temporal *fn(const Temporal *)` + * ===================================================================== */ + +#define TH3_UNARY_TEMP(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + UnaryExecutor::ExecuteWithNulls( \ + args.data[0], result, args.size(), \ + [&](string_t blob, ValidityMask &mask, idx_t idx) -> string_t { \ + Temporal *t = BlobToTemp(blob); \ + Temporal *r = FN(t); \ + free(t); \ + if (!r) { mask.SetInvalid(idx); return string_t(); } \ + return TempToBlob(result, r); \ + }); \ +} + +TH3_UNARY_TEMP(Tbigint_to_th3index, tbigint_to_th3index) +TH3_UNARY_TEMP(Th3index_to_tbigint, th3index_to_tbigint) +TH3_UNARY_TEMP(Th3index_to_tgeogpoint, th3index_to_tgeogpoint) +TH3_UNARY_TEMP(Th3index_to_tgeompoint, th3index_to_tgeompoint) +TH3_UNARY_TEMP(Th3index_get_resolution, th3index_get_resolution) +TH3_UNARY_TEMP(Th3index_get_base_cell_number, th3index_get_base_cell_number) +TH3_UNARY_TEMP(Th3index_is_valid_cell, th3index_is_valid_cell) +TH3_UNARY_TEMP(Th3index_is_res_class_iii, th3index_is_res_class_iii) +TH3_UNARY_TEMP(Th3index_is_pentagon, th3index_is_pentagon) +TH3_UNARY_TEMP(Th3index_cell_to_parent_next, th3index_cell_to_parent_next) +TH3_UNARY_TEMP(Th3index_cell_to_center_child_next, th3index_cell_to_center_child_next) +TH3_UNARY_TEMP(Th3index_cell_to_boundary, th3index_cell_to_boundary) +TH3_UNARY_TEMP(Th3index_is_valid_directed_edge, th3index_is_valid_directed_edge) +TH3_UNARY_TEMP(Th3index_get_directed_edge_origin, th3index_get_directed_edge_origin) +TH3_UNARY_TEMP(Th3index_get_directed_edge_destination, th3index_get_directed_edge_destination) +TH3_UNARY_TEMP(Th3index_directed_edge_to_boundary, th3index_directed_edge_to_boundary) +TH3_UNARY_TEMP(Th3index_vertex_to_latlng, th3index_vertex_to_latlng) +TH3_UNARY_TEMP(Th3index_is_valid_vertex, th3index_is_valid_vertex) + +#undef TH3_UNARY_TEMP + +#define TH3_TEMP_INT32_TEMP(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + BinaryExecutor::ExecuteWithNulls( \ + args.data[0], args.data[1], result, args.size(), \ + [&](string_t blob, int32_t n, ValidityMask &mask, idx_t idx) -> string_t { \ + Temporal *t = BlobToTemp(blob); \ + Temporal *r = FN(t, n); \ + free(t); \ + if (!r) { mask.SetInvalid(idx); return string_t(); } \ + return TempToBlob(result, r); \ + }); \ +} + +TH3_TEMP_INT32_TEMP(Tgeogpoint_to_th3index, tgeogpoint_to_th3index) +TH3_TEMP_INT32_TEMP(Tgeompoint_to_th3index, tgeompoint_to_th3index) +TH3_TEMP_INT32_TEMP(Th3index_cell_to_parent, th3index_cell_to_parent) +TH3_TEMP_INT32_TEMP(Th3index_cell_to_center_child, th3index_cell_to_center_child) +TH3_TEMP_INT32_TEMP(Th3index_cell_to_child_pos, th3index_cell_to_child_pos) +TH3_TEMP_INT32_TEMP(Th3index_cell_to_vertex, th3index_cell_to_vertex) + +#undef TH3_TEMP_INT32_TEMP + +/* th3index_child_pos_to_cell takes (Temporal *, Temporal *, int32). */ +void H3IndexFunctions::Th3index_child_pos_to_cell(DataChunk &args, ExpressionState &state, Vector &result) { + TernaryExecutor::ExecuteWithNulls( + args.data[0], args.data[1], args.data[2], result, args.size(), + [&](string_t a, string_t b, int32_t res, ValidityMask &mask, idx_t idx) -> string_t { + Temporal *child_pos = BlobToTemp(a); + Temporal *parent = BlobToTemp(b); + Temporal *r = th3index_child_pos_to_cell(child_pos, parent, res); + free(child_pos); free(parent); + if (!r) { mask.SetInvalid(idx); return string_t(); } + return TempToBlob(result, r); + }); +} + +#define TH3_TEMP_TEMP_TEMP(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + BinaryExecutor::ExecuteWithNulls( \ + args.data[0], args.data[1], result, args.size(), \ + [&](string_t a, string_t b, ValidityMask &mask, idx_t idx) -> string_t { \ + Temporal *t1 = BlobToTemp(a); \ + Temporal *t2 = BlobToTemp(b); \ + Temporal *r = FN(t1, t2); \ + free(t1); free(t2); \ + if (!r) { mask.SetInvalid(idx); return string_t(); } \ + return TempToBlob(result, r); \ + }); \ +} + +TH3_TEMP_TEMP_TEMP(Th3index_are_neighbor_cells, th3index_are_neighbor_cells) +TH3_TEMP_TEMP_TEMP(Th3index_cells_to_directed_edge, th3index_cells_to_directed_edge) +TH3_TEMP_TEMP_TEMP(Th3index_grid_distance, th3index_grid_distance) +TH3_TEMP_TEMP_TEMP(Th3index_cell_to_local_ij, th3index_cell_to_local_ij) +TH3_TEMP_TEMP_TEMP(Th3index_local_ij_to_cell, th3index_local_ij_to_cell) + +#undef TH3_TEMP_TEMP_TEMP + +/* tgeogpoint_great_circle_distance(a, b, unit) — Temporal × Temporal × VARCHAR. */ +void H3IndexFunctions::Tgeogpoint_great_circle_distance(DataChunk &args, ExpressionState &state, Vector &result) { + TernaryExecutor::ExecuteWithNulls( + args.data[0], args.data[1], args.data[2], result, args.size(), + [&](string_t a, string_t b, string_t unit, ValidityMask &mask, idx_t idx) -> string_t { + Temporal *t1 = BlobToTemp(a); + Temporal *t2 = BlobToTemp(b); + std::string u(unit.GetData(), unit.GetSize()); + Temporal *r = tgeogpoint_great_circle_distance(t1, t2, u.c_str()); + free(t1); free(t2); + if (!r) { mask.SetInvalid(idx); return string_t(); } + return TempToBlob(result, r); + }); +} + +#define TH3_TEMP_TEXT_TEMP(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + BinaryExecutor::ExecuteWithNulls( \ + args.data[0], args.data[1], result, args.size(), \ + [&](string_t blob, string_t unit, ValidityMask &mask, idx_t idx) -> string_t { \ + Temporal *t = BlobToTemp(blob); \ + std::string u(unit.GetData(), unit.GetSize()); \ + Temporal *r = FN(t, u.c_str()); \ + free(t); \ + if (!r) { mask.SetInvalid(idx); return string_t(); } \ + return TempToBlob(result, r); \ + }); \ +} + +TH3_TEMP_TEXT_TEMP(Th3index_cell_area, th3index_cell_area) +TH3_TEMP_TEXT_TEMP(Th3index_edge_length, th3index_edge_length) + +#undef TH3_TEMP_TEXT_TEMP + +/* ===================================================================== + * Ever / always boolean predicates — int returning, with H3Index ↔ Temporal + * ===================================================================== */ + +#define TH3_EA_H3_T(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + BinaryExecutor::ExecuteWithNulls( \ + args.data[0], args.data[1], result, args.size(), \ + [&](int64_t cell, string_t blob, ValidityMask &mask, idx_t idx) -> bool { \ + Temporal *t = BlobToTemp(blob); \ + int r = FN(static_cast(cell), t); \ + free(t); \ + if (r < 0) { mask.SetInvalid(idx); return false; } \ + return IntToBool(r); \ + }); \ +} + +TH3_EA_H3_T(Ever_eq_h3index_th3index, ever_eq_h3index_th3index) +TH3_EA_H3_T(Ever_ne_h3index_th3index, ever_ne_h3index_th3index) +TH3_EA_H3_T(Always_eq_h3index_th3index, always_eq_h3index_th3index) +TH3_EA_H3_T(Always_ne_h3index_th3index, always_ne_h3index_th3index) + +#undef TH3_EA_H3_T + +#define TH3_EA_T_H3(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + BinaryExecutor::ExecuteWithNulls( \ + args.data[0], args.data[1], result, args.size(), \ + [&](string_t blob, int64_t cell, ValidityMask &mask, idx_t idx) -> bool { \ + Temporal *t = BlobToTemp(blob); \ + int r = FN(t, static_cast(cell)); \ + free(t); \ + if (r < 0) { mask.SetInvalid(idx); return false; } \ + return IntToBool(r); \ + }); \ +} + +TH3_EA_T_H3(Ever_eq_th3index_h3index, ever_eq_th3index_h3index) +TH3_EA_T_H3(Ever_ne_th3index_h3index, ever_ne_th3index_h3index) +TH3_EA_T_H3(Always_eq_th3index_h3index, always_eq_th3index_h3index) +TH3_EA_T_H3(Always_ne_th3index_h3index, always_ne_th3index_h3index) + +#undef TH3_EA_T_H3 + +#define TH3_EA_T_T(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + BinaryExecutor::ExecuteWithNulls( \ + args.data[0], args.data[1], result, args.size(), \ + [&](string_t a, string_t b, ValidityMask &mask, idx_t idx) -> bool { \ + Temporal *t1 = BlobToTemp(a); \ + Temporal *t2 = BlobToTemp(b); \ + int r = FN(t1, t2); \ + free(t1); free(t2); \ + if (r < 0) { mask.SetInvalid(idx); return false; } \ + return IntToBool(r); \ + }); \ +} + +TH3_EA_T_T(Ever_eq_th3index_th3index, ever_eq_th3index_th3index) +TH3_EA_T_T(Ever_ne_th3index_th3index, ever_ne_th3index_th3index) +TH3_EA_T_T(Always_eq_th3index_th3index, always_eq_th3index_th3index) +TH3_EA_T_T(Always_ne_th3index_th3index, always_ne_th3index_th3index) + +#undef TH3_EA_T_T + +/* ===================================================================== + * Temporal equality / inequality — `Temporal *fn(...)` returning tbool + * ===================================================================== */ + +#define TH3_T_H3_T_TEMP(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + BinaryExecutor::ExecuteWithNulls( \ + args.data[0], args.data[1], result, args.size(), \ + [&](int64_t cell, string_t blob, ValidityMask &mask, idx_t idx) -> string_t { \ + Temporal *t = BlobToTemp(blob); \ + Temporal *r = FN(static_cast(cell), t); \ + free(t); \ + if (!r) { mask.SetInvalid(idx); return string_t(); } \ + return TempToBlob(result, r); \ + }); \ +} + +TH3_T_H3_T_TEMP(Teq_h3index_th3index, teq_h3index_th3index) +TH3_T_H3_T_TEMP(Tne_h3index_th3index, tne_h3index_th3index) + +#undef TH3_T_H3_T_TEMP + +#define TH3_T_T_H3_TEMP(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + BinaryExecutor::ExecuteWithNulls( \ + args.data[0], args.data[1], result, args.size(), \ + [&](string_t blob, int64_t cell, ValidityMask &mask, idx_t idx) -> string_t { \ + Temporal *t = BlobToTemp(blob); \ + Temporal *r = FN(t, static_cast(cell)); \ + free(t); \ + if (!r) { mask.SetInvalid(idx); return string_t(); } \ + return TempToBlob(result, r); \ + }); \ +} + +TH3_T_T_H3_TEMP(Teq_th3index_h3index, teq_th3index_h3index) +TH3_T_T_H3_TEMP(Tne_th3index_h3index, tne_th3index_h3index) + +#undef TH3_T_T_H3_TEMP + +#define TH3_T_T_T_TEMP(NAME, FN) \ +void H3IndexFunctions::NAME(DataChunk &args, ExpressionState &state, Vector &result) { \ + BinaryExecutor::ExecuteWithNulls( \ + args.data[0], args.data[1], result, args.size(), \ + [&](string_t a, string_t b, ValidityMask &mask, idx_t idx) -> string_t { \ + Temporal *t1 = BlobToTemp(a); \ + Temporal *t2 = BlobToTemp(b); \ + Temporal *r = FN(t1, t2); \ + free(t1); free(t2); \ + if (!r) { mask.SetInvalid(idx); return string_t(); } \ + return TempToBlob(result, r); \ + }); \ +} + +TH3_T_T_T_TEMP(Teq_th3index_th3index, teq_th3index_th3index) +TH3_T_T_T_TEMP(Tne_th3index_th3index, tne_th3index_th3index) + +#undef TH3_T_T_T_TEMP + +/* ===================================================================== + * Registration + * ===================================================================== */ + +void H3IndexTypes::RegisterScalarFunctions(ExtensionLoader &loader) { + const auto H3 = H3INDEX(); + const auto TH3 = TH3INDEX(); + const auto V = LogicalType::VARCHAR; + const auto B = LogicalType::BOOLEAN; + const auto I32 = LogicalType::INTEGER; + const auto I64 = LogicalType::BIGINT; + const auto TS = LogicalType::TIMESTAMP_TZ; + + /* --- I/O scalar text helpers --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "h3IndexFromText", {V}, H3, H3IndexFunctions::H3index_from_text)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "h3IndexAsText", {H3}, V, H3IndexFunctions::H3index_as_text)); + + /* --- Constructor --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3index", {H3, TS}, TH3, H3IndexFunctions::Th3index_make)); + + /* --- Accessors --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "startValue", {TH3}, H3, H3IndexFunctions::Th3index_start_value)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "endValue", {TH3}, H3, H3IndexFunctions::Th3index_end_value)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "valueN", {TH3, I32}, H3, H3IndexFunctions::Th3index_value_n)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "values", {TH3}, LogicalType::LIST(H3), H3IndexFunctions::Th3index_values)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "valueAtTimestamp", {TH3, TS}, H3, H3IndexFunctions::Th3index_value_at_timestamptz)); + + /* --- Casts to/from other temporal types --- + * + * `th3index(tbigint)` / `tbigint(th3index)` round-trip the + * 64-bit cell id through a generic temporal-bigint carrier. + * MobilityDuck does not currently expose a `tbigint` type + * (deferred until the larger temporal-pgtypes work lands), so + * these two overloads stay unregistered. Re-enable once + * `TemporalTypes::TBIGINT()` is published. + */ + /* Note: tgeompoint/tgeogpoint variants take a resolution arg. */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3index", {TgeogpointType::TGEOGPOINT(), I32}, TH3, H3IndexFunctions::Tgeogpoint_to_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3index", {TgeompointType::TGEOMPOINT(), I32}, TH3, H3IndexFunctions::Tgeompoint_to_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tgeogpoint", {TH3}, TgeogpointType::TGEOGPOINT(), H3IndexFunctions::Th3index_to_tgeogpoint)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tgeompoint", {TH3}, TgeompointType::TGEOMPOINT(), H3IndexFunctions::Th3index_to_tgeompoint)); + + /* --- Ever / always predicates --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "everEq", {H3, TH3}, B, H3IndexFunctions::Ever_eq_h3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "everEq", {TH3, H3}, B, H3IndexFunctions::Ever_eq_th3index_h3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "everEq", {TH3, TH3}, B, H3IndexFunctions::Ever_eq_th3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "everNe", {H3, TH3}, B, H3IndexFunctions::Ever_ne_h3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "everNe", {TH3, H3}, B, H3IndexFunctions::Ever_ne_th3index_h3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "everNe", {TH3, TH3}, B, H3IndexFunctions::Ever_ne_th3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "alwaysEq", {H3, TH3}, B, H3IndexFunctions::Always_eq_h3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "alwaysEq", {TH3, H3}, B, H3IndexFunctions::Always_eq_th3index_h3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "alwaysEq", {TH3, TH3}, B, H3IndexFunctions::Always_eq_th3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "alwaysNe", {H3, TH3}, B, H3IndexFunctions::Always_ne_h3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "alwaysNe", {TH3, H3}, B, H3IndexFunctions::Always_ne_th3index_h3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "alwaysNe", {TH3, TH3}, B, H3IndexFunctions::Always_ne_th3index_th3index)); + + /* --- Temporal equality / inequality (returns tbool) --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tEq", {H3, TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Teq_h3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tEq", {TH3, H3}, TemporalTypes::TBOOL(), H3IndexFunctions::Teq_th3index_h3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tEq", {TH3, TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Teq_th3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tNe", {H3, TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Tne_h3index_th3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tNe", {TH3, H3}, TemporalTypes::TBOOL(), H3IndexFunctions::Tne_th3index_h3index)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tNe", {TH3, TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Tne_th3index_th3index)); + + /* --- H3 cell properties --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexGetResolution", {TH3}, TemporalTypes::TINT(), H3IndexFunctions::Th3index_get_resolution)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexGetBaseCellNumber", {TH3}, TemporalTypes::TINT(), H3IndexFunctions::Th3index_get_base_cell_number)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexIsValidCell", {TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Th3index_is_valid_cell)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexIsResClassIII", {TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Th3index_is_res_class_iii)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexIsPentagon", {TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Th3index_is_pentagon)); + + /* --- Hierarchy --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellToParent", {TH3, I32}, TH3, H3IndexFunctions::Th3index_cell_to_parent)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellToParentNext", {TH3}, TH3, H3IndexFunctions::Th3index_cell_to_parent_next)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellToCenterChild", {TH3, I32}, TH3, H3IndexFunctions::Th3index_cell_to_center_child)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellToCenterChildNext", {TH3}, TH3, H3IndexFunctions::Th3index_cell_to_center_child_next)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellToChildPos", {TH3, I32}, TH3, H3IndexFunctions::Th3index_cell_to_child_pos)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexChildPosToCell", {TH3, TH3, I32}, TH3, H3IndexFunctions::Th3index_child_pos_to_cell)); + + /* --- Geometry / boundary --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellToBoundary", {TH3}, TgeompointType::TGEOMPOINT(), H3IndexFunctions::Th3index_cell_to_boundary)); + + /* --- Directed edges --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexAreNeighborCells", {TH3, TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Th3index_are_neighbor_cells)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellsToDirectedEdge", {TH3, TH3}, TH3, H3IndexFunctions::Th3index_cells_to_directed_edge)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexIsValidDirectedEdge", {TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Th3index_is_valid_directed_edge)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexGetDirectedEdgeOrigin", {TH3}, TH3, H3IndexFunctions::Th3index_get_directed_edge_origin)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexGetDirectedEdgeDestination", {TH3}, TH3, H3IndexFunctions::Th3index_get_directed_edge_destination)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexDirectedEdgeToBoundary", {TH3}, TgeompointType::TGEOMPOINT(), H3IndexFunctions::Th3index_directed_edge_to_boundary)); + + /* --- Vertices --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellToVertex", {TH3, I32}, TH3, H3IndexFunctions::Th3index_cell_to_vertex)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexVertexToLatlng", {TH3}, TgeompointType::TGEOMPOINT(), H3IndexFunctions::Th3index_vertex_to_latlng)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexIsValidVertex", {TH3}, TemporalTypes::TBOOL(), H3IndexFunctions::Th3index_is_valid_vertex)); + + /* --- Grid traversal --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexGridDistance", {TH3, TH3}, TemporalTypes::TINT(), H3IndexFunctions::Th3index_grid_distance)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellToLocalIj", {TH3, TH3}, TH3, H3IndexFunctions::Th3index_cell_to_local_ij)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexLocalIjToCell", {TH3, TH3}, TH3, H3IndexFunctions::Th3index_local_ij_to_cell)); + + /* --- Cell area / edge length / great-circle distance --- */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexCellArea", {TH3, V}, TemporalTypes::TFLOAT(), H3IndexFunctions::Th3index_cell_area)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "th3indexEdgeLength", {TH3, V}, TemporalTypes::TFLOAT(), H3IndexFunctions::Th3index_edge_length)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction( + "tgeogpointGreatCircleDistance", {TgeogpointType::TGEOGPOINT(), TgeogpointType::TGEOGPOINT(), V}, + TemporalTypes::TFLOAT(), H3IndexFunctions::Tgeogpoint_great_circle_distance)); +} + +} // namespace duckdb diff --git a/src/include/h3/th3index.hpp b/src/include/h3/th3index.hpp new file mode 100644 index 00000000..8c2ab960 --- /dev/null +++ b/src/include/h3/th3index.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include "meos_wrapper_simple.hpp" +#include "duckdb/common/exception.hpp" +#include "duckdb/common/string_util.hpp" +#include "duckdb/function/scalar_function.hpp" +#include "duckdb/main/extension/extension_loader.hpp" +#include + +namespace duckdb { + +/* H3INDEX is a 64-bit unsigned cell id; surfaced as BIGINT (signed + * reinterpretation is safe because the comparison and equality + * operators care only about the bit pattern). TH3INDEX is the + * temporal cell index, stored as a Temporal* blob (BLOB). */ +struct H3IndexTypes { + static LogicalType H3INDEX(); + static LogicalType TH3INDEX(); + + static void RegisterTypes(ExtensionLoader &loader); + static void RegisterCastFunctions(ExtensionLoader &loader); + static void RegisterScalarFunctions(ExtensionLoader &loader); +}; + +struct H3IndexFunctions { + /* In/out — H3 cell scalar */ + static bool H3index_in_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + static bool H3index_out_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + static void H3index_from_text(DataChunk &args, ExpressionState &state, Vector &result); + static void H3index_as_text(DataChunk &args, ExpressionState &state, Vector &result); + + /* In/out — TH3INDEX temporal value */ + static bool Th3index_in_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + static bool Th3index_out_cast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters); + + /* Constructor */ + static void Th3index_make(DataChunk &args, ExpressionState &state, Vector &result); + + /* Accessors */ + static void Th3index_start_value(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_end_value(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_value_n(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_values(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_value_at_timestamptz(DataChunk &args, ExpressionState &state, Vector &result); + + /* Casts to/from other temporal types */ + static void Tbigint_to_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_to_tbigint(DataChunk &args, ExpressionState &state, Vector &result); + static void Tgeogpoint_to_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Tgeompoint_to_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_to_tgeogpoint(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_to_tgeompoint(DataChunk &args, ExpressionState &state, Vector &result); + + /* Ever / always boolean predicates */ + static void Ever_eq_h3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Ever_eq_th3index_h3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Ever_eq_th3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Ever_ne_h3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Ever_ne_th3index_h3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Ever_ne_th3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Always_eq_h3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Always_eq_th3index_h3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Always_eq_th3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Always_ne_h3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Always_ne_th3index_h3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Always_ne_th3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + + /* Temporal equality / inequality (returns tbool) */ + static void Teq_h3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Teq_th3index_h3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Teq_th3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Tne_h3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Tne_th3index_h3index(DataChunk &args, ExpressionState &state, Vector &result); + static void Tne_th3index_th3index(DataChunk &args, ExpressionState &state, Vector &result); + + /* H3 cell properties — all `Temporal *fn(const Temporal *)` */ + static void Th3index_get_resolution(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_get_base_cell_number(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_is_valid_cell(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_is_res_class_iii(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_is_pentagon(DataChunk &args, ExpressionState &state, Vector &result); + + /* Hierarchy */ + static void Th3index_cell_to_parent(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_cell_to_parent_next(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_cell_to_center_child(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_cell_to_center_child_next(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_cell_to_child_pos(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_child_pos_to_cell(DataChunk &args, ExpressionState &state, Vector &result); + + /* Geometry / boundary */ + static void Th3index_cell_to_boundary(DataChunk &args, ExpressionState &state, Vector &result); + + /* Directed edges */ + static void Th3index_are_neighbor_cells(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_cells_to_directed_edge(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_is_valid_directed_edge(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_get_directed_edge_origin(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_get_directed_edge_destination(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_directed_edge_to_boundary(DataChunk &args, ExpressionState &state, Vector &result); + + /* Vertices */ + static void Th3index_cell_to_vertex(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_vertex_to_latlng(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_is_valid_vertex(DataChunk &args, ExpressionState &state, Vector &result); + + /* Grid traversal */ + static void Th3index_grid_distance(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_cell_to_local_ij(DataChunk &args, ExpressionState &state, Vector &result); + static void Th3index_local_ij_to_cell(DataChunk &args, ExpressionState &state, Vector &result); + + /* Cell area / edge length / great-circle distance */ + 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); +}; + +} // namespace duckdb diff --git a/src/mobilityduck_extension.cpp b/src/mobilityduck_extension.cpp index f6d3422f..c268abc1 100644 --- a/src/mobilityduck_extension.cpp +++ b/src/mobilityduck_extension.cpp @@ -17,6 +17,7 @@ #include "geo/tgeography_ops.hpp" #include "geo/tgeogpoint.hpp" #include "geo/tgeogpoint_ops.hpp" +#include "h3/th3index.hpp" #include "temporal/span.hpp" #include "temporal/span_aggregates.hpp" #include "temporal/temporal_aggregates.hpp" @@ -341,6 +342,10 @@ static void LoadInternal(ExtensionLoader &loader) { SpatialSetType::RegisterCastFunctions(loader); SpatialSetType::RegisterScalarFunctions(loader); + H3IndexTypes::RegisterTypes(loader); + H3IndexTypes::RegisterCastFunctions(loader); + H3IndexTypes::RegisterScalarFunctions(loader); + SpansetTypes::RegisterTypes(loader); SpansetTypes::RegisterCastFunctions(loader); SpansetTypes::RegisterScalarFunctions(loader); diff --git a/test/sql/th3index.test b/test/sql/th3index.test new file mode 100644 index 00000000..acd93b85 --- /dev/null +++ b/test/sql/th3index.test @@ -0,0 +1,16 @@ +# name: test/sql/th3index.test +# description: th3index H3 cell-index I/O smoke coverage +# group: [mobilityduck] + +require mobilityduck + +# H3INDEX scalar text round-trip (resolution 5 and resolution 0 cells) +query I +SELECT h3IndexAsText(h3IndexFromText('85283473fffffff')); +---- +85283473fffffff + +query I +SELECT h3IndexAsText(h3IndexFromText('8001fffffffffff')); +---- +8001fffffffffff diff --git a/vcpkg.json b/vcpkg.json index ee22c1e5..8f777f0a 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,6 +2,7 @@ "dependencies": [ "geos", "gsl", + "h3", { "name": "meos", "version>=": "0" }, "vcpkg-cmake", diff --git a/vcpkg_ports/h3/portfile.cmake b/vcpkg_ports/h3/portfile.cmake new file mode 100644 index 00000000..6cfd4515 --- /dev/null +++ b/vcpkg_ports/h3/portfile.cmake @@ -0,0 +1,37 @@ +vcpkg_check_linkage(ONLY_STATIC_LIBRARY) + +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO uber/h3 + REF v${VERSION} + SHA512 6ed93c5e69adbba9804282b5814f1617d4c930b677df4735e4d4cf10fcba813f61b6be3a125d191d375e52e3e22af7c244efb007f27ca487b34eae9e24fb6c7b + HEAD_REF master +) + +vcpkg_cmake_configure( + SOURCE_PATH ${SOURCE_PATH} + OPTIONS + -DBUILD_BENCHMARKS=OFF + -DBUILD_FUZZERS=OFF + -DBUILD_FILTERS=OFF + -DBUILD_GENERATORS=OFF + -DBUILD_TESTING=OFF + # h3 runs a clang-tidy pass on its own sources whenever clang-tidy is on + # PATH (ENABLE_LINTING defaults ON) and compiles it under + # WARNINGS_AS_ERRORS. The wasm32-emscripten toolchain ships clang-tidy, + # so h3 4.3.0's own source fails the lint (readability-braces-around- + # statements, bugprone-narrowing-conversions, and a stale .clang-tidy key + # AnalyzeTemporaryDtors that newer clang-tidy rejects), aborting the + # dependency build. Linting the upstream dependency is not our concern. + -DENABLE_LINTING=OFF +) + +vcpkg_cmake_install() + +vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/${PORT}) + +vcpkg_copy_pdbs() + +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/bin" "${CURRENT_PACKAGES_DIR}/debug/bin" "${CURRENT_PACKAGES_DIR}/debug/include") + +file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/vcpkg_ports/h3/vcpkg.json b/vcpkg_ports/h3/vcpkg.json new file mode 100644 index 00000000..20902c31 --- /dev/null +++ b/vcpkg_ports/h3/vcpkg.json @@ -0,0 +1,18 @@ +{ + "name": "h3", + "version-semver": "4.3.0", + "description": "A Hexagonal Hierarchical Geospatial Indexing System", + "homepage": "https://github.com/uber/h3", + "license": "Apache-2.0", + "supports": "!uwp", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ] +} diff --git a/vcpkg_ports/meos/portfile.cmake b/vcpkg_ports/meos/portfile.cmake index f4de1fab..fd0da052 100644 --- a/vcpkg_ports/meos/portfile.cmake +++ b/vcpkg_ports/meos/portfile.cmake @@ -17,17 +17,147 @@ endif() ]=] ) +# Upstream gap at commit beddae670: `meos/include/h3/th3index_internal.h` +# does `#include ` unconditionally. `fmgr.h` is a PG-internal +# header and is not bundled in MEOS's `postgres/` subtree, so the +# standalone MEOS build of `meos/src/h3/h3index.c` fails with +# `fatal error: fmgr.h: No such file or directory`. Guard the +# include with `#if !MEOS`, mirroring the same idiom already used by +# `meos/include/temporal/temporal.h`. +vcpkg_replace_string( + "${SOURCE_PATH}/meos/include/h3/th3index_internal.h" + [=[ +#include +#include +]=] + [=[ +#include +#if ! MEOS +#include +#endif +]=] +) + +# Upstream gap at commit beddae670: `meos/CMakeLists.txt` builds the +# `h3` OBJECT library (via `add_subdirectory(h3)` + `add_library`) +# but the `PROJECT_OBJECTS` list that feeds the final +# `add_library(meos ${PROJECT_OBJECTS})` lists every other optional +# family (cbuffer / npoint / pose / rgeo) and silently omits `h3`. +# Without this injection libmeos ships without H3 symbols, so any +# consumer linking against `meos` sees ~120 `undefined reference to +# 'th3index_*'` link errors. +vcpkg_replace_string( + "${SOURCE_PATH}/meos/CMakeLists.txt" + [=[if(RGEO) + message(STATUS "Including rigid geometries") + set(PROJECT_OBJECTS ${PROJECT_OBJECTS} "$") +endif()]=] + [=[if(RGEO) + message(STATUS "Including rigid geometries") + set(PROJECT_OBJECTS ${PROJECT_OBJECTS} "$") +endif() +if(H3) + message(STATUS "Including temporal H3 index (th3index)") + set(PROJECT_OBJECTS ${PROJECT_OBJECTS} "$") +endif()]=] +) + +# Upstream gap at commit beddae670: `meos/CMakeLists.txt` carries +# `install()` rules for `meos_npoint.h` / `meos_pose.h` / +# `meos_rgeo.h` / `meos_cbuffer.h` but no rule for `meos_h3.h`. +# Without it the H3 public header is missing from the installed +# `include/` directory, so any consumer of `#include ` +# fails to compile. +vcpkg_replace_string( + "${SOURCE_PATH}/meos/CMakeLists.txt" + [=[if(RGEO) + install( + FILES "${CMAKE_SOURCE_DIR}/meos/include/meos_rgeo.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +endif()]=] + [=[if(RGEO) + install( + FILES "${CMAKE_SOURCE_DIR}/meos/include/meos_rgeo.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +endif() +if(H3) + install( + FILES "${CMAKE_SOURCE_DIR}/meos/include/meos_h3.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +endif()]=] +) + +# Upstream gap at commit beddae670: the h3-side source files call +# `ensure_srid_is_latlong()` (declared in +# `meos/include/geo/tgeo_spatialfuncs.h`) without including that +# header, yielding implicit-declaration errors under `MEOS=1`. +foreach(_h3_src + meos/src/h3/h3_geo.c + meos/src/h3/th3index_latlng.c + meos/src/h3/th3index_metrics.c) + if(EXISTS "${SOURCE_PATH}/${_h3_src}") + vcpkg_replace_string( + "${SOURCE_PATH}/${_h3_src}" + "#include " + [=[ +#include + +#include "geo/tgeo_spatialfuncs.h" +]=] + ) + endif() +endforeach() + +# vcpkg installs h3 at the per-triplet +# `installed//{lib,include/h3}` layout, but MEOS's own +# `find_library(NAMES h3)` / `find_path(NAMES h3api.h PATH_SUFFIXES h3)` +# does not consult vcpkg's CMAKE_PREFIX_PATH on every triplet +# (notably `arm64-linux-release`). Pass the resolved paths explicitly. +set(_meos_h3_lib_candidates + "${CURRENT_INSTALLED_DIR}/lib/libh3.a" + "${CURRENT_INSTALLED_DIR}/lib/libh3.so" + "${CURRENT_INSTALLED_DIR}/lib/libh3${CMAKE_STATIC_LIBRARY_SUFFIX}" + "${CURRENT_INSTALLED_DIR}/lib/libh3${CMAKE_SHARED_LIBRARY_SUFFIX}") +set(_MEOS_H3_LIB "") +foreach(_cand IN LISTS _meos_h3_lib_candidates) + if(EXISTS "${_cand}") + set(_MEOS_H3_LIB "${_cand}") + break() + endif() +endforeach() +if(NOT _MEOS_H3_LIB) + message(FATAL_ERROR "MEOS port: cannot locate vcpkg-installed libh3 under ${CURRENT_INSTALLED_DIR}/lib") +endif() +# h3's header lands at `include/h3/h3api.h` (subdirectory). MEOS +# source uses `#include ` so the include path must point +# at `include/h3`. +set(_MEOS_H3_INC_CANDIDATES + "${CURRENT_INSTALLED_DIR}/include/h3" + "${CURRENT_INSTALLED_DIR}/include") +set(_MEOS_H3_INC "") +foreach(_cand IN LISTS _MEOS_H3_INC_CANDIDATES) + if(EXISTS "${_cand}/h3api.h") + set(_MEOS_H3_INC "${_cand}") + break() + endif() +endforeach() +if(NOT _MEOS_H3_INC) + message(FATAL_ERROR "MEOS port: cannot locate vcpkg-installed h3api.h under ${CURRENT_INSTALLED_DIR}/include or ${CURRENT_INSTALLED_DIR}/include/h3") +endif() + vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS -DMEOS=ON + -DH3=ON + "-DH3_LIBRARY=${_MEOS_H3_LIB}" + "-DH3_INCLUDE_DIR=${_MEOS_H3_INC}" -DBUILD_SHARED_LIBS=ON # Build only the MEOS library, not the MEOS C test binaries: those link # the GEOS C++ API, which the arm64-linux vcpkg triplet does not carry. -DBUILD_TESTING=OFF -DCMAKE_C_FLAGS="-Dsession_timezone=meos_session_timezone" -DCMAKE_CXX_FLAGS="-Dsession_timezone=meos_session_timezone" - ) vcpkg_cmake_build(TARGET all) diff --git a/vcpkg_ports/meos/vcpkg.json b/vcpkg_ports/meos/vcpkg.json index 22bd9c38..44c4fc6a 100644 --- a/vcpkg_ports/meos/vcpkg.json +++ b/vcpkg_ports/meos/vcpkg.json @@ -10,6 +10,7 @@ "geos", "proj", "json-c", - "gsl" + "gsl", + "h3" ] } \ No newline at end of file