From: Paul Ramsey Date: Fri, 10 Oct 2008 04:39:54 +0000 (+0000) Subject: Prepared geometries getting closer to readiness. Integrated into standard functions... X-Git-Tag: 1.4.0b1~639 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f2a42d94cab614ce05c23b1c833586cf1e9b44d3;p=postgis Prepared geometries getting closer to readiness. Integrated into standard functions, regression tests added. git-svn-id: http://svn.osgeo.org/postgis/trunk@3085 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/lwgeom/Makefile.in b/lwgeom/Makefile.in index c13a7478f..683f017a6 100644 --- a/lwgeom/Makefile.in +++ b/lwgeom/Makefile.in @@ -38,6 +38,7 @@ PG_OBJS=lwgeom_pg.o \ lwgeom_box2dfloat4.o \ lwgeom_chip.o \ lwgeom_geos_c.o \ + lwgeom_geos_prepared.o \ lwgeom_svg.o \ lwgeom_gml.o \ lwgeom_kml.o \ diff --git a/lwgeom/lwgeom_geos.h b/lwgeom/lwgeom_geos.h new file mode 100644 index 000000000..39a367dd4 --- /dev/null +++ b/lwgeom/lwgeom_geos.h @@ -0,0 +1,45 @@ +/********************************************************************** + * $Id$ + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.refractions.net + * Copyright 2008 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include + +#include "../postgis_config.h" + +#include "postgres.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "fmgr.h" +#include "miscadmin.h" + +#include "geos_c.h" + +#include "lwgeom_pg.h" +#include "liblwgeom.h" +#include "profile.h" + +#ifdef PROFILE +#warning PROFILE enabled! +#endif + +/* +** Public prototypes for GEOS utility functions. +*/ + +PG_LWGEOM *GEOS2POSTGIS(GEOSGeom geom, char want3d); +GEOSGeom POSTGIS2GEOS(PG_LWGEOM *g); + +LWGEOM *GEOS2LWGEOM(GEOSGeom geom, char want3d); +GEOSGeom LWGEOM2GEOS(LWGEOM *g); + +POINTARRAY *ptarray_from_GEOSCoordSeq(GEOSCoordSeq cs, char want3d); +void errorIfGeometryCollection(PG_LWGEOM *g1, PG_LWGEOM *g2); + diff --git a/lwgeom/lwgeom_geos_c.c b/lwgeom/lwgeom_geos_c.c index e240d182c..b1479d63a 100644 --- a/lwgeom/lwgeom_geos_c.c +++ b/lwgeom/lwgeom_geos_c.c @@ -1,31 +1,20 @@ -#include "../postgis_config.h" - -#include "postgres.h" -#include "utils/array.h" -#include "utils/builtins.h" -#include "fmgr.h" -#include "miscadmin.h" - - -#include "lwgeom_pg.h" -#include "liblwgeom.h" -#include "profile.h" +/********************************************************************** + * $Id$ + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.refractions.net + * Copyright 2001-2003 Refractions Research Inc. + * Copyright 2008 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ -#include "geos_c.h" +#include "lwgeom_geos.h" #include "lwgeom_rtree.h" +#include "lwgeom_geos_prepared.h" -#include - - -#ifdef PROFILE -#warning PROFILE enabled! -#endif - -/* - * Define this to have have many notices printed - * during postgis->geos and geos->postgis conversions - * - */ /* * @@ -52,6 +41,7 @@ Datum intersects(PG_FUNCTION_ARGS); Datum crosses(PG_FUNCTION_ARGS); Datum within(PG_FUNCTION_ARGS); Datum contains(PG_FUNCTION_ARGS); +Datum containsproperly(PG_FUNCTION_ARGS); Datum covers(PG_FUNCTION_ARGS); Datum overlaps(PG_FUNCTION_ARGS); Datum isvalid(PG_FUNCTION_ARGS); @@ -77,14 +67,7 @@ Datum LWGEOM_buildarea(PG_FUNCTION_ARGS); Datum linemerge(PG_FUNCTION_ARGS); Datum coveredby(PG_FUNCTION_ARGS); - -LWGEOM *GEOS2LWGEOM(GEOSGeom geom, char want3d); -PG_LWGEOM *GEOS2POSTGIS(GEOSGeom geom, char want3d); -GEOSGeom POSTGIS2GEOS(PG_LWGEOM *g); -GEOSGeom LWGEOM2GEOS(LWGEOM *g); -POINTARRAY *ptarray_from_GEOSCoordSeq(GEOSCoordSeq cs, char want3d); - -void errorIfGeometryCollection(PG_LWGEOM *g1, PG_LWGEOM *g2); +/* TODO: move these to a lwgeom_functions_analytic.h */ int point_in_polygon_rtree(RTREE_NODE **root, int ringCount, LWPOINT *point); int point_in_multipolygon_rtree(RTREE_NODE **root, int polyCount, int ringCount, LWPOINT *point); int point_in_polygon(LWPOLY *polygon, LWPOINT *point); @@ -92,6 +75,26 @@ int point_in_multipolygon(LWMPOLY *mpolygon, LWPOINT *pont); /* PROTOTYPES end */ +/********************************************************************************* +** +** PreparedGeometry implementations that cache intermediate indexed versions +** of geometry in a special MemoryContext for re-used by future function +** invocations. +** +** By creating a memory context to hold the GEOS PreparedGeometry and Geometry +** and making it a child of the fmgr memory context, we can get the memory held +** by the GEOS objects released when the memory context delete callback is +** invoked by the parent context. +** +*********************************************************************************/ + +#include "utils/memutils.h" +#include "executor/spi.h" +#include "access/hash.h" +#include "utils/hsearch.h" + + + PG_FUNCTION_INFO_V1(postgis_geos_version); Datum postgis_geos_version(PG_FUNCTION_ARGS) { @@ -126,7 +129,7 @@ Datum unite_garray(PG_FUNCTION_ARGS) static int call=1; #endif -#if POSTGIS_DEBUG_LEVEL >= 2 +#if POSTGIS_DEBUG_LEVEL > 0 call++; POSTGIS_DEBUGF(2, "GEOS incremental union (call %d)", call); #endif @@ -1402,11 +1405,11 @@ Datum contains(PG_FUNCTION_ARGS) RTREE_POLY_CACHE *poly_cache; MemoryContext old_context; bool result; - -#ifdef PROFILE - profstart(PROF_QRUN); +#ifdef PREPARED_GEOM + PrepGeomCache *prep_cache; #endif + geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); geom2 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); @@ -1491,31 +1494,26 @@ Datum contains(PG_FUNCTION_ARGS) initGEOS(lwnotice, lwnotice); -#ifdef PROFILE - profstart(PROF_P2G1); -#endif - g1 = POSTGIS2GEOS(geom1); -#ifdef PROFILE - profstop(PROF_P2G1); -#endif -#ifdef PROFILE - profstart(PROF_P2G2); -#endif - g2 = POSTGIS2GEOS(geom2); -#ifdef PROFILE - profstop(PROF_P2G2); -#endif +#ifdef PREPARED_GEOM + prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 ); -#ifdef PROFILE - profstart(PROF_GRUN); -#endif - result = GEOSContains(g1,g2); -#ifdef PROFILE - profstop(PROF_GRUN); + if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 ) + { + g1 = POSTGIS2GEOS(geom2); + POSTGIS_DEBUG(4, "containsPrepared: cache is live, running preparedcontains"); + result = GEOSPreparedContains( prep_cache->prepared_geom, g1); + GEOSGeom_destroy(g1); + } + else #endif - - GEOSGeom_destroy(g1); - GEOSGeom_destroy(g2); + { + g1 = POSTGIS2GEOS(geom1); + g2 = POSTGIS2GEOS(geom2); + POSTGIS_DEBUG(4, "containsPrepared: cache is not ready, running standard contains"); + result = GEOSContains( g1, g2); + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + } if (result == 2) { @@ -1523,10 +1521,69 @@ Datum contains(PG_FUNCTION_ARGS) PG_RETURN_NULL(); /* never get here */ } -#ifdef PROFILE - profstop(PROF_QRUN); - profreport("cont",geom1, geom2, NULL); + PG_FREE_IF_COPY(geom1, 0); + PG_FREE_IF_COPY(geom2, 1); + + PG_RETURN_BOOL(result); + +} + +PG_FUNCTION_INFO_V1(containsproperly); +Datum containsproperly(PG_FUNCTION_ARGS) +{ + PG_LWGEOM * geom1; + PG_LWGEOM * geom2; + bool result; + BOX2DFLOAT4 box1, box2; +#ifdef PREPARED_GEOM + PrepGeomCache * prep_cache; +#endif + + geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + geom2 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + + errorIfGeometryCollection(geom1,geom2); + errorIfSRIDMismatch(pglwgeom_getSRID(geom1), pglwgeom_getSRID(geom2)); + + /* + * short-circuit: if geom2 bounding box is not completely inside + * geom1 bounding box we can prematurely return FALSE. + * Do the test IFF BOUNDING BOX AVAILABLE. + */ + if ( getbox2d_p(SERIALIZED_FORM(geom1), &box1) && + getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) + { + if (( box2.xmin < box1.xmin ) || ( box2.xmax > box1.xmax ) || + ( box2.ymin < box1.ymin ) || ( box2.ymax > box1.ymax )) + PG_RETURN_BOOL(FALSE); + } + + initGEOS(lwnotice, lwnotice); + +#ifdef PREPARED_GEOM + prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 ); + + if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 ) + { + GEOSGeom g = POSTGIS2GEOS(geom2); + result = GEOSPreparedContainsProperly( prep_cache->prepared_geom, g); + GEOSGeom_destroy(g); + } + else #endif + { + GEOSGeom g1 = POSTGIS2GEOS(geom1); + GEOSGeom g2 = POSTGIS2GEOS(geom2); + result = GEOSRelatePattern( g1, g2, "T**FF*FF*" ); + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + } + + if (result == 2) + { + elog(ERROR,"GEOS contains() threw an error!"); + PG_RETURN_NULL(); /* never get here */ + } PG_FREE_IF_COPY(geom1, 0); PG_FREE_IF_COPY(geom2, 1); @@ -1534,7 +1591,6 @@ Datum contains(PG_FUNCTION_ARGS) PG_RETURN_BOOL(result); } - /* * Described at * http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html @@ -1544,19 +1600,15 @@ Datum covers(PG_FUNCTION_ARGS) { PG_LWGEOM *geom1; PG_LWGEOM *geom2; - GEOSGeom g1,g2; bool result; BOX2DFLOAT4 box1, box2; int type1, type2; LWGEOM *lwgeom; - /* LWMPOLY *mpoly; */ LWPOINT *point; RTREE_POLY_CACHE *poly_cache; MemoryContext old_context; - char *patt = "******FF*"; - -#ifdef PROFILE - profstart(PROF_QRUN); +#ifdef PREPARED_GEOM + PrepGeomCache *prep_cache; #endif geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); @@ -1571,7 +1623,7 @@ Datum covers(PG_FUNCTION_ARGS) * Do the test IFF BOUNDING BOX AVAILABLE. */ if ( getbox2d_p(SERIALIZED_FORM(geom1), &box1) && - getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) + getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) { if ( box2.xmin < box1.xmin ) PG_RETURN_BOOL(FALSE); if ( box2.xmax > box1.xmax ) PG_RETURN_BOOL(FALSE); @@ -1641,31 +1693,24 @@ Datum covers(PG_FUNCTION_ARGS) initGEOS(lwnotice, lwnotice); -#ifdef PROFILE - profstart(PROF_P2G1); -#endif - g1 = POSTGIS2GEOS(geom1); -#ifdef PROFILE - profstop(PROF_P2G1); -#endif -#ifdef PROFILE - profstart(PROF_P2G2); -#endif - g2 = POSTGIS2GEOS(geom2); -#ifdef PROFILE - profstop(PROF_P2G2); -#endif + prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 ); -#ifdef PROFILE - profstart(PROF_GRUN); -#endif - result = GEOSRelatePattern(g1,g2,patt); -#ifdef PROFILE - profstop(PROF_GRUN); +#ifdef PREPARED_GEOM + if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 ) + { + GEOSGeom g1 = POSTGIS2GEOS(geom2); + result = GEOSPreparedCovers( prep_cache->prepared_geom, g1); + GEOSGeom_destroy(g1); + } + else #endif - - GEOSGeom_destroy(g1); - GEOSGeom_destroy(g2); + { + GEOSGeom g1 = POSTGIS2GEOS(geom1); + GEOSGeom g2 = POSTGIS2GEOS(geom2); + result = GEOSRelatePattern( g1, g2, "******FF*" ); + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + } if (result == 2) { @@ -1673,17 +1718,15 @@ Datum covers(PG_FUNCTION_ARGS) PG_RETURN_NULL(); /* never get here */ } -#ifdef PROFILE - profstop(PROF_QRUN); - profreport("geos",geom1, geom2, NULL); -#endif - PG_FREE_IF_COPY(geom1, 0); PG_FREE_IF_COPY(geom2, 1); PG_RETURN_BOOL(result); + } + + PG_FUNCTION_INFO_V1(within); Datum within(PG_FUNCTION_ARGS) { @@ -2053,7 +2096,6 @@ Datum intersects(PG_FUNCTION_ARGS) PG_LWGEOM *geom1; PG_LWGEOM *geom2; uchar *serialized_poly; - GEOSGeom g1,g2; bool result; BOX2DFLOAT4 box1, box2; int type1, type2, polytype; @@ -2061,9 +2103,8 @@ Datum intersects(PG_FUNCTION_ARGS) LWGEOM *lwgeom; MemoryContext old_context; RTREE_POLY_CACHE *poly_cache; - -#ifdef PROFILE - profstart(PROF_QRUN); +#ifdef PREPARED_GEOM + PrepGeomCache *prep_cache; #endif geom1 = (PG_LWGEOM *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); @@ -2154,42 +2195,40 @@ Datum intersects(PG_FUNCTION_ARGS) } initGEOS(lwnotice, lwnotice); +#ifdef PREPARED_GEOM + prep_cache = GetPrepGeomCache( fcinfo, geom1, geom2 ); -#ifdef PROFILE - profstart(PROF_P2G1); -#endif - g1 = POSTGIS2GEOS(geom1 ); -#ifdef PROFILE - profstop(PROF_P2G1); -#endif -#ifdef PROFILE - profstart(PROF_P2G2); -#endif - g2 = POSTGIS2GEOS(geom2 ); -#ifdef PROFILE - profstop(PROF_P2G2); + if ( prep_cache && prep_cache->prepared_geom ) + { + if ( prep_cache->argnum == 1 ) + { + GEOSGeom g = POSTGIS2GEOS(geom2); + result = GEOSPreparedIntersects( prep_cache->prepared_geom, g); + GEOSGeom_destroy(g); + } + else + { + GEOSGeom g = POSTGIS2GEOS(geom1); + result = GEOSPreparedIntersects( prep_cache->prepared_geom, g); + GEOSGeom_destroy(g); + } + } + else #endif + { + GEOSGeom g1 = POSTGIS2GEOS(geom1); + GEOSGeom g2 = POSTGIS2GEOS(geom2); + result = GEOSIntersects( g1, g2); + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + } -#ifdef PROFILE - profstart(PROF_GRUN); -#endif - result = GEOSIntersects(g1,g2); -#ifdef PROFILE - profstop(PROF_GRUN); -#endif - GEOSGeom_destroy(g1); - GEOSGeom_destroy(g2); if (result == 2) { elog(ERROR,"GEOS intersects() threw an error!"); PG_RETURN_NULL(); /* never get here */ } -#ifdef PROFILE - profstop(PROF_QRUN); - profreport("intr",geom1, geom2, NULL); -#endif - PG_FREE_IF_COPY(geom1, 0); PG_FREE_IF_COPY(geom2, 1); @@ -3517,734 +3556,3 @@ Datum LWGEOM_buildarea(PG_FUNCTION_ARGS) } -/********************************************************************************* -** -** PreparedGeometry implementations that cache intermediate indexed versions -** of geometry in a special MemoryContext for re-used by future function -** invocations. -** -** By creating a memory context to hold the GEOS PreparedGeometry and Geometry -** and making it a child of the fmgr memory context, we can get the memory held -** by the GEOS objects released when the memory context delete callback is -** invoked by the parent context. -** -*********************************************************************************/ - -#include "utils/memutils.h" -#include "executor/spi.h" -#include "access/hash.h" -#include "utils/hsearch.h" - -/* -** GEOS prepared geometry is only available from GEOS 3.1 onwards -*/ -#if POSTGIS_GEOS_VERSION >= 31 -#define PREPARED_GEOM 1 -#warning COMPILING PREPARED GEOMETRY -#endif - -/* Prepared geometry function prototypes */ -Datum containsPrepared(PG_FUNCTION_ARGS); -Datum containsProperlyPrepared(PG_FUNCTION_ARGS); -Datum coversPrepared(PG_FUNCTION_ARGS); -Datum intersectsPrepared(PG_FUNCTION_ARGS); - -/* -** Cache structure. Keys are unique for a unique geometry, usually the row -** primary key is passed in. This avoid the need to do a memcmp to test -** for geometry equality at each function invocation. The argnum gives -** the number of function arguments we are caching. Intersects requires that -** both arguments be checked for cacheability, while Contains only requires -** that the containing argument be checked. Both the Geometry and the -** PreparedGeometry have to be cached, because the PreparedGeometry -** contains a reference to the geometry. -*/ -#ifdef PREPARED_GEOM -typedef struct -{ - PG_LWGEOM* pg_geom1; - PG_LWGEOM* pg_geom2; - size_t pg_geom1_size; - size_t pg_geom2_size; - int32 argnum; - const GEOSPreparedGeometry* prepared_geom; - const GEOSGeometry* geom; - MemoryContext context; -} PrepGeomCache; - -/* -** Backend prepared hash table -** -** The memory context call-backs use a MemoryContext as the parameter -** so we need to map that over to actual references to GEOS objects to -** delete. -** -** This hash table stores a key/value pair of MemoryContext/Geom* objects. -*/ -static HTAB* PrepGeomHash = NULL; - -#define PREPARED_BACKEND_HASH_SIZE 32 - -typedef struct -{ - MemoryContext context; - const GEOSPreparedGeometry* prepared_geom; - const GEOSGeometry* geom; -} PrepGeomHashEntry; - -/* Utility function to pull or prepare the current cache */ -PrepGeomCache *GetPrepGeomCache(FunctionCallInfoData *fcinfo, PG_LWGEOM *pg_geom1, PG_LWGEOM *pg_geom2); - -/* Memory context hash table function prototypes */ -uint32 mcxt_ptr_hasha(const void *key, Size keysize); -static void CreatePrepGeomHash(void); -static void AddPrepGeomHashEntry(PrepGeomHashEntry pghe); -static PrepGeomHashEntry *GetPrepGeomHashEntry(MemoryContext mcxt); -static void DeletePrepGeomHashEntry(MemoryContext mcxt); - - -/* Memory context cache function prototypes */ -static void PreparedCacheInit(MemoryContext context); -static void PreparedCacheReset(MemoryContext context); -static void PreparedCacheDelete(MemoryContext context); -static bool PreparedCacheIsEmpty(MemoryContext context); -static void PreparedCacheStats(MemoryContext context, int level); -#ifdef MEMORY_CONTEXT_CHECKING -static void PreparedCacheCheck(MemoryContext context); -#endif - -/* Memory context definition must match the current version of PostgreSQL */ -static MemoryContextMethods PreparedCacheContextMethods = { - NULL, - NULL, - NULL, - PreparedCacheInit, - PreparedCacheReset, - PreparedCacheDelete, - NULL, - PreparedCacheIsEmpty, - PreparedCacheStats -#ifdef MEMORY_CONTEXT_CHECKING - , PreparedCacheCheck -#endif -}; - -static void -PreparedCacheInit(MemoryContext context) -{ - /* - * Do nothing as the cache is initialised when the transform() - * function is first called - */ -} - -static void -PreparedCacheDelete(MemoryContext context) -{ - PrepGeomHashEntry* pghe; - - /* Lookup the hash entry pointer in the global hash table so we can free it */ - pghe = GetPrepGeomHashEntry(context); - - if (!pghe) - elog(ERROR, "PreparedCacheDelete: Trying to delete non-existant hash entry object with MemoryContext key (%p)", (void *)context); - - POSTGIS_DEBUGF(3, "deleting geom object (%p) and prepared geom object (%p) with MemoryContext key (%p)", pghe->geom, pghe->prepared_geom, context); - - /* Free them */ - if( pghe->prepared_geom ) - GEOSPreparedGeom_destroy( pghe->prepared_geom ); - if( pghe->geom ) - GEOSGeom_destroy( pghe->geom ); - - /* Remove the hash entry as it is no longer needed */ - DeletePrepGeomHashEntry(context); -} - -static void -PreparedCacheReset(MemoryContext context) -{ - /* - * Do nothing, but we must supply a function since this call is mandatory according to tgl - * (see postgis-devel archives July 2007) - */ -} - -static bool -PreparedCacheIsEmpty(MemoryContext context) -{ - /* - * Always return false since this call is mandatory according to tgl - * (see postgis-devel archives July 2007) - */ - return FALSE; -} - -static void -PreparedCacheStats(MemoryContext context, int level) -{ - /* - * Simple stats display function - we must supply a function since this call is mandatory according to tgl - * (see postgis-devel archives July 2007) - */ - - fprintf(stderr, "%s: Prepared context\n", context->name); -} - -#ifdef MEMORY_CONTEXT_CHECKING -static void -PreparedCacheCheck(MemoryContext context) -{ - /* - * Do nothing - stub required for when PostgreSQL is compiled - * with MEMORY_CONTEXT_CHECKING defined - */ -} -#endif - -/* TODO: put this in common are for both transform and prepared -** mcxt_ptr_hash -** Build a key from a pointer and a size value. -*/ -uint32 -mcxt_ptr_hasha(const void *key, Size keysize) -{ - uint32 hashval; - - hashval = DatumGetUInt32(hash_any(key, keysize)); - - return hashval; -} - -static void -CreatePrepGeomHash(void) -{ - HASHCTL ctl; - - ctl.keysize = sizeof(MemoryContext); - ctl.entrysize = sizeof(PrepGeomHashEntry); - ctl.hash = mcxt_ptr_hasha; - - PrepGeomHash = hash_create("PostGIS Prepared Geometry Backend MemoryContext Hash", PREPARED_BACKEND_HASH_SIZE, &ctl, (HASH_ELEM | HASH_FUNCTION)); -} - -static void -AddPrepGeomHashEntry(PrepGeomHashEntry pghe) -{ - bool found; - void **key; - PrepGeomHashEntry *he; - - /* The hash key is the MemoryContext pointer */ - key = (void *)&(pghe.context); - - he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_ENTER, &found); - if (!found) - { - /* Insert the entry into the new hash element */ - he->context = pghe.context; - he->geom = pghe.geom; - he->prepared_geom = pghe.prepared_geom; - } - else - { - elog(ERROR, "AddPrepGeomHashEntry: This memory context is already in use! (%p)", (void *)pghe.context); - } -} - -static PrepGeomHashEntry* -GetPrepGeomHashEntry(MemoryContext mcxt) -{ - void **key; - PrepGeomHashEntry *he; - - /* The hash key is the MemoryContext pointer */ - key = (void *)&mcxt; - - /* Return the projection object from the hash */ - he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_FIND, NULL); - - return he; -} - - -static void -DeletePrepGeomHashEntry(MemoryContext mcxt) -{ - void **key; - PrepGeomHashEntry *he; - - /* The hash key is the MemoryContext pointer */ - key = (void *)&mcxt; - - /* Delete the projection object from the hash */ - he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_REMOVE, NULL); - - he->prepared_geom = NULL; - he->geom = NULL; - - if (!he) - elog(ERROR, "DeletePrepGeomHashEntry: There was an error removing the geometry object from this MemoryContext (%p)", (void *)mcxt); -} - -/* -** GetPrepGeomCache -** -** Pull the current prepared geometry from the cache or make -** one if there is not one available. Only prepare geometry -** if we are seeing a key for the second time. That way rapidly -** cycling keys don't cause too much preparing. -** -*/ -PrepGeomCache* -GetPrepGeomCache(FunctionCallInfoData *fcinfo, PG_LWGEOM *pg_geom1, PG_LWGEOM *pg_geom2) -{ - MemoryContext old_context; - PrepGeomCache* cache = fcinfo->flinfo->fn_extra; - int copy_keys = 1; - size_t pg_geom1_size = 0; - size_t pg_geom2_size = 0; - - if (!PrepGeomHash) - CreatePrepGeomHash(); - - if( pg_geom1 ) - pg_geom1_size = VARSIZE(pg_geom1) + VARHDRSZ; - - if( pg_geom2 ) - pg_geom2_size = VARSIZE(pg_geom2) + VARHDRSZ; - - if ( cache == NULL) - { - /* - ** Cache hit, but the cache isn't set up yet. - ** Set it up, but don't prepare the geometry yet. - */ - PrepGeomHashEntry pghe; - - old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); - cache = palloc(sizeof(PrepGeomCache)); - MemoryContextSwitchTo(old_context); - - cache->prepared_geom = 0; - cache->geom = 0; - cache->argnum = 0; - cache->pg_geom1 = 0; - cache->pg_geom2 = 0; - cache->pg_geom1_size = 0; - cache->pg_geom2_size = 0; - cache->context = MemoryContextCreate(T_AllocSetContext, 8192, - &PreparedCacheContextMethods, - fcinfo->flinfo->fn_mcxt, - "PostGIS Prepared Geometry Context"); - - POSTGIS_DEBUGF(1, "GetPrepGeomCache: creating cache: %p", cache); - - pghe.context = cache->context; - pghe.geom = 0; - pghe.prepared_geom = 0; - AddPrepGeomHashEntry( pghe ); - - fcinfo->flinfo->fn_extra = cache; - - POSTGIS_DEBUGF(3, "GetPrepGeomCache: adding context to hash: %p", cache); - } - else if ( pg_geom1 && - cache->argnum != 2 && - cache->pg_geom1_size == pg_geom1_size && - memcmp(cache->pg_geom1, pg_geom1, pg_geom1_size) == 0) - { - if ( !cache->prepared_geom ) - { - /* - ** Cache hit, but we haven't prepared our geometry yet. - ** Prepare it. - */ - PrepGeomHashEntry* pghe; - - GEOSGeom g = POSTGIS2GEOS( pg_geom1 ); - cache->geom = g; - cache->prepared_geom = GEOSPrepare( g ); - cache->argnum = 1; - POSTGIS_DEBUG(1, "GetPrepGeomCache: preparing obj in argument 1"); - - pghe = GetPrepGeomHashEntry(cache->context); - pghe->geom = cache->geom; - pghe->prepared_geom = cache->prepared_geom; - POSTGIS_DEBUG(3, "GetPrepGeomCache: storing references to prepared obj in argument 1"); - } - else - { - /* - ** Cache hit, and we're good to go. Do nothing. - */ - POSTGIS_DEBUG(1, "GetPrepGeomCache: cache hit, argument 1"); - } - /* we don't need new keys until we have a cache miss */ - copy_keys = 0; - } - else if ( pg_geom2 && - cache->argnum != 1 && - cache->pg_geom2_size == pg_geom2_size && - memcmp(cache->pg_geom2, pg_geom2, pg_geom2_size) == 0) - { - if ( !cache->prepared_geom ) - { - /* - ** Cache hit on arg2, but we haven't prepared our geometry yet. - ** Prepare it. - */ - PrepGeomHashEntry* pghe; - GEOSGeom g = POSTGIS2GEOS( pg_geom2 ); - cache->geom = g; - cache->prepared_geom = GEOSPrepare( g ); - cache->argnum = 2; - POSTGIS_DEBUG(1, "GetPrepGeomCache: preparing obj in argument 2"); - - pghe = GetPrepGeomHashEntry(cache->context); - pghe->geom = cache->geom; - pghe->prepared_geom = cache->prepared_geom; - POSTGIS_DEBUG(3, "GetPrepGeomCache: storing references to prepared obj in argument 2"); - } - else - { - /* - ** Cache hit, and we're good to go. Do nothing. - */ - POSTGIS_DEBUG(1, "GetPrepGeomCache: cache hit, argument 2"); - } - /* we don't need new keys until we have a cache miss */ - copy_keys = 0; - } - else if ( cache->prepared_geom ) - { - /* - ** No cache hits, so this must be a miss. - ** Destroy the GEOS objects, empty the cache. - */ - PrepGeomHashEntry* pghe; - - pghe = GetPrepGeomHashEntry(cache->context); - pghe->geom = 0; - pghe->prepared_geom = 0; - - POSTGIS_DEBUGF(1, "GetPrepGeomCache: cache miss, argument %d", cache->argnum); - GEOSPreparedGeom_destroy( cache->prepared_geom ); - GEOSGeom_destroy( cache->geom ); - - cache->prepared_geom = 0; - cache->geom = 0; - cache->argnum = 0; - - } - - if( copy_keys && pg_geom1 ) - { - POSTGIS_DEBUG(1, "GetPrepGeomCache: copying pg_geom1 into cache"); - old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); - if( cache->pg_geom1 ) - pfree(cache->pg_geom1); - cache->pg_geom1 = palloc(pg_geom1_size); - MemoryContextSwitchTo(old_context); - memcpy(cache->pg_geom1, pg_geom1, pg_geom1_size); - cache->pg_geom1_size = pg_geom1_size; - } - if( copy_keys && pg_geom2 ) - { - POSTGIS_DEBUG(1, "GetPrepGeomCache: copying pg_geom2 into cache"); - old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); - if( cache->pg_geom2 ) - pfree(cache->pg_geom2); - cache->pg_geom2 = palloc(pg_geom2_size); - MemoryContextSwitchTo(old_context); - memcpy(cache->pg_geom2, pg_geom2, pg_geom2_size); - cache->pg_geom2_size = pg_geom2_size; - } - - return cache; - - /* select sum(v.gid) from vada2005 v, ed2000 e where st_contains(e.the_geom, v.the_geom, e.gid) and e.id like 'PR%' */ - -} -#endif /* PREPARED_GEOM */ - - -PG_FUNCTION_INFO_V1(containsPrepared); -Datum containsPrepared(PG_FUNCTION_ARGS) -{ -#ifndef PREPARED_GEOM - elog(ERROR,"Not implemented in this version!"); - PG_RETURN_NULL(); -#else - PG_LWGEOM * geom1; - PG_LWGEOM * geom2; - bool result; - BOX2DFLOAT4 box1, box2; - PrepGeomCache * prep_cache; - int32 key1; - - geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - geom2 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); - key1 = PG_GETARG_INT32(2); - - errorIfGeometryCollection(geom1,geom2); - errorIfSRIDMismatch(pglwgeom_getSRID(geom1), pglwgeom_getSRID(geom2)); - - POSTGIS_DEBUG(3, "containsPrepared: entered function"); - - /* - * short-circuit: if geom2 bounding box is not completely inside - * geom1 bounding box we can prematurely return FALSE. - * Do the test IFF BOUNDING BOX AVAILABLE. - */ - if ( getbox2d_p(SERIALIZED_FORM(geom1), &box1) && - getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) - { - if (( box2.xmin < box1.xmin ) || ( box2.xmax > box1.xmax ) || - ( box2.ymin < box1.ymin ) || ( box2.ymax > box1.ymax )) - PG_RETURN_BOOL(FALSE); - } - - prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 ); - - initGEOS(lwnotice, lwnotice); - - if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 ) - { - GEOSGeom g = POSTGIS2GEOS(geom2); - POSTGIS_DEBUG(4, "containsPrepared: cache is live, running preparedcontains"); - result = GEOSPreparedContains( prep_cache->prepared_geom, g); - GEOSGeom_destroy(g); - } - else - { - GEOSGeom g1 = POSTGIS2GEOS(geom1); - GEOSGeom g2 = POSTGIS2GEOS(geom2); - POSTGIS_DEBUG(4, "containsPrepared: cache is not ready, running standard contains"); - result = GEOSContains( g1, g2); - GEOSGeom_destroy(g1); - GEOSGeom_destroy(g2); - } - - if (result == 2) - { - elog(ERROR,"GEOS contains() threw an error!"); - PG_RETURN_NULL(); /* never get here */ - } - - PG_FREE_IF_COPY(geom1, 0); - PG_FREE_IF_COPY(geom2, 1); - - PG_RETURN_BOOL(result); -#endif /* PREPARED_GEOM */ -} - -PG_FUNCTION_INFO_V1(containsProperlyPrepared); -Datum containsProperlyPrepared(PG_FUNCTION_ARGS) -{ -#ifndef PREPARED_GEOM - elog(ERROR,"Not implemented in this version!"); - PG_RETURN_NULL(); /* never get here */ -#else - PG_LWGEOM * geom1; - PG_LWGEOM * geom2; - bool result; - BOX2DFLOAT4 box1, box2; - PrepGeomCache * prep_cache; - int32 key1; - - geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - geom2 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); - key1 = PG_GETARG_INT32(2); - - errorIfGeometryCollection(geom1,geom2); - errorIfSRIDMismatch(pglwgeom_getSRID(geom1), pglwgeom_getSRID(geom2)); - - /* - * short-circuit: if geom2 bounding box is not completely inside - * geom1 bounding box we can prematurely return FALSE. - * Do the test IFF BOUNDING BOX AVAILABLE. - */ - if ( getbox2d_p(SERIALIZED_FORM(geom1), &box1) && - getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) - { - if (( box2.xmin < box1.xmin ) || ( box2.xmax > box1.xmax ) || - ( box2.ymin < box1.ymin ) || ( box2.ymax > box1.ymax )) - PG_RETURN_BOOL(FALSE); - } - - prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 ); - - initGEOS(lwnotice, lwnotice); - - if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 ) - { - GEOSGeom g = POSTGIS2GEOS(geom2); - result = GEOSPreparedContainsProperly( prep_cache->prepared_geom, g); - GEOSGeom_destroy(g); - } - else - { - GEOSGeom g1 = POSTGIS2GEOS(geom1); - GEOSGeom g2 = POSTGIS2GEOS(geom2); - result = GEOSRelatePattern( g1, g2, "T**FF*FF*" ); - GEOSGeom_destroy(g1); - GEOSGeom_destroy(g2); - } - - if (result == 2) - { - elog(ERROR,"GEOS contains() threw an error!"); - PG_RETURN_NULL(); /* never get here */ - } - - PG_FREE_IF_COPY(geom1, 0); - PG_FREE_IF_COPY(geom2, 1); - - PG_RETURN_BOOL(result); -#endif /* PREPARED_GEOM */ -} - -PG_FUNCTION_INFO_V1(coversPrepared); -Datum coversPrepared(PG_FUNCTION_ARGS) -{ -#ifndef PREPARED_GEOM - elog(ERROR,"Not implemented in this version!"); - PG_RETURN_NULL(); /* never get here */ -#else - PG_LWGEOM * geom1; - PG_LWGEOM * geom2; - bool result; - BOX2DFLOAT4 box1, box2; - PrepGeomCache * prep_cache; - int32 key1; - - geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - geom2 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); - key1 = PG_GETARG_INT32(2); - - errorIfGeometryCollection(geom1,geom2); - errorIfSRIDMismatch(pglwgeom_getSRID(geom1), pglwgeom_getSRID(geom2)); - - /* - * short-circuit: if geom2 bounding box is not completely inside - * geom1 bounding box we can prematurely return FALSE. - * Do the test IFF BOUNDING BOX AVAILABLE. - */ - if ( getbox2d_p(SERIALIZED_FORM(geom1), &box1) && - getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) - { - if (( box2.xmin < box1.xmin ) || ( box2.xmax > box1.xmax ) || - ( box2.ymin < box1.ymin ) || ( box2.ymax > box1.ymax )) - PG_RETURN_BOOL(FALSE); - } - - prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 ); - - initGEOS(lwnotice, lwnotice); - - if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 ) - { - GEOSGeom g = POSTGIS2GEOS(geom2); - result = GEOSPreparedCovers( prep_cache->prepared_geom, g); - GEOSGeom_destroy(g); - } - else - { - GEOSGeom g1 = POSTGIS2GEOS(geom1); - GEOSGeom g2 = POSTGIS2GEOS(geom2); - result = GEOSRelatePattern( g1, g2, "******FF*" ); - GEOSGeom_destroy(g1); - GEOSGeom_destroy(g2); - } - - if (result == 2) - { - elog(ERROR,"GEOS contains() threw an error!"); - PG_RETURN_NULL(); /* never get here */ - } - - PG_FREE_IF_COPY(geom1, 0); - PG_FREE_IF_COPY(geom2, 1); - - PG_RETURN_BOOL(result); -#endif /* PREPARED_GEOM */ -} - - -PG_FUNCTION_INFO_V1(intersectsPrepared); -Datum intersectsPrepared(PG_FUNCTION_ARGS) -{ -#ifndef PREPARED_GEOM - elog(ERROR,"Not implemented in this version!"); - PG_RETURN_NULL(); /* never get here */ -#else - PG_LWGEOM * geom1; - PG_LWGEOM * geom2; - bool result; - BOX2DFLOAT4 box1, box2; - PrepGeomCache * prep_cache; - int32 key1, key2; - - geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); - geom2 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); - key1 = PG_GETARG_INT32(2); - key2 = PG_GETARG_INT32(3); - - errorIfGeometryCollection(geom1,geom2); - errorIfSRIDMismatch(pglwgeom_getSRID(geom1), pglwgeom_getSRID(geom2)); - - /* - * short-circuit 1: if geom2 bounding box does not overlap - * geom1 bounding box we can prematurely return FALSE. - * Do the test IFF BOUNDING BOX AVAILABLE. - */ - if ( getbox2d_p(SERIALIZED_FORM(geom1), &box1) && - getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) - { - if (( box2.xmax < box1.xmin ) || ( box2.xmin > box1.xmax ) || - ( box2.ymax < box1.ymin ) || ( box2.ymin > box1.ymax )) - PG_RETURN_BOOL(FALSE); - } - - prep_cache = GetPrepGeomCache( fcinfo, geom1, geom2 ); - - initGEOS(lwnotice, lwnotice); - - if ( prep_cache && prep_cache->prepared_geom ) - { - if ( prep_cache->argnum == 1 ) - { - GEOSGeom g = POSTGIS2GEOS(geom2); - result = GEOSPreparedIntersects( prep_cache->prepared_geom, g); - GEOSGeom_destroy(g); - } - else - { - GEOSGeom g = POSTGIS2GEOS(geom1); - result = GEOSPreparedIntersects( prep_cache->prepared_geom, g); - GEOSGeom_destroy(g); - } - } - else - { - GEOSGeom g1 = POSTGIS2GEOS(geom1); - GEOSGeom g2 = POSTGIS2GEOS(geom2); - result = GEOSIntersects( g1, g2); - GEOSGeom_destroy(g1); - GEOSGeom_destroy(g2); - } - - if (result == 2) - { - elog(ERROR,"GEOS contains() threw an error!"); - PG_RETURN_NULL(); /* never get here */ - } - - PG_FREE_IF_COPY(geom1, 0); - PG_FREE_IF_COPY(geom2, 1); - - PG_RETURN_BOOL(result); -#endif /* PREPARED_GEOM */ -} - diff --git a/lwgeom/lwgeom_geos_prepared.c b/lwgeom/lwgeom_geos_prepared.c new file mode 100644 index 000000000..5d221e814 --- /dev/null +++ b/lwgeom/lwgeom_geos_prepared.c @@ -0,0 +1,679 @@ +/********************************************************************** + * $Id$ + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.refractions.net + * Copyright 2007 Refractions Research Inc. + * Copyright 2008 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "lwgeom_geos_prepared.h" + +/* +** Prepared geometry function prototypes +*/ +Datum containsPrepared(PG_FUNCTION_ARGS); +Datum coversPrepared(PG_FUNCTION_ARGS); +Datum intersectsPrepared(PG_FUNCTION_ARGS); + +/********************************************************************** +** Internal prototype and structure definitions for the +** prepared geometry code. +** +** Working parts: +** +** PrepGeomCache, the actual struct that holds the keys we compare +** to determine if our cache is stale, and references to the GEOS +** objects used in computations. +** +** PrepGeomHash, a global hash table that uses a MemoryContext as +** key and returns a structure holding references to the GEOS +** objects used in computations. +** +** PreparedCacheContextMethods, a set of callback functions that +** get hooked into a MemoryContext that is in turn used as a +** key in the PrepGeomHash. +** +** All this is to allow us to clean up external malloc'ed objects +** (the GEOS Geometry and PreparedGeometry) before the structure +** that references them (PrepGeomCache) is pfree'd by PgSQL. The +** methods in the PreparedCacheContext are called just before the +** function context is freed, allowing us to look up the references +** in the PrepGeomHash and free them before the function context +** is freed. +**/ + +#ifdef PREPARED_GEOM + +#warning COMPILING PREPARED GEOMETRY + +/* +** Backend prepared hash table +** +** The memory context call-backs use a MemoryContext as the parameter +** so we need to map that over to actual references to GEOS objects to +** delete. +** +** This hash table stores a key/value pair of MemoryContext/Geom* objects. +*/ +static HTAB* PrepGeomHash = NULL; + +#define PREPARED_BACKEND_HASH_SIZE 32 + +typedef struct +{ + MemoryContext context; + const GEOSPreparedGeometry* prepared_geom; + const GEOSGeometry* geom; +} PrepGeomHashEntry; + +/* Memory context hash table function prototypes */ +uint32 mcxt_ptr_hasha(const void *key, Size keysize); +static void CreatePrepGeomHash(void); +static void AddPrepGeomHashEntry(PrepGeomHashEntry pghe); +static PrepGeomHashEntry *GetPrepGeomHashEntry(MemoryContext mcxt); +static void DeletePrepGeomHashEntry(MemoryContext mcxt); + +/* Memory context cache function prototypes */ +static void PreparedCacheInit(MemoryContext context); +static void PreparedCacheReset(MemoryContext context); +static void PreparedCacheDelete(MemoryContext context); +static bool PreparedCacheIsEmpty(MemoryContext context); +static void PreparedCacheStats(MemoryContext context, int level); +#ifdef MEMORY_CONTEXT_CHECKING +static void PreparedCacheCheck(MemoryContext context); +#endif + +/* Memory context definition must match the current version of PostgreSQL */ +static MemoryContextMethods PreparedCacheContextMethods = { + NULL, + NULL, + NULL, + PreparedCacheInit, + PreparedCacheReset, + PreparedCacheDelete, + NULL, + PreparedCacheIsEmpty, + PreparedCacheStats +#ifdef MEMORY_CONTEXT_CHECKING + , PreparedCacheCheck +#endif +}; + +static void +PreparedCacheInit(MemoryContext context) +{ + /* + * Do nothing as the cache is initialised when the transform() + * function is first called + */ +} + +static void +PreparedCacheDelete(MemoryContext context) +{ + PrepGeomHashEntry* pghe; + + /* Lookup the hash entry pointer in the global hash table so we can free it */ + pghe = GetPrepGeomHashEntry(context); + + if (!pghe) + elog(ERROR, "PreparedCacheDelete: Trying to delete non-existant hash entry object with MemoryContext key (%p)", (void *)context); + + POSTGIS_DEBUGF(3, "deleting geom object (%p) and prepared geom object (%p) with MemoryContext key (%p)", pghe->geom, pghe->prepared_geom, context); + + /* Free them */ + if( pghe->prepared_geom ) + GEOSPreparedGeom_destroy( pghe->prepared_geom ); + if( pghe->geom ) + GEOSGeom_destroy( pghe->geom ); + + /* Remove the hash entry as it is no longer needed */ + DeletePrepGeomHashEntry(context); +} + +static void +PreparedCacheReset(MemoryContext context) +{ + /* + * Do nothing, but we must supply a function since this call is mandatory according to tgl + * (see postgis-devel archives July 2007) + */ +} + +static bool +PreparedCacheIsEmpty(MemoryContext context) +{ + /* + * Always return false since this call is mandatory according to tgl + * (see postgis-devel archives July 2007) + */ + return FALSE; +} + +static void +PreparedCacheStats(MemoryContext context, int level) +{ + /* + * Simple stats display function - we must supply a function since this call is mandatory according to tgl + * (see postgis-devel archives July 2007) + */ + + fprintf(stderr, "%s: Prepared context\n", context->name); +} + +#ifdef MEMORY_CONTEXT_CHECKING +static void +PreparedCacheCheck(MemoryContext context) +{ + /* + * Do nothing - stub required for when PostgreSQL is compiled + * with MEMORY_CONTEXT_CHECKING defined + */ +} +#endif + +/* TODO: put this in common are for both transform and prepared +** mcxt_ptr_hash +** Build a key from a pointer and a size value. +*/ +uint32 +mcxt_ptr_hasha(const void *key, Size keysize) +{ + uint32 hashval; + + hashval = DatumGetUInt32(hash_any(key, keysize)); + + return hashval; +} + +static void +CreatePrepGeomHash(void) +{ + HASHCTL ctl; + + ctl.keysize = sizeof(MemoryContext); + ctl.entrysize = sizeof(PrepGeomHashEntry); + ctl.hash = mcxt_ptr_hasha; + + PrepGeomHash = hash_create("PostGIS Prepared Geometry Backend MemoryContext Hash", PREPARED_BACKEND_HASH_SIZE, &ctl, (HASH_ELEM | HASH_FUNCTION)); +} + +static void +AddPrepGeomHashEntry(PrepGeomHashEntry pghe) +{ + bool found; + void **key; + PrepGeomHashEntry *he; + + /* The hash key is the MemoryContext pointer */ + key = (void *)&(pghe.context); + + he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_ENTER, &found); + if (!found) + { + /* Insert the entry into the new hash element */ + he->context = pghe.context; + he->geom = pghe.geom; + he->prepared_geom = pghe.prepared_geom; + } + else + { + elog(ERROR, "AddPrepGeomHashEntry: This memory context is already in use! (%p)", (void *)pghe.context); + } +} + +static PrepGeomHashEntry* +GetPrepGeomHashEntry(MemoryContext mcxt) +{ + void **key; + PrepGeomHashEntry *he; + + /* The hash key is the MemoryContext pointer */ + key = (void *)&mcxt; + + /* Return the projection object from the hash */ + he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_FIND, NULL); + + return he; +} + + +static void +DeletePrepGeomHashEntry(MemoryContext mcxt) +{ + void **key; + PrepGeomHashEntry *he; + + /* The hash key is the MemoryContext pointer */ + key = (void *)&mcxt; + + /* Delete the projection object from the hash */ + he = (PrepGeomHashEntry *) hash_search(PrepGeomHash, key, HASH_REMOVE, NULL); + + he->prepared_geom = NULL; + he->geom = NULL; + + if (!he) + elog(ERROR, "DeletePrepGeomHashEntry: There was an error removing the geometry object from this MemoryContext (%p)", (void *)mcxt); +} + +/* +** GetPrepGeomCache +** +** Pull the current prepared geometry from the cache or make +** one if there is not one available. Only prepare geometry +** if we are seeing a key for the second time. That way rapidly +** cycling keys don't cause too much preparing. +*/ +PrepGeomCache* +GetPrepGeomCache(FunctionCallInfoData *fcinfo, PG_LWGEOM *pg_geom1, PG_LWGEOM *pg_geom2) +{ + MemoryContext old_context; + PrepGeomCache* cache = fcinfo->flinfo->fn_extra; + int copy_keys = 1; + size_t pg_geom1_size = 0; + size_t pg_geom2_size = 0; + + if (!PrepGeomHash) + CreatePrepGeomHash(); + + if( pg_geom1 ) + pg_geom1_size = VARSIZE(pg_geom1) + VARHDRSZ; + + if( pg_geom2 ) + pg_geom2_size = VARSIZE(pg_geom2) + VARHDRSZ; + + if ( cache == NULL) + { + /* + ** Cache requested, but the cache isn't set up yet. + ** Set it up, but don't prepare the geometry yet. + ** That way if the next call is a cache miss we haven't + ** wasted time preparing a geometry we don't need. + */ + PrepGeomHashEntry pghe; + + old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); + cache = palloc(sizeof(PrepGeomCache)); + MemoryContextSwitchTo(old_context); + + cache->prepared_geom = 0; + cache->geom = 0; + cache->argnum = 0; + cache->pg_geom1 = 0; + cache->pg_geom2 = 0; + cache->pg_geom1_size = 0; + cache->pg_geom2_size = 0; + cache->context = MemoryContextCreate(T_AllocSetContext, 8192, + &PreparedCacheContextMethods, + fcinfo->flinfo->fn_mcxt, + "PostGIS Prepared Geometry Context"); + + POSTGIS_DEBUGF(1, "GetPrepGeomCache: creating cache: %p", cache); + + pghe.context = cache->context; + pghe.geom = 0; + pghe.prepared_geom = 0; + AddPrepGeomHashEntry( pghe ); + + fcinfo->flinfo->fn_extra = cache; + + POSTGIS_DEBUGF(3, "GetPrepGeomCache: adding context to hash: %p", cache); + } + else if ( pg_geom1 && + cache->argnum != 2 && + cache->pg_geom1_size == pg_geom1_size && + memcmp(cache->pg_geom1, pg_geom1, pg_geom1_size) == 0) + { + if ( !cache->prepared_geom ) + { + /* + ** Cache hit, but we haven't prepared our geometry yet. + ** Prepare it. + */ + PrepGeomHashEntry* pghe; + + cache->geom = POSTGIS2GEOS( pg_geom1 ); + cache->prepared_geom = GEOSPrepare( cache->geom ); + cache->argnum = 1; + POSTGIS_DEBUG(1, "GetPrepGeomCache: preparing obj in argument 1"); + + pghe = GetPrepGeomHashEntry(cache->context); + pghe->geom = cache->geom; + pghe->prepared_geom = cache->prepared_geom; + POSTGIS_DEBUG(3, "GetPrepGeomCache: storing references to prepared obj in argument 1"); + } + else + { + /* + ** Cache hit, and we're good to go. Do nothing. + */ + POSTGIS_DEBUG(1, "GetPrepGeomCache: cache hit, argument 1"); + } + /* We don't need new keys until we have a cache miss */ + copy_keys = 0; + } + else if ( pg_geom2 && + cache->argnum != 1 && + cache->pg_geom2_size == pg_geom2_size && + memcmp(cache->pg_geom2, pg_geom2, pg_geom2_size) == 0) + { + if ( !cache->prepared_geom ) + { + /* + ** Cache hit on arg2, but we haven't prepared our geometry yet. + ** Prepare it. + */ + PrepGeomHashEntry* pghe; + + cache->geom = POSTGIS2GEOS( pg_geom2 ); + cache->prepared_geom = GEOSPrepare( cache->geom ); + cache->argnum = 2; + POSTGIS_DEBUG(1, "GetPrepGeomCache: preparing obj in argument 2"); + + pghe = GetPrepGeomHashEntry(cache->context); + pghe->geom = cache->geom; + pghe->prepared_geom = cache->prepared_geom; + POSTGIS_DEBUG(3, "GetPrepGeomCache: storing references to prepared obj in argument 2"); + } + else + { + /* + ** Cache hit, and we're good to go. Do nothing. + */ + POSTGIS_DEBUG(1, "GetPrepGeomCache: cache hit, argument 2"); + } + /* We don't need new keys until we have a cache miss */ + copy_keys = 0; + } + else if ( cache->prepared_geom ) + { + /* + ** No cache hits, so this must be a miss. + ** Destroy the GEOS objects, empty the cache. + */ + PrepGeomHashEntry* pghe; + + pghe = GetPrepGeomHashEntry(cache->context); + pghe->geom = 0; + pghe->prepared_geom = 0; + + POSTGIS_DEBUGF(1, "GetPrepGeomCache: cache miss, argument %d", cache->argnum); + GEOSPreparedGeom_destroy( cache->prepared_geom ); + GEOSGeom_destroy( cache->geom ); + + cache->prepared_geom = 0; + cache->geom = 0; + cache->argnum = 0; + + } + + if( copy_keys && pg_geom1 ) + { + /* + ** If this is a new key (cache miss) we flip into the function + ** manager memory context and make a copy. We can't just store a pointer + ** because this copy will be pfree'd at the end of this function + ** call. + */ + POSTGIS_DEBUG(1, "GetPrepGeomCache: copying pg_geom1 into cache"); + old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); + if( cache->pg_geom1 ) + pfree(cache->pg_geom1); + cache->pg_geom1 = palloc(pg_geom1_size); + MemoryContextSwitchTo(old_context); + memcpy(cache->pg_geom1, pg_geom1, pg_geom1_size); + cache->pg_geom1_size = pg_geom1_size; + } + if( copy_keys && pg_geom2 ) + { + POSTGIS_DEBUG(1, "GetPrepGeomCache: copying pg_geom2 into cache"); + old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); + if( cache->pg_geom2 ) + pfree(cache->pg_geom2); + cache->pg_geom2 = palloc(pg_geom2_size); + MemoryContextSwitchTo(old_context); + memcpy(cache->pg_geom2, pg_geom2, pg_geom2_size); + cache->pg_geom2_size = pg_geom2_size; + } + + return cache; + +} +#endif /* PREPARED_GEOM */ + + + +#if 0 +/* +** No longer directly called. Left as example for now. +** Functionality directly embedded in contains() +*/ +PG_FUNCTION_INFO_V1(containsPrepared); +Datum containsPrepared(PG_FUNCTION_ARGS) +{ +#ifndef PREPARED_GEOM + elog(ERROR,"Not implemented in this version!"); + PG_RETURN_NULL(); +#else + PG_LWGEOM * geom1; + PG_LWGEOM * geom2; + bool result; + BOX2DFLOAT4 box1, box2; + PrepGeomCache * prep_cache; + int32 key1; + + geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + geom2 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + key1 = PG_GETARG_INT32(2); + + errorIfGeometryCollection(geom1,geom2); + errorIfSRIDMismatch(pglwgeom_getSRID(geom1), pglwgeom_getSRID(geom2)); + + POSTGIS_DEBUG(3, "containsPrepared: entered function"); + + /* + * short-circuit: if geom2 bounding box is not completely inside + * geom1 bounding box we can prematurely return FALSE. + * Do the test IFF BOUNDING BOX AVAILABLE. + */ + if ( getbox2d_p(SERIALIZED_FORM(geom1), &box1) && + getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) + { + if (( box2.xmin < box1.xmin ) || ( box2.xmax > box1.xmax ) || + ( box2.ymin < box1.ymin ) || ( box2.ymax > box1.ymax )) + PG_RETURN_BOOL(FALSE); + } + + prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 ); + + initGEOS(lwnotice, lwnotice); + + if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 ) + { + GEOSGeom g = POSTGIS2GEOS(geom2); + POSTGIS_DEBUG(4, "containsPrepared: cache is live, running preparedcontains"); + result = GEOSPreparedContains( prep_cache->prepared_geom, g); + GEOSGeom_destroy(g); + } + else + { + GEOSGeom g1 = POSTGIS2GEOS(geom1); + GEOSGeom g2 = POSTGIS2GEOS(geom2); + POSTGIS_DEBUG(4, "containsPrepared: cache is not ready, running standard contains"); + result = GEOSContains( g1, g2); + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + } + + if (result == 2) + { + elog(ERROR,"GEOS contains() threw an error!"); + PG_RETURN_NULL(); /* never get here */ + } + + PG_FREE_IF_COPY(geom1, 0); + PG_FREE_IF_COPY(geom2, 1); + + PG_RETURN_BOOL(result); +#endif /* PREPARED_GEOM */ +} + + +/* +** No longer directly called. Left as example for now. +** Functionality directly embedded in covers() +*/ +PG_FUNCTION_INFO_V1(coversPrepared); +Datum coversPrepared(PG_FUNCTION_ARGS) +{ +#ifndef PREPARED_GEOM + elog(ERROR,"Not implemented in this version!"); + PG_RETURN_NULL(); /* never get here */ +#else + PG_LWGEOM * geom1; + PG_LWGEOM * geom2; + bool result; + BOX2DFLOAT4 box1, box2; + PrepGeomCache * prep_cache; + int32 key1; + + geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + geom2 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + key1 = PG_GETARG_INT32(2); + + errorIfGeometryCollection(geom1,geom2); + errorIfSRIDMismatch(pglwgeom_getSRID(geom1), pglwgeom_getSRID(geom2)); + + /* + * short-circuit: if geom2 bounding box is not completely inside + * geom1 bounding box we can prematurely return FALSE. + * Do the test IFF BOUNDING BOX AVAILABLE. + */ + if ( getbox2d_p(SERIALIZED_FORM(geom1), &box1) && + getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) + { + if (( box2.xmin < box1.xmin ) || ( box2.xmax > box1.xmax ) || + ( box2.ymin < box1.ymin ) || ( box2.ymax > box1.ymax )) + PG_RETURN_BOOL(FALSE); + } + + prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 ); + + initGEOS(lwnotice, lwnotice); + + if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 ) + { + GEOSGeom g = POSTGIS2GEOS(geom2); + result = GEOSPreparedCovers( prep_cache->prepared_geom, g); + GEOSGeom_destroy(g); + } + else + { + GEOSGeom g1 = POSTGIS2GEOS(geom1); + GEOSGeom g2 = POSTGIS2GEOS(geom2); + result = GEOSRelatePattern( g1, g2, "******FF*" ); + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + } + + if (result == 2) + { + elog(ERROR,"GEOS contains() threw an error!"); + PG_RETURN_NULL(); /* never get here */ + } + + PG_FREE_IF_COPY(geom1, 0); + PG_FREE_IF_COPY(geom2, 1); + + PG_RETURN_BOOL(result); +#endif /* PREPARED_GEOM */ +} + + +/* +** No longer directly called. Left as example for now. +** Functionality directly embedded in intersects() +*/ +PG_FUNCTION_INFO_V1(intersectsPrepared); +Datum intersectsPrepared(PG_FUNCTION_ARGS) +{ +#ifndef PREPARED_GEOM + elog(ERROR,"Not implemented in this version!"); + PG_RETURN_NULL(); /* never get here */ +#else + PG_LWGEOM * geom1; + PG_LWGEOM * geom2; + bool result; + BOX2DFLOAT4 box1, box2; + PrepGeomCache * prep_cache; + int32 key1, key2; + + geom1 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + geom2 = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + key1 = PG_GETARG_INT32(2); + key2 = PG_GETARG_INT32(3); + + errorIfGeometryCollection(geom1,geom2); + errorIfSRIDMismatch(pglwgeom_getSRID(geom1), pglwgeom_getSRID(geom2)); + + /* + * short-circuit 1: if geom2 bounding box does not overlap + * geom1 bounding box we can prematurely return FALSE. + * Do the test IFF BOUNDING BOX AVAILABLE. + */ + if ( getbox2d_p(SERIALIZED_FORM(geom1), &box1) && + getbox2d_p(SERIALIZED_FORM(geom2), &box2) ) + { + if (( box2.xmax < box1.xmin ) || ( box2.xmin > box1.xmax ) || + ( box2.ymax < box1.ymin ) || ( box2.ymin > box1.ymax )) + PG_RETURN_BOOL(FALSE); + } + + prep_cache = GetPrepGeomCache( fcinfo, geom1, geom2 ); + + initGEOS(lwnotice, lwnotice); + + if ( prep_cache && prep_cache->prepared_geom ) + { + if ( prep_cache->argnum == 1 ) + { + GEOSGeom g = POSTGIS2GEOS(geom2); + result = GEOSPreparedIntersects( prep_cache->prepared_geom, g); + GEOSGeom_destroy(g); + } + else + { + GEOSGeom g = POSTGIS2GEOS(geom1); + result = GEOSPreparedIntersects( prep_cache->prepared_geom, g); + GEOSGeom_destroy(g); + } + } + else + { + GEOSGeom g1 = POSTGIS2GEOS(geom1); + GEOSGeom g2 = POSTGIS2GEOS(geom2); + result = GEOSIntersects( g1, g2); + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + } + + if (result == 2) + { + elog(ERROR,"GEOS contains() threw an error!"); + PG_RETURN_NULL(); /* never get here */ + } + + PG_FREE_IF_COPY(geom1, 0); + PG_FREE_IF_COPY(geom2, 1); + + PG_RETURN_BOOL(result); +#endif /* PREPARED_GEOM */ +} +#endif /* 0 */ + diff --git a/lwgeom/lwgeom_geos_prepared.h b/lwgeom/lwgeom_geos_prepared.h new file mode 100644 index 000000000..aee360c68 --- /dev/null +++ b/lwgeom/lwgeom_geos_prepared.h @@ -0,0 +1,70 @@ +/********************************************************************** + * $Id$ + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.refractions.net + * Copyright 2008 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "../postgis_config.h" + +#include "postgres.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "access/hash.h" + +#include "geos_c.h" + +#include "lwgeom_pg.h" +#include "liblwgeom.h" +#include "lwgeom_geos.h" + +/* +** GEOS prepared geometry is only available from GEOS 3.1 onwards +*/ +#if POSTGIS_GEOS_VERSION >= 31 +#define PREPARED_GEOM +#endif + +/* +** Cache structure. We use PG_LWGEOM as keys so no transformations +** are needed before we memcmp them with other keys. We store the +** size to avoid having to calculate the size every time. +** The argnum gives the number of function arguments we are caching. +** Intersects requires that both arguments be checked for cacheability, +** while Contains only requires that the containing argument be checked. +** Both the Geometry and the PreparedGeometry have to be cached, +** because the PreparedGeometry contains a reference to the geometry. +*/ +#ifdef PREPARED_GEOM +typedef struct +{ + PG_LWGEOM* pg_geom1; + PG_LWGEOM* pg_geom2; + size_t pg_geom1_size; + size_t pg_geom2_size; + int32 argnum; + const GEOSPreparedGeometry* prepared_geom; + const GEOSGeometry* geom; + MemoryContext context; +} PrepGeomCache; + +/* +** Get the current cache, given the input geometries. +** Function will create cache if none exists, and prepare geometries in +** cache if necessary, or pull an existing cache if possible. +** +** If you are only caching one argument (e.g., in contains) supply 0 as the +** value for pg_geom2. +*/ +PrepGeomCache *GetPrepGeomCache(FunctionCallInfoData *fcinfo, PG_LWGEOM *pg_geom1, PG_LWGEOM *pg_geom2); + + +#endif /* PREPARED_GEOM */ + diff --git a/lwgeom/lwgeom_rtree.h b/lwgeom/lwgeom_rtree.h index cc694cfb2..26d45d648 100644 --- a/lwgeom/lwgeom_rtree.h +++ b/lwgeom/lwgeom_rtree.h @@ -61,4 +61,6 @@ RTREE_POLY_CACHE *createCache(void); void populateCache(RTREE_POLY_CACHE *cache, LWGEOM *lwgeom, uchar *serializedPoly); void clearCache(RTREE_POLY_CACHE *cache); + + #endif /* !defined _LIBLWGEOM_H */ diff --git a/lwgeom/lwpostgis.sql.in.c b/lwgeom/lwpostgis.sql.in.c index 83c0ba5dc..ebed6622c 100644 --- a/lwgeom/lwpostgis.sql.in.c +++ b/lwgeom/lwpostgis.sql.in.c @@ -3875,6 +3875,21 @@ CREATEFUNCTION ST_Covers(geometry,geometry) LANGUAGE 'SQL' _IMMUTABLE; -- WITH (iscachable); #endif +#if POSTGIS_GEOS_VERSION >= 31 +-- Availability: 1.4.0 +CREATEFUNCTION _ST_ContainsProperly(geometry,geometry) + RETURNS boolean + AS 'MODULE_PATHNAME','containsproperly' + LANGUAGE 'C' _IMMUTABLE_STRICT; -- WITH (isstrict,iscachable); + +-- Availability: 1.4.0 +-- Inlines index magic +CREATEFUNCTION ST_ContainsProperly(geometry,geometry) + RETURNS boolean + AS 'SELECT $1 && $2 AND _ST_ContainsProperly($1,$2)' + LANGUAGE 'SQL' _IMMUTABLE; -- WITH (iscachable); +#endif + -- Deprecation in 1.2.3 CREATEFUNCTION overlaps(geometry,geometry) RETURNS boolean @@ -3979,74 +3994,6 @@ CREATEFUNCTION ST_Equals(geometry,geometry) AS 'MODULE_PATHNAME','geomequals' LANGUAGE 'C' _IMMUTABLE_STRICT; -- WITH (isstrict,iscachable); ------------------------------------------------------------------------ --- Prepared Geometry Predicates --- requires GEOS 3.1.0-CAPI-1.5.0 or better ------------------------------------------------------------------------ - -#if POSTGIS_GEOS_VERSION >= 31 - --- Availability: 1.4.0 -CREATEFUNCTION _ST_ContainsPrepared(geometry,geometry,integer) - RETURNS boolean - AS 'MODULE_PATHNAME','containsPrepared' - LANGUAGE 'C' _IMMUTABLE_STRICT; -- WITH (isstrict,iscachable); - --- Availability: 1.4.0 --- Inlines index magic -CREATEFUNCTION ST_Contains(geometry,geometry,integer) - RETURNS boolean - AS 'SELECT $1 && $2 AND _ST_ContainsPrepared($1,$2,$3)' - LANGUAGE 'SQL' _IMMUTABLE; -- WITH (iscachable); - --- Availability: 1.4.0 -CREATEFUNCTION _ST_ContainsProperlyPrepared(geometry,geometry,integer) - RETURNS boolean - AS 'MODULE_PATHNAME','containsProperlyPrepared' - LANGUAGE 'C' _IMMUTABLE_STRICT; -- WITH (isstrict,iscachable); - --- Availability: 1.4.0 --- Inlines index magic -CREATEFUNCTION ST_ContainsProperly(geometry,geometry,integer) - RETURNS boolean - AS 'SELECT $1 && $2 AND _ST_ContainsProperlyPrepared($1,$2,$3)' - LANGUAGE 'SQL' _IMMUTABLE; -- WITH (iscachable); - --- Availability: 1.4.0 --- Added for completeness, and to make testing ST_ContainsProperlyPrepared easier -CREATE OR REPLACE FUNCTION ST_ContainsProperly(geometry,geometry) - RETURNS boolean - AS 'SELECT $1 && $2 AND ST_relate($1,$2,''T**FF*FF*'')' - LANGUAGE 'SQL' IMMUTABLE; - --- Availability: 1.4.0 -CREATEFUNCTION _ST_CoversPrepared(geometry,geometry,integer) - RETURNS boolean - AS 'MODULE_PATHNAME','coversPrepared' - LANGUAGE 'C' _IMMUTABLE_STRICT; -- WITH (isstrict,iscachable); - --- Availability: 1.4.0 --- Inlines index magic -CREATEFUNCTION ST_Covers(geometry,geometry,integer) - RETURNS boolean - AS 'SELECT $1 && $2 AND _ST_CoversPrepared($1,$2,$3)' - LANGUAGE 'SQL' _IMMUTABLE; -- WITH (iscachable); - --- Availability: 1.4.0 -CREATEFUNCTION _ST_IntersectsPrepared(geometry,geometry,integer,integer) - RETURNS boolean - AS 'MODULE_PATHNAME','intersectsPrepared' - LANGUAGE 'C' _IMMUTABLE_STRICT; -- WITH (isstrict,iscachable); - --- Availability: 1.4.0 --- Inlines index magic -CREATEFUNCTION ST_Intersects(geometry,geometry,integer,integer) - RETURNS boolean - AS 'SELECT $1 && $2 AND _ST_IntersectsPrepared($1,$2,$3,$4)' - LANGUAGE 'SQL' _IMMUTABLE; -- WITH (iscachable); - -#endif - ----------------------------------------------------------------------- -- SVG OUTPUT ----------------------------------------------------------------------- diff --git a/regress/README b/regress/README index c7ea310f8..396bf8fb6 100644 --- a/regress/README +++ b/regress/README @@ -9,6 +9,9 @@ How to add a regression test -a -- unaligned columns -f | -- use | (pipe) as the field separator between columns -t -- output rows only, ie. no table header + + cat file.sql | psql -F\| -t -A > file_expected + 3. Edit regress/Makefile adding to the TESTS variable. Any _expected.in files need to be added to the PREPROC variable. diff --git a/regress/regress_ogc_prep.sql b/regress/regress_ogc_prep.sql index d10e918ee..1b3acd11a 100644 --- a/regress/regress_ogc_prep.sql +++ b/regress/regress_ogc_prep.sql @@ -3,199 +3,270 @@ --- --- -SELECT 'intersects', ST_intersects('LINESTRING(0 10, 0 -10)', p, 0) from ( values -('LINESTRING(0 0, 1 1)'),('LINESTRING(0 0, 1 1)'),('LINESTRING(0 0, 1 1)') -) as v(p); +SELECT c, ST_Intersects(ply, pt) FROM +( VALUES +-- PIP - point within polygon (no cache) +('intersects099', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(5 5)'), -- PIP - point within polygon -SELECT 'intersects100', ST_intersects('POINT(5 5)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); +('intersects100', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(5 5)'), -- PIP - point on polygon vertex -SELECT 'intersects101', ST_intersects('POINT(0 0)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); +('intersects101', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 0)'), -- PIP - point outside polygon -SELECT 'intersects102', ST_intersects('POINT(-1 0)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); +('intersects102', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(-1 0)'), -- PIP - point on polygon edge -SELECT 'intersects103', ST_intersects('POINT(0 5)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); +('intersects103', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 5)'), -- PIP - point in line with polygon edge -SELECT 'intersects104', ST_intersects('POINT(0 12)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); --- PIP - point vertically aligned with polygon vertex -SELECT 'intersects105', ST_intersects(ST_GeomFromText('POINT(521513 5377804)', 32631), ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), 0); --- PIP - repeated vertex -SELECT 'intersects106', ST_intersects(ST_GeomFromText('POINT(521543 5377804)', 32631), ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), 0); +('intersects104', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 12)') +) AS v(c,ply,pt); + +SELECT c, ST_Contains(ply, pt) FROM +( VALUES +-- PIP - point within polygon (no cache) +('contains099', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(5 5)'), -- PIP - point within polygon -SELECT 'intersects150', ST_intersects('POINT(5 5)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); +('contains100', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(5 5)'), -- PIP - point on polygon vertex -SELECT 'intersects151', ST_intersects('POINT(0 0)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); +('contains101', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 0)'), -- PIP - point outside polygon -SELECT 'intersects152', ST_intersects('POINT(-1 0)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); +('contains102', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(-1 0)'), -- PIP - point on polygon edge -SELECT 'intersects153', ST_intersects('POINT(0 5)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); +('contains103', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 5)'), -- PIP - point in line with polygon edge -SELECT 'intersects154', ST_intersects('POINT(0 12)', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 0); --- PIP - point vertically aligned with polygon vertex -SELECT 'intersects155', ST_intersects(ST_GeomFromText('POINT(521513 5377804)', 32631), ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), 0); --- PIP - repeated vertex -SELECT 'intersects156', ST_intersects(ST_GeomFromText('POINT(521543 5377804)', 32631), ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), 0); - -SELECT 'intersects200', ST_intersects('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(5 5)'),('POINT(5 5)'),('POINT(5 5)') -) as v(p); --- PIP - point on vertex of polygon -SELECT 'intersects201', ST_intersects('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 0)'),('POINT(0 0)'),('POINT(0 0)') -) as v(p); +('contains104', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 12)') +) AS v(c,ply,pt); + +SELECT c, ST_Covers(ply, pt) FROM +( VALUES +-- PIP - point within polygon (no cache) +('covers099', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(5 5)'), +-- PIP - point within polygon +('covers100', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(5 5)'), +-- PIP - point on polygon vertex +('covers101', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 0)'), -- PIP - point outside polygon -SELECT 'intersects202', ST_intersects('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(-1 0)'),('POINT(-1 0)'),('POINT(-1 0)') -) as v(p); --- PIP - point on edge of polygon -SELECT 'intersects203', ST_intersects('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 5)'),('POINT(0 5)'),('POINT(0 5)') -) as v(p); +('covers102', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(-1 0)'), +-- PIP - point on polygon edge +('covers103', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 5)'), -- PIP - point in line with polygon edge -SELECT 'intersects204', ST_intersects('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 12)'),('POINT(0 12)'),('POINT(0 12)') -) as v(p); --- PIP - point vertically aligned with polygon vertex -SELECT 'intersects205', ST_intersects(ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), p, 0) from ( values -(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)) -) as v(p); --- PIP - repeated vertex -SELECT 'intersects206', ST_intersects(ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), p, 0) from ( values -(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)) -) as v(p); -SELECT 'intersects210', ST_intersects('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)') -) as v(p); -SELECT 'intersects211', ST_intersects('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)') -) as v(p); - +('covers104', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 12)') +) AS v(c,ply,pt); +SELECT c, ST_ContainsProperly(ply, pt) FROM +( VALUES +-- PIP - point within polygon (no cache) +('containsproperly099', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(5 5)'), -- PIP - point within polygon -SELECT 'contains100', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(5 5)'),('POINT(5 5)'),('POINT(5 5)') -) as v(p); --- PIP - point on vertex of polygon -SELECT 'contains101', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 0)'),('POINT(0 0)'),('POINT(0 0)') -) as v(p); +('containsproperly100', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(5 5)'), +-- PIP - point on polygon vertex +('containsproperly101', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 0)'), -- PIP - point outside polygon -SELECT 'contains102', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(-1 0)'),('POINT(-1 0)'),('POINT(-1 0)') -) as v(p); --- PIP - point on edge of rect -SELECT 'contains103', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 5)'),('POINT(0 5)'),('POINT(0 5)') -) as v(p); --- PIP - point on other edge of rect -SELECT 'contains103a', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(5 0)'),('POINT(5 0)'),('POINT(5 0)') -) as v(p); --- PIP - point on edge of polygon -SELECT 'contains103b', ST_Contains('POLYGON((0 0, 0 10, 10 10, 15 0, 0 0))', p, 0) from ( values -('POINT(0 5)'),('POINT(0 5)'),('POINT(0 5)') -) as v(p); --- PIP - point on other edge of polygon -SELECT 'contains103c', ST_Contains('POLYGON((0 0, 0 10, 10 10, 15 0, 0 0))', p, 0) from ( values -('POINT(5 0)'),('POINT(5 0)'),('POINT(5 0)') -) as v(p); +('containsproperly102', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(-1 0)'), +-- PIP - point on polygon edge +('containsproperly103', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 5)'), -- PIP - point in line with polygon edge -SELECT 'contains104', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 12)'),('POINT(0 12)'),('POINT(0 12)') -) as v(p); --- PIP - point vertically aligned with polygon vertex -SELECT 'contains105', ST_Contains(ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), p, 0) from ( values -(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)) -) as v(p); --- PIP - repeated vertex -SELECT 'contains106', ST_Contains(ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), p, 0) from ( values -(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)) -) as v(p); -SELECT 'contains110', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values +('containsproperly104', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POINT(0 12)') +) AS v(c,ply,pt); + + +-- PIP - point vertically aligned with polygon vertex, poly first +SELECT 'intersects105', ST_Intersects(p, ST_GeomFromText('POINT(521513 5377804)', 32631)) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - point vertically aligned with polygon vertex, point first +SELECT 'intersects106', ST_Intersects(ST_GeomFromText('POINT(521513 5377804)', 32631), p) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - repeated vertex, poly first +SELECT 'intersects107', ST_Intersects(p, ST_GeomFromText('POINT(521543 5377804)', 32631)) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - repeated vertex, point first +SELECT 'intersects108', ST_Intersects(ST_GeomFromText('POINT(521543 5377804)', 32631), p) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); + + +-- PIP - point vertically aligned with polygon vertex, poly first +SELECT 'contains105', ST_Contains(p, ST_GeomFromText('POINT(521513 5377804)', 32631)) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - point vertically aligned with polygon vertex, point first +SELECT 'contains106', ST_Contains(ST_GeomFromText('POINT(521513 5377804)', 32631), p) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - repeated vertex, poly first +SELECT 'contains107', ST_Contains(p, ST_GeomFromText('POINT(521543 5377804)', 32631)) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - repeated vertex, point first +SELECT 'contains108', ST_Contains(ST_GeomFromText('POINT(521543 5377804)', 32631), p) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); + +-- PIP - point vertically aligned with polygon vertex, poly first +SELECT 'containsproperly105', ST_ContainsProperly(p, ST_GeomFromText('POINT(521513 5377804)', 32631)) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - point vertically aligned with polygon vertex, point first +SELECT 'containsproperly106', ST_ContainsProperly(ST_GeomFromText('POINT(521513 5377804)', 32631), p) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - repeated vertex, poly first +SELECT 'containsproperly107', ST_ContainsProperly(p, ST_GeomFromText('POINT(521543 5377804)', 32631)) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - repeated vertex, point first +SELECT 'containsproperly108', ST_ContainsProperly(ST_GeomFromText('POINT(521543 5377804)', 32631), p) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); + +-- PIP - point vertically aligned with polygon vertex, poly first +SELECT 'covers105', ST_Covers(p, ST_GeomFromText('POINT(521513 5377804)', 32631)) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - point vertically aligned with polygon vertex, point first +SELECT 'covers106', ST_Covers(ST_GeomFromText('POINT(521513 5377804)', 32631), p) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - repeated vertex, poly first +SELECT 'covers107', ST_Covers(p, ST_GeomFromText('POINT(521543 5377804)', 32631)) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); +-- PIP - repeated vertex, point first +SELECT 'covers108', ST_Covers(ST_GeomFromText('POINT(521543 5377804)', 32631), p) FROM +( VALUES + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)), + (ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631)) +) AS v(p); + + +SELECT c, ST_Intersects(p1, p2) AS intersects_p1p2, ST_Intersects(p2, p1) AS intersects_p2p1 FROM +( VALUES +('intersects200', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('intersects201', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('intersects202', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), +('intersects203', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((-5 -5, 5 -5, 5 5, -5 5, -5 -5))'), +('intersects204', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((-2 -2, -2 -3, -3 -3, -3 -2, -2 -2))'), +('intersects205', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('intersects206', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('intersects207', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), +('intersects208', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((-5 -5, 5 -5, 5 5, -5 5, -5 -5))'), +('intersects209', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((-2 -2, -2 -3, -3 -3, -3 -2, -2 -2))') +) AS v(c,p1,p2); + +SELECT c, ST_Contains(p1, p2) AS contains_p1p2, ST_Contains(p2, p1) AS contains_p2p1 FROM +( VALUES +('contains200', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('contains201', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('contains202', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), +('contains203', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((-5 -5, 5 -5, 5 5, -5 5, -5 -5))'), +('contains204', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((-2 -2, -2 -3, -3 -3, -3 -2, -2 -2))'), +('contains205', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('contains206', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('contains207', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), +('contains208', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((-5 -5, 5 -5, 5 5, -5 5, -5 -5))'), +('contains209', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((-2 -2, -2 -3, -3 -3, -3 -2, -2 -2))') +) AS v(c,p1,p2); + +SELECT c, ST_ContainsProperly(p1, p2) AS containsproperly_p1p2, ST_ContainsProperly(p2, p1) AS containsproperly_p2p1 FROM +( VALUES +('containsproperly200', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('containsproperly201', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('containsproperly202', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), +('containsproperly203', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((-5 -5, 5 -5, 5 5, -5 5, -5 -5))'), +('containsproperly204', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((-2 -2, -2 -3, -3 -3, -3 -2, -2 -2))'), +('containsproperly205', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('containsproperly206', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('containsproperly207', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), +('containsproperly208', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((-5 -5, 5 -5, 5 5, -5 5, -5 -5))'), +('containsproperly209', 'POLYGON((0 0, 0 10, 10 11, 10 0, 0 0))', 'POLYGON((-2 -2, -2 -3, -3 -3, -3 -2, -2 -2))') +) AS v(c,p1,p2); + +SELECT c, ST_Covers(p1, p2) AS covers_p1p2, ST_Covers(p2, p1) AS covers_p2p1 FROM +( VALUES +('covers200', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('covers201', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('covers202', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), +('covers203', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((-5 -5, 5 -5, 5 5, -5 5, -5 -5))'), +('covers204', 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', 'POLYGON((-2 -2, -2 -3, -3 -3, -3 -2, -2 -2))'), +('covers205', 'POLYGON((0 0, 0 10, 10 10, 11 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('covers206', 'POLYGON((0 0, 0 10, 10 10, 11 0, 0 0))', 'POLYGON((2 2, 2 3, 3 3, 3 2, 2 2))'), +('covers207', 'POLYGON((0 0, 0 10, 10 10, 11 0, 0 0))', 'POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))'), +('covers208', 'POLYGON((0 0, 0 10, 10 10, 11 0, 0 0))', 'POLYGON((-5 -5, 5 -5, 5 5, -5 5, -5 -5))'), +('covers209', 'POLYGON((0 0, 0 10, 10 10, 11 0, 0 0))', 'POLYGON((-2 -2, -2 -3, -3 -3, -3 -2, -2 -2))') +) AS v(c,p1,p2); + +SELECT 'intersects310', ST_intersects('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p) FROM ( VALUES ('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)') -) as v(p); -SELECT 'contains111', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values +) AS v(p); +SELECT 'intersects311', ST_intersects('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p) FROM ( VALUES ('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)') -) as v(p); +) AS v(p); --- PIP - point within polygon -SELECT 'containsproperly100', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(5 5)'),('POINT(5 5)'),('POINT(5 5)') -) as v(p); --- PIP - point on vertex of polygon -SELECT 'containsproperly101', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 0)'),('POINT(0 0)'),('POINT(0 0)') -) as v(p); --- PIP - point outside polygon -SELECT 'containsproperly102', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(-1 0)'),('POINT(-1 0)'),('POINT(-1 0)') -) as v(p); --- PIP - point on edge of rect -SELECT 'containsproperly103', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 5)'),('POINT(0 5)'),('POINT(0 5)') -) as v(p); --- PIP - point on other edge of rect -SELECT 'containsproperly103a', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(5 0)'),('POINT(5 0)'),('POINT(5 0)') -) as v(p); --- PIP - point on edge of polygon -SELECT 'containsproperly103b', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 15 0, 0 0))', p, 0) from ( values -('POINT(0 5)'),('POINT(0 5)'),('POINT(0 5)') -) as v(p); --- PIP - point on other edge of polygon -SELECT 'containsproperly103c', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 15 0, 0 0))', p, 0) from ( values -('POINT(5 0)'),('POINT(5 0)'),('POINT(5 0)') -) as v(p); --- PIP - point in line with polygon edge -SELECT 'containsproperly104', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 12)'),('POINT(0 12)'),('POINT(0 12)') -) as v(p); --- PIP - point vertically aligned with polygon vertex -SELECT 'containsproperly105', ST_ContainsProperly(ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), p, 0) from ( values -(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)) -) as v(p); --- PIP - repeated vertex -SELECT 'containsproperly106', ST_ContainsProperly(ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), p, 0) from ( values -(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)) -) as v(p); -SELECT 'containsproperly110', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values +SELECT 'contains310', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p) FROM ( VALUES ('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)') -) as v(p); -SELECT 'containsproperly111', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values +) AS v(p); +SELECT 'contains311', ST_Contains('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p) FROM ( VALUES ('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)') -) as v(p); +) AS v(p); --- Covers cases -SELECT 'covers100', ST_Covers('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values +SELECT 'containsproperly310', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p) FROM ( VALUES ('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)') -) as v(p); -SELECT 'covers101', ST_Covers('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values +) AS v(p); +SELECT 'containsproperly311', ST_ContainsProperly('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p) FROM ( VALUES ('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)') -) as v(p); --- PIP - point within polygon -SELECT 'covers102', ST_Covers('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(5 5)'),('POINT(5 5)'),('POINT(5 5)') -) as v(p); --- PIP - point on vertex of polygon -SELECT 'covers103', ST_Covers('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 0)'),('POINT(0 0)'),('POINT(0 0)') -) as v(p); --- PIP - point outside polygon -SELECT 'covers104', ST_Covers('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(-1 0)'),('POINT(-1 0)'),('POINT(-1 0)') -) as v(p); --- PIP - point on edge of polygon -SELECT 'covers105', ST_Covers('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 5)'),('POINT(0 5)'),('POINT(0 5)') -) as v(p); --- PIP - point in line with polygon edge -SELECT 'covers106', ST_Covers('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p, 0) from ( values -('POINT(0 12)'),('POINT(0 12)'),('POINT(0 12)') -) as v(p); --- PIP - point vertically aligned with polygon vertex -SELECT 'covers107', ST_Covers(ST_GeomFromText('POLYGON((521526 5377783, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), p, 0) from ( values -(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)) -) as v(p); --- PIP - repeated vertex -SELECT 'covers108', ST_Covers(ST_GeomFromText('POLYGON((521526 5377783, 521482 5377811, 521481 5377811, 521494 5377832, 521539 5377804, 521526 5377783))', 32631), p, 0) from ( values -(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)),(ST_GeomFromText('POINT(521513 5377804)', 32631)) -) as v(p); +) AS v(p); + +SELECT 'covers310', ST_Covers('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p) FROM ( VALUES +('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)'),('LINESTRING(1 10, 9 10, 9 8)') +) AS v(p); +SELECT 'covers311', ST_Covers('POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))', p) FROM ( VALUES +('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)'),('LINESTRING(1 10, 10 10, 10 8)') +) AS v(p); + diff --git a/regress/regress_ogc_prep_expected b/regress/regress_ogc_prep_expected index 0d26077b4..72ecd6883 100644 --- a/regress/regress_ogc_prep_expected +++ b/regress/regress_ogc_prep_expected @@ -1,143 +1,136 @@ -intersects|t -intersects|t -intersects|t +intersects099|t intersects100|t intersects101|t intersects102|f intersects103|t intersects104|f -intersects105|t -intersects106|f -intersects150|t -intersects151|t -intersects152|f -intersects153|t -intersects154|f -intersects155|t -intersects156|f -intersects200|t -intersects200|t -intersects200|t -intersects201|t -intersects201|t -intersects201|t -intersects202|f -intersects202|f -intersects202|f -intersects203|t -intersects203|t -intersects203|t -intersects204|f -intersects204|f -intersects204|f -intersects205|t -intersects205|t -intersects205|t -intersects206|t -intersects206|t -intersects206|t -intersects210|t -intersects210|t -intersects210|t -intersects211|t -intersects211|t -intersects211|t -contains100|t -contains100|t +contains099|t contains100|t contains101|f -contains101|f -contains101|f -contains102|f contains102|f -contains102|f -contains103|f -contains103|f contains103|f -contains103a|f -contains103a|f -contains103a|f -contains103b|f -contains103b|f -contains103b|f -contains103c|f -contains103c|f -contains103c|f contains104|f -contains104|f -contains104|f -contains105|t -contains105|t -contains105|t -contains106|t -contains106|t -contains106|t -contains110|t -contains110|t -contains110|t -contains111|f -contains111|f -contains111|f -containsproperly100|t -containsproperly100|t +covers099|t +covers100|t +covers101|t +covers102|f +covers103|t +covers104|f +containsproperly099|t containsproperly100|t containsproperly101|f -containsproperly101|f -containsproperly101|f -containsproperly102|f containsproperly102|f -containsproperly102|f -containsproperly103|f -containsproperly103|f containsproperly103|f -containsproperly103a|f -containsproperly103a|f -containsproperly103a|f -containsproperly103b|f -containsproperly103b|f -containsproperly103b|f -containsproperly103c|f -containsproperly103c|f -containsproperly103c|f -containsproperly104|f -containsproperly104|f containsproperly104|f +intersects105|t +intersects105|t +intersects105|t +intersects106|t +intersects106|t +intersects106|t +intersects107|f +intersects107|f +intersects107|f +intersects108|f +intersects108|f +intersects108|f +contains105|t +contains105|t +contains105|t +contains106|f +contains106|f +contains106|f +contains107|f +contains107|f +contains107|f +contains108|f +contains108|f +contains108|f containsproperly105|t containsproperly105|t containsproperly105|t -containsproperly106|t -containsproperly106|t -containsproperly106|t -containsproperly110|f -containsproperly110|f -containsproperly110|f -containsproperly111|f -containsproperly111|f -containsproperly111|f -covers100|t -covers100|t -covers100|t -covers101|t -covers101|t -covers101|t -covers102|t -covers102|t -covers102|t -covers103|t -covers103|t -covers103|t -covers104|f -covers104|f -covers104|f +containsproperly106|f +containsproperly106|f +containsproperly106|f +containsproperly107|f +containsproperly107|f +containsproperly107|f +containsproperly108|f +containsproperly108|f +containsproperly108|f covers105|t covers105|t covers105|t covers106|f covers106|f covers106|f -covers107|t -covers107|t -covers107|t -covers108|t -covers108|t -covers108|t +covers107|f +covers107|f +covers107|f +covers108|f +covers108|f +covers108|f +intersects200|t|t +intersects201|t|t +intersects202|t|t +intersects203|t|t +intersects204|f|f +intersects205|t|t +intersects206|t|t +intersects207|t|t +intersects208|t|t +intersects209|f|f +contains200|t|f +contains201|t|f +contains202|t|f +contains203|f|f +contains204|f|f +contains205|t|f +contains206|t|f +contains207|t|f +contains208|f|f +contains209|f|f +containsproperly200|t|f +containsproperly201|t|f +containsproperly202|f|f +containsproperly203|f|f +containsproperly204|f|f +containsproperly205|t|f +containsproperly206|t|f +containsproperly207|f|f +containsproperly208|f|f +containsproperly209|f|f +covers200|t|f +covers201|t|f +covers202|t|f +covers203|f|f +covers204|f|f +covers205|t|f +covers206|t|f +covers207|t|f +covers208|f|f +covers209|f|f +intersects310|t +intersects310|t +intersects310|t +intersects311|t +intersects311|t +intersects311|t +contains310|t +contains310|t +contains310|t +contains311|f +contains311|f +contains311|f +containsproperly310|f +containsproperly310|f +containsproperly310|f +containsproperly311|f +containsproperly311|f +containsproperly311|f +covers310|t +covers310|t +covers310|t +covers311|t +covers311|t +covers311|t