mirror of
https://git.osgeo.org/gitea/postgis/postgis
synced 2024-10-24 09:02:37 +00:00
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:
parent
8c8c6a3824
commit
c2a95f250f
|
@ -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
308
libpgcommon/lwgeom_cache.c
Normal 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
101
libpgcommon/lwgeom_cache.h
Normal 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_ */
|
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 */
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -26,4 +26,4 @@ GEOSGeometry * POSTGIS2GEOS(GSERIALIZED *g);
|
|||
|
||||
void errorIfGeometryCollection(GSERIALIZED *g1, GSERIALIZED *g2);
|
||||
|
||||
#endif /* LWGEOM_GEOS_H_ 1 */
|
||||
#endif /* LWGEOM_GEOS_H_ */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in a new issue