""" Proxies for libgeos, GEOS-specific exceptions, and utilities """ import atexit from ctypes import ( CDLL, cdll, pointer, string_at, DEFAULT_MODE, c_void_p, c_size_t, c_char_p) from ctypes.util import find_library import glob import logging import os import re import sys import threading from functools import partial from .ctypes_declarations import prototype, EXCEPTION_HANDLER_FUNCTYPE from .errors import WKBReadingError, WKTReadingError, TopologicalError, PredicateError # Add message handler to this module's logger LOG = logging.getLogger(__name__) if sys.version_info[0] >= 3: text_types = str else: text_types = (str, unicode) # Find and load the GEOS and C libraries # If this ever gets any longer, we'll break it into separate modules def load_dll(libname, fallbacks=None, mode=DEFAULT_MODE): lib = find_library(libname) dll = None if lib is not None: try: LOG.debug("Trying `CDLL(%s)`", lib) dll = CDLL(lib, mode=mode) except OSError: LOG.debug("Failed `CDLL(%s)`", lib) pass if not dll and fallbacks is not None: for name in fallbacks: try: LOG.debug("Trying `CDLL(%s)`", name) dll = CDLL(name, mode=mode) except OSError: # move on to the next fallback LOG.debug("Failed `CDLL(%s)`", name) pass if dll: LOG.debug("Library path: %r", lib or name) LOG.debug("DLL: %r", dll) return dll else: # No shared library was loaded. Raise OSError. raise OSError( "Could not find lib {} or load any of its variants {}.".format( libname, fallbacks or [])) _lgeos = None if sys.platform.startswith('linux'): # Test to see if we have a wheel repaired by 'auditwheel' containing its # own libgeos_c geos_whl_so = glob.glob(os.path.abspath(os.path.join(os.path.dirname( __file__), '.libs/libgeos_c-*.so.*'))) if len(geos_whl_so) == 1: _lgeos = CDLL(geos_whl_so[0]) LOG.debug("Found GEOS DLL: %r, using it.", _lgeos) elif hasattr(sys, 'frozen'): geos_pyinstaller_so = glob.glob(os.path.join(sys.prefix, 'libgeos_c-*.so.*')) if len(geos_pyinstaller_so) == 1: _lgeos = CDLL(geos_pyinstaller_so[0]) LOG.debug("Found GEOS DLL: %r, using it.", _lgeos) elif os.getenv('CONDA_PREFIX', ''): # conda package. _lgeos = CDLL(os.path.join(sys.prefix, 'lib', 'libgeos_c.so')) else: alt_paths = [ 'libgeos_c.so.1', 'libgeos_c.so', ] _lgeos = load_dll('geos_c', fallbacks=alt_paths) # Necessary for environments with only libc.musl c_alt_paths = [ 'libc.musl-x86_64.so.1' ] free = load_dll('c', fallbacks=c_alt_paths).free free.argtypes = [c_void_p] free.restype = None elif sys.platform == 'darwin': # Test to see if we have a delocated wheel with a GEOS dylib. geos_whl_dylib = os.path.abspath(os.path.join(os.path.dirname( __file__), '.dylibs/libgeos_c.1.dylib')) if os.path.exists(geos_whl_dylib): handle = CDLL(None) if hasattr(handle, "initGEOS_r"): LOG.debug("GEOS already loaded") _lgeos = handle else: _lgeos = CDLL(geos_whl_dylib) LOG.debug("Found GEOS DLL: %r, using it.", _lgeos) elif os.getenv('CONDA_PREFIX', ''): # conda package. _lgeos = CDLL(os.path.join(sys.prefix, 'lib', 'libgeos_c.dylib')) else: if hasattr(sys, 'frozen'): try: # .app file from py2app alt_paths = [os.path.join( os.environ['RESOURCEPATH'], '..', 'Frameworks', 'libgeos_c.dylib')] except KeyError: # binary from pyinstaller alt_paths = [ os.path.join(sys.executable, 'libgeos_c.dylib')] if hasattr(sys, '_MEIPASS'): alt_paths.append( os.path.join(sys._MEIPASS, 'libgeos_c.1.dylib')) else: alt_paths = [ # The Framework build from Kyng Chaos "/Library/Frameworks/GEOS.framework/Versions/Current/GEOS", # macports '/opt/local/lib/libgeos_c.dylib', # homebrew '/usr/local/lib/libgeos_c.dylib', ] _lgeos = load_dll('geos_c', fallbacks=alt_paths) # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen # manpage says, "If filename is NULL, then the returned handle is for the # main program". This way we can let the linker do the work to figure out # which libc Python is actually using. free = CDLL(None).free free.argtypes = [c_void_p] free.restype = None elif sys.platform == 'win32': if os.getenv('CONDA_PREFIX', ''): # conda package. _lgeos = CDLL(os.path.join(sys.prefix, 'Library', 'bin', 'geos_c.dll')) else: try: egg_dlls = os.path.abspath( os.path.join(os.path.dirname(__file__), 'DLLs')) if hasattr(sys, '_MEIPASS'): wininst_dlls = sys._MEIPASS elif hasattr(sys, "frozen"): wininst_dlls = os.path.normpath( os.path.abspath(sys.executable + '../../DLLS')) else: wininst_dlls = os.path.abspath(os.__file__ + "../../../DLLs") original_path = os.environ['PATH'] os.environ['PATH'] = "%s;%s;%s" % \ (egg_dlls, wininst_dlls, original_path) _lgeos = load_dll("geos_c.dll") except (ImportError, WindowsError, OSError): raise def free(m): try: cdll.msvcrt.free(m) except WindowsError: # XXX: See http://trac.gispython.org/projects/PCL/ticket/149 pass elif sys.platform == 'sunos5': _lgeos = load_dll('geos_c', fallbacks=['libgeos_c.so.1', 'libgeos_c.so']) free = CDLL('libc.so.1').free free.argtypes = [c_void_p] free.restype = None else: # other *nix systems _lgeos = load_dll('geos_c', fallbacks=['libgeos_c.so.1', 'libgeos_c.so']) free = load_dll('c', fallbacks=['libc.so.6']).free free.argtypes = [c_void_p] free.restype = None def _geos_version(): GEOSversion = _lgeos.GEOSversion GEOSversion.restype = c_char_p GEOSversion.argtypes = [] geos_version_string = GEOSversion() if sys.version_info[0] >= 3: geos_version_string = geos_version_string.decode('ascii') res = re.findall(r'(\d+)\.(\d+)\.(\d+)', geos_version_string) assert len(res) == 2, res geos_version = tuple(int(x) for x in res[0]) capi_version = tuple(int(x) for x in res[1]) return geos_version_string, geos_version, capi_version geos_version_string, geos_version, geos_capi_version = _geos_version() # If we have the new interface, then record a baseline so that we know what # additional functions are declared in ctypes_declarations. if geos_version >= (3, 1, 0): start_set = set(_lgeos.__dict__) # Apply prototypes for the libgeos_c functions prototype(_lgeos, geos_version) # If we have the new interface, automatically detect all function # declarations, and declare their re-entrant counterpart. if geos_version >= (3, 1, 0): end_set = set(_lgeos.__dict__) new_func_names = end_set - start_set for func_name in new_func_names: new_func_name = "%s_r" % func_name if hasattr(_lgeos, new_func_name): new_func = getattr(_lgeos, new_func_name) old_func = getattr(_lgeos, func_name) new_func.restype = old_func.restype if old_func.argtypes is None: # Handle functions that didn't take an argument before, # finishGEOS. new_func.argtypes = [c_void_p] else: new_func.argtypes = [c_void_p] + list(old_func.argtypes) if old_func.errcheck is not None: new_func.errcheck = old_func.errcheck # Handle special case. _lgeos.initGEOS_r.restype = c_void_p _lgeos.initGEOS_r.argtypes = \ [EXCEPTION_HANDLER_FUNCTYPE, EXCEPTION_HANDLER_FUNCTYPE] _lgeos.finishGEOS_r.argtypes = [c_void_p] def make_logging_callback(func): """Error or notice handler callback producr Wraps a logger method, func, as a GEOS callback. """ def callback(fmt, *fmt_args): fmt = fmt.decode('ascii') conversions = re.findall(r'%.', fmt) args = [ string_at(arg).decode('ascii') for spec, arg in zip(conversions, fmt_args) if spec == '%s' and arg is not None] func(fmt, *args) return callback error_handler = make_logging_callback(LOG.error) notice_handler = make_logging_callback(LOG.info) error_h = EXCEPTION_HANDLER_FUNCTYPE(error_handler) notice_h = EXCEPTION_HANDLER_FUNCTYPE(notice_handler) class WKTReader(object): _lgeos = None _reader = None def __init__(self, lgeos): """Create WKT Reader""" self._lgeos = lgeos self._reader = self._lgeos.GEOSWKTReader_create() def __del__(self): """Destroy WKT Reader""" if self._lgeos is not None: self._lgeos.GEOSWKTReader_destroy(self._reader) self._reader = None self._lgeos = None def read(self, text): """Returns geometry from WKT""" if not isinstance(text, text_types): raise TypeError("Only str is accepted.") if sys.version_info[0] >= 3: text = text.encode() c_string = c_char_p(text) geom = self._lgeos.GEOSWKTReader_read(self._reader, c_string) if not geom: raise WKTReadingError( "Could not create geometry because of errors " "while reading input.") # avoid circular import dependency from shapely.geometry.base import geom_factory return geom_factory(geom) class WKTWriter(object): _lgeos = None _writer = None # Establish default output settings defaults = {} if geos_version >= (3, 3, 0): defaults['trim'] = True defaults['output_dimension'] = 3 # GEOS' defaults for methods without "get" _trim = False _rounding_precision = -1 _old_3d = False @property def trim(self): """Trimming of unnecessary decimals (default: True)""" return getattr(self, '_trim') @trim.setter def trim(self, value): self._trim = bool(value) self._lgeos.GEOSWKTWriter_setTrim(self._writer, self._trim) @property def rounding_precision(self): """Rounding precision when writing the WKT. A precision of -1 (default) disables it.""" return getattr(self, '_rounding_precision') @rounding_precision.setter def rounding_precision(self, value): self._rounding_precision = int(value) self._lgeos.GEOSWKTWriter_setRoundingPrecision( self._writer, self._rounding_precision) @property def output_dimension(self): """Output dimension, either 2 or 3 (default)""" return self._lgeos.GEOSWKTWriter_getOutputDimension( self._writer) @output_dimension.setter def output_dimension(self, value): self._lgeos.GEOSWKTWriter_setOutputDimension( self._writer, int(value)) @property def old_3d(self): """Show older style for 3D WKT, without 'Z' (default: False)""" return getattr(self, '_old_3d') @old_3d.setter def old_3d(self, value): self._old_3d = bool(value) self._lgeos.GEOSWKTWriter_setOld3D(self._writer, self._old_3d) def __init__(self, lgeos, **settings): """Create WKT Writer Note: writer defaults are set differently for GEOS 3.3.0 and up. For example, with 'POINT Z (1 2 3)': newer: POINT Z (1 2 3) older: POINT (1.0000000000000000 2.0000000000000000) The older formatting can be achieved for GEOS 3.3.0 and up by setting the properties: trim = False output_dimension = 2 """ self._lgeos = lgeos self._writer = self._lgeos.GEOSWKTWriter_create() applied_settings = self.defaults.copy() applied_settings.update(settings) for name in applied_settings: setattr(self, name, applied_settings[name]) def __setattr__(self, name, value): """Limit setting attributes""" if hasattr(self, name): object.__setattr__(self, name, value) else: raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, name)) def __del__(self): """Destroy WKT Writer""" if self._lgeos is not None: self._lgeos.GEOSWKTWriter_destroy(self._writer) self._writer = None self._lgeos = None def write(self, geom): """Returns WKT string for geometry""" if geom is None or geom._geom is None: raise ValueError("Null geometry supports no operations") result = self._lgeos.GEOSWKTWriter_write(self._writer, geom._geom) text = string_at(result) lgeos.GEOSFree(result) if sys.version_info[0] >= 3: return text.decode('ascii') else: return text class WKBReader(object): _lgeos = None _reader = None def __init__(self, lgeos): """Create WKB Reader""" self._lgeos = lgeos self._reader = self._lgeos.GEOSWKBReader_create() def __del__(self): """Destroy WKB Reader""" if self._lgeos is not None: self._lgeos.GEOSWKBReader_destroy(self._reader) self._reader = None self._lgeos = None def read(self, data): """Returns geometry from WKB""" geom = self._lgeos.GEOSWKBReader_read( self._reader, c_char_p(data), c_size_t(len(data))) if not geom: raise WKBReadingError( "Could not create geometry because of errors " "while reading input.") # avoid circular import dependency from shapely import geometry return geometry.base.geom_factory(geom) def read_hex(self, data): """Returns geometry from WKB hex""" if sys.version_info[0] >= 3: data = data.encode('ascii') geom = self._lgeos.GEOSWKBReader_readHEX( self._reader, c_char_p(data), c_size_t(len(data))) if not geom: raise WKBReadingError( "Could not create geometry because of errors " "while reading input.") # avoid circular import dependency from shapely import geometry return geometry.base.geom_factory(geom) class WKBWriter(object): _lgeos = None _writer = None # EndianType enum in ByteOrderValues.h _ENDIAN_BIG = 0 _ENDIAN_LITTLE = 1 # Establish default output setting defaults = {'output_dimension': 3} @property def output_dimension(self): """Output dimension, either 2 or 3 (default)""" return self._lgeos.GEOSWKBWriter_getOutputDimension(self._writer) @output_dimension.setter def output_dimension(self, value): self._lgeos.GEOSWKBWriter_setOutputDimension( self._writer, int(value)) @property def big_endian(self): """Byte order is big endian, True (default) or False""" return (self._lgeos.GEOSWKBWriter_getByteOrder(self._writer) == self._ENDIAN_BIG) @big_endian.setter def big_endian(self, value): self._lgeos.GEOSWKBWriter_setByteOrder( self._writer, self._ENDIAN_BIG if value else self._ENDIAN_LITTLE) @property def include_srid(self): """Include SRID, True or False (default)""" return bool(self._lgeos.GEOSWKBWriter_getIncludeSRID(self._writer)) @include_srid.setter def include_srid(self, value): self._lgeos.GEOSWKBWriter_setIncludeSRID(self._writer, bool(value)) def __init__(self, lgeos, **settings): """Create WKB Writer""" self._lgeos = lgeos self._writer = self._lgeos.GEOSWKBWriter_create() applied_settings = self.defaults.copy() applied_settings.update(settings) for name in applied_settings: setattr(self, name, applied_settings[name]) def __setattr__(self, name, value): """Limit setting attributes""" if hasattr(self, name): object.__setattr__(self, name, value) else: raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, name)) def __del__(self): """Destroy WKB Writer""" if self._lgeos is not None: self._lgeos.GEOSWKBWriter_destroy(self._writer) self._writer = None self._lgeos = None def write(self, geom): """Returns WKB byte string for geometry""" if geom is None or geom._geom is None: raise ValueError("Null geometry supports no operations") size = c_size_t() result = self._lgeos.GEOSWKBWriter_write( self._writer, geom._geom, pointer(size)) data = string_at(result, size.value) lgeos.GEOSFree(result) return data def write_hex(self, geom): """Returns WKB hex string for geometry""" if geom is None or geom._geom is None: raise ValueError("Null geometry supports no operations") size = c_size_t() result = self._lgeos.GEOSWKBWriter_writeHEX( self._writer, geom._geom, pointer(size)) data = string_at(result, size.value) lgeos.GEOSFree(result) if sys.version_info[0] >= 3: return data.decode('ascii') else: return data # Errcheck functions for ctypes def errcheck_wkb(result, func, argtuple): """Returns bytes from a C pointer""" if not result: return None size_ref = argtuple[-1] size = size_ref.contents retval = string_at(result, size.value)[:] lgeos.GEOSFree(result) return retval def errcheck_just_free(result, func, argtuple): """Returns string from a C pointer""" retval = string_at(result) lgeos.GEOSFree(result) if sys.version_info[0] >= 3: return retval.decode('ascii') else: return retval def errcheck_null_exception(result, func, argtuple): """Wraps errcheck_just_free Raises TopologicalError if result is NULL. """ if not result: raise TopologicalError( "The operation '{}' could not be performed." "Likely cause is invalidity of the geometry.".format( func.__name__)) return errcheck_just_free(result, func, argtuple) def errcheck_predicate(result, func, argtuple): """Result is 2 on exception, 1 on True, 0 on False""" if result == 2: raise PredicateError("Failed to evaluate %s" % repr(func)) return result class LGEOSBase(threading.local): """Proxy for GEOS C API This is a base class. Do not instantiate. """ methods = {} def __init__(self, dll): self._lgeos = dll self.geos_handle = None def __del__(self): """Cleanup GEOS related processes""" if self._lgeos is not None: self._lgeos.finishGEOS() self._lgeos = None self.geos_handle = None class LGEOS300(LGEOSBase): """Proxy for GEOS 3.0.0-CAPI-1.4.1 """ geos_version = (3, 0, 0) geos_capi_version = (1, 4, 0) def __init__(self, dll): super(LGEOS300, self).__init__(dll) self.geos_handle = self._lgeos.initGEOS(notice_h, error_h) keys = list(self._lgeos.__dict__.keys()) for key in keys: setattr(self, key, getattr(self._lgeos, key)) self.GEOSFree = self._lgeos.free # Deprecated self.GEOSGeomToWKB_buf.errcheck = errcheck_wkb self.GEOSGeomToWKT.errcheck = errcheck_just_free self.GEOSRelate.errcheck = errcheck_null_exception for pred in ( self.GEOSDisjoint, self.GEOSTouches, self.GEOSIntersects, self.GEOSCrosses, self.GEOSWithin, self.GEOSContains, self.GEOSOverlaps, self.GEOSEquals, self.GEOSEqualsExact, self.GEOSRelatePattern, self.GEOSisEmpty, self.GEOSisValid, self.GEOSisSimple, self.GEOSisRing, self.GEOSHasZ): pred.errcheck = errcheck_predicate self.methods['area'] = self.GEOSArea self.methods['boundary'] = self.GEOSBoundary self.methods['buffer'] = self.GEOSBuffer self.methods['centroid'] = self.GEOSGetCentroid self.methods['representative_point'] = self.GEOSPointOnSurface self.methods['convex_hull'] = self.GEOSConvexHull self.methods['distance'] = self.GEOSDistance self.methods['envelope'] = self.GEOSEnvelope self.methods['length'] = self.GEOSLength self.methods['has_z'] = self.GEOSHasZ self.methods['is_empty'] = self.GEOSisEmpty self.methods['is_ring'] = self.GEOSisRing self.methods['is_simple'] = self.GEOSisSimple self.methods['is_valid'] = self.GEOSisValid self.methods['disjoint'] = self.GEOSDisjoint self.methods['touches'] = self.GEOSTouches self.methods['intersects'] = self.GEOSIntersects self.methods['crosses'] = self.GEOSCrosses self.methods['within'] = self.GEOSWithin self.methods['contains'] = self.GEOSContains self.methods['overlaps'] = self.GEOSOverlaps self.methods['equals'] = self.GEOSEquals self.methods['equals_exact'] = self.GEOSEqualsExact self.methods['relate'] = self.GEOSRelate self.methods['difference'] = self.GEOSDifference self.methods['symmetric_difference'] = self.GEOSSymDifference self.methods['union'] = self.GEOSUnion self.methods['intersection'] = self.GEOSIntersection self.methods['relate_pattern'] = self.GEOSRelatePattern self.methods['simplify'] = self.GEOSSimplify self.methods['topology_preserve_simplify'] = \ self.GEOSTopologyPreserveSimplify class LGEOS310(LGEOSBase): """Proxy for GEOS 3.1.0-CAPI-1.5.0 """ geos_version = (3, 1, 0) geos_capi_version = (1, 5, 0) def __init__(self, dll): super(LGEOS310, self).__init__(dll) self.geos_handle = self._lgeos.initGEOS_r(notice_h, error_h) keys = list(self._lgeos.__dict__.keys()) for key in [x for x in keys if not x.endswith('_r')]: if key + '_r' in keys: reentr_func = getattr(self._lgeos, key + '_r') attr = partial(reentr_func, self.geos_handle) attr.__name__ = reentr_func.__name__ setattr(self, key, attr) else: setattr(self, key, getattr(self._lgeos, key)) if not hasattr(self, 'GEOSFree'): # GEOS < 3.1.1 self.GEOSFree = self._lgeos.free # Deprecated self.GEOSGeomToWKB_buf.func.errcheck = errcheck_wkb self.GEOSGeomToWKT.func.errcheck = errcheck_just_free self.GEOSRelate.func.errcheck = errcheck_null_exception for pred in ( self.GEOSDisjoint, self.GEOSTouches, self.GEOSIntersects, self.GEOSCrosses, self.GEOSWithin, self.GEOSContains, self.GEOSOverlaps, self.GEOSCovers, self.GEOSEquals, self.GEOSEqualsExact, self.GEOSPreparedDisjoint, self.GEOSPreparedTouches, self.GEOSPreparedCrosses, self.GEOSPreparedWithin, self.GEOSPreparedOverlaps, self.GEOSPreparedContains, self.GEOSPreparedContainsProperly, self.GEOSPreparedCovers, self.GEOSPreparedIntersects, self.GEOSRelatePattern, self.GEOSisEmpty, self.GEOSisValid, self.GEOSisSimple, self.GEOSisRing, self.GEOSHasZ): pred.func.errcheck = errcheck_predicate self.GEOSisValidReason.func.errcheck = errcheck_just_free self.methods['area'] = self.GEOSArea self.methods['boundary'] = self.GEOSBoundary self.methods['buffer'] = self.GEOSBuffer self.methods['centroid'] = self.GEOSGetCentroid self.methods['representative_point'] = self.GEOSPointOnSurface self.methods['convex_hull'] = self.GEOSConvexHull self.methods['distance'] = self.GEOSDistance self.methods['envelope'] = self.GEOSEnvelope self.methods['length'] = self.GEOSLength self.methods['has_z'] = self.GEOSHasZ self.methods['is_empty'] = self.GEOSisEmpty self.methods['is_ring'] = self.GEOSisRing self.methods['is_simple'] = self.GEOSisSimple self.methods['is_valid'] = self.GEOSisValid self.methods['disjoint'] = self.GEOSDisjoint self.methods['touches'] = self.GEOSTouches self.methods['intersects'] = self.GEOSIntersects self.methods['crosses'] = self.GEOSCrosses self.methods['within'] = self.GEOSWithin self.methods['contains'] = self.GEOSContains self.methods['overlaps'] = self.GEOSOverlaps self.methods['covers'] = self.GEOSCovers self.methods['equals'] = self.GEOSEquals self.methods['equals_exact'] = self.GEOSEqualsExact self.methods['relate'] = self.GEOSRelate self.methods['difference'] = self.GEOSDifference self.methods['symmetric_difference'] = self.GEOSSymDifference self.methods['union'] = self.GEOSUnion self.methods['intersection'] = self.GEOSIntersection self.methods['prepared_disjoint'] = self.GEOSPreparedDisjoint self.methods['prepared_touches'] = self.GEOSPreparedTouches self.methods['prepared_intersects'] = self.GEOSPreparedIntersects self.methods['prepared_crosses'] = self.GEOSPreparedCrosses self.methods['prepared_within'] = self.GEOSPreparedWithin self.methods['prepared_contains'] = self.GEOSPreparedContains self.methods['prepared_contains_properly'] = \ self.GEOSPreparedContainsProperly self.methods['prepared_overlaps'] = self.GEOSPreparedOverlaps self.methods['prepared_covers'] = self.GEOSPreparedCovers self.methods['relate_pattern'] = self.GEOSRelatePattern self.methods['simplify'] = self.GEOSSimplify self.methods['topology_preserve_simplify'] = \ self.GEOSTopologyPreserveSimplify self.methods['cascaded_union'] = self.GEOSUnionCascaded class LGEOS311(LGEOS310): """Proxy for GEOS 3.1.1-CAPI-1.6.0 """ geos_version = (3, 1, 1) geos_capi_version = (1, 6, 0) def __init__(self, dll): super(LGEOS311, self).__init__(dll) class LGEOS320(LGEOS311): """Proxy for GEOS 3.2.0-CAPI-1.6.0 """ geos_version = (3, 2, 0) geos_capi_version = (1, 6, 0) def __init__(self, dll): super(LGEOS320, self).__init__(dll) if geos_version >= (3, 2, 0): def parallel_offset(geom, distance, resolution=16, join_style=1, mitre_limit=5.0, side='right'): side = side == 'left' if distance < 0: distance = abs(distance) side = not side return self.GEOSSingleSidedBuffer( geom, distance, resolution, join_style, mitre_limit, side) self.methods['parallel_offset'] = parallel_offset self.methods['project'] = self.GEOSProject self.methods['project_normalized'] = self.GEOSProjectNormalized self.methods['interpolate'] = self.GEOSInterpolate self.methods['interpolate_normalized'] = \ self.GEOSInterpolateNormalized self.methods['buffer_with_style'] = self.GEOSBufferWithStyle self.methods['hausdorff_distance'] = self.GEOSHausdorffDistance class LGEOS330(LGEOS320): """Proxy for GEOS 3.3.0-CAPI-1.7.0 """ geos_version = (3, 3, 0) geos_capi_version = (1, 7, 0) def __init__(self, dll): super(LGEOS330, self).__init__(dll) # GEOS 3.3.8 from homebrew has, but doesn't advertise # GEOSPolygonize_full. We patch it in explicitly here. key = 'GEOSPolygonize_full' func = getattr(self._lgeos, key + '_r') attr = partial(func, self.geos_handle) attr.__name__ = func.__name__ setattr(self, key, attr) for pred in (self.GEOSisClosed,): pred.func.errcheck = errcheck_predicate def parallel_offset(geom, distance, resolution=16, join_style=1, mitre_limit=5.0, side='right'): if side == 'right': distance *= -1 return self.GEOSOffsetCurve( geom, distance, resolution, join_style, mitre_limit) self.methods['parallel_offset'] = parallel_offset self.methods['unary_union'] = self.GEOSUnaryUnion self.methods['is_closed'] = self.GEOSisClosed self.methods['cascaded_union'] = self.methods['unary_union'] self.methods['snap'] = self.GEOSSnap self.methods['shared_paths'] = self.GEOSSharedPaths self.methods['buffer_with_params'] = self.GEOSBufferWithParams class LGEOS340(LGEOS330): """Proxy for GEOS 3.4.0-CAPI-1.8.0 """ geos_version = (3, 4, 0) geos_capi_version = (1, 8, 0) def __init__(self, dll): super(LGEOS340, self).__init__(dll) self.methods['delaunay_triangulation'] = self.GEOSDelaunayTriangulation self.methods['nearest_points'] = self.GEOSNearestPoints class LGEOS350(LGEOS340): """Proxy for GEOS 3.5.0-CAPI-1.9.0 """ geos_version = (3, 5, 0) geos_capi_version = (1, 9, 0) def __init__(self, dll): super(LGEOS350, self).__init__(dll) self.methods['clip_by_rect'] = self.GEOSClipByRect class LGEOS360(LGEOS350): """Proxy for GEOS 3.6.0-CAPI-1.10.0 """ geos_version = (3, 6, 0) geos_capi_version = (1, 10, 0) def __init__(self, dll): super(LGEOS360, self).__init__(dll) self.methods['minimum_clearance'] = self.GEOSMinimumClearance if geos_version >= (3, 6, 0): L = LGEOS360 elif geos_version >= (3, 5, 0): L = LGEOS350 elif geos_version >= (3, 4, 0): L = LGEOS340 elif geos_version >= (3, 3, 0): L = LGEOS330 elif geos_version >= (3, 2, 0): L = LGEOS320 elif geos_version >= (3, 1, 1): L = LGEOS311 elif geos_version >= (3, 1, 0): L = LGEOS310 else: L = LGEOS300 lgeos = L(_lgeos) def cleanup(proxy): del proxy atexit.register(cleanup, lgeos)