Commit 61d09e61 authored by Claude Paroz's avatar Claude Paroz
Browse files

Lazy loading of GEOS functions

parent 143255c8
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
from django.contrib.gis.geos import geos_version
from django.contrib.gis.geos import geos_version_info
from django.db.backends.sqlite3.features import \
    DatabaseFeatures as SQLiteDatabaseFeatures
from django.utils.encoding import force_text
from django.utils.functional import cached_property


@@ -20,4 +19,4 @@ class DatabaseFeatures(BaseSpatialFeatures, SQLiteDatabaseFeatures):

    @cached_property
    def supports_3d_storage(self):
        return force_text(geos_version()) >= '3.3'
        return geos_version_info()['version'] >= '3.3'
+86 −60
Original line number Diff line number Diff line
@@ -14,9 +14,12 @@ 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

logger = logging.getLogger('django.contrib.gis')


def load_geos():
    # Custom library path set?
    try:
        from django.conf import settings
@@ -53,12 +56,19 @@ if lib_path is None:
            '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)
    _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 -- this actually occurs in
    # geos/prototypes/threadsafe.py.
    _lgeos.initGEOS_r.restype = CONTEXT_PTR
    _lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
    return _lgeos


# The notice and error handler C function callback definitions.
# Supposed to mimic the GEOS message handler (C below):
@@ -120,11 +130,42 @@ def get_pointer_arr(n):
    GeomArr = GEOM_PTR * n
    return GeomArr()


lgeos = SimpleLazyObject(load_geos)


class GEOSFuncFactory(object):
    argtypes = None
    restype = None
    errcheck = None

    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)
        return self.func(*args, **kwargs)

    def get_func(self, *args, **kwargs):
        from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
        func = GEOSFunc(self.func_name)
        func.argtypes = self.argtypes or []
        func.restype = self.restype
        if self.errcheck:
            func.errcheck = self.errcheck
        return func


# Returns the string version of the GEOS library. Have to set the restype
# explicitly to c_char_p to ensure compatibility across 32 and 64-bit platforms.
geos_version = lgeos.GEOSversion
geos_version.argtypes = None
geos_version.restype = c_char_p
geos_version = GEOSFuncFactory('GEOSversion', restype=c_char_p)

# 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'
@@ -147,18 +188,3 @@ def geos_version_info():
        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')}

# Version numbers and whether or not prepared geometry support is available.
_verinfo = geos_version_info()
GEOS_MAJOR_VERSION = int(_verinfo['major'])
GEOS_MINOR_VERSION = int(_verinfo['minor'])
GEOS_SUBMINOR_VERSION = int(_verinfo['subminor'])
del _verinfo
GEOS_VERSION = (GEOS_MAJOR_VERSION, GEOS_MINOR_VERSION, GEOS_SUBMINOR_VERSION)

# 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]
+54 −55
Original line number Diff line number Diff line
from ctypes import POINTER, c_double, c_int, c_uint

from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR
from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, GEOSFuncFactory
from django.contrib.gis.geos.prototypes.errcheck import (
    GEOSException, last_arg_byref,
)
from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc


# ## Error-checking routines specific to coordinate sequences. ##
def check_cs_ptr(result, func, cargs):
    "Error checking on routines that return Geometries."
    if not result:
        raise GEOSException(
            'Error encountered checking Coordinate Sequence returned from GEOS '
            'C function "%s".' % func.__name__
        )
    return result


def check_cs_op(result, func, cargs):
    "Checks the status code of a coordinate sequence operation."
    if result == 0:
@@ -33,63 +22,73 @@ def check_cs_get(result, func, cargs):
    return last_arg_byref(cargs)


# ## Coordinate sequence prototype generation functions. ##
def cs_int(func):
# ## Coordinate sequence prototype factory classes. ##
class CsInt(GEOSFuncFactory):
    "For coordinate sequence routines that return an integer."
    func.argtypes = [CS_PTR, POINTER(c_uint)]
    func.restype = c_int
    func.errcheck = check_cs_get
    return func
    argtypes = [CS_PTR, POINTER(c_uint)]
    restype = c_int
    errcheck = staticmethod(check_cs_get)


def cs_operation(func, ordinate=False, get=False):
class CsOperation(GEOSFuncFactory):
    "For coordinate sequence operations."
    restype = c_int

    def get_func(self, ordinate=False, get=False):
        if get:
        # Get routines get double parameter passed-in by reference.
        func.errcheck = check_cs_get
            # Get routines have double parameter passed-in by reference.
            self.errcheck = check_cs_get
            dbl_param = POINTER(c_double)
        else:
        func.errcheck = check_cs_op
            self.errcheck = check_cs_op
            dbl_param = c_double

        if ordinate:
            # Get/Set ordinate routines have an extra uint parameter.
        func.argtypes = [CS_PTR, c_uint, c_uint, dbl_param]
            self.argtypes = [CS_PTR, c_uint, c_uint, dbl_param]
        else:
        func.argtypes = [CS_PTR, c_uint, dbl_param]
            self.argtypes = [CS_PTR, c_uint, dbl_param]
        return super(CsOperation, self).get_func()

    func.restype = c_int
    return func

class CsOutput(GEOSFuncFactory):
    restype = CS_PTR

    def get_func(self, argtypes):
        self.argtypes = argtypes
        return super(CsOutput, self).get_func()

    @staticmethod
    def errcheck(result, func, cargs):
        if not result:
            raise GEOSException(
                'Error encountered checking Coordinate Sequence returned from GEOS '
                'C function "%s".' % func.__name__
            )
        return result

def cs_output(func, argtypes):
    "For routines that return a coordinate sequence."
    func.argtypes = argtypes
    func.restype = CS_PTR
    func.errcheck = check_cs_ptr
    return func

# ## Coordinate Sequence ctypes prototypes ##

# Coordinate Sequence constructors & cloning.
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])
cs_clone = CsOutput('GEOSCoordSeq_clone', [CS_PTR])
create_cs = CsOutput('GEOSCoordSeq_create', [c_uint, c_uint])
get_cs = CsOutput('GEOSGeom_getCoordSeq', [GEOM_PTR])

# Getting, setting ordinate
cs_getordinate = cs_operation(GEOSFunc('GEOSCoordSeq_getOrdinate'), ordinate=True, get=True)
cs_setordinate = cs_operation(GEOSFunc('GEOSCoordSeq_setOrdinate'), ordinate=True)
cs_getordinate = CsOperation('GEOSCoordSeq_getOrdinate', ordinate=True, get=True)
cs_setordinate = CsOperation('GEOSCoordSeq_setOrdinate', ordinate=True)

# For getting, x, y, z
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)
cs_getx = CsOperation('GEOSCoordSeq_getX', get=True)
cs_gety = CsOperation('GEOSCoordSeq_getY', get=True)
cs_getz = CsOperation('GEOSCoordSeq_getZ', get=True)

# For setting, x, y, z
cs_setx = cs_operation(GEOSFunc('GEOSCoordSeq_setX'))
cs_sety = cs_operation(GEOSFunc('GEOSCoordSeq_setY'))
cs_setz = cs_operation(GEOSFunc('GEOSCoordSeq_setZ'))
cs_setx = CsOperation('GEOSCoordSeq_setX')
cs_sety = CsOperation('GEOSCoordSeq_setY')
cs_setz = CsOperation('GEOSCoordSeq_setZ')

# These routines return size & dimensions.
cs_getsize = cs_int(GEOSFunc('GEOSCoordSeq_getSize'))
cs_getdims = cs_int(GEOSFunc('GEOSCoordSeq_getDimensions'))
cs_getsize = CsInt('GEOSCoordSeq_getSize')
cs_getdims = CsInt('GEOSCoordSeq_getDimensions')
+2 −3
Original line number Diff line number Diff line
@@ -4,13 +4,12 @@
from ctypes import c_void_p, string_at

from django.contrib.gis.geos.error import GEOSException
from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
from django.contrib.gis.geos.libgeos import GEOSFuncFactory

# Getting the `free` routine used to free the memory allocated for
# string pointers returned by GEOS.
free = GEOSFunc('GEOSFree')
free = GEOSFuncFactory('GEOSFree')
free.argtypes = [c_void_p]
free.restype = None


def last_arg_byref(args):
+56 −70
Original line number Diff line number Diff line
from ctypes import POINTER, c_char_p, c_int, c_size_t, c_ubyte

from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR
from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, GEOSFuncFactory
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)
@@ -21,109 +20,96 @@ class geos_char_p(c_char_p):
    pass


# ### ctypes generation functions ###
def bin_constructor(func):
# ### ctypes factory classes ###
class BinConstructor(GEOSFuncFactory):
    "Generates a prototype for binary construction (HEX, WKB) GEOS routines."
    func.argtypes = [c_char_p, c_size_t]
    func.restype = GEOM_PTR
    func.errcheck = check_geom
    return func
    argtypes = [c_char_p, c_size_t]
    restype = GEOM_PTR
    errcheck = staticmethod(check_geom)


# HEX & WKB output
def bin_output(func):
class BinOutput(GEOSFuncFactory):
    "Generates a prototype for the routines that return a sized string."
    func.argtypes = [GEOM_PTR, POINTER(c_size_t)]
    func.errcheck = check_sized_string
    func.restype = c_uchar_p
    return func
    argtypes = [GEOM_PTR, POINTER(c_size_t)]
    restype = c_uchar_p
    errcheck = staticmethod(check_sized_string)


def geom_output(func, argtypes):
class GeomOutput(GEOSFuncFactory):
    "For GEOS routines that return a geometry."
    if argtypes:
        func.argtypes = argtypes
    func.restype = GEOM_PTR
    func.errcheck = check_geom
    return func
    restype = GEOM_PTR
    errcheck = staticmethod(check_geom)

    def get_func(self, argtypes):
        self.argtypes = argtypes
        return super(GeomOutput, self).get_func()

def geom_index(func):
    "For GEOS routines that return geometries from an index."
    return geom_output(func, [GEOM_PTR, c_int])


def int_from_geom(func, zero=False):
class IntFromGeom(GEOSFuncFactory):
    "Argument is a geometry, return type is an integer."
    func.argtypes = [GEOM_PTR]
    func.restype = c_int
    argtypes = [GEOM_PTR]
    restype = c_int

    def get_func(self, zero=False):
        if zero:
        func.errcheck = check_zero
            self.errcheck = check_zero
        else:
        func.errcheck = check_minus_one
    return func
            self.errcheck = check_minus_one
        return super(IntFromGeom, self).get_func()


def string_from_geom(func):
class StringFromGeom(GEOSFuncFactory):
    "Argument is a Geometry, return type is a string."
    func.argtypes = [GEOM_PTR]
    func.restype = geos_char_p
    func.errcheck = check_string
    return func
    argtypes = [GEOM_PTR]
    restype = geos_char_p
    errcheck = staticmethod(check_string)


# ### ctypes prototypes ###

# 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])
from_hex = BinConstructor('GEOSGeomFromHEX_buf')
from_wkb = BinConstructor('GEOSGeomFromWKB_buf')
from_wkt = GeomOutput('GEOSGeomFromWKT', [c_char_p])

# Deprecated output routines
to_hex = bin_output(GEOSFunc('GEOSGeomToHEX_buf'))
to_wkb = bin_output(GEOSFunc('GEOSGeomToWKB_buf'))
to_wkt = string_from_geom(GEOSFunc('GEOSGeomToWKT'))
to_hex = BinOutput('GEOSGeomToHEX_buf')
to_wkb = BinOutput('GEOSGeomToWKB_buf')
to_wkt = StringFromGeom('GEOSGeomToWKT')

# 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'))
geos_normalize = IntFromGeom('GEOSNormalize')
geos_type = StringFromGeom('GEOSGeomType')
geos_typeid = IntFromGeom('GEOSGeomTypeId')
get_dims = IntFromGeom('GEOSGeom_getDimensions', zero=True)
get_num_coords = IntFromGeom('GEOSGetNumCoordinates')
get_num_geoms = IntFromGeom('GEOSGetNumGeometries')

# Geometry creation factories
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])
create_point = GeomOutput('GEOSGeom_createPoint', [CS_PTR])
create_linestring = GeomOutput('GEOSGeom_createLineString', [CS_PTR])
create_linearring = GeomOutput('GEOSGeom_createLinearRing', [CS_PTR])

# Polygon and collection creation routines are special and will not
# have their argument types defined.
create_polygon = geom_output(GEOSFunc('GEOSGeom_createPolygon'), None)
create_collection = geom_output(GEOSFunc('GEOSGeom_createCollection'), None)
create_polygon = GeomOutput('GEOSGeom_createPolygon', None)
create_collection = GeomOutput('GEOSGeom_createCollection', None)

# Ring routines
get_extring = geom_output(GEOSFunc('GEOSGetExteriorRing'), [GEOM_PTR])
get_intring = geom_index(GEOSFunc('GEOSGetInteriorRingN'))
get_nrings = int_from_geom(GEOSFunc('GEOSGetNumInteriorRings'))
get_extring = GeomOutput('GEOSGetExteriorRing', [GEOM_PTR])
get_intring = GeomOutput('GEOSGetInteriorRingN', [GEOM_PTR, c_int])
get_nrings = IntFromGeom('GEOSGetNumInteriorRings')

# Collection Routines
get_geomn = geom_index(GEOSFunc('GEOSGetGeometryN'))
get_geomn = GeomOutput('GEOSGetGeometryN', [GEOM_PTR, c_int])

# Cloning
geom_clone = GEOSFunc('GEOSGeom_clone')
geom_clone.argtypes = [GEOM_PTR]
geom_clone.restype = GEOM_PTR
geom_clone = GEOSFuncFactory('GEOSGeom_clone', argtypes=[GEOM_PTR], restype=GEOM_PTR)

# Destruction routine.
destroy_geom = GEOSFunc('GEOSGeom_destroy')
destroy_geom.argtypes = [GEOM_PTR]
destroy_geom.restype = None
destroy_geom = GEOSFuncFactory('GEOSGeom_destroy', argtypes=[GEOM_PTR])

# SRID routines
geos_get_srid = GEOSFunc('GEOSGetSRID')
geos_get_srid.argtypes = [GEOM_PTR]
geos_get_srid.restype = c_int

geos_set_srid = GEOSFunc('GEOSSetSRID')
geos_set_srid.argtypes = [GEOM_PTR, c_int]
geos_set_srid.restype = None
geos_get_srid = GEOSFuncFactory('GEOSGetSRID', argtypes=[GEOM_PTR], restype=c_int)
geos_set_srid = GEOSFuncFactory('GEOSSetSRID', argtypes=[GEOM_PTR, c_int])
Loading