From 32e0117abb205da87a6d6251b9ce3490d4dd8bda Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Tue, 12 Jan 2010 18:40:54 +0000 Subject: [PATCH] Fixed #10923 -- The GEOS bindings now use the thread-safe API, when applicable. Thanks, Tuure Laurinolli, for assistance in developing this patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@12214 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/geos/geometry.py | 31 +-- django/contrib/gis/geos/io.py | 122 +---------- django/contrib/gis/geos/libgeos.py | 35 ++-- .../contrib/gis/geos/prototypes/coordseq.py | 29 +-- .../contrib/gis/geos/prototypes/errcheck.py | 5 +- django/contrib/gis/geos/prototypes/geom.py | 58 +++--- django/contrib/gis/geos/prototypes/io.py | 192 ++++++++++++++++-- django/contrib/gis/geos/prototypes/misc.py | 9 +- .../contrib/gis/geos/prototypes/predicates.py | 33 +-- .../contrib/gis/geos/prototypes/prepared.py | 15 +- .../contrib/gis/geos/prototypes/threadsafe.py | 90 ++++++++ .../contrib/gis/geos/prototypes/topology.py | 33 +-- 12 files changed, 400 insertions(+), 252 deletions(-) create mode 100644 django/contrib/gis/geos/prototypes/threadsafe.py diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 68c116657c..84b0454921 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -21,6 +21,10 @@ from django.contrib.gis.geos.mutable_list import ListMixin # the underlying GEOS library. from django.contrib.gis.geos import prototypes as capi +# These functions provide access to a thread-local instance +# of their corresponding GEOS I/O class. +from django.contrib.gis.geos.prototypes.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d + # Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure # to prevent potentially malicious input from reaching the underlying C # library. Not a substitute for good web security programming practices. @@ -61,13 +65,13 @@ class GEOSGeometry(GEOSBase, ListMixin): if wkt_m: # Handling WKT input. if wkt_m.group('srid'): srid = int(wkt_m.group('srid')) - g = wkt_r.read(wkt_m.group('wkt')) + g = wkt_r().read(wkt_m.group('wkt')) elif hex_regex.match(geo_input): # Handling HEXEWKB input. - g = wkb_r.read(geo_input) + g = wkb_r().read(geo_input) elif gdal.GEOJSON and gdal.geometries.json_regex.match(geo_input): # Handling GeoJSON input. - g = wkb_r.read(gdal.OGRGeometry(geo_input).wkb) + g = wkb_r().read(gdal.OGRGeometry(geo_input).wkb) else: raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.') elif isinstance(geo_input, GEOM_PTR): @@ -75,7 +79,7 @@ class GEOSGeometry(GEOSBase, ListMixin): g = geo_input elif isinstance(geo_input, buffer): # When the input is a buffer (WKB). - g = wkb_r.read(geo_input) + g = wkb_r().read(geo_input) elif isinstance(geo_input, GEOSGeometry): g = capi.geom_clone(geo_input.ptr) else: @@ -368,7 +372,7 @@ class GEOSGeometry(GEOSBase, ListMixin): @property def wkt(self): "Returns the WKT (Well-Known Text) representation of this Geometry." - return wkt_w.write(self) + return wkt_w().write(self) @property def hex(self): @@ -380,7 +384,7 @@ class GEOSGeometry(GEOSBase, ListMixin): """ # A possible faster, all-python, implementation: # str(self.wkb).encode('hex') - return wkb_w.write_hex(self) + return wkb_w().write_hex(self) @property def hexewkb(self): @@ -393,9 +397,9 @@ class GEOSGeometry(GEOSBase, ListMixin): if not GEOS_PREPARE: # See: http://trac.osgeo.org/geos/ticket/216 raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D HEXEWKB.') - return ewkb_w3d.write_hex(self) + return ewkb_w3d().write_hex(self) else: - return ewkb_w.write_hex(self) + return ewkb_w().write_hex(self) @property def json(self): @@ -416,7 +420,7 @@ class GEOSGeometry(GEOSBase, ListMixin): as a Python buffer. SRID and Z values are not included, use the `ewkb` property instead. """ - return wkb_w.write(self) + return wkb_w().write(self) @property def ewkb(self): @@ -429,9 +433,9 @@ class GEOSGeometry(GEOSBase, ListMixin): if not GEOS_PREPARE: # See: http://trac.osgeo.org/geos/ticket/216 raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D EWKB.') - return ewkb_w3d.write(self) + return ewkb_w3d().write(self) else: - return ewkb_w.write(self) + return ewkb_w().write(self) @property def kml(self): @@ -493,7 +497,7 @@ class GEOSGeometry(GEOSBase, ListMixin): g = gdal.OGRGeometry(self.wkb, srid) g.transform(ct) # Getting a new GEOS pointer - ptr = wkb_r.read(g.wkb) + ptr = wkb_r().read(g.wkb) if clone: # User wants a cloned transformed geometry returned. return GEOSGeometry(ptr, srid=g.srid) @@ -655,9 +659,6 @@ GEOS_CLASSES = {0 : Point, 7 : GeometryCollection, } -# Similarly, import the GEOS I/O instances here to avoid conflicts. -from django.contrib.gis.geos.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d - # If supported, import the PreparedGeometry class. if GEOS_PREPARE: from django.contrib.gis.geos.prepared import PreparedGeometry diff --git a/django/contrib/gis/geos/io.py b/django/contrib/gis/geos/io.py index 2f895fbc2d..54ba6b4ac5 100644 --- a/django/contrib/gis/geos/io.py +++ b/django/contrib/gis/geos/io.py @@ -3,128 +3,18 @@ Module that holds classes for performing I/O operations on GEOS geometry objects. Specifically, this has Python implementations of WKB/WKT reader and writer classes. """ -from ctypes import byref, c_size_t -from django.contrib.gis.geos.base import GEOSBase -from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.geometry import GEOSGeometry -from django.contrib.gis.geos.libgeos import GEOM_PTR -from django.contrib.gis.geos.prototypes import io as capi +from django.contrib.gis.geos.prototypes.io import _WKTReader, _WKBReader, WKBWriter, WKTWriter -class IOBase(GEOSBase): - "Base class for GEOS I/O objects." - def __init__(self): - # Getting the pointer with the constructor. - self.ptr = self._constructor() - - def __del__(self): - # Cleaning up with the appropriate destructor. - if self._ptr: self._destructor(self._ptr) - -### WKT Reading and Writing objects ### - -# Non-public class for internal use because its `read` method returns -# _pointers_ instead of a GEOSGeometry object. -class _WKTReader(IOBase): - _constructor = capi.wkt_reader_create - _destructor = capi.wkt_reader_destroy - ptr_type = capi.WKT_READ_PTR - - def read(self, wkt): - if not isinstance(wkt, basestring): raise TypeError - return capi.wkt_reader_read(self.ptr, wkt) +# Public classes for (WKB|WKT)Reader, which return GEOSGeometry +class WKBReader(_WKBReader): + def read(self, wkb): + "Returns a GEOSGeometry for the given WKB buffer." + return GEOSGeometry(super(WKBReader, self).read(wkb)) class WKTReader(_WKTReader): def read(self, wkt): "Returns a GEOSGeometry for the given WKT string." return GEOSGeometry(super(WKTReader, self).read(wkt)) -class WKTWriter(IOBase): - _constructor = capi.wkt_writer_create - _destructor = capi.wkt_writer_destroy - ptr_type = capi.WKT_WRITE_PTR - def write(self, geom): - "Returns the WKT representation of the given geometry." - return capi.wkt_writer_write(self.ptr, geom.ptr) - -### WKB Reading and Writing objects ### - -# Non-public class for the same reason as _WKTReader above. -class _WKBReader(IOBase): - _constructor = capi.wkb_reader_create - _destructor = capi.wkb_reader_destroy - ptr_type = capi.WKB_READ_PTR - - def read(self, wkb): - "Returns a _pointer_ to C GEOS Geometry object from the given WKB." - if isinstance(wkb, buffer): - wkb_s = str(wkb) - return capi.wkb_reader_read(self.ptr, wkb_s, len(wkb_s)) - elif isinstance(wkb, basestring): - return capi.wkb_reader_read_hex(self.ptr, wkb, len(wkb)) - else: - raise TypeError - -class WKBReader(_WKBReader): - def read(self, wkb): - "Returns a GEOSGeometry for the given WKB buffer." - return GEOSGeometry(super(WKBReader, self).read(wkb)) - -class WKBWriter(IOBase): - _constructor = capi.wkb_writer_create - _destructor = capi.wkb_writer_destroy - ptr_type = capi.WKB_WRITE_PTR - - def write(self, geom): - "Returns the WKB representation of the given geometry." - return buffer(capi.wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t()))) - - def write_hex(self, geom): - "Returns the HEXEWKB representation of the given geometry." - return capi.wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t())) - - ### WKBWriter Properties ### - - # Property for getting/setting the byteorder. - def _get_byteorder(self): - return capi.wkb_writer_get_byteorder(self.ptr) - - def _set_byteorder(self, order): - if not order in (0, 1): raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).') - capi.wkb_writer_set_byteorder(self.ptr, order) - - byteorder = property(_get_byteorder, _set_byteorder) - - # Property for getting/setting the output dimension. - def _get_outdim(self): - return capi.wkb_writer_get_outdim(self.ptr) - - def _set_outdim(self, new_dim): - if not new_dim in (2, 3): raise ValueError('WKB output dimension must be 2 or 3') - capi.wkb_writer_set_outdim(self.ptr, new_dim) - - outdim = property(_get_outdim, _set_outdim) - - # Property for getting/setting the include srid flag. - def _get_include_srid(self): - return bool(ord(capi.wkb_writer_get_include_srid(self.ptr))) - - def _set_include_srid(self, include): - if bool(include): flag = chr(1) - else: flag = chr(0) - capi.wkb_writer_set_include_srid(self.ptr, flag) - - srid = property(_get_include_srid, _set_include_srid) - -# Instances of the WKT and WKB reader/writer objects. -wkt_r = _WKTReader() -wkt_w = WKTWriter() -wkb_r = _WKBReader() -wkb_w = WKBWriter() - -# These instances are for writing EWKB in 2D and 3D. -ewkb_w = WKBWriter() -ewkb_w.srid = True -ewkb_w3d = WKBWriter() -ewkb_w3d.srid = True -ewkb_w3d.outdim = 3 diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py index 244bfa7967..84299a0540 100644 --- a/django/contrib/gis/geos/libgeos.py +++ b/django/contrib/gis/geos/libgeos.py @@ -6,7 +6,7 @@ This module also houses GEOS Pointer utilities, including get_pointer_arr(), and GEOM_PTR. """ -import atexit, os, re, sys +import os, re, sys from ctypes import c_char_p, Structure, CDLL, CFUNCTYPE, POINTER from ctypes.util import find_library from django.contrib.gis.geos.error import GEOSException @@ -45,14 +45,14 @@ if lib_path is None: '", "'.join(lib_names)) # Getting the GEOS C library. The C interface (CDLL) is used for -# both *NIX and Windows. +# both *NIX and Windows. # See the GEOS C API source code for more details on the library function calls: # http://geos.refractions.net/ro/doxygen_docs/html/geos__c_8h-source.html lgeos = CDLL(lib_path) # The notice and error handler C function callback definitions. -# Supposed to mimic the GEOS message handler (C below): -# "typedef void (*GEOSMessageHandler)(const char *fmt, ...);" +# Supposed to mimic the GEOS message handler (C below): +# typedef void (*GEOSMessageHandler)(const char *fmt, ...); NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p) def notice_h(fmt, lst, output_h=sys.stdout): try: @@ -71,23 +71,19 @@ def error_h(fmt, lst, output_h=sys.stderr): output_h.write('GEOS_ERROR: %s\n' % err_msg) error_h = ERRORFUNC(error_h) -# The initGEOS routine should be called first, however, that routine takes -# the notice and error functions as parameters. Here is the C code that -# is wrapped: -# "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);" -lgeos.initGEOS(notice_h, error_h) - #### GEOS Geometry C data structures, and utility functions. #### # Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR class GEOSGeom_t(Structure): pass class GEOSPrepGeom_t(Structure): pass class GEOSCoordSeq_t(Structure): pass +class GEOSContextHandle_t(Structure): pass # Pointers to opaque GEOS geometry structures. GEOM_PTR = POINTER(GEOSGeom_t) PREPGEOM_PTR = POINTER(GEOSPrepGeom_t) CS_PTR = POINTER(GEOSCoordSeq_t) +CONTEXT_PTR = POINTER(GEOSContextHandle_t) # Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection # GEOS routines @@ -126,5 +122,20 @@ del _verinfo GEOS_VERSION = (GEOS_MAJOR_VERSION, GEOS_MINOR_VERSION, GEOS_SUBMINOR_VERSION) GEOS_PREPARE = GEOS_VERSION >= (3, 1, 0) -# Calling the finishGEOS() upon exit of the interpreter. -atexit.register(lgeos.finishGEOS) +if GEOS_PREPARE: + # Here we set up the prototypes for the initGEOS_r and finishGEOS_r + # routines. These functions aren't actually called until they are + # attached to a GEOS context handle -- this actually occurs in + # geos/prototypes/threadsafe.py. + lgeos.initGEOS_r.restype = CONTEXT_PTR + lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR] +else: + # When thread-safety isn't available, the initGEOS routine must be called + # first. This function takes the notice and error functions, defined + # as Python callbacks above, as parameters. Here is the C code that is + # wrapped: + # extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function); + lgeos.initGEOS(notice_h, error_h) + # Calling finishGEOS() upon exit of the interpreter. + import atexit + atexit.register(lgeos.finishGEOS) diff --git a/django/contrib/gis/geos/prototypes/coordseq.py b/django/contrib/gis/geos/prototypes/coordseq.py index 3b5fb2cf35..68b9480ff8 100644 --- a/django/contrib/gis/geos/prototypes/coordseq.py +++ b/django/contrib/gis/geos/prototypes/coordseq.py @@ -1,6 +1,7 @@ from ctypes import c_double, c_int, c_uint, POINTER -from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, CS_PTR +from django.contrib.gis.geos.libgeos import GEOM_PTR, CS_PTR from django.contrib.gis.geos.prototypes.errcheck import last_arg_byref, GEOSException +from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc ## Error-checking routines specific to coordinate sequences. ## def check_cs_ptr(result, func, cargs): @@ -59,24 +60,24 @@ def cs_output(func, argtypes): ## Coordinate Sequence ctypes prototypes ## # Coordinate Sequence constructors & cloning. -cs_clone = cs_output(lgeos.GEOSCoordSeq_clone, [CS_PTR]) -create_cs = cs_output(lgeos.GEOSCoordSeq_create, [c_uint, c_uint]) -get_cs = cs_output(lgeos.GEOSGeom_getCoordSeq, [GEOM_PTR]) +cs_clone = cs_output(GEOSFunc('GEOSCoordSeq_clone'), [CS_PTR]) +create_cs = cs_output(GEOSFunc('GEOSCoordSeq_create'), [c_uint, c_uint]) +get_cs = cs_output(GEOSFunc('GEOSGeom_getCoordSeq'), [GEOM_PTR]) # Getting, setting ordinate -cs_getordinate = cs_operation(lgeos.GEOSCoordSeq_getOrdinate, ordinate=True, get=True) -cs_setordinate = cs_operation(lgeos.GEOSCoordSeq_setOrdinate, ordinate=True) +cs_getordinate = cs_operation(GEOSFunc('GEOSCoordSeq_getOrdinate'), ordinate=True, get=True) +cs_setordinate = cs_operation(GEOSFunc('GEOSCoordSeq_setOrdinate'), ordinate=True) # For getting, x, y, z -cs_getx = cs_operation(lgeos.GEOSCoordSeq_getX, get=True) -cs_gety = cs_operation(lgeos.GEOSCoordSeq_getY, get=True) -cs_getz = cs_operation(lgeos.GEOSCoordSeq_getZ, get=True) +cs_getx = cs_operation(GEOSFunc('GEOSCoordSeq_getX'), get=True) +cs_gety = cs_operation(GEOSFunc('GEOSCoordSeq_getY'), get=True) +cs_getz = cs_operation(GEOSFunc('GEOSCoordSeq_getZ'), get=True) # For setting, x, y, z -cs_setx = cs_operation(lgeos.GEOSCoordSeq_setX) -cs_sety = cs_operation(lgeos.GEOSCoordSeq_setY) -cs_setz = cs_operation(lgeos.GEOSCoordSeq_setZ) +cs_setx = cs_operation(GEOSFunc('GEOSCoordSeq_setX')) +cs_sety = cs_operation(GEOSFunc('GEOSCoordSeq_setY')) +cs_setz = cs_operation(GEOSFunc('GEOSCoordSeq_setZ')) # These routines return size & dimensions. -cs_getsize = cs_int(lgeos.GEOSCoordSeq_getSize) -cs_getdims = cs_int(lgeos.GEOSCoordSeq_getDimensions) +cs_getsize = cs_int(GEOSFunc('GEOSCoordSeq_getSize')) +cs_getdims = cs_int(GEOSFunc('GEOSCoordSeq_getDimensions')) diff --git a/django/contrib/gis/geos/prototypes/errcheck.py b/django/contrib/gis/geos/prototypes/errcheck.py index a00b93be6e..97fcd21388 100644 --- a/django/contrib/gis/geos/prototypes/errcheck.py +++ b/django/contrib/gis/geos/prototypes/errcheck.py @@ -4,14 +4,15 @@ import os from ctypes import c_void_p, string_at, CDLL from django.contrib.gis.geos.error import GEOSException -from django.contrib.gis.geos.libgeos import lgeos, GEOS_VERSION +from django.contrib.gis.geos.libgeos import GEOS_VERSION +from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc # Getting the `free` routine used to free the memory allocated for # string pointers returned by GEOS. if GEOS_VERSION >= (3, 1, 1): # In versions 3.1.1 and above, `GEOSFree` was added to the C API # because `free` isn't always available on all platforms. - free = lgeos.GEOSFree + free = GEOSFunc('GEOSFree') free.argtypes = [c_void_p] free.restype = None else: diff --git a/django/contrib/gis/geos/prototypes/geom.py b/django/contrib/gis/geos/prototypes/geom.py index e3f2417cd2..03f98978e3 100644 --- a/django/contrib/gis/geos/prototypes/geom.py +++ b/django/contrib/gis/geos/prototypes/geom.py @@ -1,7 +1,8 @@ from ctypes import c_char_p, c_int, c_size_t, c_ubyte, c_uint, POINTER -from django.contrib.gis.geos.libgeos import lgeos, CS_PTR, GEOM_PTR, PREPGEOM_PTR, GEOS_PREPARE +from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, PREPGEOM_PTR, GEOS_PREPARE from django.contrib.gis.geos.prototypes.errcheck import \ check_geom, check_minus_one, check_sized_string, check_string, check_zero +from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc # This is the return type used by binary output (WKB, HEX) routines. c_uchar_p = POINTER(c_ubyte) @@ -62,56 +63,57 @@ def string_from_geom(func): ### ctypes prototypes ### -# Deprecated creation and output routines from WKB, HEX, WKT -from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf) -from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf) -from_wkt = geom_output(lgeos.GEOSGeomFromWKT, [c_char_p]) +# Deprecated creation routines from WKB, HEX, WKT +from_hex = bin_constructor(GEOSFunc('GEOSGeomFromHEX_buf')) +from_wkb = bin_constructor(GEOSFunc('GEOSGeomFromWKB_buf')) +from_wkt = geom_output(GEOSFunc('GEOSGeomFromWKT'), [c_char_p]) -to_hex = bin_output(lgeos.GEOSGeomToHEX_buf) -to_wkb = bin_output(lgeos.GEOSGeomToWKB_buf) -to_wkt = string_from_geom(lgeos.GEOSGeomToWKT) +# Deprecated output routines +to_hex = bin_output(GEOSFunc('GEOSGeomToHEX_buf')) +to_wkb = bin_output(GEOSFunc('GEOSGeomToWKB_buf')) +to_wkt = string_from_geom(GEOSFunc('GEOSGeomToWKT')) -# The GEOS geometry type, typeid, num_coordinates and number of geometries -geos_normalize = int_from_geom(lgeos.GEOSNormalize) -geos_type = string_from_geom(lgeos.GEOSGeomType) -geos_typeid = int_from_geom(lgeos.GEOSGeomTypeId) -get_dims = int_from_geom(lgeos.GEOSGeom_getDimensions, zero=True) -get_num_coords = int_from_geom(lgeos.GEOSGetNumCoordinates) -get_num_geoms = int_from_geom(lgeos.GEOSGetNumGeometries) +# The GEOS geometry type, typeid, num_coordites and number of geometries +geos_normalize = int_from_geom(GEOSFunc('GEOSNormalize')) +geos_type = string_from_geom(GEOSFunc('GEOSGeomType')) +geos_typeid = int_from_geom(GEOSFunc('GEOSGeomTypeId')) +get_dims = int_from_geom(GEOSFunc('GEOSGeom_getDimensions'), zero=True) +get_num_coords = int_from_geom(GEOSFunc('GEOSGetNumCoordinates')) +get_num_geoms = int_from_geom(GEOSFunc('GEOSGetNumGeometries')) # Geometry creation factories -create_point = geom_output(lgeos.GEOSGeom_createPoint, [CS_PTR]) -create_linestring = geom_output(lgeos.GEOSGeom_createLineString, [CS_PTR]) -create_linearring = geom_output(lgeos.GEOSGeom_createLinearRing, [CS_PTR]) +create_point = geom_output(GEOSFunc('GEOSGeom_createPoint'), [CS_PTR]) +create_linestring = geom_output(GEOSFunc('GEOSGeom_createLineString'), [CS_PTR]) +create_linearring = geom_output(GEOSFunc('GEOSGeom_createLinearRing'), [CS_PTR]) # Polygon and collection creation routines are special and will not # have their argument types defined. -create_polygon = geom_output(lgeos.GEOSGeom_createPolygon, None) -create_collection = geom_output(lgeos.GEOSGeom_createCollection, None) +create_polygon = geom_output(GEOSFunc('GEOSGeom_createPolygon'), None) +create_collection = geom_output(GEOSFunc('GEOSGeom_createCollection'), None) # Ring routines -get_extring = geom_output(lgeos.GEOSGetExteriorRing, [GEOM_PTR]) -get_intring = geom_index(lgeos.GEOSGetInteriorRingN) -get_nrings = int_from_geom(lgeos.GEOSGetNumInteriorRings) +get_extring = geom_output(GEOSFunc('GEOSGetExteriorRing'), [GEOM_PTR]) +get_intring = geom_index(GEOSFunc('GEOSGetInteriorRingN')) +get_nrings = int_from_geom(GEOSFunc('GEOSGetNumInteriorRings')) # Collection Routines -get_geomn = geom_index(lgeos.GEOSGetGeometryN) +get_geomn = geom_index(GEOSFunc('GEOSGetGeometryN')) # Cloning -geom_clone = lgeos.GEOSGeom_clone +geom_clone = GEOSFunc('GEOSGeom_clone') geom_clone.argtypes = [GEOM_PTR] geom_clone.restype = GEOM_PTR # Destruction routine. -destroy_geom = lgeos.GEOSGeom_destroy +destroy_geom = GEOSFunc('GEOSGeom_destroy') destroy_geom.argtypes = [GEOM_PTR] destroy_geom.restype = None # SRID routines -geos_get_srid = lgeos.GEOSGetSRID +geos_get_srid = GEOSFunc('GEOSGetSRID') geos_get_srid.argtypes = [GEOM_PTR] geos_get_srid.restype = c_int -geos_set_srid = lgeos.GEOSSetSRID +geos_set_srid = GEOSFunc('GEOSSetSRID') geos_set_srid.argtypes = [GEOM_PTR, c_int] geos_set_srid.restype = None diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py index ece1c70cf0..5c0c8b5bef 100644 --- a/django/contrib/gis/geos/prototypes/io.py +++ b/django/contrib/gis/geos/prototypes/io.py @@ -1,7 +1,10 @@ -from ctypes import c_char_p, c_int, c_char, c_size_t, Structure, POINTER -from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR +import threading +from ctypes import byref, c_char_p, c_int, c_char, c_size_t, Structure, POINTER +from django.contrib.gis.geos.base import GEOSBase +from django.contrib.gis.geos.libgeos import GEOM_PTR from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string, check_sized_string from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p +from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc ### The WKB/WKT Reader/Writer structures and pointers ### class WKTReader_st(Structure): pass @@ -15,34 +18,34 @@ WKB_READ_PTR = POINTER(WKBReader_st) WKB_WRITE_PTR = POINTER(WKBReader_st) ### WKTReader routines ### -wkt_reader_create = lgeos.GEOSWKTReader_create +wkt_reader_create = GEOSFunc('GEOSWKTReader_create') wkt_reader_create.restype = WKT_READ_PTR -wkt_reader_destroy = lgeos.GEOSWKTReader_destroy +wkt_reader_destroy = GEOSFunc('GEOSWKTReader_destroy') wkt_reader_destroy.argtypes = [WKT_READ_PTR] -wkt_reader_read = lgeos.GEOSWKTReader_read +wkt_reader_read = GEOSFunc('GEOSWKTReader_read') wkt_reader_read.argtypes = [WKT_READ_PTR, c_char_p] wkt_reader_read.restype = GEOM_PTR wkt_reader_read.errcheck = check_geom ### WKTWriter routines ### -wkt_writer_create = lgeos.GEOSWKTWriter_create +wkt_writer_create = GEOSFunc('GEOSWKTWriter_create') wkt_writer_create.restype = WKT_WRITE_PTR -wkt_writer_destroy = lgeos.GEOSWKTWriter_destroy +wkt_writer_destroy = GEOSFunc('GEOSWKTWriter_destroy') wkt_writer_destroy.argtypes = [WKT_WRITE_PTR] -wkt_writer_write = lgeos.GEOSWKTWriter_write +wkt_writer_write = GEOSFunc('GEOSWKTWriter_write') wkt_writer_write.argtypes = [WKT_WRITE_PTR, GEOM_PTR] wkt_writer_write.restype = geos_char_p wkt_writer_write.errcheck = check_string ### WKBReader routines ### -wkb_reader_create = lgeos.GEOSWKBReader_create +wkb_reader_create = GEOSFunc('GEOSWKBReader_create') wkb_reader_create.restype = WKB_READ_PTR -wkb_reader_destroy = lgeos.GEOSWKBReader_destroy +wkb_reader_destroy = GEOSFunc('GEOSWKBReader_destroy') wkb_reader_destroy.argtypes = [WKB_READ_PTR] def wkb_read_func(func): @@ -56,14 +59,14 @@ def wkb_read_func(func): func.errcheck = check_geom return func -wkb_reader_read = wkb_read_func(lgeos.GEOSWKBReader_read) -wkb_reader_read_hex = wkb_read_func(lgeos.GEOSWKBReader_readHEX) +wkb_reader_read = wkb_read_func(GEOSFunc('GEOSWKBReader_read')) +wkb_reader_read_hex = wkb_read_func(GEOSFunc('GEOSWKBReader_readHEX')) ### WKBWriter routines ### -wkb_writer_create = lgeos.GEOSWKBWriter_create +wkb_writer_create = GEOSFunc('GEOSWKBWriter_create') wkb_writer_create.restype = WKB_WRITE_PTR -wkb_writer_destroy = lgeos.GEOSWKBWriter_destroy +wkb_writer_destroy = GEOSFunc('GEOSWKBWriter_destroy') wkb_writer_destroy.argtypes = [WKB_WRITE_PTR] # WKB Writing prototypes. @@ -73,8 +76,8 @@ def wkb_write_func(func): func.errcheck = check_sized_string return func -wkb_writer_write = wkb_write_func(lgeos.GEOSWKBWriter_write) -wkb_writer_write_hex = wkb_write_func(lgeos.GEOSWKBWriter_writeHEX) +wkb_writer_write = wkb_write_func(GEOSFunc('GEOSWKBWriter_write')) +wkb_writer_write_hex = wkb_write_func(GEOSFunc('GEOSWKBWriter_writeHEX')) # WKBWriter property getter/setter prototypes. def wkb_writer_get(func, restype=c_int): @@ -86,9 +89,154 @@ def wkb_writer_set(func, argtype=c_int): func.argtypes = [WKB_WRITE_PTR, argtype] return func -wkb_writer_get_byteorder = wkb_writer_get(lgeos.GEOSWKBWriter_getByteOrder) -wkb_writer_set_byteorder = wkb_writer_set(lgeos.GEOSWKBWriter_setByteOrder) -wkb_writer_get_outdim = wkb_writer_get(lgeos.GEOSWKBWriter_getOutputDimension) -wkb_writer_set_outdim = wkb_writer_set(lgeos.GEOSWKBWriter_setOutputDimension) -wkb_writer_get_include_srid = wkb_writer_get(lgeos.GEOSWKBWriter_getIncludeSRID, restype=c_char) -wkb_writer_set_include_srid = wkb_writer_set(lgeos.GEOSWKBWriter_setIncludeSRID, argtype=c_char) +wkb_writer_get_byteorder = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getByteOrder')) +wkb_writer_set_byteorder = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setByteOrder')) +wkb_writer_get_outdim = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getOutputDimension')) +wkb_writer_set_outdim = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setOutputDimension')) +wkb_writer_get_include_srid = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getIncludeSRID'), restype=c_char) +wkb_writer_set_include_srid = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setIncludeSRID'), argtype=c_char) + +### Base I/O Class ### +class IOBase(GEOSBase): + "Base class for GEOS I/O objects." + def __init__(self): + # Getting the pointer with the constructor. + self.ptr = self._constructor() + + def __del__(self): + # Cleaning up with the appropriate destructor. + if self._ptr: self._destructor(self._ptr) + +### Base WKB/WKT Reading and Writing objects ### + +# Non-public WKB/WKT reader classes for internal use because +# their `read` methods return _pointers_ instead of GEOSGeometry +# objects. +class _WKTReader(IOBase): + _constructor = wkt_reader_create + _destructor = wkt_reader_destroy + ptr_type = WKT_READ_PTR + + def read(self, wkt): + if not isinstance(wkt, basestring): raise TypeError + return wkt_reader_read(self.ptr, wkt) + +class _WKBReader(IOBase): + _constructor = wkb_reader_create + _destructor = wkb_reader_destroy + ptr_type = WKB_READ_PTR + + def read(self, wkb): + "Returns a _pointer_ to C GEOS Geometry object from the given WKB." + if isinstance(wkb, buffer): + wkb_s = str(wkb) + return wkb_reader_read(self.ptr, wkb_s, len(wkb_s)) + elif isinstance(wkb, basestring): + return wkb_reader_read_hex(self.ptr, wkb, len(wkb)) + else: + raise TypeError + +### WKB/WKT Writer Classes ### +class WKTWriter(IOBase): + _constructor = wkt_writer_create + _destructor = wkt_writer_destroy + ptr_type = WKT_WRITE_PTR + + def write(self, geom): + "Returns the WKT representation of the given geometry." + return wkt_writer_write(self.ptr, geom.ptr) + +class WKBWriter(IOBase): + _constructor = wkb_writer_create + _destructor = wkb_writer_destroy + ptr_type = WKB_WRITE_PTR + + def write(self, geom): + "Returns the WKB representation of the given geometry." + return buffer(wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t()))) + + def write_hex(self, geom): + "Returns the HEXEWKB representation of the given geometry." + return wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t())) + + ### WKBWriter Properties ### + + # Property for getting/setting the byteorder. + def _get_byteorder(self): + return wkb_writer_get_byteorder(self.ptr) + + def _set_byteorder(self, order): + if not order in (0, 1): raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).') + wkb_writer_set_byteorder(self.ptr, order) + + byteorder = property(_get_byteorder, _set_byteorder) + + # Property for getting/setting the output dimension. + def _get_outdim(self): + return wkb_writer_get_outdim(self.ptr) + + def _set_outdim(self, new_dim): + if not new_dim in (2, 3): raise ValueError('WKB output dimension must be 2 or 3') + wkb_writer_set_outdim(self.ptr, new_dim) + + outdim = property(_get_outdim, _set_outdim) + + # Property for getting/setting the include srid flag. + def _get_include_srid(self): + return bool(ord(wkb_writer_get_include_srid(self.ptr))) + + def _set_include_srid(self, include): + if bool(include): flag = chr(1) + else: flag = chr(0) + wkb_writer_set_include_srid(self.ptr, flag) + + srid = property(_get_include_srid, _set_include_srid) + +# `ThreadLocalIO` object holds instances of the WKT and WKB reader/writer +# objects that are local to the thread. The `GEOSGeometry` internals +# access these instances by calling the module-level functions, defined +# below. +class ThreadLocalIO(threading.local): + wkt_r = None + wkt_w = None + wkb_r = None + wkb_w = None + ewkb_w = None + ewkb_w3d = None + +thread_context = ThreadLocalIO() + +# These module-level routines return the I/O object that is local to the +# the thread. If the I/O object does not exist yet it will be initialized. +def wkt_r(): + if not thread_context.wkt_r: + thread_context.wkt_r = _WKTReader() + return thread_context.wkt_r + +def wkt_w(): + if not thread_context.wkt_w: + thread_context.wkt_w = WKTWriter() + return thread_context.wkt_w + +def wkb_r(): + if not thread_context.wkb_r: + thread_context.wkb_r = _WKBReader() + return thread_context.wkb_r + +def wkb_w(): + if not thread_context.wkb_w: + thread_context.wkb_w = WKBWriter() + return thread_context.wkb_w + +def ewkb_w(): + if not thread_context.ewkb_w: + thread_context.ewkb_w = WKBWriter() + thread_context.ewkb_w.srid = True + return thread_context.ewkb_w + +def ewkb_w3d(): + if not thread_context.ewkb_w3d: + thread_context.ewkb_w3d = WKBWriter() + thread_context.ewkb_w3d.srid = True + thread_context.ewkb_w3d.outdim = 3 + return thread_context.ewkb_w3d diff --git a/django/contrib/gis/geos/prototypes/misc.py b/django/contrib/gis/geos/prototypes/misc.py index 255da91d9e..5b3b658fc0 100644 --- a/django/contrib/gis/geos/prototypes/misc.py +++ b/django/contrib/gis/geos/prototypes/misc.py @@ -3,8 +3,9 @@ ones that return the area, distance, and length. """ from ctypes import c_int, c_double, POINTER -from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR +from django.contrib.gis.geos.libgeos import GEOM_PTR from django.contrib.gis.geos.prototypes.errcheck import check_dbl +from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc ### ctypes generator function ### def dbl_from_geom(func, num_geom=1): @@ -22,6 +23,6 @@ def dbl_from_geom(func, num_geom=1): ### ctypes prototypes ### # Area, distance, and length prototypes. -geos_area = dbl_from_geom(lgeos.GEOSArea) -geos_distance = dbl_from_geom(lgeos.GEOSDistance, num_geom=2) -geos_length = dbl_from_geom(lgeos.GEOSLength) +geos_area = dbl_from_geom(GEOSFunc('GEOSArea')) +geos_distance = dbl_from_geom(GEOSFunc('GEOSDistance'), num_geom=2) +geos_length = dbl_from_geom(GEOSFunc('GEOSLength')) diff --git a/django/contrib/gis/geos/prototypes/predicates.py b/django/contrib/gis/geos/prototypes/predicates.py index 596df0a7ce..bf69bb140b 100644 --- a/django/contrib/gis/geos/prototypes/predicates.py +++ b/django/contrib/gis/geos/prototypes/predicates.py @@ -3,8 +3,9 @@ unary and binary predicate operations on geometries. """ from ctypes import c_char, c_char_p, c_double -from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR +from django.contrib.gis.geos.libgeos import GEOM_PTR from django.contrib.gis.geos.prototypes.errcheck import check_predicate +from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc ## Binary & unary predicate functions ## def binary_predicate(func, *args): @@ -24,20 +25,20 @@ def unary_predicate(func): return func ## Unary Predicates ## -geos_hasz = unary_predicate(lgeos.GEOSHasZ) -geos_isempty = unary_predicate(lgeos.GEOSisEmpty) -geos_isring = unary_predicate(lgeos.GEOSisRing) -geos_issimple = unary_predicate(lgeos.GEOSisSimple) -geos_isvalid = unary_predicate(lgeos.GEOSisValid) +geos_hasz = unary_predicate(GEOSFunc('GEOSHasZ')) +geos_isempty = unary_predicate(GEOSFunc('GEOSisEmpty')) +geos_isring = unary_predicate(GEOSFunc('GEOSisRing')) +geos_issimple = unary_predicate(GEOSFunc('GEOSisSimple')) +geos_isvalid = unary_predicate(GEOSFunc('GEOSisValid')) ## Binary Predicates ## -geos_contains = binary_predicate(lgeos.GEOSContains) -geos_crosses = binary_predicate(lgeos.GEOSCrosses) -geos_disjoint = binary_predicate(lgeos.GEOSDisjoint) -geos_equals = binary_predicate(lgeos.GEOSEquals) -geos_equalsexact = binary_predicate(lgeos.GEOSEqualsExact, c_double) -geos_intersects = binary_predicate(lgeos.GEOSIntersects) -geos_overlaps = binary_predicate(lgeos.GEOSOverlaps) -geos_relatepattern = binary_predicate(lgeos.GEOSRelatePattern, c_char_p) -geos_touches = binary_predicate(lgeos.GEOSTouches) -geos_within = binary_predicate(lgeos.GEOSWithin) +geos_contains = binary_predicate(GEOSFunc('GEOSContains')) +geos_crosses = binary_predicate(GEOSFunc('GEOSCrosses')) +geos_disjoint = binary_predicate(GEOSFunc('GEOSDisjoint')) +geos_equals = binary_predicate(GEOSFunc('GEOSEquals')) +geos_equalsexact = binary_predicate(GEOSFunc('GEOSEqualsExact'), c_double) +geos_intersects = binary_predicate(GEOSFunc('GEOSIntersects')) +geos_overlaps = binary_predicate(GEOSFunc('GEOSOverlaps')) +geos_relatepattern = binary_predicate(GEOSFunc('GEOSRelatePattern'), c_char_p) +geos_touches = binary_predicate(GEOSFunc('GEOSTouches')) +geos_within = binary_predicate(GEOSFunc('GEOSWithin')) diff --git a/django/contrib/gis/geos/prototypes/prepared.py b/django/contrib/gis/geos/prototypes/prepared.py index 6fde0cddd7..7342d7d966 100644 --- a/django/contrib/gis/geos/prototypes/prepared.py +++ b/django/contrib/gis/geos/prototypes/prepared.py @@ -1,13 +1,14 @@ from ctypes import c_char -from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, PREPGEOM_PTR +from django.contrib.gis.geos.libgeos import GEOM_PTR, PREPGEOM_PTR from django.contrib.gis.geos.prototypes.errcheck import check_predicate +from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc # Prepared geometry constructor and destructors. -geos_prepare = lgeos.GEOSPrepare +geos_prepare = GEOSFunc('GEOSPrepare') geos_prepare.argtypes = [GEOM_PTR] geos_prepare.restype = PREPGEOM_PTR -prepared_destroy = lgeos.GEOSPreparedGeom_destroy +prepared_destroy = GEOSFunc('GEOSPreparedGeom_destroy') prepared_destroy.argtpes = [PREPGEOM_PTR] prepared_destroy.restype = None @@ -18,7 +19,7 @@ def prepared_predicate(func): func.errcheck = check_predicate return func -prepared_contains = prepared_predicate(lgeos.GEOSPreparedContains) -prepared_contains_properly = prepared_predicate(lgeos.GEOSPreparedContainsProperly) -prepared_covers = prepared_predicate(lgeos.GEOSPreparedCovers) -prepared_intersects = prepared_predicate(lgeos.GEOSPreparedIntersects) +prepared_contains = prepared_predicate(GEOSFunc('GEOSPreparedContains')) +prepared_contains_properly = prepared_predicate(GEOSFunc('GEOSPreparedContainsProperly')) +prepared_covers = prepared_predicate(GEOSFunc('GEOSPreparedCovers')) +prepared_intersects = prepared_predicate(GEOSFunc('GEOSPreparedIntersects')) diff --git a/django/contrib/gis/geos/prototypes/threadsafe.py b/django/contrib/gis/geos/prototypes/threadsafe.py new file mode 100644 index 0000000000..5888ed16e2 --- /dev/null +++ b/django/contrib/gis/geos/prototypes/threadsafe.py @@ -0,0 +1,90 @@ +import threading +from django.contrib.gis.geos.libgeos import lgeos, notice_h, error_h, CONTEXT_PTR + +class GEOSContextHandle(object): + """ + Python object representing a GEOS context handle. + """ + def __init__(self): + # Initializing the context handler for this thread with + # the notice and error handler. + self.ptr = lgeos.initGEOS_r(notice_h, error_h) + + def __del__(self): + if self.ptr: lgeos.finishGEOS_r(self.ptr) + +# Defining a thread-local object and creating an instance +# to hold a reference to GEOSContextHandle for this thread. +class GEOSContext(threading.local): + handle = None + +thread_context = GEOSContext() + +def call_geos_threaded(cfunc, args): + """ + This module-level routine calls the specified GEOS C thread-safe + function with the context for this current thread. + """ + # If a context handle does not exist for this thread, initialize one. + if not thread_context.handle: + thread_context.handle = GEOSContextHandle() + # Call the threaded GEOS routine with pointer of the context handle + # as the first argument. + return cfunc(thread_context.handle.ptr, *args) + +class GEOSFunc(object): + """ + Class that serves as a wrapper for GEOS C Functions, and will + use thread-safe function variants when available. + """ + def __init__(self, func_name): + try: + # GEOS thread-safe function signatures end with '_r', and + # take an additional context handle parameter. + self.cfunc = getattr(lgeos, func_name + '_r') + self.threaded = True + except AttributeError: + # Otherwise, use usual function. + self.cfunc = getattr(lgeos, func_name) + self.threaded = False + + def __call__(self, *args): + if self.threaded: + return call_geos_threaded(self.cfunc, args) + else: + return self.cfunc(*args) + + def __str__(self): + return self.cfunc.__name__ + + # argtypes property + def _get_argtypes(self): + return self.cfunc.argtypes + + def _set_argtypes(self, argtypes): + if self.threaded: + new_argtypes = [CONTEXT_PTR] + new_argtypes.extend(argtypes) + self.cfunc.argtypes = new_argtypes + else: + self.cfunc.argtypes = argtypes + + argtypes = property(_get_argtypes, _set_argtypes) + + # restype property + def _get_restype(self): + return self.cfunc.restype + + def _set_restype(self, restype): + self.cfunc.restype = restype + + restype = property(_get_restype, _set_restype) + + # errcheck property + def _get_errcheck(self): + return self.cfunc.errcheck + + def _set_errcheck(self, errcheck): + self.cfunc.errcheck = errcheck + + errcheck = property(_get_errcheck, _set_errcheck) diff --git a/django/contrib/gis/geos/prototypes/topology.py b/django/contrib/gis/geos/prototypes/topology.py index 65c26f9f37..50817f9e38 100644 --- a/django/contrib/gis/geos/prototypes/topology.py +++ b/django/contrib/gis/geos/prototypes/topology.py @@ -8,9 +8,10 @@ __all__ = ['geos_boundary', 'geos_buffer', 'geos_centroid', 'geos_convexhull', 'geos_simplify', 'geos_symdifference', 'geos_union', 'geos_relate'] from ctypes import c_char_p, c_double, c_int -from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, GEOS_PREPARE +from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string from django.contrib.gis.geos.prototypes.geom import geos_char_p +from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc def topology(func, *args): "For GEOS unary topology functions." @@ -22,29 +23,29 @@ def topology(func, *args): return func ### Topology Routines ### -geos_boundary = topology(lgeos.GEOSBoundary) -geos_buffer = topology(lgeos.GEOSBuffer, c_double, c_int) -geos_centroid = topology(lgeos.GEOSGetCentroid) -geos_convexhull = topology(lgeos.GEOSConvexHull) -geos_difference = topology(lgeos.GEOSDifference, GEOM_PTR) -geos_envelope = topology(lgeos.GEOSEnvelope) -geos_intersection = topology(lgeos.GEOSIntersection, GEOM_PTR) -geos_linemerge = topology(lgeos.GEOSLineMerge) -geos_pointonsurface = topology(lgeos.GEOSPointOnSurface) -geos_preservesimplify = topology(lgeos.GEOSTopologyPreserveSimplify, c_double) -geos_simplify = topology(lgeos.GEOSSimplify, c_double) -geos_symdifference = topology(lgeos.GEOSSymDifference, GEOM_PTR) -geos_union = topology(lgeos.GEOSUnion, GEOM_PTR) +geos_boundary = topology(GEOSFunc('GEOSBoundary')) +geos_buffer = topology(GEOSFunc('GEOSBuffer'), c_double, c_int) +geos_centroid = topology(GEOSFunc('GEOSGetCentroid')) +geos_convexhull = topology(GEOSFunc('GEOSConvexHull')) +geos_difference = topology(GEOSFunc('GEOSDifference'), GEOM_PTR) +geos_envelope = topology(GEOSFunc('GEOSEnvelope')) +geos_intersection = topology(GEOSFunc('GEOSIntersection'), GEOM_PTR) +geos_linemerge = topology(GEOSFunc('GEOSLineMerge')) +geos_pointonsurface = topology(GEOSFunc('GEOSPointOnSurface')) +geos_preservesimplify = topology(GEOSFunc('GEOSTopologyPreserveSimplify'), c_double) +geos_simplify = topology(GEOSFunc('GEOSSimplify'), c_double) +geos_symdifference = topology(GEOSFunc('GEOSSymDifference'), GEOM_PTR) +geos_union = topology(GEOSFunc('GEOSUnion'), GEOM_PTR) # GEOSRelate returns a string, not a geometry. -geos_relate = lgeos.GEOSRelate +geos_relate = GEOSFunc('GEOSRelate') geos_relate.argtypes = [GEOM_PTR, GEOM_PTR] geos_relate.restype = geos_char_p geos_relate.errcheck = check_string # Routines only in GEOS 3.1+ if GEOS_PREPARE: - geos_cascaded_union = lgeos.GEOSUnionCascaded + geos_cascaded_union = GEOSFunc('GEOSUnionCascaded') geos_cascaded_union.argtypes = [GEOM_PTR] geos_cascaded_union.restype = GEOM_PTR __all__.append('geos_cascaded_union')