1""" 2Proxies for libgeos, GEOS-specific exceptions, and utilities 3""" 4 5import atexit 6from ctypes import ( 7 CDLL, cdll, pointer, string_at, DEFAULT_MODE, c_void_p, c_size_t, c_char_p) 8from ctypes.util import find_library 9import glob 10import logging 11import os 12import re 13import sys 14import threading 15from functools import partial 16 17from .ctypes_declarations import prototype, EXCEPTION_HANDLER_FUNCTYPE 18from .errors import InvalidGeometryError, WKBReadingError, WKTReadingError, TopologicalError, PredicateError 19 20 21# Add message handler to this module's logger 22LOG = logging.getLogger(__name__) 23 24# Find and load the GEOS and C libraries 25# If this ever gets any longer, we'll break it into separate modules 26 27def load_dll(libname, fallbacks=None, mode=DEFAULT_MODE): 28 lib = find_library(libname) 29 dll = None 30 if lib is not None: 31 try: 32 LOG.debug("Trying `CDLL(%s)`", lib) 33 dll = CDLL(lib, mode=mode) 34 except OSError: 35 LOG.debug("Failed `CDLL(%s)`", lib) 36 pass 37 38 if not dll and fallbacks is not None: 39 for name in fallbacks: 40 try: 41 LOG.debug("Trying `CDLL(%s)`", name) 42 dll = CDLL(name, mode=mode) 43 except OSError: 44 # move on to the next fallback 45 LOG.debug("Failed `CDLL(%s)`", name) 46 pass 47 48 if dll: 49 LOG.debug("Library path: %r", lib or name) 50 LOG.debug("DLL: %r", dll) 51 return dll 52 else: 53 # No shared library was loaded. Raise OSError. 54 raise OSError( 55 "Could not find lib {} or load any of its variants {}.".format( 56 libname, fallbacks or [])) 57 58_lgeos = None 59def exists_conda_env(): 60 """Does this module exist in a conda environment?""" 61 return os.path.exists(os.path.join(sys.prefix, 'conda-meta')) 62 63 64if sys.platform.startswith('linux'): 65 # Test to see if we have a wheel repaired by auditwheel which contains its 66 # own libgeos_c. Note: auditwheel 3.1 changed the location of libs. 67 geos_whl_so = glob.glob( 68 os.path.abspath(os.path.join(os.path.dirname(__file__), ".libs/libgeos*.so*")) 69 ) or glob.glob( 70 os.path.abspath( 71 os.path.join( 72 os.path.dirname(__file__), "..", "Shapely.libs", "libgeos*.so*" 73 ) 74 ) 75 ) 76 77 if len(geos_whl_so) > 0: 78 # We have observed problems with CDLL of libgeos_c not automatically 79 # loading the sibling c++ library since the change made by auditwheel 80 # 3.1, so we explicitly load them both. 81 geos_whl_so = sorted(geos_whl_so) 82 CDLL(geos_whl_so[0]) 83 _lgeos = CDLL(geos_whl_so[-1]) 84 LOG.debug("Found GEOS DLL: %r, using it.", _lgeos) 85 86 elif hasattr(sys, 'frozen'): 87 geos_pyinstaller_so = glob.glob(os.path.join(sys.prefix, 'libgeos_c-*.so.*')) 88 if len(geos_pyinstaller_so) >= 1: 89 _lgeos = CDLL(geos_pyinstaller_so[0]) 90 LOG.debug("Found GEOS DLL: %r, using it.", _lgeos) 91 elif exists_conda_env(): 92 # conda package. 93 _lgeos = CDLL(os.path.join(sys.prefix, 'lib', 'libgeos_c.so')) 94 else: 95 alt_paths = [ 96 'libgeos_c.so.1', 97 'libgeos_c.so', 98 ] 99 _lgeos = load_dll('geos_c', fallbacks=alt_paths) 100 101 # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen 102 # manpage says, "If filename is NULL, then the returned handle is for the 103 # main program". This way we can let the linker do the work to figure out 104 # which libc Python is actually using. 105 free = CDLL(None).free 106 free.argtypes = [c_void_p] 107 free.restype = None 108 109elif sys.platform == 'darwin': 110 # Test to see if we have a delocated wheel with a GEOS dylib. 111 geos_whl_dylib = os.path.abspath(os.path.join(os.path.dirname( 112 __file__), '.dylibs/libgeos_c.1.dylib')) 113 114 if os.path.exists(geos_whl_dylib): 115 handle = CDLL(None) 116 if hasattr(handle, "initGEOS_r"): 117 LOG.debug("GEOS already loaded") 118 _lgeos = handle 119 else: 120 _lgeos = CDLL(geos_whl_dylib) 121 LOG.debug("Found GEOS DLL: %r, using it.", _lgeos) 122 123 elif exists_conda_env(): 124 # conda package. 125 _lgeos = CDLL(os.path.join(sys.prefix, 'lib', 'libgeos_c.dylib')) 126 else: 127 if hasattr(sys, 'frozen'): 128 try: 129 # .app file from py2app 130 alt_paths = [os.path.join( 131 os.environ['RESOURCEPATH'], '..', 'Frameworks', 132 'libgeos_c.dylib')] 133 except KeyError: 134 alt_paths = [ 135 # binary from pyinstaller 136 os.path.join(sys.executable, 'libgeos_c.dylib'), 137 # .app from cx_Freeze 138 os.path.join(os.path.dirname(sys.executable), 'libgeos_c.1.dylib')] 139 if hasattr(sys, '_MEIPASS'): 140 alt_paths.append( 141 os.path.join(sys._MEIPASS, 'libgeos_c.1.dylib')) 142 else: 143 alt_paths = [ 144 # The Framework build from Kyng Chaos 145 "/Library/Frameworks/GEOS.framework/Versions/Current/GEOS", 146 # macports 147 '/opt/local/lib/libgeos_c.dylib', 148 # homebrew Intel 149 '/usr/local/lib/libgeos_c.dylib', 150 # homebrew Apple Silicon 151 '/opt/homebrew/lib/libgeos_c.dylib', 152 ] 153 _lgeos = load_dll('geos_c', fallbacks=alt_paths) 154 155 free = CDLL(None).free 156 free.argtypes = [c_void_p] 157 free.restype = None 158 159elif sys.platform == 'win32': 160 _conda_dll_path = os.path.join(sys.prefix, 'Library', 'bin', 'geos_c.dll') 161 if exists_conda_env() and os.path.exists(_conda_dll_path): 162 # conda package. 163 _lgeos = CDLL(_conda_dll_path) 164 else: 165 try: 166 egg_dlls = os.path.abspath( 167 os.path.join(os.path.dirname(__file__), 'DLLs')) 168 if hasattr(sys, '_MEIPASS'): 169 wininst_dlls = sys._MEIPASS 170 elif hasattr(sys, "frozen"): 171 wininst_dlls = os.path.normpath( 172 os.path.abspath(sys.executable + '../../DLLS')) 173 else: 174 wininst_dlls = os.path.abspath(os.__file__ + "../../../DLLs") 175 original_path = os.environ['PATH'] 176 os.environ['PATH'] = "%s;%s;%s" % \ 177 (egg_dlls, wininst_dlls, original_path) 178 _lgeos = load_dll("geos_c.dll") 179 except (ImportError, WindowsError, OSError): 180 raise 181 182 def free(m): 183 try: 184 cdll.msvcrt.free(m) 185 except WindowsError: 186 # XXX: See http://trac.gispython.org/projects/PCL/ticket/149 187 pass 188 189elif sys.platform == 'sunos5': 190 _lgeos = load_dll('geos_c', fallbacks=['libgeos_c.so.1', 'libgeos_c.so']) 191 free.restype = None 192 free.argtypes = [c_void_p] 193 free.restype = None 194 195else: # other *nix systems 196 _lgeos = load_dll('geos_c', fallbacks=['libgeos_c.so.1', 'libgeos_c.so']) 197 free = CDLL(None).free 198 free.argtypes = [c_void_p] 199 free.restype = None 200 201 202def _geos_version(): 203 GEOSversion = _lgeos.GEOSversion 204 GEOSversion.restype = c_char_p 205 GEOSversion.argtypes = [] 206 geos_version_string = GEOSversion().decode('ascii') 207 res = re.findall(r'(\d+)\.(\d+)\.(\d+)', geos_version_string) 208 assert len(res) == 2, res 209 geos_version = tuple(int(x) for x in res[0]) 210 capi_version = tuple(int(x) for x in res[1]) 211 return geos_version_string, geos_version, capi_version 212 213geos_version_string, geos_version, geos_capi_version = _geos_version() 214 215 216# Record a baseline so that we know what additional functions are declared 217# in ctypes_declarations. 218start_set = set(_lgeos.__dict__) 219 220# Apply prototypes for the libgeos_c functions 221prototype(_lgeos, geos_version) 222 223# Automatically detect all function declarations, and declare their 224# re-entrant counterpart. 225end_set = set(_lgeos.__dict__) 226new_func_names = end_set - start_set 227 228for func_name in new_func_names: 229 new_func_name = "%s_r" % func_name 230 if hasattr(_lgeos, new_func_name): 231 new_func = getattr(_lgeos, new_func_name) 232 old_func = getattr(_lgeos, func_name) 233 new_func.restype = old_func.restype 234 if old_func.argtypes is None: 235 # Handle functions that didn't take an argument before, 236 # finishGEOS. 237 new_func.argtypes = [c_void_p] 238 else: 239 new_func.argtypes = [c_void_p] + list(old_func.argtypes) 240 if old_func.errcheck is not None: 241 new_func.errcheck = old_func.errcheck 242 243# Handle special case. 244_lgeos.initGEOS_r.restype = c_void_p 245_lgeos.initGEOS_r.argtypes = \ 246 [EXCEPTION_HANDLER_FUNCTYPE, EXCEPTION_HANDLER_FUNCTYPE] 247_lgeos.finishGEOS_r.argtypes = [c_void_p] 248 249 250def make_logging_callback(func): 251 """Error or notice handler callback producr 252 253 Wraps a logger method, func, as a GEOS callback. 254 """ 255 def callback(fmt, *fmt_args): 256 fmt = fmt.decode('ascii') 257 conversions = re.findall(r'%.', fmt) 258 args = [ 259 string_at(arg).decode('ascii') 260 for spec, arg in zip(conversions, fmt_args) 261 if spec == '%s' and arg is not None] 262 263 func(fmt, *args) 264 265 return callback 266 267error_handler = make_logging_callback(LOG.error) 268notice_handler = make_logging_callback(LOG.info) 269 270error_h = EXCEPTION_HANDLER_FUNCTYPE(error_handler) 271notice_h = EXCEPTION_HANDLER_FUNCTYPE(notice_handler) 272 273 274class WKTReader: 275 276 _lgeos = None 277 _reader = None 278 279 def __init__(self, lgeos): 280 """Create WKT Reader""" 281 self._lgeos = lgeos 282 self._reader = self._lgeos.GEOSWKTReader_create() 283 284 def __del__(self): 285 """Destroy WKT Reader""" 286 if self._lgeos is not None: 287 self._lgeos.GEOSWKTReader_destroy(self._reader) 288 self._reader = None 289 self._lgeos = None 290 291 def read(self, text): 292 """Returns geometry from WKT""" 293 if not isinstance(text, str): 294 raise TypeError("Only str is accepted.") 295 text = text.encode() 296 c_string = c_char_p(text) 297 geom = self._lgeos.GEOSWKTReader_read(self._reader, c_string) 298 if not geom: 299 raise WKTReadingError( 300 "Could not create geometry because of errors " 301 "while reading input.") 302 # avoid circular import dependency 303 from shapely.geometry.base import geom_factory 304 return geom_factory(geom) 305 306 307class WKTWriter: 308 309 _lgeos = None 310 _writer = None 311 312 # Establish default output settings 313 defaults = { 314 'trim': True, 315 'output_dimension': 3, 316 } 317 318 # GEOS' defaults for methods without "get" 319 _trim = False 320 _rounding_precision = -1 321 _old_3d = False 322 323 @property 324 def trim(self): 325 """Trimming of unnecessary decimals (default: True)""" 326 return getattr(self, '_trim') 327 328 @trim.setter 329 def trim(self, value): 330 self._trim = bool(value) 331 self._lgeos.GEOSWKTWriter_setTrim(self._writer, self._trim) 332 333 @property 334 def rounding_precision(self): 335 """Rounding precision when writing the WKT. 336 A precision of -1 (default) disables it.""" 337 return getattr(self, '_rounding_precision') 338 339 @rounding_precision.setter 340 def rounding_precision(self, value): 341 self._rounding_precision = int(value) 342 self._lgeos.GEOSWKTWriter_setRoundingPrecision( 343 self._writer, self._rounding_precision) 344 345 @property 346 def output_dimension(self): 347 """Output dimension, either 2 or 3 (default)""" 348 return self._lgeos.GEOSWKTWriter_getOutputDimension( 349 self._writer) 350 351 @output_dimension.setter 352 def output_dimension(self, value): 353 self._lgeos.GEOSWKTWriter_setOutputDimension( 354 self._writer, int(value)) 355 356 @property 357 def old_3d(self): 358 """Show older style for 3D WKT, without 'Z' (default: False)""" 359 return getattr(self, '_old_3d') 360 361 @old_3d.setter 362 def old_3d(self, value): 363 self._old_3d = bool(value) 364 self._lgeos.GEOSWKTWriter_setOld3D(self._writer, self._old_3d) 365 366 def __init__(self, lgeos, **settings): 367 """Create WKT Writer 368 369 Note: older formatting before GEOS 3.3.0 can be achieved by setting 370 the properties: 371 trim = False 372 output_dimension = 2 373 """ 374 self._lgeos = lgeos 375 self._writer = self._lgeos.GEOSWKTWriter_create() 376 377 applied_settings = self.defaults.copy() 378 applied_settings.update(settings) 379 for name in applied_settings: 380 setattr(self, name, applied_settings[name]) 381 382 def __setattr__(self, name, value): 383 """Limit setting attributes""" 384 if hasattr(self, name): 385 object.__setattr__(self, name, value) 386 else: 387 raise AttributeError('%r object has no attribute %r' % 388 (self.__class__.__name__, name)) 389 390 def __del__(self): 391 """Destroy WKT Writer""" 392 if self._lgeos is not None: 393 self._lgeos.GEOSWKTWriter_destroy(self._writer) 394 self._writer = None 395 self._lgeos = None 396 397 def write(self, geom): 398 """Returns WKT string for geometry""" 399 if geom is None or geom._geom is None: 400 raise InvalidGeometryError("Null geometry supports no operations") 401 result = self._lgeos.GEOSWKTWriter_write(self._writer, geom._geom) 402 text = string_at(result) 403 lgeos.GEOSFree(result) 404 return text.decode('ascii') 405 406 407class WKBReader: 408 409 _lgeos = None 410 _reader = None 411 412 def __init__(self, lgeos): 413 """Create WKB Reader""" 414 self._lgeos = lgeos 415 self._reader = self._lgeos.GEOSWKBReader_create() 416 417 def __del__(self): 418 """Destroy WKB Reader""" 419 if self._lgeos is not None: 420 self._lgeos.GEOSWKBReader_destroy(self._reader) 421 self._reader = None 422 self._lgeos = None 423 424 def read(self, data): 425 """Returns geometry from WKB""" 426 geom = self._lgeos.GEOSWKBReader_read( 427 self._reader, c_char_p(data), c_size_t(len(data))) 428 if not geom: 429 raise WKBReadingError( 430 "Could not create geometry because of errors " 431 "while reading input.") 432 # avoid circular import dependency 433 from shapely import geometry 434 return geometry.base.geom_factory(geom) 435 436 def read_hex(self, data): 437 """Returns geometry from WKB hex""" 438 data = data.encode('ascii') 439 geom = self._lgeos.GEOSWKBReader_readHEX( 440 self._reader, c_char_p(data), c_size_t(len(data))) 441 if not geom: 442 raise WKBReadingError( 443 "Could not create geometry because of errors " 444 "while reading input.") 445 # avoid circular import dependency 446 from shapely import geometry 447 return geometry.base.geom_factory(geom) 448 449 450class WKBWriter: 451 452 _lgeos = None 453 _writer = None 454 455 # EndianType enum in ByteOrderValues.h 456 _ENDIAN_BIG = 0 457 _ENDIAN_LITTLE = 1 458 459 # Establish default output setting 460 defaults = {'output_dimension': 3} 461 462 @property 463 def output_dimension(self): 464 """Output dimension, either 2 or 3 (default)""" 465 return self._lgeos.GEOSWKBWriter_getOutputDimension(self._writer) 466 467 @output_dimension.setter 468 def output_dimension(self, value): 469 self._lgeos.GEOSWKBWriter_setOutputDimension( 470 self._writer, int(value)) 471 472 @property 473 def big_endian(self): 474 """Byte order is big endian, True (default) or False""" 475 return (self._lgeos.GEOSWKBWriter_getByteOrder(self._writer) == 476 self._ENDIAN_BIG) 477 478 @big_endian.setter 479 def big_endian(self, value): 480 self._lgeos.GEOSWKBWriter_setByteOrder( 481 self._writer, self._ENDIAN_BIG if value else self._ENDIAN_LITTLE) 482 483 @property 484 def include_srid(self): 485 """Include SRID, True or False (default)""" 486 return bool(self._lgeos.GEOSWKBWriter_getIncludeSRID(self._writer)) 487 488 @include_srid.setter 489 def include_srid(self, value): 490 self._lgeos.GEOSWKBWriter_setIncludeSRID(self._writer, bool(value)) 491 492 def __init__(self, lgeos, **settings): 493 """Create WKB Writer""" 494 self._lgeos = lgeos 495 self._writer = self._lgeos.GEOSWKBWriter_create() 496 497 applied_settings = self.defaults.copy() 498 applied_settings.update(settings) 499 for name in applied_settings: 500 setattr(self, name, applied_settings[name]) 501 502 def __setattr__(self, name, value): 503 """Limit setting attributes""" 504 if hasattr(self, name): 505 object.__setattr__(self, name, value) 506 else: 507 raise AttributeError('%r object has no attribute %r' % 508 (self.__class__.__name__, name)) 509 510 def __del__(self): 511 """Destroy WKB Writer""" 512 if self._lgeos is not None: 513 self._lgeos.GEOSWKBWriter_destroy(self._writer) 514 self._writer = None 515 self._lgeos = None 516 517 def write(self, geom): 518 """Returns WKB byte string for geometry""" 519 if geom is None or geom._geom is None: 520 raise InvalidGeometryError("Null geometry supports no operations") 521 size = c_size_t() 522 result = self._lgeos.GEOSWKBWriter_write( 523 self._writer, geom._geom, pointer(size)) 524 data = string_at(result, size.value) 525 lgeos.GEOSFree(result) 526 return data 527 528 def write_hex(self, geom): 529 """Returns WKB hex string for geometry""" 530 if geom is None or geom._geom is None: 531 raise InvalidGeometryError("Null geometry supports no operations") 532 size = c_size_t() 533 result = self._lgeos.GEOSWKBWriter_writeHEX( 534 self._writer, geom._geom, pointer(size)) 535 data = string_at(result, size.value) 536 lgeos.GEOSFree(result) 537 return data.decode('ascii') 538 539 540# Errcheck functions for ctypes 541 542def errcheck_wkb(result, func, argtuple): 543 """Returns bytes from a C pointer""" 544 if not result: 545 return None 546 size_ref = argtuple[-1] 547 size = size_ref.contents 548 retval = string_at(result, size.value)[:] 549 lgeos.GEOSFree(result) 550 return retval 551 552 553def errcheck_just_free(result, func, argtuple): 554 """Returns string from a C pointer""" 555 retval = string_at(result) 556 lgeos.GEOSFree(result) 557 return retval.decode('ascii') 558 559 560def errcheck_null_exception(result, func, argtuple): 561 """Wraps errcheck_just_free 562 563 Raises TopologicalError if result is NULL. 564 """ 565 if not result: 566 raise TopologicalError( 567 "The operation '{}' could not be performed." 568 "Likely cause is invalidity of the geometry.".format( 569 func.__name__)) 570 return errcheck_just_free(result, func, argtuple) 571 572 573def errcheck_predicate(result, func, argtuple): 574 """Result is 2 on exception, 1 on True, 0 on False""" 575 if result == 2: 576 raise PredicateError("Failed to evaluate %s" % repr(func)) 577 return result 578 579 580class LGEOSBase(threading.local): 581 """Proxy for GEOS C API 582 583 This is a base class. Do not instantiate. 584 """ 585 methods = {} 586 587 def __init__(self, dll): 588 self._lgeos = dll 589 self.geos_handle = None 590 591 def __del__(self): 592 """Cleanup GEOS related processes""" 593 if self._lgeos is not None: 594 self._lgeos.finishGEOS() 595 self._lgeos = None 596 self.geos_handle = None 597 598 599class LGEOS330(LGEOSBase): 600 """Proxy for GEOS 3.3.0-CAPI-1.7.0 601 """ 602 geos_version = (3, 3, 0) 603 geos_capi_version = (1, 7, 0) 604 605 def __init__(self, dll): 606 super().__init__(dll) 607 self.geos_handle = self._lgeos.initGEOS_r(notice_h, error_h) 608 keys = list(self._lgeos.__dict__.keys()) 609 for key in [x for x in keys if not x.endswith('_r')]: 610 if key + '_r' in keys: 611 reentr_func = getattr(self._lgeos, key + '_r') 612 attr = partial(reentr_func, self.geos_handle) 613 attr.__name__ = reentr_func.__name__ 614 setattr(self, key, attr) 615 else: 616 setattr(self, key, getattr(self._lgeos, key)) 617 618 # GEOS 3.3.8 from homebrew has, but doesn't advertise 619 # GEOSPolygonize_full. We patch it in explicitly here. 620 key = 'GEOSPolygonize_full' 621 func = getattr(self._lgeos, key + '_r') 622 attr = partial(func, self.geos_handle) 623 attr.__name__ = func.__name__ 624 setattr(self, key, attr) 625 626 # Deprecated 627 self.GEOSGeomToWKB_buf.func.errcheck = errcheck_wkb 628 self.GEOSGeomToWKT.func.errcheck = errcheck_just_free 629 self.GEOSRelate.func.errcheck = errcheck_null_exception 630 for pred in ( 631 self.GEOSDisjoint, 632 self.GEOSTouches, 633 self.GEOSIntersects, 634 self.GEOSCrosses, 635 self.GEOSWithin, 636 self.GEOSContains, 637 self.GEOSOverlaps, 638 self.GEOSCovers, 639 self.GEOSEquals, 640 self.GEOSEqualsExact, 641 self.GEOSPreparedDisjoint, 642 self.GEOSPreparedTouches, 643 self.GEOSPreparedCrosses, 644 self.GEOSPreparedWithin, 645 self.GEOSPreparedOverlaps, 646 self.GEOSPreparedContains, 647 self.GEOSPreparedContainsProperly, 648 self.GEOSPreparedCovers, 649 self.GEOSPreparedIntersects, 650 self.GEOSRelatePattern, 651 self.GEOSisEmpty, 652 self.GEOSisValid, 653 self.GEOSisSimple, 654 self.GEOSisRing, 655 self.GEOSHasZ, 656 self.GEOSisClosed, 657 self.GEOSCoveredBy): 658 pred.func.errcheck = errcheck_predicate 659 660 self.GEOSisValidReason.func.errcheck = errcheck_just_free 661 662 self.methods['area'] = self.GEOSArea 663 self.methods['boundary'] = self.GEOSBoundary 664 self.methods['buffer'] = self.GEOSBuffer 665 self.methods['centroid'] = self.GEOSGetCentroid 666 self.methods['representative_point'] = self.GEOSPointOnSurface 667 self.methods['convex_hull'] = self.GEOSConvexHull 668 self.methods['distance'] = self.GEOSDistance 669 self.methods['envelope'] = self.GEOSEnvelope 670 self.methods['length'] = self.GEOSLength 671 self.methods['has_z'] = self.GEOSHasZ 672 self.methods['is_empty'] = self.GEOSisEmpty 673 self.methods['is_ring'] = self.GEOSisRing 674 self.methods['is_simple'] = self.GEOSisSimple 675 self.methods['is_valid'] = self.GEOSisValid 676 self.methods['disjoint'] = self.GEOSDisjoint 677 self.methods['touches'] = self.GEOSTouches 678 self.methods['intersects'] = self.GEOSIntersects 679 self.methods['crosses'] = self.GEOSCrosses 680 self.methods['within'] = self.GEOSWithin 681 self.methods['contains'] = self.GEOSContains 682 self.methods['overlaps'] = self.GEOSOverlaps 683 self.methods['covers'] = self.GEOSCovers 684 self.methods['equals'] = self.GEOSEquals 685 self.methods['equals_exact'] = self.GEOSEqualsExact 686 self.methods['relate'] = self.GEOSRelate 687 self.methods['difference'] = self.GEOSDifference 688 self.methods['symmetric_difference'] = self.GEOSSymDifference 689 self.methods['union'] = self.GEOSUnion 690 self.methods['intersection'] = self.GEOSIntersection 691 self.methods['prepared_disjoint'] = self.GEOSPreparedDisjoint 692 self.methods['prepared_touches'] = self.GEOSPreparedTouches 693 self.methods['prepared_intersects'] = self.GEOSPreparedIntersects 694 self.methods['prepared_crosses'] = self.GEOSPreparedCrosses 695 self.methods['prepared_within'] = self.GEOSPreparedWithin 696 self.methods['prepared_contains'] = self.GEOSPreparedContains 697 self.methods['prepared_contains_properly'] = \ 698 self.GEOSPreparedContainsProperly 699 self.methods['prepared_overlaps'] = self.GEOSPreparedOverlaps 700 self.methods['prepared_covers'] = self.GEOSPreparedCovers 701 self.methods['relate_pattern'] = self.GEOSRelatePattern 702 self.methods['simplify'] = self.GEOSSimplify 703 self.methods['topology_preserve_simplify'] = \ 704 self.GEOSTopologyPreserveSimplify 705 self.methods['normalize'] = self.GEOSNormalize 706 self.methods['cascaded_union'] = self.GEOSUnionCascaded 707 708 def parallel_offset(geom, distance, resolution=16, join_style=1, 709 mitre_limit=5.0, side='right'): 710 if side == 'right': 711 distance *= -1 712 return self.GEOSOffsetCurve( 713 geom, distance, resolution, join_style, mitre_limit) 714 715 self.methods['parallel_offset'] = parallel_offset 716 self.methods['project'] = self.GEOSProject 717 self.methods['project_normalized'] = self.GEOSProjectNormalized 718 self.methods['interpolate'] = self.GEOSInterpolate 719 self.methods['interpolate_normalized'] = \ 720 self.GEOSInterpolateNormalized 721 self.methods['buffer_with_style'] = self.GEOSBufferWithStyle 722 self.methods['hausdorff_distance'] = self.GEOSHausdorffDistance 723 self.methods['unary_union'] = self.GEOSUnaryUnion 724 self.methods['cascaded_union'] = self.methods['unary_union'] 725 self.methods['is_closed'] = self.GEOSisClosed 726 self.methods['snap'] = self.GEOSSnap 727 self.methods['shared_paths'] = self.GEOSSharedPaths 728 self.methods['buffer_with_params'] = self.GEOSBufferWithParams 729 self.methods['covered_by'] = self.GEOSCoveredBy 730 731 732class LGEOS340(LGEOS330): 733 """Proxy for GEOS 3.4.0-CAPI-1.8.0 734 """ 735 geos_version = (3, 4, 0) 736 geos_capi_version = (1, 8, 0) 737 738 def __init__(self, dll): 739 super().__init__(dll) 740 self.methods['delaunay_triangulation'] = self.GEOSDelaunayTriangulation 741 self.methods['nearest_points'] = self.GEOSNearestPoints 742 743 744class LGEOS350(LGEOS340): 745 """Proxy for GEOS 3.5.0-CAPI-1.9.0 746 """ 747 geos_version = (3, 5, 0) 748 geos_capi_version = (1, 9, 0) 749 750 def __init__(self, dll): 751 super().__init__(dll) 752 self.methods['clip_by_rect'] = self.GEOSClipByRect 753 self.methods['voronoi_diagram'] = self.GEOSVoronoiDiagram 754 755 756class LGEOS360(LGEOS350): 757 """Proxy for GEOS 3.6.0-CAPI-1.10.0 758 """ 759 geos_version = (3, 6, 0) 760 geos_capi_version = (1, 10, 0) 761 762 def __init__(self, dll): 763 super().__init__(dll) 764 self.methods['minimum_clearance'] = self.GEOSMinimumClearance 765 766 767class LGEOS380(LGEOS360): 768 """Proxy for GEOS 3.8.0-CAPI-1.13.0""" 769 770 geos_version = (3, 8, 0) 771 geos_capi_version = (1, 13, 0) 772 773 def __init__(self, dll): 774 super().__init__(dll) 775 self.methods['make_valid'] = self.GEOSMakeValid 776 777 778if geos_version >= (3, 8, 0): 779 L = LGEOS380 780elif geos_version >= (3, 6, 0): 781 L = LGEOS360 782elif geos_version >= (3, 5, 0): 783 L = LGEOS350 784elif geos_version >= (3, 4, 0): 785 L = LGEOS340 786elif geos_version >= (3, 3, 0): 787 L = LGEOS330 788else: 789 raise ValueError('unexpected geos_version: ' + str(geos_version)) 790 791lgeos = L(_lgeos) 792 793 794def cleanup(proxy): 795 del proxy 796 797atexit.register(cleanup, lgeos) 798