SA_OBJS = \
gserialized_gist.o \
lwgeom_transform.o \
+ lwgeom_cache.o \
lwgeom_pg.o
SA_HEADERS = \
lwgeom_pg.h \
lwgeom_transform.h \
+ lwgeom_cache.h \
gserialized_gist.h \
pgsql_compat.h
--- /dev/null
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.refractions.net
+ *
+ * Copyright (C) 2012 Sandro Santilli <strk@keybit.net>
+ *
+ * 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 "postgres.h"
+#include "fmgr.h"
+
+#include "../postgis_config.h"
+#include "lwgeom_cache.h"
+
+/*
+* Generic statement caching infrastructure. We cache
+* the following kinds of objects:
+*
+* geometries-with-trees
+* PreparedGeometry, RTree, CIRC_TREE, RECT_TREE
+* srids-with-projections
+* projPJ
+*
+* Each GenericCache* has a type, and after that
+* some data. Similar to generic LWGEOM*. Test that
+* the type number is what you expect before casting
+* and de-referencing struct members.
+*/
+typedef struct {
+ int type;
+ char data[1];
+} GenericCache;
+
+/*
+* Although there are only two basic kinds of
+* cache entries, the actual trees stored in the
+* geometries-with-trees pattern are quite diverse,
+* and they might be used in combination, so we have
+* one slot for each tree type as well as a slot for
+* projections.
+*/
+typedef struct {
+ GenericCache* entry[NUM_CACHE_ENTRIES];
+} GenericCacheCollection;
+
+/**
+* Utility function to read the upper memory context off a function call
+* info data.
+*/
+static MemoryContext
+FIContext(FunctionCallInfoData* fcinfo)
+{
+ return fcinfo->flinfo->fn_mcxt;
+}
+
+/**
+* Get the generic collection off the statement, allocate a
+* new one if we don't have one already.
+*/
+static GenericCacheCollection*
+GetGenericCacheCollection(FunctionCallInfoData* fcinfo)
+{
+ GenericCacheCollection* cache = fcinfo->flinfo->fn_extra;
+
+ if ( ! cache )
+ {
+ cache = MemoryContextAlloc(FIContext(fcinfo), sizeof(GenericCacheCollection));
+ memset(cache, 0, sizeof(GenericCacheCollection));
+ fcinfo->flinfo->fn_extra = cache;
+ }
+ return cache;
+}
+
+
+/**
+* Get the Proj4 entry from the generic cache if one exists.
+* If it doesn't exist, make a new empty one and return it.
+*/
+PROJ4PortalCache*
+GetPROJ4SRSCache(FunctionCallInfoData* fcinfo)
+{
+ GenericCacheCollection* generic_cache = GetGenericCacheCollection(fcinfo);
+ PROJ4PortalCache* cache = (PROJ4PortalCache*)(generic_cache->entry[PROJ_CACHE_ENTRY]);
+
+ if ( ! cache )
+ {
+ /* Allocate in the upper context */
+ cache = MemoryContextAlloc(FIContext(fcinfo), sizeof(PROJ4PortalCache));
+
+ if (cache)
+ {
+ int i;
+
+ POSTGIS_DEBUGF(3, "Allocating PROJ4Cache for portal with transform() MemoryContext %p", FIContext(fcinfo));
+ /* Put in any required defaults */
+ for (i = 0; i < PROJ4_CACHE_ITEMS; i++)
+ {
+ cache->PROJ4SRSCache[i].srid = SRID_UNKNOWN;
+ cache->PROJ4SRSCache[i].projection = NULL;
+ cache->PROJ4SRSCache[i].projection_mcxt = NULL;
+ }
+ cache->type = PROJ_CACHE_ENTRY;
+ cache->PROJ4SRSCacheCount = 0;
+ cache->PROJ4SRSCacheContext = FIContext(fcinfo);
+
+ /* Store the pointer in GenericCache */
+ generic_cache->entry[PROJ_CACHE_ENTRY] = (GenericCache*)cache;
+ }
+ }
+ return cache;
+}
+
+/**
+* Get an appropriate (based on the entry type number)
+* GeomCache entry from the generic cache if one exists.
+* If it doesn't exist, make a new empty one and return it.
+*/
+GeomCache*
+GetGeomCache(FunctionCallInfoData* fcinfo, int cache_entry)
+{
+ GeomCache* cache;
+ GenericCacheCollection* generic_cache = GetGenericCacheCollection(fcinfo);
+
+ if ( (cache_entry) < 0 || (cache_entry >= NUM_CACHE_ENTRIES) )
+ return NULL;
+
+ cache = (GeomCache*)(generic_cache->entry[cache_entry]);
+
+ if ( ! cache )
+ {
+ /* Allocate in the upper context */
+ cache = MemoryContextAlloc(FIContext(fcinfo), sizeof(GeomCache));
+ /* Zero everything out */
+ memset(cache, 0, sizeof(GeomCache));
+ /* Set the cache type */
+ cache->type = cache_entry;
+ cache->context_statement = FIContext(fcinfo);
+
+ /* Store the pointer in GenericCache */
+ generic_cache->entry[cache_entry] = (GenericCache*)cache;
+
+ }
+
+ /* The cache object type should always equal the entry type */
+ if ( cache->type != cache_entry )
+ {
+ lwerror("cache type does not equal expected entry type");
+ return NULL;
+ }
+
+ return cache;
+}
+
+
+
+
+
+/**
+* Get an appropriate tree from the cache, based on the entry number
+* and the geometry values. Checks for a cache, checks for cache hits,
+* returns a built tree if one exists.
+*/
+void*
+GetGeomIndex(FunctionCallInfoData* fcinfo, int cache_entry, GeomIndexBuilder index_build, GeomIndexFreer index_free, const GSERIALIZED* geom1, const GSERIALIZED* geom2, int* argnum)
+{
+ int cache_hit = 0;
+ MemoryContext old_context;
+ GeomCache* cache = GetGeomCache(fcinfo, cache_entry);
+ const GSERIALIZED *geom;
+
+ /* Initialize output of argnum */
+ if ( argnum )
+ *argnum = cache_hit;
+
+ /* Cache hit on the first argument */
+ if ( geom1 &&
+ cache->argnum != 2 &&
+ cache->geom1_size == VARSIZE(geom1) &&
+ memcmp(cache->geom1, geom1, cache->geom1_size) == 0 )
+ {
+ cache_hit = 1;
+ geom = geom1;
+
+ }
+ /* Cache hit on second argument */
+ else if ( geom2 &&
+ cache->argnum != 1 &&
+ cache->geom2_size == VARSIZE(geom2) &&
+ memcmp(cache->geom2, geom2, cache->geom2_size) == 0 )
+ {
+ cache_hit = 2;
+ geom = geom2;
+ }
+ /* No cache hit. If we have a tree, free it. */
+ else
+ {
+ cache_hit = 0;
+ if ( cache->index )
+ {
+ index_free(cache);
+ cache->index = NULL;
+ }
+ }
+
+ /* Cache hit, but no tree built yet, build it! */
+ if ( cache_hit && ! cache->index )
+ {
+ LWGEOM *lwgeom = lwgeom_from_gserialized(geom);
+ int rv;
+
+ /* Can't build a tree on a NULL or empty */
+ if ( (!lwgeom) || lwgeom_is_empty(lwgeom) )
+ return NULL;
+
+ cache->argnum = cache_hit;
+ old_context = MemoryContextSwitchTo(FIContext(fcinfo));
+ rv = index_build(lwgeom, cache);
+ MemoryContextSwitchTo(old_context);
+
+ /* Something went awry in the tree build phase */
+ if ( ! rv )
+ {
+ cache->argnum = 0;
+ return NULL;
+ }
+
+ }
+
+ /* We have a hit and a calculated tree, we're done */
+ if ( cache_hit && (cache_hit == cache->argnum) && cache->index )
+ {
+ if ( argnum )
+ *argnum = cache_hit;
+ return cache->index;
+ }
+
+ /* Argument one didn't match, so copy the new value in. */
+ if ( geom1 && cache_hit != 1 )
+ {
+ if ( cache->geom1 ) pfree(cache->geom1);
+ cache->geom1_size = VARSIZE(geom1);
+ cache->geom1 = MemoryContextAlloc(FIContext(fcinfo), cache->geom1_size);
+ memcpy(cache->geom1, geom1, cache->geom1_size);
+ }
+ /* Argument two didn't match, so copy the new value in. */
+ if ( geom2 && cache_hit != 2 )
+ {
+ if ( cache->geom2 ) pfree(cache->geom2);
+ cache->geom2_size = VARSIZE(geom2);
+ cache->geom2 = MemoryContextAlloc(FIContext(fcinfo), cache->geom2_size);
+ memcpy(cache->geom2, geom2, cache->geom2_size);
+ }
+
+ return NULL;
+}
+
+
+
+/**
+* Builder, freeer and public accessor for cached CIRC_NODE trees
+*/
+static int
+CircTreeBuilder(const LWGEOM* lwgeom, GeomCache* cache)
+{
+ CIRC_NODE* tree = lwgeom_calculate_circ_tree(lwgeom);
+ if ( cache->index )
+ {
+ circ_tree_free((CIRC_NODE*)(cache->index));
+ cache->index = 0;
+ }
+ if ( ! tree )
+ return LW_FAILURE;
+
+ cache->index = (void*)tree;
+ return LW_SUCCESS;
+}
+
+static int
+CircTreeFreer(GeomCache* cache)
+{
+ CIRC_NODE* tree = (CIRC_NODE*)(cache->index);
+ if ( tree )
+ {
+ circ_tree_free(tree);
+ cache->index = 0;
+ }
+ return LW_SUCCESS;
+}
+
+CIRC_NODE*
+GetCircTree(FunctionCallInfoData* fcinfo, GSERIALIZED* g1, GSERIALIZED* g2, int* argnum_of_cache)
+{
+ int argnum = 0;
+ CIRC_NODE* tree = NULL;
+
+ tree = (CIRC_NODE*)GetGeomIndex(fcinfo, CIRC_CACHE_ENTRY, CircTreeBuilder, CircTreeFreer, g1, g2, &argnum);
+
+ if ( argnum_of_cache )
+ *argnum_of_cache = argnum;
+
+ return tree;
+}
+
+
--- /dev/null
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.refractions.net
+ *
+ * Copyright (C) 2012 Sandro Santilli <strk@keybit.net>
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#ifndef LWGEOM_CACHE_H_
+#define LWGEOM_CACHE_H_ 1
+
+#include "postgres.h"
+#include "fmgr.h"
+
+#include "liblwgeom_internal.h"
+#include "lwgeodetic.h";
+#include "lwgeodetic_tree.h"
+
+#include "lwgeom_pg.h"
+
+
+#define PROJ_CACHE_ENTRY 0
+#define PREP_CACHE_ENTRY 1
+#define RTREE_CACHE_ENTRY 2
+#define CIRC_CACHE_ENTRY 3
+#define RECT_CACHE_ENTRY 4
+#define NUM_CACHE_ENTRIES 5
+
+
+/*
+* For the geometry-with-tree case, we need space for
+* the serialized geometries and their sizes, so we can
+* test for cache hits/misses. The argnum tells us which
+* argument the tree is built for.
+*/
+typedef struct {
+ int type;
+ GSERIALIZED* geom1;
+ GSERIALIZED* geom2;
+ size_t geom1_size;
+ size_t geom2_size;
+ int32 argnum;
+ MemoryContext context_statement;
+ MemoryContext context_callback;
+ void* index;
+} GeomCache;
+
+
+/* An entry in the PROJ4 SRS cache */
+typedef struct struct_PROJ4SRSCacheItem
+{
+ int srid;
+ projPJ projection;
+ MemoryContext projection_mcxt;
+}
+PROJ4SRSCacheItem;
+
+/* PROJ 4 lookup transaction cache methods */
+#define PROJ4_CACHE_ITEMS 8
+
+/*
+* The proj4 cache holds a fixed number of reprojection
+* entries. In normal usage we don't expect it to have
+* many entries, so we always linearly scan the list.
+*/
+typedef struct struct_PROJ4PortalCache
+{
+ int type;
+ PROJ4SRSCacheItem PROJ4SRSCache[PROJ4_CACHE_ITEMS];
+ int PROJ4SRSCacheCount;
+ MemoryContext PROJ4SRSCacheContext;
+}
+PROJ4PortalCache;
+
+/*
+* Generic signature for function to take a serialized
+* geometry and return a tree structure for fast edge
+* access.
+*/
+typedef int (*GeomIndexBuilder)(const LWGEOM* lwgeom, GeomCache* cache);
+typedef int (*GeomIndexFreer)(GeomCache* cache);
+
+/*
+* Cache retrieval functions
+*/
+PROJ4PortalCache* GetPROJ4SRSCache(FunctionCallInfoData *fcinfo);
+GeomCache* GetGeomCache(FunctionCallInfoData *fcinfo, int cache_entry);
+CIRC_NODE* GetCircTree(FunctionCallInfoData* fcinfo, GSERIALIZED* g1, GSERIALIZED* g2, int* argnum_of_cache);
+
+/*
+* Given candidate geometries, a builer function and an entry type, cache and/or return an
+* appropriate tree.
+*/
+void* GetGeomIndex(FunctionCallInfoData* fcinfo, int cache_entry, GeomIndexBuilder index_build, GeomIndexFreer tree_free, const GSERIALIZED* g1, const GSERIALIZED* g2, int* argnum);
+
+
+#endif /* LWGEOM_CACHE_H_ */
Datum LWGEOM_addBBOX(PG_FUNCTION_ARGS);
Datum LWGEOM_dropBBOX(PG_FUNCTION_ARGS);
-#endif /* !defined _LWGEOM_PG_H 1 */
+#endif /* !defined _LWGEOM_PG_H */
#include "../postgis_config.h"
#include "liblwgeom.h"
#include "lwgeom_pg.h"
+#include "lwgeom_cache.h"
#include "lwgeom_transform.h"
/* C headers */
int pj_transform_nodatum(projPJ srcdefn, projPJ dstdefn, long point_count, int point_offset, double *x, double *y, double *z );
-/* PROJ 4 lookup transaction cache methods */
-#define PROJ4_CACHE_ITEMS 8
-
/*
* PROJ 4 backend hash table initial hash size
* (since 16 is the default portal hash table size, and we would
#define PROJ4_BACKEND_HASH_SIZE 32
-/* An entry in the PROJ4 SRS cache */
-typedef struct struct_PROJ4SRSCacheItem
-{
- int srid;
- projPJ projection;
- MemoryContext projection_mcxt;
-}
-PROJ4SRSCacheItem;
-
-/** The portal cache: it's contents and cache context
- */
-typedef struct struct_PROJ4PortalCache
-{
- PROJ4SRSCacheItem PROJ4SRSCache[PROJ4_CACHE_ITEMS];
- int PROJ4SRSCacheCount;
- MemoryContext PROJ4SRSCacheContext;
-}
-PROJ4PortalCache;
-
/**
* Backend projPJ hash table
*
static void DeletePJHashEntry(MemoryContext mcxt);
/* Internal Cache API */
-static PROJ4PortalCache *GetPROJ4SRSCache(FunctionCallInfo fcinfo) ;
+/* static PROJ4PortalCache *GetPROJ4SRSCache(FunctionCallInfo fcinfo) ; */
static bool IsInPROJ4SRSCache(PROJ4PortalCache *PROJ4Cache, int srid);
static projPJ GetProjectionFromPROJ4SRSCache(PROJ4PortalCache *PROJ4Cache, int srid);
static void AddToPROJ4SRSCache(PROJ4PortalCache *PROJ4Cache, int srid, int other_srid);
}
Proj4Cache GetPROJ4Cache(FunctionCallInfo fcinfo) {
- return (Proj4Cache)GetPROJ4SRSCache(fcinfo) ;
+ return (Proj4Cache)GetPROJ4SRSCache(fcinfo);
}
+#if 0
static PROJ4PortalCache *GetPROJ4SRSCache(FunctionCallInfo fcinfo)
{
- PROJ4PortalCache *PROJ4Cache ;
+ PROJ4PortalCache *PROJ4Cache = (GetGeomCache(fcinfo))->proj;
/*
* If we have not already created PROJ4 cache for this portal
return PROJ4Cache ;
}
-
+#endif
int
GetProjectionsUsingFCInfo(FunctionCallInfo fcinfo, int srid1, int srid2, projPJ *pj1, projPJ *pj2)
lwgeom_btree.o \
lwgeom_box.o \
lwgeom_box3d.o \
- lwgeom_cache.o \
lwgeom_geos.o \
lwgeom_geos_prepared.o \
lwgeom_geos_clean.o \
+++ /dev/null
-/**********************************************************************
- *
- * PostGIS - Spatial Types for PostgreSQL
- * http://postgis.refractions.net
- *
- * Copyright (C) 2012 Sandro Santilli <strk@keybit.net>
- *
- * 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 "postgres.h"
-#include "fmgr.h"
-
-#include "../postgis_config.h"
-#include "lwgeom_cache.h"
-
-GeomCache* GetGeomCache(FunctionCallInfoData *fcinfo)
-{
- MemoryContext old_context;
- GeomCache* cache = fcinfo->flinfo->fn_extra;
- if ( ! cache ) {
- old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
- cache = palloc(sizeof(GeomCache));
- MemoryContextSwitchTo(old_context);
- cache->prep = 0;
- cache->rtree = 0;
- fcinfo->flinfo->fn_extra = cache;
- }
- return cache;
-}
-
+++ /dev/null
-/**********************************************************************
- *
- * PostGIS - Spatial Types for PostgreSQL
- * http://postgis.refractions.net
- *
- * Copyright (C) 2012 Sandro Santilli <strk@keybit.net>
- *
- * This is free software; you can redistribute and/or modify it under
- * the terms of the GNU General Public Licence. See the COPYING file.
- *
- **********************************************************************/
-
-#ifndef LWGEOM_GEOS_CACHE_H_
-#define LWGEOM_GEOS_CACHE_H_ 1
-
-#include "postgres.h"
-#include "fmgr.h"
-
-#include "lwgeom_pg.h"
-#include "lwgeom_rtree.h"
-#include "lwgeom_geos_prepared.h"
-
-typedef struct {
- PrepGeomCache* prep;
- RTREE_POLY_CACHE* rtree;
-} GeomCache;
-
-GeomCache* GetGeomCache(FunctionCallInfoData *fcinfo);
-
-#endif /* LWGEOM_GEOS_CACHE_H_ 1 */
POSTGIS_DEBUG(2, "point_in_ring called.");
- lines = findLineSegments(root, point->y);
+ lines = RTreeFindLineSegments(root, point->y);
if (!lines)
return -1;
#include "../postgis_config.h"
#include "lwgeom_functions_analytic.h" /* for point_in_polygon */
-#include "lwgeom_cache.h"
#include "lwgeom_geos.h"
#include "liblwgeom_internal.h"
#include "lwgeom_rtree.h"
-
-/*
-** GEOS prepared geometry is only available from GEOS 3.1 onwards
-*/
-#define PREPARED_GEOM
-
-#ifdef PREPARED_GEOM
#include "lwgeom_geos_prepared.h"
-#endif
+
#include <string.h>
#include <assert.h>
** Prototypes end
*/
-static RTREE_POLY_CACHE *
-GetRtreeCache(FunctionCallInfoData *fcinfo, LWGEOM *lwgeom, GSERIALIZED *poly)
-{
- MemoryContext old_context;
- GeomCache* supercache = GetGeomCache(fcinfo);
- RTREE_POLY_CACHE *poly_cache = supercache->rtree;
-
- /*
- * Switch the context to the function-scope context,
- * retrieve the appropriate cache object, cache it for
- * future use, then switch back to the local context.
- */
- old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
- poly_cache = retrieveCache(lwgeom, poly, poly_cache);
- supercache->rtree = poly_cache;
- MemoryContextSwitchTo(old_context);
-
- return poly_cache;
-}
-
PG_FUNCTION_INFO_V1(postgis_geos_version);
Datum postgis_geos_version(PG_FUNCTION_ARGS)
LWPOINT *point;
RTREE_POLY_CACHE *poly_cache;
bool result;
-#ifdef PREPARED_GEOM
PrepGeomCache *prep_cache;
-#endif
geom1 = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
geom2 = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
POSTGIS_DEBUGF(3, "Precall point_in_multipolygon_rtree %p, %p", lwgeom, point);
- poly_cache = GetRtreeCache(fcinfo, lwgeom, geom1);
+ poly_cache = GetRtreeCache(fcinfo, geom1);
- if ( poly_cache->ringIndices )
+ if ( poly_cache && poly_cache->ringIndices )
{
result = point_in_multipolygon_rtree(poly_cache->ringIndices, poly_cache->polyCount, poly_cache->ringCounts, point);
}
initGEOS(lwnotice, lwgeom_geos_error);
-#ifdef PREPARED_GEOM
prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 );
if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 )
GEOSGeom_destroy(g1);
}
else
-#endif
{
g1 = (GEOSGeometry *)POSTGIS2GEOS(geom1);
if ( 0 == g1 ) /* exception thrown at construction */
GSERIALIZED * geom2;
bool result;
GBOX box1, box2;
-#ifdef PREPARED_GEOM
PrepGeomCache * prep_cache;
-#endif
geom1 = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
geom2 = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
initGEOS(lwnotice, lwgeom_geos_error);
-#ifdef PREPARED_GEOM
prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 );
if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 )
GEOSGeom_destroy(g);
}
else
-#endif
{
GEOSGeometry *g2;
GEOSGeometry *g1;
LWGEOM *lwgeom;
LWPOINT *point;
RTREE_POLY_CACHE *poly_cache;
-#ifdef PREPARED_GEOM
PrepGeomCache *prep_cache;
-#endif
geom1 = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
geom2 = (GSERIALIZED *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
gserialized_get_gbox_p(geom2, &box2) )
{
if (( box2.xmin < box1.xmin ) || ( box2.xmax > box1.xmax ) ||
- ( box2.ymin < box1.ymin ) || ( box2.ymax > box1.ymax ))
+ ( box2.ymin < box1.ymin ) || ( box2.ymax > box1.ymax ))
{
PG_RETURN_BOOL(FALSE);
}
POSTGIS_DEBUGF(3, "Precall point_in_multipolygon_rtree %p, %p", lwgeom, point);
- poly_cache = GetRtreeCache(fcinfo, lwgeom, geom1);
+ poly_cache = GetRtreeCache(fcinfo, geom1);
- if ( poly_cache->ringIndices )
+ if ( poly_cache && poly_cache->ringIndices )
{
result = point_in_multipolygon_rtree(poly_cache->ringIndices, poly_cache->polyCount, poly_cache->ringCounts, point);
}
initGEOS(lwnotice, lwgeom_geos_error);
-#ifdef PREPARED_GEOM
prep_cache = GetPrepGeomCache( fcinfo, geom1, 0 );
if ( prep_cache && prep_cache->prepared_geom && prep_cache->argnum == 1 )
GEOSGeom_destroy(g1);
}
else
-#endif
{
GEOSGeometry *g1;
GEOSGeometry *g2;
point = lwgeom_as_lwpoint(lwgeom_from_gserialized(geom1));
lwgeom = lwgeom_from_gserialized(geom2);
- poly_cache = GetRtreeCache(fcinfo, lwgeom, geom2);
+ poly_cache = GetRtreeCache(fcinfo, geom2);
- if ( poly_cache->ringIndices )
+ if ( poly_cache && poly_cache->ringIndices )
{
result = point_in_multipolygon_rtree(poly_cache->ringIndices, poly_cache->polyCount, poly_cache->ringCounts, point);
}
LWPOINT *point;
LWGEOM *lwgeom;
RTREE_POLY_CACHE *poly_cache;
-#ifdef PREPARED_GEOM
PrepGeomCache *prep_cache;
-#endif
geom1 = (GSERIALIZED *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
geom2 = (GSERIALIZED *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
type1 = gserialized_get_type(geom1);
type2 = gserialized_get_type(geom2);
if ( (type1 == POINTTYPE && (type2 == POLYGONTYPE || type2 == MULTIPOLYGONTYPE)) ||
- (type2 == POINTTYPE && (type1 == POLYGONTYPE || type1 == MULTIPOLYGONTYPE)))
+ (type2 == POINTTYPE && (type1 == POLYGONTYPE || type1 == MULTIPOLYGONTYPE)))
{
POSTGIS_DEBUG(3, "Point in Polygon test requested...short-circuiting.");
polytype = type1;
}
- poly_cache = GetRtreeCache(fcinfo, lwgeom, serialized_poly);
+ poly_cache = GetRtreeCache(fcinfo, serialized_poly);
- if ( poly_cache->ringIndices )
+ if ( poly_cache && poly_cache->ringIndices )
{
result = point_in_multipolygon_rtree(poly_cache->ringIndices, poly_cache->polyCount, poly_cache->ringCounts, point);
}
}
initGEOS(lwnotice, lwgeom_geos_error);
-#ifdef PREPARED_GEOM
prep_cache = GetPrepGeomCache( fcinfo, geom1, geom2 );
if ( prep_cache && prep_cache->prepared_geom )
}
}
else
-#endif
{
GEOSGeometry *g1;
GEOSGeometry *g2;
void errorIfGeometryCollection(GSERIALIZED *g1, GSERIALIZED *g2);
-#endif /* LWGEOM_GEOS_H_ 1 */
+#endif /* LWGEOM_GEOS_H_ */
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.
+/**
+* Given a generic GeomCache, and a geometry to prepare,
+* prepare a PrepGeomCache and stick it into the GeomCache->index
+* slot. The PrepGeomCache includes the original GEOS geometry,
+* and the GEOS prepared geometry, and a pointer to the
+* MemoryContext where the callback functions are registered.
+*
+* This function is passed into the generic GetGeomCache function
+* so that it can build an appropriate indexed structure in the case
+* of a cache hit when there is no indexed structure yet
+* available to return.
*/
-PrepGeomCache*
-GetPrepGeomCache(FunctionCallInfoData *fcinfo, GSERIALIZED *pg_geom1, GSERIALIZED *pg_geom2)
+static int
+PrepGeomCacheBuilder(const LWGEOM *lwgeom, GeomCache *cache)
{
- MemoryContext old_context;
- GeomCache* supercache = GetGeomCache(fcinfo);
- PrepGeomCache* cache = supercache->prep;
- int copy_keys = 1;
- size_t pg_geom1_size = 0;
- size_t pg_geom2_size = 0;
-
- assert ( ! cache || cache->type == 2 );
-
+ PrepGeomCache* prepcache;
+ PrepGeomHashEntry* pghe;
+
+ /*
+ * First time through? allocate the global hash.
+ */
if (!PrepGeomHash)
CreatePrepGeomHash();
- if ( pg_geom1 )
- pg_geom1_size = VARSIZE(pg_geom1);
-
- if ( pg_geom2 )
- pg_geom2_size = VARSIZE(pg_geom2);
-
- if ( cache == NULL)
+ /*
+ * No callback entry for this statement context yet? Set it up
+ */
+ if ( ! cache->context_callback )
{
- /*
- ** 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->type = 2;
- 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(3, "GetPrepGeomCache: creating cache: %p", cache);
-
- pghe.context = cache->context;
+ cache->context_callback = MemoryContextCreate(T_AllocSetContext, 8192,
+ &PreparedCacheContextMethods,
+ cache->context_statement,
+ "PostGIS Prepared Geometry Context");
+ pghe.context = cache->context_callback;
pghe.geom = 0;
pghe.prepared_geom = 0;
- AddPrepGeomHashEntry( pghe );
-
- supercache->prep = cache;
-
- POSTGIS_DEBUGF(3, "GetPrepGeomCache: adding context to hash: %p", cache);
+ AddPrepGeomHashEntry( pghe );
}
- else if ( pg_geom1 &&
- cache->argnum != 2 &&
- cache->pg_geom1_size == pg_geom1_size &&
- memcmp(cache->pg_geom1, pg_geom1, pg_geom1_size) == 0)
+
+ /*
+ * Hum, we shouldn't be asked to build a new cache on top of
+ * an existing one. Error.
+ */
+ if ( cache->index )
{
- 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(3, "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(3, "GetPrepGeomCache: cache hit, argument 1");
- }
- /* We don't need new keys until we have a cache miss */
- copy_keys = 0;
+ lwerror("PrepGeomCacheBuilder asked to build new prepcache where one already exists.");
+ return LW_FAILURE;
}
- else if ( pg_geom2 &&
- cache->argnum != 1 &&
- cache->pg_geom2_size == pg_geom2_size &&
- memcmp(cache->pg_geom2, pg_geom2, pg_geom2_size) == 0)
+
+ /*
+ * Allocate a new index structure
+ */
+ cache->index = MemoryContextAlloc(cache->context_statement, sizeof(PrepGeomCache));
+ memset(cache->index, 0, sizeof(PrepGeomCache));
+ prepcache = (PrepGeomCache*)(cache->index);
+
+ prepcache->geom = LWGEOM2GEOS( lwgeom );
+ if ( ! prepcache->geom ) return LW_FAILURE;
+ prepcache->prepared_geom = GEOSPrepare( prepcache->geom );
+ if ( ! prepcache->prepared_geom ) return LW_FAILURE;
+ prepcache->argnum = cache->argnum;
+
+ /*
+ * In order to find the objects we need to destroy, we keep
+ * extra references in a global hash object.
+ */
+ pghe = GetPrepGeomHashEntry(cache->context_callback);
+ if ( ! pghe )
{
- 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(3, "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(3, "GetPrepGeomCache: cache hit, argument 2");
- }
- /* We don't need new keys until we have a cache miss */
- copy_keys = 0;
+ lwerror("PrepGeomCacheBuilder failed to find hash entry for context %p", cache->context_callback);
+ return LW_FAILURE;
}
- else if ( cache->prepared_geom )
- {
- /*
- ** No cache hits, so this must be a miss.
- ** Destroy the GEOS objects, empty the cache.
- */
- PrepGeomHashEntry* pghe;
+
+ pghe->geom = prepcache->geom;
+ pghe->prepared_geom = prepcache->prepared_geom;
- pghe = GetPrepGeomHashEntry(cache->context);
- pghe->geom = 0;
- pghe->prepared_geom = 0;
-
- POSTGIS_DEBUGF(3, "GetPrepGeomCache: cache miss, argument %d", cache->argnum);
- GEOSPreparedGeom_destroy( cache->prepared_geom );
- GEOSGeom_destroy( (GEOSGeometry *)cache->geom );
-
- cache->prepared_geom = 0;
- cache->geom = 0;
- cache->argnum = 0;
-
- }
+ return LW_SUCCESS;
+}
- 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(3, "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 )
+/**
+* This function is passed into the generic GetGeomCache function
+* in the case of a cache miss, so that it can free the particular
+* indexed structure being managed.
+*
+* In the case of prepared geometry, we want to leave the actual
+* PrepGeomCache allocated and in place, but ensure that the
+* GEOS Geometry and PreparedGeometry are freed so we don't leak
+* memory as we transition from cache hit to miss to hit, etc.
+*/
+static int
+PrepGeomCacheCleaner(GeomCache *cache)
+{
+ PrepGeomHashEntry* pghe = 0;
+ PrepGeomCache* prepcache = (PrepGeomCache*)(cache->index);
+
+ if ( ! prepcache )
+ return LW_FAILURE;
+
+ /*
+ * Clear out the references to the soon-to-be-freed GEOS objects
+ * from the callback hash entry
+ */
+ pghe = GetPrepGeomHashEntry(cache->context_callback);
+ if ( ! pghe )
{
- POSTGIS_DEBUG(3, "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;
+ lwerror("PrepGeomCacheCleaner failed to find hash entry for context %p", cache->context_callback);
+ return LW_FAILURE;
}
+ pghe->geom = 0;
+ pghe->prepared_geom = 0;
+
+ /*
+ * Free the GEOS objects and free the index tree
+ */
+ POSTGIS_DEBUGF(3, "PrepGeomCacheFreeer: freeing %p argnum %d", prepcache, prepcache->argnum);
+ GEOSPreparedGeom_destroy( prepcache->prepared_geom );
+ GEOSGeom_destroy( (GEOSGeometry *)prepcache->geom );
+ pfree(cache->index);
+ cache->index = 0;
+
+ return LW_SUCCESS;
+}
+
+/**
+* Given a couple potential geometries and a function
+* call context, return a prepared structure for one
+* of them, if such a structure doesn't already exist.
+* If it doesn't exist, and there is a cache hit,
+* ensure that the structure is built for next time.
+* Most of the work is done by the GetGeomCache generic
+* function, but we pass in call-backs to handle building
+* and freeing the GEOS PreparedGeometry structures
+* we need for this particular caching strategy.
+*/
+PrepGeomCache*
+GetPrepGeomCache(FunctionCallInfoData* fcinfo, GSERIALIZED* g1, GSERIALIZED* g2)
+{
+ int argnum = 0;
+ PrepGeomCache* prepcache = NULL;
- return cache;
+ prepcache = (PrepGeomCache*)GetGeomIndex(fcinfo, PREP_CACHE_ENTRY, PrepGeomCacheBuilder, PrepGeomCacheCleaner, g1, g2, &argnum);
+ return prepcache;
}
** 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.
-*/
typedef struct
{
char type;
MemoryContext context;
}
PrepGeomCache;
+*/
+
+typedef struct
+{
+ int argnum;
+ const GEOSPreparedGeometry *prepared_geom;
+ const GEOSGeometry *geom;
+}
+PrepGeomCache;
/*
** Get the current cache, given the input geometries.
*/
PrepGeomCache *GetPrepGeomCache(FunctionCallInfoData *fcinfo, GSERIALIZED *pg_geom1, GSERIALIZED *pg_geom2);
-#endif /* LWGEOM_GEOS_PREPARED_H_ 1 */
+#endif /* LWGEOM_GEOS_PREPARED_H_ */
/* Write to WKT and free the geometry */
wkt = lwgeom_to_wkt(lwgeom, WKT_ISO, DBL_DIG, &wkt_size);
lwgeom_free(lwgeom);
- POSTGIS_DEBUGF(3, "WKT size = %d, WKT length = %d", wkt_size, strlen(wkt));
+ POSTGIS_DEBUGF(3, "WKT size = %u, WKT length = %u", (unsigned int)wkt_size, (unsigned int)strlen(wkt));
/* Write to text and free the WKT */
result = cstring2text(wkt);
#include "lwgeom_pg.h"
#include "liblwgeom.h"
#include "liblwgeom_internal.h" /* For FP comparators. */
+#include "lwgeom_cache.h"
#include "lwgeom_rtree.h"
-Datum LWGEOM_polygon_index(PG_FUNCTION_ARGS);
-
+/* Prototypes */
+static void RTreeFree(RTREE_NODE* root);
/**
- * Creates an rtree given a pointer to the point array.
- * Must copy the point array.
- */
-RTREE_NODE *createTree(POINTARRAY *pointArray)
+* Allocate a fresh clean RTREE_POLY_CACHE
+*/
+static RTREE_POLY_CACHE*
+RTreeCacheCreate()
{
- RTREE_NODE *root;
- RTREE_NODE** nodes = lwalloc(sizeof(RTREE_NODE*) * pointArray->npoints);
- int i, nodeCount;
- int childNodes, parentNodes;
-
- POSTGIS_DEBUGF(2, "createTree called with pointarray %p", pointArray);
-
- nodeCount = pointArray->npoints - 1;
+ RTREE_POLY_CACHE* result;
+ result = lwalloc(sizeof(RTREE_POLY_CACHE));
+ memset(result, 0, sizeof(RTREE_POLY_CACHE));
+ return result;
+}
- POSTGIS_DEBUGF(3, "Total leaf nodes: %d", nodeCount);
+/**
+* Recursively frees the child nodes, the interval and the line before
+* freeing the root node.
+*/
+static void
+RTreeFree(RTREE_NODE* root)
+{
+ POSTGIS_DEBUGF(2, "RTreeFree called for %p", root);
- /*
- * Create a leaf node for every line segment.
- */
- for (i = 0; i < nodeCount; i++)
+ if (root->leftNode)
+ RTreeFree(root->leftNode);
+ if (root->rightNode)
+ RTreeFree(root->rightNode);
+ lwfree(root->interval);
+ if (root->segment)
{
- nodes[i] = createLeafNode(pointArray, i);
+ lwline_free(root->segment);
}
+ lwfree(root);
+}
- /*
- * Next we group nodes by pairs. If there's an odd number of nodes,
- * we bring the last node up a level as is. Continue until we have
- * a single top node.
- */
- childNodes = nodeCount;
- parentNodes = nodeCount / 2;
- while (parentNodes > 0)
+/**
+* Free the cache object and all the sub-objects properly.
+*/
+static void
+RTreeCacheClear(RTREE_POLY_CACHE* cache)
+{
+ int g, r, i;
+ POSTGIS_DEBUGF(2, "RTreeCacheClear called for %p", cache);
+ i = 0;
+ for (g = 0; g < cache->polyCount; g++)
{
- POSTGIS_DEBUGF(3, "Merging %d children into %d parents.", childNodes, parentNodes);
-
- i = 0;
- while (i < parentNodes)
+ for (r = 0; r < cache->ringCounts[g]; r++)
{
- nodes[i] = createInteriorNode(nodes[i*2], nodes[i*2+1]);
+ RTreeFree(cache->ringIndices[i]);
i++;
}
- /*
- * Check for an odd numbered final node.
- */
- if (parentNodes * 2 < childNodes)
- {
- POSTGIS_DEBUGF(3, "Shuffling child %d to parent %d", childNodes - 1, i);
-
- nodes[i] = nodes[childNodes - 1];
- parentNodes++;
- }
- childNodes = parentNodes;
- parentNodes = parentNodes / 2;
}
+ lwfree(cache->ringIndices);
+ lwfree(cache->ringCounts);
+ cache->ringIndices = 0;
+ cache->ringCounts = 0;
+ cache->polyCount = 0;
+}
- root = nodes[0];
- lwfree(nodes);
- POSTGIS_DEBUGF(3, "createTree returning %p", root);
- return root;
+/**
+ * Returns 1 if min < value <= max, 0 otherwise.
+*/
+static uint32
+IntervalIsContained(RTREE_INTERVAL* interval, double value)
+{
+ return FP_CONTAINS_INCL(interval->min, value, interval->max) ? 1 : 0;
+}
+
+/**
+* Creates an interval with the total extents of the two given intervals.
+*/
+static RTREE_INTERVAL*
+RTreeMergeIntervals(RTREE_INTERVAL *inter1, RTREE_INTERVAL *inter2)
+{
+ RTREE_INTERVAL *interval;
+
+ POSTGIS_DEBUGF(2, "RTreeMergeIntervals called with %p, %p", inter1, inter2);
+
+ interval = lwalloc(sizeof(RTREE_INTERVAL));
+ interval->max = FP_MAX(inter1->max, inter2->max);
+ interval->min = FP_MIN(inter1->min, inter2->min);
+
+ POSTGIS_DEBUGF(3, "interval min = %8.3f, max = %8.3f", interval->min, interval->max);
+
+ return interval;
}
/**
- * Creates an interior node given the children.
- */
-RTREE_NODE *createInteriorNode(RTREE_NODE *left, RTREE_NODE *right)
+* Creates an interval given the min and max values, in arbitrary order.
+*/
+static RTREE_INTERVAL*
+RTreeCreateInterval(double value1, double value2)
+{
+ RTREE_INTERVAL *interval;
+
+ POSTGIS_DEBUGF(2, "RTreeCreateInterval called with %8.3f, %8.3f", value1, value2);
+
+ interval = lwalloc(sizeof(RTREE_INTERVAL));
+ interval->max = FP_MAX(value1, value2);
+ interval->min = FP_MIN(value1, value2);
+
+ POSTGIS_DEBUGF(3, "interval min = %8.3f, max = %8.3f", interval->min, interval->max);
+
+ return interval;
+}
+
+/**
+* Creates an interior node given the children.
+*/
+static RTREE_NODE*
+RTreeCreateInteriorNode(RTREE_NODE* left, RTREE_NODE* right)
{
RTREE_NODE *parent;
- POSTGIS_DEBUGF(2, "createInteriorNode called for children %p, %p", left, right);
+ POSTGIS_DEBUGF(2, "RTreeCreateInteriorNode called for children %p, %p", left, right);
parent = lwalloc(sizeof(RTREE_NODE));
parent->leftNode = left;
parent->rightNode = right;
- parent->interval = mergeIntervals(left->interval, right->interval);
+ parent->interval = RTreeMergeIntervals(left->interval, right->interval);
parent->segment = NULL;
- POSTGIS_DEBUGF(3, "createInteriorNode returning %p", parent);
+ POSTGIS_DEBUGF(3, "RTreeCreateInteriorNode returning %p", parent);
return parent;
}
/**
- * Creates a leaf node given the pointer to the start point of the segment.
- */
-RTREE_NODE *createLeafNode(POINTARRAY *pa, int startPoint)
+* Creates a leaf node given the pointer to the start point of the segment.
+*/
+static RTREE_NODE*
+RTreeCreateLeafNode(POINTARRAY* pa, int startPoint)
{
RTREE_NODE *parent;
LWLINE *line;
POINT4D tmp;
POINTARRAY *npa;
- POSTGIS_DEBUGF(2, "createLeafNode called for point %d of %p", startPoint, pa);
+ POSTGIS_DEBUGF(2, "RTreeCreateLeafNode called for point %d of %p", startPoint, pa);
if (pa->npoints < startPoint + 2)
{
- lwerror("createLeafNode: npoints = %d, startPoint = %d", pa->npoints, startPoint);
+ lwerror("RTreeCreateLeafNode: npoints = %d, startPoint = %d", pa->npoints, startPoint);
}
/*
line = lwline_construct(SRID_UNKNOWN, NULL, npa);
parent = lwalloc(sizeof(RTREE_NODE));
- parent->interval = createInterval(value1, value2);
+ parent->interval = RTreeCreateInterval(value1, value2);
parent->segment = line;
parent->leftNode = NULL;
parent->rightNode = NULL;
- POSTGIS_DEBUGF(3, "createLeafNode returning %p", parent);
+ POSTGIS_DEBUGF(3, "RTreeCreateLeafNode returning %p", parent);
return parent;
}
/**
- * Creates an interval with the total extents of the two given intervals.
- */
-INTERVAL *mergeIntervals(INTERVAL *inter1, INTERVAL *inter2)
+* Creates an rtree given a pointer to the point array.
+* Must copy the point array.
+*/
+static RTREE_NODE*
+RTreeCreate(POINTARRAY* pointArray)
{
- INTERVAL *interval;
-
- POSTGIS_DEBUGF(2, "mergeIntervals called with %p, %p", inter1, inter2);
-
- interval = lwalloc(sizeof(INTERVAL));
- interval->max = FP_MAX(inter1->max, inter2->max);
- interval->min = FP_MIN(inter1->min, inter2->min);
-
- POSTGIS_DEBUGF(3, "interval min = %8.3f, max = %8.3f", interval->min, interval->max);
-
- return interval;
-}
-
-/**
- * Creates an interval given the min and max values, in arbitrary order.
- */
-INTERVAL *createInterval(double value1, double value2)
-{
- INTERVAL *interval;
-
- POSTGIS_DEBUGF(2, "createInterval called with %8.3f, %8.3f", value1, value2);
-
- interval = lwalloc(sizeof(INTERVAL));
- interval->max = FP_MAX(value1, value2);
- interval->min = FP_MIN(value1, value2);
-
- POSTGIS_DEBUGF(3, "interval min = %8.3f, max = %8.3f", interval->min, interval->max);
-
- return interval;
-}
-
-/**
- * Recursively frees the child nodes, the interval and the line before
- * freeing the root node.
- */
-void freeTree(RTREE_NODE *root)
-{
- POSTGIS_DEBUGF(2, "freeTree called for %p", root);
-
- if (root->leftNode)
- freeTree(root->leftNode);
- if (root->rightNode)
- freeTree(root->rightNode);
- lwfree(root->interval);
- if (root->segment)
- {
- lwline_free(root->segment);
- }
- lwfree(root);
-}
-
-
-/**
- * Free the cache object and all the sub-objects properly.
- */
-void clearCache(RTREE_POLY_CACHE *cache)
-{
- int g, r, i;
- POSTGIS_DEBUGF(2, "clearCache called for %p", cache);
- i = 0;
- for (g = 0; g < cache->polyCount; g++)
- {
- for (r = 0; r < cache->ringCounts[g]; r++)
- {
- freeTree(cache->ringIndices[i]);
- i++;
- }
- }
- lwfree(cache->ringIndices);
- lwfree(cache->ringCounts);
- lwfree(cache->poly);
- cache->poly = 0;
- cache->ringIndices = 0;
- cache->ringCounts = 0;
- cache->polyCount = 0;
-}
-
-
-/**
- * Retrieves a collection of line segments given the root and crossing value.
- * The collection is a multilinestring consisting of two point lines
- * representing the segments of the ring that may be crossed by the
- * horizontal projection line at the given y value.
- */
-LWMLINE *findLineSegments(RTREE_NODE *root, double value)
-{
- LWMLINE *tmp, *result;
- LWGEOM **lwgeoms;
-
- POSTGIS_DEBUGF(2, "findLineSegments called for tree %p and value %8.3f", root, value);
+ RTREE_NODE* root;
+ RTREE_NODE** nodes = lwalloc(pointArray->npoints * sizeof(RTREE_NODE*));
+ int i, nodeCount;
+ int childNodes, parentNodes;
- result = NULL;
+ POSTGIS_DEBUGF(2, "RTreeCreate called with pointarray %p", pointArray);
- if (!isContained(root->interval, value))
- {
- POSTGIS_DEBUGF(3, "findLineSegments %p: not contained.", root);
+ nodeCount = pointArray->npoints - 1;
- return NULL;
- }
+ POSTGIS_DEBUGF(3, "Total leaf nodes: %d", nodeCount);
- /* If there is a segment defined for this node, include it. */
- if (root->segment)
+ /*
+ * Create a leaf node for every line segment.
+ */
+ for (i = 0; i < nodeCount; i++)
{
- POSTGIS_DEBUGF(3, "findLineSegments %p: adding segment %p %d.", root, root->segment, root->segment->type);
-
- lwgeoms = lwalloc(sizeof(LWGEOM *));
- lwgeoms[0] = (LWGEOM *)root->segment;
-
- POSTGIS_DEBUGF(3, "Found geom %p, type %d, dim %d", root->segment, root->segment->type, FLAGS_GET_Z(root->segment->flags));
-
- result = (LWMLINE *)lwcollection_construct(MULTILINETYPE, SRID_UNKNOWN, NULL, 1, lwgeoms);
+ nodes[i] = RTreeCreateLeafNode(pointArray, i);
}
- /* If there is a left child node, recursively include its results. */
- if (root->leftNode)
+ /*
+ * Next we group nodes by pairs. If there's an odd number of nodes,
+ * we bring the last node up a level as is. Continue until we have
+ * a single top node.
+ */
+ childNodes = nodeCount;
+ parentNodes = nodeCount / 2;
+ while (parentNodes > 0)
{
- POSTGIS_DEBUGF(3, "findLineSegments %p: recursing left.", root);
+ POSTGIS_DEBUGF(3, "Merging %d children into %d parents.", childNodes, parentNodes);
- tmp = findLineSegments(root->leftNode, value);
- if (tmp)
+ i = 0;
+ while (i < parentNodes)
{
- POSTGIS_DEBUGF(3, "Found geom %p, type %d, dim %d", tmp, tmp->type, FLAGS_GET_Z(tmp->flags));
-
- if (result)
- result = mergeMultiLines(result, tmp);
- else
- result = tmp;
+ nodes[i] = RTreeCreateInteriorNode(nodes[i*2], nodes[i*2+1]);
+ i++;
}
- }
-
- /* Same for any right child. */
- if (root->rightNode)
- {
- POSTGIS_DEBUGF(3, "findLineSegments %p: recursing right.", root);
-
- tmp = findLineSegments(root->rightNode, value);
- if (tmp)
+ /*
+ * Check for an odd numbered final node.
+ */
+ if (parentNodes * 2 < childNodes)
{
- POSTGIS_DEBUGF(3, "Found geom %p, type %d, dim %d", tmp, tmp->type, FLAGS_GET_Z(tmp->flags));
+ POSTGIS_DEBUGF(3, "Shuffling child %d to parent %d", childNodes - 1, i);
- if (result)
- result = mergeMultiLines(result, tmp);
- else
- result = tmp;
+ nodes[i] = nodes[childNodes - 1];
+ parentNodes++;
}
+ childNodes = parentNodes;
+ parentNodes = parentNodes / 2;
}
- return result;
+ root = nodes[0];
+ lwfree(nodes);
+ POSTGIS_DEBUGF(3, "RTreeCreate returning %p", root);
+
+ return root;
}
-/** Merges two multilinestrings into a single multilinestring. */
-LWMLINE *mergeMultiLines(LWMLINE *line1, LWMLINE *line2)
+
+/**
+* Merges two multilinestrings into a single multilinestring.
+*/
+static LWMLINE*
+RTreeMergeMultiLines(LWMLINE *line1, LWMLINE *line2)
{
LWGEOM **geoms;
LWCOLLECTION *col;
int i, j, ngeoms;
- POSTGIS_DEBUGF(2, "mergeMultiLines called on %p, %d, %d; %p, %d, %d", line1, line1->ngeoms, line1->type, line2, line2->ngeoms, line2->type);
+ POSTGIS_DEBUGF(2, "RTreeMergeMultiLines called on %p, %d, %d; %p, %d, %d", line1, line1->ngeoms, line1->type, line2, line2->ngeoms, line2->type);
ngeoms = line1->ngeoms + line2->ngeoms;
geoms = lwalloc(sizeof(LWGEOM *) * ngeoms);
}
col = lwcollection_construct(MULTILINETYPE, SRID_UNKNOWN, NULL, ngeoms, geoms);
- POSTGIS_DEBUGF(3, "mergeMultiLines returning %p, %d, %d", col, col->ngeoms, col->type);
+ POSTGIS_DEBUGF(3, "RTreeMergeMultiLines returning %p, %d, %d", col, col->ngeoms, col->type);
return (LWMLINE *)col;
}
-/**
- * Returns 1 if min < value <= max, 0 otherwise. */
-uint32 isContained(INTERVAL *interval, double value)
-{
- return FP_CONTAINS_INCL(interval->min, value, interval->max) ? 1 : 0;
-}
-
-PG_FUNCTION_INFO_V1(LWGEOM_polygon_index);
-Datum LWGEOM_polygon_index(PG_FUNCTION_ARGS)
-{
- GSERIALIZED *igeom, *result;
- LWPOLY *poly;
- LWMLINE *mline;
- RTREE_NODE *root;
- double yval;
-#if POSTGIS_DEBUG_LEVEL >= 3
- int i = 0;
-#endif
-
- POSTGIS_DEBUG(2, "polygon_index called.");
-
- result = NULL;
- igeom = (GSERIALIZED *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
- yval = PG_GETARG_FLOAT8(1);
- if ( gserialized_get_type(igeom) != POLYGONTYPE )
- {
- PG_FREE_IF_COPY(igeom, 0);
- PG_RETURN_NULL();
- }
- poly = lwgeom_as_lwpoly(lwgeom_from_gserialized(igeom));
- root = createTree(poly->rings[0]);
-
- mline = findLineSegments(root, yval);
-
-#if POSTGIS_DEBUG_LEVEL >= 3
- POSTGIS_DEBUGF(3, "mline returned %p %d", mline, mline->type);
- for (i = 0; i < mline->ngeoms; i++)
- {
- POSTGIS_DEBUGF(3, "geom[%d] %p %d", i, mline->geoms[i], mline->geoms[i]->type);
- }
-#endif
-
- if (mline)
- result = geometry_serialize((LWGEOM *)mline);
-
- POSTGIS_DEBUGF(3, "returning result %p", result);
-
- lwfree(root);
-
- lwpoly_free(poly);
- lwmline_free(mline);
- PG_FREE_IF_COPY(igeom, 0);
- PG_RETURN_POINTER(result);
-
-}
-
-RTREE_POLY_CACHE * createCache()
-{
- RTREE_POLY_CACHE *result;
- result = lwalloc(sizeof(RTREE_POLY_CACHE));
- result->polyCount = 0;
- result->ringCounts = 0;
- result->ringIndices = 0;
- result->poly = 0;
- result->type = 1;
- return result;
-}
-void populateCache(RTREE_POLY_CACHE *currentCache, LWGEOM *lwgeom, GSERIALIZED *serializedPoly)
+/**
+* Callback function sent into the GetGeomCache generic caching system. Given a
+* LWGEOM* this function builds and stores an RTREE_POLY_CACHE into the provided
+* GeomCache object.
+*/
+static int
+RTreeBuilder(const LWGEOM* lwgeom, GeomCache* cache)
{
- int i, p, r, length;
+ int i, p, r;
LWMPOLY *mpoly;
LWPOLY *poly;
int nrings;
-
- POSTGIS_DEBUGF(2, "populateCache called with cache %p geom %p", currentCache, lwgeom);
-
+ RTREE_POLY_CACHE* currentCache;
+
+ if ( ! cache )
+ return LW_FAILURE;
+
if (lwgeom->type == MULTIPOLYGONTYPE)
{
- POSTGIS_DEBUG(2, "populateCache MULTIPOLYGON");
+ POSTGIS_DEBUG(2, "RTreeBuilder MULTIPOLYGON");
mpoly = (LWMPOLY *)lwgeom;
nrings = 0;
/*
** Count the total number of rings.
*/
+ currentCache = RTreeCacheCreate();
currentCache->polyCount = mpoly->ngeoms;
currentCache->ringCounts = lwalloc(sizeof(int) * mpoly->ngeoms);
for ( i = 0; i < mpoly->ngeoms; i++ )
{
- currentCache->ringCounts[i] = mpoly->geoms[i]->nrings;
+ currentCache->ringCounts[i] = mpoly->geoms[i]->nrings;
nrings += mpoly->geoms[i]->nrings;
}
currentCache->ringIndices = lwalloc(sizeof(RTREE_NODE *) * nrings);
{
for ( r = 0; r < mpoly->geoms[p]->nrings; r++ )
{
- currentCache->ringIndices[i] = createTree(mpoly->geoms[p]->rings[r]);
+ currentCache->ringIndices[i] = RTreeCreate(mpoly->geoms[p]->rings[r]);
i++;
}
}
+ cache->index = (void*)currentCache;
}
else if ( lwgeom->type == POLYGONTYPE )
{
- POSTGIS_DEBUG(2, "populateCache POLYGON");
+ POSTGIS_DEBUG(2, "RTreeBuilder POLYGON");
poly = (LWPOLY *)lwgeom;
+ currentCache = RTreeCacheCreate();
currentCache->polyCount = 1;
currentCache->ringCounts = lwalloc(sizeof(int));
currentCache->ringCounts[0] = poly->nrings;
currentCache->ringIndices = lwalloc(sizeof(RTREE_NODE *) * poly->nrings);
for ( i = 0; i < poly->nrings; i++ )
{
- currentCache->ringIndices[i] = createTree(poly->rings[i]);
+ currentCache->ringIndices[i] = RTreeCreate(poly->rings[i]);
}
+ cache->index = (void*)currentCache;
}
else
{
/* Uh oh, shouldn't be here. */
- return;
+ lwerror("RTreeBuilder got asked to build index on non-polygon");
+ return LW_FAILURE;
}
+ return LW_SUCCESS;
+}
- /*
- ** Copy the serialized form of the polygon into the cache so
- ** we can test for equality against subsequent polygons.
- */
- length = VARSIZE(serializedPoly);
- currentCache->poly = lwalloc(length);
- memcpy(currentCache->poly, serializedPoly, length);
- POSTGIS_DEBUGF(3, "populateCache returning %p", currentCache);
+/**
+* Callback function sent into the GetGeomCache generic caching system. On a
+* cache miss, this function clears the cached index object.
+*/
+static int
+RTreeFreer(GeomCache* cache)
+{
+ if ( ! cache )
+ return LW_FAILURE;
+
+ if ( cache->index )
+ {
+ RTREE_POLY_CACHE* currentCache = (RTREE_POLY_CACHE*)(cache->index);
+ RTreeCacheClear(currentCache);
+ lwfree(currentCache);
+ cache->index = 0;
+ }
+ return LW_SUCCESS;
+}
+
+RTREE_POLY_CACHE*
+GetRtreeCache(FunctionCallInfoData* fcinfo, GSERIALIZED* g1)
+{
+ int argnum = 0;
+ RTREE_POLY_CACHE* index = NULL;
+
+ index = (RTREE_POLY_CACHE*)GetGeomIndex(fcinfo, RTREE_CACHE_ENTRY, RTreeBuilder, RTreeFreer, g1, 0, &argnum);
+
+ return index;
}
+
/**
- * Creates a new cachable index if needed, or returns the current cache if
- * it is applicable to the current polygon.
- * The memory context must be changed to function scope before calling this
- * method. The method will allocate memory for the cache it creates,
- * as well as freeing the memory of any cache that is no longer applicable.
- */
-RTREE_POLY_CACHE *retrieveCache(LWGEOM *lwgeom, GSERIALIZED *serializedPoly, RTREE_POLY_CACHE *currentCache)
+* Retrieves a collection of line segments given the root and crossing value.
+* The collection is a multilinestring consisting of two point lines
+* representing the segments of the ring that may be crossed by the
+* horizontal projection line at the given y value.
+*/
+LWMLINE *RTreeFindLineSegments(RTREE_NODE *root, double value)
{
- int length;
+ LWMLINE *tmp, *result;
+ LWGEOM **lwgeoms;
- POSTGIS_DEBUGF(2, "retrieveCache called with %p %p %p", lwgeom, serializedPoly, currentCache);
+ POSTGIS_DEBUGF(2, "RTreeFindLineSegments called for tree %p and value %8.3f", root, value);
- assert ( ! currentCache || currentCache->type == 1 );
+ result = NULL;
- if (!currentCache)
+ if (!IntervalIsContained(root->interval, value))
{
- POSTGIS_DEBUG(3, "No existing cache, create one.");
- return createCache();
+ POSTGIS_DEBUGF(3, "RTreeFindLineSegments %p: not contained.", root);
+
+ return NULL;
}
- if (!(currentCache->poly))
+
+ /* If there is a segment defined for this node, include it. */
+ if (root->segment)
{
- POSTGIS_DEBUG(3, "Cache contains no polygon, populating it.");
- populateCache(currentCache, lwgeom, serializedPoly);
- return currentCache;
- }
+ POSTGIS_DEBUGF(3, "RTreeFindLineSegments %p: adding segment %p %d.", root, root->segment, root->segment->type);
- length = VARSIZE(serializedPoly);
+ lwgeoms = lwalloc(sizeof(LWGEOM *));
+ lwgeoms[0] = (LWGEOM *)root->segment;
- if (VARSIZE(currentCache->poly) != length)
- {
- POSTGIS_DEBUG(3, "Polygon size mismatch, creating new cache.");
- clearCache(currentCache);
- return currentCache;
+ POSTGIS_DEBUGF(3, "Found geom %p, type %d, dim %d", root->segment, root->segment->type, FLAGS_GET_Z(root->segment->flags));
+
+ result = (LWMLINE *)lwcollection_construct(MULTILINETYPE, SRID_UNKNOWN, NULL, 1, lwgeoms);
}
- if ( memcmp(serializedPoly, currentCache->poly, length) )
+
+ /* If there is a left child node, recursively include its results. */
+ if (root->leftNode)
{
- POSTGIS_DEBUG(3, "Polygon mismatch, creating new cache.");
- clearCache(currentCache);
- return currentCache;
+ POSTGIS_DEBUGF(3, "RTreeFindLineSegments %p: recursing left.", root);
+
+ tmp = RTreeFindLineSegments(root->leftNode, value);
+ if (tmp)
+ {
+ POSTGIS_DEBUGF(3, "Found geom %p, type %d, dim %d", tmp, tmp->type, FLAGS_GET_Z(tmp->flags));
+
+ if (result)
+ result = RTreeMergeMultiLines(result, tmp);
+ else
+ result = tmp;
+ }
}
- POSTGIS_DEBUGF(3, "Polygon match, retaining current cache, %p.",
- currentCache);
+ /* Same for any right child. */
+ if (root->rightNode)
+ {
+ POSTGIS_DEBUGF(3, "RTreeFindLineSegments %p: recursing right.", root);
+
+ tmp = RTreeFindLineSegments(root->rightNode, value);
+ if (tmp)
+ {
+ POSTGIS_DEBUGF(3, "Found geom %p, type %d, dim %d", tmp, tmp->type, FLAGS_GET_Z(tmp->flags));
+
+ if (result)
+ result = RTreeMergeMultiLines(result, tmp);
+ else
+ result = tmp;
+ }
+ }
- return currentCache;
+ return result;
}
+
+
#ifndef _LWGEOM_RTREE_H
-#define _LWGEOM_RTREE_H
+#define _LWGEOM_RTREE_H 1
#include "liblwgeom.h"
+/**
+* Representation for the y-axis interval spanned by an edge.
+*/
typedef struct
{
double min;
double max;
}
-INTERVAL;
+RTREE_INTERVAL;
-/* Returns 1 if min < value <= max, 0 otherwise */
-uint32 isContained(INTERVAL *interval, double value);
-/* Creates an interval given the min and max values, in whatever order. */
-INTERVAL *createInterval(double value1, double value2);
-/* Creates an interval with the total extents of the two given intervals. */
-INTERVAL *mergeIntervals(INTERVAL *inter1, INTERVAL *inter2);
-
-/*
- * The following struct and methods are used for a 1D RTree implementation,
- * described at:
- * http://lin-ear-th-inking.blogspot.com/2007/06/packed-1-dimensional-r-tree.html
- */
+/**
+* The following struct and methods are used for a 1D RTree implementation,
+* described at:
+* http://lin-ear-th-inking.blogspot.com/2007/06/packed-1-dimensional-r-tree.html
+*/
typedef struct rtree_node
{
- INTERVAL *interval;
+ RTREE_INTERVAL *interval;
struct rtree_node *leftNode;
struct rtree_node *rightNode;
LWLINE *segment;
}
RTREE_NODE;
-/* Creates an interior node given the children. */
-RTREE_NODE *createInteriorNode(RTREE_NODE *left, RTREE_NODE *right);
-/* Creates a leaf node given the pointer to the start point of the segment. */
-RTREE_NODE *createLeafNode(POINTARRAY *pa, int startPoint);
-/*
- * Creates an rtree given a pointer to the point array.
- * Must copy the point array.
- */
-RTREE_NODE *createTree(POINTARRAY *pointArray);
-/* Frees the tree. */
-void freeTree(RTREE_NODE *root);
-/* Retrieves a collection of line segments given the root and crossing value. */
-LWMLINE *findLineSegments(RTREE_NODE *root, double value);
-/* Merges two multilinestrings into a single multilinestring. */
-LWMLINE *mergeMultiLines(LWMLINE *line1, LWMLINE *line2);
-
+/**
+* The tree structure used for fast P-i-P tests by point_in_multipolygon_rtree()
+*/
typedef struct
{
- char type;
RTREE_NODE **ringIndices;
int* ringCounts;
int polyCount;
- GSERIALIZED *poly;
}
RTREE_POLY_CACHE;
-/*
- * Creates a new cachable index if needed, or returns the current cache if
- * it is applicable to the current polygon.
- */
-RTREE_POLY_CACHE *retrieveCache(LWGEOM *lwgeom, GSERIALIZED *serializedPoly, RTREE_POLY_CACHE *currentCache);
-RTREE_POLY_CACHE *createCache(void);
-/* Frees the cache. */
-void populateCache(RTREE_POLY_CACHE *cache, LWGEOM *lwgeom, GSERIALIZED *serializedPoly);
-void clearCache(RTREE_POLY_CACHE *cache);
+/**
+* Retrieves a collection of line segments given the root and crossing value.
+*/
+LWMLINE *RTreeFindLineSegments(RTREE_NODE *root, double value);
+
+/**
+* Checks for a cache hit against the provided geometry and returns
+* a pre-built index structure (RTREE_POLY_CACHE) if one exists. Otherwise
+* builds a new one and returns that.
+*/
+RTREE_POLY_CACHE* GetRtreeCache(FunctionCallInfoData* fcinfo, GSERIALIZED* g1);
-#endif /* !defined _LIBLWGEOM_H */
+#endif /* !defined _LWGEOM_RTREE_H */