[SCM] PostGIS branch master updated. 3.6.0rc2-277-g48a31d424
git at osgeo.org
git at osgeo.org
Mon Jan 12 13:28:37 PST 2026
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".
The branch, master has been updated
via 48a31d424a91b9fda7c125a5c5594689e87d562c (commit)
from 3c50b132d145ab4c8a5a32d8f8bc7f1676e2ad6b (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 48a31d424a91b9fda7c125a5c5594689e87d562c
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date: Mon Jan 12 13:26:14 2026 -0800
Back ST_DWithin with GEOSPreparedDistance for cases where
it might be useful. When at least one object is > 1kb.
Standard caching criteria (see object more than once).
Use brute force implementation where caching is not
indicated.
Includes a commented out ST_DwithinUncached which is backed by the
old brute force implementation, for testing purposes.
Closes #2614
diff --git a/NEWS b/NEWS
index f4c61cbc6..9e96fdbe3 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,7 @@ xxxx/xx/xx
- #4222, [raster] ST_DumpAsPolygons honours PostgreSQL interrupts (Darafei Praliaskouski)
- #5109, Document the meaning of topology.next_left_edge and topology.next_right_edge links (Darafei Praliaskouski)
- #5889, [topology] Include representative locations in topology build errors (Darafei Praliaskouski)
+ - #2614, Use GEOSPreparedDistance and caching to accelerate ST_DWithin (Paul Ramsey)
* Bug Fixes *
diff --git a/postgis/lwgeom_functions_basic.c b/postgis/lwgeom_functions_basic.c
index b3e1179b3..ed5f076f2 100644
--- a/postgis/lwgeom_functions_basic.c
+++ b/postgis/lwgeom_functions_basic.c
@@ -66,7 +66,7 @@ Datum ST_Distance(PG_FUNCTION_ARGS);
Datum LWGEOM_closestpoint(PG_FUNCTION_ARGS);
Datum LWGEOM_shortestline2d(PG_FUNCTION_ARGS);
Datum LWGEOM_longestline2d(PG_FUNCTION_ARGS);
-Datum LWGEOM_dwithin(PG_FUNCTION_ARGS);
+Datum ST_DWithinUncached(PG_FUNCTION_ARGS);
Datum LWGEOM_maxdistance3d(PG_FUNCTION_ARGS);
Datum LWGEOM_mindistance3d(PG_FUNCTION_ARGS);
@@ -727,8 +727,8 @@ Returns boolean describing if
mininimum 2d distance between objects in
geom1 and geom2 is shorter than tolerance
*/
-PG_FUNCTION_INFO_V1(LWGEOM_dwithin);
-Datum LWGEOM_dwithin(PG_FUNCTION_ARGS)
+PG_FUNCTION_INFO_V1(ST_DWithinUncached);
+Datum ST_DWithinUncached(PG_FUNCTION_ARGS)
{
double mindist;
GSERIALIZED *geom1 = PG_GETARG_GSERIALIZED_P(0);
diff --git a/postgis/lwgeom_geos_predicates.c b/postgis/lwgeom_geos_predicates.c
index 092795921..fb8989f05 100644
--- a/postgis/lwgeom_geos_predicates.c
+++ b/postgis/lwgeom_geos_predicates.c
@@ -46,6 +46,7 @@
/* Prototypes for SQL-bound functions */
+Datum LWGEOM_dwithin(PG_FUNCTION_ARGS);
Datum relate_full(PG_FUNCTION_ARGS);
Datum relate_pattern(PG_FUNCTION_ARGS);
Datum disjoint(PG_FUNCTION_ARGS);
@@ -1018,3 +1019,100 @@ Datum relate_full(PG_FUNCTION_ARGS)
}
+/*
+ * ST_DWithin(geom1, geom2, radius) returns boolean
+ *
+ * For GEOS >= 3.10, this is a cached predicate implementation much
+ * like the other functions in this module. As with the other prepared
+ * predicates, the utility of the caching/preparation is a function
+ * of the size of the inputs, in particular the cached input, and
+ * that caching only happens if the input shows up multiple times in
+ * a row, which mostly is a result of either a literal or a
+ * nested loop join. The size of the inputs is used as an initial
+ * pre-check on whether to engage the geos/prepared geometry
+ * machinery.
+ */
+PG_FUNCTION_INFO_V1(LWGEOM_dwithin);
+Datum LWGEOM_dwithin(PG_FUNCTION_ARGS)
+{
+
+ double tolerance = PG_GETARG_FLOAT8(2);
+ double mindist;
+
+#if POSTGIS_GEOS_VERSION < 31000
+ const GSERIALIZED *geom1 = PG_GETARG_GSERIALIZED_P(0);
+ const GSERIALIZED *geom2 = PG_GETARG_GSERIALIZED_P(1);
+ LWGEOM *lwgeom1 = lwgeom_from_gserialized(geom1);
+ LWGEOM *lwgeom2 = lwgeom_from_gserialized(geom2);
+ mindist = lwgeom_mindistance2d_tolerance(lwgeom1, lwgeom2, tolerance);
+ PG_RETURN_BOOL(tolerance >= mindist);
+
+#else
+ SHARED_GSERIALIZED *shared_geom1 = ToastCacheGetGeometry(fcinfo, 0);
+ SHARED_GSERIALIZED *shared_geom2 = ToastCacheGetGeometry(fcinfo, 1);
+ const GSERIALIZED *geom1 = shared_gserialized_get(shared_geom1);
+ const GSERIALIZED *geom2 = shared_gserialized_get(shared_geom2);
+ PrepGeomCache *prep_cache = NULL;
+ const size_t small_threshold = 1024;
+ char is_dwithin = -1;
+
+ /*
+ * Only enter the GEOS/PreparedGeometry code line if one of the
+ * operands is large enough that the win from indexing will exceed
+ * the loss from GEOS fixed overhead.
+ */
+ bool use_prepared =
+ (LWSIZE_GET(geom1->size) > small_threshold) ||
+ (LWSIZE_GET(geom2->size) > small_threshold);
+
+ if (tolerance < 0)
+ elog(ERROR, "Tolerance cannot be less than zero");
+
+ /*
+ * Error out early for mismatched SRID or empty inputs.
+ */
+ gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+ if (gserialized_is_empty(geom1) || gserialized_is_empty(geom2))
+ PG_RETURN_BOOL(false);
+
+ /*
+ * Only enter GEOS/PreparedGeometry code line if objects
+ * are big enough and cache is populated.
+ */
+ if (use_prepared)
+ {
+ initGEOS(lwpgnotice, lwgeom_geos_error);
+ prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, shared_geom2);
+ if (prep_cache && prep_cache->prepared_geom)
+ {
+ GEOSGeometry *g = NULL;
+ if (prep_cache->gcache.argnum == 1)
+ g = POSTGIS2GEOS(geom2);
+ else
+ g = POSTGIS2GEOS(geom1);
+
+ if (!g) HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
+ is_dwithin = GEOSPreparedDistanceWithin(prep_cache->prepared_geom, g, tolerance);
+ if (is_dwithin == 2) HANDLE_GEOS_ERROR("GEOSPreparedDistanceWithin");
+ GEOSGeom_destroy(g);
+ }
+ }
+
+ /*
+ * If for any reason we did not use GEOS/PreparedGeometry, we still
+ * need an answer, so use the brute force implementation.
+ */
+ if (is_dwithin < 0)
+ {
+ LWGEOM *lwgeom1 = lwgeom_from_gserialized(geom1);
+ LWGEOM *lwgeom2 = lwgeom_from_gserialized(geom2);
+ mindist = lwgeom_mindistance2d_tolerance(lwgeom1, lwgeom2, tolerance);
+ is_dwithin = (tolerance >= mindist);
+ }
+
+ PG_RETURN_BOOL(is_dwithin);
+
+#endif
+}
+
+
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index 3e5916205..4d13ced28 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -4674,6 +4674,14 @@ CREATE OR REPLACE FUNCTION ST_DWithin(geom1 geometry, geom2 geometry, float8)
LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
_COST_HIGH;
+-- Availability: 3.7.0
+-- Purpose: Uncomment for testing against cached version of ST_DWithin
+-- CREATE OR REPLACE FUNCTION ST_DWithinUncached(geom1 geometry, geom2 geometry, float8)
+-- RETURNS boolean
+-- AS 'MODULE_PATHNAME', 'ST_DWithinUncached'
+-- LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
+-- _COST_HIGH;
+
-- Availability: 1.2.2
CREATE OR REPLACE FUNCTION ST_Touches(geom1 geometry, geom2 geometry)
RETURNS boolean
-----------------------------------------------------------------------
Summary of changes:
NEWS | 1 +
postgis/lwgeom_functions_basic.c | 6 +--
postgis/lwgeom_geos_predicates.c | 98 ++++++++++++++++++++++++++++++++++++++++
postgis/postgis.sql.in | 8 ++++
4 files changed, 110 insertions(+), 3 deletions(-)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list