You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

194 lines
6.1 KiB

  1. """
  2. This module houses the ctypes initialization procedures, as well
  3. as the notice and error handler function callbacks (get called
  4. when an error occurs in GEOS).
  5. This module also houses GEOS Pointer utilities, including
  6. get_pointer_arr(), and GEOM_PTR.
  7. """
  8. import logging
  9. import os
  10. import re
  11. from ctypes import CDLL, CFUNCTYPE, POINTER, Structure, c_char_p
  12. from ctypes.util import find_library
  13. from django.contrib.gis.geos.error import GEOSException
  14. from django.core.exceptions import ImproperlyConfigured
  15. from django.utils.functional import SimpleLazyObject
  16. logger = logging.getLogger('django.contrib.gis')
  17. def load_geos():
  18. # Custom library path set?
  19. try:
  20. from django.conf import settings
  21. lib_path = settings.GEOS_LIBRARY_PATH
  22. except (AttributeError, EnvironmentError,
  23. ImportError, ImproperlyConfigured):
  24. lib_path = None
  25. # Setting the appropriate names for the GEOS-C library.
  26. if lib_path:
  27. lib_names = None
  28. elif os.name == 'nt':
  29. # Windows NT libraries
  30. lib_names = ['geos_c', 'libgeos_c-1']
  31. elif os.name == 'posix':
  32. # *NIX libraries
  33. lib_names = ['geos_c', 'GEOS']
  34. else:
  35. raise ImportError('Unsupported OS "%s"' % os.name)
  36. # Using the ctypes `find_library` utility to find the path to the GEOS
  37. # shared library. This is better than manually specifying each library name
  38. # and extension (e.g., libgeos_c.[so|so.1|dylib].).
  39. if lib_names:
  40. for lib_name in lib_names:
  41. lib_path = find_library(lib_name)
  42. if lib_path is not None:
  43. break
  44. # No GEOS library could be found.
  45. if lib_path is None:
  46. raise ImportError(
  47. 'Could not find the GEOS library (tried "%s"). '
  48. 'Try setting GEOS_LIBRARY_PATH in your settings.' %
  49. '", "'.join(lib_names)
  50. )
  51. # Getting the GEOS C library. The C interface (CDLL) is used for
  52. # both *NIX and Windows.
  53. # See the GEOS C API source code for more details on the library function calls:
  54. # http://geos.refractions.net/ro/doxygen_docs/html/geos__c_8h-source.html
  55. _lgeos = CDLL(lib_path)
  56. # Here we set up the prototypes for the initGEOS_r and finishGEOS_r
  57. # routines. These functions aren't actually called until they are
  58. # attached to a GEOS context handle -- this actually occurs in
  59. # geos/prototypes/threadsafe.py.
  60. _lgeos.initGEOS_r.restype = CONTEXT_PTR
  61. _lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
  62. return _lgeos
  63. # The notice and error handler C function callback definitions.
  64. # Supposed to mimic the GEOS message handler (C below):
  65. # typedef void (*GEOSMessageHandler)(const char *fmt, ...);
  66. NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
  67. def notice_h(fmt, lst):
  68. fmt, lst = fmt.decode(), lst.decode()
  69. try:
  70. warn_msg = fmt % lst
  71. except TypeError:
  72. warn_msg = fmt
  73. logger.warning('GEOS_NOTICE: %s\n' % warn_msg)
  74. notice_h = NOTICEFUNC(notice_h)
  75. ERRORFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
  76. def error_h(fmt, lst):
  77. fmt, lst = fmt.decode(), lst.decode()
  78. try:
  79. err_msg = fmt % lst
  80. except TypeError:
  81. err_msg = fmt
  82. logger.error('GEOS_ERROR: %s\n' % err_msg)
  83. error_h = ERRORFUNC(error_h)
  84. # #### GEOS Geometry C data structures, and utility functions. ####
  85. # Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR
  86. class GEOSGeom_t(Structure):
  87. pass
  88. class GEOSPrepGeom_t(Structure):
  89. pass
  90. class GEOSCoordSeq_t(Structure):
  91. pass
  92. class GEOSContextHandle_t(Structure):
  93. pass
  94. # Pointers to opaque GEOS geometry structures.
  95. GEOM_PTR = POINTER(GEOSGeom_t)
  96. PREPGEOM_PTR = POINTER(GEOSPrepGeom_t)
  97. CS_PTR = POINTER(GEOSCoordSeq_t)
  98. CONTEXT_PTR = POINTER(GEOSContextHandle_t)
  99. # Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection
  100. # GEOS routines
  101. def get_pointer_arr(n):
  102. "Gets a ctypes pointer array (of length `n`) for GEOSGeom_t opaque pointer."
  103. GeomArr = GEOM_PTR * n
  104. return GeomArr()
  105. lgeos = SimpleLazyObject(load_geos)
  106. class GEOSFuncFactory(object):
  107. """
  108. Lazy loading of GEOS functions.
  109. """
  110. argtypes = None
  111. restype = None
  112. errcheck = None
  113. def __init__(self, func_name, *args, **kwargs):
  114. self.func_name = func_name
  115. self.restype = kwargs.pop('restype', self.restype)
  116. self.errcheck = kwargs.pop('errcheck', self.errcheck)
  117. self.argtypes = kwargs.pop('argtypes', self.argtypes)
  118. self.args = args
  119. self.kwargs = kwargs
  120. self.func = None
  121. def __call__(self, *args, **kwargs):
  122. if self.func is None:
  123. self.func = self.get_func(*self.args, **self.kwargs)
  124. return self.func(*args, **kwargs)
  125. def get_func(self, *args, **kwargs):
  126. from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
  127. func = GEOSFunc(self.func_name)
  128. func.argtypes = self.argtypes or []
  129. func.restype = self.restype
  130. if self.errcheck:
  131. func.errcheck = self.errcheck
  132. return func
  133. # Returns the string version of the GEOS library. Have to set the restype
  134. # explicitly to c_char_p to ensure compatibility across 32 and 64-bit platforms.
  135. geos_version = GEOSFuncFactory('GEOSversion', restype=c_char_p)
  136. # Regular expression should be able to parse version strings such as
  137. # '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'
  138. version_regex = re.compile(
  139. r'^(?P<version>(?P<major>\d+)\.(?P<minor>\d+)\.(?P<subminor>\d+))'
  140. r'((rc(?P<release_candidate>\d+))|dev)?-CAPI-(?P<capi_version>\d+\.\d+\.\d+)( r\d+)?$'
  141. )
  142. def geos_version_info():
  143. """
  144. Returns a dictionary containing the various version metadata parsed from
  145. the GEOS version string, including the version number, whether the version
  146. is a release candidate (and what number release candidate), and the C API
  147. version.
  148. """
  149. ver = geos_version().decode()
  150. m = version_regex.match(ver)
  151. if not m:
  152. raise GEOSException('Could not parse version info string "%s"' % ver)
  153. return {key: m.group(key) for key in (
  154. 'version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor')}