diff --git a/src/geo/tgeompoint.cpp b/src/geo/tgeompoint.cpp index 64304299..e6e678ef 100644 --- a/src/geo/tgeompoint.cpp +++ b/src/geo/tgeompoint.cpp @@ -1716,6 +1716,13 @@ void TgeompointType::RegisterScalarFunctions(ExtensionLoader &loader) { duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("nad", {G, TG}, D, TgeompointFunctions::Nad_geo_tgeo)); duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("nad", {TG, TG}, D, TgeompointFunctions::Nad_tgeo_tgeo)); + /* minDistance(tgeo, geometry) — spatial minimum to a static geometry, + which reduces to the nearest-approach distance. The (tgeo, tgeo) + cross-pair minimum is the set-set minDistance(tgeompoint[], + tgeompoint[]); the per-pair value is nearestApproachDistance. */ + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("minDistance", {TG, G}, D, TgeompointFunctions::Mindistance_tgeo_geo)); + duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("minDistance", {G, TG}, D, TgeompointFunctions::Mindistance_geo_tgeo)); + /* affine (12-arg and 6-arg) */ duckdb::RegisterSerializedScalarFunction(loader, ScalarFunction("affine", {TG, D, D, D, D, D, D, D, D, D, D, D, D}, TG, diff --git a/src/geo/tgeompoint_functions.cpp b/src/geo/tgeompoint_functions.cpp index d063f23f..7d578ffe 100644 --- a/src/geo/tgeompoint_functions.cpp +++ b/src/geo/tgeompoint_functions.cpp @@ -3607,6 +3607,52 @@ void TgeompointFunctions::Nad_tgeo_tgeo(DataChunk &args, ExpressionState &state, ); } +/* *************************************************** + * minDistance — minimum spatial distance over the union of temporal + * extents (MobilityDB #1007). The (tgeo, geo) overloads reuse the NAD + * kernel since NAD reduces to spatial-min when one argument has no + * time dimension. The (tgeo, tgeo) overload calls the threshold-aware + * kernel with DBL_MAX so every call computes the exact per-pair + * minimum; wrap with the built-in MIN aggregate for the canonical + * GROUP-BY-over-cross-join shape. + ****************************************************/ + +void TgeompointFunctions::Mindistance_tgeo_geo(DataChunk &args, ExpressionState &state, Vector &result) { + BinaryExecutor::ExecuteWithNulls( + args.data[0], args.data[1], result, args.size(), + [](string_t tblob, string_t gblob, ValidityMask &mask, idx_t idx) -> double { + size_t tsz = tblob.GetSize(); + uint8_t *tcopy = (uint8_t *)malloc(tsz); + memcpy(tcopy, tblob.GetData(), tsz); + Temporal *temp = reinterpret_cast(tcopy); + GSERIALIZED *gs = GeometryToGSerialized(gblob, 0); + if (!gs) { free(temp); mask.SetInvalid(idx); return 0.0; } + double d = nad_tgeo_geo(temp, gs); + free(temp); free(gs); + if (d == DBL_MAX) { mask.SetInvalid(idx); return 0.0; } + return d; + } + ); +} + +void TgeompointFunctions::Mindistance_geo_tgeo(DataChunk &args, ExpressionState &state, Vector &result) { + BinaryExecutor::ExecuteWithNulls( + args.data[0], args.data[1], result, args.size(), + [](string_t gblob, string_t tblob, ValidityMask &mask, idx_t idx) -> double { + size_t tsz = tblob.GetSize(); + uint8_t *tcopy = (uint8_t *)malloc(tsz); + memcpy(tcopy, tblob.GetData(), tsz); + Temporal *temp = reinterpret_cast(tcopy); + GSERIALIZED *gs = GeometryToGSerialized(gblob, 0); + if (!gs) { free(temp); mask.SetInvalid(idx); return 0.0; } + double d = nad_tgeo_geo(temp, gs); + free(temp); free(gs); + if (d == DBL_MAX) { mask.SetInvalid(idx); return 0.0; } + return d; + } + ); +} + /* *************************************************** * Affine/translate/rotate/rotateX/Y/Z/transscale/scale transforms * All build an AFFINE struct and delegate to tgeo_affine (or tgeo_scale). diff --git a/src/include/geo/tgeompoint_functions.hpp b/src/include/geo/tgeompoint_functions.hpp index 1f5b1eb8..dc7507ab 100644 --- a/src/include/geo/tgeompoint_functions.hpp +++ b/src/include/geo/tgeompoint_functions.hpp @@ -172,6 +172,8 @@ struct TgeompointFunctions { static void Nad_tgeo_geo(DataChunk &args, ExpressionState &state, Vector &result); static void Nad_geo_tgeo(DataChunk &args, ExpressionState &state, Vector &result); static void Nad_tgeo_tgeo(DataChunk &args, ExpressionState &state, Vector &result); + static void Mindistance_tgeo_geo(DataChunk &args, ExpressionState &state, Vector &result); + static void Mindistance_geo_tgeo(DataChunk &args, ExpressionState &state, Vector &result); /* *************************************************** * Affine / translate / rotate / scale transforms diff --git a/test/sql/parity/064_tpoint_distance.test b/test/sql/parity/064_tpoint_distance.test index 6db51576..2ea311db 100644 --- a/test/sql/parity/064_tpoint_distance.test +++ b/test/sql/parity/064_tpoint_distance.test @@ -54,3 +54,18 @@ query I SELECT round(nad(tgeompoint '[POINT(0 0)@2000-01-01, POINT(2 2)@2000-01-02]', tgeompoint '[POINT(3 3)@2000-01-01, POINT(5 5)@2000-01-02]')::numeric, 6); ---- 4.242641 + +# minDistance(tgeo, geometry) — spatial minimum distance between a temporal +# geometry and a static geometry, which reduces to the nearest-approach +# distance. The (tgeo, tgeo) cross-pair minimum is the set-set +# minDistance(tgeompoint[], tgeompoint[]); the per-pair value is +# nearestApproachDistance. +query I +SELECT minDistance(tgeompoint '[POINT(0 0)@2000-01-01, POINT(2 2)@2000-01-02]', ST_GeomFromText('POINT(1 1)')); +---- +0.0 + +query I +SELECT minDistance(ST_GeomFromText('POINT(1 1)'), tgeompoint '[POINT(0 0)@2000-01-01, POINT(2 2)@2000-01-02]'); +---- +0.0