Update the caching infrastructure to allow for arbitrary new caching systems to be combined into one fn_extra pointer slot. Clean up the 1-d r-tree code a little bit. Make the cache handling code for geometry caching generic so it can be re-used across different internal indexing approaches

git-svn-id: http://svn.osgeo.org/postgis/trunk@9918 b70326c6-7e19-0410-871a-916f4a2858ee
This commit is contained in:
Paul Ramsey 2012-06-15 23:11:03 +00:00
parent 8c8c6a3824
commit c2a95f250f
16 changed files with 893 additions and 707 deletions

View file

@ -23,12 +23,14 @@ LEX=@LEX@
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

308
libpgcommon/lwgeom_cache.c Normal file
View file

@ -0,0 +1,308 @@
/**********************************************************************
*
* 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;
}

101
libpgcommon/lwgeom_cache.h Normal file
View file

@ -0,0 +1,101 @@
/**********************************************************************
*
* 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_ */

View file

@ -154,4 +154,4 @@ Datum LWGEOM_getBBOX(PG_FUNCTION_ARGS);
Datum LWGEOM_addBBOX(PG_FUNCTION_ARGS);
Datum LWGEOM_dropBBOX(PG_FUNCTION_ARGS);
#endif /* !defined _LWGEOM_PG_H 1 */
#endif /* !defined _LWGEOM_PG_H */

View file

@ -24,6 +24,7 @@
#include "../postgis_config.h"
#include "liblwgeom.h"
#include "lwgeom_pg.h"
#include "lwgeom_cache.h"
#include "lwgeom_transform.h"
/* C headers */
@ -37,9 +38,6 @@
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
@ -49,25 +47,6 @@ int pj_transform_nodatum(projPJ srcdefn, projPJ dstdefn, long point_count, int p
#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
*
@ -98,7 +77,7 @@ static projPJ GetPJHashEntry(MemoryContext mcxt);
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);
@ -635,12 +614,13 @@ void SetPROJ4LibPath(void)
}
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
@ -681,7 +661,7 @@ static PROJ4PortalCache *GetPROJ4SRSCache(FunctionCallInfo fcinfo)
return PROJ4Cache ;
}
#endif
int
GetProjectionsUsingFCInfo(FunctionCallInfo fcinfo, int srid1, int srid2, projPJ *pj1, projPJ *pj2)

View file

@ -36,7 +36,6 @@ PG_OBJS= \
lwgeom_btree.o \
lwgeom_box.o \
lwgeom_box3d.o \
lwgeom_cache.o \
lwgeom_geos.o \
lwgeom_geos_prepared.o \
lwgeom_geos_clean.o \

View file

@ -1,33 +0,0 @@
/**********************************************************************
*
* 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;
}

View file

@ -1,30 +0,0 @@
/**********************************************************************
*
* 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 */

View file

@ -912,7 +912,7 @@ int point_in_ring_rtree(RTREE_NODE *root, POINT2D *point)
POSTGIS_DEBUG(2, "point_in_ring called.");
lines = findLineSegments(root, point->y);
lines = RTreeFindLineSegments(root, point->y);
if (!lines)
return -1;

View file

@ -25,19 +25,11 @@
#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>
@ -87,26 +79,6 @@ Datum pgis_union_geometry_array(PG_FUNCTION_ARGS);
** 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)
@ -1985,9 +1957,7 @@ Datum contains(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));
@ -2030,9 +2000,9 @@ Datum contains(PG_FUNCTION_ARGS)
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);
}
@ -2070,7 +2040,6 @@ Datum contains(PG_FUNCTION_ARGS)
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 )
@ -2086,7 +2055,6 @@ Datum contains(PG_FUNCTION_ARGS)
GEOSGeom_destroy(g1);
}
else
#endif
{
g1 = (GEOSGeometry *)POSTGIS2GEOS(geom1);
if ( 0 == g1 ) /* exception thrown at construction */
@ -2127,9 +2095,7 @@ Datum containsproperly(PG_FUNCTION_ARGS)
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));
@ -2156,7 +2122,6 @@ Datum containsproperly(PG_FUNCTION_ARGS)
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 )
@ -2171,7 +2136,6 @@ Datum containsproperly(PG_FUNCTION_ARGS)
GEOSGeom_destroy(g);
}
else
#endif
{
GEOSGeometry *g2;
GEOSGeometry *g1;
@ -2221,9 +2185,7 @@ Datum covers(PG_FUNCTION_ARGS)
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));
@ -2244,7 +2206,7 @@ Datum covers(PG_FUNCTION_ARGS)
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);
}
@ -2264,9 +2226,9 @@ Datum covers(PG_FUNCTION_ARGS)
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);
}
@ -2305,7 +2267,6 @@ Datum covers(PG_FUNCTION_ARGS)
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 )
@ -2320,7 +2281,6 @@ Datum covers(PG_FUNCTION_ARGS)
GEOSGeom_destroy(g1);
}
else
#endif
{
GEOSGeometry *g1;
GEOSGeometry *g2;
@ -2421,9 +2381,9 @@ Datum coveredby(PG_FUNCTION_ARGS)
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);
}
@ -2574,9 +2534,7 @@ Datum intersects(PG_FUNCTION_ARGS)
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));
@ -2609,7 +2567,7 @@ Datum intersects(PG_FUNCTION_ARGS)
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.");
@ -2628,9 +2586,9 @@ Datum intersects(PG_FUNCTION_ARGS)
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);
}
@ -2664,7 +2622,6 @@ Datum intersects(PG_FUNCTION_ARGS)
}
initGEOS(lwnotice, lwgeom_geos_error);
#ifdef PREPARED_GEOM
prep_cache = GetPrepGeomCache( fcinfo, geom1, geom2 );
if ( prep_cache && prep_cache->prepared_geom )
@ -2693,7 +2650,6 @@ Datum intersects(PG_FUNCTION_ARGS)
}
}
else
#endif
{
GEOSGeometry *g1;
GEOSGeometry *g2;

View file

@ -26,4 +26,4 @@ GEOSGeometry * POSTGIS2GEOS(GSERIALIZED *g);
void errorIfGeometryCollection(GSERIALIZED *g1, GSERIALIZED *g2);
#endif /* LWGEOM_GEOS_H_ 1 */
#endif /* LWGEOM_GEOS_H_ */

View file

@ -266,191 +266,149 @@ DeletePrepGeomHashEntry(MemoryContext mcxt)
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 = 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;
}
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 )
{
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;
}
return cache;
pghe->geom = prepcache->geom;
pghe->prepared_geom = prepcache->prepared_geom;
return LW_SUCCESS;
}
/**
* 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 )
{
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;
prepcache = (PrepGeomCache*)GetGeomIndex(fcinfo, PREP_CACHE_ENTRY, PrepGeomCacheBuilder, PrepGeomCacheCleaner, g1, g2, &argnum);
return prepcache;
}

View file

@ -33,7 +33,6 @@
** 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;
@ -47,6 +46,15 @@ typedef struct
MemoryContext context;
}
PrepGeomCache;
*/
typedef struct
{
int argnum;
const GEOSPreparedGeometry *prepared_geom;
const GEOSGeometry *geom;
}
PrepGeomCache;
/*
** Get the current cache, given the input geometries.
@ -58,4 +66,4 @@ PrepGeomCache;
*/
PrepGeomCache *GetPrepGeomCache(FunctionCallInfoData *fcinfo, GSERIALIZED *pg_geom1, GSERIALIZED *pg_geom2);
#endif /* LWGEOM_GEOS_PREPARED_H_ 1 */
#endif /* LWGEOM_GEOS_PREPARED_H_ */

View file

@ -833,7 +833,7 @@ Datum LWGEOM_asText(PG_FUNCTION_ARGS)
/* 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);

View file

@ -15,99 +15,144 @@
#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;
POSTGIS_DEBUGF(3, "Total leaf nodes: %d", nodeCount);
/*
* Create a leaf node for every line segment.
*/
for (i = 0; i < nodeCount; i++)
{
nodes[i] = createLeafNode(pointArray, i);
}
/*
* 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, "Merging %d children into %d parents.", childNodes, parentNodes);
i = 0;
while (i < parentNodes)
{
nodes[i] = createInteriorNode(nodes[i*2], nodes[i*2+1]);
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;
}
root = nodes[0];
lwfree(nodes);
POSTGIS_DEBUGF(3, "createTree returning %p", root);
return root;
RTREE_POLY_CACHE* result;
result = lwalloc(sizeof(RTREE_POLY_CACHE));
memset(result, 0, sizeof(RTREE_POLY_CACHE));
return result;
}
/**
* Creates an interior node given the children.
*/
RTREE_NODE *createInteriorNode(RTREE_NODE *left, RTREE_NODE *right)
* 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);
if (root->leftNode)
RTreeFree(root->leftNode);
if (root->rightNode)
RTreeFree(root->rightNode);
lwfree(root->interval);
if (root->segment)
{
lwline_free(root->segment);
}
lwfree(root);
}
/**
* 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++)
{
for (r = 0; r < cache->ringCounts[g]; r++)
{
RTreeFree(cache->ringIndices[i]);
i++;
}
}
lwfree(cache->ringIndices);
lwfree(cache->ringCounts);
cache->ringIndices = 0;
cache->ringCounts = 0;
cache->polyCount = 0;
}
/**
* 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 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;
@ -116,11 +161,11 @@ RTREE_NODE *createLeafNode(POINTARRAY *pa, int startPoint)
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);
}
/*
@ -141,179 +186,92 @@ RTREE_NODE *createLeafNode(POINTARRAY *pa, int 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;
RTREE_NODE* root;
RTREE_NODE** nodes = lwalloc(pointArray->npoints * sizeof(RTREE_NODE*));
int i, nodeCount;
int childNodes, parentNodes;
POSTGIS_DEBUGF(2, "mergeIntervals called with %p, %p", inter1, inter2);
POSTGIS_DEBUGF(2, "RTreeCreate called with pointarray %p", pointArray);
interval = lwalloc(sizeof(INTERVAL));
interval->max = FP_MAX(inter1->max, inter2->max);
interval->min = FP_MIN(inter1->min, inter2->min);
nodeCount = pointArray->npoints - 1;
POSTGIS_DEBUGF(3, "interval min = %8.3f, max = %8.3f", interval->min, interval->max);
POSTGIS_DEBUGF(3, "Total leaf nodes: %d", nodeCount);
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)
/*
* Create a leaf node for every line segment.
*/
for (i = 0; i < nodeCount; i++)
{
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);
result = NULL;
if (!isContained(root->interval, value))
{
POSTGIS_DEBUGF(3, "findLineSegments %p: not contained.", root);
return NULL;
nodes[i] = RTreeCreateLeafNode(pointArray, i);
}
/* If there is a segment defined for this node, include it. */
if (root->segment)
/*
* 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: adding segment %p %d.", root, root->segment, root->segment->type);
POSTGIS_DEBUGF(3, "Merging %d children into %d parents.", childNodes, parentNodes);
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);
}
/* If there is a left child node, recursively include its results. */
if (root->leftNode)
{
POSTGIS_DEBUGF(3, "findLineSegments %p: recursing left.", root);
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);
@ -329,101 +287,43 @@ LWMLINE *mergeMultiLines(LWMLINE *line1, LWMLINE *line2)
}
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)
* 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)
{
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)
{
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);
@ -436,15 +336,17 @@ void populateCache(RTREE_POLY_CACHE *currentCache, LWGEOM *lwgeom, GSERIALIZED *
{
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;
@ -454,70 +356,122 @@ void populateCache(RTREE_POLY_CACHE *currentCache, LWGEOM *lwgeom, GSERIALIZED *
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;
}
/*
** 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);
return LW_SUCCESS;
}
/**
* 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)
* 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)
{
int length;
POSTGIS_DEBUGF(2, "retrieveCache called with %p %p %p", lwgeom, serializedPoly, currentCache);
assert ( ! currentCache || currentCache->type == 1 );
if (!currentCache)
if ( ! cache )
return LW_FAILURE;
if ( cache->index )
{
POSTGIS_DEBUG(3, "No existing cache, create one.");
return createCache();
RTREE_POLY_CACHE* currentCache = (RTREE_POLY_CACHE*)(cache->index);
RTreeCacheClear(currentCache);
lwfree(currentCache);
cache->index = 0;
}
if (!(currentCache->poly))
{
POSTGIS_DEBUG(3, "Cache contains no polygon, populating it.");
populateCache(currentCache, lwgeom, serializedPoly);
return currentCache;
}
length = VARSIZE(serializedPoly);
if (VARSIZE(currentCache->poly) != length)
{
POSTGIS_DEBUG(3, "Polygon size mismatch, creating new cache.");
clearCache(currentCache);
return currentCache;
}
if ( memcmp(serializedPoly, currentCache->poly, length) )
{
POSTGIS_DEBUG(3, "Polygon mismatch, creating new cache.");
clearCache(currentCache);
return currentCache;
}
POSTGIS_DEBUGF(3, "Polygon match, retaining current cache, %p.",
currentCache);
return currentCache;
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;
}
/**
* 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)
{
LWMLINE *tmp, *result;
LWGEOM **lwgeoms;
POSTGIS_DEBUGF(2, "RTreeFindLineSegments called for tree %p and value %8.3f", root, value);
result = NULL;
if (!IntervalIsContained(root->interval, value))
{
POSTGIS_DEBUGF(3, "RTreeFindLineSegments %p: not contained.", root);
return NULL;
}
/* If there is a segment defined for this node, include it. */
if (root->segment)
{
POSTGIS_DEBUGF(3, "RTreeFindLineSegments %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);
}
/* If there is a left child node, recursively include its results. */
if (root->leftNode)
{
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;
}
}
/* 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 result;
}

View file

@ -1,72 +1,55 @@
#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 */