django1/django/contrib/gis/geos/libgeos.py

220 lines
6.8 KiB
Python
Raw Normal View History

"""
This module houses the ctypes initialization procedures, as well
as the notice and error handler function callbacks (get called
when an error occurs in GEOS).
This module also houses GEOS Pointer utilities, including
get_pointer_arr(), and GEOM_PTR.
"""
import logging
import os
import re
import threading
from ctypes import CDLL, CFUNCTYPE, POINTER, Structure, c_char_p
from ctypes.util import find_library
from django.contrib.gis.geos.error import GEOSException
from django.core.exceptions import ImproperlyConfigured
from django.utils.functional import SimpleLazyObject, cached_property
logger = logging.getLogger('django.contrib.gis')
2015-04-24 21:45:36 +08:00
def load_geos():
# Custom library path set?
try:
from django.conf import settings
lib_path = settings.GEOS_LIBRARY_PATH
except (AttributeError, EnvironmentError,
ImportError, ImproperlyConfigured):
lib_path = None
# Setting the appropriate names for the GEOS-C library.
if lib_path:
lib_names = None
elif os.name == 'nt':
# Windows NT libraries
lib_names = ['geos_c', 'libgeos_c-1']
elif os.name == 'posix':
# *NIX libraries
lib_names = ['geos_c', 'GEOS']
else:
raise ImportError('Unsupported OS "%s"' % os.name)
# Using the ctypes `find_library` utility to find the path to the GEOS
# shared library. This is better than manually specifying each library name
# and extension (e.g., libgeos_c.[so|so.1|dylib].).
if lib_names:
for lib_name in lib_names:
lib_path = find_library(lib_name)
if lib_path is not None:
break
# No GEOS library could be found.
if lib_path is None:
raise ImportError(
'Could not find the GEOS library (tried "%s"). '
'Try setting GEOS_LIBRARY_PATH in your settings.' %
'", "'.join(lib_names)
)
# Getting the GEOS C library. The C interface (CDLL) is used for
# 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)
# 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.
2015-04-24 21:45:36 +08:00
_lgeos.initGEOS_r.restype = CONTEXT_PTR
_lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
# Ensures compatibility across 32 and 64-bit platforms.
_lgeos.GEOSversion.restype = c_char_p
2015-04-24 21:45:36 +08:00
return _lgeos
# The notice and error handler C function callback definitions.
# Supposed to mimic the GEOS message handler (C below):
# typedef void (*GEOSMessageHandler)(const char *fmt, ...);
NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
2013-11-03 04:12:09 +08:00
def notice_h(fmt, lst):
2012-09-24 01:59:27 +08:00
fmt, lst = fmt.decode(), lst.decode()
try:
warn_msg = fmt % lst
except TypeError:
warn_msg = fmt
logger.warning('GEOS_NOTICE: %s\n', warn_msg)
notice_h = NOTICEFUNC(notice_h)
ERRORFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
2013-11-03 04:12:09 +08:00
def error_h(fmt, lst):
2012-09-24 01:59:27 +08:00
fmt, lst = fmt.decode(), lst.decode()
try:
err_msg = fmt % lst
except TypeError:
err_msg = fmt
logger.error('GEOS_ERROR: %s\n', err_msg)
error_h = ERRORFUNC(error_h)
2015-02-06 02:25:34 +08:00
# #### GEOS Geometry C data structures, and utility functions. ####
2013-11-03 04:12:09 +08:00
# Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR
class GEOSGeom_t(Structure):
pass
2013-11-03 04:12:09 +08:00
class GEOSPrepGeom_t(Structure):
pass
2013-11-03 04:12:09 +08:00
class GEOSCoordSeq_t(Structure):
pass
2013-11-03 04:12:09 +08:00
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)
2013-10-22 21:31:43 +08:00
CONTEXT_PTR = POINTER(GEOSContextHandle_t)
2013-11-03 04:12:09 +08:00
# Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection
# GEOS routines
def get_pointer_arr(n):
"Gets a ctypes pointer array (of length `n`) for GEOSGeom_t opaque pointer."
GeomArr = GEOM_PTR * n
return GeomArr()
2015-04-24 21:45:36 +08:00
lgeos = SimpleLazyObject(load_geos)
class GEOSContextHandle(object):
def __init__(self):
# Initializing the context handle for this thread with
# the notice and error handler.
self.ptr = lgeos.initGEOS_r(notice_h, error_h)
def __del__(self):
if self.ptr and lgeos:
lgeos.finishGEOS_r(self.ptr)
class GEOSContext(threading.local):
@cached_property
def ptr(self):
# Assign handle so it will will garbage collected when
# thread is finished.
self.handle = GEOSContextHandle()
return self.handle.ptr
2015-04-24 21:45:36 +08:00
class GEOSFuncFactory(object):
2015-04-25 00:09:20 +08:00
"""
Lazy loading of GEOS functions.
"""
2015-04-24 21:45:36 +08:00
argtypes = None
restype = None
errcheck = None
thread_context = GEOSContext()
2015-04-24 21:45:36 +08:00
def __init__(self, func_name, *args, **kwargs):
self.func_name = func_name
self.restype = kwargs.pop('restype', self.restype)
self.errcheck = kwargs.pop('errcheck', self.errcheck)
self.argtypes = kwargs.pop('argtypes', self.argtypes)
self.args = args
self.kwargs = kwargs
self.func = None
def __call__(self, *args, **kwargs):
if self.func is None:
self.func = self.get_func(*self.args, **self.kwargs)
# Call the threaded GEOS routine with pointer of the context handle
# as the first argument.
return self.func(self.thread_context.ptr, *args)
2015-04-24 21:45:36 +08:00
def get_func(self, *args, **kwargs):
# GEOS thread-safe function signatures end with '_r' and
# take an additional context handle parameter.
func = getattr(lgeos, self.func_name + '_r')
func.argtypes = [CONTEXT_PTR] + (self.argtypes or [])
2015-04-24 21:45:36 +08:00
func.restype = self.restype
if self.errcheck:
func.errcheck = self.errcheck
return func
# Returns the string version of the GEOS library.
geos_version = lambda: lgeos.GEOSversion()
# Regular expression should be able to parse version strings such as
# '3.0.0rc4-CAPI-1.3.3', '3.0.0-CAPI-1.4.1', '3.4.0dev-CAPI-1.8.0' or '3.4.0dev-CAPI-1.8.0 r0'
version_regex = re.compile(
r'^(?P<version>(?P<major>\d+)\.(?P<minor>\d+)\.(?P<subminor>\d+))'
r'((rc(?P<release_candidate>\d+))|dev)?-CAPI-(?P<capi_version>\d+\.\d+\.\d+)( r\d+)?$'
)
2013-11-03 04:12:09 +08:00
def geos_version_info():
"""
Returns a dictionary containing the various version metadata parsed from
the GEOS version string, including the version number, whether the version
is a release candidate (and what number release candidate), and the C API
version.
"""
ver = geos_version().decode()
m = version_regex.match(ver)
if not m:
raise GEOSException('Could not parse version info string "%s"' % ver)
return {key: m.group(key) for key in (
'version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor')}