[SCM] PostGIS branch master updated. 3.5.0alpha2-38-g907df5ff9

git at osgeo.org git at osgeo.org
Fri Aug 23 12:01:19 PDT 2024


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  907df5ff97242a1e16e0d9ef08929531301dadf1 (commit)
      from  6337992ba91eb278a7df176d28ed50948287c137 (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 907df5ff97242a1e16e0d9ef08929531301dadf1
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Fri Aug 23 11:59:23 2024 -0700

    Support for GEOS 3.13 and RelateNG.
    Most functionality remains the same, but new GEOS predicate implementation has a few small changes.
    
    - Boundary Node Rule relate matrices might be different when
      using the "multi-valent end point" rule.
    - Relate matrices for situations with invalid MultiPolygons
      with shared boundaries might be different. Run ST_MakeValid
      to get valid inputs to feed to the calculation.
    - Zero length LineStrings are treated as if they are the
      equivalent Point object.
    
    References #5770

diff --git a/NEWS b/NEWS
index abd4cc928..acb4fbb67 100644
--- a/NEWS
+++ b/NEWS
@@ -2,12 +2,12 @@ PostGIS 3.5.0
 2024/xx/xx
 
 To take advantage of all postgis_sfcgal extension features SFCGAL 1.5 is needed.
-PostgreSQL 12-17 required. GEOS 3.8+ required. Proj 6.1+ required.
+PostgreSQL 12-17 required. GEOS 3.8+ required. Proj 6.1+ required. 
 
 Many thanks to our translation teams, in particular:
 
 Dapeng Wang, Zuo Chenwei from HighGo (Chinese Team)
-Teramoto Ikuhiro (Japanese Team)
+Teramoto Ikuhiro (Japanese Team) 
 Andreas Schild (German Team)
 
 * Breaking Changes *
@@ -38,6 +38,17 @@ Andreas Schild (German Team)
            be ST_Contains(ST_Buffer(A, R), B) (Paul Ramsey)
   - Remove the WFS_locks extra package. (Paul Ramsey)
   - #5747, GH-776, ST_Length: Return 0 for CurvePolygon (Dan Baston)
+  - #5770, support for GEOS 3.13 and RelateNG. Most functionality
+           remains the same, but new GEOS predicate implementation
+           has a few small changes.
+      - Boundary Node Rule relate matrices might be different when
+        using the "multi-valent end point" rule.
+      - Relate matrices for situations with invalid MultiPolygons
+        with shared boundaries might be different. Run ST_MakeValid
+        to get valid inputs to feed to the calculation.
+      - Zero length LineStrings are treated as if they are the
+        equivalent Point object.
+
 
 * Deprecated signatures *
 
diff --git a/liblwgeom/gbox.c b/liblwgeom/gbox.c
index 265c69286..ae13dd4f0 100644
--- a/liblwgeom/gbox.c
+++ b/liblwgeom/gbox.c
@@ -346,6 +346,18 @@ gbox_contains_2d(const GBOX *g1, const GBOX *g2)
 	return LW_TRUE;
 }
 
+
+int
+gbox_within_2d(const GBOX *g1, const GBOX *g2)
+{
+	if ( ( g1->xmin < g2->xmin ) || ( g1->xmax > g2->xmax ) ||
+	     ( g1->ymin < g2->ymin ) || ( g1->ymax > g2->ymax ) )
+	{
+		return LW_FALSE;
+	}
+	return LW_TRUE;
+}
+
 int
 gbox_contains_point2d(const GBOX *g, const POINT2D *p)
 {
diff --git a/liblwgeom/liblwgeom.h.in b/liblwgeom/liblwgeom.h.in
index 5a7d00c78..85085a49d 100644
--- a/liblwgeom/liblwgeom.h.in
+++ b/liblwgeom/liblwgeom.h.in
@@ -2060,6 +2060,11 @@ extern int gbox_overlaps_2d(const GBOX *g1, const GBOX *g2);
 */
 extern int  gbox_contains_2d(const GBOX *g1, const GBOX *g2);
 
+/**
+* Return #LW_TRUE if the first #GBOX is within the second on the 2d plane, #LW_FALSE otherwise.
+*/
+extern int  gbox_within_2d(const GBOX *g1, const GBOX *g2);
+
 /**
 * Copy the values of original #GBOX into duplicate.
 */
diff --git a/liblwgeom/lwgeom_geos.h b/liblwgeom/lwgeom_geos.h
index 4af0d85d4..84638d82b 100644
--- a/liblwgeom/lwgeom_geos.h
+++ b/liblwgeom/lwgeom_geos.h
@@ -33,8 +33,6 @@ typedef void (*GEOSMessageHandler)(const char *fmt, ...) __attribute__ (( format
 #include "liblwgeom.h"
 #include "lwunionfind.h"
 
-
-
 /*
 ** Public prototypes for GEOS utility functions.
 */
diff --git a/postgis/Makefile.in b/postgis/Makefile.in
index 15a69d2d4..67feb698d 100644
--- a/postgis/Makefile.in
+++ b/postgis/Makefile.in
@@ -83,6 +83,7 @@ PG_OBJS= \
 	lwgeom_box.o \
 	lwgeom_box3d.o \
 	lwgeom_geos.o \
+	lwgeom_geos_predicates.o \
 	lwgeom_geos_prepared.o \
 	lwgeom_geos_clean.o \
 	lwgeom_geos_relatematch.o \
diff --git a/postgis/lwgeom_geos.c b/postgis/lwgeom_geos.c
index 92b2b5f53..a6bc297b8 100644
--- a/postgis/lwgeom_geos.c
+++ b/postgis/lwgeom_geos.c
@@ -50,17 +50,6 @@
 /*
 ** Prototypes for SQL-bound functions
 */
-Datum relate_full(PG_FUNCTION_ARGS);
-Datum relate_pattern(PG_FUNCTION_ARGS);
-Datum disjoint(PG_FUNCTION_ARGS);
-Datum touches(PG_FUNCTION_ARGS);
-Datum ST_Intersects(PG_FUNCTION_ARGS);
-Datum crosses(PG_FUNCTION_ARGS);
-Datum contains(PG_FUNCTION_ARGS);
-Datum within(PG_FUNCTION_ARGS);
-Datum containsproperly(PG_FUNCTION_ARGS);
-Datum covers(PG_FUNCTION_ARGS);
-Datum overlaps(PG_FUNCTION_ARGS);
 Datum isvalid(PG_FUNCTION_ARGS);
 Datum isvalidreason(PG_FUNCTION_ARGS);
 Datum isvaliddetail(PG_FUNCTION_ARGS);
@@ -83,7 +72,6 @@ Datum polygonize_garray(PG_FUNCTION_ARGS);
 Datum clusterintersecting_garray(PG_FUNCTION_ARGS);
 Datum cluster_within_distance_garray(PG_FUNCTION_ARGS);
 Datum linemerge(PG_FUNCTION_ARGS);
-Datum coveredby(PG_FUNCTION_ARGS);
 Datum hausdorffdistance(PG_FUNCTION_ARGS);
 Datum hausdorffdistancedensify(PG_FUNCTION_ARGS);
 Datum ST_FrechetDistance(PG_FUNCTION_ARGS);
@@ -100,46 +88,28 @@ Datum pgis_union_geometry_array(PG_FUNCTION_ARGS);
 ** Prototypes end
 */
 
-
 PG_FUNCTION_INFO_V1(postgis_geos_version);
 Datum postgis_geos_version(PG_FUNCTION_ARGS)
 {
-	const char *ver = lwgeom_geos_version();
-	text *result = cstring_to_text(ver);
-	PG_RETURN_POINTER(result);
+    const char *ver = lwgeom_geos_version();
+    text *result = cstring_to_text(ver);
+    PG_RETURN_POINTER(result);
 }
 
 PG_FUNCTION_INFO_V1(postgis_geos_compiled_version);
 Datum postgis_geos_compiled_version(PG_FUNCTION_ARGS)
 {
-	const char *ver = lwgeom_geos_compiled_version();
-	text *result = cstring_to_text(ver);
-	PG_RETURN_POINTER(result);
+    const char *ver = lwgeom_geos_compiled_version();
+    text *result = cstring_to_text(ver);
+    PG_RETURN_POINTER(result);
 }
 
-
-static char
-is_poly(const GSERIALIZED* g)
-{
-    int type = gserialized_get_type(g);
-    return type == POLYGONTYPE || type == MULTIPOLYGONTYPE;
-}
-
-static char
-is_point(const GSERIALIZED* g)
-{
-	int type = gserialized_get_type(g);
-	return type == POINTTYPE || type == MULTIPOINTTYPE;
-}
-
-
 /**
  *  @brief Compute the Hausdorff distance thanks to the corresponding GEOS function
  *  @example ST_HausdorffDistance {@link #hausdorffdistance} - SELECT ST_HausdorffDistance(
  *      'POLYGON((0 0, 0 2, 1 2, 2 2, 2 0, 0 0))'::geometry,
  *      'POLYGON((0.5 0.5, 0.5 2.5, 1.5 2.5, 2.5 2.5, 2.5 0.5, 0.5 0.5))'::geometry);
  */
-
 PG_FUNCTION_INFO_V1(hausdorffdistance);
 Datum hausdorffdistance(PG_FUNCTION_ARGS)
 {
@@ -1761,819 +1731,6 @@ Datum isvaliddetail(PG_FUNCTION_ARGS)
 	PG_RETURN_HEAPTUPLEHEADER(result);
 }
 
-/**
- * overlaps(GSERIALIZED g1,GSERIALIZED g2)
- * @param g1
- * @param g2
- * @return  if GEOS::g1->overlaps(g2) returns true
- * @throw an error (elog(ERROR,...)) if GEOS throws an error
- */
-PG_FUNCTION_INFO_V1(overlaps);
-Datum overlaps(PG_FUNCTION_ARGS)
-{
-	GSERIALIZED *geom1;
-	GSERIALIZED *geom2;
-	GEOSGeometry *g1, *g2;
-	char result;
-	GBOX box1, box2;
-
-	geom1 = PG_GETARG_GSERIALIZED_P(0);
-	geom2 = PG_GETARG_GSERIALIZED_P(1);
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/* A.Overlaps(Empty) == FALSE */
-	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
-		PG_RETURN_BOOL(false);
-
-	/*
-	 * short-circuit 1: if geom2 bounding box does not overlap
-	 * geom1 bounding box we can return FALSE.
-	 */
-	if ( gserialized_get_gbox_p(geom1, &box1) &&
-	     gserialized_get_gbox_p(geom2, &box2) )
-	{
-		if ( ! gbox_overlaps_2d(&box1, &box2) )
-		{
-			PG_RETURN_BOOL(false);
-		}
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	g1 = POSTGIS2GEOS(geom1);
-	if (!g1)
-		HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-
-	g2 = POSTGIS2GEOS(geom2);
-
-	if (!g2)
-	{
-		GEOSGeom_destroy(g1);
-		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-	}
-
-	result = GEOSOverlaps(g1,g2);
-
-	GEOSGeom_destroy(g1);
-	GEOSGeom_destroy(g2);
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSOverlaps");
-
-	PG_FREE_IF_COPY(geom1, 0);
-	PG_FREE_IF_COPY(geom2, 1);
-
-	PG_RETURN_BOOL(result);
-}
-
-
-PG_FUNCTION_INFO_V1(contains);
-Datum contains(PG_FUNCTION_ARGS)
-{
-	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);
-	int result;
-	GEOSGeometry *g1, *g2;
-	GBOX box1, box2;
-	PrepGeomCache *prep_cache;
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/* A.Contains(Empty) == FALSE */
-	if (gserialized_is_empty(geom1) || gserialized_is_empty(geom2))
-		PG_RETURN_BOOL(false);
-
-	POSTGIS_DEBUGF(3, "Contains: type1: %d, type2: %d", gserialized_get_type(geom1), gserialized_get_type(geom2));
-
-	/*
-	** short-circuit 1: if geom2 bounding box is not completely inside
-	** geom1 bounding box we can return FALSE.
-	*/
-	if (gserialized_get_gbox_p(geom1, &box1) &&
-	    gserialized_get_gbox_p(geom2, &box2))
-	{
-		if (!gbox_contains_2d(&box1, &box2))
-			PG_RETURN_BOOL(false);
-	}
-
-	/*
-	** short-circuit 2: if geom2 is a point and geom1 is a polygon
-	** call the point-in-polygon function.
-	*/
-	if (is_poly(geom1) && is_point(geom2))
-	{
-		const GSERIALIZED *gpoint = shared_gserialized_get(shared_geom2);
-		LWGEOM *lwpt = lwgeom_from_gserialized(gpoint);
-		IntervalTree *itree = GetIntervalTree(fcinfo, shared_geom1);
-		bool result = itree_pip_contains(itree, lwpt);
-		lwgeom_free(lwpt);
-		PG_RETURN_BOOL(result);
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, NULL);
-
-	if ( prep_cache && prep_cache->prepared_geom && prep_cache->gcache.argnum == 1 )
-	{
-		g1 = POSTGIS2GEOS(geom2);
-		if (!g1)
-			HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
-
-		POSTGIS_DEBUG(4, "containsPrepared: cache is live, running preparedcontains");
-		result = GEOSPreparedContains( prep_cache->prepared_geom, g1);
-		GEOSGeom_destroy(g1);
-	}
-	else
-	{
-		g1 = POSTGIS2GEOS(geom1);
-		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-		g2 = POSTGIS2GEOS(geom2);
-		if (!g2)
-		{
-			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-			GEOSGeom_destroy(g1);
-		}
-		POSTGIS_DEBUG(4, "containsPrepared: cache is not ready, running standard contains");
-		result = GEOSContains( g1, g2);
-		GEOSGeom_destroy(g1);
-		GEOSGeom_destroy(g2);
-	}
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSContains");
-
-	PG_RETURN_BOOL(result > 0);
-}
-
-
-PG_FUNCTION_INFO_V1(within);
-Datum within(PG_FUNCTION_ARGS)
-{
-	PG_RETURN_DATUM(CallerFInfoFunctionCall2(contains, fcinfo->flinfo, InvalidOid,
-		PG_GETARG_DATUM(1), PG_GETARG_DATUM(0)));
-}
-
-
-
-PG_FUNCTION_INFO_V1(containsproperly);
-Datum containsproperly(PG_FUNCTION_ARGS)
-{
-	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);
-	char 					result;
-	GBOX 			box1, box2;
-	PrepGeomCache *	prep_cache;
-
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/* A.ContainsProperly(Empty) == FALSE */
-	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
-		PG_RETURN_BOOL(false);
-
-	/*
-	 * short-circuit: if geom2 bounding box is not completely inside
-	 * geom1 bounding box we can return FALSE.
-	 */
-	if ( gserialized_get_gbox_p(geom1, &box1) &&
-	     gserialized_get_gbox_p(geom2, &box2) )
-	{
-		if ( ! gbox_contains_2d(&box1, &box2) )
-			PG_RETURN_BOOL(false);
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, 0);
-
-	if ( prep_cache && prep_cache->prepared_geom && prep_cache->gcache.argnum == 1 )
-	{
-		GEOSGeometry *g = POSTGIS2GEOS(geom2);
-		if (!g) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-		result = GEOSPreparedContainsProperly( prep_cache->prepared_geom, g);
-		GEOSGeom_destroy(g);
-	}
-	else
-	{
-		GEOSGeometry *g2;
-		GEOSGeometry *g1;
-
-		g1 = POSTGIS2GEOS(geom1);
-		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-		g2 = POSTGIS2GEOS(geom2);
-		if (!g2)
-		{
-			GEOSGeom_destroy(g1);
-			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-		}
-		result = GEOSRelatePattern( g1, g2, "T**FF*FF*" );
-		GEOSGeom_destroy(g1);
-		GEOSGeom_destroy(g2);
-	}
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSContains");
-
-	PG_RETURN_BOOL(result);
-}
-
-/*
- * Described at
- * http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html
- */
-PG_FUNCTION_INFO_V1(covers);
-Datum covers(PG_FUNCTION_ARGS)
-{
-	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);
-	int result;
-	GBOX box1, box2;
-	PrepGeomCache *prep_cache;
-
-	POSTGIS_DEBUGF(3, "Covers: type1: %d, type2: %d", gserialized_get_type(geom1), gserialized_get_type(geom2));
-
-	/* A.Covers(Empty) == FALSE */
-	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
-		PG_RETURN_BOOL(false);
-
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/*
-	 * short-circuit 1: if geom2 bounding box is not completely inside
-	 * geom1 bounding box we can return FALSE.
-	 */
-	if ( gserialized_get_gbox_p(geom1, &box1) &&
-	     gserialized_get_gbox_p(geom2, &box2) )
-	{
-		if ( ! gbox_contains_2d(&box1, &box2) )
-		{
-			PG_RETURN_BOOL(false);
-		}
-	}
-	/*
-	 * short-circuit 2: if geom2 is a point and geom1 is a polygon
-	 * call the point-in-polygon function.
-	 */
-	if (is_poly(geom1) && is_point(geom2))
-	{
-		const GSERIALIZED *gpoint = shared_gserialized_get(shared_geom2);
-		LWGEOM *lwpt = lwgeom_from_gserialized(gpoint);
-		IntervalTree *itree = GetIntervalTree(fcinfo, shared_geom1);
-		bool result = itree_pip_covers(itree, lwpt);
-		lwgeom_free(lwpt);
-		PG_RETURN_BOOL(result);
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, 0);
-
-	if ( prep_cache && prep_cache->prepared_geom && prep_cache->gcache.argnum == 1 )
-	{
-		GEOSGeometry *g1 = POSTGIS2GEOS(geom2);
-		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-		result = GEOSPreparedCovers( prep_cache->prepared_geom, g1);
-		GEOSGeom_destroy(g1);
-	}
-	else
-	{
-		GEOSGeometry *g1;
-		GEOSGeometry *g2;
-
-		g1 = POSTGIS2GEOS(geom1);
-		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-		g2 = POSTGIS2GEOS(geom2);
-		if (!g2)
-		{
-			GEOSGeom_destroy(g1);
-			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-		}
-		result = GEOSRelatePattern( g1, g2, "******FF*" );
-		GEOSGeom_destroy(g1);
-		GEOSGeom_destroy(g2);
-	}
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSCovers");
-
-	PG_RETURN_BOOL(result);
-}
-
-
-/**
-* ST_Within(A, B) => ST_Contains(B, A) so we just delegate this calculation to the
-* Contains implementation.
-PG_FUNCTION_INFO_V1(within);
-Datum within(PG_FUNCTION_ARGS)
-*/
-
-/*
- * Described at:
- * http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html
- */
-PG_FUNCTION_INFO_V1(coveredby);
-Datum coveredby(PG_FUNCTION_ARGS)
-{
-	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);
-	GEOSGeometry *g1, *g2;
-	int result;
-	GBOX box1, box2;
-	char *patt = "**F**F***";
-
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	POSTGIS_DEBUGF(3, "CoveredBy: type1: %d, type2: %d", gserialized_get_type(geom1), gserialized_get_type(geom2));
-
-	/* A.CoveredBy(Empty) == FALSE */
-	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
-		PG_RETURN_BOOL(false);
-
-	/*
-	 * short-circuit 1: if geom1 bounding box is not completely inside
-	 * geom2 bounding box we can return FALSE.
-	 */
-	if ( gserialized_get_gbox_p(geom1, &box1) &&
-	     gserialized_get_gbox_p(geom2, &box2) )
-	{
-		if ( ! gbox_contains_2d(&box2, &box1) )
-		{
-			PG_RETURN_BOOL(false);
-		}
-
-		POSTGIS_DEBUG(3, "bounding box short-circuit missed.");
-	}
-	/*
-	 * short-circuit 2: if geom1 is a point and geom2 is a polygon
-	 * call the point-in-polygon function.
-	 */
-	if (is_point(geom1) && is_poly(geom2))
-	{
-		const GSERIALIZED *gpoint = shared_gserialized_get(shared_geom1);
-		IntervalTree *itree = GetIntervalTree(fcinfo, shared_geom2);
-		LWGEOM *lwpt = lwgeom_from_gserialized(gpoint);
-		bool result = itree_pip_covers(itree, lwpt);
-		lwgeom_free(lwpt);
-		PG_RETURN_BOOL(result);
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	g1 = POSTGIS2GEOS(geom1);
-
-	if (!g1)
-		HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-
-	g2 = POSTGIS2GEOS(geom2);
-
-	if (!g2)
-	{
-		GEOSGeom_destroy(g1);
-		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-	}
-
-	result = GEOSRelatePattern(g1,g2,patt);
-
-	GEOSGeom_destroy(g1);
-	GEOSGeom_destroy(g2);
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSCoveredBy");
-
-	PG_RETURN_BOOL(result);
-}
-
-PG_FUNCTION_INFO_V1(crosses);
-Datum crosses(PG_FUNCTION_ARGS)
-{
-	GSERIALIZED *geom1;
-	GSERIALIZED *geom2;
-	GEOSGeometry *g1, *g2;
-	int result;
-	GBOX box1, box2;
-
-	geom1 = PG_GETARG_GSERIALIZED_P(0);
-	geom2 = PG_GETARG_GSERIALIZED_P(1);
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/* A.Crosses(Empty) == FALSE */
-	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
-		PG_RETURN_BOOL(false);
-
-	/*
-	 * short-circuit 1: if geom2 bounding box does not overlap
-	 * geom1 bounding box we can return FALSE.
-	 */
-	if ( gserialized_get_gbox_p(geom1, &box1) &&
-	     gserialized_get_gbox_p(geom2, &box2) )
-	{
-		if ( gbox_overlaps_2d(&box1, &box2) == LW_FALSE )
-		{
-			PG_RETURN_BOOL(false);
-		}
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	g1 = POSTGIS2GEOS(geom1);
-	if (!g1)
-		HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-
-	g2 = POSTGIS2GEOS(geom2);
-	if (!g2)
-	{
-		GEOSGeom_destroy(g1);
-		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-	}
-
-	result = GEOSCrosses(g1,g2);
-
-	GEOSGeom_destroy(g1);
-	GEOSGeom_destroy(g2);
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSCrosses");
-
-	PG_FREE_IF_COPY(geom1, 0);
-	PG_FREE_IF_COPY(geom2, 1);
-
-	PG_RETURN_BOOL(result);
-}
-
-PG_FUNCTION_INFO_V1(ST_Intersects);
-Datum ST_Intersects(PG_FUNCTION_ARGS)
-{
-	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);
-	int result;
-	GBOX box1, box2;
-	PrepGeomCache *prep_cache;
-
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/* A.Intersects(Empty) == FALSE */
-	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
-		PG_RETURN_BOOL(false);
-
-	/*
-	 * short-circuit 1: if geom2 bounding box does not overlap
-	 * geom1 bounding box we can return FALSE.
-	 */
-	if ( gserialized_get_gbox_p(geom1, &box1) &&
-	     gserialized_get_gbox_p(geom2, &box2) )
-	{
-		if ( gbox_overlaps_2d(&box1, &box2) == LW_FALSE )
-			PG_RETURN_BOOL(false);
-	}
-
-	/*
-	 * short-circuit 2: if the geoms are a point and a polygon,
-	 * call the itree_pip_intersects function.
-	 */
-	if ((is_point(geom1) && is_poly(geom2)) ||
-	    (is_point(geom2) && is_poly(geom1)))
-	{
-		SHARED_GSERIALIZED *shared_gpoly = is_poly(geom1) ? shared_geom1 : shared_geom2;
-		SHARED_GSERIALIZED *shared_gpoint = is_point(geom1) ? shared_geom1 : shared_geom2;
-		const GSERIALIZED *gpoint = shared_gserialized_get(shared_gpoint);
-		LWGEOM *lwpt = lwgeom_from_gserialized(gpoint);
-		IntervalTree *itree = GetIntervalTree(fcinfo, shared_gpoly);
-		bool result = itree_pip_intersects(itree, lwpt);
-		lwgeom_free(lwpt);
-		PG_RETURN_BOOL(result);
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, shared_geom2);
-
-	if ( prep_cache && prep_cache->prepared_geom )
-	{
-		if ( prep_cache->gcache.argnum == 1 )
-		{
-			GEOSGeometry *g = POSTGIS2GEOS(geom2);
-			if (!g) HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
-			result = GEOSPreparedIntersects( prep_cache->prepared_geom, g);
-			GEOSGeom_destroy(g);
-		}
-		else
-		{
-			GEOSGeometry *g = POSTGIS2GEOS(geom1);
-			if (!g)
-				HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
-			result = GEOSPreparedIntersects( prep_cache->prepared_geom, g);
-			GEOSGeom_destroy(g);
-		}
-	}
-	else
-	{
-		GEOSGeometry *g1;
-		GEOSGeometry *g2;
-		g1 = POSTGIS2GEOS(geom1);
-		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-		g2 = POSTGIS2GEOS(geom2);
-		if (!g2)
-		{
-			GEOSGeom_destroy(g1);
-			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-		}
-		result = GEOSIntersects( g1, g2);
-		GEOSGeom_destroy(g1);
-		GEOSGeom_destroy(g2);
-	}
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSIntersects");
-
-	PG_RETURN_BOOL(result);
-}
-
-
-PG_FUNCTION_INFO_V1(touches);
-Datum touches(PG_FUNCTION_ARGS)
-{
-	GSERIALIZED *geom1;
-	GSERIALIZED *geom2;
-	GEOSGeometry *g1, *g2;
-	char result;
-	GBOX box1, box2;
-
-	geom1 = PG_GETARG_GSERIALIZED_P(0);
-	geom2 = PG_GETARG_GSERIALIZED_P(1);
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/* A.Touches(Empty) == FALSE */
-	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
-		PG_RETURN_BOOL(false);
-
-	/*
-	 * short-circuit 1: if geom2 bounding box does not overlap
-	 * geom1 bounding box we can return FALSE.
-	 */
-	if ( gserialized_get_gbox_p(geom1, &box1) &&
-	     gserialized_get_gbox_p(geom2, &box2) )
-	{
-		if ( gbox_overlaps_2d(&box1, &box2) == LW_FALSE )
-		{
-			PG_RETURN_BOOL(false);
-		}
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	g1 = POSTGIS2GEOS(geom1 );
-	if (!g1)
-		HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-
-	g2 = POSTGIS2GEOS(geom2 );
-	if (!g2)
-	{
-		GEOSGeom_destroy(g1);
-		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-	}
-
-	result = GEOSTouches(g1,g2);
-
-	GEOSGeom_destroy(g1);
-	GEOSGeom_destroy(g2);
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSTouches");
-
-	PG_FREE_IF_COPY(geom1, 0);
-	PG_FREE_IF_COPY(geom2, 1);
-
-	PG_RETURN_BOOL(result);
-}
-
-
-PG_FUNCTION_INFO_V1(disjoint);
-Datum disjoint(PG_FUNCTION_ARGS)
-{
-	GSERIALIZED *geom1;
-	GSERIALIZED *geom2;
-	GEOSGeometry *g1, *g2;
-	char result;
-	GBOX box1, box2;
-
-	geom1 = PG_GETARG_GSERIALIZED_P(0);
-	geom2 = PG_GETARG_GSERIALIZED_P(1);
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/* A.Disjoint(Empty) == TRUE */
-	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
-		PG_RETURN_BOOL(true);
-
-	/*
-	 * short-circuit 1: if geom2 bounding box does not overlap
-	 * geom1 bounding box we can return TRUE.
-	 */
-	if ( gserialized_get_gbox_p(geom1, &box1) &&
-	     gserialized_get_gbox_p(geom2, &box2) )
-	{
-		if ( gbox_overlaps_2d(&box1, &box2) == LW_FALSE )
-		{
-			PG_RETURN_BOOL(true);
-		}
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	g1 = POSTGIS2GEOS(geom1);
-	if (!g1)
-		HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-
-	g2 = POSTGIS2GEOS(geom2);
-	if (!g2)
-	{
-		GEOSGeom_destroy(g1);
-		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-	}
-
-	result = GEOSDisjoint(g1,g2);
-
-	GEOSGeom_destroy(g1);
-	GEOSGeom_destroy(g2);
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSDisjoint");
-
-	PG_FREE_IF_COPY(geom1, 0);
-	PG_FREE_IF_COPY(geom2, 1);
-
-	PG_RETURN_BOOL(result);
-}
-
-
-PG_FUNCTION_INFO_V1(relate_pattern);
-Datum relate_pattern(PG_FUNCTION_ARGS)
-{
-	GSERIALIZED *geom1;
-	GSERIALIZED *geom2;
-	char *patt;
-	char result;
-	GEOSGeometry *g1, *g2;
-	size_t i;
-
-	geom1 = PG_GETARG_GSERIALIZED_P(0);
-	geom2 = PG_GETARG_GSERIALIZED_P(1);
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/* TODO handle empty */
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	g1 = POSTGIS2GEOS(geom1);
-	if (!g1)
-		HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-	g2 = POSTGIS2GEOS(geom2);
-	if (!g2)
-	{
-		GEOSGeom_destroy(g1);
-		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-	}
-
-	patt =  DatumGetCString(DirectFunctionCall1(textout, PG_GETARG_DATUM(2)));
-
-	/*
-	** Need to make sure 't' and 'f' are upper-case before handing to GEOS
-	*/
-	for ( i = 0; i < strlen(patt); i++ )
-	{
-		if ( patt[i] == 't' ) patt[i] = 'T';
-		if ( patt[i] == 'f' ) patt[i] = 'F';
-	}
-
-	result = GEOSRelatePattern(g1,g2,patt);
-	GEOSGeom_destroy(g1);
-	GEOSGeom_destroy(g2);
-	pfree(patt);
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSRelatePattern");
-
-	PG_FREE_IF_COPY(geom1, 0);
-	PG_FREE_IF_COPY(geom2, 1);
-
-	PG_RETURN_BOOL(result);
-}
-
-
-
-PG_FUNCTION_INFO_V1(relate_full);
-Datum relate_full(PG_FUNCTION_ARGS)
-{
-	GSERIALIZED *geom1;
-	GSERIALIZED *geom2;
-	GEOSGeometry *g1, *g2;
-	char *relate_str;
-	text *result;
-	int bnr = GEOSRELATE_BNR_OGC;
-
-	/* TODO handle empty */
-	geom1 = PG_GETARG_GSERIALIZED_P(0);
-	geom2 = PG_GETARG_GSERIALIZED_P(1);
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	if ( PG_NARGS() > 2 )
-		bnr = PG_GETARG_INT32(2);
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	g1 = POSTGIS2GEOS(geom1 );
-	if (!g1)
-		HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-	g2 = POSTGIS2GEOS(geom2 );
-	if (!g2)
-	{
-		GEOSGeom_destroy(g1);
-		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-	}
-
-	POSTGIS_DEBUG(3, "constructed geometries ");
-
-	POSTGIS_DEBUGF(3, "%s", GEOSGeomToWKT(g1));
-	POSTGIS_DEBUGF(3, "%s", GEOSGeomToWKT(g2));
-
-	relate_str = GEOSRelateBoundaryNodeRule(g1, g2, bnr);
-
-	GEOSGeom_destroy(g1);
-	GEOSGeom_destroy(g2);
-
-	if (!relate_str) HANDLE_GEOS_ERROR("GEOSRelate");
-
-	result = cstring_to_text(relate_str);
-	GEOSFree(relate_str);
-
-	PG_FREE_IF_COPY(geom1, 0);
-	PG_FREE_IF_COPY(geom2, 1);
-
-	PG_RETURN_TEXT_P(result);
-}
-
-
-PG_FUNCTION_INFO_V1(ST_Equals);
-Datum ST_Equals(PG_FUNCTION_ARGS)
-{
-	GSERIALIZED *geom1;
-	GSERIALIZED *geom2;
-	GEOSGeometry *g1, *g2;
-	char result;
-	GBOX box1, box2;
-
-	geom1 = PG_GETARG_GSERIALIZED_P(0);
-	geom2 = PG_GETARG_GSERIALIZED_P(1);
-	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
-
-	/* Empty == Empty */
-	if ( gserialized_is_empty(geom1) && gserialized_is_empty(geom2) )
-		PG_RETURN_BOOL(true);
-
-	/*
-	 * short-circuit: If geom1 and geom2 do not have the same bounding box
-	 * we can return FALSE.
-	 */
-	if ( gserialized_get_gbox_p(geom1, &box1) &&
-	     gserialized_get_gbox_p(geom2, &box2) )
-	{
-		if ( gbox_same_2d_float(&box1, &box2) == LW_FALSE )
-		{
-			PG_RETURN_BOOL(false);
-		}
-	}
-
-	/*
-	 * short-circuit: if geom1 and geom2 are binary-equivalent, we can return
-	 * TRUE.  This is much faster than doing the comparison using GEOS.
-	 */
-	if (VARSIZE(geom1) == VARSIZE(geom2) && !memcmp(geom1, geom2, VARSIZE(geom1))) {
-	    PG_RETURN_BOOL(true);
-	}
-
-	initGEOS(lwpgnotice, lwgeom_geos_error);
-
-	g1 = POSTGIS2GEOS(geom1);
-
-	if (!g1)
-		HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
-
-	g2 = POSTGIS2GEOS(geom2);
-
-	if (!g2)
-	{
-		GEOSGeom_destroy(g1);
-		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
-	}
-
-	result = GEOSEquals(g1,g2);
-
-	GEOSGeom_destroy(g1);
-	GEOSGeom_destroy(g2);
-
-	if (result == 2) HANDLE_GEOS_ERROR("GEOSEquals");
-
-	PG_FREE_IF_COPY(geom1, 0);
-	PG_FREE_IF_COPY(geom2, 1);
-
-	PG_RETURN_BOOL(result);
-}
 
 PG_FUNCTION_INFO_V1(issimple);
 Datum issimple(PG_FUNCTION_ARGS)
diff --git a/postgis/lwgeom_geos_predicates.c b/postgis/lwgeom_geos_predicates.c
new file mode 100644
index 000000000..ec97e0185
--- /dev/null
+++ b/postgis/lwgeom_geos_predicates.c
@@ -0,0 +1,993 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * PostGIS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * PostGIS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PostGIS.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2009-2014 Sandro Santilli <strk at kbt.io>
+ * Copyright 2008 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright 2001-2003 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+#include "../postgis_config.h"
+
+/* PostgreSQL */
+#include "postgres.h"
+#include "funcapi.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/numeric.h"
+#include "access/htup_details.h"
+
+/* PostGIS */
+#include "lwgeom_geos.h"
+#include "liblwgeom.h"
+#include "liblwgeom_internal.h"
+#include "lwgeom_itree.h"
+#include "lwgeom_geos_prepared.h"
+#include "lwgeom_accum.h"
+
+
+/* Prototypes for SQL-bound functions */
+Datum relate_full(PG_FUNCTION_ARGS);
+Datum relate_pattern(PG_FUNCTION_ARGS);
+Datum disjoint(PG_FUNCTION_ARGS);
+Datum touches(PG_FUNCTION_ARGS);
+Datum ST_Intersects(PG_FUNCTION_ARGS);
+Datum crosses(PG_FUNCTION_ARGS);
+Datum contains(PG_FUNCTION_ARGS);
+Datum within(PG_FUNCTION_ARGS);
+Datum containsproperly(PG_FUNCTION_ARGS);
+Datum covers(PG_FUNCTION_ARGS);
+Datum overlaps(PG_FUNCTION_ARGS);
+Datum coveredby(PG_FUNCTION_ARGS);
+Datum ST_Equals(PG_FUNCTION_ARGS);
+
+
+/*
+ * Utility to quickly check for polygonal geometries
+ */
+static inline char
+is_poly(const GSERIALIZED* g)
+{
+    int type = gserialized_get_type(g);
+    return type == POLYGONTYPE || type == MULTIPOLYGONTYPE;
+}
+
+/*
+ * Utility to quickly check for point geometries
+ */
+static inline char
+is_point(const GSERIALIZED* g)
+{
+	int type = gserialized_get_type(g);
+	return type == POINTTYPE || type == MULTIPOINTTYPE;
+}
+
+
+
+
+
+
+
+PG_FUNCTION_INFO_V1(ST_Intersects);
+Datum ST_Intersects(PG_FUNCTION_ARGS)
+{
+	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);
+	int result;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/* A.Intersects(Empty) == FALSE */
+	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
+		PG_RETURN_BOOL(false);
+
+	/*
+	 * Short-circuit 1: if geom2 bounding box does not overlap
+	 * geom1 bounding box we can return FALSE.
+	 */
+	if ( gserialized_get_gbox_p(geom1, &box1) &&
+	     gserialized_get_gbox_p(geom2, &box2) )
+	{
+		if ( gbox_overlaps_2d(&box1, &box2) == LW_FALSE )
+			PG_RETURN_BOOL(false);
+	}
+
+	/*
+	 * Short-circuit 2: if the geoms are a point and a polygon,
+	 * call the itree_pip_intersects function.
+	 */
+	if ((is_point(geom1) && is_poly(geom2)) ||
+	    (is_point(geom2) && is_poly(geom1)))
+	{
+		SHARED_GSERIALIZED *shared_gpoly = is_poly(geom1) ? shared_geom1 : shared_geom2;
+		SHARED_GSERIALIZED *shared_gpoint = is_point(geom1) ? shared_geom1 : shared_geom2;
+		const GSERIALIZED *gpoint = shared_gserialized_get(shared_gpoint);
+		LWGEOM *lwpt = lwgeom_from_gserialized(gpoint);
+		IntervalTree *itree = GetIntervalTree(fcinfo, shared_gpoly);
+		bool result = itree_pip_intersects(itree, lwpt);
+		lwgeom_free(lwpt);
+		PG_RETURN_BOOL(result);
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, shared_geom2);
+	if ( prep_cache && prep_cache->prepared_geom )
+	{
+		GEOSGeometry *g = prep_cache->gcache.argnum == 1
+			? POSTGIS2GEOS(geom2)
+			: POSTGIS2GEOS(geom1);
+		if (!g) HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
+		result = GEOSPreparedIntersects(prep_cache->prepared_geom, g);
+		GEOSGeom_destroy(g);
+	}
+	else
+	{
+		GEOSGeometry *g1, *g2;
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			GEOSGeom_destroy(g1);
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+		}
+		result = GEOSIntersects(g1, g2);
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSIntersects");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+PG_FUNCTION_INFO_V1(ST_Equals);
+Datum ST_Equals(PG_FUNCTION_ARGS)
+{
+	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);
+	char result;
+	GBOX box1, box2;
+	GEOSGeometry *g1, *g2;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/* Empty == Empty */
+	if ( gserialized_is_empty(geom1) && gserialized_is_empty(geom2) )
+		PG_RETURN_BOOL(true);
+
+	/*
+	 * Short-circuit: If geom1 and geom2 do not have the same bounding box
+	 * we can return FALSE.
+	 */
+	if ( gserialized_get_gbox_p(geom1, &box1) &&
+	     gserialized_get_gbox_p(geom2, &box2) )
+	{
+		if ( gbox_same_2d_float(&box1, &box2) == LW_FALSE )
+		{
+			PG_RETURN_BOOL(false);
+		}
+	}
+
+	/*
+	 * Short-circuit: if geom1 and geom2 are binary-equivalent, we can return
+	 * TRUE.  This is much faster than doing the comparison using GEOS.
+	 */
+	if (VARSIZE(geom1) == VARSIZE(geom2) && !memcmp(geom1, geom2, VARSIZE(geom1))) {
+	    PG_RETURN_BOOL(true);
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	g1 = POSTGIS2GEOS(geom1);
+	if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+	g2 = POSTGIS2GEOS(geom2);
+	if (!g2)
+	{
+		GEOSGeom_destroy(g1);
+		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+	}
+	result = GEOSEquals(g1, g2);
+	GEOSGeom_destroy(g1);
+	GEOSGeom_destroy(g2);
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSEquals");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+
+PG_FUNCTION_INFO_V1(touches);
+Datum touches(PG_FUNCTION_ARGS)
+{
+	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);
+	char result;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/* A.Touches(Empty) == FALSE */
+	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
+		PG_RETURN_BOOL(false);
+
+	/*
+	 * Short-circuit 1: if geom2 bounding box does not overlap
+	 * geom1 bounding box we can return FALSE.
+	 */
+	if ( gserialized_get_gbox_p(geom1, &box1) &&
+	     gserialized_get_gbox_p(geom2, &box2) )
+	{
+		if ( gbox_overlaps_2d(&box1, &box2) == LW_FALSE )
+		{
+			PG_RETURN_BOOL(false);
+		}
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, shared_geom2);
+	if ( prep_cache && prep_cache->prepared_geom )
+	{
+		GEOSGeometry *g = prep_cache->gcache.argnum == 1
+			? POSTGIS2GEOS(geom2)
+			: POSTGIS2GEOS(geom1);
+		if (!g) HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
+		result = GEOSPreparedTouches(prep_cache->prepared_geom, g);
+		GEOSGeom_destroy(g);
+	}
+	else
+	{
+		GEOSGeometry *g1, *g2;
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			GEOSGeom_destroy(g1);
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+		}
+		result = GEOSTouches(g1, g2);
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSTouches");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+PG_FUNCTION_INFO_V1(disjoint);
+Datum disjoint(PG_FUNCTION_ARGS)
+{
+	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);
+	char result;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/* A.Disjoint(Empty) == TRUE */
+	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
+		PG_RETURN_BOOL(true);
+
+	/*
+	 * Short-circuit 1: if geom2 bounding box does not overlap
+	 * geom1 bounding box we can return TRUE.
+	 */
+	if ( gserialized_get_gbox_p(geom1, &box1) &&
+	     gserialized_get_gbox_p(geom2, &box2) )
+	{
+		if ( gbox_overlaps_2d(&box1, &box2) == LW_FALSE )
+		{
+			PG_RETURN_BOOL(true);
+		}
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, shared_geom2);
+	if ( prep_cache && prep_cache->prepared_geom )
+	{
+		GEOSGeometry *g = prep_cache->gcache.argnum == 1
+			? POSTGIS2GEOS(geom2)
+			: POSTGIS2GEOS(geom1);
+		if (!g) HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
+		result = GEOSPreparedDisjoint(prep_cache->prepared_geom, g);
+		GEOSGeom_destroy(g);
+	}
+	else
+	{
+		GEOSGeometry *g1, *g2;
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			GEOSGeom_destroy(g1);
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+		}
+		result = GEOSDisjoint(g1, g2);
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSDisjoint");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+
+/**
+ * ST_Overlaps(geometry, geometry)
+ */
+PG_FUNCTION_INFO_V1(overlaps);
+Datum overlaps(PG_FUNCTION_ARGS)
+{
+	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);
+	char result;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/* A.Overlaps(Empty) == FALSE */
+	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
+		PG_RETURN_BOOL(false);
+
+	/*
+	 * Short-circuit 1: if geom2 bounding box does not overlap
+	 * geom1 bounding box we can return FALSE.
+	 */
+	if ( gserialized_get_gbox_p(geom1, &box1) &&
+	     gserialized_get_gbox_p(geom2, &box2) )
+	{
+		if ( ! gbox_overlaps_2d(&box1, &box2) )
+		{
+			PG_RETURN_BOOL(false);
+		}
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, shared_geom2);
+	if ( prep_cache && prep_cache->prepared_geom )
+	{
+		GEOSGeometry *g = prep_cache->gcache.argnum == 1
+			? POSTGIS2GEOS(geom2)
+			: POSTGIS2GEOS(geom1);
+		if (!g) HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
+		result = GEOSPreparedOverlaps(prep_cache->prepared_geom, g);
+		GEOSGeom_destroy(g);
+	}
+	else
+	{
+		GEOSGeometry *g1, *g2;
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			GEOSGeom_destroy(g1);
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+		}
+		result = GEOSOverlaps(g1, g2);
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSOverlaps");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+/**
+ * ST_Crosses(geometry, geometry)
+ */
+PG_FUNCTION_INFO_V1(crosses);
+Datum crosses(PG_FUNCTION_ARGS)
+{
+	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);
+	int result;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/* A.Crosses(Empty) == FALSE */
+	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
+		PG_RETURN_BOOL(false);
+
+	/*
+	 * Short-circuit 1: if geom2 bounding box does not overlap
+	 * geom1 bounding box we can return FALSE.
+	 */
+	if ( gserialized_get_gbox_p(geom1, &box1) &&
+	     gserialized_get_gbox_p(geom2, &box2) )
+	{
+		if ( gbox_overlaps_2d(&box1, &box2) == LW_FALSE )
+		{
+			PG_RETURN_BOOL(false);
+		}
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, shared_geom2);
+	if ( prep_cache && prep_cache->prepared_geom )
+	{
+		GEOSGeometry *g = prep_cache->gcache.argnum == 1
+			? POSTGIS2GEOS(geom2)
+			: POSTGIS2GEOS(geom1);
+		if (!g) HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
+		result = GEOSPreparedCrosses(prep_cache->prepared_geom, g);
+		GEOSGeom_destroy(g);
+	}
+	else
+	{
+		GEOSGeometry *g1, *g2;
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			GEOSGeom_destroy(g1);
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+		}
+		result = GEOSCrosses(g1, g2);
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSCrosses");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+
+/**
+ * ST_Contains(geometry, geometry)
+ */
+PG_FUNCTION_INFO_V1(contains);
+Datum contains(PG_FUNCTION_ARGS)
+{
+	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);
+	int result;
+	GEOSGeometry *g1, *g2;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/* A.Contains(Empty) == FALSE */
+	if (gserialized_is_empty(geom1) || gserialized_is_empty(geom2))
+		PG_RETURN_BOOL(false);
+
+	POSTGIS_DEBUGF(3, "Contains: type1: %d, type2: %d", gserialized_get_type(geom1), gserialized_get_type(geom2));
+
+	/*
+	** Short-circuit 1: if geom2 bounding box is not completely inside
+	** geom1 bounding box we can return FALSE.
+	*/
+	if (gserialized_get_gbox_p(geom1, &box1) &&
+	    gserialized_get_gbox_p(geom2, &box2))
+	{
+		if (!gbox_contains_2d(&box1, &box2))
+			PG_RETURN_BOOL(false);
+	}
+
+	/*
+	** Short-circuit 2: if geom2 is a point and geom1 is a polygon
+	** call the point-in-polygon function.
+	*/
+	if (is_poly(geom1) && is_point(geom2))
+	{
+		const GSERIALIZED *gpoint = shared_gserialized_get(shared_geom2);
+		LWGEOM *lwpt = lwgeom_from_gserialized(gpoint);
+		IntervalTree *itree = GetIntervalTree(fcinfo, shared_geom1);
+		bool result = itree_pip_contains(itree, lwpt);
+		lwgeom_free(lwpt);
+		PG_RETURN_BOOL(result);
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, NULL);
+	if ( prep_cache && prep_cache->prepared_geom && prep_cache->gcache.argnum == 1 )
+	{
+		g1 = POSTGIS2GEOS(geom2);
+		if (!g1)
+			HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
+
+		result = GEOSPreparedContains( prep_cache->prepared_geom, g1);
+		GEOSGeom_destroy(g1);
+	}
+	else
+	{
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+			GEOSGeom_destroy(g1);
+		}
+		result = GEOSContains( g1, g2);
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSContains");
+
+	PG_RETURN_BOOL(result > 0);
+}
+
+
+/**
+ * ST_Within(geometry, geometry)
+ */
+PG_FUNCTION_INFO_V1(within);
+Datum within(PG_FUNCTION_ARGS)
+{
+	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);
+	int result;
+	GEOSGeometry *g1, *g2;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/* A.Contains(Empty) == FALSE */
+	if (gserialized_is_empty(geom1) || gserialized_is_empty(geom2))
+		PG_RETURN_BOOL(false);
+
+	POSTGIS_DEBUGF(3, "Contains: type1: %d, type2: %d", gserialized_get_type(geom1), gserialized_get_type(geom2));
+
+	/*
+	** Short-circuit 1: if geom2 bounding box is not completely inside
+	** geom1 bounding box we can return FALSE.
+	*/
+	if (gserialized_get_gbox_p(geom1, &box1) &&
+	    gserialized_get_gbox_p(geom2, &box2))
+	{
+		if (!gbox_within_2d(&box1, &box2))
+			PG_RETURN_BOOL(false);
+	}
+
+	/*
+	** Short-circuit 2: if geom2 is a polygon and geom1 is a point
+	** call the point-in-polygon function.
+	*/
+	if (is_poly(geom2) && is_point(geom1))
+	{
+		const GSERIALIZED *gpoint = shared_gserialized_get(shared_geom1);
+		LWGEOM *lwpt = lwgeom_from_gserialized(gpoint);
+		IntervalTree *itree = GetIntervalTree(fcinfo, shared_geom2);
+		bool result = itree_pip_contains(itree, lwpt);
+		lwgeom_free(lwpt);
+		PG_RETURN_BOOL(result);
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, NULL);
+	if ( prep_cache && prep_cache->prepared_geom && prep_cache->gcache.argnum == 2 )
+	{
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1)
+			HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
+
+		result = GEOSPreparedContains(prep_cache->prepared_geom, g1);
+		GEOSGeom_destroy(g1);
+	}
+	else
+	{
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+			GEOSGeom_destroy(g1);
+		}
+		result = GEOSWithin(g1, g2);
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if ( result == 2 ) HANDLE_GEOS_ERROR("GEOSWithin");
+
+	PG_RETURN_BOOL(result > 0);
+}
+
+/**
+ * ST_ContainsProperly(geometry, geometry)
+ */
+PG_FUNCTION_INFO_V1(containsproperly);
+Datum containsproperly(PG_FUNCTION_ARGS)
+{
+	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);
+	char result;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/* A.ContainsProperly(Empty) == FALSE */
+	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
+		PG_RETURN_BOOL(false);
+
+	/*
+	 * Short-circuit: if geom2 bounding box is not completely inside
+	 * geom1 bounding box we can return FALSE.
+	 */
+	if ( gserialized_get_gbox_p(geom1, &box1) &&
+	     gserialized_get_gbox_p(geom2, &box2) )
+	{
+		if ( ! gbox_contains_2d(&box1, &box2) )
+			PG_RETURN_BOOL(false);
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, 0);
+	if ( prep_cache && prep_cache->prepared_geom && prep_cache->gcache.argnum == 1 )
+	{
+		GEOSGeometry *g = POSTGIS2GEOS(geom2);
+		if (!g) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		result = GEOSPreparedContainsProperly( prep_cache->prepared_geom, g);
+		GEOSGeom_destroy(g);
+	}
+	else
+	{
+		GEOSGeometry *g1, *g2;
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			GEOSGeom_destroy(g1);
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+		}
+		result = GEOSRelatePattern(g1, g2, "T**FF*FF*");
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSContainProperly");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+/**
+ * ST_Covers(geometry, geometry)
+ *
+ * http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html
+ */
+PG_FUNCTION_INFO_V1(covers);
+Datum covers(PG_FUNCTION_ARGS)
+{
+	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);
+	int result;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	POSTGIS_DEBUGF(3, "Covers: type1: %d, type2: %d", gserialized_get_type(geom1), gserialized_get_type(geom2));
+
+	/* A.Covers(Empty) == FALSE */
+	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
+		PG_RETURN_BOOL(false);
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/*
+	 * Short-circuit 1: if geom2 bounding box is not completely inside
+	 * geom1 bounding box we can return FALSE.
+	 */
+	if ( gserialized_get_gbox_p(geom1, &box1) &&
+	     gserialized_get_gbox_p(geom2, &box2) )
+	{
+		if ( ! gbox_contains_2d(&box1, &box2) )
+		{
+			PG_RETURN_BOOL(false);
+		}
+	}
+	/*
+	 * Short-circuit 2: if geom2 is a point and geom1 is a polygon
+	 * call the point-in-polygon function.
+	 */
+	if (is_poly(geom1) && is_point(geom2))
+	{
+		const GSERIALIZED *gpoint = shared_gserialized_get(shared_geom2);
+		LWGEOM *lwpt = lwgeom_from_gserialized(gpoint);
+		IntervalTree *itree = GetIntervalTree(fcinfo, shared_geom1);
+		bool result = itree_pip_covers(itree, lwpt);
+		lwgeom_free(lwpt);
+		PG_RETURN_BOOL(result);
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, 0);
+	if ( prep_cache && prep_cache->prepared_geom && prep_cache->gcache.argnum == 1 )
+	{
+		GEOSGeometry *g1 = POSTGIS2GEOS(geom2);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		result = GEOSPreparedCovers( prep_cache->prepared_geom, g1);
+		GEOSGeom_destroy(g1);
+	}
+	else
+	{
+		GEOSGeometry *g1, *g2;
+
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			GEOSGeom_destroy(g1);
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+		}
+		result = GEOSRelatePattern( g1, g2, "******FF*" );
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSCovers");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+/**
+ * ST_CoveredBy(geometry, geometry)
+ *
+ * Described at:
+ * http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html
+ */
+PG_FUNCTION_INFO_V1(coveredby);
+Datum coveredby(PG_FUNCTION_ARGS)
+{
+	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);
+	int result;
+	GBOX box1, box2;
+	PrepGeomCache *prep_cache;
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	POSTGIS_DEBUGF(3, "CoveredBy: type1: %d, type2: %d", gserialized_get_type(geom1), gserialized_get_type(geom2));
+
+	/* A.CoveredBy(Empty) == FALSE */
+	if ( gserialized_is_empty(geom1) || gserialized_is_empty(geom2) )
+		PG_RETURN_BOOL(false);
+
+	/*
+	 * Short-circuit 1: if geom1 bounding box is not completely inside
+	 * geom2 bounding box we can return FALSE.
+	 */
+	if ( gserialized_get_gbox_p(geom1, &box1) &&
+	     gserialized_get_gbox_p(geom2, &box2) )
+	{
+		if ( ! gbox_within_2d(&box1, &box2) )
+		{
+			PG_RETURN_BOOL(false);
+		}
+	}
+	/*
+	 * Short-circuit 2: if geom1 is a point and geom2 is a polygon
+	 * call the point-in-polygon function.
+	 */
+	if (is_point(geom1) && is_poly(geom2))
+	{
+		const GSERIALIZED *gpoint = shared_gserialized_get(shared_geom1);
+		IntervalTree *itree = GetIntervalTree(fcinfo, shared_geom2);
+		LWGEOM *lwpt = lwgeom_from_gserialized(gpoint);
+		bool result = itree_pip_covers(itree, lwpt);
+		lwgeom_free(lwpt);
+		PG_RETURN_BOOL(result);
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, 0);
+	if ( prep_cache && prep_cache->prepared_geom && prep_cache->gcache.argnum == 2)
+	{
+		GEOSGeometry *g1 = POSTGIS2GEOS(geom2);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		result = GEOSPreparedCovers( prep_cache->prepared_geom, g1);
+		GEOSGeom_destroy(g1);
+	}
+	else
+	{
+		GEOSGeometry *g1, *g2;
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			GEOSGeom_destroy(g1);
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+		}
+
+		result = GEOSCoveredBy(g1, g2);
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSCoveredBy");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+PG_FUNCTION_INFO_V1(relate_pattern);
+Datum relate_pattern(PG_FUNCTION_ARGS)
+{
+	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);
+	text *patt = PG_GETARG_TEXT_P(2);
+	char *pstr = text_to_cstring(patt);
+	char result;
+	size_t i;
+#if POSTGIS_GEOS_VERSION >= 31300
+	PrepGeomCache *prep_cache;
+#endif
+
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	/*
+	** Need to make sure 't' and 'f' are upper-case before handing to GEOS
+	*/
+	for ( i = 0; i < strlen(pstr); i++ )
+	{
+		if ( pstr[i] == 't' ) pstr[i] = 'T';
+		if ( pstr[i] == 'f' ) pstr[i] = 'F';
+	}
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+#if POSTGIS_GEOS_VERSION >= 31300
+	prep_cache = GetPrepGeomCache(fcinfo, shared_geom1, shared_geom2);
+	if ( prep_cache && prep_cache->prepared_geom )
+	{
+		GEOSGeometry *g = prep_cache->gcache.argnum == 1
+			? POSTGIS2GEOS(geom2)
+			: POSTGIS2GEOS(geom1);
+		if (!g) HANDLE_GEOS_ERROR("Geometry could not be converted to GEOS");
+		result = GEOSPreparedRelatePattern(prep_cache->prepared_geom, g, pstr);
+		GEOSGeom_destroy(g);
+	}
+	else
+#endif
+	{
+		GEOSGeometry *g1, *g2;
+		g1 = POSTGIS2GEOS(geom1);
+		if (!g1) HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+		g2 = POSTGIS2GEOS(geom2);
+		if (!g2)
+		{
+			HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+			GEOSGeom_destroy(g1);
+		}
+		result = GEOSRelatePattern(g1, g2, pstr);
+		GEOSGeom_destroy(g1);
+		GEOSGeom_destroy(g2);
+	}
+
+	pfree(pstr);
+	if (result == 2) HANDLE_GEOS_ERROR("GEOSRelatePattern");
+
+	PG_RETURN_BOOL(result);
+}
+
+
+PG_FUNCTION_INFO_V1(relate_full);
+Datum relate_full(PG_FUNCTION_ARGS)
+{
+	GSERIALIZED *geom1 = PG_GETARG_GSERIALIZED_P(0);
+	GSERIALIZED *geom2 = PG_GETARG_GSERIALIZED_P(1);
+	GEOSGeometry *g1, *g2;
+	char *relate_str;
+	text *result;
+	int bnr = GEOSRELATE_BNR_OGC;
+
+	/* TODO handle empty */
+	gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
+
+	if ( PG_NARGS() > 2 )
+		bnr = PG_GETARG_INT32(2);
+
+	initGEOS(lwpgnotice, lwgeom_geos_error);
+
+	g1 = POSTGIS2GEOS(geom1);
+	if (!g1)
+		HANDLE_GEOS_ERROR("First argument geometry could not be converted to GEOS");
+	g2 = POSTGIS2GEOS(geom2);
+	if (!g2)
+	{
+		GEOSGeom_destroy(g1);
+		HANDLE_GEOS_ERROR("Second argument geometry could not be converted to GEOS");
+	}
+
+	POSTGIS_DEBUG(3, "constructed geometries ");
+
+	POSTGIS_DEBUGF(3, "%s", GEOSGeomToWKT(g1));
+	POSTGIS_DEBUGF(3, "%s", GEOSGeomToWKT(g2));
+
+	relate_str = GEOSRelateBoundaryNodeRule(g1, g2, bnr);
+	if (!relate_str) HANDLE_GEOS_ERROR("GEOSRelate");
+	result = cstring_to_text(relate_str);
+	GEOSFree(relate_str);
+	GEOSGeom_destroy(g1);
+	GEOSGeom_destroy(g2);
+
+	PG_FREE_IF_COPY(geom1, 0);
+	PG_FREE_IF_COPY(geom2, 1);
+
+	PG_RETURN_TEXT_P(result);
+}
+
+
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index e8524bd1f..e62a2c1c8 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -4492,8 +4492,8 @@ CREATE OR REPLACE FUNCTION ST_Relate(geom1 geometry, geom2 geometry, integer)
 	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
 	_COST_HIGH;
 
--- PostGIS equivalent function: relate(geom1 geometry, geom2 geometry,text)
-CREATE OR REPLACE FUNCTION ST_Relate(geom1 geometry, geom2 geometry,text)
+-- PostGIS equivalent function: relate(geom1 geometry, geom2 geometry, text)
+CREATE OR REPLACE FUNCTION ST_Relate(geom1 geometry, geom2 geometry, text)
 	RETURNS boolean
 	AS 'MODULE_PATHNAME','relate_pattern'
 	LANGUAGE 'c' IMMUTABLE STRICT PARALLEL SAFE
diff --git a/raster/rt_core/rt_wkb.c b/raster/rt_core/rt_wkb.c
index 7fcf3ed3c..84b7ed55b 100644
--- a/raster/rt_core/rt_wkb.c
+++ b/raster/rt_core/rt_wkb.c
@@ -353,7 +353,7 @@ rt_raster_from_wkb(const uint8_t* wkb, uint32_t wkbsize) {
 	if (!rast->numBands) {
 		/* Here ptr should have been left to right after last used byte */
 		if (ptr < wkbend) {
-			rtwarn("%" PRIu64 " bytes of WKB remained unparsed", wkbend - ptr);
+			rtwarn("%zu bytes of WKB remained unparsed", wkbend - ptr);
 		}
 		else if (ptr > wkbend) {
 			/* Easier to get a segfault before I guess */
diff --git a/regress/core/relate.sql b/regress/core/relate.sql
index 07e2233db..a7172a40d 100644
--- a/regress/core/relate.sql
+++ b/regress/core/relate.sql
@@ -23,8 +23,8 @@ SELECT '22', ST_Relate('LINESTRING(100 140,100 40)','MULTIPOLYGON(((20 80,180 79
 SELECT '23', ST_Relate('LINESTRING(100 140,100 40)','MULTIPOLYGON(((20 80,180 80,100 0,20 80)),((20 160,180 160,100 80,20 160)))');
 SELECT '24', ST_Relate('LINESTRING(110 60,20 150,200 150,110 60)','POLYGON((20 20,200 20,110 110,20 20))');
 SELECT '25', ST_Relate('LINESTRING(0 0,0 50,50 50,50 0,0 0)','MULTILINESTRING((0 0,0 50),(0 50,50 50),(50 50,50 0),(50 0,0 0))');
-SELECT '26', ST_Relate('LINESTRING(40 180,140 180)','MULTIPOLYGON(((20 320,180 320,180 180,20 180,20 320)),((20 180,20 80,180 80,180 180,20 180)))');
-SELECT '27', ST_Relate('LINESTRING(40 180,140 180)','MULTIPOLYGON(((20 320,180 320,180 180,20 180,20 320)),((60 180,60 80,180 80,180 180,60 180)))');
+SELECT '26', ST_Relate('LINESTRING(40 180,140 180)','GEOMETRYCOLLECTION(POLYGON((20 320,180 320,180 180,20 180,20 320)),POLYGON((20 180,20 80,180 80,180 180,20 180)))');
+SELECT '27', ST_Relate('LINESTRING(40 180,140 180)','GEOMETRYCOLLECTION(POLYGON((20 320,180 320,180 180,20 180,20 320)),POLYGON((60 180,60 80,180 80,180 180,60 180)))');
 SELECT '28', ST_Relate('LINESTRING(0 0,60 0,60 60,60 0,120 0)','MULTILINESTRING((0 0,60 0),(60 0,120 0),(60 0,60 60))');
 SELECT '29', ST_Relate('LINESTRING(60 0,20 80,100 80,80 120,40 140)','LINESTRING(140 300,220 160,260 200,240 260)');
 SELECT '30', ST_Relate('LINESTRING(60 0,20 80,100 80,80 120,40 140)','LINESTRING(60 40,140 40,140 160,0 160)');
@@ -253,8 +253,8 @@ SELECT '252', ST_Relate('MULTILINESTRING((20 110,200 110,200 160),(110 110,200 1
 SELECT '253', ST_Relate('MULTILINESTRING((20 160,70 110,150 110,200 160),(110 110,20 110,50 80,70 110,200 110))','MULTIPOLYGON(((110 110,20 20,200 20,110 110)),((110 110,20 200,200 200,110 110)))');
 SELECT '254', ST_Relate('MULTILINESTRING((20 110,200 110),(110 110,20 170,20 130,200 90))','MULTIPOLYGON(((110 110,20 20,200 20,110 110)),((110 110,20 200,200 200,110 110)))');
 SELECT '255', ST_Relate('LINESTRING(0 0,0 50,50 50,50 0,0 0)','MULTILINESTRING((0 0,0 50),(0 50,50 50),(50 50,50 0),(50 0,0 0))');
-SELECT '256', ST_Relate('LINESTRING(40 180,140 180)','MULTIPOLYGON(((20 320,180 320,180 180,20 180,20 320)),((20 180,20 80,180 80,180 180,20 180)))');
-SELECT '257', ST_Relate('LINESTRING(40 180,140 180)','MULTIPOLYGON(((20 320,180 320,180 180,20 180,20 320)),((60 180,60 80,180 80,180 180,60 180)))');
+SELECT '256', ST_Relate('LINESTRING(40 180,140 180)','GEOMETRYCOLLECTION(POLYGON((20 320,180 320,180 180,20 180,20 320)),POLYGON((20 180,20 80,180 80,180 180,20 180)))');
+SELECT '257', ST_Relate('LINESTRING(40 180,140 180)','GEOMETRYCOLLECTION(POLYGON((20 320,180 320,180 180,20 180,20 320)),POLYGON((60 180,60 80,180 80,180 180,60 180)))');
 SELECT '258', ST_Relate('LINESTRING(0 0,60 0,60 60,60 0,120 0)','MULTILINESTRING((0 0,60 0),(60 0,120 0),(60 0,60 60))');
 SELECT '259', ST_Relate('LINESTRING(40 40,120 120)','LINESTRING(40 40,60 120)');
 SELECT '260', ST_Relate('LINESTRING(40 40,120 120)','LINESTRING(60 240,40 40)');
diff --git a/regress/core/tests.mk.in b/regress/core/tests.mk.in
index d2dfe71a7..3dd65b92f 100644
--- a/regress/core/tests.mk.in
+++ b/regress/core/tests.mk.in
@@ -132,7 +132,6 @@ TESTS += \
 	$(top_srcdir)/regress/core/unaryunion \
 	$(top_srcdir)/regress/core/union \
 	$(top_srcdir)/regress/core/clean \
-	$(top_srcdir)/regress/core/relate_bnr \
 	$(top_srcdir)/regress/core/delaunaytriangles \
 	$(top_srcdir)/regress/core/clipbybox2d \
 	$(top_srcdir)/regress/core/subdivide \
@@ -178,6 +177,11 @@ ifeq ($(shell expr "$(POSTGIS_GEOS_VERSION)" ">=" 31200),1)
 		$(top_srcdir)/regress/core/coverage
 endif
 
+ifeq ($(shell expr "$(POSTGIS_GEOS_VERSION)" "<=" 31200),1)
+	TESTS += \
+		$(top_srcdir)/regress/core/relate_bnr
+endif
+
 ifeq ($(INTERRUPTTESTS),yes)
 	# Allow CI servers to configure --with-interrupt-tests
 	TESTS += \

-----------------------------------------------------------------------

Summary of changes:
 NEWS                             |  15 +-
 liblwgeom/gbox.c                 |  12 +
 liblwgeom/liblwgeom.h.in         |   5 +
 liblwgeom/lwgeom_geos.h          |   2 -
 postgis/Makefile.in              |   1 +
 postgis/lwgeom_geos.c            | 855 +--------------------------------
 postgis/lwgeom_geos_predicates.c | 993 +++++++++++++++++++++++++++++++++++++++
 postgis/postgis.sql.in           |   4 +-
 raster/rt_core/rt_wkb.c          |   2 +-
 regress/core/relate.sql          |   8 +-
 regress/core/tests.mk.in         |   6 +-
 11 files changed, 1042 insertions(+), 861 deletions(-)
 create mode 100644 postgis/lwgeom_geos_predicates.c


hooks/post-receive
-- 
PostGIS


More information about the postgis-tickets mailing list