Permalink
Browse files

Fixed #10923 -- The GEOS bindings now use the thread-safe API, when a…

…pplicable. 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
  • Loading branch information...
1 parent 418bfa2 commit 32e0117abb205da87a6d6251b9ce3490d4dd8bda @jbronn jbronn committed Jan 12, 2010
@@ -21,6 +21,10 @@
# 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,21 +65,21 @@ def __init__(self, geo_input, srid=None):
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):
# When the input is a pointer to a geomtry (GEOM_PTR).
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 @@ def ewkt(self):
@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 @@ def hex(self):
"""
# 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 @@ def hexewkb(self):
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 @@ def wkb(self):
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 @@ def ewkb(self):
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 @@ def transform(self, ct, clone=False):
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 @@ def clone(self):
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
@@ -3,128 +3,18 @@
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
-
-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)
-
-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
+from django.contrib.gis.geos.prototypes.io import _WKTReader, _WKBReader, WKBWriter, WKTWriter
+# 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 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)
+class WKTReader(_WKTReader):
+ def read(self, wkt):
+ "Returns a GEOSGeometry for the given WKT string."
+ return GEOSGeometry(super(WKTReader, self).read(wkt))
-# 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
@@ -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 @@
'", "'.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 @@ def geos_version_info():
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)
@@ -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'))
@@ -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:
Oops, something went wrong.

0 comments on commit 32e0117

Please sign in to comment.