pax_global_header00006660000000000000000000000064126061051650014514gustar00rootroot0000000000000052 comment=f8f3f2998313a751d2ce376bb6bbbe010b12e2d1 Shapely-1.5.13/000077500000000000000000000000001260610516500132105ustar00rootroot00000000000000Shapely-1.5.13/.gitignore000066400000000000000000000002361260610516500152010ustar00rootroot00000000000000*.pyc *.pyo *.c *.so VERSION.txt Shapely.egg-info/ build/ dist/ docs/_build/ docs/shapely.*.txt docs/shapely.txt docs/modules.txt .cache/ .idea/ *.pyd *.pdb Shapely-1.5.13/.travis.yml000066400000000000000000000011041260610516500153150ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.3" - "3.4" env: - "TRAVIS_SPEEDUP_OPTS=--with-speedups" - "TRAVIS_SPEEDUP_OPTS=" before_install: - sudo add-apt-repository -y ppa:ubuntugis/ppa - sudo apt-get update -qq - sudo apt-get install -qq libgeos-dev python-numpy - if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install unittest2; fi - pip install --install-option="--no-cython-compile" cython - pip install -r requirements-dev.txt install: - pip install -e . script: "py.test tests ${TRAVIS_SPEEDUP_OPTS}" notifications: email: false Shapely-1.5.13/CHANGES.txt000066400000000000000000000270011260610516500150210ustar00rootroot00000000000000Changes ======= 1.5.13 (2015-10-09) ------------------- - Restore setup and runtime discovery and loading of GEOS shared library to state at version 1.5.9 (#326). - On OS X we try to reuse any GEOS shared library that may have been loaded via import of Fiona or Rasterio in order to avoid a bug involving the GEOS AbstractSTRtree (#324, #327). 1.5.12 (2015-08-27) ------------------- - Remove configuration of root logger from libgeos.py (#312). - Skip test_fallbacks on Windows (#308). - Call setlocale(locale.LC_ALL, "") instead of resetlocale() on Windows when tearing down the locale test (#308). - Fix for Sphinx warnings (#309). - Addition of .cache, .idea, .pyd, .pdb to .gitignore (#310). 1.5.11 (2015-08-23) ------------------- - Remove packaging module requirement added in 1.5.10 (#305). Distutils can't parse versions using 'rc', but if we stick to 'a' and 'b' we will be fine. 1.5.10 (2015-08-22) ------------------- - Monkey patch affinity module by absolute reference (#299). - Raise TopologicalError in relate() instead of crashing (#294, #295, #303). 1.5.9 (2015-05-27) ------------------ - Fix for 64 bit speedups compatibility (#274). 1.5.8 (2015-04-29) ------------------ - Setup file encoding bug fix (#254). - Support for pyinstaller (#261). - Major prepared geometry operation fix for Windows (#268, #269). - Major fix for OS X binary wheel (#262). 1.5.7 (2015-03-16) ------------------ - Test and fix buggy error and notice handlers (#249). 1.5.6 (2015-02-02) ------------------ - Fix setup regression (#232, #234). - SVG representation improvements (#233, #237). 1.5.5 (2015-01-20) ------------------ - MANIFEST changes to restore _geox.pxi (#231). 1.5.4 (2015-01-19) ------------------ - Fixed OS X binary wheel library load path (#224). 1.5.3 (2015-01-12) ------------------ - Fixed ownership and potential memory leak in polygonize (#223). - Wider release of binary wheels for OS X. 1.5.2 (2015-01-04) ------------------ - Fail installation if GEOS dependency is not met, preventing update breakage (#218, #219). 1.5.1 (2014-12-04) ------------------ - Restore geometry hashing (#209). 1.5.0 (2014-12-02) ------------------ - Affine transformation speedups (#197). - New `==` rich comparison (#195). - Geometry collection constructor (#200). - ops.snap() backed by GEOSSnap (#201). - Clearer exceptions in cases of topological invalidity (#203). 1.4.4 (2014-11-02) ------------------ - Proper conversion of numpy float32 vals to coords (#186). 1.4.3 (2014-10-01) ------------------ - Fix for endianness bug in WKB writer (#174). 1.4.2 (2014-09-29) ------------------ - Fix bungled 1.4.1 release (#176). 1.4.1 (2014-09-23) ------------------ - Return of support for GEOS 3.2 (#176, #178). 1.4.0 (2014-09-08) ------------------ - SVG representations for IPython's inline image protocol. - Efficient and fast vectorized contains(). - Change mitre_limit default to 5.0; raise ValueError with 0.0 (#139). - Allow mix of tuples and Points in sped-up LineString ctor (#152). - New STRtree class (#73). - Add ops.nearest_points() (#147). - Faster creation of geometric objects from others (cloning) (#165). - Removal of tests from package. 1.3.3 (2014-07-23) ------------------ - Allow single-part geometries as argument to ops.cacaded_union() (#135). - Support affine transformations of LinearRings (#112). 1.3.2 (2014-05-13) ------------------ - Let LineString() take a sequence of Points (#130). 1.3.1 (2014-04-22) ------------------ - More reliable proxy cleanup on exit (#106). - More robust DLL loading on all platforms (#114). 1.3.0 (2013-12-31) ------------------ - Include support for Python 3.2 and 3.3 (#56), minimum version is now 2.6. - Switch to GEOS WKT/WKB Reader/Writer API, with defaults changed to enable 3D output dimensions, and to 'trim' WKT output for GEOS >=3.3.0. - Use GEOS version instead of GEOS C API version to determine library capabilities (#65). 1.2.19 (2013-12-30) ------------------- - Add buffering style options (#55). 1.2.18 (2013-07-23) -------------------- - Add shapely.ops.transform. - Permit empty sequences in collection constructors (#49, #50). - Individual polygons in MultiPolygon.__geo_interface__ are changed to tuples to match Polygon.__geo_interface__ (#51). - Add shapely.ops.polygonize_full (#57). 1.2.17 (2013-01-27) ------------------- - Avoid circular import between wkt/wkb and geometry.base by moving calls to GEOS serializers to the latter module. - Set _ndim when unpickling (issue #6). - Don't install DLLs to Python's DLL directory (#37). - Add affinity module of affine transformation (#31). - Fix NameError that blocked installation with PyPy (#40, #41). 1.2.16 (2012-09-18) ------------------- - Add ops.unary_union function. - Alias ops.cascaded_union to ops.unary_union when GEOS CAPI >= (1,7,0). - Add geos_version_string attribute to shapely.geos. - Ensure parent is set when child geometry is accessed. - Generate _speedups.c using Cython when building from repo when missing, stale, or the build target is "sdist". - The is_simple predicate of invalid, self-intersecting linear rings now returns ``False``. - Remove VERSION.txt from repo, it's now written by the distutils setup script with value of shapely.__version__. 1.2.15 (2012-06-27) ------------------- - Eliminate numerical sensitivity in a method chaining test (Debian bug #663210). - Account for cascaded union of random buffered test points being a polygon or multipolygon (Debian bug #666655). - Use Cython to build speedups if it is installed. - Avoid stumbling over SVN revision numbers in GEOS C API version strings. 1.2.14 (2012-01-23) ------------------- - A geometry's coords property is now sliceable, yielding a list of coordinate values. - Homogeneous collections are now sliceable, yielding a new collection of the same type. 1.2.13 (2011-09-16) ------------------- - Fixed errors in speedups on 32bit systems when GEOS references memory above 2GB. - Add shapely.__version__ attribute. - Update the manual. 1.2.12 (2011-08-15) ------------------- - Build Windows distributions with VC7 or VC9 as appropriate. - More verbose report on failure to speed up. - Fix for prepared geometries broken in 1.2.11. - DO NOT INSTALL 1.2.11 1.2.11 (2011-08-04) ------------------- - Ignore AttributeError during exit. - PyPy 1.5 support. - Prevent operation on prepared geometry crasher (#12). - Optional Cython speedups for Windows. - Linux 3 platform support. 1.2.10 (2011-05-09) ------------------- - Add optional Cython speedups. - Add is_cww predicate to LinearRing. - Add function that forces orientation of Polygons. - Disable build of speedups on Windows pending packaging work. 1.2.9 (2011-03-31) ------------------ - Remove extra glob import. - Move examples to shapely.examples. - Add box() constructor for rectangular polygons. - Fix extraneous imports. 1.2.8 (2011-12-03) ------------------ - New parallel_offset method (#6). - Support for Python 2.4. 1.2.7 (2010-11-05) ------------------ - Support for Windows eggs. 1.2.6 (2010-10-21) ------------------ - The geoms property of an empty collection yields [] instead of a ValueError (#3). - The coords and geometry type sproperties have the same behavior as above. - Ensure that z values carry through into products of operations (#4). 1.2.5 (2010-09-19) ------------------ - Stop distributing docs/_build. - Include library fallbacks in test_dlls.py for linux platform. 1.2.4 (2010-09-09) ------------------ - Raise AttributeError when there's no backend support for a method. - Raise OSError if libgeos_c.so (or variants) can't be found and loaded. - Add geos_c DLL loading support for linux platforms where find_library doesn't work. 1.2.3 (2010-08-17) ------------------ - Add mapping function. - Fix problem with GEOSisValidReason symbol for GEOS < 3.1. 1.2.2 (2010-07-23) ------------------ - Add representative_point method. 1.2.1 (2010-06-23) ------------------ - Fixed bounds of singular polygons. - Added shapely.validation.explain_validity function (#226). 1.2 (2010-05-27) ---------------- - Final release. 1.2rc2 (2010-05-26) ------------------- - Add examples and tests to MANIFEST.in. - Release candidate 2. 1.2rc1 (2010-05-25) ------------------- - Release candidate. 1.2b7 (2010-04-22) ------------------ - Memory leak associated with new empty geometry state fixed. 1.2b6 (2010-04-13) ------------------ - Broken GeometryCollection fixed. 1.2b5 (2010-04-09) ------------------ - Objects can be constructed from others of the same type, thereby making copies. Collections can be constructed from sequences of objects, also making copies. - Collections are now iterators over their component objects. - New code for manual figures, using the descartes package. 1.2b4 (2010-03-19) ------------------ - Adds support for the "sunos5" platform. 1.2b3 (2010-02-28) ------------------ - Only provide simplification implementations for GEOS C API >= 1.5. 1.2b2 (2010-02-19) ------------------ - Fix cascaded_union bug introduced in 1.2b1 (#212). 1.2b1 (2010-02-18) ------------------ - Update the README. Remove cruft from setup.py. Add some version 1.2 metadata regarding required Python version (>=2.5,<3) and external dependency (libgeos_c >= 3.1). 1.2a6 (2010-02-09) ------------------ - Add accessor for separate arrays of X and Y values (#210). TODO: fill gap here 1.2a1 (2010-01-20) ------------------ - Proper prototyping of WKB writer, and avoidance of errors on 64-bit systems (#191). - Prototype libgeos_c functions in a way that lets py2exe apps import shapely (#189). 1.2 Branched (2009-09-19) 1.0.12 (2009-04-09) ------------------- - Fix for references held by topology and predicate descriptors. 1.0.11 (2008-11-20) ------------------- - Work around bug in GEOS 2.2.3, GEOSCoordSeq_getOrdinate not exported properly (#178). 1.0.10 (2008-11-17) ------------------- - Fixed compatibility with GEOS 2.2.3 that was broken in 1.0.8 release (#176). 1.0.9 (2008-11-16) ------------------ - Find and load MacPorts libgeos. 1.0.8 (2008-11-01) ------------------ - Fill out GEOS function result and argument types to prevent faults on a 64-bit arch. 1.0.7 (2008-08-22) ------------------ - Polygon rings now have the same dimensions as parent (#168). - Eliminated reference cycles in polygons (#169). 1.0.6 (2008-07-10) ------------------ - Fixed adaptation of multi polygon data. - Raise exceptions earlier from binary predicates. - Beginning distributing new windows DLLs (#166). 1.0.5 (2008-05-20) ------------------ - Added access to GEOS polygonizer function. - Raise exception when insufficient coordinate tuples are passed to LinearRing constructor (#164). 1.0.4 (2008-05-01) ------------------ - Disentangle Python and topological equality (#163). - Add shape(), a factory that copies coordinates from a geo interface provider. To be used instead of asShape() unless you really need to store coordinates outside shapely for efficient use in other code. - Cache GEOS geometries in adapters (#163). 1.0.3 (2008-04-09) ------------------ - Do not release GIL when calling GEOS functions (#158). - Prevent faults when chaining multiple GEOS operators (#159). 1.0.2 (2008-02-26) ------------------ - Fix loss of dimensionality in polygon rings (#155). 1.0.1 (2008-02-08) ------------------ - Allow chaining expressions involving coordinate sequences and geometry parts (#151). - Protect against abnormal use of coordinate accessors (#152). - Coordinate sequences now implement the numpy array protocol (#153). 1.0 (2008-01-18) ---------------- - Final release. 1.0 RC2 (2008-01-16) -------------------- - Added temporary solution for #149. 1.0 RC1 (2008-01-14) -------------------- - First release candidate Shapely-1.5.13/CREDITS.txt000066400000000000000000000032771260610516500150570ustar00rootroot00000000000000Credits ======= Shapely is written by: * Sean Gillies * Aron Bierbaum * Kai Lautaportti * Oliver Tonnhofer Patches contributed by: * Allan Adair (https://github.com/allanadair) * Joshua Arnott (https://github.com/snorfalorpagus) * David Baumgold (https://github.com/singingwolfboy) * Howard Butler * Gabi Davar (https://github.com/mindw) * Phil Elson (https://github.com/pelson) * Johan Euphrosine (https://github.com/proppy) * Bertrand Gervais (https://github.com/BertrandGervais) * Marc Jansen (https://github.com/marcjansen) * Kelsey Jordahl (https://github.com/kjordahl) * Frédéric Junod * Thomas Kluyver (https://github.com/takluyver) * William Kyngesburye (https://github.com/kyngchaos) * Eric Lemoine * Naveen Michaud-Agrawal (https://github.com/nmichaud) * om-henners (https://github.com/om-henners) * psagers https://github.com/psagers * Jeethu Rao (https://github.com/jeethu) * Benjamin Root (https://github.com/WeatherGod) * Jason Sanford (https://github.com/JasonSanford) * Johannes Schönberger (https://github.com/ahojnnes) * Jonathan Tartley * Kristian Thy * Mike Toews (https://github.com/mwtoews) * Maarten Vermeyen (https://github.com/maarten-vermeyen) * Jacob Wasserman (https://github.com/jwass) * Brandon Wood (https://github.com/woodb) See also: https://github.com/Toblerity/Shapely/graphs/contributors. Additional help from: * Justin Bronn (GeoDjango) for ctypes inspiration * Martin Davis (JTS) * Jaakko Salli for the Windows distributions * Sandro Santilli, Mateusz Loskot, Paul Ramsey, et al (GEOS Project) Major portions of this work were supported by a grant (for Pleiades_) from the U.S. National Endowment for the Humanities (http://www.neh.gov). .. _Pleiades: http://pleiades.stoa.org Shapely-1.5.13/DLLs_AMD64_VC9/000077500000000000000000000000001260610516500153225ustar00rootroot00000000000000Shapely-1.5.13/DLLs_AMD64_VC9/README.txt000066400000000000000000000000131260610516500170120ustar00rootroot00000000000000AMD64 DLLs Shapely-1.5.13/DLLs_x86_VC7/000077500000000000000000000000001260610516500151725ustar00rootroot00000000000000Shapely-1.5.13/DLLs_x86_VC7/README.txt000066400000000000000000000000111260610516500166600ustar00rootroot00000000000000x86 DLLs Shapely-1.5.13/DLLs_x86_VC9/000077500000000000000000000000001260610516500151745ustar00rootroot00000000000000Shapely-1.5.13/DLLs_x86_VC9/README.txt000066400000000000000000000000111260610516500166620ustar00rootroot00000000000000x86 DLLs Shapely-1.5.13/GEOS-C-API.txt000066400000000000000000000060071260610516500153000ustar00rootroot00000000000000Ctypes declarations for functions present in GEOS C API 1.4, but not present in 1.3, are listed below: lgeos.GEOS_getWKBOutputDims.restype = ctypes.c_int lgeos.GEOS_getWKBByteOrder.restype = ctypes.c_int lgeos.GEOS_setWKBByteOrder.restype = ctypes.c_int lgeos.GEOS_setWKBByteOrder.argtypes = [ctypes.c_int] lgeos.GEOSGeomFromHEX_buf.restype = ctypes.c_void_p lgeos.GEOSGeomFromHEX_buf.argtypes = [ctypes.c_void_p, ctypes.c_size_t] lgeos.GEOSSimplify.restype = ctypes.c_void_p lgeos.GEOSSimplify.argtypes = [ctypes.c_void_p, ctypes.c_double] lgeos.GEOSTopologyPreserveSimplify.restype = ctypes.c_void_p lgeos.GEOSTopologyPreserveSimplify.argtypes = [ctypes.c_void_p, ctypes.c_double] lgeos.GEOSEqualsExact.restype = ctypes.c_int lgeos.GEOSEqualsExact.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_double] lgeos.GEOSNormalize.restype = ctypes.c_int lgeos.GEOSNormalize.argtypes = [ctypes.c_void_p] lgeos.GEOSWKTReader_create.restype = ctypes.c_void_p lgeos.GEOSWKTReader_destroy.restype = None lgeos.GEOSWKTReader_destroy.argtypes = [ctypes.c_void_p] lgeos.GEOSWKTReader_read.restype = ctypes.c_void_p lgeos.GEOSWKTReader_read.argtypes = [ctypes.c_void_p, ctypes.c_char_p] lgeos.GEOSWKTWriter_create.restype = ctypes.c_void_p lgeos.GEOSWKTWriter_destroy.restype = None lgeos.GEOSWKTWriter_destroy.argtypes = [ctypes.c_void_p] lgeos.GEOSWKTWriter_write.restype = ctypes.c_char_p lgeos.GEOSWKTWriter_write.argtypes = [ctypes.c_void_p, ctypes.c_void_p] lgeos.GEOSWKBReader_create.restype = ctypes.c_void_p lgeos.GEOSWKBReader_destroy.restype = None lgeos.GEOSWKBReader_destroy.argtypes = [ctypes.c_void_p] lgeos.GEOSWKBReader_read.restype = ctypes.c_void_p lgeos.GEOSWKBReader_read.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t] lgeos.GEOSWKBReader_readHEX.restype = ctypes.c_void_p lgeos.GEOSWKBReader_readHEX.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t] lgeos.GEOSWKBWriter_create.restype = ctypes.c_void_p lgeos.GEOSWKBWriter_destroy.restype = None lgeos.GEOSWKBWriter_destroy.argtypes = [ctypes.c_void_p] lgeos.GEOSWKBWriter_getOutputDimension.restype = ctypes.c_int lgeos.GEOSWKBWriter_getOutputDimension.argtypes = [ctypes.c_void_p] lgeos.GEOSWKBWriter_setOutputDimension.restype = None lgeos.GEOSWKBWriter_setOutputDimension.argtypes = [ctypes.c_void_p, ctypes.c_int] lgeos.GEOSWKBWriter_getByteOrder.restype = ctypes.c_int lgeos.GEOSWKBWriter_getByteOrder.argtypes = [ctypes.c_void_p] lgeos.GEOSWKBWriter_setByteOrder.restype = None lgeos.GEOSWKBWriter_setByteOrder.argtypes = [ctypes.c_void_p, ctypes.c_int] lgeos.GEOSWKBWriter_getIncludeSRID.restype = ctypes.c_char lgeos.GEOSWKBWriter_getIncludeSRID.argtypes = [ctypes.c_void_p] lgeos.GEOSWKBWriter_setIncludeSRID.restype = None lgeos.GEOSWKBWriter_setIncludeSRID.argtypes = [ctypes.c_void_p, ctypes.c_char] Furthermore, the following unneeded declarations are removed to avoid problems with Debian's 2.2.3 package: lgeos.GEOSCoordSeq_getOrdinate.restype = ctypes.c_int lgeos.GEOSCoordSeq_getOrdinate.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p] Shapely-1.5.13/LICENSE.txt000066400000000000000000000027571260610516500150460ustar00rootroot00000000000000 Copyright (c) 2007, Sean C. Gillies All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Sean C. Gillies nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Shapely-1.5.13/MANIFEST.in000066400000000000000000000005661260610516500147550ustar00rootroot00000000000000prune manual prune debian prune docs prune DLLs_AMD64 prune DLLs_x86 exclude *.txt exclude MANIFEST.in include CHANGES.txt CREDITS.txt LICENSE.txt README.rst VERSION.txt recursive-include tests *.py *.txt recursive-include shapely/examples *.py recursive-include shapely/speedups *.pyx recursive-include shapely/vectorized *.pyx include shapely/_geos.pxi include docs/*.rst Shapely-1.5.13/README.rst000066400000000000000000000105571260610516500147070ustar00rootroot00000000000000======= Shapely ======= Manipulation and analysis of geometric objects in the Cartesian plane. .. image:: https://travis-ci.org/Toblerity/Shapely.png?branch=master :target: https://travis-ci.org/Toblerity/Shapely .. image:: http://farm3.staticflickr.com/2738/4511827859_b5822043b7_o_d.png :width: 800 :height: 400 Shapely is a BSD-licensed Python package for manipulation and analysis of planar geometric objects. It is based on the widely deployed `GEOS `__ (the engine of `PostGIS `__) and `JTS `__ (from which GEOS is ported) libraries. Shapely is not concerned with data formats or coordinate systems, but can be readily integrated with packages that are. For more details, see: * Shapely on `GitHub `__ * The Shapely `manual `__ Requirements ============ Shapely 1.5.x requires * Python >=2.6 (including Python 3.x) * GEOS >=3.3 (Shapely 1.2.x requires only GEOS 3.1 but YMMV) Installing Shapely ================== Windows users should download an executable installer from http://www.lfd.uci.edu/~gohlke/pythonlibs/#shapely or PyPI (if available). On other systems, acquire the GEOS by any means (`brew install geos` on OS X or `apt-get install libgeos-dev` on Debian/Ubuntu), make sure that it is on the system library path, and install Shapely from the Python package index. .. code-block:: console $ pip install shapely If you've installed GEOS to a non-standard location, the geos-config program will be used to get compiler and linker options. If it is not on the PATH, it can be specified with a GEOS_CONFIG environment variable, e.g.: .. code-block:: console $ GEOS_CONFIG=/path/to/geos-config pip install shapely If your system's GEOS version is < 3.3.0 you cannot use Shapely 1.3+ and must stick to 1.2.x as shown below. .. code-block:: console $ pip install shapely<1.3 Or, if you're using pip 6+ .. code-block:: console $ pip install shapely~=1.2 Shapely is also provided by popular Python distributions like Canopy (Enthought) and Anaconda (Continuum Analytics). Usage ===== Here is the canonical example of building an approximately circular patch by buffering a point. .. code-block:: pycon >>> from shapely.geometry import Point >>> patch = Point(0.0, 0.0).buffer(10.0) >>> patch >>> patch.area 313.65484905459385 See the manual for comprehensive usage snippets and the dissolve.py and intersect.py examples. Integration =========== Shapely does not read or write data files, but it can serialize and deserialize using several well known formats and protocols. The shapely.wkb and shapely.wkt modules provide dumpers and loaders inspired by Python's pickle module. .. code-block:: pycon >>> from shapely.wkt import dumps, loads >>> dumps(loads('POINT (0 0)')) 'POINT (0.0000000000000000 0.0000000000000000)' Shapely can also integrate with other Python GIS packages using GeoJSON-like dicts. .. code-block:: pycon >>> import json >>> from shapely.geometry import mapping, shape >>> s = shape(json.loads('{"type": "Point", "coordinates": [0.0, 0.0]}')) >>> s >>> print(json.dumps(mapping(s))) {"type": "Point", "coordinates": [0.0, 0.0]} Development and Testing ======================= Dependencies for developing Shapely are listed in requirements-dev.txt. Cython and Numpy are not required for production installations, only for development. Use of a virtual environment is strongly recommended. .. code-block:: console $ virtualenv . $ source bin/activate (env)$ pip install -r requirements-dev.txt (env)$ pip install -e . We use py.test to run Shapely's suite of unittests and doctests. .. code-block:: console (env)$ py.test tests Roadmap and Maintenance ======================= Shapely 1.2.x is a maintenance-only branch which supports Python 2.4-2.6, but not Python 3+. There will be no new features in Shapely 1.2.x and only fixes for major bugs. Shapely 1.4.x is a maintenance-only branch supporting Pythons 2.7 and 3.3+. Support ======= Please discuss Shapely with us at http://lists.gispython.org/mailman/listinfo/community. Bugs may be reported at https://github.com/Toblerity/Shapely/issues. Shapely-1.5.13/build-scripts/000077500000000000000000000000001260610516500157745ustar00rootroot00000000000000Shapely-1.5.13/build-scripts/macosx-10.6-intel.sh000066400000000000000000000006071260610516500213200ustar00rootroot00000000000000#!/bin/bash # Dependent on the Kyngchaos Frameworks: # http://www.kyngchaos.com/software/frameworks export GEOS_CONFIG="/Library/Frameworks/GEOS.framework/Versions/3/unix/bin/geos-config" CFLAGS="`$GEOS_CONFIG --cflags`" LDFLAGS="`$GEOS_CONFIG --clibs`" python setup.py bdist_wheel delocate-wheel -w fixed_wheels --require-archs=intel -v dist/Shapely-1.5.2-cp27-none-macosx_10_6_intel.whl Shapely-1.5.13/build-wheels.sh000066400000000000000000000016371260610516500161370ustar00rootroot00000000000000#!/bin/bash # Automation of this is a TODO. For now, it depends on manually built libraries # as detailed in https://gist.github.com/sgillies/a8a2fb910a98a8566d0a. export MACOSX_DEPLOYMENT_TARGET=10.6 export GEOS_CONFIG="/usr/local/bin/geos-config" VERSION=$1 source $HOME/envs/pydotorg27/bin/activate touch shapely/speedups/*.pyx touch shapely/vectorized/*.pyx CFLAGS="`$GEOS_CONFIG --cflags`" LDFLAGS="`$GEOS_CONFIG --libs`" python setup.py bdist_wheel -d wheels/$VERSION source $HOME/envs/pydotorg34/bin/activate touch shapely/speedups/*.pyx touch shapely/vectorized/*.pyx CFLAGS="`$GEOS_CONFIG --cflags`" LDFLAGS="`$GEOS_CONFIG --libs`" python setup.py bdist_wheel -d wheels/$VERSION parallel delocate-wheel -w fixed_wheels/$VERSION --require-archs=intel -v {} ::: wheels/$VERSION/*.whl parallel cp {} {.}.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl ::: fixed_wheels/$VERSION/*.whl Shapely-1.5.13/docs/000077500000000000000000000000001260610516500141405ustar00rootroot00000000000000Shapely-1.5.13/docs/Makefile000066400000000000000000000063331260610516500156050ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* build/plot_directive/* html: apidocs $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: apidocs $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Shapely.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Shapely.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." apidocs: sphinx-apidoc -f -s txt -o . ../shapely @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." Shapely-1.5.13/docs/code/000077500000000000000000000000001260610516500150525ustar00rootroot00000000000000Shapely-1.5.13/docs/code/buffer.py000066400000000000000000000027321260610516500167010ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import LineString from descartes import PolygonPatch from figures import SIZE, BLUE, GRAY def plot_line(ax, ob): x, y = ob.xy ax.plot(x, y, color=GRAY, linewidth=3, solid_capstyle='round', zorder=1) line = LineString([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) fig = pyplot.figure(1, figsize=SIZE, dpi=90) # 1 ax = fig.add_subplot(121) plot_line(ax, line) dilated = line.buffer(0.5, cap_style=3) patch1 = PolygonPatch(dilated, fc=BLUE, ec=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch1) ax.set_title('a) dilation, cap_style=3') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2 ax = fig.add_subplot(122) patch2a = PolygonPatch(dilated, fc=GRAY, ec=GRAY, alpha=0.5, zorder=1) ax.add_patch(patch2a) eroded = dilated.buffer(-0.3) # GeoJSON-like data works as well polygon = eroded.__geo_interface__ # >>> geo['type'] # 'Polygon' # >>> geo['coordinates'][0][:2] # ((0.50502525316941682, 0.78786796564403572), (0.5247963548222736, 0.8096820147509064)) patch2b = PolygonPatch(polygon, fc=BLUE, ec=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch2b) ax.set_title('b) erosion, join_style=1') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/cascaded_union.py000066400000000000000000000017511260610516500203670ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import Point from shapely.ops import cascaded_union from descartes import PolygonPatch from figures import SIZE, BLUE, GRAY polygons = [Point(i, 0).buffer(0.7) for i in range(5)] fig = pyplot.figure(1, figsize=SIZE, dpi=90) # 1 ax = fig.add_subplot(121) for ob in polygons: p = PolygonPatch(ob, fc=GRAY, ec=GRAY, alpha=0.5, zorder=1) ax.add_patch(p) ax.set_title('a) polygons') xrange = [-2, 6] yrange = [-2, 2] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2 ax = fig.add_subplot(122) u = cascaded_union(polygons) patch2b = PolygonPatch(u, fc=BLUE, ec=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch2b) ax.set_title('b) union') xrange = [-2, 6] yrange = [-2, 2] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/convex_hull.py000066400000000000000000000022471260610516500177570ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import MultiPoint from descartes.patch import PolygonPatch from figures import SIZE fig = pyplot.figure(1, figsize=SIZE, dpi=90) fig.set_frameon(True) # 1 ax = fig.add_subplot(121) points2 = MultiPoint([(0, 0), (2, 2)]) for p in points2: ax.plot(p.x, p.y, 'o', color='#999999') hull2 = points2.convex_hull x, y = hull2.xy ax.plot(x, y, color='#6699cc', linewidth=3, alpha=0.5, zorder=2) ax.set_title('a) N = 2') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2 ax = fig.add_subplot(122) points1 = MultiPoint([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) for p in points1: ax.plot(p.x, p.y, 'o', color='#999999') hull1 = points1.convex_hull patch1 = PolygonPatch(hull1, facecolor='#6699cc', edgecolor='#6699cc', alpha=0.5, zorder=2) ax.add_patch(patch1) ax.set_title('b) N > 2') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/difference.py000066400000000000000000000024301260610516500175150ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import Point from descartes import PolygonPatch from figures import SIZE, BLUE, GRAY fig = pyplot.figure(1, figsize=SIZE, dpi=90) a = Point(1, 1).buffer(1.5) b = Point(2, 1).buffer(1.5) # 1 ax = fig.add_subplot(121) patch1 = PolygonPatch(a, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch1) patch2 = PolygonPatch(b, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch2) c = a.difference(b) patchc = PolygonPatch(c, fc=BLUE, ec=BLUE, alpha=0.5, zorder=2) ax.add_patch(patchc) ax.set_title('a.difference(b)') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2 ax = fig.add_subplot(122) patch1 = PolygonPatch(a, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch1) patch2 = PolygonPatch(b, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch2) c = b.difference(a) patchc = PolygonPatch(c, fc=BLUE, ec=BLUE, alpha=0.5, zorder=2) ax.add_patch(patchc) ax.set_title('b.difference(a)') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/figures.py000066400000000000000000000001601260610516500170650ustar00rootroot00000000000000from math import sqrt GM = (sqrt(5)-1.0)/2.0 W = 8.0 H = W*GM SIZE = (W, H) BLUE = '#6699cc' GRAY = '#999999' Shapely-1.5.13/docs/code/geometrycollection.py000066400000000000000000000031461260610516500213370ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import LineString from figures import SIZE BLUE = '#6699cc' YELLOW = '#ffcc33' GREEN = '#339933' GRAY = '#999999' def plot_coords(ax, ob): x, y = ob.xy ax.plot(x, y, 'o', color=GRAY, zorder=1) fig = pyplot.figure(1, figsize=SIZE, dpi=90) #1, figsize=(10, 4), dpi=180) a = LineString([(0, 0), (1, 1), (1,2), (2,2)]) b = LineString([(0, 0), (1, 1), (2,1), (2,2)]) # 1: disconnected multilinestring ax = fig.add_subplot(121) plot_coords(ax, a) plot_coords(ax, b) x, y = a.xy ax.plot(x, y, color=YELLOW, alpha=0.5, linewidth=3, solid_capstyle='round', zorder=2) x, y = b.xy ax.plot(x, y, color=GREEN, alpha=0.5, linewidth=3, solid_capstyle='round', zorder=2) ax.set_title('a) lines') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2: invalid self-touching ring ax = fig.add_subplot(122) x, y = a.xy ax.plot(x, y, color=GRAY, alpha=0.7, linewidth=1, solid_capstyle='round', zorder=1) x, y = b.xy ax.plot(x, y, color=GRAY, alpha=0.7, linewidth=1, solid_capstyle='round', zorder=1) for ob in a.intersection(b): x, y = ob.xy if len(x) == 1: ax.plot(x, y, 'o', color=BLUE, zorder=2) else: ax.plot(x, y, color=BLUE, alpha=0.7, linewidth=3, solid_capstyle='round', zorder=2) ax.set_title('b) collection') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/intersection-sym-difference.py000066400000000000000000000027571260610516500230430ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import Point from descartes import PolygonPatch from figures import SIZE, BLUE, GRAY fig = pyplot.figure(1, figsize=SIZE, dpi=90) a = Point(1, 1).buffer(1.5) b = Point(2, 1).buffer(1.5) # 1 ax = fig.add_subplot(121) patch1 = PolygonPatch(a, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch1) patch2 = PolygonPatch(b, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch2) c = a.intersection(b) patchc = PolygonPatch(c, fc=BLUE, ec=BLUE, alpha=0.5, zorder=2) ax.add_patch(patchc) ax.set_title('a.intersection(b)') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2 ax = fig.add_subplot(122) patch1 = PolygonPatch(a, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch1) patch2 = PolygonPatch(b, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch2) c = a.symmetric_difference(b) if c.geom_type == 'Polygon': patchc = PolygonPatch(c, fc=BLUE, ec=BLUE, alpha=0.5, zorder=2) ax.add_patch(patchc) elif c.geom_type == 'MultiPolygon': for p in c: patchp = PolygonPatch(p, fc=BLUE, ec=BLUE, alpha=0.5, zorder=2) ax.add_patch(patchp) ax.set_title('a.symmetric_difference(b)') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/linearring.py000066400000000000000000000023651260610516500175640ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry.polygon import LinearRing from figures import SIZE COLOR = { True: '#6699cc', False: '#ff3333' } def v_color(ob): return COLOR[ob.is_valid] def plot_coords(ax, ob): x, y = ob.xy ax.plot(x, y, 'o', color='#999999', zorder=1) def plot_line(ax, ob): x, y = ob.xy ax.plot(x, y, color=v_color(ob), alpha=0.7, linewidth=3, solid_capstyle='round', zorder=2) fig = pyplot.figure(1, figsize=SIZE, dpi=90) # 1: valid ring ax = fig.add_subplot(121) ring = LinearRing([(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 0.8), (0, 0)]) plot_coords(ax, ring) plot_line(ax, ring) ax.set_title('a) valid') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2: invalid self-touching ring ax = fig.add_subplot(122) ring2 = LinearRing([(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)]) plot_coords(ax, ring2) plot_line(ax, ring2) ax.set_title('b) invalid') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/linestring.py000066400000000000000000000024631260610516500176070ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import LineString from figures import SIZE COLOR = { True: '#6699cc', False: '#ffcc33' } def v_color(ob): return COLOR[ob.is_simple] def plot_coords(ax, ob): x, y = ob.xy ax.plot(x, y, 'o', color='#999999', zorder=1) def plot_bounds(ax, ob): x, y = zip(*list((p.x, p.y) for p in ob.boundary)) ax.plot(x, y, 'o', color='#000000', zorder=1) def plot_line(ax, ob): x, y = ob.xy ax.plot(x, y, color=v_color(ob), alpha=0.7, linewidth=3, solid_capstyle='round', zorder=2) fig = pyplot.figure(1, figsize=SIZE, dpi=90) # 1: simple line ax = fig.add_subplot(121) line = LineString([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) plot_coords(ax, line) plot_bounds(ax, line) plot_line(ax, line) ax.set_title('a) simple') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_ylim(*yrange) ax.set_yticks(list(range(*yrange)) + [yrange[-1]]) ax.set_aspect(1) #2: complex line ax = fig.add_subplot(122) line2 = LineString([(0, 0), (1, 1), (0, 2), (2, 2), (-1, 1), (1, 0)]) plot_coords(ax, line2) plot_bounds(ax, line2) plot_line(ax, line2) ax.set_title('b) complex') xrange = [-2, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_ylim(*yrange) ax.set_yticks(list(range(*yrange)) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/multilinestring.py000066400000000000000000000030261260610516500206560ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import MultiLineString from figures import SIZE COLOR = { True: '#6699cc', False: '#ffcc33' } def v_color(ob): return COLOR[ob.is_simple] def plot_coords(ax, ob): for line in ob: x, y = line.xy ax.plot(x, y, 'o', color='#999999', zorder=1) def plot_bounds(ax, ob): x, y = zip(*list((p.x, p.y) for p in ob.boundary)) ax.plot(x, y, 'o', color='#000000', zorder=1) def plot_lines(ax, ob): for line in ob: x, y = line.xy ax.plot(x, y, color=v_color(ob), alpha=0.7, linewidth=3, solid_capstyle='round', zorder=2) fig = pyplot.figure(1, figsize=SIZE, dpi=90) # 1: disconnected multilinestring ax = fig.add_subplot(121) mline1 = MultiLineString([((0, 0), (1, 1)), ((0, 2), (1, 1.5), (1.5, 1), (2, 0))]) plot_coords(ax, mline1) plot_bounds(ax, mline1) plot_lines(ax, mline1) ax.set_title('a) simple') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2: invalid self-touching ring ax = fig.add_subplot(122) mline2 = MultiLineString([((0, 0), (1, 1), (1.5, 1)), ((0, 2), (1, 1.5), (1.5, 1), (2, 0))]) plot_coords(ax, mline2) plot_bounds(ax, mline2) plot_lines(ax, mline2) ax.set_title('b) complex') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/multipolygon.py000066400000000000000000000031061260610516500201660ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import MultiPolygon from descartes.patch import PolygonPatch from figures import SIZE COLOR = { True: '#6699cc', False: '#ff3333' } def v_color(ob): return COLOR[ob.is_valid] def plot_coords(ax, ob): x, y = ob.xy ax.plot(x, y, 'o', color='#999999', zorder=1) fig = pyplot.figure(1, figsize=SIZE, dpi=90) # 1: valid multi-polygon ax = fig.add_subplot(121) a = [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)] b = [(1, 1), (1, 2), (2, 2), (2, 1), (1, 1)] multi1 = MultiPolygon([[a, []], [b, []]]) for polygon in multi1: plot_coords(ax, polygon.exterior) patch = PolygonPatch(polygon, facecolor=v_color(multi1), edgecolor=v_color(multi1), alpha=0.5, zorder=2) ax.add_patch(patch) ax.set_title('a) valid') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2: invalid self-touching ring ax = fig.add_subplot(122) c = [(0, 0), (0, 1.5), (1, 1.5), (1, 0), (0, 0)] d = [(1, 0.5), (1, 2), (2, 2), (2, 0.5), (1, 0.5)] multi2 = MultiPolygon([[c, []], [d, []]]) for polygon in multi2: plot_coords(ax, polygon.exterior) patch = PolygonPatch(polygon, facecolor=v_color(multi2), edgecolor=v_color(multi2), alpha=0.5, zorder=2) ax.add_patch(patch) ax.set_title('b) invalid') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/parallel_offset.py000066400000000000000000000037511260610516500205740ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import LineString from descartes import PolygonPatch from figures import SIZE, BLUE, GRAY def plot_coords(ax, x, y, color='#999999', zorder=1): ax.plot(x, y, 'o', color=color, zorder=zorder) def plot_line(ax, ob, color=GRAY): parts = hasattr(ob, 'geoms') and ob or [ob] for part in parts: x, y = part.xy ax.plot(x, y, color=color, linewidth=3, solid_capstyle='round', zorder=1) def set_limits(ax, x_range, y_range): ax.set_xlim(*x_range) ax.set_xticks(range(*x_range) + [x_range[-1]]) ax.set_ylim(*y_range) ax.set_yticks(range(*y_range) + [y_range[-1]]) ax.set_aspect(1) line = LineString([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) line_bounds = line.bounds ax_range = [int(line_bounds[0] - 1.0), int(line_bounds[2] + 1.0)] ay_range = [int(line_bounds[1] - 1.0), int(line_bounds[3] + 1.0)] fig = pyplot.figure(1, figsize=(SIZE[0], 2 * SIZE[1]), dpi=90) # 1 ax = fig.add_subplot(221) plot_line(ax, line) x, y = list(line.coords)[0] plot_coords(ax, x, y) offset = line.parallel_offset(0.5, 'left', join_style=1) plot_line(ax, offset, color=BLUE) ax.set_title('a) left, round') set_limits(ax, ax_range, ay_range) #2 ax = fig.add_subplot(222) plot_line(ax, line) x, y = list(line.coords)[0] plot_coords(ax, x, y) offset = line.parallel_offset(0.5, 'left', join_style=2) plot_line(ax, offset, color=BLUE) ax.set_title('b) left, mitred') set_limits(ax, ax_range, ay_range) #3 ax = fig.add_subplot(223) plot_line(ax, line) x, y = list(line.coords)[0] plot_coords(ax, x, y) offset = line.parallel_offset(0.5, 'left', join_style=3) plot_line(ax, offset, color=BLUE) ax.set_title('c) left, beveled') set_limits(ax, ax_range, ay_range) #4 ax = fig.add_subplot(224) plot_line(ax, line) x, y = list(line.coords)[0] plot_coords(ax, x, y) offset = line.parallel_offset(0.5, 'right', join_style=1) plot_line(ax, offset, color=BLUE) ax.set_title('d) right, round') set_limits(ax, ax_range, ay_range) pyplot.show() Shapely-1.5.13/docs/code/parallel_offset_mitre.py000066400000000000000000000041001260610516500217610ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import LineString from descartes import PolygonPatch from figures import SIZE, BLUE, GRAY def plot_coords(ax, x, y, color='#999999', zorder=1): ax.plot(x, y, 'o', color=color, zorder=zorder) def plot_line(ax, ob, color=GRAY): parts = hasattr(ob, 'geoms') and ob or [ob] for part in parts: x, y = part.xy ax.plot(x, y, color=color, linewidth=3, solid_capstyle='round', zorder=1) def set_limits(ax, x_range, y_range): ax.set_xlim(*x_range) ax.set_xticks(range(*x_range) + [x_range[-1]]) ax.set_ylim(*y_range) ax.set_yticks(range(*y_range) + [y_range[-1]]) ax.set_aspect(1) line = LineString([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) line_bounds = line.bounds ax_range = [int(line_bounds[0] - 1.0), int(line_bounds[2] + 1.0)] ay_range = [int(line_bounds[1] - 1.0), int(line_bounds[3] + 1.0)] fig = pyplot.figure(1, figsize=(SIZE[0], 2 * SIZE[1]), dpi=90) # 1 ax = fig.add_subplot(221) plot_line(ax, line) x, y = list(line.coords)[0] plot_coords(ax, x, y) offset = line.parallel_offset(0.5, 'left', join_style=2, mitre_limit=0.1) plot_line(ax, offset, color=BLUE) ax.set_title('a) left, limit=0.1') set_limits(ax, ax_range, ay_range) #2 ax = fig.add_subplot(222) plot_line(ax, line) x, y = list(line.coords)[0] plot_coords(ax, x, y) offset = line.parallel_offset(0.5, 'left', join_style=2, mitre_limit=10.0) plot_line(ax, offset, color=BLUE) ax.set_title('b) left, limit=10.0') set_limits(ax, ax_range, ay_range) #3 ax = fig.add_subplot(223) plot_line(ax, line) x, y = list(line.coords)[0] plot_coords(ax, x, y) offset = line.parallel_offset(0.5, 'right', join_style=2, mitre_limit=0.1) plot_line(ax, offset, color=BLUE) ax.set_title('c) right, limit=0.1') set_limits(ax, ax_range, ay_range) #4 ax = fig.add_subplot(224) plot_line(ax, line) x, y = list(line.coords)[0] plot_coords(ax, x, y) offset = line.parallel_offset(0.5, 'right', join_style=2, mitre_limit=10.0) plot_line(ax, offset, color=BLUE) ax.set_title('d) right, limit=10.0') set_limits(ax, ax_range, ay_range) pyplot.show() Shapely-1.5.13/docs/code/polygon.py000066400000000000000000000031061260610516500171130ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import Polygon from descartes.patch import PolygonPatch from figures import SIZE COLOR = { True: '#6699cc', False: '#ff3333' } def v_color(ob): return COLOR[ob.is_valid] def plot_coords(ax, ob): x, y = ob.xy ax.plot(x, y, 'o', color='#999999', zorder=1) fig = pyplot.figure(1, figsize=SIZE, dpi=90) # 1: valid polygon ax = fig.add_subplot(121) ext = [(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)] int = [(1, 0), (0.5, 0.5), (1, 1), (1.5, 0.5), (1, 0)][::-1] polygon = Polygon(ext, [int]) plot_coords(ax, polygon.interiors[0]) plot_coords(ax, polygon.exterior) patch = PolygonPatch(polygon, facecolor=v_color(polygon), edgecolor=v_color(polygon), alpha=0.5, zorder=2) ax.add_patch(patch) ax.set_title('a) valid') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2: invalid self-touching ring ax = fig.add_subplot(122) ext = [(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)] int = [(1, 0), (0, 1), (0.5, 1.5), (1.5, 0.5), (1, 0)][::-1] polygon = Polygon(ext, [int]) plot_coords(ax, polygon.interiors[0]) plot_coords(ax, polygon.exterior) patch = PolygonPatch(polygon, facecolor=v_color(polygon), edgecolor=v_color(polygon), alpha=0.5, zorder=2) ax.add_patch(patch) ax.set_title('b) invalid') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/polygon2.py000066400000000000000000000034061260610516500172000ustar00rootroot00000000000000from matplotlib import pyplot from matplotlib.patches import Circle from shapely.geometry import Polygon from descartes.patch import PolygonPatch from figures import SIZE COLOR = { True: '#6699cc', False: '#ff3333' } def v_color(ob): return COLOR[ob.is_valid] def plot_coords(ax, ob): x, y = ob.xy ax.plot(x, y, 'o', color='#999999', zorder=1) fig = pyplot.figure(1, figsize=SIZE, dpi=90) # 3: invalid polygon, ring touch along a line ax = fig.add_subplot(121) ext = [(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)] int = [(0.5, 0), (1.5, 0), (1.5, 1), (0.5, 1), (0.5, 0)] polygon = Polygon(ext, [int]) plot_coords(ax, polygon.interiors[0]) plot_coords(ax, polygon.exterior) patch = PolygonPatch(polygon, facecolor=v_color(polygon), edgecolor=v_color(polygon), alpha=0.5, zorder=2) ax.add_patch(patch) ax.set_title('c) invalid') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #4: invalid self-touching ring ax = fig.add_subplot(122) ext = [(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)] int_1 = [(0.5, 0.25), (1.5, 0.25), (1.5, 1.25), (0.5, 1.25), (0.5, 0.25)] int_2 = [(0.5, 1.25), (1, 1.25), (1, 1.75), (0.5, 1.75)] # int_2 = [ polygon = Polygon(ext, [int_1, int_2]) plot_coords(ax, polygon.interiors[0]) plot_coords(ax, polygon.interiors[1]) plot_coords(ax, polygon.exterior) patch = PolygonPatch(polygon, facecolor=v_color(polygon), edgecolor=v_color(polygon), alpha=0.5, zorder=2) ax.add_patch(patch) ax.set_title('d) invalid') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/rotate.py000066400000000000000000000025461260610516500167310ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import LineString from shapely import affinity from figures import SIZE, BLUE, GRAY def add_origin(ax, geom, origin): x, y = xy = affinity.interpret_origin(geom, origin, 2) ax.plot(x, y, 'o', color=GRAY, zorder=1) ax.annotate(str(xy), xy=xy, ha='center', textcoords='offset points', xytext=(0, 8)) def plot_line(ax, ob, color): x, y = ob.xy ax.plot(x, y, color=color, alpha=0.7, linewidth=3, solid_capstyle='round', zorder=2) fig = pyplot.figure(1, figsize=SIZE, dpi=90) line = LineString([(1, 3), (1, 1), (4, 1)]) xrange = [0, 5] yrange = [0, 4] # 1 ax = fig.add_subplot(121) plot_line(ax, line, GRAY) plot_line(ax, affinity.rotate(line, 90, 'center'), BLUE) add_origin(ax, line, 'center') ax.set_title(u"90\N{DEGREE SIGN}, default origin (center)") ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) # 2 ax = fig.add_subplot(122) plot_line(ax, line, GRAY) plot_line(ax, affinity.rotate(line, 90, 'centroid'), BLUE) add_origin(ax, line, 'centroid') ax.set_title(u"90\N{DEGREE SIGN}, origin='centroid'") ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/scale.py000066400000000000000000000032611260610516500165150ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import Polygon from shapely import affinity from descartes.patch import PolygonPatch from figures import SIZE, BLUE, GRAY def add_origin(ax, geom, origin): x, y = xy = affinity.interpret_origin(geom, origin, 2) ax.plot(x, y, 'o', color=GRAY, zorder=1) ax.annotate(str(xy), xy=xy, ha='center', textcoords='offset points', xytext=(0, 8)) fig = pyplot.figure(1, figsize=SIZE, dpi=90) triangle = Polygon([(1, 1), (2, 3), (3, 1)]) xrange = [0, 5] yrange = [0, 4] # 1 ax = fig.add_subplot(121) patch = PolygonPatch(triangle, facecolor=GRAY, edgecolor=GRAY, alpha=0.5, zorder=1) triangle_a = affinity.scale(triangle, xfact=1.5, yfact=-1) patch_a = PolygonPatch(triangle_a, facecolor=BLUE, edgecolor=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch) ax.add_patch(patch_a) add_origin(ax, triangle, 'center') ax.set_title("a) xfact=1.5, yfact=-1") ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) # 2 ax = fig.add_subplot(122) patch = PolygonPatch(triangle, facecolor=GRAY, edgecolor=GRAY, alpha=0.5, zorder=1) triangle_b = affinity.scale(triangle, xfact=2, origin=(1, 1)) patch_b = PolygonPatch(triangle_b, facecolor=BLUE, edgecolor=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch) ax.add_patch(patch_b) add_origin(ax, triangle, (1, 1)) ax.set_title("b) xfact=2, origin=(1, 1)") ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/simplify.py000066400000000000000000000023051260610516500172600ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import MultiPoint, Point from descartes.patch import PolygonPatch from figures import SIZE, BLUE, GRAY fig = pyplot.figure(1, figsize=SIZE, dpi=90) #1, figsize=SIZE, dpi=90) p = Point(1, 1).buffer(1.5) # 1 ax = fig.add_subplot(121) q = p.simplify(0.2) patch1a = PolygonPatch(p, facecolor=GRAY, edgecolor=GRAY, alpha=0.5, zorder=1) ax.add_patch(patch1a) patch1b = PolygonPatch(q, facecolor=BLUE, edgecolor=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch1b) ax.set_title('a) tolerance 0.2') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) #2 ax = fig.add_subplot(122) r = p.simplify(0.5) patch2a = PolygonPatch(p, facecolor=GRAY, edgecolor=GRAY, alpha=0.5, zorder=1) ax.add_patch(patch2a) patch2b = PolygonPatch(r, facecolor=BLUE, edgecolor=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch2b) ax.set_title('b) tolerance 0.5') xrange = [-1, 3] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/skew.py000066400000000000000000000047211260610516500164010ustar00rootroot00000000000000from matplotlib import pyplot from shapely.wkt import loads as load_wkt from shapely import affinity from descartes.patch import PolygonPatch from figures import SIZE, BLUE, GRAY def add_origin(ax, geom, origin): x, y = xy = affinity.interpret_origin(geom, origin, 2) ax.plot(x, y, 'o', color=GRAY, zorder=1) ax.annotate(str(xy), xy=xy, ha='center', textcoords='offset points', xytext=(0, 8)) fig = pyplot.figure(1, figsize=SIZE, dpi=90) # Geometry from JTS TestBuilder with fixed precision model of 100.0 # Using CreateShape > FontGlyphSanSerif and A = triangle.wkt from scale.py R = load_wkt('''\ POLYGON((2.218 2.204, 2.273 2.18, 2.328 2.144, 2.435 2.042, 2.541 1.895, 2.647 1.702, 3 1, 2.626 1, 2.298 1.659, 2.235 1.777, 2.173 1.873, 2.112 1.948, 2.051 2.001, 1.986 2.038, 1.91 2.064, 1.823 2.08, 1.726 2.085, 1.347 2.085, 1.347 1, 1 1, 1 3.567, 1.784 3.567, 1.99 3.556, 2.168 3.521, 2.319 3.464, 2.441 3.383, 2.492 3.334, 2.536 3.279, 2.604 3.152, 2.644 3.002, 2.658 2.828, 2.651 2.712, 2.63 2.606, 2.594 2.51, 2.545 2.425, 2.482 2.352, 2.407 2.29, 2.319 2.241, 2.218 2.204), (1.347 3.282, 1.347 2.371, 1.784 2.371, 1.902 2.378, 2.004 2.4, 2.091 2.436, 2.163 2.487, 2.219 2.552, 2.259 2.63, 2.283 2.722, 2.291 2.828, 2.283 2.933, 2.259 3.025, 2.219 3.103, 2.163 3.167, 2.091 3.217, 2.004 3.253, 1.902 3.275, 1.784 3.282, 1.347 3.282))''') xrange = [0, 5] yrange = [0, 4] # 1 ax = fig.add_subplot(121) patch1a = PolygonPatch(R, facecolor=GRAY, edgecolor=GRAY, alpha=0.5, zorder=1) skewR = affinity.skew(R, xs=20, origin=(1, 1)) patch1b = PolygonPatch(skewR, facecolor=BLUE, edgecolor=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch1a) ax.add_patch(patch1b) add_origin(ax, R, (1, 1)) ax.set_title("a) xs=20, origin(1, 1)") ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) # 2 ax = fig.add_subplot(122) patch2a = PolygonPatch(R, facecolor=GRAY, edgecolor=GRAY, alpha=0.5, zorder=1) skewR = affinity.skew(R, ys=30) patch2b = PolygonPatch(skewR, facecolor=BLUE, edgecolor=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch2a) ax.add_patch(patch2b) add_origin(ax, R, 'center') ax.set_title("b) ys=30") ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/code/triangulate.py000066400000000000000000000012201260610516500177360ustar00rootroot00000000000000from shapely.geometry import MultiPoint from shapely.ops import triangulate from matplotlib import pyplot from descartes.patch import PolygonPatch from figures import SIZE, BLUE, GRAY points = MultiPoint([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) triangles = triangulate(points) fig = pyplot.figure(1, figsize=SIZE, dpi=90) fig.set_frameon(True) ax = fig.add_subplot(111) for triangle in triangles: patch = PolygonPatch(triangle, facecolor=BLUE, edgecolor=BLUE, alpha=0.5, zorder=2) ax.add_patch(patch) for point in points: pyplot.plot(point.x, point.y, 'o', color=GRAY) pyplot.xlim(-0.5, 3.5) pyplot.ylim(-0.5, 2.5) pyplot.show() Shapely-1.5.13/docs/code/union.py000066400000000000000000000026211260610516500165550ustar00rootroot00000000000000from matplotlib import pyplot from shapely.geometry import Point from descartes import PolygonPatch from figures import SIZE, BLUE, GRAY fig = pyplot.figure(1, figsize=SIZE, dpi=90) a = Point(1, 1).buffer(1.5) b = Point(2, 1).buffer(1.5) # 1 ax = fig.add_subplot(121) patch1 = PolygonPatch(a, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch1) patch2 = PolygonPatch(b, fc=GRAY, ec=GRAY, alpha=0.2, zorder=1) ax.add_patch(patch2) c = a.union(b) patchc = PolygonPatch(c, fc=BLUE, ec=BLUE, alpha=0.5, zorder=2) ax.add_patch(patchc) ax.set_title('a.union(b)') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) def plot_line(ax, ob, color=GRAY): x, y = ob.xy ax.plot(x, y, color, linewidth=3, solid_capstyle='round', zorder=1) #2 ax = fig.add_subplot(122) plot_line(ax, a.exterior) plot_line(ax, b.exterior) u = a.exterior.union(b.exterior) if u.geom_type in ['LineString', 'LinearRing', 'Point']: plot_line(ax, u, color=BLUE) elif u.geom_type is 'MultiLineString': for p in u: plot_line(ax, p, color=BLUE) ax.set_title('a.boundary.union(b.boundary)') xrange = [-1, 4] yrange = [-1, 3] ax.set_xlim(*xrange) ax.set_xticks(range(*xrange) + [xrange[-1]]) ax.set_ylim(*yrange) ax.set_yticks(range(*yrange) + [yrange[-1]]) ax.set_aspect(1) pyplot.show() Shapely-1.5.13/docs/conf.py000066400000000000000000000154131260610516500154430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Shapely documentation build configuration file, created by # sphinx-quickstart on Mon Apr 12 11:07:08 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.append(os.path.abspath('sphinxext')) # Load latest source tree sys.path.insert(0, os.path.abspath('..')) import shapely # For pyplots in code/, load functions here first, so they are visible from shapely import geometry, affinity, wkt, wkb from shapely.ops import cascaded_union # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'matplotlib.sphinxext.only_directives', 'matplotlib.sphinxext.plot_directive', 'sphinx.ext.autodoc', #'sphinx.ext.pngmath', # <----- pick one, not both 'sphinx.ext.mathjax', # <--/ ] # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Shapely' copyright = '2011-2013, Sean Gillies, Aron Bierbaum, Kai Lautaportti ' \ 'and others' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = shapely.__version__ # The full version, including alpha/beta/rc tags. release = shapely.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme = 'haiku' html_theme = 'sphinxdoc' # html_theme = 'shapely' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = "Shapely 1.2 and 1.3 documentation" # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Shapelydoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Shapely.tex', 'Shapely Documentation', 'Sean Gillies', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True Shapely-1.5.13/docs/design.rst000066400000000000000000000026031260610516500161440ustar00rootroot00000000000000============ Design Notes ============ Shapely provides classes that implement, more or less, the interfaces in the OGC's simple feature acess specification [1]_. The classes are defined in similarly named modules under ``shapely.geometry``: ``Point`` is in ``shapely.geometry.point``, ``MultiPolygon`` is in ``shapely.geometry.multipolygon``. These classes derive from ``shapely.geometry.base.BaseGeometry``. The simple features methods of ``BaseGeometry`` call functions registered in a class variable ``impl``. For example, ``BaseGeometry.area`` calls ``BaseGeometry.impl['area']``. The default registry is in the ``shapely.impl`` module. Its items are classes that operate on single geometric objects or pairs of geometric objects. Pluggability is a goal of this design, but we're not there yet. Some work needs to be done before anybody can use CGAL as a Shapely backend. In sum, Shapely's stack is 4 layers: * Python geometry classes in ``shapely.geometry`` * An implementation registry: an abstraction that permits alternate geometry engines, even a mix of geometry engines. The default is in ``shapely.impl``. * The GEOS implementations of methods for the registry in ``shapely.geos``. * libgeos: algorithms written in C++. .. [1] John R. Herring, Ed., “OpenGIS Implementation Specification for Geographic information - Simple feature access - Part 1: Common architecture,” Oct. 2006. Shapely-1.5.13/docs/images/000077500000000000000000000000001260610516500154055ustar00rootroot00000000000000Shapely-1.5.13/docs/images/4511827859_b5822043b7_o.png000066400000000000000000002116611260610516500210430ustar00rootroot00000000000000PNG  IHDR V%4sBIT|d pHYsaa?i IDATxy|SU߽ӦYZ@\@aE}qEDEdQ|}UQ@eZhYPk&]%MǝFBM~>h{ν=O<֬YcZ.55~;7_0~x^<B]TBc vmAnn.`ܹ8~8233Cdd$y[7K!}գ2xP ǽkh@PP&N={F !OzB @1fh4|'/233ЀÇZn@FF]dBS%K菞z)|GHk駟TTTZsZC!$$ 2X#''SNGQBHW.xWZ=^y_v:|6$|g?~QBHhFH"000qD<7|O<֫qf} @A:Js{9lܸ%TAe+W47Ѷ@e-Tv+hF h4"//P(xb~wDFFB*\:t(z=ť_*{io GwEeT' XwFmmi<$$$`Μ9XRB雨%Z@d`ܸqpssCEEvލ;v`ٲenWƘ1cp]wa娯ʕ+入KQBHzBX!>>ǎ?n .D^^>3I cǎA"#<(8qtq%QB!bEaѢE-#Gpyv;գϿ+*{c.$''#>>IIIzP!@?>BHz !B!n(!Bرcyս]zWcYرck׮*,\gn۷;oMo MzdXl^8m!}… onn /Xd2Gaar?>{ uuupB >'Zf nf(v<̘1Ʋe6qӧyyyx;`ս]7l0|G3g0tPL:f:t Æ CVV֮]gyp_[nŨQnzoժU/uVddd`ƍزe }]2wuJJJpa10.[ ~1o?Ę~ZЪh"hZà zٲe T*Ehh(>c}={G\.G\\\'{E\\ |}}/0FKD",YbձlܸHII1[̙3~)/^YfsSO=w;wb֬Yf]xs9s`ڴifA*bڴi-> ĉشi{?~cǎ\.^y3x衇hjy7-.O{:~y|'={6l="##P(0i$l۶Z-Z'''x{{cƍ]:;ﴻk̝;@/رcK/!((ƍoA3?05k{3f`#F`1~z6tP&J[`h41qfWf1^z%$ a}zGeL&QF .cFbre˖1^c`0_|0X̼ŋ>W, ]|M6YnG_YYz!ʔJ%>}:KOOtm۶#F0\T*{XAAA}jvwֲ߳C2XrssYCC[t)g218p^EE3gfEDD?޽{;]ܹs89|28]tɴ̱c󬸸m;rw&Xee۷3gggs0鏬GgϞmٳYll,c/O,66=C1ƚئM3+--e1ܹsYxx8;x +((`Ǐg~-c쏺[oe+KKKc&MbF2͍},???~EFFe˖1۱csrrb`ܹslǎWZBBB,>glÆ ӓUUUonnf_e13TʚܶVe<ϳ4cϟg~~~/0[cVe7|3{'Lߋ``W^er-]eggC1ooo/v^gk֬a/^d?gNNNO>1z:1 dwfyyylҥL.2c)))L"+V\w^xgZgpp0`[neweDZϛm vxKJJX@@KJJbٌVGeaaaݺZK,a&M2{رc8v׵^ }]8-p 6qDyf:[̙3ʕ+l̙,88t6׫5ѣGYAA_M$;cLIR6i$222Nc?8q"KLLdlӦML..l˖-ʕ+,;;L"Gm8ϡȑ#GLV3O2L& @VXƌc^^^^ @7/ys1T*ْ%K\~Ϟ=իWq_~is-u7fDZjcz+۰az_}sssc +  Cزe xg,11'N0ooo1& yVRR>q,77g/28I$<[n]evD"Qږo=f-] >콭[2k˳>Nkkξ_Ƅ:70gb}1Ỉ7wL{uaHH{G3.XokF#6m[f cjd[ݻbk.f4Yii)4i8}WkOc4e?~#<fPo⡇H$8998xyyKKK7|'Oo6{z 7x#aԩSQSSJk^3M.7x/Q\\ ___L:<#G4m!JO[uZw.]BBB0j(B???ٳ<@0[@8g7oF\\j5<<<,*WGY ^>QQQL|(..'g?۷cÆ 3랱xb9rv¤I...jhh1|%%%1[N$%%%6w뒒1GOCR`0 f[o@^n:\r hnnFcc#Ο?LfC3l0h4PTHNNFRRY@`ܹsi&DDD`ԩ!hYb T*(++3mg֬Y|eLˠCh3{w2s-7W\AMM [T*ddd`̙?ܹsI₶nZ~1[.\0]t P* AFF{=xqA[UR0.www},_B"== J2] x"-|MZ fBTT~,]k׮5["JҪcq]}vX>,N8 .8K[:~[tixѶ-ӧOC&A"ѣG&$$wށD"D"c=V D۷^{ UUU@EE)IXXGH:j&M>"^^^P(`pذa\\\p),z->}}(++CPP=KbӦMTjqw믿aoKϑhT*Ņ Zm¿f|ؼy3 LKXYYYHHHJ2}*(3 (//o:ak8{-zzߤj ^bb"b޽lHmȑhllӧqM7uLqqqHKKKP{=܃'x8UOg4MիW;rJtsNVEDD8Df<>77aaaV=,Dtƶzu)(JީS0~xVV屦||';s~yfYSNΝ;M=$N>mv>;_O_D|  @x\`LbzԩS`SKuhllĹsL 555HIIs=z)))ڵk 8we,2j(455GnsSNa٘;w.edd؊^ܹs}~M7AVˈ#Yrz8{,n&aƍtRuT*FD?̙3o ((t xwMTDL0"00(((\.;x Xlz)cŊXxZ[EFFbΝ8|0"##_~1--ģ>-[`ժUXh.^[A/ ЪEE``떀4<<\\߭711<ϛշ{+T{NHRݻׯ_M- upbΝ^8j5.FAAN$%%?Gss3JJJPRRb2tPL6 ?8~w$''駟ƼyZ!._ϣ.\0kHLLDtt4L˃`0҅mʔ)¢E'N`Ŋ_jQx:m̘1Xf x `x뭷̖0a~a̘1^^^x}fΜGyf{[VYZL2þ}0b 6 ׯ7qrr–-[0j(DGGسgHEEE"ɘ1c1adddয়~2צma0n:DGG[nAEEN:eֲі|ռA|Ozx'1|9&:t,(_륗^Bcc#|DDD8~8 b…Xn]=Ә1cfϞx`fߗ%YZ"&&~-KDGGcƍXbcd}vV>Ϸz\%˴t޽{1fbΝZ؈};I"%N:cMV ʂuk2S*O?Ԕ1!… S4/cb &$$m1!*fN:nT*B`cƌ1}ݳg;v,spp`RdzѴ믿BCC8kmg,`{_UU,X\\\L.##m\.gr ۷oyޔswξm۶1WWVzj*$ `3g4e)//gӧOg2y{{+Wʢm6|4<ϛGaZ^_>=LR1ggg裏 ikr>=mssssٝwɔJ%d/Uue=3DbJ]j9;;q[oe111f%$$0777Do.F^8>CgclO$''#>>IIIԟ IDATױBCCzj,XBH2뙁~|Ç[=aGV^V2vdرX|9̙czW^.]jW}ƌOOO:u O>$׿M88%&&… ` ֟;w.\\\lgƀ'55|BHDFFZեvK];Nnk3RQQ////yhbYb@H5o diH! T6m¦Mz}ΨQpYk:_ B'B!ž={^3h>Z@!BH{njLgϞEUUu(!B!qݚ,E!BҷQB!B% >' @!BHCGE!BW((!B! B!_c`B!y| B!>&"$ F46!9$& @H5 HɯFFI-J4 k4,E"H$b140"#]0e7B<Zm.jqr9Vm02Ppw G~#Op\ tptt;\=eD` ZK*޳EcᬔجB`CE2M]~H. F(2Herr0PQ$`Z)Ej$eH7@(eϲB'r[R qchnnF]]4 ~H.ơ%5zfEićGa>d.o0Vq6s4xdbFt B`DFc 'Sy <8BB7***]Z<{g$r㹐dJjs...pqqT>.T?ZܽzEUذ? z#!pph:s=Hoo\e 5ڣKe @xQSS&<ӃB" >> @US8RC,ψ8xxx@R!+3v,YD"APPq9+ wFB*.[N?1DA"+X,FXX8\bj !XiiiFyy9cfr ** }MN~jj ƍn+%}‘#G0oveVoD"8::Քƾr .^OOOx]!jjT?? :ZmnZd ꫯ***OmrvGAE=4uMo}LE.APHwHh(!qT*Ź,<-Ɵo yMmOAArssMZ ̙37tyy ]F$BB+W%K_>ƀ-[[:҂ㄛ '^{ Ǣ"3\rND"App0ڽ1P]]b߿7n8xسG`֒uQ͙L9 ݰb |زe Fe>s ̛7ͳ>UZ , #_\ ?BR=;V7֖ruu^|8uښC|/h4T z=ϟƝq???6ٮO>n>}}m=BsB ֐Ʉ`yyo ]{c E# a';99/_FԩSoE7ŧH(q;V ^{M;)hDH^k֬ڵkxb׫qpuup7nD\\m &eRZ:17&?v;-t; @ŭ:kxzzZW#6"E: wrr2{kI[S=ږ:!88ئ7`* r@jkC:pn-//Tx ?w};N 7~~-8N 7_"#VtχZFyy9ʄ m긨P(Gbʔ) i  +'O>jռ.)~=_QW6-_p( \xz;"##]'ݘ/&v;-xuV)"TZVb +W ) c#g8BWlѢxˠ~7!nE"nz Z8; vGKr0&'"99zTnVT Eff&.]n}Ww'/KOZR[ t֞/M` gBNPAE- Vxwj*L6 3fjqOƌ ˗/7M入KFѻQ&0e Wc)]}|qW 3bmN뱺;ZF-'+,칹2r!V۵q |#N.qvX:FAPP<<(q<}q ΐ!Cp1˸ ? os讄`DCCEc%1TWcdP:>}93|}}?#C\nʺ) MF$<`mS2>+$$$Xl\\9҃pGH<]RWW \0W\/fJv0h*'OOɻڑZX}I(of4 "5 W 0Dpw"|l7omT*5\=mDuTgZv{WJ_S@͵*a㨭Ő!Cڝ|\]]Q^^]ʀ#GMD"%AgmR1mWcЅO}X۔9NI<ƄL>>>v1. "&ʺ+IY)())]ɨ<}|q)'ex9셭ɀ/N!1Jٹex!.H<t@e"3z{k}}=xvV|}=]MMBz2 B  Bw2yaQUHvv6rrrf;WխG֭Bh.99A1u*N2A&#!{qYdXdN@ii)|m.rLpppmjF-WWW!9[i#{n&l<uMBBBw#777jdUa4A)(w1 .?!] ш:8}MM }`鐓h44668(J)d²Z!6**? Y7SZcEw6Ξ= ''NSFJmw;{Vh%UG Xٳג˅Av6¼.ÆٶQB,"ýqb1lTꐑgnƯFA~>& @luAČ8_!5+ aa] Bz=j5{ňy8::"fl! z!Czq<==y Gt:pǯc?LeqH@ R  zՈmFٳge 8r9J%chllDff&RRR Jq 7` & F#<Q@Ə^77 ~aaD(Cs0~Z@zpK0ڊh3eq@f22l;HZrq]>Z<*w2!uPB,6sҊkȨ(h4F F[=xee%rss0_o"Ów#YHȀU]4 `42r~Y;D.WHSeöc9kFԐht!8ppp/ tҊkؤPH{!9O(E]7#c{NN6z... t=S97pB8T &СC())&ydggnnnP(ŐH$V?LdDnn. HL.dSrtM//aBȞ*dj .Ϊ.Xս.eN///fuGC4ݮn)Y>rvIXM!; \GtcI1Fssx:Λ1.r1t  p񁛛[ݰL}n,T fAuU?Ϸ4RYeu(nI  tƸ(wPNWQ! =A*&{fbw\79|IYU rtR(]Ŷc9X<%:l?M\Je6 %sF5f^MQ@(5OāsP6CEр1/eu#qatE`DQU\=,+3"sU>g!/ ߏ˗/#%%iiiyr455GK IDAT AR=|pssCQQ탳Bs7}Z Lk(++B ZSLi+-Mi >\]۟@ZF @ @H+4`[;aB@pp.ff") [RNϹP*:t(ʶ[58\.Vq&%xHn21n⁓iY+!yz\),B rƜwJ4 ]ɽ=-sb(l~G*bȑ1bj5JJJP]]Fnnnprr޽{cB1FkL@@di7|p^ ?_owִ|X m^/~~~6ٞ 'qyy2UOOAAAw/jPOR!33*^ 7G/*wⷌJ!,,&]R)|Ko0PUU2ŝqVMsN6ՕMJn (--Euu5LA;<<<&֩Sݭ G.&I#) ~61RX@gdⳟs.rQQCv5oy+Tr1|]tCۂGPTT??_!777`R1PWЌ첺u-$bՍ6NIHc4M3ty,U9RDaذafFpww{%Cd4AHȂR111t<==m QQQ200~xDGG]Vƺj՛ ;3zGz=H_w1W #U-T*p.[!~*lKȁCaHǞ3E -MpRJ06yC%4 uŽ7co0 mu-B,HO:;:iwk6aɀg 1*^̋RVVǏ NNN JD"1[Oh4|2.]89<ϣ=>OO[nɮFnCFFrrr٭z)))ptthN󈊊B\\\uvK+^/X >O(!*ޛ7vw}eY^}lN&$& C6Z@/-}iK_ m߫¯-@K lfC&$fXlyd~~<#He;s]&<:y}j1+"mj Z`M/J–Z(;3??O Sptrn : ;1䵓C9[`Jz -6v|l I":%p077dzx⠗+Mk*vUQǏ^bֆEݎnx<޽S]/TǖK q(͆%x8*9"ҵB!//-< uuu˺֧R)z{{qڌD$!o[pM{\ 2b0fVTl63"P!̛șQ `/HDJazzz8lF4a_TZEqq1`>BG.eD &3d%l6؈,ƽ^ǻ7wT&jk=޽{Njn  ^(RDc1NJذIYW6-|g'|;wM3iw^ػW<νNoB!=:r6ڻU䕹h.3aWՒLxWXRRQQᠿ>_JHujg_wNrt٩\^zb*c>n~a{[NC2B.MʖNaa!}љ|k,UTa޽RUUpNss3LMMe0p:J <3;+ҕv8x-jjĂ~ #YY >7"x؛q\Kn2% &&&zq%F#:WU-oqÿ[f4VdWF2 GeM J2#^N-Al6FwW'T/gO;SM7UdJf<aj6ƷEͼc&zs.-6, kqs.i2L_o?ǟ֜QQZ K/aۗ->X,jkk'*8̐eO{ރ0=-m4|>͛O?ß)<(:-]-IB(,%|x[8RZJAAYd2I4%4;KclV]$rUU"%iߙp)\u+gqy"$~qՠRE6අzȪYf1tBzDy-yk5Ef6W`k(+HX.NP1"7SԔE(RJ`hhl{l'{:**s1樭D&Hmmˎ`0H$vNz8ݣa$boAiԧN D>yQ$R,vVZ-xv 룧~C'zm[@[C$? 7b?C!_Қ9W<{zDCl B!8Ql8J^r91:QO̝Ofh4f ^8:z;֙LD㩜4K,ZܯR2у賟MYzG=qԝ0\v}|ˍtЀæM?@$!c=( --HhN8g߀[Ç? o%͙uJONp\{-|k05PXxʞ, Z[1FwTjD%W'`+4; &_ӹjyĢOΊL*DmZ-٥6vD jE"! jEڐ#_it:G?B$&GT^--G61}xW*)Dxۢ&l/]hh)U#"簾>!>Dm?c Tnf)>vfzz+A(rw4Heȴ<)gwj Ǹ״Sh502rr9FC}}/**fll V{A,ˌktp" 22}oDB,@A_ѣ"'[ cdC:h?7{I|(,q5'7 4D4l}.ZY"%}nGFڝQsS/}υH!R)1B嗿~peUj J^HtNRZZM۷\M,1RVxl2DAXdrBUL166F7Ħ*UTrl^4$LOOsW~NPHIG zϩ-z"߄+ UB [1u4*"2.,Q_H33/((UV&g_k4VE!n]{<""q(Q>Ha d}>O *@T^>> -m᠔B>[V҄/8ll&rIׅDQ]$xlhRug-:ؽgjq1Ʉ^ep" 5M$QZ$ GenF$ K/='Nڂ ɤ"{+N'"H,Y ~#ȷDMxzXݷ޽B,)|`?s[n3jZV=$q9u,7$!ԎǶDչwȍʻUF7wt2OCcc^keÙي E=WZ`Ĩ (PyQK+l)DsXW;vsJw>ru]¦DSO]3S2EXg~TD/^{MtyVhsرv뮻p_Gʢy`Ƥ:j-&"?~!2ł_]sY|~x,h4BD"졇D/G)!TQYTStS_߀;Grfrݳh$Trbl E/rt:&iAqGi߸5*L<`OEoRzy#Ma?x)u]b~_#ˋ/ 8|X'  Z^yE,sA:K].Q8 bo6 N|9UyĜdYpRWʚG *kvwVL'5mF"!"6~;\avvV(V^#ᠲp8̨÷>|m57m-cKu!xFs&@= YTTMQQ===R5zx<\.wy־t  } sݤ]Bb'g?Cҩdj=R]}D:pZ Es$ICw_ Ѩ`m] UԗZ{6qmk1뜴-pzqԗZ$T Z457SSSëLɔLDtAmSGѣh4JmFTT*++IR̞_KDQZ-W "2T=^~Y,x?DMF[,RcJDѦgWG|dV+\.a}ʪ 5դb̒fT9$`3(+\\$~{g hU•$9v2ȏ_Eh"l큳e~~D2E}6UywP\\Laa!y z}~#3=&RLmnd^/v{leYX~Bl$zXʉ|ՊbtID֢H#I"@DA.ԥ]eC 5Ф(·w~.wSih,ZG1::~Z$fHRuSǙLz-ewWQQIJkPH4EH4x?WQ  ^Di$I٥㪬O5)wL WbCg{쩩)b7l)kw0KЛz\R\\Lg{G+1㛋%L25V'sj@t2:!8/m&E&Z+ދѣGmԂ$}Ex.6cvu4.zo\6%$Ay$ k_|EC>*kUIZ&#P]l=v$x^ _[Z4$Qvsq(13??,y;N7 Ē<81M`41< zƧy8f6aml 4 7x#<444 {H$BQ>\ N!yD=oaa~@:%W^Ȳx7\#MI^xA-HDeMbkHR-vuORR)Y87EIbT^kaf3v;ӡ8Dp8͖'w_yH4H(E˵hiYD"LNN ^91vձ0oE]}/O?4IjR)BmmmyѨ7!IΨG{DG>`<~Z|rP/q^ 6*@68k#rgvHcs&N3lB<8z|>gB0dpZ7o+>Йشi3555X,EE$Ifٴy3Zo=˳GWK[o%pqV133C*!/ST!e1nEE/g^Gr睢3b||O|1DʆF Y ݻ)))A|e8poNQQw}7PF)q޹Cwv80ڵq6ܹsM15,FM6f=B 6Wgc5ę7%}*HIPFL,/|2djj'xؽ?z_Ss<TUU-+^$n7%%%a&֞CF~l޽={PWWܚrH+rՎ(MM#=Rc޽W%?O }inu*J5ەeniak!d`D"*րdI]]333p4 }K_l6/_ve477~6. sssf,sQͤ[h^ϟ8| 155x$I$ "sɔ>pe=5.e^h4RSSb^If.W8x^G%%%=tc6q\9=f*eszb05PVl]Fzsw~G4:4ER~p% pS,?O"=8d! *5.H$xǹ*֭nGY)k.otRZhbp~  133í&Zig6Tt>HG`,F. ` @oooљ4,3::f׷(EN3DX˞+퇷Ƿ5/w ¸3$j<pDF%7Q rޘL&eʜ'c[A( O7E]Qv/=AT64j$Dضmymݺg}X,_uV#_u3::>J9d2 5. ׶fyyC :Z|[\?'MqqEPgP(/YG0^91jGfnq5| ʻ:zyh48NL9.r]Z3Qk͛a`@G)ሕq"ʐkNV2+Iq26FqSӲ{: ؘY^`0HHWg?}C(*fa2jȲL4% QQdBsE7X" nſEEN% kwu_|ʦM2RX.sssHg"I/|v ڲ2Sf{D 3VeHG2~/X{ ;:H{&UUu+|q IDAToq Wh9>?/٢ў:P2/SeM 5g?Yv3|##Hf_޻h<FS]S~D"IĢm7<F[}3aT6qX |`x,9eehiYx nUta"N}'oߞ7$IWRQdW9~(R\./(,;tPZ %%B$H/һ.ؘ]&QMD܏~$>&>Aq, *@rN??0 )Y02KHl/[5:LPDtlX:%,6d괃A-]SCAP\juNR Ywjj.{t,/5d>l&-{ʍ@ɲ-M̲rp_D"M%SSS~nf˰M$< QVVFEEŒzJJJp\LNNviW]%R{~cO1EjU|%"J2?/۶MP55e,v:Bdr>1Ǐg\Dj~8vL]pynbV+ qG$)/dvVkVX*C '$3{رcol6|+;`~~/}Kj3 qǒŽ' "E]{*(2^/VYHx2Ij"Ix{n'RYYyF*++9y8HhYl6[ܿ"HK,Olj\SX,x:ZUe[y]GO>$hU)8O?Js122BcccV:$QRRfk_ ~EooiQ'PT axϮJ>?VcDzgbCc W_-;xNzøV!P.N_: *3J8\~g.TXU3>O344>ȃ>$I PSSCkk+/ws:nocc2p8K1 Jx|U7&455eu\^O}C6׺ho_~89~?mmm&>dY122BMM ;w\8`xx8kڙf_[n`;R8!kn`Ť+[Hc7߄/QTJD0M&7wE)~Me:73#e2 $)[ȯQ?e000oγ>lGgγ$RS\j1Lx<<GލQ~N"Qp'EkwwMMvj*))ADrտXR" 059l(Dki[[96߯tffr57*ѣnf7 rIvڵƝ?0wZ߿@.G@aa!zeDi``?:()E^2Vi**೟ÇEؘ+1ı6oK.xʆF *yg<g44,TTT^ ~b YC]ȲCo`ZqvZEESOYFYV/7= uZ6;&면X,;0oLciص[/-IC.?]GWǃ٠aV**A4#DkR&h4()){n-c1177Պν΅԰cD*hؘ#MQhWiFJ񣢲Q+)Y/ W,l6۶mC*^7a2(//Y<#'c|jnRʠo!MMMZM `n-lx%HNZ$6%c>ޙ)>yS=-x{099b7==4V퀮8$E53h4B__O={(jhhIF, &AeHu蓱QDDhnMB\U\@-)s0giiiY5乬n~$s$D^G*dX8~K1M'{uQqe0pݔ088?>PNNxfykN] `hp+\ӲU馉YZFN8 /]wu q,UIlx^eܼYR?k5GN‚w`@LE""E qQQ* Fy3Lb=H4&'zSP`_qjQ@cU6}.t vy&S2~[Z.7 470888٨eKʝ䷮!MpJϿ,LLL񌰹}Ov*0 kjZ[[|} șcdbtt4>KruoA R+)>@YxexQ;ލ(?.TbtZ E4`2 E??|yG% O-˗~P^^N$azz:lӦ$zyN叽5Jxe4$QWWG2?^|mJj$>uS=S.~x+9->6mZ#$Ae(YE%KT5GO9 RU]* ҖZ(5j!˲">@0GF/ѠωfctfD**͛7 _Hssh.;XV&155,ˊ5=RIa,##04f^G1؁>1BF{:D *Ya/65FVBV8y$& -{iFN̨l !312̄F sxx8gEJb0cll ̌b)HziiiYEk_g)Y#\Eu/r֘QAFt9G!lTQ 8FXd;]+frr2ŋ,ˌ100MND*ڏlOzXBH\GQR\gAAAQN'$)2CCCh4 f^ _ﴵl0 χenSK^LTQɊQq\ֹt-qa&'CCC0;;eOg FGG >~C z]uHٞFzQywzzTUU+׿rB[l!L׷l"2'Ok.,³_(FEW6D"_}l+ " 1 }|De`dLѰf$7"]P(hf]u\P/Ɉ.z]p8sss .l6J3Osp Ćsn谪wl6+c, &In|IzzzϪN,L244 ]wuuuL0xV0O?/~5׋t|nI(枙RId*@T"8G:~gFGGn;Q֚B6We ]ANNj+ӽ..m-Fm6ԕXƼDQ ;opMZ,yYwi$aZ;g㔕QZZI*bzz1R7|3 +<(0?.""r~ȤG"̈HE.6z1JD%+R)LoI/7W_䯑$6UJ%S2$f64hJ,/P" 2D˂i/s<rt ɴPr{ؿ?ǎc||l6& Fpb10@D"A}}=W_}5źmg+ʟ{N,m6h7#S2 8 P^wb/JX'M*kUdդ# %WB#7x< ͵k I?cA NMPZZh3BIan.̏_lYw;**255h\i,+p5\CGG=== 2::zVXȖ-[hiiYz9W"ZM,DI>Y+`rRD6+D%+ MG&H&몘2جV< K;륲Rѱ5 nwtzfif*nBR;Y,::: J1??O<GbX?}'2WI$N x,УQضmɗR)fgg-n_^b*kWɊ ,nDedYFѐL&Fvгhȍlt~9LrI9On,mb M9  O2\בX,Я#Hxz($aXp:n {<#GYć, ߀LD#N$ {*yj$QRRBcc#---9ivQJVT8L\Urknn8_3O"H ϒ* ˖vIa/ɔvu+**k,Jr8Nx N8A<`0`2 g߾}AKKR8^wEbk33/%,_I7i!tna!Y7`nnNyy9e!1H0??O07K/e۶m]T$qRc6N!^(sGy2_bLᓭ$)ڊ{9yl6FGSxU)Fep@XFТȌAKCv;HYLMM144d`0PUU59277ccc\.1LY+p1n?mMͻO|x&(-xpPWWwnx<cppݻw^ gxxUI/(//nݍA*u,&0>يx<՘?|VQnt}j6SGx3h$h(qMK1W59ѫbDEaH$D"7l---9rܞ0 jkkHDAAB!xyއ|="n)Nnu)UU9"MT +JzlO<={" ɪ,2s}-?|yUt\LMM1 $!$I¡0MeyGx)0x,g&"ANu)Yi}ʸəU0?\vرY_ZfG{S]]ùwm$R0LMM@ZԢiCA2%?V+F۷NSSMʹoKhll$4G;G2&5޾)^&&Fxg#Tk(%\nJR"l޼y!Kp7{VlHRB!Eh'ofKɤporv`tw/?a"b00'O‡>_"Ht:)Rf***8z(~_ET"vV 2>| +hZZZ[&OL~1L(F2%gc#^NhJp8hjj_(>|#%Ǜz1Y,47,Bqq1MMӽ'}ŰX,\}LNNx$L200f.;9mF"+4h4R|`0PQQAWWBʒi-[Z|2 s1a|\q֊26'N/j:$ cⱯ~.b}JKK1 9rD1UQY1.K%o"`hh(Ǔ$J:::鱖b9L1 742<ޜc)b"ğiGRd6ֶvzE_'2?xqYиty$G7ehooeo$ z{{޽{%%%u]X,:;;".9n,cddcX˅N/kE R\ 5#G8rD_H?i`"mizzz(((PFtח3kր,Yy{>B,!rp:rE 2_\Z?l/fZ͑.h.Z- g=(:4Y篏0Ő$믿Y!SYYqYeo}I!={8z(brrՊjd2hHRDQ0PNGMM CCCTUU)Q́ꪥ_f}UY !ˢ{fhm='76mR55q+]$ x#{8"0=]^GYY~k"UG *"ѫCt\Un\Y`"`d$h{AM:Ej)//grޮIvwd"B bu,r8q8>'n:;qv[F)3ADtvvrd8%u/UQIhصk.}133CII ŋ ʲL0(++ǫ6K͛dhh |>Ƃ(a444b.Zgb .Sn Hfu_ju8b39`^Qc +ӂLD4erRu4^u']8lz֯Μ5iUl@T5)Y?_䭾i(V*Q ###466^b(2333zFSI-2HQX`eQ^OaA!fVUXZf=pXB%TVyo[/-_29e`"x:jjfϴ*@TC$nJ]]QL&FV,qHRq 7ԔujN&MM%IZycb4sh`_msX6%>a[/?PeY,#KLq(G,&j@~^yn"U:]vW/Qɚgw:FCAÉ'l:XVF#$L&D"B!3D1k)Id|$ZS  %1V)$Ilsd^3碸(ju!JE?^P@>l v;_=W_}5###x^hVKqq1TUUt:It4X,qJX X" +ߍWK|B/Rτ}qYDžBIӢ6bsqbb"gcUdѷG)++[S;֨C*ʡ{Գը㲺Bo/tV#qmdֻfd`<d)j.捞iBPxE$ EV#Iy&cNj, ^o|⼺%0 444(|hڜwSS'"ݕZU<(gooϽIiZ6J D"xeFÕW^*kUd/Š+j$(*"Onm""̄bē2f#.Ⴛ5Uf*f|9 ֻeT9͌z<ngbbp@p>.l_: QjrK%ŢvbZZd59"`Gd&`~~^Q,T*{J~QmxsH(ⳟ,n̥^~|Ok蟡lU/N8`:CP]la[TU@Iq$I~i9``pw;=+s<}{4wwllSC'4BH`%M!ل$N6 K a )u齨4i952Ei4󻮹 'A sa38'f=dR8LNq1EXl\d2ML&=D-jkO?>#X#LF3HfVT*O> 苓\E.n&<_k׮ߎM6Ł1AV[R]&v`\ϥXSmD]}982"-ZU }LU$*BcJdާT_bV-хib2H$O8ccc(--z sENɝ$++sW|$j ㏣n=˅ ,, kaشinV\>|[­ޚU谎CVkάTەi8a|Jtuv b\[[ ?==D"(((=#ėD,B48HAH"5ُX\F"(( Q_?E`0<3$N.9"@Ν'PEE@s3P4 ˡpgry:P$ЛrNK;։ũL2Q#7u"jp>zhE0Jט!۷`p^UR˜/~%zQS(G/:\h!044H^W/; qnuQ &E:eZr8F$A(¸ ߇ںaR|!1(4(] )чaU[ 9u41Xl9N7sV~\rԕӭ[}bJj%łC`JǩJZmfw(F8?1ݍ8Coo/". ?Ad51x ˕S3b;SKG$; ÁFdB*Ys aO({H jdSX _;(JJJx=7j 5b :3;6 Jjs+ntb1^Gk׮jEgg'###Z83Q8x8x0 oXLCCԊuyyf8j= nZ00FFi`i)E? ̤`ZH( E,+/:;;QSSr2˲ƥ^sQ" '/~J w8xXQeF*3sxu5k˅OW4{h_ޮQ塺d,HPQQ>3qdЋ:(**⭗< ;>TSuH$K k76m:Ŭͷ8WB@"ફ/6TVVʲ,a۱tRqSn'/F6\0 Mŀeˀ+I|8[Κ8D!6l-[҂2~BKrb.N:I!L&Ӕs$!i7b;t)5)1ܝhZ>z8"#o`0@̉ ۃ:ߔK. <>??Fxlkn\Wx,ܘD/Q-#vv@"%~E6Zp۷k֬3Jx]((J\عs':;;a4QXX8cKV"j" aXb P5u`Ov}Lhv;WItEvX,|xZ|Sx^. yyy+dX\E bŊشiX=ae˖שMcoA(ʹX45!Bbؤgd…^Z!4B oO<rO=JJJ~ydƪJEIKԯs x`.f;u:si~E~0 z֢ݰZ' 'D0 hjjBmmvW@_ %DRCPSCfko(߇f,:5N{/Ӣt;0 PVV@ ۍ`0 a0PTT4 @2ĕW^. w}7^/jjji&lݺdL"Kxeb|qt:OT$on尳فWYơPTLSh4@ nzIol6XÐ|R\({j5E"+*7pQTVVξ+zp`hh++ IkN\Dd0c^YѴ~K`6vt$4˒z زXwxvDBUUD"`rĝH(J.?9p8~'g ٶڲjkLfSv>07&Gn* 5 ) p Ad͛7.cttMMMxgN[h\ﶹׇں:^dۍחΞ@oF݇|uz=&S%1( r P+x{O_:>8B0z{{ۋzh_Nq|ǥ-i})lj|E|>E jx:)C T ev䋋ifE_p7 lH$~<<ųVWLhF$zEq:' c: m3ۊDO }7en"@B DVcƍظq| o(db|*v@D"1 a0Fp\pm3W.ƚ/n?! I'T9gF4Ś& R{`H"FhkkC8F{{;hqDiA0FI_PSJQJ1gl ZHi$:voHol?JKDD,Z=eq !bSougC=$c: TX:ޏH$H4yL\jFkhKT*ŲeD&LV2Qźr4h='8@qqqʙ0uUO@ mZZJR_82 -%2N7ybGuu٩h$txbzu:lTy}LTbxbx],^/>;N-gB dX xi2i"a.ڕ+JOd2 \GL|@7ߜDz$^xZw9{@k+Ud,[8cR%N 5 xxy' IDAT*V;}.FG)Iwt>oG PiVҬg05A8T"I#CF6/8Ga/G. U5K/=Qy&şjg&D"JKގ/Ϟq1 "r(6*qσv#???[R)R)t:JJJt:wC+kPFF@ )Zc#ˣo~REEQ{-?+dbTRyy|FJw[3 n71"L !ߏ~·efz=x`C=,~HD" h9*ˇ9CC4$1hݳHPT47o1NϽ2 R)}Hݽ^X$4oTRQjg? y6HBme69xַ|_[G,&"SH{\@"EnƠ+Œtp8K~0_px |AZk0',8|(XfHa`0Ƞq;a@|0@&l)^RmQ"P[WfK'~&r@l ,2:;i':Uc\.VwŨ'1do,(Ci$S#q.:eIxwT90hQ~|V@B . EL06U5R3>wozx _V28S ch4_Ҏ'BCccҳ-0W'K0nǕ x5;udvJb]- m@oAZCC,A&Ţo^BIi)ftFF@---M"feLXZoW֞^2ÑzQ*0 O.jy ynK8GT"#VKxJL0HFwߥ֢[%[GcEE34yoF` aPRB'? 6@ 1zHںճ ^(嘑cgbcjJx ( <jVd(RY|Bb.<: @?0ƦT*V^5xays $ JKp-CXR:E03*Ui;I"j1faO"?=&'O6@mj0E^I.M~z?VW E|au=zZ֯9 @ Oo"SF!aҶs<߾ '¢bX,)Cd2d2zp w~b.KrmSә0 0 68(>~0G 4j5ލ.²$ 04Ds4^v52o͜~pedI+EP(瞣-LV<O~B!Ibuժ=f4Jd|9֬ɮi_ ^,@|F8***Sq8. [ǃ0PB+0y &%5^$I'SjecY A]v482V`4x~ZȕN|5۱N8>I`vI^2WY HC\pxrpkQek[6R|P<^)GP߯w/rA^ , 8ëltяð(((d\.? ,<##X/^Tɋq9t"!++SaI#[ _ à!3}555Q-CMaMMq?  󹓜* OSQA?|8֢:l4;|DLy¬Sۚ^je&[DTٿL!Xwi^ʳR|y9 @ Af'ҖeY8G8Nl62) b XE,A q0鳪aŽش|>H"526.I80D{{OnYKk"Z͆xZId 7 twm|U>]{@dž3Ldڿ%ԐQ^?>_ڵ B 7\J@9uЊHUE$bQ֪N8ݺF C^tpxBcjcI<[ψ7G(..Iɜxh*pGUT0 ***rqj_-j3$@8mw:p~54-׬|۶VK$$D"zqO,] y'p鉇h> _H[Z;/c X;PYCD"㜆|Yp1bE4B9ƨ/8)y;7%$v00W[&rJih &S>*IӮј BH$d |[QWЎv {w}!\-)j^?9d_IH=0HX$tPȔꫂYD`QE!%wA˼x<.h"\[A;| 9*5Z-T"8Ch9VTոam1q>rØy68C(So$Z31e.ihf###8: ,~} Kdb--;:͟&!yS*+w3!$yy$D>i7fHrM&UTP LU\L%[]#LmXXD,DI8ՅH8>Q_܁o&S>ʫ-ӶBq݆u:>}^LvO( K3CCCu}%f/RekvK(J(2 D$XnɅlə˗.$LPPuhfK_z0l#>*hp%8Q%zl%|(H|$pZM{B*F(O꾩p, ׋~7-_T&GSӒY= `0@raoAu]ZSЇGi1ޏL.9fn-gCtuJlnIJ  ,\h׼ndz|)lBQsɞBڕ߹}+S|$*U*H$dXO'yF&lh "D ,h(<j5)@1 |T*tvvAy ,AР@mEHfB4 X[L-cņEdyfT*UNIR"pʹy[ng? 4N_}R}bTy*';l0 2bVJoO-iF"k"{S]MӾlvL}FNvl@8= EQ#òr=vL&nm6J ,*X !З0PTPT(((څkՅϩL`0VsIǃX< a'ZfWau:թ=|xxM8BM (#!3DH rZ'(-h7h_O@e[%fS0 5$Q;Ŝ7P`a Euk/pÜma,6wC&6 /<?j 5XRCI \Ǔ88vTYԸvB:9ldX,ߏyM hGY.֎DrҼU'tNu:җNd;M,Ne)mlÆ> ͨQaVe  Xh==4%f@&maPTTrx#+~?z(qp(|>?;H04ϟ9OpSDHh?< <؉WP09#c.L5\ ;nnRm- ēˁ6[ ~E}nqD CD`QqTՙD"h\᫗UC#c3ͨoUP[[+C G$S`0~Ò,xxfw?GEѱ|m:@>݆30st΃d ͒p=.B<~|!> X`ŹlL%>Fj55ꀿz{I)Ipna A,*bJfKalll m-xzidFEI{?2ezu;6Kn[$ z9>s^rQ7Mʕ+qg@SK\}V+";ShIFG$xs3חLb&o w:眩`~Xz/{y6{ D`ѡk!h¨ FqĠq,- VNw|dFJNòezlBPI ru"8A/hT"BZ* ZqЎo%X,1z:f kQdHg?KЭV2֨N'7mnnߩ:P@"-zc]K?4@qxk& 8_~'D"spV /}4ZLX,˲F"U5XYXlv=0$E&\ ߏ6455<bŊDV<G__㣮Q_(J ZB9D"XE8!tB!1&>===XSm 3tV+_X[n ɔ9@N"jn;0^}#%-d<,h/Hqq1\.'oG^ci5+W<F͡D$HGaa!ZZFW -XɠRQ֣/@ jqz7n)@ f%Mn"29DɈ^!u\ܱ {!ٟ2W~PӥTꢈlN>Z/sM yxSpXD pDYgHtp8<Џnj `4gJX,V=_~T VaweAaEEE )X /ӹ気N_:U;N{"S8al|Hڤ} l6jÙi)^J钛7&tiz瘗*utf>`~jw逯} x5LR\:;Ix~;Krmc*ߑ*Ia0/@O9"^x!V^{`q>oA3)u\?HRYY2j52 tLV5vvŊpF-GMJhv?jmJ6ݢ9LN5¹om-};@?U^jc2p秬lE2;~DT),z Im3UC8\QjqJP%x|3>N#C@8*-TVVv\.|r}(J+#֬Y:N`0RhZ^>rQoXvӔ^*@:GͲwiVjǼZМJ+>6z~[$"Sl3EV,?⮼?!ysz^|@-|:"*% T'Q$>pgxR]\tQfg,X& X,W^y_N^W^^./~ƃ1,ʲpdc|!Z IDATIq5 .T%Qttt@C7@VCV9@ V$!gq$u$(6m{@DzrǑGa|vγ^~9zTxNmQP@)IdL\ CGI@닐HhBmtTJłldkyۡ{z} seYb1,+| v `!Htٲ9UR}oYT"O@x!hX\< =6"|"8|ĈXL' є8繊Xwd+ U2Im|s]T_O?79/nT lBⱸx \.utΥ# ".a坚71 q9L&ͲĹM- 2] B8Р0h,ѡD 'l꜒P(f/sE xBRrBͱgpd~)//K$n%#OaVndϴ2v,[1T(|''JT CB0"tpiWXN'~?x-, ysq ]v/NrN?\ cw=E Vv2]xۃBmKW\A&_^}ڶS[\L-^7ݔvKáCсp8 a 96TSӡ K,I;S`aq ͏SVVұM.(q7  nqt %Ycw}>nR~lXDquaUi]{f1xՉƌA!,/ϭ6m¦MNmll 4ٵUU C|pU?ۀ;0e 6[#$g2_ y'!Ӑy&ɶP"!ՒGeBeY8pH$dhJ:0::?矏BH+),,ŌJ:>7n܈իWgra 3`.|x7!Jm8ѕbP>2^YGن6BmvZsf}f^/\G @3+J%>8n'q9 ICU"NYYRD"z hhpH[n0 QXX8O,CCףغu+V\uD[V+ Hpb7$o&ny<$O#G8(VBSSo8L|~I eDnԖda;^?`A-E(Ƒ#GЗ]F˅(T,\Rb^G}|n n޼55/Cy `FF(5Xݷ?D|0wO)Q2pgE0*A,kÁz$"QSSRsD_{mB|u]מ .oxطj\N^+"9v?R@fHQEo TnsFqՊ6 4EKC"rr- vލ2T9O H$}իWc۶m<êJ21v#WllqQRZm(|>XE$A؎1˱X_֕obtxx|λـʊZAcﷻ[PhZRTT*EyyK ETq^G~C۔ rkei~D°k0;|pYq`G#c?kgN'}]8( zd v만NRx<߿{./r" X*%0iI $"\/~4 ٜ3 0;JF 1Zr"QHR*6 QąXWVWY6<!>f",\@8v"??I l6d2f4ǡ X ?u) 3 ,2fLARt/..XfJq2"EV:\\u /s>DXu5}N8n8Css3BZt)sZ 4ywD"lFkk+֭['.2E`^ cx݅=+&P_<1jнtyoEoO`Y,iaq k pw o8680|E")//IlB\f ./4"O5pjjkVt)D(Nca0 +*ݍKD@ y+rDǗ=؎8n_e=vz}ׯ'a`R\^דA{κgh޽8p PRRτdjy?!}VD:JJˠT*'h@ɳJ8EJ*᫗U[ՅDH$AGG;Үqa,Q gTqFh}0BQ{hxmϒǻ.\x;8<{**g fi4x^BUUU5j\|N.j[4sMU//vp=έVt:\v}o7NEB.jj6g?~8yG(--EJhokEXcpXe/HLv +Q_9 xJY& (1wPTucd2k(hb cԗ9R X,7GRkߴcO>tIĄ͛,YϹw[a}^`9ڵg> MzZ2BA_}gǃ{fyGB! [q"KmG87$=[T _]4S늴xF<--Ga2壠`}"nvP_P؆8#S+B@MmZiw?wǡ6}%ǙZ XNuET]}J },{T*Eiii+Y$ɢv:"XzK'q ӑLΎ|iw R Z=aNH>Ca52|8?F GSSÜwɕJ%JK˰6䣾ą?G Ga^,BC_ȋ/9W*9:G_n=,xYgG 9X ?_ұ^/e|YJ066>TTT{pS#H )-@Ѡ~ 4LoEbU!.Zf~=p#g!cyh*A,bǰf(^؊O 1H_,bFa.ʜ!1U?FhL ̆ Gk+snhH~TJFz']pP45 <+Qr$Bv ޽`DBekkF t:]NU8C0DMM|ΧL gvL낾~Z͜*5Sa00 xN]LX:=FXoWq.h L"U5ws*D{[FGG8,СCHsy;tτ aPS[x6nu g |dln&[FFrѮk+#ZZ.l>|s-8rPćE !oNۻk_࿣yeɒg;! HH<\J[PRv}2^hK444PH[撐9 [y9q,ے}t$Dy묳Z W~x_Gn.O9F$]wmlQ(عߖw CuwOPhL(  ȴdzUDVØnuX5_=M=n荙:-z\X5f n>o8EoUӪjQ'buO0fCNfd%lP[*peoFɮzsK ⮲5=mrhmmkB.`eAryyP(cǀ2&cO~ؿ 22 emq_u:k|}o!>\LRK-Y{ɭ @H`"v0D0tB!G6$ePB&/}+ him9`(!Oخ|ŻMGR94kc& !փL 2z1mCJȪš}A ,.΀ÉN'|'jPPX9$zQ2'tTAPTT~䙵(L$E9 ENZ#Z1* f!uuR{_:v$+QR"Nt;+dyW1u].㮩?"c &~T1fkNǃ~\2q(PG,z0WDzZ)m:r(}LVVdtuɸ\.J)2Y?TiZ,]廍h 3M ,xj,-6uu)oFjEEE)a Bj5~c "+*+y&r&HTJ%46 |+/Ǚڀ[o --|Ubb+QYɿ㷕@@\?oNŬ@p'WzbsjJ T*!HQd hiiAZZϟ?7'5?I:91M&}P,2N_]b\IʡP(V+s (ԡ#dXX,d|%B 2 %%%P5xFBѯiUrܶ/IF}}=:zQ***PTT,X+"L"qepo /jϙv/ҥ|c~ |3ߚTQ23O|.؝+ϦRݻG}b2@,YY|`Ll~?_YZ@c$ɐwD`k֬5egڂE&79n$:V`GQD݃AOarP&рe.Ap]~FF(ϧVaf/{phTrW?|މI̓dS\Ǐ#=Xx _6hF[}b8iii0{Ky/-yB2E >"|OF27y¼}FC_M*/r|+yKx^L0=8~brxnj"77---`,}A477chhk֬I Nd(!ҩ1MH\שc@CA; ?F VV9w0c|;W"C\n7K,FgZݍWtkfcϡnfQJ#A"3ӂ=fa6/ ȢCEpgPosB-f2Q?H9Ndـ|J~a!Jt]DZUQUZZ*̑#|UI4drŖaOoT85_v+())'vO`I4*\ףP1iQB&F(F BqN^\ 2-VY㞨B\,lh 4j2jzGۇp^%iY.hrU)jx_w aAal vD0$VJiii` hu:?HL dG >G%?N%?$ t 91\io%z'7<bJW_QkHKKCWW^Q:C8Gqq1.곀D3 Iee' %V}$=zp`(z0Z@yQXX8Ur\.z2c =ݨ5jBx>\5U  B8 VZDMGlI}N߅윜5x"ޡĕ$3@OOJXw;tDR]͓ދ|x:nwon~EAr< L0Ls=VN&=z FYY6oތuQ1KȬBRAIbJ* Veb\ Gvx!e,[6v.W^Z9c]~b\JBG?xPZF5VhzJӣ-J5hfS0 IDATL.15s jT!I;O{%#B\6(|N'MP)(stʭ[S <^W{ hhh@KK 4M¶F.|I"APݍ%YZZ|D? P*0gZ0dNKxކfT W^MOKK1=ΰLBW.7Ohhhbh-1 ݎ^%^)J%ܓVtOc aƠBg1YJʕR2&BWV\N\x+_W_W@Z o&PUUTOBT,A dBڡR*`JZF^^A'^ uJW '^(q^GyEŘۃ  ׇayF R¡ ^/0 k%`I&!CePB[ 3dEhFf:ql >I> %<GySC2r{8̛-{ɬY|[ )jRpW׿5%sB!3$YP()S1ÁX J|scŘJF@a?|0T ҵ p_L VFWW'gEϐŒA"+=e9e_LX SK r,ex-D|v|$Ft;BW:U3jyB VVgn6>Og韦6 h4 p:4X,ƴ*Oqatz1gNv"p8ĕ ;w =ȃ^`ޫG8ECUZZ?Lp7L Ʉ_ǦMfDv? @ȸ''*W@,\іdШUX5׌kϝSf~N# i-.R9sJ%JJJ`0cs *r 5ia`*}8FO K3{ooף@eUur?>Doo/n8`$b˗DD6C|r9g/ɓΝHǁ_EBl6L&WANǮ]p5׈$Շ$2Pȅ*b! LT50(/QV+2ǂ s0tpfs޿=(-+?ϛЈ hW+ xSD#qySAglU'po^VW_偍V\ χ.Q;Z |>/cWV$3  d\2A@U?ұf3 c vu(pf{XwNIT`ho6%E.L 7TƾZy?f+VfnؓQT(..FWWN8!sģLhiqCCClJD0q{Gz`cNq$RχL[O]EsA}]Hx444(r/lPUge=y˅z477cyie{ؐoJG7Qc@k+og7Er!nxOoyW5~O?/^J477GՈtL&۷pX%wvY922-g[=Ѹ\.00:U`(=r8rXi]?\>PjTr|kS 8q6-I^P ''ZC=zF& zj:at:Z-1gΜ< dR`PYU yWRD8Fݎ<=,pBVdt1R V:H םW 2>@GG*++*ӛ*twwc8uJUʘLؽ=ZȈ^_BpzʑA&nP(lxjO|x<mŸ`=Bꡒ_6R5vPg9ɪĿ|B!Cc@#O{8SIKx1̦.CCűb.b_( ̝;s΅E__-RTy.IڬLbg[*Ps8=d2WT@1W2]7?FP!m W`yZD^/k[ m<>uƆru32Ial&$@]pp(pw}=Oq8.?3:0fżƍ= JQ2||{4 P]] јs@dKfelV=2/ׄ:aZ>c*Er1-C)0"+,=mYqjDM=AgggͶ2]0h֕U( Oi9sCl6~<l }  %#vJOaއbR vKry*Ut:853S;#n PT0Lذa>vZd2a˖-hjjx2TxJvi ^~=QFtvv| B"oѶ+9:}i"/ƼyD9X0|>ֆG +Mx⺹|$ͣgxUWxPiPZ K8g~Ģ lI |kڷy4*5>0իy~8+3b'O^fC=w}/2<V^ckjjj*Aر۷oGmm-."%JTAr32ok\5̪ё!Nh4 ǖPH:r1Q96Cc0,"Q( M@/#GP__.47W/%ͣqPNL` ?S4yB8|=}ădur^xbv'"E$6/4R[nbiWx hZڵ ˗/GEEmۆ{Nұ' (ԡ wu>iRDvvvG71Ai'+5EgPH_Rmr>澚^_ՏT B;ז"7C4%5J5G(>]/<~VAY?>,<8vTtl|#bzdxbюIV@ZFUU#WAڵ [l"^$M nQ6~P#YȉM`Iqcdt[U2oCG::!AW0D[[[*r`7WEFXWDGhlv܉W] ?_Ursy)XDrŖ1p$zy.NSpe_.GoorV?yߖOt3iz{{!PZZ*qIrQ2M1"]B-Z4 .D}}}BI& r^n>?v v}Ҡ񠱱ǎcG100\dPa tK InúEYQ(vӓԽP-M(OմJhmΝ_GCN5C0O\2+1˫k54J`n/w}+<s:`]I*,gR*vMOuCIx?^UQQcǎӪZCss3T*/_.(I*Hwlݻ^ziL=p@ԫp?8^y$KU9|cC:=ΎcNtv >uu+C\b#r~‡5}Dzzz0~L%"{-߬ƥ.ĽquWj9¡خn[.7ċ:475lJ)c p8mb^M}CtM,4kL!۶ҽӝ*+]7-~_#\K^6yyHxG?xn}K `͚5عs'Q>őϭalڴ d6& 5kjbl'QQnݺ< }YsFW9ZA?Ai+7tn馘ƛjZl^Y+0cB*9'%YzdvQ61 k5*\Teh{p%vI ` tᎵ(FF  4zRU廍8P99Z"@Af woF9IIIꫯW_uCē$G5^exҸ~ @s3o6xW@/@mm^|{9/CC0;?ԪY]u|OIIMg'a?+PϿn]*]|1k<țj~ݼ…$j5\wyǏjEvv`}}}lP*ظq#c-@GgYLSO=[.Q(+sN?9kmmᅬx@!hJ %V=CCQLoBX;ɶR.kK=^vX,VL&hQ' 1|> ׋@ 5 pmMMVwNl6V1^Z LP(sj1?Rn8ɷf鑦Un~8X zE>]8Ҋ6:( 0qU⺕X5 \g8:.rdxq)3Q_D): l .7yW| V>?ThO<O+ <y`b1UjJԱ7Cǒ%sL eddkšCpA`0`0@ьtN|p\#UUUXb%9?}ׯǦM'֭[b \qxGxO ++9e&v~Q3k' kfcY {vlPePգ&@0\sLX$97jP_WF >Ñtf UOUűH&zw!77WfAcEdL ~lqm;\@^"3*/mT `012hTgySb>"Z?ݮ3{"-o}Aۚ<m,~m| txo- p-|djT(]wO>ɷ{O>:%t( ,[ ,@cc#ZZZ ;R łs碼|ڻ@HvAou zTUUa޽xquAP`͚5ضmf+3?:`PTT$1].ثʘ*|Bl^nz\s 0cգ"I5]OߪCc/(l)CN'Ǻ94^P4ҴS_etA?ڃVM;dxx}r^t& gawOJhcƠy;ޝ{?s~}:X^*޶A *{`Onk-ՙy`_:@}=5|@#IDAT7v\qml%JBuu5V@  1BKtȞ}͘RsІvjۃ0jO@:Ƿ̋#\ ~VoNbh1<< ݎ~u*|9p@yyy͝q~?jO /Cu&;癙M9cηY|;ŒH |&7Č6zu\|%Ļ ؾHnU RV6Si) N5TG Ɍfa679Fr)Z1Vx|s}'zw^V.'|GZ UJ(J~7c [YY%M^R+:Cnnn'H^ *yY)$z~IEWD9,6<̯OڞHf&m,+XLD R|ug?/$/ɷMȈ-|tv7 >Y2#e^WF jkQV^WY? ҌYϴ27 iqӉ~݁F5r (0k,iY.d?| ۅ¢>A7VChkmkJpNR3hX^jR݋@l\B[ = 05|p8LxA!%rY|DD=[tN ^ky? hjl^jI2|eyBѣq$px1^I뮻x]BrŤWk=ځdfZ`4F>cBN0,:|mc5 2F/ߎb^!4j X8  @ L9F_\,2ÝG4j5/&vǃ~n4:B̃+ Ǥ I @ȌRpㅅX5ߊwu ` QcM+K82EF)Ǻ9lQ6=h w?L@^|FEf]!]P\̿!(!3XN_xn-n{0 !$vB!@!ćB!d( QB!L25B!qBB!$|BPB!h׮]|B4)=B!d&` nBB!$ oBA2B2}B!B$C!B!D2B!B$C!B!D2B!B$C!B!D2B!B$C!B!D2B!B$C!B!D2B!B$C!B!D2B!B$C!B!D2Xn Rq9^cڵHKKd–-[$ !$uG !P'$/e] Rd:-_=Qo>ff0XFFۼy3kllxܞ={؍7JJJRdl>Td=˘ba '|rǧ~4 [d o'7^[{_ORmA(ͣy4h%FI688ya۶mػw/x ,Zwy'yQU cl߾袋'_xl6r[*tl4T믿4\n׿N|駒'㽾FNձd}:hͣEhy4Q(I1A8Kؽ{7|zGuXp!rT1fأI9rs΅L6Ww…GJ6Jv"gS}'>y4c&Nhr|rwiy4hͣ$ @D[nEc-_pmmmCYYZm+OFEET*Քo:cߺu#<2Td=x-Z*ˇ,X@LWg}c?Sd>immSz4Q_IűOͣ4ƋĢytͣR$^z%v0r93Ll>~߾}lڵL36o%5j*&ɘ cd2٘ǧcxxO{KK˨Ǧ؇rssZfK,s%/kX|T{n7[7$*ZjJ\Pq<Hct:;->lnnL&?s{rx>>?!eZFTP(|8066׫sp?:.C~nEWWHH$B(r10(Jy2 \|c1>b@2 ,,,Nx?0H__>=9?5_WELo:@AzzzBb[aI&R)BO?4W\хV^xۼ-/CZT*.c|j"+" *|AI$l $/|!%-6ᾼ߾~ úz"V(X_C:}1/M #oP.f``@.s\˗/}N3 :0 ~tm:pIB1aR)_bn$jDZ;p5bZUڃaNww7/===VK^,W~̵4zZVTbeel&>7מB1::ҶY^^T*/}$Mp*,~P`rrx2=Ő9.ߟ'[4K22”]#T_|̌֓rp>J?xo#:U9:23;hH$8=͵4GT5$b\.Nj/VKGdllׯ NS8&k5%6 L֧tX,xH\楗^\.+E{rL$7֏8M8z8"x9ӟt:̌{8"2ͻ mpDlE.n~~y{(ۋýP:Y=0<۷7?V}Oww7]]]-/ʐ*@*(w _Ng(9y\0LD}(ޝǖ>^"xwi77o$Dn"GΥVQV)r9of<=Wݢ_-Ż.k  m1]ETbmmÈ6I\8J<Ẅin7nG$appbH2g{ S fy3jOO1]f~+7e/y8Ż1>m*2wUU-UBwjJV݃c!i{gZ=5"&RCj(ĉ8~6G Sx=B-*_ɗѝ^qz//-4_Xk=00xlb *՚}o[= q*f~V\b7J0?5pmtHVlrk>K=VH$+X])2fߚНUq({U|_R.oRy)EL( n;prXAlB?lŻ]xb;W ujʶEZMlA*՚)" Ms^0u"VQ/[``󹆊w9/ŻHCjySCR=*ܻC>A٬Cy0Ż]zM3{2Ǧo_ ??zwK ,%Z.& ^G%NrC"dɚգ:`0H8|z*mϭ8bkkL&Co[ ƺa%ewH<իNDr9s#Y/ `kՅeqqRą v`px.xnؖ]K.r.b۷oߏmMcⰰ_cwX-\xD"ap0`'?_YG$NQːۆ߅#:t:<1^3n߾͏c\B8n~%[P%@Չj\\.GPT*RV՜D"탴VQO<*;;;ܹsVvcpͳregPr^w3j_>/6u_wsj54 A CPB "j&K4m(ޝɶ~cfgg1nw9/Żșܸq7|W 6^qZ=IJ2aIh7fUk#X8"e*ޝvs~~f0Coo~.9"gH$y&MR99!S_ P¾Sf;p ⇑(\(m8J~whλ6].#_Oz@S)"v Bܼyh4GWոs <!ldl]t\D"[Yl=?{GEgMxRgKo)=(Sߟ ry4OoJ/)"Fr {iwvx"t륲L~:or\Ļ\]:]Tx\B++<3bHK~:pŻ]tlnn1k(Oxc"w)ޛo{{J!'4 )O8:A.x9⽹1]e*aHڇt6wP(ENxor&OBm7tmxQNn\.?VxopŻ]twsi#͢x?]{[;(E"SJyz$M~NwaՃh^_Y<C:À՟nV7wNg.&y3 Ih.4]T~~\l6çns>SSSI0i>x?]{G;(HbwS)rf8.ӏkxpŻ)[L.r*N}'o)Onޑw;R]T lopScvxo1Żȩv)4#x0%;:AnGw#>;vswoC&b $;xͿ^_=.U%+ .\h͎Mw;|DQV6Ytn  B|q'ƻ3P?ZcZ2|yycm9:?ܩfI$zlcu{<| f&QDn?~p$GE>=ՁNn?`ϯ7q\̍8CC`]]]uTcTl`"###9XC]ADT&7}xD"j5nݺEww7$]|EazI7Hn?p{ mK~:MWWw)Ua,֚?.N묊h|1@p8}> +=R*y&9w Bj5ޛOruDW^gŻD"yoajzux^yUYfffy*{CpT՝#z{xu*].ݤiVVVݟ(Cn?HQ>|6|XvS?0us|VBE~. f|ooMl{ܵ"]o[ޕ2ޣ([[[d2)OxW7[JUo}sPۋ&=(j7s-T5 9Y^_czzzl;Ż󱴴D?}xk|j2N,zHRۋ\.smi_%P?կ:%.]gs\5lyP(D6Ν;n7n<;C$hgSߟ^///3==M0hsD=k'WCP(+QTXYYի}PU~Fwd$r|Ry(Ż}B!)|JWDvx"a"CJ%_u{ )(V$,V{8&LjIrz}ܾcDr0 Ww^-):j|eeK.vyȻ2HUpC[Rĝ;wz>Gu Gx@ @\g.AB;;x..v[=i3VGmV{(bwwk7ޫU^~GK= E,UxZq{}>>צKn=MPdc'Wp柿ؚUVC$b7M2O>vvvx9oE׌e#F@kfr5g' Pz>G1Wn7]MlV{,*,%3j;6=[[[K̃4;.6tww{2k\rvˊ*x%ɐ;.ܬ{:C e\V`nCP"iս @3~q݌&; àۼi}.IbιCO `ggh4jy92e{TVV|\."밿`Τ;ca+KfX=G)4`ejV^5?̖#z(Ew,ƍ#"mnrr}ki0xvS7}rp(_gaƛlM٬vݱvxH ^H$ IDATowD"Jk{yr&{"Uǿ۷S r1{>HHd@Pz(欈`{{:/h/Cl>( 7+_[[[LLL>Ϻu"'wn7x~tb)E"ɤ8f3wktuuqz(t`zql7ɔŤWDqc:՚A:n~eL?nG G7&LoP( '?Bbwvvp&S^ $*Z=nN@ݴ~w2޻ёr WNXup\Y a.n]C G  ~7{ `H.xB.k梅p2gC<_9J9Yut9s|q߶Ww {J 2T߇=p *P(rU,\]g>2k!! Dx<8~ݭQ75Z`ǻPRsrZq]؉\.[rW.^:wǾ;llq#P?{qŅffgtxx蝵l8w/iU,^~[{x^GFw϶I.DNZ#NZﯾ*KKK\xԠ rld\.GEsn+Jނ{ @ z{{-DQ|>._n7 {6ߧT*Y={z|˅r 9=8c{,q7c`3{*Z|0Ub\nBNMfp\xwnfK芻 { ?z%'jUSeDz0 m7Mnf].*[`e7ǟ@˹Se䓿؟/Cl|m83UޠzO~e}묬ꫯM*㴷dV8?͘&c`[< ̌nlf0 JڔߝVM:+{գ Wd~;|r 83Ĥ`o̓Zr#(Lvd AXf)))O<ŬNSejAwJUUۭwpo"E{k U[ {]Z%V_|>Of0ev`d^]=7C6{QNXu Y {]T"e+~Z{y*N?lv"hm8-EV܆iS8M*"d%J%zzzhh!E{kEbs@ rh { IURHBWܥ5Zp]xO.? MY*(J0`~nK3&;cvvyz-{_|[f&Q[#Qո4૦ Q0;x$ Z؅xKlNL&ȃQ89:栧?UUn SCo?5ǽ)ڭf{݌AmuL!=*h+2L6}_JR&TdH$v`¡ cm?5N}{r_18_{:Jq݃Amp2*9"NJh/U?nB"'TL&`_cLw`c߈rۋ|cN֐jJ.㩧2 R7@nbH&g&?g yeY2hϗ=XnBbXKר|&L#fJ%?g v2 9m;<<0 [}p?'Ez\|z2`oT{>3C.aU\0jOVX*ӄD"VsN.T0͜帿υ DwPzapXoo|]~^pr 4ux=='=i=^[XQr߃VVIR\pW_}U@`$_&[(f|mIqraQ:05oƂ,|&'vJ0)5* v_ \VlA訂tpm$4q=[:v)G?\h@ V;,M3iC̳=w=y0E{[7 y {CÝ ; `{{ ~iy:>bK Gv3=/Oq!hoL&<_6_J`OOgyƑ?wE؎\jkkk{OvYEz++ˤAԆ v;, L&~뷸|C: wE؊<+E/Ns2R[Zrt{|xmrT;3X]]^pZґhP[VcggMFAW Y=,ivkynݺx o v{ZessFFFmV:.b s3 T*B?yvGv8E=R)nf"_V v0ckkZƳ>O<6? wEXNސL&&L+]e(%Jt.ּUU`Olnnr|| gD"VTv!LG =!<*fYUKЬE>gxx{6y =:"b{C o>3§&bq2Q[Tbuu Ăl{'n*%?022bК].P7D.hw0888`scrx"$ vu+ڥ QK+)ڝVb.?wpzC'W]\tvskt100H<c=m+ڥ QK)ڝ0 ,d> ÀJ͠T=. H$:v{{ipWK(`+(OR!S*T*TU\.nG  vwαYޞ. (*viw VhS7D.VRK;Sw wE4! v]ڕE+ڥi Q(ڥ);].M`o]B.Fv1! vE].R7D.vhv`92Wwsov1! vU^yE8]qᾝ*'߽gfvN.`o]jkk_|nE8]QʕޢPq3w^!: (wK0dffeoTr rƟ|& s.(ڭ`o]˿r17׺vi]c.YglhGqoO|Q v0xרT*\rE.p`gW{9"[:'2>>N8z8ho-{C$>ZOE]ap?ʗwoH$z8ho{C4 ݒ}*<bۇ_NՀqgƃ5 QoI0lY<(Q)Ld/0>>dͧ`o]laamZ;hG`fm "]agNQ7! vqJoM" 6}v9+4mÍ k{yfggQ7D.b~~c.^}), 2IW8ܒ+)R7! vi'ZD"A0l0 vi%[nLK_Wm E QK;Z^^&rʕG.`+26O#[=SK[nD^EF.V]Wkqx9h7! viw| ƚE܏]v~c\xOC9E9 QKXZZ H4e9EC.vbp~'M0' Y=S7Nt5hSQKPyh7 JBZ0 n7^cK.vdp!x(`o]:QV#L288h v2%7slR#2 b?C +B4W)lGeCV`o]:.J)Zl{0 %QCpRBUslqe``Ki:]VᾼQ7D.۸nW)WӓjfapppR(<=q,npp 5nﲻK"gt.4]Vᾝ.|VN'R`[PȨ5CP`uul6d>\. X؇R 0CCCw\Q,܋M9(Bh QT*ՔA<ݕ烃VW 1{fJ?/ ~Q{D vq2[{2UׁP? {C"KӦOe9*1\Ća3=fy 0/yCfffm;ǿQ vi @ބw.Q7D.`aP(L" ;E{]<3lCWWCz t[{-(OY=GP>ptT2ugeh{r`.]1K.fw(ExpͿRB\n\w;G;ύsd2MZD vd D5W>D̠`iP(Cfhȣ#z{{Miuc-].bpYOe#S4_,#ϛͰ`%h~|XP(C. [`,qa0< vI$ |py/tRMDK`M xZ͠X,Z^,") j5S`c}٦|^7<5o100В7*ENgp8dYؒ]:CCCA.#] >??\̰GUh2.7L vU.d2Y=)EӃ'ɘ17 M۾ӣㆯNwn䔩>*Fys[P؇bddls \8M,*j86)!bbb+ vGgpr<+&͚~EE"411 R o=n4h"## s_(XYY8S'y:Ir@X`rr\Z"2 ðzwÿ''؝ ϳXv{ό(ElT* 144Ԕ} xo~P0(xLǁr6;;}/\CM)EW_e{{˗/73V>/Dh`0rQ(yGGGx\j,>Oӆ45u 'Tkn7aP.9>>&͒:<TWSX6H.\.z)^{5r]]r"hx<;L&lX;*S/_\`yȕ+u\E{݇G˗x"DQ:ĈZƷ- `Io;E{ݍ7x7r pQڟĈ:ܷC#zP,s̕ǮvP5~255qwfp6Zc<]?_;\y*@Y>b308hGio v/c=ڮu/6x`'}JUŷ>0;7uh{[|M.]"h`,LobYooYZZʕ+s4 viw yz8MPstW:ӽ"k~~_,?j or뀋_`vpx[|Mfggr D{Q/orqz vxY\\˄B!w "IDATcD{`ll#DŽ{fVٹKm|#^wN㇋wƁY."w+|;!q%~ؠFM}(إ9& *ys.|=37׸p&>"r\.wjsssx}Uəuoׯ_oe 2[d3M+\3eYn^4% ~~QL=(E,RwzLOOҹlJLNN v$ wl?{q|陖5LT%g3CoCa/bddq`GK/̌#4_M[xVo}}}l vD wl¿"LNNǭҙw8+S<_%7H$LLL8n4Es0 677I&<< 7?dppQG[r2^M388Ȉ ZVY]]/_=?ݼ{K8iE v1K>_fooQl},)Jdxx'6^0~:?ωD"LMM~Z]'_ 1>1i˥,++2'xf5s 7?_~ȈmO\ vij;õk׈bn0HRxZ[[[TU.\@"qW. LJ{N2" bzH\f}}&_- * ޸K+mTH+oP.bppǙB;w8::b||/}K-y7YYY!166f"m\꯶y[=^dLRaww$~>w','$Sz GF,>clV."-U,yw [vT*.]]]|s{qq%4C,)?(ne#hE(SnRA (Hl=H\g2(7-\x%q/^ @VKWtbիW;UOus?*WNH:==\}_~.ϝ4ӗ]c5 y[0,SƓ`p%ݮԣG8t$IVNҦinaHŵ AϷnWYVh^_*|$:;;SוT*XW߬h|sϷE*%5Z*Jy}ipW'z>~J2z?.˲Tl6xצ84LT.~(tt*͋2se ʲLwֵW>5 b}}_NƲy*^U\.q7Py8|xk4H>Z6ɝƥey^xT_?+Isll\},4L455DJAЧ-5a7zvww5 d۶*Ջ9u7^\Qi2}F#A ˲nu}Ya9YiooO;;;ۓ$ؔi'MSM&FsHiV޽{ܔyo'FSGPo}Q*IrlKv ,Km˲,i,˔$Vʲ![,^UZծ^ϕ7u}97k$iaA*lǑm;<ϕ4UGHQH{A>\um,bu\\nWԓ5q.۞ ʲl6Gq0 5Vժ׵v=/|?0bʲ8#۶gst|bgPʊt5i1n\T:: u48Q0N%Jrі[TUjJjUZ1:9Otc/ЫP0(LE׋~?tތ'"_DϮ{mΈ^O_0b\t)n7'_J:Xթ γO\y)]_x9L|㟾9XYYE}x|U7ooNy@|؃qbǧ7gE 2'qOz+>kGU ݌=︳o}W~p-~8~xxKw`;WvO"ށ77K w`";#܁(O%ށ[v Lxn09wT=FR\AY^:9Rxx_[_|r|W>WQEe^/{Hd wvNcDQ1 b}}=B1o1; fK ag;LfU`F]!܁ v@Cu;#DvpFM'܁v`+ ܁w vVCJCwh1"ڡ~;`#ڡ;`v"ڡ;`!ڡބ;4`%ڡ;4`vCCD%! ؁퐋p;W`CCN~vKC w1Lh;Ԑ`&IC3wLhP"ܡB#ܡ&$a;0mK v`D;4p)vh>S ؁Y&H&ڡ=;L` ڡ];`"ڡ};`$ڡ;`&ڡ;Au ڡ݄;lCu!CvND;!=;P7ICvD;p;PWp;PgF*;"id ځwMYv`'F@&pQ;h%id$ځ&؁D;[;hB`^ wR@v`?;&؁~ wjIM"ځIԊ`F"ܩ4h&IS)4h&MS 4hA3Sh:Lpg&;&T v-D;0m@v`,˪XZZo}xwSg=Gro/>W.F(bP1w5O@c<#VWW((2xcc}PS%ܷzj>}:N<n7` ˭k]/O@#:#Ͻ}.>ޭzLΝCmkkkk7'/T0Y=<{\/a|ӂhΈ͸pg0}ogΜv7qڵOU?7?;C׺nK_ЌΈ/x"mEQDQ ī:w}>`3;6EYQЗ`0>LV}>n]MLpߢEzlj{`z8Ѓ>^WTL?Nbcc#GnNXYY8r_|6&o1^/;p:UL@p;$   wH@@p;$   wH@@p;$   wH@@p;$   wH@@p;$   wH@@p;$   wH@@p;$   wH@@p;$   wH@@p;$   wHi0IENDB`Shapely-1.5.13/docs/images/simplify.png000066400000000000000000000466531260610516500177650ustar00rootroot00000000000000PNG  IHDR,6sBIT|d pHYsaa?i IDATxisվ6K`RˎxvHLv>;! Suyߟ"ϛ읰lN'ijc[ԚgE#rARW傸W_Z_kEQ@DDDDDfԺ"""""z9w""""NDDDD܉z;Q`p'"""" DDDDD=0w""""NDDDD܉z;Q`p'"""" DDDDD=0w""""NDDDD܉z;Q`p'"""" DDDDD=0w""""NDDDD܉z;Q`p'"""" DDDDD=0w""""NDDDD܉z;Q`p'"""" DDDDD=0w""""NDDDD܉z;Q`p'"""" DDDDD=.0$!͢R@oCufj?NZ!I:,<=bhGԒAQE"hpɲ\.L&t:bL&ԍ6ĕ T3lV+**æ$†8,rzazd2u?V!"4rsL&S ;~.OBRLpx;"^7Zq:-nF#H; 5   #ˡ#h4aa`ٚnX`4a4-H4`= `@  p:(ˈHE2=`̭@eZ JrJ. `gΜ(j"edHO>E"@>o^7L-fF& .نX_ǐ\O+EQPIZ#dY nB!btt^5::DP.a0044xv;,KZVR H͊F0|۫1  1X?_gVkrbB,b43g  r&":%EQH$OB$Ɩ|>prˏGSEQ`166֜aNbp*JD$roGiGI"2 nBQJ&(`xx_!2 r|3[=== uZ]>F3WfPw\p:SVὕz|>|{>ȏbjj _9 tjZ XYY?15j@LK",f3BP(tZ Dq1rC #GFF0??lHDԏ* ױg(73?:qcjjSb`peH+++@V?|x$@].0|>_[9*l6("bbُwui$IdYF={sss8{,h"XZ O<*l41::ʵSt" td cuu(JǞmV`7ήo\lڍGJUqƭO1_+Hd2d2bł̙3w$( >}C$8=[,'7t#7T*$ R)e8annP:2wz)EQCEX,~8c88g+a 4يQeib | p!NНS.tRoyΰQߑe+++駟d` aQ $7t3X,"L"JA$K.arr^%2VWWO?!Nraxx+łW #chhH7UX(H&PdS>Y( 0\.Wӄnkh|>.],'yP t@VǏ?#bddDd`=n(\lzs1k\׮,  J%!JjŋX\\bV"9JKKKx*~?FFF{J;B{V!cooL. :Ν;I:*>|_~r9;c]MuqC=mc\cH0R "Hh4bqq/^<"~<|,# bxxX v;~._Q "1B{amm . jP b :VޔeD"z]Yz2xY+ vvvN1::˗/up"H/D0>>n=w@mF>,{aL-ᅦ`gx+̒$KfEH# "zT*1ET˪ќ c|[2 QVk}J$xV+&&&z.Ao]Q$ D"(z3}<3::z䰝* @ !f[3EAP(H0}1<,,ˈFc u(X__\.cddúZw@mD"lP @$|WX^^>V[iuviMG IRsKJUBЩ`>½q3Dvbwnb z n߾\. oqa\]xr A|}5 BcdsAwv2 `2pUwh";;;s:&''uӠޠhކƵk .:O)G믿n dT Sa6Ӏh@  mJ.fQ1VOf=/x=%I677fq%O(":1Y㧟~Ԕfk e8<+{V/A0>>~hȫվ%Ȕ\lj2P:YJcpZWB󐛁(C$0>fKD-?G,Ù3g022ޠ.2vvv "lkC }&(Joq:d}0\.AP,!"Y_q^̚尹 +Wٳ/zBQLOOſC{;R)lmmpڵkZDm'E7tbzz⢺$`BMVk.f-W?L&/w":T^Ƿ~bjjJw[Bho{xT*@XĻヒ/rRO0zwb}}pcccZcrQ\}ۍ YEA.(Ȥ0(bE8 H?* >Sb1!nޠ.2vww033+Wp0jFj( UadɈ`0A^HZ"#.j5 ?L6X[[y5yܼyyZc z:a|G{q =X,֭[HӘF<0*t8 |EA:!n·R X,q|>%Rn޼ I0??I^ s'pޣ,R`vv8R`G8#Q:R.fM$PeLz-%[kke| $ h4[nd2annNz!Enƍx38~O{R*BWDcvww?aT^! ( D1T*#_K$.Y^^_|׋i]֏»$IX[[CRG}1Kc`p!_)f P(ĝDI$q$"*U N nuiDa?3ALNNr"C{C/zuy\z333ZDG#V׷n`u81<<߯˙EQdF߲yU"YZZW_}2k»,D:G}(DF! 2  |>d4[X__׺,"u|W :a۱p 53k(IZWth4^/>3DQK#`9u;[ ylc1BH¸]D"K"6D"su»`4nݺT*uI :88hp K$$ňm?ǵ. O?bONBx7djESrypFyv{҉vxnč#Zy&٬e)dYܼy6 333l l6cnn$͛T*ZDo:VO?E><63&.gS70"GXԸ*":bМ)z![V#O?EVӺ$j]gdY۷177ͼ`PҨy^X-f,,C\ƭ[PV.Z֭[(˘e fho»nDQ۷!˲%suo6fffp]|G`0 $`anntww% Ν;HӘf / }hh3337|u9wƒ066׋, N+ A$t:155-,--i]'O`jj NSr`h?^^cccx.|wޅC8,ŀ!CCCWGbc)( ?k݃(ZGD/ "ݻp8 ϧu90O/p8 ׋wrapY@sKl!!quOA@Drp8gߝH*>38Ni] 'n0055 :#azzH&#Q?q\pmX}qr^T¿/錢(׿Rim~:zfӈb.ஹOd8t7HSok,RJ'farrߴ-y199Ũ rpOѺTb;w`xxPsQ*uF0ho _#HhW5% |~_raho/x<ܹsgh]#z}+-nװBW& `D@~3fbb6 o߆$A4`$I۷a0>>u90w{߽^Ν;wF>|Objjjߠ'7RA@lx_=Ӯ8"w}\.]L2wûb">|u9K?) x]{(V^Wh8\CXnǙ3gd2MqD.H_ř3gt䕡;=B;J%H o(ʁm*5`5 a_ Q'BO@깱7f Q)/y0wEᩪapﲽ=,//cttcrpIi> ;{h4bbbhG4VVVF1>>vm5fbyyhTr>F!2K 犢.J+F#!T޽{22QFԧ"DQt%F cmm lV"{ϰX,P,f|>_+#:hhhCNgEt:qFԧ߿өYo;C{KxԵz?6 )x1AhK@,'`u"-0v@|ZqwwxH'#EH'_ލF#0?~bc_tYQn-`@0$`- FbH":\.cggk h ~?VVV -v:cpoOT*:$$ TҿP(y(Eԫס(JWdhgiރ JOn36ZYYnly 9N'~KinBhee!Zvxw:G{j5lɔ,"a*n@ h4\ "@.C4l;C;H7{]f}};{lmmp"`659%褆tرk}h4buuUL&|} v:n@ ZڈGç IDATMVVV)&@0$tu^2  ale|FP[YYdNѭ؁lyy3_`1E,=t&e- TꉔD&d4![C|>iF#"wi6EzГPDfu23Lx-=w j-@GNfhFxwݨjCfX d2-&VE va \SO}ll"/tvjNw ɝ6apoH$r7"`Zz5=.0q\nQD=qh'vjNwɝ6ap?zrpPOKp<,rN\.|>6vNw˅=)}9'rk?gTkkƝ:S}~JO>m߮(Tρ5wIz܉Zkg;C;uC'{ϝcp?w@“RB!e`%q{jW;C;uS;ۇ^߾$NzQZ|XէJjr9m#ҙv3 H$Z* R2u߃h4AUD]8͌;C;ir^#~ 鴺nhD Т,rݰlx# łL&MaD:NaZONzЮp8Sbp?L& |_]Ł@0D`0@ vhe2NzҎn2`ZOZ iuv'R 0 '6wtDf1)Dv5 #ٌ@ " ?H1(+ϯ\%0hƝᝓ;~B oOh[@`E zf j(ZJ4@Jjځ5P/N4ᝓ;~BbZmlCX|VFN!'Y}Ptm^p3S9ix1P#4jXI- i`0"95t D ҏeh^t޸G]OAy%( ,~?fSskHGDPvFo ˎ9sz '젬(Tw~a4 8Hu}\|Dv/N8h4n3 4 ZJjE>'\.N8bP(t>~B$5Xz(vy; lz=X!I8<{xC;wшj UUF%`#00%@,2; :IZ3S?;Jx0Pc68`0.H36K1DP̸3 xYx0$I"a6.H3!! )&j5AKQj}3 4H^9s: '$IP .:3DJ3 4 l<j,5Ժ"͙f~P4/ZF#C; Vd2qbO@Qk&+V (֌Nѿ[[i=Ny: ' ID0LZCCCCp:fpUP 8ډ?w fκ $  8kAb0i"+1^lgh']#$~X~"j]ROb$DDt(}s" 'D<Y.H7EA2\~uLr4w;pAmcD2?RkAKI '`X @D^G*Һ"( (c{ , ;*`* 4}\ÿS{ 1`\GVE\i]nYY4V+`$?ρڳ? [eNf3 EE!\VC*Ĭ Nq,Nz͟ Y,,8-,#Ѡ0p/d90ӠjhNUh99X,"$ GJMQf| Jd0`6m^N/hO, :^  Lj]f,*U aDĩ1NEPg@OjBe8-c1KKc:lAgXZnNei1Pc.@RA.Ӹ*T*dX4:gSh={x;v3~B.9؞q1 E0P\Vr-Hcnj O@$ u>~B^r,` Np&7,#\0>T*|>m #Ҙ}qB,(z+0P㇮10QhPR)ju\?˲JA޳;/N8P'vq '+ E40b n5|ywE >=9r:0B@]*I5i +#b|E11ҏgxr<   x<}r ԭ(pZg&N8(f;Nᤡ@)|}3H5ˣT*iTQj5$ \3/e"`=pxwҳӄv)}̭!%I(WBA&wy C yA0Bcg0! HzQ/Sb,i?|n. D;Ɍ{;I;B{^GZep?%S8l+!.H&ZEQ\JdCV$I~PVQN -ļG)AL&r}wۀ^u`f}ܣμskH'x&zRZbA9('s;uCC;WyhSe;,op"~aDmN!jX Lߙ3gY Iz0L/~J/sRc1 @CQ.E֮>g1S't*oo'~>w L&AeDQ*xdI>Xw"s;S'C;ۋ FGG[@x98OR&"fd籿,wjNvF{PfGzd"!d5~9(xQvw:NvFcccH- YwAduYCt: vC\.|CN'эQ m2??Br| Y5d- c.zt/#N^wgxVh/Ճ(:{LNNl6#L~AjuT˕\P@Tn$ΤȲFc rfw:nv@}bk6q}&f333HR-wTy*Qmܭ'I >-p'oEEA* fsЀapoy喻{PDPreD'IR$V׳,dh~~laZfhd2mFgΜ8Գ^`E8POn%Lh4bffu L2ӳxڌFcsRvRd%j'EQ 'J011znkڍmB{Mf~~ɴm677l!h\Cg ɠR=)S¡;u`"@6EV=, :⴨OXYy"=BN@j}=Lbpc:{ w Uh6χ@ н/: `0`aatq1 +ՎGtZJlvYH$0;;˝l6cvvDz0-C$Id2XXXt* pyFDіׇ[C>I=D"j^{_j2@4h{l6,.."\j0NgPV55Yx% ;tܹsb-&uD\j#ы=IShcccCHA&{CheX Ν~^cV.T#Nw2  ⡋R,"gۉҥK(ni APx׵)`@0w3*0R$Қ( O#jQ=j`xmz ,#bvvG"{]t酻, @._@TreDjH%RO}^>G>ǥKQ \tޤg{TU1 ;,ٳk9>f.R%m'*xE{{{|j]Dnrr>OYw)7?={.u{(-{MF$joQ)ôfdor |Md2z{B;e2 ܻ caaHYV~r9+ՖReY6FFF0;;,.c ^Dp9am ]`0`ww5[i- 61ߡX,J˗/sC ._JXL Iovwwa4;h]`pÁwyxB P*5]DZ"`QPO}ӧOȾE X\\ӧOuq6û1 qp8Z30ܻ>0><6.REf#0"f᭷~aD護ނjΎ֥`x =vEQA?^r {F\|*Y5d:H]!2q炀ŴZ6E*{ժMDjC*|jûuAjX˗a42Jv]622rBՅ?䰓V)NCqEØӦ85??ahP]z  ֺ஁\"Rljb g\ƂT.k,T-˺X]z  R}]KH p8x<] UIB&Ѩ:R |U. Hc.T-Zzlxoj]#.\(677 ~!' D""fYۍ~[o6n7u20wC$I(.\u9]#FW^d֘EfsiQבLq^tlooRڵkXtt @׮]CR.3 Ю( 677a2pU.Hr:z*,h3~f֐DKRx + }T*u90C;!իp:Z356>>7xHyRSq1Izb/T*lmmaff>ϟ PT.g{hx)x k]cpׁz p-" @.#Lj\|>R߷l;N/.2D:c0ax?Z aƧ :`4@c&<@TDQ?T*ڵkC=7 >/•+Wd{.BBqu$IB:bX=7N#ރ / H3 C,C:ֺޏGPd2r \.udrr/^.2 =j2R;q|($.W_$677Q,.Оd/ٳZC`pיw}X__GXH%wh,JW χ+WG \r>[ 0L/|>uLLLtTbpшk׮AbU Hh]L&TÂ_*v;_ξvcZ'ncuuUή^R kkk׮]c_f|Gp\XwHq'nܸ㪉zč7kkkWt~ګ*VWWrl6k]SQW ʕ*٬eQ*r{V~:<e)x<\~J&`xo^հ ł7nfi]]\._ǐ8 A>PDD #7֛AeYn>q.^]~?_E:b$/YPbj](^T*wЮ( 666P*pu|>K`p?x&X[[E"cQEQN\'oMbffF번fffpeb1D"w89;?D8ֺ$:pjo?>EVql6! AQ$IHETP__}C҈~ܻw`5]1 `0ўR/zuy\z;=~z 0bJRz=0<.j,EQP(~?Q1#9 } ZHD]/333ί{/vIJ>cccZDރ8n޼b14) Ef $ j :$D1R x%X9Z?' 'O>. RQׯsޣ,R`vv8$kI@ 8 gJDQD2@]1@U Y'_hEQܺu & sssԺ$:!WVhSSSEvjL&cs1n׸j:jx<(B0Rgק||>5n\~{QS>͛71;; ˥uIrxОJ.1zwb}}pccc$*V !! 6R\("Nd8\,.0>c0DDT*|駈bC8ߋ]]e"affW\>'E{tbzz@F5 bF0$@$]VC"@\\o\~&2 ^{5Pz~-B.]V,M(#.\.M \r;ё=ywޅ(]L/wT*-8|A뒨PZ_| Cց )m3 5IDAT$gp;GeR)",BWB󐛀(C$0>tH<>sb19s###sxshe;;;E333_~>ާEG_nczzRxbf0 ۨR@E$quyŦgMIl6K.O.V! 2~ܿSSS z zr(xqy]apsDoF.K U80$54!.feX<>EQf!bds/6aL&-L&|<厈fggwA^$^%5)5+D"mn\v `P벨$Iꫯ ˅#͢ˊ:$OslB0$  q#$شRt l0OR4׿O?"bgg>ڋ"籰?Ϻx2A>@"Ki#**3q@׋ q3EAP(H0@,C4ۻ8㿹̅30Y%FR|AEd5PY 0Ä֍ $~ EQs> Ч~e~.Mї_~)˲z>OY3ާ1]UTRXFq}_?kb15W>s-s o$c&fŮy2MS֡#Kd;U8ǏjwwWmYApeѷ~Ǐ+Nkiii*\GO[ni6 @ WpK }WzٹϜ ]-iXb*˪jo՛;-˚6=j|,Lsg_2Xvq>3/t:}j6S}*}ڢbܹO>䭚k;-s_5r_)9lfVڼr% u:Z:9k&):9C:/n뚟֧Wmkm1|7r]WJEz/<z+%w{R2WRUV;ךFmnݒJKE)qO `*nY__W,SZU^2}Zݲ,HdR}b pض'ODz,KRI ɤ7ݚ\+9jy H&A~VPqia.xߗizt}n0á׵!EBs> >c޽{H !⺮>}N~_BA zKOo@JR&WJNyU#k1ٻ~"/Bht:{(x<Ɔev3;;{Ǜ*Ç{[}F|׳gϴN\.z~U@zҚJK*KjrS ?'W9G@,M}1wæq6|w}@乮Q o"ޯ+ڃ PJ>|;w%/E @jJT*T.d=@]ì4;k0k"qfSҽnU^h4i:>>xNJaSSRgaӗz:/t~8Gɶ7o2 4;;F`z4'l6oɲ,b1eYe2T*uf&CؒyU*3W/|ߗij5KO]r \וeYDz,K0 T*FCr iBOٔ8aȿ8G$W{ϊF{r`aq#D +stɉN qt:-0;L*H(5I+$T*5??L&#˲jdz{@7ߓr]7x-˒m۲,KIRKER r^Dd.=GGG'x< tvA}/^:G/i2GyUp1PU"q|ɉnt:t:% yAR3ƶdW=aj>V=_dŢŢ B+)yz^87O˲7D"X2uKNP&nL&sY"1\Ux<8g~ƶ_v{ ճn%c&Tϟt:t:9^ g۶zlۖm/'~:NAQ~6ypQP)D{                                               /pUqIENDB`Shapely-1.5.13/docs/index.rst000066400000000000000000000004001260610516500157730ustar00rootroot00000000000000======= Shapely ======= Documentation Contents ====================== .. toctree:: :maxdepth: 2 The Project User Manual API Documentation Indices and tables ================== * :ref:`genindex` * :ref:`search` Shapely-1.5.13/docs/manual.rst000066400000000000000000002233521260610516500161560ustar00rootroot00000000000000.. _manual: ======================= The Shapely User Manual ======================= :Author: Sean Gillies, :Version: 1.2 and 1.3 :Date: |today| :Copyright: This work is licensed under a `Creative Commons Attribution 3.0 United States License`__. .. __: http://creativecommons.org/licenses/by/3.0/us/ :Abstract: This document explains how to use the Shapely Python package for computational geometry. .. _intro: Introduction ============ Deterministic spatial analysis is an important component of computational approaches to problems in agriculture, ecology, epidemiology, sociology, and many other fields. What is the surveyed perimeter/area ratio of these patches of animal habitat? Which properties in this town intersect with the 50-year flood contour from this new flooding model? What are the extents of findspots for ancient ceramic wares with maker's marks "A" and "B", and where do the extents overlap? What's the path from home to office that best skirts identified zones of location based spam? These are just a few of the possible questions addressable using non-statistical spatial analysis, and more specifically, computational geometry. Shapely is a Python package for set-theoretic analysis and manipulation of planar features using (via Python's :mod:`ctypes` module) functions from the well known and widely deployed GEOS_ library. GEOS, a port of the `Java Topology Suite`_ (JTS), is the geometry engine of the PostGIS_ spatial extension for the PostgreSQL RDBMS. The designs of JTS and GEOS are largely guided by the `Open Geospatial Consortium`_'s Simple Features Access Specification [1]_ and Shapely adheres mainly to the same set of standard classes and operations. Shapely is thereby deeply rooted in the conventions of the geographic information systems (GIS) world, but aspires to be equally useful to programmers working on non-conventional problems. The first premise of Shapely is that Python programmers should be able to perform PostGIS type geometry operations outside of an RDBMS. Not all geographic data originate or reside in a RDBMS or are best processed using SQL. We can load data into a spatial RDBMS to do work, but if there's no mandate to manage (the "M" in "RDBMS") the data over time in the database we're using the wrong tool for the job. The second premise is that the persistence, serialization, and map projection of features are significant, but orthogonal problems. You may not need a hundred GIS format readers and writers or the multitude of State Plane projections, and Shapely doesn't burden you with them. The third premise is that Python idioms trump GIS (or Java, in this case, since the GEOS library is derived from JTS, a Java project) idioms. If you enjoy and profit from idiomatic Python, appreciate packages that do one thing well, and agree that a spatially enabled RDBMS is often enough the wrong tool for your computational geometry job, Shapely might be for you. .. _intro-spatial-data-model: Spatial Data Model ------------------ The fundamental types of geometric objects implemented by Shapely are points, curves, and surfaces. Each is associated with three sets of (possibly infinite) points in the plane. The `interior`, `boundary`, and `exterior` sets of a feature are mutually exclusive and their union coincides with the entire plane [2]_. * A `Point` has an `interior` set of exactly one point, a `boundary` set of exactly no points, and an `exterior` set of all other points. A `Point` has a topological dimension of 0. * A `Curve` has an `interior` set consisting of the infinitely many points along its length (imagine a `Point` dragged in space), a `boundary` set consisting of its two end points, and an `exterior` set of all other points. A `Curve` has a topological dimension of 1. * A `Surface` has an `interior` set consisting of the infinitely many points within (imagine a `Curve` dragged in space to cover an area), a `boundary` set consisting of one or more `Curves`, and an `exterior` set of all other points including those within holes that might exist in the surface. A `Surface` has a topological dimension of 2. That may seem a bit esoteric, but will help clarify the meanings of Shapely's spatial predicates, and it's as deep into theory as this manual will go. Consequences of point-set theory, including some that manifest themselves as "gotchas", for different classes will be discussed later in this manual. The point type is implemented by a `Point` class; curve by the `LineString` and `LinearRing` classes; and surface by a `Polygon` class. Shapely implements no smooth (`i.e.` having continuous tangents) curves. All curves must be approximated by linear splines. All rounded patches must be approximated by regions bounded by linear splines. Collections of points are implemented by a `MultiPoint` class, collections of curves by a `MultiLineString` class, and collections of surfaces by a `MultiPolygon` class. These collections aren't computationally significant, but are useful for modeling certain kinds of features. A Y-shaped line feature, for example, is well modeled as a whole by a `MultiLineString`. The standard data model has additional constraints specific to certain types of geometric objects that will be discussed in following sections of this manual. See also http://www.vividsolutions.com/jts/discussion.htm#spatialDataModel for more illustrations of this data model. .. _intro-relationships: Relationships ------------- The spatial data model is accompanied by a group of natural language relationships between geometric objects – `contains`, `intersects`, `overlaps`, `touches`, etc. – and a theoretical framework for understanding them using the 3x3 matrix of the mutual intersections of their component point sets [2]_: the DE-9IM. A comprehensive review of the relationships in terms of the DE-9IM is found in [4]_ and will not be reiterated in this manual. .. _intro-operations: Operations ---------- Following the JTS technical specs [5]_, this manual will make a distinction between constructive (`buffer`, `convex hull`) and set-theoretic operations (`intersection`, `union`, etc.). The individual operations will be fully described in a following section of the manual. .. _intro-coordinate-systems: Coordinate Systems ------------------ Even though the Earth is not flat – and for that matter not exactly spherical – there are many analytic problems that can be approached by transforming Earth features to a Cartesian plane, applying tried and true algorithms, and then transforming the results back to geographic coordinates. This practice is as old as the tradition of accurate paper maps. Shapely does not support coordinate system transformations. All operations on two or more features presume that the features exist in the same Cartesian plane. .. _objects: Geometric Objects ================= Geometric objects are created in the typical Python fashion, using the classes themselves as instance factories. A few of their intrinsic properties will be discussed in this sections, others in the following sections on operations and serializations. Instances of `Point`, `LineString`, and `LinearRing` have as their most important attribute a finite sequence of coordinates that determines their interior, boundary, and exterior point sets. A line string can be determined by as few as 2 points, but contains an infinite number of points. Coordinate sequences are immutable. A third `z` coordinate value may be used when constructing instances, but has no effect on geometric analysis. All operations are performed in the `x-y` plane. In all constructors, numeric values are converted to type ``float``. In other words, ``Point(0, 0)`` and ``Point(0.0, 0.0)`` produce geometrically equivalent instances. Shapely does not check the topological simplicity or validity of instances when they are constructed as the cost is unwarranted in most cases. Validating factories are trivially implemented, using the :attr:`is_valid` predicate, by users that require them. General Attributes and Methods ------------------------------ .. attribute:: object.area Returns the area (``float``) of the object. .. attribute:: object.bounds Returns a ``(minx, miny, maxx, maxy)`` tuple (``float`` values) that bounds the object. .. attribute:: object.length Returns the length (``float``) of the object. .. attribute:: object.geom_type Returns a string specifying the `Geometry Type` of the object in accordance with [1]_. .. code-block:: pycon >>> print Point(0, 0).geom_type Point .. method:: object.distance(other) Returns the minimum distance (``float``) to the `other` geometric object. .. code-block:: pycon >>> Point(0,0).distance(Point(1,1)) 1.4142135623730951 .. method:: object.representative_point() Returns a cheaply computed point that is guaranteed to be within the geometric object. .. note:: This is not in general the same as the centroid. .. code-block:: pycon >>> donut = Point(0, 0).buffer(2.0).difference(Point(0, 0).buffer(1.0)) >>> donut.centroid.wkt 'POINT (-0.0000000000000001 -0.0000000000000000)' >>> donut.representative_point().wkt 'POINT (-1.5000000000000000 0.0000000000000000)' .. _points: Points ------ .. class:: Point(coordinates) The `Point` constructor takes positional coordinate values or point tuple parameters. .. code-block:: pycon >>> from shapely.geometry import Point >>> point = Point(0.0, 0.0) >>> q = Point((0.0, 0.0)) A `Point` has zero area and zero length. .. code-block:: pycon >>> point.area 0.0 >>> point.length 0.0 Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple. .. code-block:: pycon >>> point.bounds (0.0, 0.0, 0.0, 0.0) Coordinate values are accessed via `coords`, `x`, `y`, and `z` properties. .. code-block:: pycon >>> list(point.coords) [(0.0, 0.0)] >>> point.x 0.0 >>> point.y 0.0 Coordinates may also be sliced. `New in version 1.2.14`. .. code-block:: pycon >>> point.coords[:] [(0.0, 0.0)] The `Point` constructor also accepts another `Point` instance, thereby making a copy. .. code-block:: pycon >>> Point(point) .. _linestrings: LineStrings ----------- .. class:: LineString(coordinates) The `LineString` constructor takes an ordered sequence of 2 or more ``(x, y[, z])`` point tuples. The constructed `LineString` object represents one or more connected linear splines between the points. Repeated points in the ordered sequence are allowed, but may incur performance penalties and should be avoided. A `LineString` may cross itself (*i.e.* be `complex` and not `simple`). .. plot:: code/linestring.py Figure 1. A simple `LineString` on the left, a complex `LineString` on the right. The (`MultiPoint`) boundary of each is shown in black, the other points that describe the lines are shown in grey. A `LineString` has zero area and non-zero length. .. code-block:: pycon >>> from shapely.geometry import LineString >>> line = LineString([(0, 0), (1, 1)]) >>> line.area 0.0 >>> line.length 1.4142135623730951 Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple. .. code-block:: pycon >>> line.bounds (0.0, 0.0, 1.0, 1.0) The defining coordinate values are accessed via the `coords` property. .. code-block:: pycon >>> len(line.coords) 2 >>> list(line.coords) [(0.0, 0.0), (1.0, 1.0)] Coordinates may also be sliced. `New in version 1.2.14`. .. code-block:: pycon >>> point.coords[:] [(0.0, 0.0), (1.0, 1.0)] >>> point.coords[1:] [(1.0, 1.0)] The constructor also accepts another `LineString` instance, thereby making a copy. .. code-block:: pycon >>> LineString(line) A `LineString` may also be constructed using a a sequence of mixed `Point` instances or coordinate tuples. The individual coordinates are copied into the new object. .. code-block:: pycon >>> LineString([Point(0.0, 1.0), (2.0, 3.0), Point(4.0, 5.0)]) .. _linearrings: LinearRings ----------- .. class:: LinearRing(coordinates) The `LinearRing` constructor takes an ordered sequence of ``(x, y[, z])`` point tuples. The sequence may be explicitly closed by passing identical values in the first and last indices. Otherwise, the sequence will be implicitly closed by copying the first tuple to the last index. As with a `LineString`, repeated points in the ordered sequence are allowed, but may incur performance penalties and should be avoided. A `LinearRing` may not cross itself, and may not touch itself at a single point. .. plot:: code/linearring.py Figure 2. A valid `LinearRing` on the left, an invalid self-touching `LinearRing` on the right. The points that describe the rings are shown in grey. A ring's boundary is `empty`. .. note:: Shapely will not prevent the creation of such rings, but exceptions will be raised when they are operated on. A `LinearRing` has zero area and non-zero length. .. code-block:: pycon >>> from shapely.geometry.polygon import LinearRing >>> ring = LinearRing([(0, 0), (1, 1), (1, 0)]) >>> ring.area 0.0 >>> ring.length 3.4142135623730949 Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple. .. code-block:: pycon >>> ring.bounds (0.0, 0.0, 1.0, 1.0) Defining coordinate values are accessed via the `coords` property. .. code-block:: pycon >>> len(ring.coords) 4 >>> list(ring.coords) [(0.0, 0.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)] The `LinearRing` constructor also accepts another `LineString` or `LinearRing` instance, thereby making a copy. .. code-block:: pycon >>> LinearRring(ring) As with `LineString`, a sequence of `Point` instances is not a valid constructor parameter. .. _polygons: Polygons -------- .. class:: Polygon(exterior [,interiors=None]) The `Polygon` constructor takes two positional parameters. The first is an ordered sequence of ``(x, y[, z])`` point tuples and is treated exactly as in the `LinearRing` case. The second is an optional unordered sequence of ring-like sequences specifying the interior boundaries or "holes" of the feature. Rings of a `valid` `Polygon` may not cross each other, but may touch at a single point only. Again, Shapely will not prevent the creation of invalid features, but exceptions will be raised when they are operated on. .. plot:: code/polygon.py Figure 3. On the left, a valid `Polygon` with one interior ring that touches the exterior ring at one point, and on the right a `Polygon` that is `invalid` because its interior ring touches the exterior ring at more than one point. The points that describe the rings are shown in grey. .. plot:: code/polygon2.py Figure 4. On the left, a `Polygon` that is `invalid` because its exterior and interior rings touch along a line, and on the right, a `Polygon` that is `invalid` because its interior rings touch along a line. A `Polygon` has non-zero area and non-zero length. .. code-block:: pycon >>> from shapely.geometry import Polygon >>> polygon = Polygon([(0, 0), (1, 1), (1, 0)]) >>> polygon.area 0.5 >>> polygon.length 3.4142135623730949 Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple. .. code-block:: pycon >>> polygon.bounds (0.0, 0.0, 1.0, 1.0) Component rings are accessed via `exterior` and `interiors` properties. .. code-block:: pycon >>> list(polygon.exterior.coords) [(0.0, 0.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)] >>> list(polygon.interiors) [] The `Polygon` constructor also accepts instances of `LineString` and `LinearRing`. .. code-block:: pycon >>> coords = [(0, 0), (1, 1), (1, 0)] >>> r = LinearRing(coords) >>> s = Polygon(r) >>> s.area 0.5 >>> t = Polygon(s.buffer(1.0).exterior, [r]) >>> t.area 6.5507620529190334 Rectangular polygons occur commonly, and can be conveniently constructed using the :func:`shapely.geometry.box()` function. .. function:: shapely.geometry.box(minx, miny, maxx, maxy, ccw=True) Makes a rectangular polygon from the provided bounding box values, with counter-clockwise order by default. `New in version 1.2.9`. For example: .. code-block:: pycon >>> from shapely.geometry import box >>> b = box(0.0, 0.0, 1.0, 1.0) >>> b >>> list(b.exterior.coords) [(1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0), (1.0, 0.0)] This is the first appearance of an explicit polygon handedness in Shapely. To obtain a polygon with a known orientation, use :func:`shapely.geometry.polygon.orient()`: .. function:: shapely.geometry.polygon.orient(polygon, sign=1.0) Returns a properly oriented copy of the given polygon. The signed area of the result will have the given sign. A sign of 1.0 means that the coordinates of the product's exterior ring will be oriented counter-clockwise. `New in version 1.2.10`. .. _collections: Collections ----------- Heterogeneous collections of geometric objects may result from some Shapely operations. For example, two `LineStrings` may intersect along a line and at a point. To represent these kind of results, Shapely provides frozenset_-like, immutable collections of geometric objects. The collections may be homogeneous (`MultiPoint` etc.) or heterogeneous. .. code-block:: python >>> a = LineString([(0, 0), (1, 1), (1,2), (2,2)]) >>> b = LineString([(0, 0), (1, 1), (2,1), (2,2)]) >>> x = a.intersection(b) >>> x >>> from pprint import pprint >>> pprint(list(x)) [, ] .. plot:: code/geometrycollection.py :class: figure Figure 5. a) a green and a yellow line that intersect along a line and at a single point; b) the intersection (in blue) is a collection containing one `LineString` and one `Point`. Members of a `GeometryCollection` are accessed via the `geoms` property or via the iterator protocol using ``in`` or ``list()``. .. code-block:: pycon >>> pprint(list(x.geoms)) [, ] >>> pprint(list(x)) [, ] Homogeneous collections can also be sliced, resulting in a new object of the same type. .. code-block:: pycon >>> from shapely.geometry import MultiPoint >>> m = MultiPoint([(0, 0), (1, 1), (1,2), (2,2)]) >>> m[:1].wkt 'MULTIPOINT (0.0000000000000000 0.0000000000000000)' >>> m[3:].wkt 'MULTIPOINT (2.0000000000000000 2.0000000000000000)' >>> m[4:].wkt 'GEOMETRYCOLLECTION EMPTY' `New in version 1.2.14`. .. note:: When possible, it is better to use one of the homogeneous collection types described below. .. _multipoints: Collections of Points --------------------- .. class:: MultiPoint(points) The `MultiPoint` constructor takes a sequence of ``(x, y[, z ])`` point tuples. A `MultiPoint` has zero area and zero length. .. code-block:: pycon >>> from shapely.geometry import MultiPoint >>> points = MultiPoint([(0.0, 0.0), (1.0, 1.0)]) >>> points.area 0.0 >>> points.length 0.0 Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple. .. code-block:: pycon >>> points.bounds (0.0, 0.0, 1.0, 1.0) Members of a multi-point collection are accessed via the ``geoms`` property or via the iterator protocol using ``in`` or :func:`list`. .. code-block:: pycon >>> import pprint >>> pprint.pprint(list(points.geoms)) [, ] >>> pprint.pprint(list(points)) [, ] The constructor also accepts another `MultiPoint` instance or an unordered sequence of `Point` instances, thereby making copies. .. code-block:: pycon >>> MultiPoint([Point(0, 0), Point(1, 1)]) .. _multilinestrings: Collections of Lines -------------------- .. class:: MultiLineString(lines) The `MultiLineString` constructor takes a sequence of line-like sequences or objects. .. plot:: code/multilinestring.py Figure 6. On the left, a `simple`, disconnected `MultiLineString`, and on the right, a non-simple `MultiLineString`. The points defining the objects are shown in gray, the boundaries of the objects in black. A `MultiLineString` has zero area and non-zero length. .. code-block:: pycon >>> from shapely.geometry import MultiLineString >>> coords = [((0, 0), (1, 1)), ((-1, 0), (1, 0))] >>> lines = MultiLineString(coords) >>> lines.area 0.0 >>> lines.length 3.4142135623730949 Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple. .. code-block:: pycon >>> lines.bounds (-1.0, 0.0, 1.0, 1.0) Its members are instances of `LineString` and are accessed via the ``geoms`` property or via the iterator protocol using ``in`` or ``list()``. .. code-block:: pycon >>> len(lines.geoms) 2 >>> pprint.pprint(list(lines.geoms)) [, ] >>> pprint.pprint(list(lines)) [, ] The constructor also accepts another instance of `MultiLineString` or an unordered sequence of `LineString` instances, thereby making copies. .. code-block:: pycon >>> MultiLineString(lines) >>> MultiLineString(lines.geoms) .. _multipolygons: Collections of Polygons ----------------------- .. class:: MultiPolygon(polygons) The `MultiPolygon` constructor takes a sequence of exterior ring and hole list tuples: [((a1, ..., aM), [(b1, ..., bN), ...]), ...]. More clearly, the constructor also accepts an unordered sequence of `Polygon` instances, thereby making copies. .. code-block:: pycon >>> polygons = MultiPolygon([polygon, s, t]) >>> len(polygons.geoms) 3 .. plot:: code/multipolygon.py Figure 7. On the left, a `valid` `MultiPolygon` with 2 members, and on the right, a `MultiPolygon` that is invalid because its members touch at an infinite number of points (along a line). Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple. .. code-block:: pycon >>> polygons.bounds (-1.0, -1.0, 2.0, 2.0) Its members are instances of `Polygon` and are accessed via the ``geoms`` property or via the iterator protocol using ``in`` or ``list()``. .. code-block:: pycon >>> len(polygons.geoms) 3 >>> len(polygons) 3 .. _empties: Empty features -------------- An "empty" feature is one with a point set that coincides with the empty set; not ``None``, but like ``set([])``. Empty features can be created by calling the various constructors with no arguments. Almost no operations are supported by empty features. .. code-block:: pycon >>> line = LineString() >>> line.is_empty True >>> line.length 0.0 >>> line.bounds () >>> line.coords [] The coordinates of a empty feature can be set, after which the geometry is no longer empty. .. code-block:: pycon >>> line.coords = [(0, 0), (1, 1)] >>> line.is_empty False >>> line.length 1.4142135623730951 >>> line.bounds (0.0, 0.0, 1.0, 1.0) Linear Referencing Methods -------------------------- It can be useful to specify position along linear features such as `LineStrings` and `MultiLineStrings` with a 1-dimensional referencing system. Shapely supports linear referencing based on length or distance, evaluating the distance along a geometric object to the projection of a given point, or the point at a given distance along the object. .. note:: Linear referencing methods require GEOS 3.2.0 or later. .. method:: object.interpolate(distance[, normalized=False]) Return a point at the specified distance along a linear geometric object. If the `normalized` arg is ``True``, the distance will be interpreted as a fraction of the geometric object's length. .. code-block:: pycon >>> ip = LineString([(0, 0), (0, 1), (1, 1)]).interpolate(1.5) >>> ip >>> ip.wkt 'POINT (0.5000000000000000 1.0000000000000000)' >>> LineString([(0, 0), (0, 1), (1, 1)]).interpolate(0.75, normalized=True).wkt 'POINT (0.5000000000000000 1.0000000000000000)' .. method:: object.project(other[, normalized=False]) Returns the distance along this geometric object to a point nearest the `other` object. If the `normalized` arg is ``True``, return the distance normalized to the length of the object. The :meth:`project` method is the inverse of :meth:`interpolate`. .. code-block:: pycon >>> LineString([(0, 0), (0, 1), (1, 1)]).project(ip) 1.5 >>> LineString([(0, 0), (0, 1), (1, 1)]).project(ip, normalized=True) 0.75 For example, the linear referencing methods might be used to cut lines at a specified distance. .. code-block:: python def cut(line, distance): # Cuts a line in two at a distance from its starting point if distance <= 0.0 or distance >= line.length: return [LineString(line)] coords = list(line.coords) for i, p in enumerate(coords): pd = line.project(Point(p)) if pd == distance: return [ LineString(coords[:i+1]), LineString(coords[i:])] if pd > distance: cp = line.interpolate(distance) return [ LineString(coords[:i] + [(cp.x, cp.y)]), LineString([(cp.x, cp.y)] + coords[i:])] .. code-block:: pycon >>> line = LineString([(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0)]) >>> pprint([list(x.coords) for x in cut(line, 1.0)]) [[(0.0, 0.0), (1.0, 0.0)], [(1.0, 0.0), (2.0, 0.0), (3.0, 0.0), (4.0, 0.0), (5.0, 0.0)]] >>> pprint([list(x.coords) for x in cut(line, 2.5)]) [[(0.0, 0.0), (1.0, 0.0), (2.0, 0.0), (2.5, 0.0)], [(2.5, 0.0), (3.0, 0.0), (4.0, 0.0), (5.0, 0.0)]] .. _predicates: Predicates and Relationships ============================ Objects of the types explained in :ref:`objects` provide standard [1]_ predicates as attributes (for unary predicates) and methods (for binary predicates). Whether unary or binary, all return ``True`` or ``False``. .. _unary-predicates: Unary Predicates ---------------- Standard unary predicates are implemented as read-only property attributes. An example will be shown for each. .. attribute:: object.has_z Returns ``True`` if the feature has not only `x` and `y`, but also `z` coordinates for 3D (or so-called, 2.5D) geometries. .. code-block:: pycon >>> Point(0, 0).has_z False >>> Point(0, 0, 0).has_z True .. attribute:: object.is_ccw Returns ``True`` if coordinates are in counter-clockwise order (bounding a region with positive signed area). This method applies to `LinearRing` objects only. `New in version 1.2.10`. .. code-block:: pycon >>> LinearRing([(1,0), (1,1), (0,0)]).is_ccw True A ring with an undesired orientation can be reversed like this: .. code-block:: pycon >>> ring = LinearRing([(0,0), (1,1), (1,0)]) >>> ring.is_ccw False >>> ring.coords = list(ring.coords)[::-1] >>> ring.is_ccw True .. attribute:: object.is_empty Returns ``True`` if the feature's `interior` and `boundary` (in point set terms) coincide with the empty set. .. code-block:: pycon >>> Point().is_empty True >>> Point(0, 0).is_empty False .. note:: With the help of the :mod:`operator` module's :func:`attrgetter` function, unary predicates such as ``is_empty`` can be easily used as predicates for the built in :func:`filter` or :func:`itertools.ifilter`. .. code-block:: pycon >>> from operator import attrgetter >>> empties = filter(attrgetter('is_empty'), [Point(), Point(0, 0)]) >>> len(empties) 1 .. attribute:: object.is_ring Returns ``True`` if the feature is closed. A closed feature's `boundary` coincides with the empty set. .. code-block:: pycon >>> LineString([(0, 0), (1, 1), (1, -1)]).is_ring False >>> LinearRing([(0, 0), (1, 1), (1, -1)]).is_ring True This property is applicable to `LineString` and `LinearRing` instances, but meaningless for others. .. attribute:: object.is_simple Returns ``True`` if the feature does not cross itself. .. note:: The simplicity test is meaningful only for `LineStrings` and `LinearRings`. .. code-block:: pycon >>> LineString([(0, 0), (1, 1), (1, -1), (0, 1)]).is_simple False Operations on non-simple `LineStrings` are fully supported by Shapely. .. attribute:: object.is_valid Returns ``True`` if a feature is "valid" in the sense of [1]_. A valid `LinearRing` may not cross itself or touch itself at a single point. A valid `Polygon` may not possess any overlapping exterior or interior rings. A valid `MultiPolygon` may not collect any overlapping polygons. Operations on invalid features may fail. .. code-block:: pycon >>> MultiPolygon([Point(0, 0).buffer(2.0), Point(1, 1).buffer(2.0)]).is_valid False The two points above are close enough that the polygons resulting from the buffer operations (explained in a following section) overlap. .. note:: The ``is_valid`` predicate can be used to write a validating decorator that could ensure that only valid objects are returned from a constructor function. .. code-block:: python from functools import wraps def validate(func): @wraps(func) def wrapper(*args, **kwargs): ob = func(*args, **kwargs) if not ob.is_valid: raise TopologicalError( "Given arguments do not determine a valid geometric object") return ob return wrapper .. code-block:: pycon >>> @validate ... def ring(coordinates): ... return LinearRing(coordinates) ... >>> coords = [(0, 0), (1, 1), (1, -1), (0, 1)] >>> ring(coords) Traceback (most recent call last): File "", line 1, in File "", line 7, in wrapper shapely.geos.TopologicalError: Given arguments do not determine a valid geometric object .. _binary-predicates: Binary Predicates ----------------- Standard binary predicates are implemented as methods. These predicates evaluate topological, set-theoretic relationships. In a few cases the results may not be what one might expect starting from different assumptions. All take another geometric object as argument and return ``True`` or ``False``. .. method:: object.__eq__(other) Returns ``True`` if the two objects are of the same geometric type, and the coordinates of the two objects match precisely. .. method:: object.equals(other) Returns ``True`` if the set-theoretic `boundary`, `interior`, and `exterior` of the object coincide with those of the other. The coordinates passed to the object constructors are of these sets, and determine them, but are not the entirety of the sets. This is a potential "gotcha" for new users. Equivalent lines, for example, can be constructed differently. .. code-block:: pycon >>> a = LineString([(0, 0), (1, 1)]) >>> b = LineString([(0, 0), (0.5, 0.5), (1, 1)]) >>> c = LineString([(0, 0), (0, 0), (1, 1)]) >>> a.equals(b) True >>> a == b False >>> b.equals(c) True >>> b == c False .. method:: object.almost_equals(other[, decimal=6]) Returns ``True`` if the object is approximately equal to the `other` at all points to specified `decimal` place precision. .. method:: object.contains(other) Returns ``True`` if no points of `other` lie in the exterior of the `object` and at least one point of the interior of `other` lies in the interior of `object`. This predicate applies to all types, and is inverse to :meth:`within`. The expression ``a.contains(b) == b.within(a)`` always evaluates to ``True``. .. code-block:: pycon >>> coords = [(0, 0), (1, 1)] >>> LineString(coords).contains(Point(0.5, 0.5)) True >>> Point(0.5, 0.5).within(LineString(coords)) True A line's endpoints are part of its `boundary` and are therefore not contained. .. code-block:: pycon >>> LineString(coords).contains(Point(1.0, 1.0)) False .. note:: Binary predicates can be used directly as predicates for ``filter()`` or ``itertools.ifilter()``. .. code-block:: pycon >>> line = LineString(coords) >>> contained = filter(line.contains, [Point(), Point(0.5, 0.5)]) >>> len(contained) 1 >>> [p.wkt for p in contained] ['POINT (0.5000000000000000 0.5000000000000000)'] .. method:: object.crosses(other) Returns ``True`` if the `interior` of the object intersects the `interior` of the other but does not contain it, and the dimension of the intersection is less than the dimension of the one or the other. .. code-block:: pycon >>> LineString(coords).crosses(LineString([(0, 1), (1, 0)])) True A line does not cross a point that it contains. .. code-block:: pycon >>> LineString(coords).crosses(Point(0.5, 0.5)) False .. method:: object.disjoint(other) Returns ``True`` if the `boundary` and `interior` of the object do not intersect at all with those of the other. .. code-block:: pycon >>> Point(0, 0).disjoint(Point(1, 1)) True This predicate applies to all types and is the inverse of :meth:`intersects`. .. method:: object.intersects(other) Returns ``True`` if the `boundary` and `interior` of the object intersect in any way with those of the other. In other words, geometric objects intersect if they have any boundary or interior point in common. .. method:: object.touches(other) Returns ``True`` if the objects have at least one point in common and their interiors do not intersect with any part of the other. Overlapping features do not therefore `touch`, another potential "gotcha". For example, the following lines touch at ``(1, 1)``, but do not overlap. .. code-block:: pycon >>> a = LineString([(0, 0), (1, 1)]) >>> b = LineString([(1, 1), (2, 2)]) >>> a.touches(b) True .. method:: object.within(other) Returns ``True`` if the object's `boundary` and `interior` intersect only with the `interior` of the other (not its `boundary` or `exterior`). This applies to all types and is the inverse of :meth:`contains`. Used in a ``sorted()`` `key`, :meth:`within` makes it easy to spatially sort objects. Let's say we have 4 stereotypic features: a point that is contained by a polygon which is itself contained by another polygon, and a free spirited point contained by none .. code-block:: pycon >>> a = Point(2, 2) >>> b = Polygon([[1, 1], [1, 3], [3, 3], [3, 1]]) >>> c = Polygon([[0, 0], [0, 4], [4, 4], [4, 0]]) >>> d = Point(-1, -1) and that copies of these are collected into a list .. code-block:: pycon >>> features = [c, a, d, b, c] that we'd prefer to have ordered as ``[d, c, c, b, a]`` in reverse containment order. As explained in the Python `Sorting HowTo`_, we can define a key function that operates on each list element and returns a value for comparison. Our key function will be a wrapper class that implements ``__lt__()`` using Shapely's binary :meth:`within` predicate. .. code-block:: python class Within(object): def __init__(self, o): self.o = o def __lt__(self, other): return self.o.within(other.o) As the howto says, the `less than` comparison is guaranteed to be used in sorting. That's what we'll rely on to spatially sort, and the reason why we use :meth:`within` in reverse instead of :meth:`contains`. Trying it out on features `d` and `c`, we see that it works. .. code-block:: pycon >>> d < c True >>> Within(d) < Within(c) False It also works on the list of features, producing the order we want. .. code-block:: pycon >>> [d, c, c, b, a] == sorted(features, key=Within, reverse=True) True DE-9IM Relationships -------------------- The :meth:`relate` method tests all the DE-9IM [4]_ relationships between objects, of which the named relationship predicates above are a subset. .. method:: object.relate(other) Returns a string representation of the DE-9IM matrix of relationships between an object's `interior`, `boundary`, `exterior` and those of another geometric object. The named relationship predicates (:meth:`contains`, etc.) are typically implemented as wrappers around :meth:`relate`. Two different points have mainly ``F`` (false) values in their matrix; the intersection of their `external` sets (the 9th element) is a ``2`` dimensional object (the rest of the plane). The intersection of the `interior` of one with the `exterior` of the other is a ``0`` dimensional object (3rd and 7th elements of the matrix). .. code-block:: pycon >>> Point(0, 0).relate(Point(1, 1)) 'FF0FFF0F2' The matrix for a line and a point on the line has more "true" (not ``F``) elements. .. code-block:: pycon >>> Point(0, 0).relate(LineString([(0, 0), (1, 1)])) 'F0FFFF102' .. method:: object.relate_pattern(other, pattern) Returns True if the DE-9IM string code for the relationship between the geometries satisfies the pattern, otherwise False. The :meth:`relate_pattern` compares the DE-9IM code string for two geometries against a specified pattern. If the string matches the pattern then ``True`` is returned, otherwise ``False``. The pattern specified can be an exact match (``0``, ``1`` or ``2``), a boolean match (``T`` or ``F``), or a wildcard (``*``). For example, the pattern for the `within` predicate is ``T*****FF*``. .. code-block:: pycon >> point = Point(0.5, 0.5) >> square = Polygon([(0, 0), (0, 1), (1, 1), (1, 0)]) >> square.relate_pattern(point, 'T*****FF*') True >> point.within(square) True Note that the order or the geometries is significant, as demonstrated below. In this example the square contains the point, but the point does not contain the square. .. code-block:: pycon >>> point.relate(square) '0FFFFF212' >>> square.relate(point) '0F2FF1FF2' Further discussion of the DE-9IM matrix is beyond the scope of this manual. See [4]_ and http://pypi.python.org/pypi/de9im. .. _analysis-methods: Spatial Analysis Methods ======================== As well as boolean attributes and methods, Shapely provides analysis methods that return new geometric objects. .. _set-theoretic-methods: Set-theoretic Methods --------------------- Almost every binary predicate method has a counterpart that returns a new geometric object. In addition, the set-theoretic `boundary` of an object is available as a read-only attribute. .. attribute:: object.boundary Returns a lower dimensional object representing the object's set-theoretic `boundary`. The boundary of a polygon is a line, the boundary of a line is a collection of points. The boundary of a point is an empty (null) collection. .. code-block:: pycon >> coords = [((0, 0), (1, 1)), ((-1, 0), (1, 0))] >>> lines = MultiLineString(coords) >>> lines.boundary >>> pprint(list(lines.boundary)) [, , , ] >>> lines.boundary.boundary >>> lines.boundary.boundary.is_empty True See the figures in :ref:`linestrings` and :ref:`multilinestrings` for the illustration of lines and their boundaries. .. attribute:: object.centroid Returns a representation of the object's geometric centroid (point). .. code-block:: pycon >>> LineString([(0, 0), (1, 1)]).centroid >>> LineString([(0, 0), (1, 1)]).centroid.wkt 'POINT (0.5000000000000000 0.5000000000000000)' .. note:: The centroid of an object might be one of its points, but this is not guaranteed. .. method:: object.difference(other) Returns a representation of the points making up this geometric object that do not make up the *other* object. .. code-block:: pycon >>> a = Point(1, 1).buffer(1.5) >>> b = Point(2, 1).buffer(1.5) >>> a.difference(b) .. note:: The :meth:`buffer` method is used to produce approximately circular polygons in the examples of this section; it will be explained in detail later in this manual. .. plot:: code/difference.py Figure 8. Differences between two approximately circular polygons. .. note:: Shapely can not represent the difference between an object and a lower dimensional object (such as the difference between a polygon and a line or point) as a single object, and in these cases the difference method returns a copy of the object named ``self``. .. method:: object.intersection(other) Returns a representation of the intersection of this object with the `other` geometric object. .. code-block:: pycon >>> a = Point(1, 1).buffer(1.5) >>> b = Point(2, 1).buffer(1.5) >>> a.intersection(b) See the figure under :meth:`symmetric_difference` below. .. method:: object.symmetric_difference(other) Returns a representation of the points in this object not in the `other` geometric object, and the points in the `other` not in this geometric object. .. code-block:: pycon >>> a = Point(1, 1).buffer(1.5) >>> b = Point(2, 1).buffer(1.5) >>> a.symmetric_difference(b) .. plot:: code/intersection-sym-difference.py .. method:: object.union(other) Returns a representation of the union of points from this object and the `other` geometric object. The type of object returned depends on the relationship between the operands. The union of polygons (for example) will be a polygon or a multi-polygon depending on whether they intersect or not. .. code-block:: pycon >>> a = Point(1, 1).buffer(1.5) >>> b = Point(2, 1).buffer(1.5) >>> a.union(b) The semantics of these operations vary with type of geometric object. For example, compare the boundary of the union of polygons to the union of their boundaries. .. code-block:: pycon >>> a.union(b).boundary >>> a.boundary.union(b.boundary) .. plot:: code/union.py .. note:: :meth:`union` is an expensive way to find the cumulative union of many objects. See :func:`shapely.ops.cascaded_union` for a more effective method. Constructive Methods -------------------- Shapely geometric object have several methods that yield new objects not derived from set-theoretic analysis. .. method:: object.buffer(distance, resolution=16, cap_style=1, join_style=1, mitre_limit=5.0) Returns an approximate representation of all points within a given `distance` of the this geometric object. The styles of caps are specified by integer values: 1 (round), 2 (flat), 3 (square). These values are also enumerated by the object :class:`shapely.geometry.CAP_STYLE` (see below). The styles of joins between offset segments are specified by integer values: 1 (round), 2 (mitre), and 3 (bevel). These values are also enumerated by the object :class:`shapely.geometry.JOIN_STYLE` (see below). .. data:: shapely.geometry.CAP_STYLE ========= ===== Attribute Value ========= ===== round 1 flat 2 square 3 ========= ===== .. data:: shapely.geometry.JOIN_STYLE ========= ===== Attribute Value ========= ===== round 1 mitre 2 bevel 3 ========= ===== .. code-block:: pycon >>> from shapely.geometry import CAP_STYLE, JOIN_STYLE >>> CAP_STYLE.flat 2 >>> JOIN_STYLE.bevel 3 A positive distance has an effect of dilation; a negative distance, erosion. The optional `resolution` argument determines the number of segments used to approximate a quarter circle around a point. .. code-block:: pycon >>> line = LineString([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) >>> dilated = line.buffer(0.5) >>> eroded = dilated.buffer(-0.3) .. plot:: code/buffer.py Figure 9. Dilation of a line (left) and erosion of a polygon (right). New object is shown in blue. The default (`resolution` of 16) buffer of a point is a polygonal patch with 99.8% of the area of the circular disk it approximates. .. code-block:: pycon >>> p = Point(0, 0).buffer(10.0) >>> len(p.exterior.coords) 66 >>> p.area 313.65484905459385 With a `resolution` of 1, the buffer is a square patch. .. code-block:: pycon >>> q = Point(0, 0).buffer(10.0, 1) >>> len(q.exterior.coords) 5 >>> q.area 200.0 Passed a `distance` of 0, :meth:`buffer` can be used to "clean" self-touching or self-crossing polygons such as the classic "bowtie". .. code-block:: pycon >>> coords = [(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)] >>> bowtie = Polygon(coords) >>> bowtie.is_valid False >>> clean = bowtie.buffer(0) >>> clean.is_valid True >>> clean >>> len(clean) 2 >>> list(clean[0].exterior.coords) [(0.0, 0.0), (0.0, 2.0), (1.0, 1.0), (0.0, 0.0)] >>> list(clean[1].exterior.coords) [(1.0, 1.0), (2.0, 2.0), (2.0, 0.0), (1.0, 1.0)] Buffering splits the polygon in two at the point where they touch. .. attribute:: object.convex_hull Returns a representation of the smallest convex `Polygon` containing all the points in the object unless the number of points in the object is less than three. For two points, the convex hull collapses to a `LineString`; for 1, a `Point`. .. code-block:: pycon >>> Point(0, 0).convex_hull >>> MultiPoint([(0, 0), (1, 1)]).convex_hull >>> MultiPoint([(0, 0), (1, 1), (1, -1)]).convex_hull .. plot:: code/convex_hull.py Figure 10. Convex hull (blue) of 2 points (left) and of 6 points (right). .. attribute:: object.envelope Returns a representation of the point or smallest rectangular polygon (with sides parallel to the coordinate axes) that contains the object. .. code-block:: pycon >>> Point(0, 0).envelope >>> MultiPoint([(0, 0), (1, 1)]).envelope .. method:: object.parallel_offset(distance, side, resolution=16, join_style=1, mitre_limit=5.0) Returns a LineString or MultiLineString geometry at a distance from the object on its right or its left side. Distance must be a positive float value. The side parameter may be 'left' or 'right'. The resolution of the offset around each vertex of the object is parameterized as in the buffer method. The join style is for outside corners between line segments. Accepted integer values are 1 (round), 2 (mitre), and 3 (bevel). See also :data:`shapely.geometry.JOIN_STYLE`. Severely mitered corners can be controlled by the mitre_limit parameter (spelled in British English, en-gb). The ratio of the distance from the corner to the end of the mitred offset corner is the miter ratio. Corners with a ratio which exceed the limit will be beveled. .. note:: This method is only available for `LinearRing` and `LineString` objects. .. plot:: code/parallel_offset.py Figure 11. Three styles of parallel offset lines on the left side of a simple line string (its starting point shown as a circle) and one offset on the right side, a multipart. The effect of the `mitre_limit` parameter is shown below. .. plot:: code/parallel_offset_mitre.py Figure 12. Large and small mitre_limit values for left and right offsets. .. method:: object.simplify(tolerance, preserve_topology=True) Returns a simplified representation of the geometric object. All points in the simplified object will be within the `tolerance` distance of the original geometry. By default a slower algorithm is used that preserves topology. If preserve topology is set to ``False`` the much quicker Douglas-Peucker algorithm [6]_ is used. .. code-block:: pycon >>> p = Point(0.0, 0.0) >>> x = p.buffer(1.0) >>> x.area 3.1365484905459389 >>> len(x.exterior.coords) 66 >>> s = x.simplify(0.05, preserve_topology=False) >>> s.area 3.0614674589207187 >>> len(s.exterior.coords) 17 .. plot:: code/simplify.py Figure 13. Simplification of a nearly circular polygon using a tolerance of 0.2 (left) and 0.5 (right). .. note:: `Invalid` geometric objects may result from simplification that does not preserve topology. Affine Transformations ====================== A collection of affine transform functions are in the :mod:`shapely.affinity` module, which return transformed geometries by either directly supplying coefficients to an affine transformation matrix, or by using a specific, named transform (`rotate`, `scale`, etc.). The functions can be used with all geometry types (except `GeometryCollection`), and 3D types are either preserved or supported by 3D affine transformations. `New in version 1.2.17`. .. function:: shapely.affinity.affine_transform(geom, matrix) Returns a transformed geometry using an affine transformation matrix. The coefficient ``matrix`` is provided as a list or tuple with 6 or 12 items for 2D or 3D transformations, respectively. For 2D affine transformations, the 6 parameter ``matrix`` is: ``[a, b, d, e, xoff, yoff]`` which represents the augmented matrix: .. math:: \begin{bmatrix} x' & y' & 1 \end{bmatrix} = \begin{bmatrix} x & y & 1 \end{bmatrix} \begin{bmatrix} a & b & x_\mathrm{off} \\ d & e & y_\mathrm{off} \\ 0 & 0 & 1 \end{bmatrix} or the equations for the transformed coordinates: .. math:: x' &= a x + b y + x_\mathrm{off} \\ y' &= d x + e y + y_\mathrm{off}. For 3D affine transformations, the 12 parameter ``matrix`` is: ``[a, b, c, d, e, f, g, h, i, xoff, yoff, zoff]`` which represents the augmented matrix: .. math:: \begin{bmatrix} x' & y' & z' & 1 \end{bmatrix} = \begin{bmatrix} x & y & z & 1 \end{bmatrix} \begin{bmatrix} a & b & c & x_\mathrm{off} \\ d & e & f & y_\mathrm{off} \\ g & h & i & z_\mathrm{off} \\ 0 & 0 & 0 & 1 \end{bmatrix} or the equations for the transformed coordinates: .. math:: x' &= a x + b y + c z + x_\mathrm{off} \\ y' &= d x + e y + f z + y_\mathrm{off} \\ z' &= g x + h y + i z + z_\mathrm{off}. .. function:: shapely.affinity.rotate(geom, angle, origin='center', use_radians=False) Returns a rotated geometry on a 2D plane. The angle of rotation can be specified in either degrees (default) or radians by setting ``use_radians=True``. Positive angles are counter-clockwise and negative are clockwise rotations. The point of origin can be a keyword ``'center'`` for the bounding box center (default), ``'centroid'`` for the geometry's centroid, a `Point` object or a coordinate tuple ``(x0, y0)``. The affine transformation matrix for 2D rotation with angle :math:`\theta` is: .. math:: \begin{bmatrix} \cos{\theta} & -\sin{\theta} & x_\mathrm{off} \\ \sin{\theta} & \cos{\theta} & y_\mathrm{off} \\ 0 & 0 & 1 \end{bmatrix} where the offsets are calculated from the origin :math:`(x_0, y_0)`: .. math:: x_\mathrm{off} &= x_0 - x_0 \cos{\theta} + y_0 \sin{\theta} \\ y_\mathrm{off} &= y_0 - x_0 \sin{\theta} - y_0 \cos{\theta} .. code-block:: pycon >>> from shapely import affinity >>> line = LineString([(1, 3), (1, 1), (4, 1)]) >>> rotated_a = affinity.rotate(line, 90) >>> rotated_b = affinity.rotate(line, 90, origin='centroid') .. plot:: code/rotate.py Figure 14. Rotation of a `LineString` (gray) by an angle of 90° counter-clockwise (blue) using different origins. .. function:: shapely.affinity.scale(geom, xfact=1.0, yfact=1.0, zfact=1.0, origin='center') Returns a scaled geometry, scaled by factors along each dimension. The point of origin can be a keyword ``'center'`` for the 2D bounding box center (default), ``'centroid'`` for the geometry's 2D centroid, a `Point` object or a coordinate tuple ``(x0, y0, z0)``. Negative scale factors will mirror or reflect coordinates. The general 3D affine transformation matrix for scaling is: .. math:: \begin{bmatrix} x_\mathrm{fact} & 0 & 0 & x_\mathrm{off} \\ 0 & y_\mathrm{fact} & 0 & y_\mathrm{off} \\ 0 & 0 & z_\mathrm{fact} & z_\mathrm{off} \\ 0 & 0 & 0 & 1 \end{bmatrix} where the offsets are calculated from the origin :math:`(x_0, y_0, z_0)`: .. math:: x_\mathrm{off} &= x_0 - x_0 x_\mathrm{fact} \\ y_\mathrm{off} &= y_0 - y_0 y_\mathrm{fact} \\ z_\mathrm{off} &= z_0 - z_0 z_\mathrm{fact} .. code-block:: pycon >>> triangle = Polygon([(1, 1), (2, 3), (3, 1)]) >>> triangle_a = affinity.scale(triangle, xfact=1.5, yfact=-1) >>> triangle_a.exterior.coords[:] [(0.5, 3.0), (2.0, 1.0), (3.5, 3.0), (0.5, 3.0)] >>> triangle_b = affinity.scale(triangle, xfact=2, origin=(1,1)) >>> triangle_b.exterior.coords[:] [(1.0, 1.0), (3.0, 3.0), (5.0, 1.0), (1.0, 1.0)] .. plot:: code/scale.py Figure 15. Scaling of a gray triangle to blue result: a) by a factor of 1.5 along x-direction, with reflection across y-axis; b) by a factor of 2 along x-direction with custom origin at (1, 1). .. function:: shapely.affinity.skew(geom, xs=0.0, ys=0.0, origin='center', use_radians=False) Returns a skewed geometry, sheared by angles along x and y dimensions. The shear angle can be specified in either degrees (default) or radians by setting ``use_radians=True``. The point of origin can be a keyword ``'center'`` for the bounding box center (default), ``'centroid'`` for the geometry's centroid, a `Point` object or a coordinate tuple ``(x0, y0)``. The general 2D affine transformation matrix for skewing is: .. math:: \begin{bmatrix} 1 & \tan{x_s} & x_\mathrm{off} \\ \tan{y_s} & 1 & y_\mathrm{off} \\ 0 & 0 & 1 \end{bmatrix} where the offsets are calculated from the origin :math:`(x_0, y_0)`: .. math:: x_\mathrm{off} &= -y_0 \tan{x_s} \\ y_\mathrm{off} &= -x_0 \tan{y_s} .. plot:: code/skew.py Figure 16. Skewing of a gray "R" to blue result: a) by a shear angle of 20° along the x-direction and an origin at (1, 1); b) by a shear angle of 30° along the y-direction, using default origin. .. function:: shapely.affinity.translate(geom, xoff=0.0, yoff=0.0, zoff=0.0) Returns a translated geometry shifted by offsets along each dimension. The general 3D affine transformation matrix for translation is: .. math:: \begin{bmatrix} 1 & 0 & 0 & x_\mathrm{off} \\ 0 & 1 & 0 & y_\mathrm{off} \\ 0 & 0 & 1 & z_\mathrm{off} \\ 0 & 0 & 0 & 1 \end{bmatrix} Other Transformations ===================== Shapely supports map projections and other arbitrary transformations of geometric objects. .. function:: shapely.ops.transform(func, geom) Applies `func` to all coordinates of `geom` and returns a new geometry of the same type from the transformed coordinates. `func` maps x, y, and optionally z to output xp, yp, zp. The input parameters may iterable types like lists or arrays or single values. The output shall be of the same type: scalars in, scalars out; lists in, lists out. `New in version 1.2.18`. For example, here is an identity function applicable to both types of input (scalar or array). .. code-block:: python def id_func(x, y, z=None): return tuple(filter(None, [x, y, z])) g2 = transform(id_func, g1) A partially applied transform function from pyproj satisfies the requirements for `func`. .. code-block:: python from shapely.ops import transform from functools import partial import pyproj project = partial( pyproj.transform, pyproj.Proj(init='epsg:4326'), pyproj.Proj(init='epsg:26913')) g2 = transform(project, g1) Lambda expressions such as the one in .. code-block:: python g2 = transform(lambda x, y, z=None: (x+1.0, y+1.0), g1) also satisfy the requirements for `func`. Other Operations ================ Merging Linear Features ----------------------- Sequences of touching lines can be merged into `MultiLineStrings` or `Polygons` using functions in the :mod:`shapely.ops` module. .. function:: shapely.ops.polygonize(lines) Returns an iterator over polygons constructed from the input `lines`. As with the :class:`MultiLineString` constructor, the input elements may be any line-like object. .. code-block:: pycon >>> from shapely.ops import polygonize >>> lines = [ ... ((0, 0), (1, 1)), ... ((0, 0), (0, 1)), ... ((0, 1), (1, 1)), ... ((1, 1), (1, 0)), ... ((1, 0), (0, 0)) ... ] >>> pprint(list(polygonize(lines))) [, ] .. function:: shapely.ops.polygonize_full(lines) Creates polygons from a source of lines, returning the polygons and leftover geometries. The source may be a MultiLineString, a sequence of LineString objects, or a sequence of objects than can be adapted to LineStrings. Returns a tuple of objects: (polygons, dangles, cut edges, invalid ring lines). Each are a geometry collection. Dangles are edges which have one or both ends which are not incident on another edge endpoint. Cut edges are connected at both ends but do not form part of polygon. Invalid ring lines form rings which are invalid (bowties, etc). `New in version 1.2.18.` .. code-block:: pycon >>> lines = [ ... ((0, 0), (1, 1)), ... ((0, 0), (0, 1)), ... ((0, 1), (1, 1)), ... ((1, 1), (1, 0)), ... ((1, 0), (0, 0)), ... ((5, 5), (6, 6)), ... ((1, 1), (100, 100)), ... ] >>> result, dangles, cuts, invalids = polygonize_full(lines) >>> len(result) 2 >>> list(result.geoms) [, ] >>> list(cuts.geoms) [, ] .. function:: shapely.ops.linemerge(lines) Returns a `LineString` or `MultiLineString` representing the merger of all contiguous elements of `lines`. As with :func:`shapely.ops.polygonize`, the input elements may be any line-like object. .. code-block:: python >>> from shapely.ops import linemerge >>> linemerge(lines) >>> pprint(list(linemerge(lines))) [, , ] Cascading Unions ---------------- The :func:`~shapely.ops.cascaded_union` function in `shapely.ops` is more efficient than accumulating with :meth:`union`. .. plot:: code/cascaded_union.py .. function:: shapely.ops.cascaded_union(geoms) Returns a representation of the union of the given geometric objects. .. code-block:: pycon >>> from shapely.ops import cascaded_union >>> polygons = [Point(i, 0).buffer(0.7) for i in range(5)] >>> cascaded_union(polygons) The function is particularly useful in dissolving `MultiPolygons`. .. code-block:: pycon >>> m = MultiPolygon(polygons) >>> m.area 7.6845438018375516 >>> cascaded_union(m).area 6.6103013551167971 .. note:: In 1.2.16 :func:`shapely.ops.cascaded_union` is superceded by :func:`shapely.ops.unary_union` if GEOS 3.2+ is used. The unary union function can operate on different geometry types, not only polygons as is the case for the older cascaded unions. .. function:: shapely.ops.unary_union(geoms) Returns a representation of the union of the given geometric objects. Delaunay triangulation ---------------------- The :func:`~shapely.ops.triangulate` function in `shapely.ops` calculates a Delaunay triangulation from a collection of points. .. plot:: code/triangulate.py .. function:: shapely.ops.triangulate(geom, tolerance=0.0, edges=False) Returns a Delaunary triangulation of the vertices of the input geometry. The source may be any geometry type. All vertices of the geometry will be used as the points of the triangulation. The `tolerance` keyword argument sets the snapping tolerance used to improve the robustness of the triangulation computation. A tolerance of 0.0 specifies that no snapping will take place. If the `edges` keyword argument is `False` a list of `Polygon` triangles will be returned. Otherwise a list of `LineString` edges is returned. `New in version 1.4.0` .. code-block:: pycon >>> from shapely.ops import triangulate >>> points = MultiPoint([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) >>> triangles = triangulate(points) >>> pprint([triangle.wkt for triangle in triangles]) ['POLYGON ((0 2, 0 0, 1 1, 0 2))', 'POLYGON ((0 2, 1 1, 2 2, 0 2))', 'POLYGON ((2 2, 1 1, 3 1, 2 2))', 'POLYGON ((3 1, 1 1, 1 0, 3 1))', 'POLYGON ((1 0, 1 1, 0 0, 1 0))'] Nearest points -------------- The :func:`~shapely.ops.nearest_points` function in `shapely.ops` calculates the nearest points in a pair of geometries. .. function:: shapely.ops.nearest_points(geom1, geom2) Returns a tuple of the nearest points in the input geometries. The points are returned in the same order as the input geometries. `New in version 1.4.0`. .. code-block:: pycon >>> from shapely.ops import nearest_points >>> triangle = Polygon([(0, 0), (1, 0), (0.5, 1), (0, 0)]) >>> square = Polygon([(0, 2), (1, 2), (1, 3), (0, 3), (0, 2)]) >>> [o.wkt for o in nearest_points(triangle, square)] ['POINT (0.5 1)', 'POINT (0.5 2)'] Note that the nearest points may not be existing vertices in the geometries. Snapping -------- The :func:`~shapely.ops.snap` function in `shapely.ops` snaps the vertices in one geometry to the vertices in a second geometry with a given tolerance. .. function:: shapely.ops.snap(geom1, geom2, tolerance) Snaps vertices in `geom1` to vertices in the `geom2`. A copy of the snapped geometry is returned. The input geometries are not modified. The `tolerance` argument specifies the minimum distance between vertices for them to be snapped. `New in version 1.5.0` .. code-block:: pycon >>> from shapely.ops import snap >>> square = Polygon([(1,1), (2, 1), (2, 2), (1, 2), (1, 1)]) >>> line = LineString([(0,0), (0.8, 0.8), (1.8, 0.95), (2.6, 0.5)]) >>> result = snap(line, square, 0.5) >>> result.wkt 'LINESTRING (0 0, 1 1, 2 1, 2.6 0.5)' Prepared Geometry Operations ---------------------------- Shapely geometries can be processed into a state that supports more efficient batches of operations. .. function:: prepared.prep(ob) Creates and returns a prepared geometric object. To test one polygon containment against a large batch of points, one should first use the :func:`prepared.prep` function. .. code-block:: pycon >>> from shapely.geometry import Point >>> from shapely.prepared import prep >>> points = [...] # large list of points >>> polygon = Point(0.0, 0.0).buffer(1.0) >>> prepared_polygon = prep(polygon) >>> prepared_polygon >>> hits = filter(prepared_polygon.contains, points) Prepared geometries instances have the following methods: ``contains``, ``contains_properly``, ``covers``, and ``intersects``. All have exactly the same arguments and usage as their counterparts in non-prepared geometric objects. Diagnostics ----------- .. function:: validation.explain_validity(ob): Returns a string explaining the validity or invalidity of the object. `New in version 1.2.1`. The messages may or may not have a representation of a problem point that can be parsed out. .. code-block:: pycon >>> coords = [(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)] >>> p = Polygon(coords) >>> from shapely.validation import explain_validity >>> explain_validity(p) 'Ring Self-intersection[1 1]' The Shapely version, GEOS library version, and GEOS C API version are accessible via :attr:`shapely.__version__`, :attr:`shapely.geos.geos_version_string`, and :attr:`shapely.geos.geos_capi_version`. .. code-block:: pycon >>> import shapely >>> shapely.__version__ '1.3.0' >>> import shapely.geos >>> shapely.geos.geos_version (3, 3, 0) >>> shapely.geos.geos_version_string '3.3.0-CAPI-1.7.0' STR-packed R-tree ================= Shapely provides an interface to the query-only GEOS R-tree packed using the Sort-Tile-Recursive algorithm. Pass a list of geometry objects to the STRtree constructor to create an R-tree that you can query with another geometric object. .. class:: strtree.STRtree(geometries) The `STRtree` constructor takes a sequence of geometric objects. These are copied and stored in the R-tree. `New in version 1.4.0`. Query-only means in this case that the R-tree, once created, is immutable. You cannot add or remove geometries. .. code-block:: pycon >>> from shapely.geometry import Point >>> from shapely.strtree import STRtree >>> points = [Point(i, i) for i in range(10)] >>> tree = STRtree(points) >>> tree.query(Point(2,2).buffer(0.99)) >>> [o.wkt for o in tree.query(Point(2,2).buffer(0.99))] ['POINT (2 2)'] >>> [o.wkt for o in tree.query(Point(2,2).buffer(1.0))] ['POINT (1 1)', 'POINT (2 2)', 'POINT (3 3)'] Interoperation ============== Shapely provides 4 avenues for interoperation with other software. Well-Known Formats ------------------ A `Well Known Text` (WKT) or `Well Known Binary` (WKB) representation [1]_ of any geometric object can be had via its :attr:`wkt` or :attr:`wkb` attribute. These representations allow interchange with many GIS programs. PostGIS, for example, trades in hex-encoded WKB. .. code-block:: pycon >>> Point(0, 0).wkt 'POINT (0.0000000000000000 0.0000000000000000)' >>> Point(0, 0).wkb.encode('hex') '010100000000000000000000000000000000000000' The `shapely.wkt` and `shapely.wkb` modules provide `dumps()` and `loads()` functions that work almost exactly as their `pickle` and `simplejson` module counterparts. To serialize a geometric object to a binary or text string, use ``dumps()``. To deserialize a string and get a new geometric object of the appropriate type, use ``loads()``. .. function:: shapely.wkb.dumps(ob) Returns a WKB representation of `ob`. .. function:: shapely.wkb.loads(wkb) Returns a geometric object from a WKB representation `wkb`. .. code-block:: pycon >> from shapely.wkb import dumps, loads >>> wkb = dumps(Point(0, 0)) >>> print wkb.encode('hex') 010100000000000000000000000000000000000000 >>> loads(wkb).wkt 'POINT (0.0000000000000000 0.0000000000000000)' All of Shapely's geometry types are supported by these functions. .. function:: shapely.wkt.dumps(ob) Returns a WKT representation of `ob`. .. function:: shapely.wkt.loads(wkt) Returns a geometric object from a WKT representation `wkt`. .. code-block:: pycon >> wkt = dumps(Point(0, 0)) >>> print wkt POINT (0.0000000000000000 0.0000000000000000) >>> loads(wkt).wkt 'POINT (0.0000000000000000 0.0000000000000000)' Numpy and Python Arrays ----------------------- All geometric objects with coordinate sequences (`Point`, `LinearRing`, `LineString`) provide the Numpy array interface and can thereby be converted or adapted to Numpy arrays. .. code-block:: pycon >>> from numpy import array >>> array(Point(0, 0)) array([ 0., 0.]) >>> array(LineString([(0, 0), (1, 1)])) array([[ 0., 0.], [ 1., 1.]]) The :func:`numpy.asarray` function does not copy coordinate values – at the price of slower Numpy access to the coordinates of Shapely objects. .. note:: The Numpy array interface is provided without a dependency on Numpy itself. The coordinates of the same types of geometric objects can be had as standard Python arrays of `x` and `y` values via the :attr:`xy` attribute. .. code-block:: pycon >>> Point(0, 0).xy (array('d', [0.0]), array('d', [0.0])) >>> LineString([(0, 0), (1, 1)]).xy (array('d', [0.0, 1.0]), array('d', [0.0, 1.0])) The :func:`shapely.geometry.asShape` family of functions can be used to wrap Numpy coordinate arrays so that they can then be analyzed using Shapely while maintaining their original storage. A 1 x 2 array can be adapted to a point .. code-block:: pycon >>> from shapely.geometry import asPoint >>> pa = asPoint(array([0.0, 0.0])) >>> pa.wkt 'POINT (0.0000000000000000 0.0000000000000000)' and a N x 2 array can be adapted to a line string .. code-block:: pycon >>> from shapely.geometry import asLineString >>> la = asLineString(array([[1.0, 2.0], [3.0, 4.0]])) >>> la.wkt 'LINESTRING (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)' There is no Numpy array representation of a polygon. Python Geo Interface -------------------- Any object that provides the GeoJSON-like `Python geo interface`_ can be adapted and used as a Shapely geometry using the :func:`shapely.geometry.asShape` or :func:`shapely.geometry.shape` functions. .. function:: shapely.geometry.asShape(context) Adapts the context to a geometry interface. The coordinates remain stored in the context. .. function:: shapely.geometry.shape(context) Returns a new, independent geometry with coordinates `copied` from the context. For example, a dictionary: .. code-block:: pycon >>> from shapely.geometry import shape >>> data = {"type": "Point", "coordinates": (0.0, 0.0)} >>> geom = shape(data) >>> geom.geom_type 'Point' >>> list(geom.coords) [(0.0, 0.0)] Or a simple placemark-type object: .. code-block:: pycon >>> class GeoThing(object): ... def __init__(self, d): ... self.__geo_interface__ = d >>> thing = GeoThing({"type": "Point", "coordinates": (0.0, 0.0)}) >>> geom = shape(thing) >>> geom.geom_type 'Point' >>> list(geom.coords) [(0.0, 0.0)] The GeoJSON-like mapping of a geometric object can be obtained using :func:`shapely.geometry.mapping`. .. function:: shapely.geometry.mapping(ob) Returns a new, independent geometry with coordinates `copied` from the context. `New in version 1.2.3`. For example, using the same `GeoThing` class: .. code-block:: pycon >>> from shapely.geometry import mapping >>> thing = GeoThing({"type": "Point", "coordinates": (0.0, 0.0)}) >>> m = mapping(thing) >>> m['type'] 'Point' >>> m['coordinates'] (0.0, 0.0)} Performance =========== Shapely uses the GEOS_ library for all operations. GEOS is written in C++ and used in many applications and you can expect that all operations are highly optimized. The creation of new geometries with many coordinates, however, involves some overhead that might slow down your code. .. versionadded:: 1.2.10 The :mod:`shapely.speedups` module contains performance enhancements written in C. They are automaticaly installed when Python has access to a compiler and GEOS development headers during installation. You can check if the speedups are installed with the :attr:`available` attribute. The constructor speedups are disabled by default. To enable the speedups call :func:`enable`. You can revert to the default implementation with :func:`disable`. .. code-block:: pycon >>> from shapely import speedups >>> speedups.available True >>> speedups.enable() Conclusion ========== We hope that you will enjoy and profit from using Shapely. Questions and comments are welcome on the GIS-Python email list_. This manual will be updated and improved regularly. Its source is available at http://github.com/Toblerity/Shapely/tree/master/docs/. References ========== .. [1] John R. Herring, Ed., “OpenGIS Implementation Specification for Geographic information - Simple feature access - Part 1: Common architecture,” Oct. 2006. .. [2] M.J. Egenhofer and John R. Herring, Categorizing Binary Topological Relations Between Regions, Lines, and Points in Geographic Databases, Orono, ME: University of Maine, 1991. .. [3] E. Clementini, P. Di Felice, and P. van Oosterom, “A Small Set of Formal Topological Relationships Suitable for End-User Interaction,” Third International Symposium on Large Spatial Databases (SSD). Lecture Notes in Computer Science no. 692, David Abel and Beng Chin Ooi, Eds., Singapore: Springer Verlag, 1993, pp. 277-295. .. [4] C. Strobl, “Dimensionally Extended Nine-Intersection Model (DE-9IM),” Encyclopedia of GIS, S. Shekhar and H. Xiong, Eds., Springer, 2008, pp. 240-245. [|Strobl-PDF|_] .. [5] Martin Davis, “JTS Technical Specifications,” Mar. 2003. [|JTS-PDF|_] .. [6] David H. Douglas and Thomas K. Peucker, “Algorithms for the Reduction of the Number of Points Required to Represent a Digitized Line or its Caricature,” Cartographica: The International Journal for Geographic Information and Geovisualization, vol. 10, Dec. 1973, pp. 112-122. .. _GEOS: http://trac.osgeo.org/geos/ .. _Java Topology Suite: http://www.vividsolutions.com/jts/jtshome.htm .. _JTS: http://www.vividsolutions.com/jts/jtshome.htm .. _PostGIS: http://postgis.refractions.net .. _record: http://pypi.python.org/pypi/Shapely .. _wiki: http://trac.gispython.org/lab/wiki/Shapely .. _Open Geospatial Consortium: http://www.opengeospatial.org/ .. _Davis: http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html .. _Understanding spatial relations: http://edndoc.esri.com/arcsde/9.1/general_topics/understand_spatial_relations.htm .. _Strobl-PDF: http://giswiki.hsr.ch/images/3/3d/9dem_springer.pdf .. |Strobl-PDF| replace:: PDF .. _JTS-PDF: http://www.vividsolutions.com/jts/bin/JTS%20Technical%20Specs.pdf .. |JTS-PDF| replace:: PDF .. _frozenset: http://docs.python.org/library/stdtypes.html#frozenset .. _Sorting HowTo: http://wiki.python.org/moin/HowTo/Sorting/ .. _Python geo interface: http://gist.github.com/2217756 .. _list: http://lists.gispython.org/mailman/listinfo/community Shapely-1.5.13/docs/project.rst000066400000000000000000000001261260610516500163370ustar00rootroot00000000000000.. include:: ../README.rst .. include:: ../CREDITS.txt .. include:: ../CHANGES.txt Shapely-1.5.13/docs/sphinxext/000077500000000000000000000000001260610516500161725ustar00rootroot00000000000000Shapely-1.5.13/docs/sphinxext/apigen.py000066400000000000000000000364531260610516500200220ustar00rootroot00000000000000"""Attempt to generate templates for module reference with Sphinx XXX - we exclude extension modules To include extension modules, first identify them as valid in the ``_uri2path`` method, then handle them in the ``_parse_module`` script. We get functions and classes by parsing the text of .py files. Alternatively we could import the modules for discovery, and we'd have to do that for extension modules. This would involve changing the ``_parse_module`` method to work via import and introspection, and might involve changing ``discover_modules`` (which determines which files are modules, and therefore which module URIs will be passed to ``_parse_module``). NOTE: this is a modified version of a script originally shipped with the PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed project.""" # Stdlib imports import os import re # Functions and classes class ApiDocWriter(object): ''' Class for automatic detection and parsing of API docs to Sphinx-parsable reST format''' # only separating first two levels rst_section_levels = ['*', '=', '-', '~', '^'] def __init__(self, package_name, rst_extension='.rst', package_skip_patterns=None, module_skip_patterns=None, ): ''' Initialize package for parsing Parameters ---------- package_name : string Name of the top-level package. *package_name* must be the name of an importable package rst_extension : string, optional Extension for reST files, default '.rst' package_skip_patterns : None or sequence of {strings, regexps} Sequence of strings giving URIs of packages to be excluded Operates on the package path, starting at (including) the first dot in the package path, after *package_name* - so, if *package_name* is ``sphinx``, then ``sphinx.util`` will result in ``.util`` being passed for earching by these regexps. If is None, gives default. Default is: ['\.tests$'] module_skip_patterns : None or sequence Sequence of strings giving URIs of modules to be excluded Operates on the module name including preceding URI path, back to the first dot after *package_name*. For example ``sphinx.util.console`` results in the string to search of ``.util.console`` If is None, gives default. Default is: ['\.setup$', '\._'] ''' if package_skip_patterns is None: package_skip_patterns = ['\\.tests$'] if module_skip_patterns is None: module_skip_patterns = ['\\.setup$', '\\._'] self.package_name = package_name self.rst_extension = rst_extension self.package_skip_patterns = package_skip_patterns self.module_skip_patterns = module_skip_patterns def get_package_name(self): return self._package_name def set_package_name(self, package_name): ''' Set package_name >>> docwriter = ApiDocWriter('sphinx') >>> import sphinx >>> docwriter.root_path == sphinx.__path__[0] True >>> docwriter.package_name = 'docutils' >>> import docutils >>> docwriter.root_path == docutils.__path__[0] True ''' # It's also possible to imagine caching the module parsing here self._package_name = package_name self.root_module = __import__(package_name) self.root_path = self.root_module.__path__[0] self.written_modules = None package_name = property(get_package_name, set_package_name, None, 'get/set package_name') def _get_object_name(self, line): ''' Get second token in line >>> docwriter = ApiDocWriter('sphinx') >>> docwriter._get_object_name(" def func(): ") 'func' >>> docwriter._get_object_name(" class Klass(object): ") 'Klass' >>> docwriter._get_object_name(" class Klass: ") 'Klass' ''' name = line.split()[1].split('(')[0].strip() # in case we have classes which are not derived from object # ie. old style classes return name.rstrip(':') def _uri2path(self, uri): ''' Convert uri to absolute filepath Parameters ---------- uri : string URI of python module to return path for Returns ------- path : None or string Returns None if there is no valid path for this URI Otherwise returns absolute file system path for URI Examples -------- >>> docwriter = ApiDocWriter('sphinx') >>> import sphinx >>> modpath = sphinx.__path__[0] >>> res = docwriter._uri2path('sphinx.builder') >>> res == os.path.join(modpath, 'builder.py') True >>> res = docwriter._uri2path('sphinx') >>> res == os.path.join(modpath, '__init__.py') True >>> docwriter._uri2path('sphinx.does_not_exist') ''' if uri == self.package_name: return os.path.join(self.root_path, '__init__.py') path = uri.replace('.', os.path.sep) path = path.replace(self.package_name + os.path.sep, '') path = os.path.join(self.root_path, path) # XXX maybe check for extensions as well? if os.path.exists(path + '.py'): # file path += '.py' elif os.path.exists(os.path.join(path, '__init__.py')): path = os.path.join(path, '__init__.py') else: return None return path def _path2uri(self, dirpath): ''' Convert directory path to uri ''' relpath = dirpath.replace(self.root_path, self.package_name) if relpath.startswith(os.path.sep): relpath = relpath[1:] return relpath.replace(os.path.sep, '.') def _parse_module(self, uri): ''' Parse module defined in *uri* ''' filename = self._uri2path(uri) if filename is None: # nothing that we could handle here. return ([],[]) f = open(filename, 'rt') functions, classes = self._parse_lines(f) f.close() return functions, classes def _parse_lines(self, linesource): ''' Parse lines of text for functions and classes ''' functions = [] classes = [] for line in linesource: if line.startswith('def ') and line.count('('): # exclude private stuff name = self._get_object_name(line) if not name.startswith('_'): functions.append(name) elif line.startswith('class '): # exclude private stuff name = self._get_object_name(line) if not name.startswith('_'): classes.append(name) else: pass functions.sort() classes.sort() return functions, classes def generate_api_doc(self, uri): '''Make autodoc documentation template string for a module Parameters ---------- uri : string python location of module - e.g 'sphinx.builder' Returns ------- S : string Contents of API doc ''' # get the names of all classes and functions functions, classes = self._parse_module(uri) if not len(functions) and not len(classes): print 'WARNING: Empty -',uri # dbg return '' # Make a shorter version of the uri that omits the package name for # titles uri_short = re.sub(r'^%s\.' % self.package_name,'',uri) ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n' chap_title = uri_short ad += (chap_title+'\n'+ self.rst_section_levels[1] * len(chap_title) + '\n\n') # Set the chapter title to read 'module' for all modules except for the # main packages if '.' in uri: title = 'Module: :mod:`' + uri_short + '`' else: title = ':mod:`' + uri_short + '`' ad += title + '\n' + self.rst_section_levels[2] * len(title) if len(classes): ad += '\nInheritance diagram for ``%s``:\n\n' % uri ad += '.. inheritance-diagram:: %s \n' % uri ad += ' :parts: 3\n' ad += '\n.. automodule:: ' + uri + '\n' ad += '\n.. currentmodule:: ' + uri + '\n' multi_class = len(classes) > 1 multi_fx = len(functions) > 1 if multi_class: ad += '\n' + 'Classes' + '\n' + \ self.rst_section_levels[2] * 7 + '\n' elif len(classes) and multi_fx: ad += '\n' + 'Class' + '\n' + \ self.rst_section_levels[2] * 5 + '\n' for c in classes: ad += '\n:class:`' + c + '`\n' \ + self.rst_section_levels[multi_class + 2 ] * \ (len(c)+9) + '\n\n' ad += '\n.. autoclass:: ' + c + '\n' # must NOT exclude from index to keep cross-refs working ad += ' :members:\n' \ ' :undoc-members:\n' \ ' :show-inheritance:\n' \ ' :inherited-members:\n' \ '\n' \ ' .. automethod:: __init__\n' if multi_fx: ad += '\n' + 'Functions' + '\n' + \ self.rst_section_levels[2] * 9 + '\n\n' elif len(functions) and multi_class: ad += '\n' + 'Function' + '\n' + \ self.rst_section_levels[2] * 8 + '\n\n' for f in functions: # must NOT exclude from index to keep cross-refs working ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n' return ad def _survives_exclude(self, matchstr, match_type): ''' Returns True if *matchstr* does not match patterns ``self.package_name`` removed from front of string if present Examples -------- >>> dw = ApiDocWriter('sphinx') >>> dw._survives_exclude('sphinx.okpkg', 'package') True >>> dw.package_skip_patterns.append('^\\.badpkg$') >>> dw._survives_exclude('sphinx.badpkg', 'package') False >>> dw._survives_exclude('sphinx.badpkg', 'module') True >>> dw._survives_exclude('sphinx.badmod', 'module') True >>> dw.module_skip_patterns.append('^\\.badmod$') >>> dw._survives_exclude('sphinx.badmod', 'module') False ''' if match_type == 'module': patterns = self.module_skip_patterns elif match_type == 'package': patterns = self.package_skip_patterns else: raise ValueError('Cannot interpret match type "%s"' % match_type) # Match to URI without package name L = len(self.package_name) if matchstr[:L] == self.package_name: matchstr = matchstr[L:] for pat in patterns: try: pat.search except AttributeError: pat = re.compile(pat) if pat.search(matchstr): return False return True def discover_modules(self): ''' Return module sequence discovered from ``self.package_name`` Parameters ---------- None Returns ------- mods : sequence Sequence of module names within ``self.package_name`` Examples -------- >>> dw = ApiDocWriter('sphinx') >>> mods = dw.discover_modules() >>> 'sphinx.util' in mods True >>> dw.package_skip_patterns.append('\.util$') >>> 'sphinx.util' in dw.discover_modules() False >>> ''' modules = [self.package_name] # raw directory parsing for dirpath, dirnames, filenames in os.walk(self.root_path): # Check directory names for packages root_uri = self._path2uri(os.path.join(self.root_path, dirpath)) for dirname in dirnames[:]: # copy list - we modify inplace package_uri = '.'.join((root_uri, dirname)) if (self._uri2path(package_uri) and self._survives_exclude(package_uri, 'package')): modules.append(package_uri) else: dirnames.remove(dirname) # Check filenames for modules for filename in filenames: module_name = filename[:-3] module_uri = '.'.join((root_uri, module_name)) if (self._uri2path(module_uri) and self._survives_exclude(module_uri, 'module')): modules.append(module_uri) return sorted(modules) def write_modules_api(self, modules,outdir): # write the list written_modules = [] for m in modules: api_str = self.generate_api_doc(m) if not api_str: continue # write out to file outfile = os.path.join(outdir, m + self.rst_extension) fileobj = open(outfile, 'wt') fileobj.write(api_str) fileobj.close() written_modules.append(m) self.written_modules = written_modules def write_api_docs(self, outdir): """Generate API reST files. Parameters ---------- outdir : string Directory name in which to store files We create automatic filenames for each module Returns ------- None Notes ----- Sets self.written_modules to list of written modules """ if not os.path.exists(outdir): os.mkdir(outdir) # compose list of modules modules = self.discover_modules() self.write_modules_api(modules,outdir) def write_index(self, outdir, froot='gen', relative_to=None): """Make a reST API index file from written files Parameters ---------- path : string Filename to write index to outdir : string Directory to which to write generated index file froot : string, optional root (filename without extension) of filename to write to Defaults to 'gen'. We add ``self.rst_extension``. relative_to : string path to which written filenames are relative. This component of the written file path will be removed from outdir, in the generated index. Default is None, meaning, leave path as it is. """ if self.written_modules is None: raise ValueError('No modules written') # Get full filename path path = os.path.join(outdir, froot+self.rst_extension) # Path written into index is relative to rootpath if relative_to is not None: relpath = outdir.replace(relative_to + os.path.sep, '') else: relpath = outdir idx = open(path,'wt') w = idx.write w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n') w('.. toctree::\n\n') for f in self.written_modules: w(' %s\n' % os.path.join(relpath,f)) idx.close() Shapely-1.5.13/docs/sphinxext/docscrape.py000066400000000000000000000347661260610516500205270ustar00rootroot00000000000000"""Extract reference documentation from the NumPy source tree. """ import inspect import textwrap import re import pydoc from StringIO import StringIO from warnings import warn 4 class Reader(object): """A line-based string reader. """ def __init__(self, data): """ Parameters ---------- data : str String with lines separated by '\n'. """ if isinstance(data,list): self._str = data else: self._str = data.split('\n') # store string as list of lines self.reset() def __getitem__(self, n): return self._str[n] def reset(self): self._l = 0 # current line nr def read(self): if not self.eof(): out = self[self._l] self._l += 1 return out else: return '' def seek_next_non_empty_line(self): for l in self[self._l:]: if l.strip(): break else: self._l += 1 def eof(self): return self._l >= len(self._str) def read_to_condition(self, condition_func): start = self._l for line in self[start:]: if condition_func(line): return self[start:self._l] self._l += 1 if self.eof(): return self[start:self._l+1] return [] def read_to_next_empty_line(self): self.seek_next_non_empty_line() def is_empty(line): return not line.strip() return self.read_to_condition(is_empty) def read_to_next_unindented_line(self): def is_unindented(line): return (line.strip() and (len(line.lstrip()) == len(line))) return self.read_to_condition(is_unindented) def peek(self,n=0): if self._l + n < len(self._str): return self[self._l + n] else: return '' def is_empty(self): return not ''.join(self._str).strip() class NumpyDocString(object): def __init__(self,docstring): docstring = textwrap.dedent(docstring).split('\n') self._doc = Reader(docstring) self._parsed_data = { 'Signature': '', 'Summary': [''], 'Extended Summary': [], 'Parameters': [], 'Returns': [], 'Raises': [], 'Warns': [], 'Other Parameters': [], 'Attributes': [], 'Methods': [], 'See Also': [], 'Notes': [], 'Warnings': [], 'References': '', 'Examples': '', 'index': {} } self._parse() def __getitem__(self,key): return self._parsed_data[key] def __setitem__(self,key,val): if not self._parsed_data.has_key(key): warn("Unknown section %s" % key) else: self._parsed_data[key] = val def _is_at_section(self): self._doc.seek_next_non_empty_line() if self._doc.eof(): return False l1 = self._doc.peek().strip() # e.g. Parameters if l1.startswith('.. index::'): return True l2 = self._doc.peek(1).strip() # ---------- or ========== return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1)) def _strip(self,doc): i = 0 j = 0 for i,line in enumerate(doc): if line.strip(): break for j,line in enumerate(doc[::-1]): if line.strip(): break return doc[i:len(doc)-j] def _read_to_next_section(self): section = self._doc.read_to_next_empty_line() while not self._is_at_section() and not self._doc.eof(): if not self._doc.peek(-1).strip(): # previous line was empty section += [''] section += self._doc.read_to_next_empty_line() return section def _read_sections(self): while not self._doc.eof(): data = self._read_to_next_section() name = data[0].strip() if name.startswith('..'): # index section yield name, data[1:] elif len(data) < 2: yield StopIteration else: yield name, self._strip(data[2:]) def _parse_param_list(self,content): r = Reader(content) params = [] while not r.eof(): header = r.read().strip() if ' : ' in header: arg_name, arg_type = header.split(' : ')[:2] else: arg_name, arg_type = header, '' desc = r.read_to_next_unindented_line() desc = dedent_lines(desc) params.append((arg_name,arg_type,desc)) return params _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) def _parse_see_also(self, content): """ func_name : Descriptive text continued text another_func_name : Descriptive text func_name1, func_name2, :meth:`func_name`, func_name3 """ items = [] def parse_item_name(text): """Match ':role:`name`' or 'name'""" m = self._name_rgx.match(text) if m: g = m.groups() if g[1] is None: return g[3], None else: return g[2], g[1] raise ValueError("%s is not a item name" % text) def push_item(name, rest): if not name: return name, role = parse_item_name(name) items.append((name, list(rest), role)) del rest[:] current_func = None rest = [] for line in content: if not line.strip(): continue m = self._name_rgx.match(line) if m and line[m.end():].strip().startswith(':'): push_item(current_func, rest) current_func, line = line[:m.end()], line[m.end():] rest = [line.split(':', 1)[1].strip()] if not rest[0]: rest = [] elif not line.startswith(' '): push_item(current_func, rest) current_func = None if ',' in line: for func in line.split(','): push_item(func, []) elif line.strip(): current_func = line elif current_func is not None: rest.append(line.strip()) push_item(current_func, rest) return items def _parse_index(self, section, content): """ .. index: default :refguide: something, else, and more """ def strip_each_in(lst): return [s.strip() for s in lst] out = {} section = section.split('::') if len(section) > 1: out['default'] = strip_each_in(section[1].split(','))[0] for line in content: line = line.split(':') if len(line) > 2: out[line[1]] = strip_each_in(line[2].split(',')) return out def _parse_summary(self): """Grab signature (if given) and summary""" if self._is_at_section(): return summary = self._doc.read_to_next_empty_line() summary_str = " ".join([s.strip() for s in summary]).strip() if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str): self['Signature'] = summary_str if not self._is_at_section(): self['Summary'] = self._doc.read_to_next_empty_line() else: self['Summary'] = summary if not self._is_at_section(): self['Extended Summary'] = self._read_to_next_section() def _parse(self): self._doc.reset() self._parse_summary() for (section,content) in self._read_sections(): if not section.startswith('..'): section = ' '.join([s.capitalize() for s in section.split(' ')]) if section in ('Parameters', 'Attributes', 'Methods', 'Returns', 'Raises', 'Warns'): self[section] = self._parse_param_list(content) elif section.startswith('.. index::'): self['index'] = self._parse_index(section, content) elif section == 'See Also': self['See Also'] = self._parse_see_also(content) else: self[section] = content # string conversion routines def _str_header(self, name, symbol='-'): return [name, len(name)*symbol] def _str_indent(self, doc, indent=4): out = [] for line in doc: out += [' '*indent + line] return out def _str_signature(self): if self['Signature']: return [self['Signature'].replace('*','\*')] + [''] else: return [''] def _str_summary(self): if self['Summary']: return self['Summary'] + [''] else: return [] def _str_extended_summary(self): if self['Extended Summary']: return self['Extended Summary'] + [''] else: return [] def _str_param_list(self, name): out = [] if self[name]: out += self._str_header(name) for param,param_type,desc in self[name]: out += ['%s : %s' % (param, param_type)] out += self._str_indent(desc) out += [''] return out def _str_section(self, name): out = [] if self[name]: out += self._str_header(name) out += self[name] out += [''] return out def _str_see_also(self, func_role): if not self['See Also']: return [] out = [] out += self._str_header("See Also") last_had_desc = True for func, desc, role in self['See Also']: if role: link = ':%s:`%s`' % (role, func) elif func_role: link = ':%s:`%s`' % (func_role, func) else: link = "`%s`_" % func if desc or last_had_desc: out += [''] out += [link] else: out[-1] += ", %s" % link if desc: out += self._str_indent([' '.join(desc)]) last_had_desc = True else: last_had_desc = False out += [''] return out def _str_index(self): idx = self['index'] out = [] out += ['.. index:: %s' % idx.get('default','')] for section, references in idx.iteritems(): if section == 'default': continue out += [' :%s: %s' % (section, ', '.join(references))] return out def __str__(self, func_role=''): out = [] out += self._str_signature() out += self._str_summary() out += self._str_extended_summary() for param_list in ('Parameters','Returns','Raises'): out += self._str_param_list(param_list) out += self._str_section('Warnings') out += self._str_see_also(func_role) for s in ('Notes','References','Examples'): out += self._str_section(s) out += self._str_index() return '\n'.join(out) def indent(str,indent=4): indent_str = ' '*indent if str is None: return indent_str lines = str.split('\n') return '\n'.join(indent_str + l for l in lines) def dedent_lines(lines): """Deindent a list of lines maximally""" return textwrap.dedent("\n".join(lines)).split("\n") def header(text, style='-'): return text + '\n' + style*len(text) + '\n' class FunctionDoc(NumpyDocString): def __init__(self, func, role='func', doc=None): self._f = func self._role = role # e.g. "func" or "meth" if doc is None: doc = inspect.getdoc(func) or '' try: NumpyDocString.__init__(self, doc) except ValueError, e: print '*'*78 print "ERROR: '%s' while parsing `%s`" % (e, self._f) print '*'*78 #print "Docstring follows:" #print doclines #print '='*78 if not self['Signature']: func, func_name = self.get_func() try: # try to read signature argspec = inspect.getargspec(func) argspec = inspect.formatargspec(*argspec) argspec = argspec.replace('*','\*') signature = '%s%s' % (func_name, argspec) except TypeError, e: signature = '%s()' % func_name self['Signature'] = signature def get_func(self): func_name = getattr(self._f, '__name__', self.__class__.__name__) if inspect.isclass(self._f): func = getattr(self._f, '__call__', self._f.__init__) else: func = self._f return func, func_name def __str__(self): out = '' func, func_name = self.get_func() signature = self['Signature'].replace('*', '\*') roles = {'func': 'function', 'meth': 'method'} if self._role: if not roles.has_key(self._role): print "Warning: invalid role %s" % self._role out += '.. %s:: %s\n \n\n' % (roles.get(self._role,''), func_name) out += super(FunctionDoc, self).__str__(func_role=self._role) return out class ClassDoc(NumpyDocString): def __init__(self,cls,modulename='',func_doc=FunctionDoc,doc=None): if not inspect.isclass(cls): raise ValueError("Initialise using a class. Got %r" % cls) self._cls = cls if modulename and not modulename.endswith('.'): modulename += '.' self._mod = modulename self._name = cls.__name__ self._func_doc = func_doc if doc is None: doc = pydoc.getdoc(cls) NumpyDocString.__init__(self, doc) @property def methods(self): return [name for name,func in inspect.getmembers(self._cls) if not name.startswith('_') and callable(func)] def __str__(self): out = '' out += super(ClassDoc, self).__str__() out += "\n\n" #for m in self.methods: # print "Parsing `%s`" % m # out += str(self._func_doc(getattr(self._cls,m), 'meth')) + '\n\n' # out += '.. index::\n single: %s; %s\n\n' % (self._name, m) return out Shapely-1.5.13/docs/sphinxext/docscrape_sphinx.py000066400000000000000000000102071260610516500221000ustar00rootroot00000000000000import re, inspect, textwrap, pydoc from docscrape import NumpyDocString, FunctionDoc, ClassDoc class SphinxDocString(NumpyDocString): # string conversion routines def _str_header(self, name, symbol='`'): return ['.. rubric:: ' + name, ''] def _str_field_list(self, name): return [':' + name + ':'] def _str_indent(self, doc, indent=4): out = [] for line in doc: out += [' '*indent + line] return out def _str_signature(self): return [''] if self['Signature']: return ['``%s``' % self['Signature']] + [''] else: return [''] def _str_summary(self): return self['Summary'] + [''] def _str_extended_summary(self): return self['Extended Summary'] + [''] def _str_param_list(self, name): out = [] if self[name]: out += self._str_field_list(name) out += [''] for param,param_type,desc in self[name]: out += self._str_indent(['**%s** : %s' % (param.strip(), param_type)]) out += [''] out += self._str_indent(desc,8) out += [''] return out def _str_section(self, name): out = [] if self[name]: out += self._str_header(name) out += [''] content = textwrap.dedent("\n".join(self[name])).split("\n") out += content out += [''] return out def _str_see_also(self, func_role): out = [] if self['See Also']: see_also = super(SphinxDocString, self)._str_see_also(func_role) out = ['.. seealso::', ''] out += self._str_indent(see_also[2:]) return out def _str_warnings(self): out = [] if self['Warnings']: out = ['.. warning::', ''] out += self._str_indent(self['Warnings']) return out def _str_index(self): idx = self['index'] out = [] if len(idx) == 0: return out out += ['.. index:: %s' % idx.get('default','')] for section, references in idx.iteritems(): if section == 'default': continue elif section == 'refguide': out += [' single: %s' % (', '.join(references))] else: out += [' %s: %s' % (section, ','.join(references))] return out def _str_references(self): out = [] if self['References']: out += self._str_header('References') if isinstance(self['References'], str): self['References'] = [self['References']] out.extend(self['References']) out += [''] return out def __str__(self, indent=0, func_role="obj"): out = [] out += self._str_signature() out += self._str_index() + [''] out += self._str_summary() out += self._str_extended_summary() for param_list in ('Parameters', 'Attributes', 'Methods', 'Returns','Raises'): out += self._str_param_list(param_list) out += self._str_warnings() out += self._str_see_also(func_role) out += self._str_section('Notes') out += self._str_references() out += self._str_section('Examples') out = self._str_indent(out,indent) return '\n'.join(out) class SphinxFunctionDoc(SphinxDocString, FunctionDoc): pass class SphinxClassDoc(SphinxDocString, ClassDoc): pass def get_doc_object(obj, what=None, doc=None): if what is None: if inspect.isclass(obj): what = 'class' elif inspect.ismodule(obj): what = 'module' elif callable(obj): what = 'function' else: what = 'object' if what == 'class': return SphinxClassDoc(obj, '', func_doc=SphinxFunctionDoc, doc=doc) elif what in ('function', 'method'): return SphinxFunctionDoc(obj, '', doc=doc) else: if doc is None: doc = pydoc.getdoc(obj) return SphinxDocString(doc) Shapely-1.5.13/docs/sphinxext/inheritance_diagram.py000066400000000000000000000325201260610516500225230ustar00rootroot00000000000000""" Defines a docutils directive for inserting inheritance diagrams. Provide the directive with one or more classes or modules (separated by whitespace). For modules, all of the classes in that module will be used. Example:: Given the following classes: class A: pass class B(A): pass class C(A): pass class D(B, C): pass class E(B): pass .. inheritance-diagram: D E Produces a graph like the following: A / \ B C / \ / E D The graph is inserted as a PNG+image map into HTML and a PDF in LaTeX. """ import inspect import os import re import subprocess try: from hashlib import md5 except ImportError: from md5 import md5 from docutils.nodes import Body, Element from docutils.parsers.rst import directives from sphinx.roles import xfileref_role def my_import(name): """Module importer - taken from the python documentation. This function allows importing names with dots in them.""" mod = __import__(name) components = name.split('.') for comp in components[1:]: mod = getattr(mod, comp) return mod class DotException(Exception): pass class InheritanceGraph(object): """ Given a list of classes, determines the set of classes that they inherit from all the way to the root "object", and then is able to generate a graphviz dot graph from them. """ def __init__(self, class_names, show_builtins=False): """ *class_names* is a list of child classes to show bases from. If *show_builtins* is True, then Python builtins will be shown in the graph. """ self.class_names = class_names self.classes = self._import_classes(class_names) self.all_classes = self._all_classes(self.classes) if len(self.all_classes) == 0: raise ValueError("No classes found for inheritance diagram") self.show_builtins = show_builtins py_sig_re = re.compile(r'''^([\w.]*\.)? # class names (\w+) \s* $ # optionally arguments ''', re.VERBOSE) def _import_class_or_module(self, name): """ Import a class using its fully-qualified *name*. """ try: path, base = self.py_sig_re.match(name).groups() except: raise ValueError( "Invalid class or module '%s' specified for inheritance diagram" % name) fullname = (path or '') + base path = (path and path.rstrip('.')) if not path: path = base try: module = __import__(path, None, None, []) # We must do an import of the fully qualified name. Otherwise if a # subpackage 'a.b' is requested where 'import a' does NOT provide # 'a.b' automatically, then 'a.b' will not be found below. This # second call will force the equivalent of 'import a.b' to happen # after the top-level import above. my_import(fullname) except ImportError: raise ValueError( "Could not import class or module '%s' specified for inheritance diagram" % name) try: todoc = module for comp in fullname.split('.')[1:]: todoc = getattr(todoc, comp) except AttributeError: raise ValueError( "Could not find class or module '%s' specified for inheritance diagram" % name) # If a class, just return it if inspect.isclass(todoc): return [todoc] elif inspect.ismodule(todoc): classes = [] for cls in todoc.__dict__.values(): if inspect.isclass(cls) and cls.__module__ == todoc.__name__: classes.append(cls) return classes raise ValueError( "'%s' does not resolve to a class or module" % name) def _import_classes(self, class_names): """ Import a list of classes. """ classes = [] for name in class_names: classes.extend(self._import_class_or_module(name)) return classes def _all_classes(self, classes): """ Return a list of all classes that are ancestors of *classes*. """ all_classes = {} def recurse(cls): all_classes[cls] = None for c in cls.__bases__: if c not in all_classes: recurse(c) for cls in classes: recurse(cls) return all_classes.keys() def class_name(self, cls, parts=0): """ Given a class object, return a fully-qualified name. This works for things I've tested in matplotlib so far, but may not be completely general. """ module = cls.__module__ if module == '__builtin__': fullname = cls.__name__ else: fullname = "%s.%s" % (module, cls.__name__) if parts == 0: return fullname name_parts = fullname.split('.') return '.'.join(name_parts[-parts:]) def get_all_class_names(self): """ Get all of the class names involved in the graph. """ return [self.class_name(x) for x in self.all_classes] # These are the default options for graphviz default_graph_options = { "rankdir": "LR", "size": '"8.0, 12.0"' } default_node_options = { "shape": "box", "fontsize": 10, "height": 0.25, "fontname": "Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans", "style": '"setlinewidth(0.5)"' } default_edge_options = { "arrowsize": 0.5, "style": '"setlinewidth(0.5)"' } def _format_node_options(self, options): return ','.join(["%s=%s" % x for x in options.items()]) def _format_graph_options(self, options): return ''.join(["%s=%s;\n" % x for x in options.items()]) def generate_dot(self, fd, name, parts=0, urls={}, graph_options={}, node_options={}, edge_options={}): """ Generate a graphviz dot graph from the classes that were passed in to __init__. *fd* is a Python file-like object to write to. *name* is the name of the graph *urls* is a dictionary mapping class names to http urls *graph_options*, *node_options*, *edge_options* are dictionaries containing key/value pairs to pass on as graphviz properties. """ g_options = self.default_graph_options.copy() g_options.update(graph_options) n_options = self.default_node_options.copy() n_options.update(node_options) e_options = self.default_edge_options.copy() e_options.update(edge_options) fd.write('digraph %s {\n' % name) fd.write(self._format_graph_options(g_options)) for cls in self.all_classes: if not self.show_builtins and cls in __builtins__.values(): continue name = self.class_name(cls, parts) # Write the node this_node_options = n_options.copy() url = urls.get(self.class_name(cls)) if url is not None: this_node_options['URL'] = '"%s"' % url fd.write(' "%s" [%s];\n' % (name, self._format_node_options(this_node_options))) # Write the edges for base in cls.__bases__: if not self.show_builtins and base in __builtins__.values(): continue base_name = self.class_name(base, parts) fd.write(' "%s" -> "%s" [%s];\n' % (base_name, name, self._format_node_options(e_options))) fd.write('}\n') def run_dot(self, args, name, parts=0, urls={}, graph_options={}, node_options={}, edge_options={}): """ Run graphviz 'dot' over this graph, returning whatever 'dot' writes to stdout. *args* will be passed along as commandline arguments. *name* is the name of the graph *urls* is a dictionary mapping class names to http urls Raises DotException for any of the many os and installation-related errors that may occur. """ try: dot = subprocess.Popen(['dot'] + list(args), stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) except OSError: raise DotException("Could not execute 'dot'. Are you sure you have 'graphviz' installed?") except ValueError: raise DotException("'dot' called with invalid arguments") except: raise DotException("Unexpected error calling 'dot'") self.generate_dot(dot.stdin, name, parts, urls, graph_options, node_options, edge_options) dot.stdin.close() result = dot.stdout.read() returncode = dot.wait() if returncode != 0: raise DotException("'dot' returned the errorcode %d" % returncode) return result class inheritance_diagram(Body, Element): """ A docutils node to use as a placeholder for the inheritance diagram. """ pass def inheritance_diagram_directive(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): """ Run when the inheritance_diagram directive is first encountered. """ node = inheritance_diagram() class_names = arguments # Create a graph starting with the list of classes graph = InheritanceGraph(class_names) # Create xref nodes for each target of the graph's image map and # add them to the doc tree so that Sphinx can resolve the # references to real URLs later. These nodes will eventually be # removed from the doctree after we're done with them. for name in graph.get_all_class_names(): refnodes, x = xfileref_role( 'class', ':class:`%s`' % name, name, 0, state) node.extend(refnodes) # Store the graph object so we can use it to generate the # dot file later node['graph'] = graph # Store the original content for use as a hash node['parts'] = options.get('parts', 0) node['content'] = " ".join(class_names) return [node] def get_graph_hash(node): return md5(node['content'] + str(node['parts'])).hexdigest()[-10:] def html_output_graph(self, node): """ Output the graph for HTML. This will insert a PNG with clickable image map. """ graph = node['graph'] parts = node['parts'] graph_hash = get_graph_hash(node) name = "inheritance%s" % graph_hash path = '_images' dest_path = os.path.join(setup.app.builder.outdir, path) if not os.path.exists(dest_path): os.makedirs(dest_path) png_path = os.path.join(dest_path, name + ".png") path = setup.app.builder.imgpath # Create a mapping from fully-qualified class names to URLs. urls = {} for child in node: if child.get('refuri') is not None: urls[child['reftitle']] = child.get('refuri') elif child.get('refid') is not None: urls[child['reftitle']] = '#' + child.get('refid') # These arguments to dot will save a PNG file to disk and write # an HTML image map to stdout. image_map = graph.run_dot(['-Tpng', '-o%s' % png_path, '-Tcmapx'], name, parts, urls) return ('%s' % (path, name, name, image_map)) def latex_output_graph(self, node): """ Output the graph for LaTeX. This will insert a PDF. """ graph = node['graph'] parts = node['parts'] graph_hash = get_graph_hash(node) name = "inheritance%s" % graph_hash dest_path = os.path.abspath(os.path.join(setup.app.builder.outdir, '_images')) if not os.path.exists(dest_path): os.makedirs(dest_path) pdf_path = os.path.abspath(os.path.join(dest_path, name + ".pdf")) graph.run_dot(['-Tpdf', '-o%s' % pdf_path], name, parts, graph_options={'size': '"6.0,6.0"'}) return '\n\\includegraphics{%s}\n\n' % pdf_path def visit_inheritance_diagram(inner_func): """ This is just a wrapper around html/latex_output_graph to make it easier to handle errors and insert warnings. """ def visitor(self, node): try: content = inner_func(self, node) except DotException, e: # Insert the exception as a warning in the document warning = self.document.reporter.warning(str(e), line=node.line) warning.parent = node node.children = [warning] else: source = self.document.attributes['source'] self.body.append(content) node.children = [] return visitor def do_nothing(self, node): pass def setup(app): setup.app = app setup.confdir = app.confdir app.add_node( inheritance_diagram, latex=(visit_inheritance_diagram(latex_output_graph), do_nothing), html=(visit_inheritance_diagram(html_output_graph), do_nothing)) app.add_directive( 'inheritance-diagram', inheritance_diagram_directive, False, (1, 100, 0), parts = directives.nonnegative_int) Shapely-1.5.13/docs/sphinxext/ipython_console_highlighting.py000066400000000000000000000101271260610516500245060ustar00rootroot00000000000000"""reST directive for syntax-highlighting ipython interactive sessions. XXX - See what improvements can be made based on the new (as of Sept 2009) 'pycon' lexer for the python console. At the very least it will give better highlighted tracebacks. """ #----------------------------------------------------------------------------- # Needed modules # Standard library import re # Third party from pygments.lexer import Lexer, do_insertions from pygments.lexers.agile import (PythonConsoleLexer, PythonLexer, PythonTracebackLexer) from pygments.token import Comment, Generic from sphinx import highlighting #----------------------------------------------------------------------------- # Global constants line_re = re.compile('.*?\n') #----------------------------------------------------------------------------- # Code begins - classes and functions class IPythonConsoleLexer(Lexer): """ For IPython console output or doctests, such as: .. sourcecode:: ipython In [1]: a = 'foo' In [2]: a Out[2]: 'foo' In [3]: print a foo In [4]: 1 / 0 Notes: - Tracebacks are not currently supported. - It assumes the default IPython prompts, not customized ones. """ name = 'IPython console session' aliases = ['ipython'] mimetypes = ['text/x-ipython-console'] input_prompt = re.compile("(In \[[0-9]+\]: )|( \.\.\.+:)") output_prompt = re.compile("(Out\[[0-9]+\]: )|( \.\.\.+:)") continue_prompt = re.compile(" \.\.\.+:") tb_start = re.compile("\-+") def get_tokens_unprocessed(self, text): pylexer = PythonLexer(**self.options) tblexer = PythonTracebackLexer(**self.options) curcode = '' insertions = [] for match in line_re.finditer(text): line = match.group() input_prompt = self.input_prompt.match(line) continue_prompt = self.continue_prompt.match(line.rstrip()) output_prompt = self.output_prompt.match(line) if line.startswith("#"): insertions.append((len(curcode), [(0, Comment, line)])) elif input_prompt is not None: insertions.append((len(curcode), [(0, Generic.Prompt, input_prompt.group())])) curcode += line[input_prompt.end():] elif continue_prompt is not None: insertions.append((len(curcode), [(0, Generic.Prompt, continue_prompt.group())])) curcode += line[continue_prompt.end():] elif output_prompt is not None: # Use the 'error' token for output. We should probably make # our own token, but error is typicaly in a bright color like # red, so it works fine for our output prompts. insertions.append((len(curcode), [(0, Generic.Error, output_prompt.group())])) curcode += line[output_prompt.end():] else: if curcode: for item in do_insertions(insertions, pylexer.get_tokens_unprocessed(curcode)): yield item curcode = '' insertions = [] yield match.start(), Generic.Output, line if curcode: for item in do_insertions(insertions, pylexer.get_tokens_unprocessed(curcode)): yield item def setup(app): """Setup as a sphinx extension.""" # This is only a lexer, so adding it below to pygments appears sufficient. # But if somebody knows that the right API usage should be to do that via # sphinx, by all means fix it here. At least having this setup.py # suppresses the sphinx warning we'd get without it. pass #----------------------------------------------------------------------------- # Register the extension as a valid pygments lexer highlighting.lexers['ipython'] = IPythonConsoleLexer() Shapely-1.5.13/docs/sphinxext/numpydoc.py000066400000000000000000000077321260610516500204130ustar00rootroot00000000000000""" ======== numpydoc ======== Sphinx extension that handles docstrings in the Numpy standard format. [1] It will: - Convert Parameters etc. sections to field lists. - Convert See Also section to a See also entry. - Renumber references. - Extract the signature from the docstring, if it can't be determined otherwise. .. [1] http://projects.scipy.org/scipy/numpy/wiki/CodingStyleGuidelines#docstring-standard """ import os, re, pydoc from docscrape_sphinx import get_doc_object, SphinxDocString import inspect def mangle_docstrings(app, what, name, obj, options, lines, reference_offset=[0]): if what == 'module': # Strip top title title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*', re.I|re.S) lines[:] = title_re.sub('', "\n".join(lines)).split("\n") else: doc = get_doc_object(obj, what, "\n".join(lines)) lines[:] = str(doc).split("\n") if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \ obj.__name__: if hasattr(obj, '__module__'): v = dict(full_name="%s.%s" % (obj.__module__, obj.__name__)) else: v = dict(full_name=obj.__name__) lines += ['', '.. htmlonly::', ''] lines += [' %s' % x for x in (app.config.numpydoc_edit_link % v).split("\n")] # replace reference numbers so that there are no duplicates references = [] for l in lines: l = l.strip() if l.startswith('.. ['): try: references.append(int(l[len('.. ['):l.index(']')])) except ValueError: print "WARNING: invalid reference in %s docstring" % name # Start renaming from the biggest number, otherwise we may # overwrite references. references.sort() if references: for i, line in enumerate(lines): for r in references: new_r = reference_offset[0] + r lines[i] = lines[i].replace('[%d]_' % r, '[%d]_' % new_r) lines[i] = lines[i].replace('.. [%d]' % r, '.. [%d]' % new_r) reference_offset[0] += len(references) def mangle_signature(app, what, name, obj, options, sig, retann): # Do not try to inspect classes that don't define `__init__` if (inspect.isclass(obj) and 'initializes x; see ' in pydoc.getdoc(obj.__init__)): return '', '' if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): return if not hasattr(obj, '__doc__'): return doc = SphinxDocString(pydoc.getdoc(obj)) if doc['Signature']: sig = re.sub("^[^(]*", "", doc['Signature']) return sig, '' def initialize(app): try: app.connect('autodoc-process-signature', mangle_signature) except: monkeypatch_sphinx_ext_autodoc() def setup(app, get_doc_object_=get_doc_object): global get_doc_object get_doc_object = get_doc_object_ app.connect('autodoc-process-docstring', mangle_docstrings) app.connect('builder-inited', initialize) app.add_config_value('numpydoc_edit_link', None, True) #------------------------------------------------------------------------------ # Monkeypatch sphinx.ext.autodoc to accept argspecless autodocs (Sphinx < 0.5) #------------------------------------------------------------------------------ def monkeypatch_sphinx_ext_autodoc(): global _original_format_signature import sphinx.ext.autodoc if sphinx.ext.autodoc.format_signature is our_format_signature: return print "[numpydoc] Monkeypatching sphinx.ext.autodoc ..." _original_format_signature = sphinx.ext.autodoc.format_signature sphinx.ext.autodoc.format_signature = our_format_signature def our_format_signature(what, obj): r = mangle_signature(None, what, None, obj, None, None, None) if r is not None: return r[0] else: return _original_format_signature(what, obj) Shapely-1.5.13/requirements-dev.txt000066400000000000000000000001071260610516500172460ustar00rootroot00000000000000setuptools Numpy>=1.8.0 Cython>=0.19 descartes==1.0.1 packaging pytest Shapely-1.5.13/setup.py000077500000000000000000000243621260610516500147340ustar00rootroot00000000000000#!/usr/bin/env python # Two environment variables influence this script. # # GEOS_LIBRARY_PATH: a path to a GEOS C shared library. # # GEOS_CONFIG: the path to a geos-config program that points to GEOS version, # headers, and libraries. # # NB: within this setup scripts, software versions are evaluated according # to https://www.python.org/dev/peps/pep-0440/. import errno import glob import logging import os import platform import re import shutil import subprocess import sys try: # If possible, use setuptools from setuptools import setup from setuptools.extension import Extension from setuptools.command.build_ext import build_ext as distutils_build_ext except ImportError: from distutils.core import setup from distutils.extension import Extension from distutils.command.build_ext import build_ext as distutils_build_ext from distutils.errors import CCompilerError, DistutilsExecError, \ DistutilsPlatformError from distutils.version import StrictVersion as Version # Get geos_version from GEOS dynamic library, which depends on # GEOS_LIBRARY_PATH and/or GEOS_CONFIG environment variables from shapely._buildcfg import geos_version_string, geos_version, \ geos_config, get_geos_config logging.basicConfig() log = logging.getLogger(__file__) # python -W all setup.py ... if 'all' in sys.warnoptions: log.level = logging.DEBUG # Get the version from the shapely module shapely_version = None with open('shapely/__init__.py', 'r') as fp: for line in fp: if line.startswith("__version__"): shapely_version = Version( line.split("=")[1].strip().strip("\"'")) break if not shapely_version: raise ValueError("Could not determine Shapely's version") # Fail installation if the GEOS shared library does not meet the minimum # version. We ship it with Shapely for Windows, so no need to check on # that platform. log.debug('GEOS shared library: %s %s', geos_version_string, geos_version) if (set(sys.argv).intersection(['install', 'build', 'build_ext']) and shapely_version >= Version('1.3') and geos_version < (3, 3)): log.critical( "Shapely >= 1.3 requires GEOS >= 3.3. " "Install GEOS 3.3+ and reinstall Shapely.") sys.exit(1) # Handle UTF-8 encoding of certain text files. open_kwds = {} if sys.version_info >= (3,): open_kwds['encoding'] = 'utf-8' with open('VERSION.txt', 'w', **open_kwds) as fp: fp.write(str(shapely_version)) with open('README.rst', 'r', **open_kwds) as fp: readme = fp.read() with open('CREDITS.txt', 'r', **open_kwds) as fp: credits = fp.read() with open('CHANGES.txt', 'r', **open_kwds) as fp: changes = fp.read() long_description = readme + '\n\n' + credits + '\n\n' + changes setup_args = dict( name = 'Shapely', version = str(shapely_version), requires = ['Python (>=2.6)', 'libgeos_c (>=3.3)'], description = 'Geometric objects, predicates, and operations', license = 'BSD', keywords = 'geometry topology gis', author = 'Sean Gillies', author_email = 'sean.gillies@gmail.com', maintainer = 'Sean Gillies', maintainer_email = 'sean.gillies@gmail.com', url = 'https://github.com/Toblerity/Shapely', long_description = long_description, packages = [ 'shapely', 'shapely.geometry', 'shapely.algorithms', 'shapely.examples', 'shapely.speedups', 'shapely.vectorized', ], classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Topic :: Scientific/Engineering :: GIS', ], data_files = [('shapely', ['shapely/_geos.pxi'])], cmdclass = {}, ) # Add DLLs for Windows if sys.platform == 'win32': try: os.mkdir('shapely/DLLs') except OSError as ex: if ex.errno != errno.EEXIST: raise if '(AMD64)' in sys.version: for dll in glob.glob('DLLs_AMD64_VC9/*.dll'): shutil.copy(dll, 'shapely/DLLs') elif sys.version_info[0:2] == (2, 5): for dll in glob.glob('DLLs_x86_VC7/*.dll'): shutil.copy(dll, 'shapely/DLLs') else: for dll in glob.glob('DLLs_x86_VC9/*.dll'): shutil.copy(dll, 'shapely/DLLs') setup_args.update( package_data={'shapely': ['shapely/DLLs/*.dll']}, include_package_data=True, ) # Prepare build opts and args for the speedups extension module. include_dirs = [] library_dirs = [] libraries = [] extra_link_args = [] try: # Get the version from geos-config. Show error if this version tuple is # different to the GEOS version loaded from the dynamic library. geos_config_version_string = get_geos_config('--version') res = re.findall(r'(\d+)\.(\d+)\.(\d+)', geos_config_version_string) geos_config_version = tuple(int(x) for x in res[0]) if geos_config_version != geos_version: log.error("The GEOS dynamic library version is %s %s,", geos_version_string, geos_version) log.error("but the version reported by %s is %s %s.", geos_config, geos_config_version_string, geos_config_version) sys.exit(1) except OSError as ex: log.error(ex) log.error('Cannot find geos-config to get headers and check version.') log.error('If available, specify a path to geos-config with a ' 'GEOS_CONFIG environment variable') geos_config = None if geos_config: # Collect other options from GEOS for item in get_geos_config('--cflags').split(): if item.startswith("-I"): include_dirs.extend(item[2:].split(":")) for item in get_geos_config('--clibs').split(): if item.startswith("-L"): library_dirs.extend(item[2:].split(":")) elif item.startswith("-l"): libraries.append(item[2:]) else: # e.g. -framework GEOS extra_link_args.append(item) # Optional compilation of speedups # setuptools stuff from Bob Ippolito's simplejson project if sys.platform == 'win32' and sys.version_info > (2, 6): # 2.6's distutils.msvc9compiler can raise an IOError when failing to # find the compiler ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError) else: ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError) class BuildFailed(Exception): pass def construct_build_ext(build_ext): class WrappedBuildExt(build_ext): # This class allows C extension building to fail. def run(self): try: build_ext.run(self) except DistutilsPlatformError as x: raise BuildFailed(x) def build_extension(self, ext): try: build_ext.build_extension(self, ext) except ext_errors as x: raise BuildFailed(x) return WrappedBuildExt if (hasattr(platform, 'python_implementation') and platform.python_implementation() == 'PyPy'): # python_implementation is only available since 2.6 ext_modules = [] libraries = [] if os.path.exists("MANIFEST.in"): pyx_file = "shapely/speedups/_speedups.pyx" c_file = "shapely/speedups/_speedups.c" force_cython = False if 'sdist' in sys.argv: force_cython = True try: if (force_cython or not os.path.exists(c_file) or os.path.getmtime(pyx_file) > os.path.getmtime(c_file)): log.info("Updating C extension with Cython.") subprocess.check_call(["cython", "shapely/speedups/_speedups.pyx"]) except (subprocess.CalledProcessError, OSError): log.warn("Could not (re)create C extension with Cython.") if force_cython: raise if not os.path.exists(c_file): log.warn("speedup extension not found") ext_modules = [ Extension( "shapely.speedups._speedups", ["shapely/speedups/_speedups.c"], include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, extra_link_args=extra_link_args, ), ] cmd_classes = setup_args.setdefault('cmdclass', {}) try: import numpy from Cython.Distutils import build_ext as cython_build_ext from distutils.extension import Extension as DistutilsExtension if 'build_ext' in setup_args['cmdclass']: raise ValueError('We need to put the Cython build_ext in ' 'cmd_classes, but it is already defined.') setup_args['cmdclass']['build_ext'] = cython_build_ext include_dirs.append(numpy.get_include()) libraries.append(numpy.get_include()) ext_modules.append(DistutilsExtension( "shapely.vectorized._vectorized", sources=["shapely/vectorized/_vectorized.pyx"], include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, extra_link_args=extra_link_args, )) except ImportError: log.info("Numpy or Cython not available, shapely.vectorized submodule " "not being built.") try: # try building with speedups existing_build_ext = setup_args['cmdclass'].\ get('build_ext', distutils_build_ext) setup_args['cmdclass']['build_ext'] = \ construct_build_ext(existing_build_ext) setup(ext_modules=ext_modules, **setup_args) except BuildFailed as ex: BUILD_EXT_WARNING = "The C extension could not be compiled, " \ "speedups are not enabled." log.warn(ex) log.warn(BUILD_EXT_WARNING) log.warn("Failure information, if any, is above.") log.warn("I'm retrying the build without the C extension now.") # Remove any previously defined build_ext command class. if 'build_ext' in setup_args['cmdclass']: del setup_args['cmdclass']['build_ext'] if 'build_ext' in cmd_classes: del cmd_classes['build_ext'] setup(**setup_args) log.warn(BUILD_EXT_WARNING) log.info("Plain-Python installation succeeded.") Shapely-1.5.13/shapely/000077500000000000000000000000001260610516500146555ustar00rootroot00000000000000Shapely-1.5.13/shapely/__init__.py000066400000000000000000000000271260610516500167650ustar00rootroot00000000000000__version__ = "1.5.13" Shapely-1.5.13/shapely/_buildcfg.py000066400000000000000000000201741260610516500171510ustar00rootroot00000000000000""" Minimal proxy to a GEOS C dynamic library, which is system dependant Two environment variables influence this module: GEOS_LIBRARY_PATH and/or GEOS_CONFIG. If GEOS_LIBRARY_PATH is set to a path to a GEOS C shared library, this is used. Otherwise GEOS_CONFIG can be set to a path to `geos-config`. If `geos-config` is already on the PATH environment variable, then it will be used to help better guess the name for the GEOS C dynamic library. """ from ctypes import CDLL, cdll, c_void_p, c_char_p from ctypes.util import find_library import os import logging import re import subprocess import sys # Add message handler to this module's logger log = logging.getLogger(__name__) ch = logging.StreamHandler() log.addHandler(ch) if 'all' in sys.warnoptions: # show GEOS messages in console with: python -W all log.setLevel(logging.DEBUG) # The main point of this module is to load a dynamic library to this variable lgeos = None # First try: use GEOS_LIBRARY_PATH environment variable if 'GEOS_LIBRARY_PATH' in os.environ: geos_library_path = os.environ['GEOS_LIBRARY_PATH'] try: lgeos = CDLL(geos_library_path) except: log.warn('cannot open shared object from GEOS_LIBRARY_PATH: %s', geos_library_path) if lgeos: if hasattr(lgeos, 'GEOSversion'): log.debug('found GEOS C library using GEOS_LIBRARY_PATH') else: raise OSError( 'shared object GEOS_LIBRARY_PATH is not a GEOS C library: ' + str(geos_library_path)) # Second try: use GEOS_CONFIG environment variable if 'GEOS_CONFIG' in os.environ: geos_config = os.environ['GEOS_CONFIG'] log.debug('geos_config: %s', geos_config) else: geos_config = 'geos-config' def get_geos_config(option): '''Get configuration option from the `geos-config` development utility Path to utility is set with a module-level `geos_config` variable, which can be changed or unset. ''' geos_config = globals().get('geos_config') if not geos_config or not isinstance(geos_config, str): raise OSError('Path to geos-config is not set') try: stdout, stderr = subprocess.Popen( [geos_config, option], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() except OSError as ex: # e.g., [Errno 2] No such file or directory raise OSError( 'Could not find geos-config %r: %s' % (geos_config, ex)) if stderr and not stdout: raise ValueError(stderr.strip()) if sys.version_info[0] >= 3: result = stdout.decode('ascii').strip() else: result = stdout.strip() log.debug('%s %s: %r', geos_config, option, result) return result # Now try and use the utility to load from `geos-config --clibs` with # some magic smoke to guess the other parts of the library name try: clibs = get_geos_config('--clibs') except OSError: geos_config = None if not lgeos and geos_config: base = '' name = 'geos_c' for item in clibs.split(): if item.startswith("-L"): base = item[2:] elif item.startswith("-l"): name = item[2:] # Now guess the actual library name using a list of possible formats if sys.platform == 'win32': # Unlikely, since geos-config is a shell script, but you never know... fmts = ['{name}.dll'] elif sys.platform == 'darwin': fmts = ['lib{name}.dylib', '{name}.dylib', '{name}.framework/{name}'] elif os.name == 'posix': fmts = ['lib{name}.so', 'lib{name}.so.1'] guesses = [] for fmt in fmts: lib_name = fmt.format(name=name) geos_library_path = os.path.join(base, lib_name) try: lgeos = CDLL(geos_library_path) break except: guesses.append(geos_library_path) if lgeos: if hasattr(lgeos, 'GEOSversion'): log.debug('found GEOS C library using geos-config') else: raise OSError( 'shared object found by geos-config is not a GEOS C library: ' + str(geos_library_path)) else: log.warn("cannot open shared object from '%s --clibs': %r", geos_config, clibs) log.warn("there were %d guess(es) for this path:\n\t%s", len(guesses), '\n\t'.join(guesses)) # Platform-specific attempts, and build `free` object def load_dll(libname, fallbacks=None): lib = find_library(libname) dll = None if lib is not None: try: log.debug("Trying `CDLL(%s)`", lib) dll = CDLL(lib) except OSError: log.warn("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) except OSError: # move on to the next fallback log.warn("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 library {0} or load any of its variants {1}".format( libname, fallbacks or [])) if sys.platform.startswith('linux'): if not lgeos: lgeos = load_dll('geos_c', fallbacks=['libgeos_c.so.1', 'libgeos_c.so']) free = load_dll('c').free free.argtypes = [c_void_p] free.restype = None elif sys.platform == 'darwin': if not lgeos: if hasattr(sys, 'frozen'): # .app file from py2app alt_paths = [os.path.join(os.environ['RESOURCEPATH'], '..', 'Frameworks', 'libgeos_c.dylib')] else: alt_paths = [ # The Framework build from Kyng Chaos "/Library/Frameworks/GEOS.framework/Versions/Current/GEOS", # macports '/opt/local/lib/libgeos_c.dylib', ] lgeos = load_dll('geos_c', fallbacks=alt_paths) free = load_dll('c', fallbacks=['/usr/lib/libc.dylib']).free free.argtypes = [c_void_p] free.restype = None elif sys.platform == 'win32': if not lgeos: try: egg_dlls = os.path.abspath( os.path.join(os.path.dirname(__file__), "DLLs")) 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 = CDLL("geos.dll") except (ImportError, WindowsError, OSError): raise def free(m): try: cdll.msvcrt.free(m) except WindowsError: # TODO: http://web.archive.org/web/20070810024932/ # + http://trac.gispython.org/projects/PCL/ticket/149 pass elif sys.platform == 'sunos5': if not lgeos: 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 if not lgeos: 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 # TODO: what to do with 'free'? It isn't used. def _geos_version(): # extern const char GEOS_DLL *GEOSversion(); GEOSversion = lgeos.GEOSversion GEOSversion.restype = c_char_p GEOSversion.argtypes = [] # #define GEOS_CAPI_VERSION "@VERSION@-CAPI-@CAPI_VERSION@" 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() Shapely-1.5.13/shapely/_geos.pxi000066400000000000000000000065521260610516500165030ustar00rootroot00000000000000# The beginnings of a Cython definition of GEOS. In the future much of this # could be auto-generated. ctypedef long ptr cdef extern from "geos_c.h": ctypedef void *GEOSContextHandle_t ctypedef struct GEOSGeometry ctypedef struct GEOSCoordSequence ctypedef struct GEOSPreparedGeometry GEOSCoordSequence *GEOSCoordSeq_create_r(GEOSContextHandle_t, unsigned int, unsigned int) nogil GEOSCoordSequence *GEOSGeom_getCoordSeq_r(GEOSContextHandle_t, GEOSGeometry *) nogil int GEOSCoordSeq_getSize_r(GEOSContextHandle_t, GEOSCoordSequence *, unsigned int *) nogil int GEOSCoordSeq_setX_r(GEOSContextHandle_t, GEOSCoordSequence *, int, double) nogil int GEOSCoordSeq_setY_r(GEOSContextHandle_t, GEOSCoordSequence *, int, double) nogil int GEOSCoordSeq_setZ_r(GEOSContextHandle_t, GEOSCoordSequence *, int, double) nogil int GEOSCoordSeq_getX_r(GEOSContextHandle_t, GEOSCoordSequence *, int, double *) nogil int GEOSCoordSeq_getY_r(GEOSContextHandle_t, GEOSCoordSequence *, int, double *) nogil int GEOSCoordSeq_getZ_r(GEOSContextHandle_t, GEOSCoordSequence *, int, double *) nogil GEOSGeometry *GEOSGeom_createPoint_r(GEOSContextHandle_t, GEOSCoordSequence *) nogil GEOSGeometry *GEOSGeom_createLineString_r(GEOSContextHandle_t, GEOSCoordSequence *) nogil GEOSGeometry *GEOSGeom_createLinearRing_r(GEOSContextHandle_t, GEOSCoordSequence *) nogil GEOSGeometry *GEOSGeom_clone_r(GEOSContextHandle_t, GEOSGeometry *) nogil GEOSCoordSequence *GEOSCoordSeq_clone_r(GEOSContextHandle_t, GEOSCoordSequence *) nogil void GEOSGeom_destroy_r(GEOSContextHandle_t, GEOSGeometry *) nogil char GEOSPreparedContains_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSPreparedContainsProperly_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSPreparedCoveredBy_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSPreparedCovers_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSPreparedCrosses_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSPreparedDisjoint_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSPreparedIntersects_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSPreparedOverlaps_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSPreparedTouches_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSPreparedWithin_r(GEOSContextHandle_t, const GEOSPreparedGeometry*, const GEOSGeometry*) nogil char GEOSHasZ_r(GEOSContextHandle_t, GEOSGeometry *) nogil char GEOSisRing_r(GEOSContextHandle_t, GEOSGeometry *) nogil char GEOSisClosed_r(GEOSContextHandle_t, GEOSGeometry *) nogil cdef GEOSContextHandle_t get_geos_context_handle(): # Note: This requires that lgeos is defined, so needs to be imported as: from shapely.geos import lgeos cdef ptr handle = lgeos.geos_handle return handle cdef GEOSPreparedGeometry *geos_from_prepared(shapely_geom) except *: """Get the Prepared GEOS geometry pointer from the given shapely geometry.""" cdef ptr geos_geom = shapely_geom._geom return geos_geom Shapely-1.5.13/shapely/affinity.py000077500000000000000000000204561260610516500170520ustar00rootroot00000000000000"""Affine transforms, both in general and specific, named transforms.""" from math import sin, cos, tan, pi __all__ = ['affine_transform', 'rotate', 'scale', 'skew', 'translate'] def affine_transform(geom, matrix): """Returns a transformed geometry using an affine transformation matrix. The coefficient matrix is provided as a list or tuple with 6 or 12 items for 2D or 3D transformations, respectively. For 2D affine transformations, the 6 parameter matrix is:: [a, b, d, e, xoff, yoff] which represents the augmented matrix:: / a b xoff \ [x' y' 1] = [x y 1] | d e yoff | \ 0 0 1 / or the equations for the transformed coordinates:: x' = a * x + b * y + xoff y' = d * x + e * y + yoff For 3D affine transformations, the 12 parameter matrix is:: [a, b, c, d, e, f, g, h, i, xoff, yoff, zoff] which represents the augmented matrix:: / a b c xoff \ [x' y' z' 1] = [x y z 1] | d e f yoff | | g h i zoff | \ 0 0 0 1 / or the equations for the transformed coordinates:: x' = a * x + b * y + c * z + xoff y' = d * x + e * y + f * z + yoff z' = g * x + h * y + i * z + zoff """ if geom.is_empty: return geom if len(matrix) == 6: ndim = 2 a, b, d, e, xoff, yoff = matrix if geom.has_z: ndim = 3 i = 1.0 c = f = g = h = zoff = 0.0 matrix = a, b, c, d, e, f, g, h, i, xoff, yoff, zoff elif len(matrix) == 12: ndim = 3 a, b, c, d, e, f, g, h, i, xoff, yoff, zoff = matrix if not geom.has_z: ndim = 2 matrix = a, b, d, e, xoff, yoff else: raise ValueError("'matrix' expects either 6 or 12 coefficients") def affine_pts(pts): """Internal function to yield affine transform of coordinate tuples""" if ndim == 2: for x, y in pts: xp = a * x + b * y + xoff yp = d * x + e * y + yoff yield (xp, yp) elif ndim == 3: for x, y, z in pts: xp = a * x + b * y + c * z + xoff yp = d * x + e * y + f * z + yoff zp = g * x + h * y + i * z + zoff yield (xp, yp, zp) # Process coordinates from each supported geometry type if geom.type in ('Point', 'LineString', 'LinearRing'): return type(geom)(list(affine_pts(geom.coords))) elif geom.type == 'Polygon': ring = geom.exterior shell = type(ring)(list(affine_pts(ring.coords))) holes = list(geom.interiors) for pos, ring in enumerate(holes): holes[pos] = type(ring)(list(affine_pts(ring.coords))) return type(geom)(shell, holes) elif geom.type.startswith('Multi') or geom.type == 'GeometryCollection': # Recursive call # TODO: fix GeometryCollection constructor return type(geom)([affine_transform(part, matrix) for part in geom.geoms]) else: raise ValueError('Type %r not recognized' % geom.type) def interpret_origin(geom, origin, ndim): """Returns interpreted coordinate tuple for origin parameter. This is a helper function for other transform functions. The point of origin can be a keyword 'center' for the 2D bounding box center, 'centroid' for the geometry's 2D centroid, a Point object or a coordinate tuple (x0, y0, z0). """ # get coordinate tuple from 'origin' from keyword or Point type if origin == 'center': # bounding box center minx, miny, maxx, maxy = geom.bounds origin = ((maxx + minx)/2.0, (maxy + miny)/2.0) elif origin == 'centroid': origin = geom.centroid.coords[0] elif isinstance(origin, str): raise ValueError("'origin' keyword %r is not recognized" % origin) elif hasattr(origin, 'type') and origin.type == 'Point': origin = origin.coords[0] # origin should now be tuple-like if len(origin) not in (2, 3): raise ValueError("Expected number of items in 'origin' to be " "either 2 or 3") if ndim == 2: return origin[0:2] else: # 3D coordinate if len(origin) == 2: return origin + (0.0,) else: return origin def rotate(geom, angle, origin='center', use_radians=False): """Returns a rotated geometry on a 2D plane. The angle of rotation can be specified in either degrees (default) or radians by setting ``use_radians=True``. Positive angles are counter-clockwise and negative are clockwise rotations. The point of origin can be a keyword 'center' for the bounding box center (default), 'centroid' for the geometry's centroid, a Point object or a coordinate tuple (x0, y0). The affine transformation matrix for 2D rotation is: / cos(r) -sin(r) xoff \ | sin(r) cos(r) yoff | \ 0 0 1 / where the offsets are calculated from the origin Point(x0, y0): xoff = x0 - x0 * cos(r) + y0 * sin(r) yoff = y0 - x0 * sin(r) - y0 * cos(r) """ if not use_radians: # convert from degrees angle *= pi/180.0 cosp = cos(angle) sinp = sin(angle) if abs(cosp) < 2.5e-16: cosp = 0.0 if abs(sinp) < 2.5e-16: sinp = 0.0 x0, y0 = interpret_origin(geom, origin, 2) matrix = (cosp, -sinp, 0.0, sinp, cosp, 0.0, 0.0, 0.0, 1.0, x0 - x0 * cosp + y0 * sinp, y0 - x0 * sinp - y0 * cosp, 0.0) return affine_transform(geom, matrix) def scale(geom, xfact=1.0, yfact=1.0, zfact=1.0, origin='center'): """Returns a scaled geometry, scaled by factors along each dimension. The point of origin can be a keyword 'center' for the 2D bounding box center (default), 'centroid' for the geometry's 2D centroid, a Point object or a coordinate tuple (x0, y0, z0). Negative scale factors will mirror or reflect coordinates. The general 3D affine transformation matrix for scaling is: / xfact 0 0 xoff \ | 0 yfact 0 yoff | | 0 0 zfact zoff | \ 0 0 0 1 / where the offsets are calculated from the origin Point(x0, y0, z0): xoff = x0 - x0 * xfact yoff = y0 - y0 * yfact zoff = z0 - z0 * zfact """ x0, y0, z0 = interpret_origin(geom, origin, 3) matrix = (xfact, 0.0, 0.0, 0.0, yfact, 0.0, 0.0, 0.0, zfact, x0 - x0 * xfact, y0 - y0 * yfact, z0 - z0 * zfact) return affine_transform(geom, matrix) def skew(geom, xs=0.0, ys=0.0, origin='center', use_radians=False): """Returns a skewed geometry, sheared by angles along x and y dimensions. The shear angle can be specified in either degrees (default) or radians by setting ``use_radians=True``. The point of origin can be a keyword 'center' for the bounding box center (default), 'centroid' for the geometry's centroid, a Point object or a coordinate tuple (x0, y0). The general 2D affine transformation matrix for skewing is: / 1 tan(xs) xoff \ | tan(ys) 1 yoff | \ 0 0 1 / where the offsets are calculated from the origin Point(x0, y0): xoff = -y0 * tan(xs) yoff = -x0 * tan(ys) """ if not use_radians: # convert from degrees xs *= pi/180.0 ys *= pi/180.0 tanx = tan(xs) tany = tan(ys) if abs(tanx) < 2.5e-16: tanx = 0.0 if abs(tany) < 2.5e-16: tany = 0.0 x0, y0 = interpret_origin(geom, origin, 2) matrix = (1.0, tanx, 0.0, tany, 1.0, 0.0, 0.0, 0.0, 1.0, -y0 * tanx, -x0 * tany, 0.0) return affine_transform(geom, matrix) def translate(geom, xoff=0.0, yoff=0.0, zoff=0.0): """Returns a translated geometry shifted by offsets along each dimension. The general 3D affine transformation matrix for translation is: / 1 0 0 xoff \ | 0 1 0 yoff | | 0 0 1 zoff | \ 0 0 0 1 / """ matrix = (1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, xoff, yoff, zoff) return affine_transform(geom, matrix) Shapely-1.5.13/shapely/algorithms/000077500000000000000000000000001260610516500170265ustar00rootroot00000000000000Shapely-1.5.13/shapely/algorithms/__init__.py000066400000000000000000000000001260610516500211250ustar00rootroot00000000000000Shapely-1.5.13/shapely/algorithms/cga.py000066400000000000000000000007171260610516500201370ustar00rootroot00000000000000 def signed_area(ring): """Return the signed area enclosed by a ring in linear time using the algorithm at: http://www.cgafaq.info/wiki/Polygon_Area. """ xs, ys = ring.coords.xy xs.append(xs[1]) ys.append(ys[1]) return sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(1, len(ring.coords)))/2.0 def is_ccw_impl(name): """Predicate implementation""" def is_ccw_op(ring): return signed_area(ring) >= 0.0 return is_ccw_op Shapely-1.5.13/shapely/coords.py000066400000000000000000000141321260610516500165210ustar00rootroot00000000000000"""Coordinate sequence utilities """ import sys from array import array from ctypes import byref, c_double, c_uint from shapely.geos import lgeos from shapely.topology import Validating if sys.version_info[0] < 3: range = xrange try: import numpy has_numpy = True except ImportError: has_numpy = False def required(ob): """Return an object that meets Shapely requirements for self-owned C-continguous data, copying if necessary, or just return the original object.""" if hasattr(ob, '__array_interface__'): if ob.__array_interface__.get('strides') and not has_numpy: # raise an error if strided. See issue #52. raise ValueError("C-contiguous data is required") else: # numpy.require will just return (ob) if it is already # float64 and well-behaved. return numpy.require(ob, numpy.float64, ["C", "OWNDATA"]) else: return ob class CoordinateSequence(object): """ Iterative access to coordinate tuples from the parent geometry's coordinate sequence. Example: >>> from shapely.wkt import loads >>> g = loads('POINT (0.0 0.0)') >>> list(g.coords) [(0.0, 0.0)] """ # Attributes # ---------- # _cseq : c_void_p # Ctypes pointer to GEOS coordinate sequence # _ndim : int # Number of dimensions (2 or 3, generally) # __p__ : object # Parent (Shapely) geometry _cseq = None _ndim = None __p__ = None def __init__(self, parent): self.__p__ = parent def _update(self): self._ndim = self.__p__._ndim self._cseq = lgeos.GEOSGeom_getCoordSeq(self.__p__._geom) def __len__(self): self._update() cs_len = c_uint(0) lgeos.GEOSCoordSeq_getSize(self._cseq, byref(cs_len)) return cs_len.value def __iter__(self): self._update() dx = c_double() dy = c_double() dz = c_double() has_z = self._ndim == 3 for i in range(self.__len__()): lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(dx)) lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(dy)) if has_z: lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(dz)) yield (dx.value, dy.value, dz.value) else: yield (dx.value, dy.value) def __getitem__(self, key): self._update() dx = c_double() dy = c_double() dz = c_double() m = self.__len__() has_z = self._ndim == 3 if isinstance(key, int): if key + m < 0 or key >= m: raise IndexError("index out of range") if key < 0: i = m + key else: i = key lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(dx)) lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(dy)) if has_z: lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(dz)) return (dx.value, dy.value, dz.value) else: return (dx.value, dy.value) elif isinstance(key, slice): res = [] start, stop, stride = key.indices(m) for i in range(start, stop, stride): lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(dx)) lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(dy)) if has_z: lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(dz)) res.append((dx.value, dy.value, dz.value)) else: res.append((dx.value, dy.value)) return res else: raise TypeError("key must be an index or slice") @property def ctypes(self): self._update() has_z = self._ndim == 3 n = self._ndim m = self.__len__() array_type = c_double * (m * n) data = array_type() temp = c_double() for i in range(m): lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(temp)) data[n*i] = temp.value lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(temp)) data[n*i+1] = temp.value if has_z: lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(temp)) data[n*i+2] = temp.value return data def array_interface(self): """Provide the Numpy array protocol.""" if sys.byteorder == 'little': typestr = ' maxx: maxx = x lgeos.GEOSCoordSeq_getY(cs, i, byref(temp)) y = temp.value if y < miny: miny = y if y > maxy: maxy = y return (minx, miny, maxx, maxy) Shapely-1.5.13/shapely/ctypes_declarations.py000066400000000000000000000407241260610516500212750ustar00rootroot00000000000000'''Prototyping of the GEOS C API See header file: geos-x.y.z/capi/geos_c.h ''' from ctypes import CFUNCTYPE, POINTER, c_void_p, c_char_p, \ c_size_t, c_byte, c_char, c_uint, c_int, c_double, py_object # Derived pointer types c_size_t_p = POINTER(c_size_t) class allocated_c_char_p(c_char_p): '''char pointer return type''' pass EXCEPTION_HANDLER_FUNCTYPE = CFUNCTYPE(None, c_char_p, c_void_p) def prototype(lgeos, geos_version): '''Protype functions in geos_c.h for different version of GEOS Use the GEOS version, not the C API version. ''' ''' Initialization, cleanup, version ''' lgeos.initGEOS.restype = None lgeos.initGEOS.argtypes = [EXCEPTION_HANDLER_FUNCTYPE, EXCEPTION_HANDLER_FUNCTYPE] lgeos.finishGEOS.restype = None lgeos.finishGEOS.argtypes = [] lgeos.GEOSversion.restype = c_char_p lgeos.GEOSversion.argtypes = [] ''' NOTE - These functions are DEPRECATED. Please use the new Reader and writer APIS! ''' lgeos.GEOSGeomFromWKT.restype = c_void_p lgeos.GEOSGeomFromWKT.argtypes = [c_char_p] lgeos.GEOSGeomToWKT.restype = allocated_c_char_p lgeos.GEOSGeomToWKT.argtypes = [c_void_p] lgeos.GEOS_setWKBOutputDims.restype = c_int lgeos.GEOS_setWKBOutputDims.argtypes = [c_int] lgeos.GEOSGeomFromWKB_buf.restype = c_void_p lgeos.GEOSGeomFromWKB_buf.argtypes = [c_void_p, c_size_t] lgeos.GEOSGeomToWKB_buf.restype = allocated_c_char_p lgeos.GEOSGeomToWKB_buf.argtypes = [c_void_p, c_size_t_p] ''' Coordinate sequence ''' lgeos.GEOSCoordSeq_create.restype = c_void_p lgeos.GEOSCoordSeq_create.argtypes = [c_uint, c_uint] lgeos.GEOSCoordSeq_clone.restype = c_void_p lgeos.GEOSCoordSeq_clone.argtypes = [c_void_p] lgeos.GEOSGeom_clone.restype = c_void_p lgeos.GEOSGeom_clone.argtypes = [c_void_p] lgeos.GEOSCoordSeq_destroy.restype = None lgeos.GEOSCoordSeq_destroy.argtypes = [c_void_p] lgeos.GEOSCoordSeq_setX.restype = c_int lgeos.GEOSCoordSeq_setX.argtypes = [c_void_p, c_uint, c_double] lgeos.GEOSCoordSeq_setY.restype = c_int lgeos.GEOSCoordSeq_setY.argtypes = [c_void_p, c_uint, c_double] lgeos.GEOSCoordSeq_setZ.restype = c_int lgeos.GEOSCoordSeq_setZ.argtypes = [c_void_p, c_uint, c_double] lgeos.GEOSCoordSeq_setOrdinate.restype = c_int lgeos.GEOSCoordSeq_setOrdinate.argtypes = [c_void_p, c_uint, c_uint, c_double] lgeos.GEOSCoordSeq_getX.restype = c_int lgeos.GEOSCoordSeq_getX.argtypes = [c_void_p, c_uint, c_void_p] lgeos.GEOSCoordSeq_getY.restype = c_int lgeos.GEOSCoordSeq_getY.argtypes = [c_void_p, c_uint, c_void_p] lgeos.GEOSCoordSeq_getZ.restype = c_int lgeos.GEOSCoordSeq_getZ.argtypes = [c_void_p, c_uint, c_void_p] lgeos.GEOSCoordSeq_getSize.restype = c_int lgeos.GEOSCoordSeq_getSize.argtypes = [c_void_p, c_void_p] lgeos.GEOSCoordSeq_getDimensions.restype = c_int lgeos.GEOSCoordSeq_getDimensions.argtypes = [c_void_p, c_void_p] ''' Linear refeferencing ''' if geos_version >= (3, 2, 0): lgeos.GEOSProject.restype = c_double lgeos.GEOSProject.argtypes = [c_void_p, c_void_p] lgeos.GEOSInterpolate.restype = c_void_p lgeos.GEOSInterpolate.argtypes = [c_void_p, c_double] lgeos.GEOSProjectNormalized.restype = c_double lgeos.GEOSProjectNormalized.argtypes = [c_void_p, c_void_p] lgeos.GEOSInterpolateNormalized.restype = c_void_p lgeos.GEOSInterpolateNormalized.argtypes = [c_void_p, c_double] ''' Buffer related ''' lgeos.GEOSBuffer.restype = c_void_p lgeos.GEOSBuffer.argtypes = [c_void_p, c_double, c_int] if geos_version >= (3, 2, 0): lgeos.GEOSBufferWithStyle.restype = c_void_p lgeos.GEOSBufferWithStyle.argtypes = [c_void_p, c_double, c_int, c_int, c_int, c_double] if geos_version >= (3, 3, 0): lgeos.GEOSOffsetCurve.restype = c_void_p lgeos.GEOSOffsetCurve.argtypes = [c_void_p, c_double, c_int, c_int, c_double] else: # deprecated in GEOS 3.3.0 in favour of GEOSOffsetCurve lgeos.GEOSSingleSidedBuffer.restype = c_void_p lgeos.GEOSSingleSidedBuffer.argtypes = [c_void_p, c_double, c_int, c_int, c_double, c_int] ''' Geometry constructors ''' lgeos.GEOSGeom_createPoint.restype = c_void_p lgeos.GEOSGeom_createPoint.argtypes = [c_void_p] lgeos.GEOSGeom_createLinearRing.restype = c_void_p lgeos.GEOSGeom_createLinearRing.argtypes = [c_void_p] lgeos.GEOSGeom_createLineString.restype = c_void_p lgeos.GEOSGeom_createLineString.argtypes = [c_void_p] lgeos.GEOSGeom_createPolygon.restype = c_void_p lgeos.GEOSGeom_createPolygon.argtypes = [c_void_p, c_void_p, c_uint] lgeos.GEOSGeom_createCollection.restype = c_void_p lgeos.GEOSGeom_createCollection.argtypes = [c_int, c_void_p, c_uint] lgeos.GEOSGeom_clone.restype = c_void_p lgeos.GEOSGeom_clone.argtypes = [c_void_p] ''' Memory management ''' lgeos.GEOSGeom_destroy.restype = None lgeos.GEOSGeom_destroy.argtypes = [c_void_p] ''' Topology operations Return NULL on exception ''' lgeos.GEOSEnvelope.restype = c_void_p lgeos.GEOSEnvelope.argtypes = [c_void_p] lgeos.GEOSIntersection.restype = c_void_p lgeos.GEOSIntersection.argtypes = [c_void_p, c_void_p] lgeos.GEOSConvexHull.restype = c_void_p lgeos.GEOSConvexHull.argtypes = [c_void_p] lgeos.GEOSDifference.restype = c_void_p lgeos.GEOSDifference.argtypes = [c_void_p, c_void_p] lgeos.GEOSSymDifference.restype = c_void_p lgeos.GEOSSymDifference.argtypes = [c_void_p, c_void_p] lgeos.GEOSBoundary.restype = c_void_p lgeos.GEOSBoundary.argtypes = [c_void_p] lgeos.GEOSUnion.restype = c_void_p lgeos.GEOSUnion.argtypes = [c_void_p, c_void_p] if geos_version >= (3, 3, 0): lgeos.GEOSUnaryUnion.restype = c_void_p lgeos.GEOSUnaryUnion.argtypes = [c_void_p] if geos_version >= (3, 1, 0): '''deprecated in 3.3.0: use GEOSUnaryUnion instead''' lgeos.GEOSUnionCascaded.restype = c_void_p lgeos.GEOSUnionCascaded.argtypes = [c_void_p] lgeos.GEOSPointOnSurface.restype = c_void_p lgeos.GEOSPointOnSurface.argtypes = [c_void_p] lgeos.GEOSGetCentroid.restype = c_void_p lgeos.GEOSGetCentroid.argtypes = [c_void_p] lgeos.GEOSPolygonize.restype = c_void_p lgeos.GEOSPolygonize.argtypes = [c_void_p, c_uint] if geos_version >= (3, 3, 0): lgeos.GEOSPolygonize_full.restype = c_void_p lgeos.GEOSPolygonize_full.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p] if geos_version >= (3, 4, 0): lgeos.GEOSDelaunayTriangulation.restype = c_void_p lgeos.GEOSDelaunayTriangulation.argtypes = [c_void_p, c_double, c_int] lgeos.GEOSLineMerge.restype = c_void_p lgeos.GEOSLineMerge.argtypes = [c_void_p] lgeos.GEOSSimplify.restype = c_void_p lgeos.GEOSSimplify.argtypes = [c_void_p, c_double] lgeos.GEOSTopologyPreserveSimplify.restype = c_void_p lgeos.GEOSTopologyPreserveSimplify.argtypes = [c_void_p, c_double] ''' Binary predicates Return 2 on exception, 1 on true, 0 on false ''' lgeos.GEOSDisjoint.restype = c_byte lgeos.GEOSDisjoint.argtypes = [c_void_p, c_void_p] lgeos.GEOSTouches.restype = c_byte lgeos.GEOSTouches.argtypes = [c_void_p, c_void_p] lgeos.GEOSIntersects.restype = c_byte lgeos.GEOSIntersects.argtypes = [c_void_p, c_void_p] lgeos.GEOSCrosses.restype = c_byte lgeos.GEOSCrosses.argtypes = [c_void_p, c_void_p] lgeos.GEOSWithin.restype = c_byte lgeos.GEOSWithin.argtypes = [c_void_p, c_void_p] lgeos.GEOSContains.restype = c_byte lgeos.GEOSContains.argtypes = [c_void_p, c_void_p] lgeos.GEOSOverlaps.restype = c_byte lgeos.GEOSOverlaps.argtypes = [c_void_p, c_void_p] lgeos.GEOSCovers.restype = c_byte lgeos.GEOSCovers.argtypes = [c_void_p, c_void_p] lgeos.GEOSEquals.restype = c_byte lgeos.GEOSEquals.argtypes = [c_void_p, c_void_p] lgeos.GEOSEqualsExact.restype = c_byte lgeos.GEOSEqualsExact.argtypes = [c_void_p, c_void_p, c_double] ''' Unary predicate Return 2 on exception, 1 on true, 0 on false ''' lgeos.GEOSisEmpty.restype = c_byte lgeos.GEOSisEmpty.argtypes = [c_void_p] lgeos.GEOSisValid.restype = c_byte lgeos.GEOSisValid.argtypes = [c_void_p] if geos_version >= (3, 1, 0): lgeos.GEOSisValidReason.restype = allocated_c_char_p lgeos.GEOSisValidReason.argtypes = [c_void_p] lgeos.GEOSisSimple.restype = c_byte lgeos.GEOSisSimple.argtypes = [c_void_p] lgeos.GEOSisRing.restype = c_byte lgeos.GEOSisRing.argtypes = [c_void_p] if geos_version >= (3, 3, 0): lgeos.GEOSisClosed.restype = c_byte lgeos.GEOSisClosed.argtypes = [c_void_p] lgeos.GEOSHasZ.restype = c_byte lgeos.GEOSHasZ.argtypes = [c_void_p] ''' Dimensionally Extended 9 Intersection Model related ''' lgeos.GEOSRelate.restype = allocated_c_char_p lgeos.GEOSRelate.argtypes = [c_void_p, c_void_p] lgeos.GEOSRelatePattern.restype = c_byte lgeos.GEOSRelatePattern.argtypes = [c_void_p, c_void_p, c_char_p] if geos_version >= (3, 3, 0): lgeos.GEOSRelatePatternMatch.restype = c_byte lgeos.GEOSRelatePatternMatch.argtypes = [c_char_p, c_char_p] ''' Prepared Geometry Binary predicates Return 2 on exception, 1 on true, 0 on false ''' if geos_version >= (3, 1, 0): lgeos.GEOSPrepare.restype = c_void_p lgeos.GEOSPrepare.argtypes = [c_void_p] lgeos.GEOSPreparedGeom_destroy.restype = None lgeos.GEOSPreparedGeom_destroy.argtypes = [c_void_p] lgeos.GEOSPreparedDisjoint.restype = c_byte lgeos.GEOSPreparedDisjoint.argtypes = [c_void_p, c_void_p] lgeos.GEOSPreparedTouches.restype = c_byte lgeos.GEOSPreparedTouches.argtypes = [c_void_p, c_void_p] lgeos.GEOSPreparedIntersects.restype = c_byte lgeos.GEOSPreparedIntersects.argtypes = [c_void_p, c_void_p] lgeos.GEOSPreparedCrosses.restype = c_byte lgeos.GEOSPreparedCrosses.argtypes = [c_void_p, c_void_p] lgeos.GEOSPreparedWithin.restype = c_byte lgeos.GEOSPreparedWithin.argtypes = [c_void_p, c_void_p] lgeos.GEOSPreparedContains.restype = c_byte lgeos.GEOSPreparedContains.argtypes = [c_void_p, c_void_p] lgeos.GEOSPreparedContainsProperly.restype = c_byte lgeos.GEOSPreparedContainsProperly.argtypes = [c_void_p, c_void_p] lgeos.GEOSPreparedOverlaps.restype = c_byte lgeos.GEOSPreparedOverlaps.argtypes = [c_void_p, c_void_p] lgeos.GEOSPreparedCovers.restype = c_byte lgeos.GEOSPreparedCovers.argtypes = [c_void_p, c_void_p] ''' Geometry info ''' lgeos.GEOSGeomType.restype = c_char_p lgeos.GEOSGeomType.argtypes = [c_void_p] lgeos.GEOSGeomTypeId.restype = c_int lgeos.GEOSGeomTypeId.argtypes = [c_void_p] lgeos.GEOSGetSRID.restype = c_int lgeos.GEOSGetSRID.argtypes = [c_void_p] lgeos.GEOSSetSRID.restype = None lgeos.GEOSSetSRID.argtypes = [c_void_p, c_int] lgeos.GEOSGetNumGeometries.restype = c_int lgeos.GEOSGetNumGeometries.argtypes = [c_void_p] lgeos.GEOSGetGeometryN.restype = c_void_p lgeos.GEOSGetGeometryN.argtypes = [c_void_p, c_int] lgeos.GEOSGetNumInteriorRings.restype = c_int lgeos.GEOSGetNumInteriorRings.argtypes = [c_void_p] lgeos.GEOSGetInteriorRingN.restype = c_void_p lgeos.GEOSGetInteriorRingN.argtypes = [c_void_p, c_int] lgeos.GEOSGetExteriorRing.restype = c_void_p lgeos.GEOSGetExteriorRing.argtypes = [c_void_p] lgeos.GEOSGetNumCoordinates.restype = c_int lgeos.GEOSGetNumCoordinates.argtypes = [c_void_p] lgeos.GEOSGeom_getCoordSeq.restype = c_void_p lgeos.GEOSGeom_getCoordSeq.argtypes = [c_void_p] lgeos.GEOSGeom_getDimensions.restype = c_int lgeos.GEOSGeom_getDimensions.argtypes = [c_void_p] ''' Misc functions ''' lgeos.GEOSArea.restype = c_double lgeos.GEOSArea.argtypes = [c_void_p, c_void_p] lgeos.GEOSLength.restype = c_int lgeos.GEOSLength.argtypes = [c_void_p, c_void_p] lgeos.GEOSDistance.restype = c_int lgeos.GEOSDistance.argtypes = [c_void_p, c_void_p, c_void_p] ''' Reader and Writer APIs ''' '''WKT Reader''' lgeos.GEOSWKTReader_create.restype = c_void_p lgeos.GEOSWKTReader_create.argtypes = [] lgeos.GEOSWKTReader_destroy.restype = None lgeos.GEOSWKTReader_destroy.argtypes = [c_void_p] lgeos.GEOSWKTReader_read.restype = c_void_p lgeos.GEOSWKTReader_read.argtypes = [c_void_p, c_char_p] '''WKT Writer''' lgeos.GEOSWKTWriter_create.restype = c_void_p lgeos.GEOSWKTWriter_create.argtypes = [] lgeos.GEOSWKTWriter_destroy.restype = None lgeos.GEOSWKTWriter_destroy.argtypes = [c_void_p] lgeos.GEOSWKTWriter_write.restype = allocated_c_char_p lgeos.GEOSWKTWriter_write.argtypes = [c_void_p, c_void_p] if geos_version >= (3, 3, 0): lgeos.GEOSWKTWriter_setTrim.restype = None lgeos.GEOSWKTWriter_setTrim.argtypes = [c_void_p, c_int] lgeos.GEOSWKTWriter_setRoundingPrecision.restype = None lgeos.GEOSWKTWriter_setRoundingPrecision.argtypes = [c_void_p, c_int] lgeos.GEOSWKTWriter_setOutputDimension.restype = None lgeos.GEOSWKTWriter_setOutputDimension.argtypes = [c_void_p, c_int] lgeos.GEOSWKTWriter_getOutputDimension.restype = c_int lgeos.GEOSWKTWriter_getOutputDimension.argtypes = [c_void_p] lgeos.GEOSWKTWriter_setOld3D.restype = None lgeos.GEOSWKTWriter_setOld3D.argtypes = [c_void_p, c_int] '''WKB Reader''' lgeos.GEOSWKBReader_create.restype = c_void_p lgeos.GEOSWKBReader_create.argtypes = [] lgeos.GEOSWKBReader_destroy.restype = None lgeos.GEOSWKBReader_destroy.argtypes = [c_void_p] lgeos.GEOSWKBReader_read.restype = c_void_p lgeos.GEOSWKBReader_read.argtypes = [c_void_p, c_char_p, c_size_t] lgeos.GEOSWKBReader_readHEX.restype = c_void_p lgeos.GEOSWKBReader_readHEX.argtypes = [c_void_p, c_char_p, c_size_t] '''WKB Writer''' lgeos.GEOSWKBWriter_create.restype = c_void_p lgeos.GEOSWKBWriter_create.argtypes = [] lgeos.GEOSWKBWriter_destroy.restype = None lgeos.GEOSWKBWriter_destroy.argtypes = [c_void_p] lgeos.GEOSWKBWriter_write.restype = allocated_c_char_p lgeos.GEOSWKBWriter_write.argtypes = [c_void_p, c_void_p, c_size_t_p] lgeos.GEOSWKBWriter_writeHEX.restype = allocated_c_char_p lgeos.GEOSWKBWriter_writeHEX.argtypes = [c_void_p, c_void_p, c_size_t_p] lgeos.GEOSWKBWriter_getOutputDimension.restype = c_int lgeos.GEOSWKBWriter_getOutputDimension.argtypes = [c_void_p] lgeos.GEOSWKBWriter_setOutputDimension.restype = None lgeos.GEOSWKBWriter_setOutputDimension.argtypes = [c_void_p, c_int] lgeos.GEOSWKBWriter_getByteOrder.restype = c_int lgeos.GEOSWKBWriter_getByteOrder.argtypes = [c_void_p] lgeos.GEOSWKBWriter_setByteOrder.restype = None lgeos.GEOSWKBWriter_setByteOrder.argtypes = [c_void_p, c_int] lgeos.GEOSWKBWriter_getIncludeSRID.restype = c_int lgeos.GEOSWKBWriter_getIncludeSRID.argtypes = [c_void_p] lgeos.GEOSWKBWriter_setIncludeSRID.restype = None lgeos.GEOSWKBWriter_setIncludeSRID.argtypes = [c_void_p, c_int] if geos_version >= (3, 1, 1): ''' Free buffers returned by stuff like GEOSWKBWriter_write(), GEOSWKBWriter_writeHEX() and GEOSWKTWriter_write() ''' lgeos.GEOSFree.restype = None lgeos.GEOSFree.argtypes = [c_void_p] if geos_version >= (3, 3, 0): lgeos.GEOSSnap.restype = c_void_p lgeos.GEOSSnap.argtypes = [c_void_p, c_void_p, c_double] if geos_version >= (3, 4, 0): lgeos.GEOSNearestPoints.restype = c_void_p lgeos.GEOSNearestPoints.argtypes = [c_void_p, c_void_p] if geos_version >= (3, 4, 2): lgeos.GEOSQueryCallback = CFUNCTYPE(None, c_void_p, c_void_p) lgeos.GEOSSTRtree_query.argtypes = [c_void_p, c_void_p, lgeos.GEOSQueryCallback, py_object] lgeos.GEOSSTRtree_query.restype = None lgeos.GEOSSTRtree_create.argtypes = [c_int] lgeos.GEOSSTRtree_create.restype = c_void_p lgeos.GEOSSTRtree_insert.argtypes = [c_void_p, c_void_p, py_object] lgeos.GEOSSTRtree_insert.restype = None lgeos.GEOSSTRtree_remove.argtypes = [c_void_p, c_void_p, py_object] lgeos.GEOSSTRtree_remove.restype = None lgeos.GEOSSTRtree_destroy.argtypes = [c_void_p] lgeos.GEOSSTRtree_destroy.restype = None Shapely-1.5.13/shapely/examples/000077500000000000000000000000001260610516500164735ustar00rootroot00000000000000Shapely-1.5.13/shapely/examples/__init__.py000066400000000000000000000000221260610516500205760ustar00rootroot00000000000000# Examples module Shapely-1.5.13/shapely/examples/dissolve.py000066400000000000000000000033231260610516500206760ustar00rootroot00000000000000# dissolve.py # # Demonstrate how Shapely can be used to build up a collection of patches by # dissolving circular regions and how Shapely supports plotting of the results. from functools import partial import random import pylab from shapely.geometry import Point from shapely.ops import cascaded_union # Use a partial function to make 100 points uniformly distributed in a 40x40 # box centered on 0,0. r = partial(random.uniform, -20.0, 20.0) points = [Point(r(), r()) for i in range(100)] # Buffer the points, producing 100 polygon spots spots = [p.buffer(2.5) for p in points] # Perform a cascaded union of the polygon spots, dissolving them into a # collection of polygon patches patches = cascaded_union(spots) if __name__ == "__main__": # Illustrate the results using matplotlib's pylab interface pylab.figure(num=None, figsize=(4, 4), dpi=180) for patch in patches.geoms: assert patch.geom_type in ['Polygon'] assert patch.is_valid # Fill and outline each patch x, y = patch.exterior.xy pylab.fill(x, y, color='#cccccc', aa=True) pylab.plot(x, y, color='#666666', aa=True, lw=1.0) # Do the same for the holes of the patch for hole in patch.interiors: x, y = hole.xy pylab.fill(x, y, color='#ffffff', aa=True) pylab.plot(x, y, color='#999999', aa=True, lw=1.0) # Plot the original points pylab.plot([p.x for p in points], [p.y for p in points], 'b,', alpha=0.75) # Write the number of patches and the total patch area to the figure pylab.text(-25, 25, "Patches: %d, total area: %.2f" % (len(patches.geoms), patches.area)) pylab.savefig('dissolve.png') Shapely-1.5.13/shapely/examples/geoms.py000066400000000000000000000022421260610516500201570ustar00rootroot00000000000000from numpy import asarray import pylab from shapely.geometry import Point, LineString, Polygon polygon = Polygon(((-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0), (1.0, -1.0))) point_r = Point(-1.5, 1.2) point_g = Point(-1.0, 1.0) point_b = Point(-0.5, 0.5) line_r = LineString(((-0.5, 0.5), (0.5, 0.5))) line_g = LineString(((1.0, -1.0), (1.8, 0.5))) line_b = LineString(((-1.8, -1.2), (1.8, 0.5))) def plot_point(g, o, l): pylab.plot([g.x], [g.y], o, label=l) def plot_line(g, o): a = asarray(g) pylab.plot(a[:,0], a[:,1], o) def fill_polygon(g, o): a = asarray(g.exterior) pylab.fill(a[:,0], a[:,1], o, alpha=0.5) def fill_multipolygon(g, o): for g in g.geoms: fill_polygon(g, o) if __name__ == "__main__": from numpy import asarray import pylab fig = pylab.figure(1, figsize=(4, 3), dpi=150) #pylab.axis([-2.0, 2.0, -1.5, 1.5]) pylab.axis('tight') a = asarray(polygon.exterior) pylab.fill(a[:,0], a[:,1], 'c') plot_point(point_r, 'ro', 'b') plot_point(point_g, 'go', 'c') plot_point(point_b, 'bo', 'd') plot_line(line_r, 'r') plot_line(line_g, 'g') plot_line(line_b, 'b') pylab.show() Shapely-1.5.13/shapely/examples/intersect.py000066400000000000000000000051021260610516500210430ustar00rootroot00000000000000# intersect.py # # Demonstrate how Shapely can be used to analyze and plot the intersection of # a trajectory and regions in space. from functools import partial import random import pylab from shapely.geometry import LineString, Point from shapely.ops import cascaded_union # Build patches as in dissolved.py r = partial(random.uniform, -20.0, 20.0) points = [Point(r(), r()) for i in range(100)] spots = [p.buffer(2.5) for p in points] patches = cascaded_union(spots) # Represent the following geolocation parameters # # initial position: -25, -25 # heading: 45.0 # speed: 50*sqrt(2) # # as a line vector = LineString(((-25.0, -25.0), (25.0, 25.0))) # Find intercepted and missed patches. List the former so we can count them # later intercepts = [patch for patch in patches.geoms if vector.intersects(patch)] misses = (patch for patch in patches.geoms if not vector.intersects(patch)) # Plot the intersection intersection = vector.intersection(patches) assert intersection.geom_type in ['MultiLineString'] if __name__ == "__main__": # Illustrate the results using matplotlib's pylab interface pylab.figure(num=None, figsize=(4, 4), dpi=180) # Plot the misses for spot in misses: x, y = spot.exterior.xy pylab.fill(x, y, color='#cccccc', aa=True) pylab.plot(x, y, color='#999999', aa=True, lw=1.0) # Do the same for the holes of the patch for hole in spot.interiors: x, y = hole.xy pylab.fill(x, y, color='#ffffff', aa=True) pylab.plot(x, y, color='#999999', aa=True, lw=1.0) # Plot the intercepts for spot in intercepts: x, y = spot.exterior.xy pylab.fill(x, y, color='red', alpha=0.25, aa=True) pylab.plot(x, y, color='red', alpha=0.5, aa=True, lw=1.0) # Do the same for the holes of the patch for hole in spot.interiors: x, y = hole.xy pylab.fill(x, y, color='#ffffff', aa=True) pylab.plot(x, y, color='red', alpha=0.5, aa=True, lw=1.0) # Draw the projected trajectory pylab.arrow(-25, -25, 50, 50, color='#999999', aa=True, head_width=1.0, head_length=1.0) for segment in intersection.geoms: x, y = segment.xy pylab.plot(x, y, color='red', aa=True, lw=1.5) # Write the number of patches and the total patch area to the figure pylab.text(-28, 25, "Patches: %d/%d (%d), total length: %.1f" \ % (len(intercepts), len(patches.geoms), len(intersection.geoms), intersection.length)) pylab.savefig('intersect.png') Shapely-1.5.13/shapely/ftools.py000066400000000000000000000055641260610516500165470ustar00rootroot00000000000000# Backport some of functools from Python 2.5 standard library for Shapely # on Python 2.4 # # Python module wrapper for _functools C module # to allow utilities written in Python to be added # to the functools module. # Written by Nick Coghlan # Copyright (C) 2006 Python Software Foundation. # See C source code for _functools credits/copyright # # _functools module written and maintained # by Hye-Shik Chang # with adaptations by Raymond Hettinger # Copyright (c) 2004, 2005, 2006 Python Software Foundation. # All rights reserved. def _partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func( *(args + fargs), **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc # update_wrapper() and wraps() are tools to help write # wrapper functions that can handle naive introspection WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__') WRAPPER_UPDATES = ('__dict__',) def _update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): """Update a wrapper function to look like the wrapped function wrapper is the function to be updated wrapped is the original function assigned is a tuple naming the attributes assigned directly from the wrapped function to the wrapper function (defaults to functools.WRAPPER_ASSIGNMENTS) updated is a tuple naming the attributes of the wrapper that are updated with the corresponding attribute from the wrapped function (defaults to functools.WRAPPER_UPDATES) """ for attr in assigned: setattr(wrapper, attr, getattr(wrapped, attr)) for attr in updated: getattr(wrapper, attr).update(getattr(wrapped, attr, {})) # Return the wrapper so this can be used as a decorator via partial() return wrapper def _wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): """Decorator factory to apply update_wrapper() to a wrapper function Returns a decorator that invokes update_wrapper() with the decorated function as the wrapper argument and the arguments to wraps() as the remaining arguments. Default arguments are as for update_wrapper(). This is a convenience function to simplify applying partial() to update_wrapper(). """ return _partial(_update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) # Use stdlib's functools if available try: from functools import partial, update_wrapper, wraps have_functools = 1 except ImportError: partial = _partial update_wrapper = _update_wrapper wraps = _wraps have_functools = 0 Shapely-1.5.13/shapely/geometry/000077500000000000000000000000001260610516500165105ustar00rootroot00000000000000Shapely-1.5.13/shapely/geometry/__init__.py000066400000000000000000000014571260610516500206300ustar00rootroot00000000000000"""Geometry classes and factories """ from .base import CAP_STYLE, JOIN_STYLE from .geo import box, shape, asShape, mapping from .point import Point, asPoint from .linestring import LineString, asLineString from .polygon import Polygon, asPolygon, LinearRing, asLinearRing from .multipoint import MultiPoint, asMultiPoint from .multilinestring import MultiLineString, asMultiLineString from .multipolygon import MultiPolygon, asMultiPolygon from .collection import GeometryCollection __all__ = [ 'box', 'shape', 'asShape', 'Point', 'asPoint', 'LineString', 'asLineString', 'Polygon', 'asPolygon', 'MultiPoint', 'asMultiPoint', 'MultiLineString', 'asMultiLineString', 'MultiPolygon', 'asMultiPolygon', 'GeometryCollection', 'mapping', 'LinearRing', 'asLinearRing', 'CAP_STYLE', 'JOIN_STYLE', ] Shapely-1.5.13/shapely/geometry/base.py000066400000000000000000000672761260610516500200160ustar00rootroot00000000000000"""Base geometry class and utilities """ import sys from warnings import warn from binascii import a2b_hex from ctypes import pointer, c_size_t, c_char_p, c_void_p from shapely.coords import CoordinateSequence from shapely.ftools import wraps from shapely.geos import lgeos, ReadingError from shapely.geos import WKBWriter, WKTWriter from shapely.impl import DefaultImplementation, delegated if sys.version_info[0] < 3: range = xrange integer_types = (int, long) else: integer_types = (int,) try: import numpy as np integer_types = integer_types + (np.integer,) except ImportError: pass GEOMETRY_TYPES = [ 'Point', 'LineString', 'LinearRing', 'Polygon', 'MultiPoint', 'MultiLineString', 'MultiPolygon', 'GeometryCollection', ] def dump_coords(geom): """Dump coordinates of a geometry in the same order as data packing""" if not isinstance(geom, BaseGeometry): raise ValueError('Must be instance of a geometry class; found ' + geom.__class__.__name__) elif geom.type in ('Point', 'LineString', 'LinearRing'): return geom.coords[:] elif geom.type == 'Polygon': return geom.exterior.coords[:] + [i.coords[:] for i in geom.interiors] elif geom.type.startswith('Multi') or geom.type == 'GeometryCollection': # Recursive call return [dump_coords(part) for part in geom] else: raise ValueError('Unhandled geometry type: ' + repr(geom.type)) def geometry_type_name(g): if g is None: raise ValueError("Null geometry has no type") return GEOMETRY_TYPES[lgeos.GEOSGeomTypeId(g)] def geom_factory(g, parent=None): # Abstract geometry factory for use with topological methods below if not g: raise ValueError("No Shapely geometry can be created from null value") ob = BaseGeometry() geom_type = geometry_type_name(g) # TODO: check cost of dynamic import by profiling mod = __import__( 'shapely.geometry', globals(), locals(), [geom_type], ) ob.__class__ = getattr(mod, geom_type) ob._geom = g ob.__p__ = parent if lgeos.methods['has_z'](g): ob._ndim = 3 else: ob._ndim = 2 return ob def geom_from_wkt(data): warn("`geom_from_wkt` is deprecated. Use `geos.wkt_reader.read(data)`.", DeprecationWarning) if sys.version_info[0] >= 3: data = data.encode('ascii') geom = lgeos.GEOSGeomFromWKT(c_char_p(data)) if not geom: raise ReadingError( "Could not create geometry because of errors while reading input.") return geom_factory(geom) def geom_to_wkt(ob): warn("`geom_to_wkt` is deprecated. Use `geos.wkt_writer.write(ob)`.", DeprecationWarning) if ob is None or ob._geom is None: raise ValueError("Null geometry supports no operations") return lgeos.GEOSGeomToWKT(ob._geom) def deserialize_wkb(data): geom = lgeos.GEOSGeomFromWKB_buf(c_char_p(data), c_size_t(len(data))) if not geom: raise ReadingError( "Could not create geometry because of errors while reading input.") return geom def geom_from_wkb(data): warn("`geom_from_wkb` is deprecated. Use `geos.wkb_reader.read(data)`.", DeprecationWarning) return geom_factory(deserialize_wkb(data)) def geom_to_wkb(ob): warn("`geom_to_wkb` is deprecated. Use `geos.wkb_writer.write(ob)`.", DeprecationWarning) if ob is None or ob._geom is None: raise ValueError("Null geometry supports no operations") size = c_size_t() return lgeos.GEOSGeomToWKB_buf(c_void_p(ob._geom), pointer(size)) def geos_geom_from_py(ob, create_func=None): """Helper function for geos_*_from_py functions in each geom type. If a create_func is specified the coodinate sequence is cloned and a new geometry is created with it, otherwise the geometry is cloned directly. This behaviour is useful for converting between LineString and LinearRing objects. """ if create_func is None: geom = lgeos.GEOSGeom_clone(ob._geom) else: cs = lgeos.GEOSGeom_getCoordSeq(ob._geom) cs = lgeos.GEOSCoordSeq_clone(cs) geom = create_func(cs) N = ob._ndim return geom, N def exceptNull(func): """Decorator which helps avoid GEOS operations on null pointers.""" @wraps(func) def wrapper(*args, **kwargs): if not args[0]._geom or args[0].is_empty: raise ValueError("Null/empty geometry supports no operations") return func(*args, **kwargs) return wrapper class CAP_STYLE(object): round = 1 flat = 2 square = 3 class JOIN_STYLE(object): round = 1 mitre = 2 bevel = 3 EMPTY = deserialize_wkb(a2b_hex(b'010700000000000000')) class BaseGeometry(object): """ Provides GEOS spatial predicates and topological operations. """ # Attributes # ---------- # __geom__ : c_void_p # Cached ctypes pointer to GEOS geometry. Not to be accessed. # _geom : c_void_p # Property by which the GEOS geometry is accessed. # __p__ : object # Parent (Shapely) geometry # _ctypes_data : object # Cached ctypes data buffer # _ndim : int # Number of dimensions (2 or 3, generally) # _crs : object # Coordinate reference system. Available for Shapely extensions, but # not implemented here. # _other_owned : bool # True if this object's GEOS geometry is owned by another as in the # case of a multipart geometry member. __geom__ = EMPTY __p__ = None _ctypes_data = None _ndim = None _crs = None _other_owned = False _is_empty = True # Backend config impl = DefaultImplementation # a reference to the so/dll proxy to preserve access during clean up _lgeos = lgeos def empty(self, val=EMPTY): # TODO: defer cleanup to the implementation. We shouldn't be # explicitly calling a lgeos method here. if not self._is_empty and not self._other_owned and self.__geom__: try: self._lgeos.GEOSGeom_destroy(self.__geom__) except AttributeError: pass # _lgeos might be empty on shutdown self._is_empty = True self.__geom__ = val def __del__(self): self.empty(val=None) self.__p__ = None def __str__(self): return self.wkt # To support pickling def __reduce__(self): return (self.__class__, (), self.wkb) def __setstate__(self, state): self.empty() self.__geom__ = deserialize_wkb(state) if lgeos.methods['has_z'](self.__geom__): self._ndim = 3 else: self._ndim = 2 @property def _geom(self): return self.__geom__ @_geom.setter def _geom(self, val): self.empty() self._is_empty = val in [EMPTY, None] self.__geom__ = val # Operators # --------- def __and__(self, other): return self.intersection(other) def __or__(self, other): return self.union(other) def __sub__(self, other): return self.difference(other) def __xor__(self, other): return self.symmetric_difference(other) def __eq__(self, other): return ( isinstance(other, self.__class__) and tuple(self.coords) == tuple(other.coords) ) def __ne__(self, other): return not self.__eq__(other) __hash__ = None # Array and ctypes interfaces # --------------------------- @property def ctypes(self): """Return ctypes buffer""" raise NotImplementedError @property def array_interface_base(self): if sys.byteorder == 'little': typestr = '' else: # Establish SVG canvas that will fit all the data + small space xmin, ymin, xmax, ymax = self.bounds if xmin == xmax and ymin == ymax: # This is a point; buffer using an arbitrary size xmin, ymin, xmax, ymax = self.buffer(1).bounds else: # Expand bounds by a fraction of the data ranges expand = 0.04 # or 4%, same as R plots widest_part = max([xmax - xmin, ymax - ymin]) expand_amount = widest_part * expand xmin -= expand_amount ymin -= expand_amount xmax += expand_amount ymax += expand_amount dx = xmax - xmin dy = ymax - ymin width = min([max([100., dx]), 300]) height = min([max([100., dy]), 300]) try: scale_factor = max([dx, dy]) / max([width, height]) except ZeroDivisionError: scale_factor = 1. view_box = "{0} {1} {2} {3}".format(xmin, ymin, dx, dy) transform = "matrix(1,0,0,-1,0,{0})".format(ymax + ymin) return svg_top + ( 'width="{1}" height="{2}" viewBox="{0}" ' 'preserveAspectRatio="xMinYMin meet">' '{4}' ).format(view_box, width, height, transform, self.svg(scale_factor)) @property def geom_type(self): """Name of the geometry's type, such as 'Point'""" return self.geometryType() # Real-valued properties and methods # ---------------------------------- @property def area(self): """Unitless area of the geometry (float)""" return self.impl['area'](self) def distance(self, other): """Unitless distance to other geometry (float)""" return self.impl['distance'](self, other) @property def length(self): """Unitless length of the geometry (float)""" return self.impl['length'](self) # Topological properties # ---------------------- @property def boundary(self): """Returns a lower dimension geometry that bounds the object The boundary of a polygon is a line, the boundary of a line is a collection of points. The boundary of a point is an empty (null) collection. """ return geom_factory(self.impl['boundary'](self)) @property def bounds(self): """Returns minimum bounding region (minx, miny, maxx, maxy)""" if self.is_empty: return () else: return self.impl['bounds'](self) @property def centroid(self): """Returns the geometric center of the object""" return geom_factory(self.impl['centroid'](self)) @delegated def representative_point(self): """Returns a point guaranteed to be within the object, cheaply.""" return geom_factory(self.impl['representative_point'](self)) @property def convex_hull(self): """Imagine an elastic band stretched around the geometry: that's a convex hull, more or less The convex hull of a three member multipoint, for example, is a triangular polygon. """ return geom_factory(self.impl['convex_hull'](self)) @property def envelope(self): """A figure that envelopes the geometry""" return geom_factory(self.impl['envelope'](self)) def buffer(self, distance, resolution=16, quadsegs=None, cap_style=CAP_STYLE.round, join_style=JOIN_STYLE.round, mitre_limit=5.0): """Returns a geometry with an envelope at a distance from the object's envelope A negative distance has a "shrink" effect. A zero distance may be used to "tidy" a polygon. The resolution of the buffer around each vertex of the object increases by increasing the resolution keyword parameter or second positional parameter. Note: the use of a `quadsegs` parameter is deprecated and will be gone from the next major release. The styles of caps are: CAP_STYLE.round (1), CAP_STYLE.flat (2), and CAP_STYLE.square (3). The styles of joins between offset segments are: JOIN_STYLE.round (1), JOIN_STYLE.mitre (2), and JOIN_STYLE.bevel (3). The mitre limit ratio is used for very sharp corners. The mitre ratio is the ratio of the distance from the corner to the end of the mitred offset corner. When two line segments meet at a sharp angle, a miter join will extend the original geometry. To prevent unreasonable geometry, the mitre limit allows controlling the maximum length of the join corner. Corners with a ratio which exceed the limit will be beveled. Example: >>> from shapely.wkt import loads >>> g = loads('POINT (0.0 0.0)') >>> g.buffer(1.0).area # 16-gon approx of a unit radius circle 3.1365484905459389 >>> g.buffer(1.0, 128).area # 128-gon approximation 3.1415138011443009 >>> g.buffer(1.0, 3).area # triangle approximation 3.0 >>> list(g.buffer(1.0, cap_style='square').exterior.coords) [(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0)] >>> g.buffer(1.0, cap_style='square').area 4.0 """ if quadsegs is not None: warn( "The `quadsegs` argument is deprecated. Use `resolution`.", DeprecationWarning) res = quadsegs else: res = resolution if mitre_limit == 0.0: raise ValueError( 'Cannot compute offset from zero-length line segment') if cap_style == CAP_STYLE.round and join_style == JOIN_STYLE.round: return geom_factory(self.impl['buffer'](self, distance, res)) if 'buffer_with_style' not in self.impl: raise NotImplementedError("Styled buffering not available for " "GEOS versions < 3.2.") return geom_factory(self.impl['buffer_with_style'](self, distance, res, cap_style, join_style, mitre_limit)) @delegated def simplify(self, tolerance, preserve_topology=True): """Returns a simplified geometry produced by the Douglas-Peucker algorithm Coordinates of the simplified geometry will be no more than the tolerance distance from the original. Unless the topology preserving option is used, the algorithm may produce self-intersecting or otherwise invalid geometries. """ if preserve_topology: op = self.impl['topology_preserve_simplify'] else: op = self.impl['simplify'] return geom_factory(op(self, tolerance)) # Binary operations # ----------------- def difference(self, other): """Returns the difference of the geometries""" return geom_factory(self.impl['difference'](self, other)) def intersection(self, other): """Returns the intersection of the geometries""" return geom_factory(self.impl['intersection'](self, other)) def symmetric_difference(self, other): """Returns the symmetric difference of the geometries (Shapely geometry)""" return geom_factory(self.impl['symmetric_difference'](self, other)) def union(self, other): """Returns the union of the geometries (Shapely geometry)""" return geom_factory(self.impl['union'](self, other)) # Unary predicates # ---------------- @property def has_z(self): """True if the geometry's coordinate sequence(s) have z values (are 3-dimensional)""" return bool(self.impl['has_z'](self)) @property def is_empty(self): """True if the set of points in this geometry is empty, else False""" return (self._geom is None) or bool(self.impl['is_empty'](self)) @property def is_ring(self): """True if the geometry is a closed ring, else False""" return bool(self.impl['is_ring'](self)) @property def is_closed(self): """True if the geometry is closed, else False Applicable only to 1-D geometries.""" if self.geom_type == 'LinearRing': return True elif self.geom_type == 'LineString': if 'is_closed' in self.impl: return bool(self.impl['is_closed'](self)) else: return self.coords[0] == self.coords[-1] else: return False @property def is_simple(self): """True if the geometry is simple, meaning that any self-intersections are only at boundary points, else False""" return bool(self.impl['is_simple'](self)) @property def is_valid(self): """True if the geometry is valid (definition depends on sub-class), else False""" return bool(self.impl['is_valid'](self)) # Binary predicates # ----------------- def relate(self, other): """Returns the DE-9IM intersection matrix for the two geometries (string)""" return self.impl['relate'](self, other) def covers(self, other): """Returns True if the geometry covers the other, else False""" return bool(self.impl['covers'](self, other)) def contains(self, other): """Returns True if the geometry contains the other, else False""" return bool(self.impl['contains'](self, other)) def crosses(self, other): """Returns True if the geometries cross, else False""" return bool(self.impl['crosses'](self, other)) def disjoint(self, other): """Returns True if geometries are disjoint, else False""" return bool(self.impl['disjoint'](self, other)) def equals(self, other): """Returns True if geometries are equal, else False""" return bool(self.impl['equals'](self, other)) def intersects(self, other): """Returns True if geometries intersect, else False""" return bool(self.impl['intersects'](self, other)) def overlaps(self, other): """Returns True if geometries overlap, else False""" return bool(self.impl['overlaps'](self, other)) def touches(self, other): """Returns True if geometries touch, else False""" return bool(self.impl['touches'](self, other)) def within(self, other): """Returns True if geometry is within the other, else False""" return bool(self.impl['within'](self, other)) def equals_exact(self, other, tolerance): """Returns True if geometries are equal to within a specified tolerance""" # return BinaryPredicateOp('equals_exact', self)(other, tolerance) return bool(self.impl['equals_exact'](self, other, tolerance)) def almost_equals(self, other, decimal=6): """Returns True if geometries are equal at all coordinates to a specified decimal place""" return self.equals_exact(other, 0.5 * 10**(-decimal)) def relate_pattern(self, other, pattern): """Returns True if the DE-9IM string code for the relationship between the geometries satisfies the pattern, else False""" pattern = c_char_p(pattern.encode('ascii')) return bool(self.impl['relate_pattern'](self, other, pattern)) # Linear referencing # ------------------ @delegated def project(self, other, normalized=False): """Returns the distance along this geometry to a point nearest the specified point If the normalized arg is True, return the distance normalized to the length of the linear geometry. """ if normalized: op = self.impl['project_normalized'] else: op = self.impl['project'] return op(self, other) @delegated def interpolate(self, distance, normalized=False): """Return a point at the specified distance along a linear geometry If the normalized arg is True, the distance will be interpreted as a fraction of the geometry's length. """ if normalized: op = self.impl['interpolate_normalized'] else: op = self.impl['interpolate'] return geom_factory(op(self, distance)) class BaseMultipartGeometry(BaseGeometry): def shape_factory(self, *args): # Factory for part instances, usually a geometry class raise NotImplementedError("To be implemented by derived classes") @property def ctypes(self): raise NotImplementedError( "Multi-part geometries have no ctypes representations") @property def __array_interface__(self): """Provide the Numpy array protocol.""" raise NotImplementedError("Multi-part geometries do not themselves " "provide the array interface") def _get_coords(self): raise NotImplementedError("Sub-geometries may have coordinate " "sequences, but collections do not") def _set_coords(self, ob): raise NotImplementedError("Sub-geometries may have coordinate " "sequences, but collections do not") @property def coords(self): raise NotImplementedError( "Multi-part geometries do not provide a coordinate sequence") @property def geoms(self): if self.is_empty: return [] return GeometrySequence(self, self.shape_factory) def __iter__(self): if not self.is_empty: return iter(self.geoms) else: return iter([]) def __len__(self): if not self.is_empty: return len(self.geoms) else: return 0 def __getitem__(self, index): if not self.is_empty: return self.geoms[index] else: return ()[index] def __eq__(self, other): return ( isinstance(other, self.__class__) and len(self) == len(other) and all(x == y for x, y in zip(self, other)) ) def __ne__(self, other): return not self.__eq__(other) __hash__ = None def svg(self, scale_factor=1., color=None): """Returns a group of SVG elements for the multipart geometry. Parameters ========== scale_factor : float Multiplication factor for the SVG stroke-width. Default is 1. color : str, optional Hex string for stroke or fill color. Default is to use "#66cc99" if geometry is valid, and "#ff3333" if invalid. """ if self.is_empty: return '' if color is None: color = "#66cc99" if self.is_valid else "#ff3333" return '' + \ ''.join(p.svg(scale_factor, color) for p in self) + \ '' class GeometrySequence(object): """ Iterative access to members of a homogeneous multipart geometry. """ # Attributes # ---------- # _factory : callable # Returns instances of Shapely geometries # _geom : c_void_p # Ctypes pointer to the parent's GEOS geometry # _ndim : int # Number of dimensions (2 or 3, generally) # __p__ : object # Parent (Shapely) geometry shape_factory = None _geom = None __p__ = None _ndim = None def __init__(self, parent, type): self.shape_factory = type self.__p__ = parent def _update(self): self._geom = self.__p__._geom self._ndim = self.__p__._ndim def _get_geom_item(self, i): g = self.shape_factory() g._other_owned = True g._geom = lgeos.GEOSGetGeometryN(self._geom, i) g._ndim = self._ndim g.__p__ = self return g def __iter__(self): self._update() for i in range(self.__len__()): yield self._get_geom_item(i) def __len__(self): self._update() return lgeos.GEOSGetNumGeometries(self._geom) def __getitem__(self, key): self._update() m = self.__len__() if isinstance(key, integer_types): if key + m < 0 or key >= m: raise IndexError("index out of range") if key < 0: i = m + key else: i = key return self._get_geom_item(i) elif isinstance(key, slice): if type(self) == HeterogeneousGeometrySequence: raise TypeError( "Heterogenous geometry collections are not sliceable") res = [] start, stop, stride = key.indices(m) for i in range(start, stop, stride): res.append(self._get_geom_item(i)) return type(self.__p__)(res or None) else: raise TypeError("key must be an index or slice") @property def _longest(self): max = 0 for g in iter(self): l = len(g.coords) if l > max: max = l class HeterogeneousGeometrySequence(GeometrySequence): """ Iterative access to a heterogeneous sequence of geometries. """ def __init__(self, parent): super(HeterogeneousGeometrySequence, self).__init__(parent, None) def _get_geom_item(self, i): sub = lgeos.GEOSGetGeometryN(self._geom, i) g = geom_factory(sub, parent=self) g._other_owned = True return g def _test(): """Test runner""" import doctest doctest.testmod() if __name__ == "__main__": _test() Shapely-1.5.13/shapely/geometry/collection.py000066400000000000000000000037571260610516500212310ustar00rootroot00000000000000"""Multi-part collections of geometries """ from ctypes import c_void_p from shapely.geos import lgeos from shapely.geometry.base import BaseGeometry from shapely.geometry.base import BaseMultipartGeometry from shapely.geometry.base import HeterogeneousGeometrySequence from shapely.geometry.base import geos_geom_from_py class GeometryCollection(BaseMultipartGeometry): """A heterogenous collection of geometries Attributes ---------- geoms : sequence A sequence of Shapely geometry instances """ def __init__(self, geoms=None): """ Parameters ---------- geoms : list A list of shapely geometry instances, which may be heterogenous. Example ------- Create a GeometryCollection with a Point and a LineString >>> p = Point(51, -1) >>> l = LineString([(52, -1), (49, 2)]) >>> gc = GeometryCollection([p, l]) """ BaseMultipartGeometry.__init__(self) if not geoms: pass else: self._geom, self._ndim = geos_geometrycollection_from_py(geoms) @property def __geo_interface__(self): geometries = [] for geom in self.geoms: geometries.append(geom.__geo_interface__) return dict(type='GeometryCollection', geometries=geometries) @property def geoms(self): if self.is_empty: return [] return HeterogeneousGeometrySequence(self) def geos_geometrycollection_from_py(ob): """Creates a GEOS GeometryCollection from a list of geometries""" L = len(ob) N = 2 subs = (c_void_p * L)() for l in range(L): assert(isinstance(ob[l], BaseGeometry)) if ob[l].has_z: N = 3 geom, n = geos_geom_from_py(ob[l]) subs[l] = geom return (lgeos.GEOSGeom_createCollection(7, subs, L), N) # Test runner def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() Shapely-1.5.13/shapely/geometry/geo.py000066400000000000000000000050561260610516500176420ustar00rootroot00000000000000""" Geometry factories based on the geo interface """ from .point import Point, asPoint from .linestring import LineString, asLineString from .polygon import Polygon, asPolygon from .multipoint import MultiPoint, asMultiPoint from .multilinestring import MultiLineString, asMultiLineString from .multipolygon import MultiPolygon, MultiPolygonAdapter def box(minx, miny, maxx, maxy, ccw=True): """Returns a rectangular polygon with configurable normal vector""" coords = [(maxx, miny), (maxx, maxy), (minx, maxy), (minx, miny)] if not ccw: coords = coords[::-1] return Polygon(coords) def shape(context): """Returns a new, independent geometry with coordinates *copied* from the context. """ if hasattr(context, "__geo_interface__"): ob = context.__geo_interface__ else: ob = context geom_type = ob.get("type").lower() if geom_type == "point": return Point(ob["coordinates"]) elif geom_type == "linestring": return LineString(ob["coordinates"]) elif geom_type == "polygon": return Polygon(ob["coordinates"][0], ob["coordinates"][1:]) elif geom_type == "multipoint": return MultiPoint(ob["coordinates"]) elif geom_type == "multilinestring": return MultiLineString(ob["coordinates"]) elif geom_type == "multipolygon": return MultiPolygon(ob["coordinates"], context_type='geojson') else: raise ValueError("Unknown geometry type: %s" % geom_type) def asShape(context): """Adapts the context to a geometry interface. The coordinates remain stored in the context. """ if hasattr(context, "__geo_interface__"): ob = context.__geo_interface__ else: ob = context try: geom_type = ob.get("type").lower() except AttributeError: raise ValueError("Context does not provide geo interface") if geom_type == "point": return asPoint(ob["coordinates"]) elif geom_type == "linestring": return asLineString(ob["coordinates"]) elif geom_type == "polygon": return asPolygon(ob["coordinates"][0], ob["coordinates"][1:]) elif geom_type == "multipoint": return asMultiPoint(ob["coordinates"]) elif geom_type == "multilinestring": return asMultiLineString(ob["coordinates"]) elif geom_type == "multipolygon": return MultiPolygonAdapter(ob["coordinates"], context_type='geojson') else: raise ValueError("Unknown geometry type: %s" % geom_type) def mapping(ob): """Returns a GeoJSON-like mapping""" return ob.__geo_interface__ Shapely-1.5.13/shapely/geometry/linestring.py000066400000000000000000000236371260610516500212530ustar00rootroot00000000000000"""Line strings and related utilities """ import sys if sys.version_info[0] < 3: range = xrange from ctypes import c_double, cast, POINTER from shapely.coords import required from shapely.geos import lgeos, TopologicalError from shapely.geometry.base import ( BaseGeometry, geom_factory, JOIN_STYLE, geos_geom_from_py ) from shapely.geometry.proxy import CachingGeometryProxy from shapely.geometry.point import Point __all__ = ['LineString', 'asLineString'] class LineString(BaseGeometry): """ A one-dimensional figure comprising one or more line segments A LineString has non-zero length and zero area. It may approximate a curve and need not be straight. Unlike a LinearRing, a LineString is not closed. """ def __init__(self, coordinates=None): """ Parameters ---------- coordinates : sequence A sequence of (x, y [,z]) numeric coordinate pairs or triples or an object that provides the numpy array interface, including another instance of LineString. Example ------- Create a line with two segments >>> a = LineString([[0, 0], [1, 0], [1, 1]]) >>> a.length 2.0 """ BaseGeometry.__init__(self) if coordinates is not None: self._set_coords(coordinates) @property def __geo_interface__(self): return { 'type': 'LineString', 'coordinates': tuple(self.coords) } def svg(self, scale_factor=1., stroke_color=None): """Returns SVG polyline element for the LineString geometry. Parameters ========== scale_factor : float Multiplication factor for the SVG stroke-width. Default is 1. stroke_color : str, optional Hex string for stroke color. Default is to use "#66cc99" if geometry is valid, and "#ff3333" if invalid. """ if self.is_empty: return '' if stroke_color is None: stroke_color = "#66cc99" if self.is_valid else "#ff3333" pnt_format = " ".join(["{0},{1}".format(*c) for c in self.coords]) return ( '' ).format(pnt_format, 2. * scale_factor, stroke_color) @property def ctypes(self): if not self._ctypes_data: self._ctypes_data = self.coords.ctypes return self._ctypes_data def array_interface(self): """Provide the Numpy array protocol.""" return self.coords.array_interface() __array_interface__ = property(array_interface) # Coordinate access def _set_coords(self, coordinates): self.empty() self._geom, self._ndim = geos_linestring_from_py(coordinates) coords = property(BaseGeometry._get_coords, _set_coords) @property def xy(self): """Separate arrays of X and Y coordinate values Example: >>> x, y = LineString(((0, 0), (1, 1))).xy >>> list(x) [0.0, 1.0] >>> list(y) [0.0, 1.0] """ return self.coords.xy def parallel_offset( self, distance, side='right', resolution=16, join_style=JOIN_STYLE.round, mitre_limit=5.0): """Returns a LineString or MultiLineString geometry at a distance from the object on its right or its left side. The side parameter may be 'left' or 'right' (default is 'right'). The resolution of the buffer around each vertex of the object increases by increasing the resolution keyword parameter or third positional parameter. If the distance parameter is negative the side is inverted, e.g. distance=5.0, side='left' is the same as distance=-5.0, side='right'. The join style is for outside corners between line segments. Accepted values are JOIN_STYLE.round (1), JOIN_STYLE.mitre (2), and JOIN_STYLE.bevel (3). The mitre ratio limit is used for very sharp corners. It is the ratio of the distance from the corner to the end of the mitred offset corner. When two line segments meet at a sharp angle, a miter join will extend far beyond the original geometry. To prevent unreasonable geometry, the mitre limit allows controlling the maximum length of the join corner. Corners with a ratio which exceed the limit will be beveled.""" if mitre_limit == 0.0: raise ValueError( 'Cannot compute offset from zero-length line segment') try: return geom_factory(self.impl['parallel_offset']( self, distance, resolution, join_style, mitre_limit, side)) except OSError: raise TopologicalError() class LineStringAdapter(CachingGeometryProxy, LineString): def __init__(self, context): self.context = context self.factory = geos_linestring_from_py @property def _ndim(self): try: # From array protocol array = self.context.__array_interface__ n = array['shape'][1] assert n == 2 or n == 3 return n except AttributeError: # Fall back on list return len(self.context[0]) @property def __array_interface__(self): """Provide the Numpy array protocol.""" try: return self.context.__array_interface__ except AttributeError: return self.array_interface() _get_coords = BaseGeometry._get_coords def _set_coords(self, ob): raise NotImplementedError( "Adapters can not modify their coordinate sources") coords = property(_get_coords) def asLineString(context): """Adapt an object the LineString interface""" return LineStringAdapter(context) def geos_linestring_from_py(ob, update_geom=None, update_ndim=0): # If a LineString is passed in, clone it and return # If a LinearRing is passed in, clone the coord seq and return a LineString if isinstance(ob, LineString): if type(ob) == LineString: return geos_geom_from_py(ob) else: return geos_geom_from_py(ob, lgeos.GEOSGeom_createLineString) # If numpy is present, we use numpy.require to ensure that we have a # C-continguous array that owns its data. View data will be copied. ob = required(ob) try: # From array protocol array = ob.__array_interface__ assert len(array['shape']) == 2 m = array['shape'][0] if m < 2: raise ValueError( "LineStrings must have at least 2 coordinate tuples") try: n = array['shape'][1] except IndexError: raise ValueError( "Input %s is the wrong shape for a LineString" % str(ob)) assert n == 2 or n == 3 # Make pointer to the coordinate array if isinstance(array['data'], tuple): # numpy tuple (addr, read-only) cp = cast(array['data'][0], POINTER(c_double)) else: cp = array['data'] # Create a coordinate sequence if update_geom is not None: cs = lgeos.GEOSGeom_getCoordSeq(update_geom) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has " "dimensions: %d" % update_ndim) else: cs = lgeos.GEOSCoordSeq_create(m, n) # add to coordinate sequence for i in range(m): dx = c_double(cp[n*i]) dy = c_double(cp[n*i+1]) dz = None if n == 3: try: dz = c_double(cp[n*i+2]) except IndexError: raise ValueError("Inconsistent coordinate dimensionality") # Because of a bug in the GEOS C API, # always set X before Y lgeos.GEOSCoordSeq_setX(cs, i, dx) lgeos.GEOSCoordSeq_setY(cs, i, dy) if n == 3: lgeos.GEOSCoordSeq_setZ(cs, i, dz) except AttributeError: # Fall back on list try: m = len(ob) except TypeError: # Iterators, e.g. Python 3 zip ob = list(ob) m = len(ob) if m < 2: raise ValueError( "LineStrings must have at least 2 coordinate tuples") def _coords(o): if isinstance(o, Point): return o.coords[0] else: return o try: n = len(_coords(ob[0])) except TypeError: raise ValueError( "Input %s is the wrong shape for a LineString" % str(ob)) assert n == 2 or n == 3 # Create a coordinate sequence if update_geom is not None: cs = lgeos.GEOSGeom_getCoordSeq(update_geom) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has " "dimensions: %d" % update_ndim) else: cs = lgeos.GEOSCoordSeq_create(m, n) # add to coordinate sequence for i in range(m): coords = _coords(ob[i]) # Because of a bug in the GEOS C API, # always set X before Y lgeos.GEOSCoordSeq_setX(cs, i, coords[0]) lgeos.GEOSCoordSeq_setY(cs, i, coords[1]) if n == 3: try: lgeos.GEOSCoordSeq_setZ(cs, i, coords[2]) except IndexError: raise ValueError("Inconsistent coordinate dimensionality") if update_geom is not None: return None else: return lgeos.GEOSGeom_createLineString(cs), n def update_linestring_from_py(geom, ob): geos_linestring_from_py(ob, geom._geom, geom._ndim) # Test runner def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() Shapely-1.5.13/shapely/geometry/multilinestring.py000066400000000000000000000076671260610516500223330ustar00rootroot00000000000000"""Collections of linestrings and related utilities """ import sys if sys.version_info[0] < 3: range = xrange from ctypes import c_double, c_void_p, cast, POINTER from shapely.geos import lgeos from shapely.geometry.base import BaseMultipartGeometry, geos_geom_from_py from shapely.geometry import linestring from shapely.geometry.proxy import CachingGeometryProxy __all__ = ['MultiLineString', 'asMultiLineString'] class MultiLineString(BaseMultipartGeometry): """ A collection of one or more line strings A MultiLineString has non-zero length and zero area. Attributes ---------- geoms : sequence A sequence of LineStrings """ def __init__(self, lines=None): """ Parameters ---------- lines : sequence A sequence of line-like coordinate sequences or objects that provide the numpy array interface, including instances of LineString. Example ------- Construct a collection containing one line string. >>> lines = MultiLineString( [[[0.0, 0.0], [1.0, 2.0]]] ) """ super(MultiLineString, self).__init__() if not lines: # allow creation of empty multilinestrings, to support unpickling pass else: self._geom, self._ndim = geos_multilinestring_from_py(lines) def shape_factory(self, *args): return linestring.LineString(*args) @property def __geo_interface__(self): return { 'type': 'MultiLineString', 'coordinates': tuple(tuple(c for c in g.coords) for g in self.geoms) } def svg(self, scale_factor=1., stroke_color=None): """Returns a group of SVG polyline elements for the LineString geometry. Parameters ========== scale_factor : float Multiplication factor for the SVG stroke-width. Default is 1. stroke_color : str, optional Hex string for stroke color. Default is to use "#66cc99" if geometry is valid, and "#ff3333" if invalid. """ if self.is_empty: return '' if stroke_color is None: stroke_color = "#66cc99" if self.is_valid else "#ff3333" return '' + \ ''.join(p.svg(scale_factor, stroke_color) for p in self) + \ '' class MultiLineStringAdapter(CachingGeometryProxy, MultiLineString): context = None _other_owned = False def __init__(self, context): self.context = context self.factory = geos_multilinestring_from_py @property def _ndim(self): try: # From array protocol array = self.context[0].__array_interface__ n = array['shape'][1] assert n == 2 or n == 3 return n except AttributeError: # Fall back on list return len(self.context[0][0]) def asMultiLineString(context): """Adapts a sequence of objects to the MultiLineString interface""" return MultiLineStringAdapter(context) def geos_multilinestring_from_py(ob): # ob must be either a MultiLineString, a sequence, or # array of sequences or arrays if isinstance(ob, MultiLineString): return geos_geom_from_py(ob) obs = getattr(ob, 'geoms', ob) L = len(obs) assert L >= 1 exemplar = obs[0] try: N = len(exemplar[0]) except TypeError: N = exemplar._ndim if N not in (2, 3): raise ValueError("Invalid coordinate dimensionality") # Array of pointers to point geometries subs = (c_void_p * L)() # add to coordinate sequence for l in range(L): geom, ndims = linestring.geos_linestring_from_py(obs[l]) subs[l] = cast(geom, c_void_p) return (lgeos.GEOSGeom_createCollection(5, subs, L), N) # Test runner def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() Shapely-1.5.13/shapely/geometry/multipoint.py000066400000000000000000000137471260610516500213020ustar00rootroot00000000000000"""Collections of points and related utilities """ import sys if sys.version_info[0] < 3: range = xrange from ctypes import byref, c_double, c_void_p, cast, POINTER from ctypes import ArgumentError from shapely.coords import required from shapely.geos import lgeos from shapely.geometry.base import ( BaseMultipartGeometry, exceptNull, geos_geom_from_py ) from shapely.geometry import point from shapely.geometry.proxy import CachingGeometryProxy __all__ = ['MultiPoint', 'asMultiPoint'] class MultiPoint(BaseMultipartGeometry): """A collection of one or more points A MultiPoint has zero area and zero length. Attributes ---------- geoms : sequence A sequence of Points """ def __init__(self, points=None): """ Parameters ---------- points : sequence A sequence of (x, y [,z]) numeric coordinate pairs or triples or a sequence of objects that implement the numpy array interface, including instaces of Point. Example ------- Construct a 2 point collection >>> ob = MultiPoint([[0.0, 0.0], [1.0, 2.0]]) >>> len(ob.geoms) 2 >>> type(ob.geoms[0]) == Point True """ super(MultiPoint, self).__init__() if points is None or len(points) == 0: # allow creation of empty multipoints, to support unpickling pass else: self._geom, self._ndim = geos_multipoint_from_py(points) def shape_factory(self, *args): return point.Point(*args) @property def __geo_interface__(self): return { 'type': 'MultiPoint', 'coordinates': tuple([g.coords[0] for g in self.geoms]) } def svg(self, scale_factor=1., fill_color=None): """Returns a group of SVG circle elements for the MultiPoint geometry. Parameters ========== scale_factor : float Multiplication factor for the SVG circle diameters. Default is 1. fill_color : str, optional Hex string for fill color. Default is to use "#66cc99" if geometry is valid, and "#ff3333" if invalid. """ if self.is_empty: return '' if fill_color is None: fill_color = "#66cc99" if self.is_valid else "#ff3333" return '' + \ ''.join(p.svg(scale_factor, fill_color) for p in self) + \ '' @property @exceptNull def ctypes(self): if not self._ctypes_data: temp = c_double() n = self._ndim m = len(self.geoms) array_type = c_double * (m * n) data = array_type() for i in range(m): g = self.geoms[i]._geom cs = lgeos.GEOSGeom_getCoordSeq(g) lgeos.GEOSCoordSeq_getX(cs, 0, byref(temp)) data[n*i] = temp.value lgeos.GEOSCoordSeq_getY(cs, 0, byref(temp)) data[n*i+1] = temp.value if n == 3: # TODO: use hasz lgeos.GEOSCoordSeq_getZ(cs, 0, byref(temp)) data[n*i+2] = temp.value self._ctypes_data = data return self._ctypes_data @exceptNull def array_interface(self): """Provide the Numpy array protocol.""" ai = self.array_interface_base ai.update({'shape': (len(self.geoms), self._ndim)}) return ai __array_interface__ = property(array_interface) class MultiPointAdapter(CachingGeometryProxy, MultiPoint): context = None _other_owned = False def __init__(self, context): self.context = context self.factory = geos_multipoint_from_py @property def _ndim(self): try: # From array protocol array = self.context.__array_interface__ n = array['shape'][1] assert n == 2 or n == 3 return n except AttributeError: # Fall back on list return len(self.context[0]) @property def __array_interface__(self): """Provide the Numpy array protocol.""" try: return self.context.__array_interface__ except AttributeError: return self.array_interface() def asMultiPoint(context): """Adapt a sequence of objects to the MultiPoint interface""" return MultiPointAdapter(context) def geos_multipoint_from_py(ob): if isinstance(ob, MultiPoint): return geos_geom_from_py(ob) # If numpy is present, we use numpy.require to ensure that we have a # C-continguous array that owns its data. View data will be copied. ob = required(ob) try: # From array protocol array = ob.__array_interface__ assert len(array['shape']) == 2 m = array['shape'][0] n = array['shape'][1] assert m >= 1 assert n == 2 or n == 3 # Make pointer to the coordinate array if isinstance(array['data'], tuple): # numpy tuple (addr, read-only) cp = cast(array['data'][0], POINTER(c_double)) else: cp = array['data'] # Array of pointers to sub-geometries subs = (c_void_p * m)() for i in range(m): geom, ndims = point.geos_point_from_py(cp[n*i:n*i+2]) subs[i] = cast(geom, c_void_p) except AttributeError: # Fall back on list m = len(ob) try: n = len(ob[0]) except TypeError: n = ob[0]._ndim assert n == 2 or n == 3 # Array of pointers to point geometries subs = (c_void_p * m)() # add to coordinate sequence for i in range(m): coords = ob[i] geom, ndims = point.geos_point_from_py(coords) subs[i] = cast(geom, c_void_p) return lgeos.GEOSGeom_createCollection(4, subs, m), n # Test runner def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() Shapely-1.5.13/shapely/geometry/multipolygon.py000066400000000000000000000124131260610516500216250ustar00rootroot00000000000000"""Collections of polygons and related utilities """ import sys if sys.version_info[0] < 3: range = xrange from ctypes import c_void_p, cast from shapely.geos import lgeos from shapely.geometry.base import BaseMultipartGeometry, geos_geom_from_py from shapely.geometry import polygon from shapely.geometry.proxy import CachingGeometryProxy __all__ = ['MultiPolygon', 'asMultiPolygon'] class MultiPolygon(BaseMultipartGeometry): """A collection of one or more polygons If component polygons overlap the collection is `invalid` and some operations on it may fail. Attributes ---------- geoms : sequence A sequence of `Polygon` instances """ def __init__(self, polygons=None, context_type='polygons'): """ Parameters ---------- polygons : sequence A sequence of (shell, holes) tuples where shell is the sequence representation of a linear ring (see linearring.py) and holes is a sequence of such linear rings Example ------- Construct a collection from a sequence of coordinate tuples >>> ob = MultiPolygon( [ ... ( ... ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)), ... [((0.1,0.1), (0.1,0.2), (0.2,0.2), (0.2,0.1))] ... ) ... ] ) >>> len(ob.geoms) 1 >>> type(ob.geoms[0]) == Polygon True """ super(MultiPolygon, self).__init__() if not polygons: # allow creation of empty multipolygons, to support unpickling pass elif context_type == 'polygons': self._geom, self._ndim = geos_multipolygon_from_polygons(polygons) elif context_type == 'geojson': self._geom, self._ndim = geos_multipolygon_from_py(polygons) def shape_factory(self, *args): return polygon.Polygon(*args) @property def __geo_interface__(self): allcoords = [] for geom in self.geoms: coords = [] coords.append(tuple(geom.exterior.coords)) for hole in geom.interiors: coords.append(tuple(hole.coords)) allcoords.append(tuple(coords)) return { 'type': 'MultiPolygon', 'coordinates': allcoords } def svg(self, scale_factor=1., fill_color=None): """Returns group of SVG path elements for the MultiPolygon geometry. Parameters ========== scale_factor : float Multiplication factor for the SVG stroke-width. Default is 1. fill_color : str, optional Hex string for fill color. Default is to use "#66cc99" if geometry is valid, and "#ff3333" if invalid. """ if self.is_empty: return '' if fill_color is None: fill_color = "#66cc99" if self.is_valid else "#ff3333" return '' + \ ''.join(p.svg(scale_factor, fill_color) for p in self) + \ '' class MultiPolygonAdapter(CachingGeometryProxy, MultiPolygon): context = None _other_owned = False def __init__(self, context, context_type='polygons'): self.context = context if context_type == 'geojson': self.factory = geos_multipolygon_from_py elif context_type == 'polygons': self.factory = geos_multipolygon_from_polygons @property def _ndim(self): try: # From array protocol array = self.context[0][0].__array_interface__ n = array['shape'][1] assert n == 2 or n == 3 return n except AttributeError: # Fall back on list return len(self.context[0][0][0]) def asMultiPolygon(context): """Adapts a sequence of objects to the MultiPolygon interface""" return MultiPolygonAdapter(context) def geos_multipolygon_from_py(ob): """ob must provide Python geo interface coordinates.""" L = len(ob) assert L >= 1 N = len(ob[0][0][0]) assert N == 2 or N == 3 subs = (c_void_p * L)() for l in range(L): geom, ndims = polygon.geos_polygon_from_py(ob[l][0], ob[l][1:]) subs[l] = cast(geom, c_void_p) return (lgeos.GEOSGeom_createCollection(6, subs, L), N) def geos_multipolygon_from_polygons(ob): """ ob must be either a MultiPolygon, sequence or array of sequences or arrays. """ if isinstance(ob, MultiPolygon): return geos_geom_from_py(ob) obs = getattr(ob, 'geoms', None) or ob L = len(obs) assert L >= 1 exemplar = obs[0] try: N = len(exemplar[0][0]) except TypeError: N = exemplar._ndim assert N == 2 or N == 3 subs = (c_void_p * L)() for l in range(L): shell = getattr(obs[l], 'exterior', None) if shell is None: shell = obs[l][0] holes = getattr(obs[l], 'interiors', None) if holes is None: holes = obs[l][1] geom, ndims = polygon.geos_polygon_from_py(shell, holes) subs[l] = cast(geom, c_void_p) return (lgeos.GEOSGeom_createCollection(6, subs, L), N) # Test runner def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() Shapely-1.5.13/shapely/geometry/point.py000066400000000000000000000162251260610516500202210ustar00rootroot00000000000000"""Points and related utilities """ from ctypes import c_double from ctypes import cast, POINTER from shapely.coords import required from shapely.geos import lgeos, DimensionError from shapely.geometry.base import BaseGeometry, geos_geom_from_py from shapely.geometry.proxy import CachingGeometryProxy __all__ = ['Point', 'asPoint'] class Point(BaseGeometry): """ A zero dimensional feature A point has zero length and zero area. Attributes ---------- x, y, z : float Coordinate values Example ------- >>> p = Point(1.0, -1.0) >>> print(p) POINT (1.0000000000000000 -1.0000000000000000) >>> p.y -1.0 >>> p.x 1.0 """ def __init__(self, *args): """ Parameters ---------- There are 2 cases: 1) 1 parameter: this must satisfy the numpy array protocol. 2) 2 or more parameters: x, y, z : float Easting, northing, and elevation. """ BaseGeometry.__init__(self) if len(args) > 0: self._set_coords(*args) # Coordinate getters and setters @property def x(self): """Return x coordinate.""" return self.coords[0][0] @property def y(self): """Return y coordinate.""" return self.coords[0][1] @property def z(self): """Return z coordinate.""" if self._ndim != 3: raise DimensionError("This point has no z coordinate.") return self.coords[0][2] @property def __geo_interface__(self): return { 'type': 'Point', 'coordinates': self.coords[0] } def svg(self, scale_factor=1., fill_color=None): """Returns SVG circle element for the Point geometry. Parameters ========== scale_factor : float Multiplication factor for the SVG circle diameter. Default is 1. fill_color : str, optional Hex string for fill color. Default is to use "#66cc99" if geometry is valid, and "#ff3333" if invalid. """ if self.is_empty: return '' if fill_color is None: fill_color = "#66cc99" if self.is_valid else "#ff3333" return ( '' ).format(self, 3. * scale_factor, 1. * scale_factor, fill_color) @property def ctypes(self): if not self._ctypes_data: array_type = c_double * self._ndim array = array_type() xy = self.coords[0] array[0] = xy[0] array[1] = xy[1] if self._ndim == 3: array[2] = xy[2] self._ctypes_data = array return self._ctypes_data def array_interface(self): """Provide the Numpy array protocol.""" ai = self.array_interface_base ai.update({'shape': (self._ndim,)}) return ai __array_interface__ = property(array_interface) @property def bounds(self): xy = self.coords[0] return (xy[0], xy[1], xy[0], xy[1]) # Coordinate access def _set_coords(self, *args): self.empty() if len(args) == 1: self._geom, self._ndim = geos_point_from_py(args[0]) else: self._geom, self._ndim = geos_point_from_py(tuple(args)) coords = property(BaseGeometry._get_coords, _set_coords) @property def xy(self): """Separate arrays of X and Y coordinate values Example: >>> x, y = Point(0, 0).xy >>> list(x) [0.0] >>> list(y) [0.0] """ return self.coords.xy class PointAdapter(CachingGeometryProxy, Point): _other_owned = False def __init__(self, context): self.context = context self.factory = geos_point_from_py @property def _ndim(self): try: # From array protocol array = self.context.__array_interface__ n = array['shape'][0] assert n == 2 or n == 3 return n except AttributeError: # Fall back on list return len(self.context) @property def __array_interface__(self): """Provide the Numpy array protocol.""" try: return self.context.__array_interface__ except AttributeError: return self.array_interface() _get_coords = BaseGeometry._get_coords def _set_coords(self, ob): raise NotImplementedError("Adapters can not modify their sources") coords = property(_get_coords) def asPoint(context): """Adapt an object to the Point interface""" return PointAdapter(context) def geos_point_from_py(ob, update_geom=None, update_ndim=0): """Create a GEOS geom from an object that is a Point, a coordinate sequence or that provides the array interface. Returns the GEOS geometry and the number of its dimensions. """ if isinstance(ob, Point): return geos_geom_from_py(ob) # If numpy is present, we use numpy.require to ensure that we have a # C-continguous array that owns its data. View data will be copied. ob = required(ob) try: # From array protocol array = ob.__array_interface__ assert len(array['shape']) == 1 n = array['shape'][0] assert n == 2 or n == 3 dz = None da = array['data'] if isinstance(da, tuple): cdata = da[0] # If we had numpy, we would do # from numpy.ctypeslib import as_ctypes # cp = as_ctypes(ob) - check that code? cp = cast(cdata, POINTER(c_double)) dx = c_double(cp[0]) dy = c_double(cp[1]) if n == 3: dz = c_double(cp[2]) else: dx, dy = da[0:2] if n == 3: dz = da[2] except AttributeError: # Fall back on the case of Python sequence data # Accept either (x, y) or [(x, y)] if not hasattr(ob, '__getitem__'): # Iterators, e.g. Python 3 zip ob = list(ob) if isinstance(ob[0], tuple): coords = ob[0] else: coords = ob n = len(coords) dx = c_double(coords[0]) dy = c_double(coords[1]) dz = None if n == 3: dz = c_double(coords[2]) if update_geom: cs = lgeos.GEOSGeom_getCoordSeq(update_geom) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: " "%d" % update_ndim) else: cs = lgeos.GEOSCoordSeq_create(1, n) # Because of a bug in the GEOS C API, always set X before Y lgeos.GEOSCoordSeq_setX(cs, 0, dx) lgeos.GEOSCoordSeq_setY(cs, 0, dy) if n == 3: lgeos.GEOSCoordSeq_setZ(cs, 0, dz) if update_geom: return None else: return lgeos.GEOSGeom_createPoint(cs), n def update_point_from_py(geom, ob): geos_point_from_py(ob, geom._geom, geom._ndim) # Test runner def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() Shapely-1.5.13/shapely/geometry/polygon.py000066400000000000000000000376441260610516500205670ustar00rootroot00000000000000"""Polygons and their linear ring components """ import sys if sys.version_info[0] < 3: range = xrange from ctypes import c_double, c_void_p, cast, POINTER from ctypes import ArgumentError import weakref from shapely.algorithms.cga import signed_area from shapely.coords import required from shapely.geos import lgeos from shapely.geometry.base import BaseGeometry, geos_geom_from_py from shapely.geometry.linestring import LineString, LineStringAdapter from shapely.geometry.proxy import PolygonProxy __all__ = ['Polygon', 'asPolygon', 'LinearRing', 'asLinearRing'] class LinearRing(LineString): """ A closed one-dimensional feature comprising one or more line segments A LinearRing that crosses itself or touches itself at a single point is invalid and operations on it may fail. """ def __init__(self, coordinates=None): """ Parameters ---------- coordinates : sequence A sequence of (x, y [,z]) numeric coordinate pairs or triples Rings are implicitly closed. There is no need to specific a final coordinate pair identical to the first. Example ------- Construct a square ring. >>> ring = LinearRing( ((0, 0), (0, 1), (1 ,1 ), (1 , 0)) ) >>> ring.is_closed True >>> ring.length 4.0 """ BaseGeometry.__init__(self) if coordinates is not None: self._set_coords(coordinates) @property def __geo_interface__(self): return { 'type': 'LinearRing', 'coordinates': tuple(self.coords) } # Coordinate access _get_coords = BaseGeometry._get_coords def _set_coords(self, coordinates): self.empty() self._geom, self._ndim = geos_linearring_from_py(coordinates) coords = property(_get_coords, _set_coords) @property def is_ccw(self): """True is the ring is oriented counter clock-wise""" return bool(self.impl['is_ccw'](self)) @property def is_simple(self): """True if the geometry is simple, meaning that any self-intersections are only at boundary points, else False""" return LineString(self).is_simple class LinearRingAdapter(LineStringAdapter): __p__ = None def __init__(self, context): self.context = context self.factory = geos_linearring_from_py @property def __geo_interface__(self): return { 'type': 'LinearRing', 'coordinates': tuple(self.coords) } coords = property(BaseGeometry._get_coords) def asLinearRing(context): """Adapt an object to the LinearRing interface""" return LinearRingAdapter(context) class InteriorRingSequence(object): _factory = None _geom = None __p__ = None _ndim = None _index = 0 _length = 0 __rings__ = None _gtag = None def __init__(self, parent): self.__p__ = parent self._geom = parent._geom self._ndim = parent._ndim def __iter__(self): self._index = 0 self._length = self.__len__() return self def __next__(self): if self._index < self._length: ring = self._get_ring(self._index) self._index += 1 return ring else: raise StopIteration if sys.version_info[0] < 3: next = __next__ def __len__(self): return lgeos.GEOSGetNumInteriorRings(self._geom) def __getitem__(self, key): m = self.__len__() if isinstance(key, int): if key + m < 0 or key >= m: raise IndexError("index out of range") if key < 0: i = m + key else: i = key return self._get_ring(i) elif isinstance(key, slice): res = [] start, stop, stride = key.indices(m) for i in range(start, stop, stride): res.append(self._get_ring(i)) return res else: raise TypeError("key must be an index or slice") @property def _longest(self): max = 0 for g in iter(self): l = len(g.coords) if l > max: max = l def gtag(self): return hash(repr(self.__p__)) def _get_ring(self, i): gtag = self.gtag() if gtag != self._gtag: self.__rings__ = {} if i not in self.__rings__: g = lgeos.GEOSGetInteriorRingN(self._geom, i) ring = LinearRing() ring._geom = g ring.__p__ = self ring._other_owned = True ring._ndim = self._ndim self.__rings__[i] = weakref.ref(ring) return self.__rings__[i]() class Polygon(BaseGeometry): """ A two-dimensional figure bounded by a linear ring A polygon has a non-zero area. It may have one or more negative-space "holes" which are also bounded by linear rings. If any rings cross each other, the feature is invalid and operations on it may fail. Attributes ---------- exterior : LinearRing The ring which bounds the positive space of the polygon. interiors : sequence A sequence of rings which bound all existing holes. """ _exterior = None _interiors = [] _ndim = 2 def __init__(self, shell=None, holes=None): """ Parameters ---------- shell : sequence A sequence of (x, y [,z]) numeric coordinate pairs or triples holes : sequence A sequence of objects which satisfy the same requirements as the shell parameters above Example ------- Create a square polygon with no holes >>> coords = ((0., 0.), (0., 1.), (1., 1.), (1., 0.), (0., 0.)) >>> polygon = Polygon(coords) >>> polygon.area 1.0 """ BaseGeometry.__init__(self) if shell is not None: self._geom, self._ndim = geos_polygon_from_py(shell, holes) @property def exterior(self): if self.is_empty: return None elif self._exterior is None or self._exterior() is None: g = lgeos.GEOSGetExteriorRing(self._geom) ring = LinearRing() ring._geom = g ring.__p__ = self ring._other_owned = True ring._ndim = self._ndim self._exterior = weakref.ref(ring) return self._exterior() @property def interiors(self): if self.is_empty: return [] return InteriorRingSequence(self) def __eq__(self, other): if not isinstance(other, Polygon): return False my_coords = [ tuple(self.exterior.coords), [tuple(interior.coords) for interior in self.interiors] ] other_coords = [ tuple(other.exterior.coords), [tuple(interior.coords) for interior in other.interiors] ] return my_coords == other_coords def __ne__(self, other): return not self.__eq__(other) __hash__ = None @property def ctypes(self): if not self._ctypes_data: self._ctypes_data = self.exterior.ctypes return self._ctypes_data @property def __array_interface__(self): raise NotImplementedError( "A polygon does not itself provide the array interface. Its rings do.") def _get_coords(self): raise NotImplementedError( "Component rings have coordinate sequences, but the polygon does not") def _set_coords(self, ob): raise NotImplementedError( "Component rings have coordinate sequences, but the polygon does not") @property def coords(self): raise NotImplementedError( "Component rings have coordinate sequences, but the polygon does not") @property def __geo_interface__(self): coords = [tuple(self.exterior.coords)] for hole in self.interiors: coords.append(tuple(hole.coords)) return { 'type': 'Polygon', 'coordinates': tuple(coords) } def svg(self, scale_factor=1., fill_color=None): """Returns SVG path element for the Polygon geometry. Parameters ========== scale_factor : float Multiplication factor for the SVG stroke-width. Default is 1. fill_color : str, optional Hex string for fill color. Default is to use "#66cc99" if geometry is valid, and "#ff3333" if invalid. """ if self.is_empty: return '' if fill_color is None: fill_color = "#66cc99" if self.is_valid else "#ff3333" exterior_coords = [ ["{0},{1}".format(*c) for c in self.exterior.coords]] interior_coords = [ ["{0},{1}".format(*c) for c in interior.coords] for interior in self.interiors] path = " ".join([ "M {0} L {1} z".format(coords[0], " L ".join(coords[1:])) for coords in exterior_coords + interior_coords]) return ( '' ).format(2. * scale_factor, path, fill_color) class PolygonAdapter(PolygonProxy, Polygon): def __init__(self, shell, holes=None): self.shell = shell self.holes = holes self.context = (shell, holes) self.factory = geos_polygon_from_py @property def _ndim(self): try: # From array protocol array = self.shell.__array_interface__ n = array['shape'][1] assert n == 2 or n == 3 return n except AttributeError: # Fall back on list return len(self.shell[0]) def asPolygon(shell, holes=None): """Adapt objects to the Polygon interface""" return PolygonAdapter(shell, holes) def orient(polygon, sign=1.0): s = float(sign) rings = [] ring = polygon.exterior if signed_area(ring)/s >= 0.0: rings.append(ring) else: rings.append(list(ring.coords)[::-1]) for ring in polygon.interiors: if signed_area(ring)/s <= 0.0: rings.append(ring) else: rings.append(list(ring.coords)[::-1]) return Polygon(rings[0], rings[1:]) def geos_linearring_from_py(ob, update_geom=None, update_ndim=0): # If a LinearRing is passed in, clone it and return # If a LineString is passed in, clone the coord seq and return a LinearRing if isinstance(ob, LineString): if type(ob) == LinearRing: return geos_geom_from_py(ob) else: if ob.is_closed and len(ob.coords) >= 4: return geos_geom_from_py(ob, lgeos.GEOSGeom_createLinearRing) # If numpy is present, we use numpy.require to ensure that we have a # C-continguous array that owns its data. View data will be copied. ob = required(ob) try: # From array protocol array = ob.__array_interface__ assert len(array['shape']) == 2 m = array['shape'][0] n = array['shape'][1] if m < 3: raise ValueError( "A LinearRing must have at least 3 coordinate tuples") assert n == 2 or n == 3 # Make pointer to the coordinate array if isinstance(array['data'], tuple): # numpy tuple (addr, read-only) cp = cast(array['data'][0], POINTER(c_double)) else: cp = array['data'] # Add closing coordinates to sequence? if cp[0] != cp[m*n-n] or cp[1] != cp[m*n-n+1]: M = m + 1 else: M = m # Create a coordinate sequence if update_geom is not None: cs = lgeos.GEOSGeom_getCoordSeq(update_geom) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = lgeos.GEOSCoordSeq_create(M, n) # add to coordinate sequence for i in range(m): # Because of a bug in the GEOS C API, # always set X before Y lgeos.GEOSCoordSeq_setX(cs, i, cp[n*i]) lgeos.GEOSCoordSeq_setY(cs, i, cp[n*i+1]) if n == 3: lgeos.GEOSCoordSeq_setZ(cs, i, cp[n*i+2]) # Add closing coordinates to sequence? if M > m: # Because of a bug in the GEOS C API, # always set X before Y lgeos.GEOSCoordSeq_setX(cs, M-1, cp[0]) lgeos.GEOSCoordSeq_setY(cs, M-1, cp[1]) if n == 3: lgeos.GEOSCoordSeq_setZ(cs, M-1, cp[2]) except AttributeError: # Fall back on list try: m = len(ob) except TypeError: # Iterators, e.g. Python 3 zip ob = list(ob) m = len(ob) n = len(ob[0]) if m < 3: raise ValueError( "A LinearRing must have at least 3 coordinate tuples") assert (n == 2 or n == 3) # Add closing coordinates if not provided if m == 3 or ob[0][0] != ob[-1][0] or ob[0][1] != ob[-1][1]: M = m + 1 else: M = m # Create a coordinate sequence if update_geom is not None: cs = lgeos.GEOSGeom_getCoordSeq(update_geom) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = lgeos.GEOSCoordSeq_create(M, n) # add to coordinate sequence for i in range(m): coords = ob[i] # Because of a bug in the GEOS C API, # always set X before Y lgeos.GEOSCoordSeq_setX(cs, i, coords[0]) lgeos.GEOSCoordSeq_setY(cs, i, coords[1]) if n == 3: try: lgeos.GEOSCoordSeq_setZ(cs, i, coords[2]) except IndexError: raise ValueError("Inconsistent coordinate dimensionality") # Add closing coordinates to sequence? if M > m: coords = ob[0] # Because of a bug in the GEOS C API, # always set X before Y lgeos.GEOSCoordSeq_setX(cs, M-1, coords[0]) lgeos.GEOSCoordSeq_setY(cs, M-1, coords[1]) if n == 3: lgeos.GEOSCoordSeq_setZ(cs, M-1, coords[2]) if update_geom is not None: return None else: return lgeos.GEOSGeom_createLinearRing(cs), n def update_linearring_from_py(geom, ob): geos_linearring_from_py(ob, geom._geom, geom._ndim) def geos_polygon_from_py(shell, holes=None): if isinstance(shell, Polygon): return geos_geom_from_py(shell) if shell is not None: geos_shell, ndim = geos_linearring_from_py(shell) if holes is not None and len(holes) > 0: ob = holes L = len(ob) exemplar = ob[0] try: N = len(exemplar[0]) except TypeError: N = exemplar._ndim if not L >= 1: raise ValueError("number of holes must be non zero") if not N in (2, 3): raise ValueError("insufficiant coordinate dimension") # Array of pointers to ring geometries geos_holes = (c_void_p * L)() # add to coordinate sequence for l in range(L): geom, ndim = geos_linearring_from_py(ob[l]) geos_holes[l] = cast(geom, c_void_p) else: geos_holes = POINTER(c_void_p)() L = 0 return ( lgeos.GEOSGeom_createPolygon( c_void_p(geos_shell), geos_holes, L ), ndim ) # Test runner def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() Shapely-1.5.13/shapely/geometry/proxy.py000066400000000000000000000024751260610516500202530ustar00rootroot00000000000000"""Proxy for coordinates stored outside Shapely geometries """ from shapely.geometry.base import deserialize_wkb, EMPTY from shapely.geos import lgeos class CachingGeometryProxy(object): context = None factory = None __geom__ = EMPTY _gtag = None def __init__(self, context): self.context = context @property def _is_empty(self): return self.__geom__ in [EMPTY, None] def empty(self, val=EMPTY): if not self._is_empty and self.__geom__: lgeos.GEOSGeom_destroy(self.__geom__) self.__geom__ = val @property def _geom(self): """Keeps the GEOS geometry in synch with the context.""" gtag = self.gtag() if gtag != self._gtag or self._is_empty: self.empty() self.__geom__, n = self.factory(self.context) self._gtag = gtag return self.__geom__ def gtag(self): return hash(repr(self.context)) class PolygonProxy(CachingGeometryProxy): @property def _geom(self): """Keeps the GEOS geometry in synch with the context.""" gtag = self.gtag() if gtag != self._gtag or self._is_empty: self.empty() self.__geom__, n = self.factory(self.context[0], self.context[1]) self._gtag = gtag return self.__geom__ Shapely-1.5.13/shapely/geos.py000066400000000000000000000732331260610516500161740ustar00rootroot00000000000000""" Proxies for libgeos, GEOS-specific exceptions, and utilities """ import os import re import sys import atexit import logging import threading from ctypes import CDLL, cdll, pointer, string_at, cast, POINTER, DEFAULT_MODE from ctypes import c_void_p, c_size_t, c_char_p, c_int, c_float from ctypes.util import find_library from .ctypes_declarations import prototype, EXCEPTION_HANDLER_FUNCTYPE from . import ftools # Add message handler to this module's logger LOG = logging.getLogger(__name__) ch = logging.StreamHandler() LOG.addHandler(ch) if 'all' in sys.warnoptions: # show GEOS messages in console with: python -W all LOG.setLevel(logging.DEBUG) # 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.warn("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.warn("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 {0} or load any of its variants {1}.".format( libname, fallbacks or [])) _lgeos = None if sys.platform.startswith('linux'): _lgeos = load_dll('geos_c', fallbacks=['libgeos_c.so.1', 'libgeos_c.so']) free = load_dll('c').free free.argtypes = [c_void_p] free.restype = None elif sys.platform == 'darwin': # First: have we already loaded GEOS through a Fiona or Rasterio? # If so, let's obtain a handle to it instead of loading this module's # copy to side step the mysterious issue described at # https://github.com/Toblerity/Shapely/issues/324. geos_mod = sys.modules.get('fiona') or sys.modules.get('rasterio') if geos_mod: dll_path = os.path.join( os.path.dirname(geos_mod.__file__), '.dylibs', 'libgeos_c.1.dylib') try: _lgeos = CDLL(dll_path, mode=(DEFAULT_MODE | 16)) except OSError: LOG.debug("GEOS DLL in fiona or rasterio .dylibs not found.") alt_paths = [ # The Framework build from Kyng Chaos "/Library/Frameworks/GEOS.framework/Versions/Current/GEOS", # macports '/opt/local/lib/libgeos_c.dylib', ] _lgeos = load_dll('geos_c', fallbacks=alt_paths, mode=(DEFAULT_MODE | 16)) if _lgeos: LOG.debug("Found %r already loaded, using it.", _lgeos) # If neither fiona nor rasterio have been imported, or if the block # above failed to assign _lgeos, we will locate and load the GEOS # DLL. if not _lgeos: # 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): _lgeos = CDLL(geos_whl_dylib) LOG.debug("Found GEOS DLL: %r, using it.", _lgeos) 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')] else: alt_paths = [ # The Framework build from Kyng Chaos "/Library/Frameworks/GEOS.framework/Versions/Current/GEOS", # macports '/opt/local/lib/libgeos_c.dylib', ] _lgeos = load_dll('geos_c', fallbacks=alt_paths) free = load_dll('c').free free.argtypes = [c_void_p] free.restype = None elif sys.platform == 'win32': try: egg_dlls = os.path.abspath(os.path.join(os.path.dirname(__file__), "DLLs")) 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 = CDLL("geos.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(): # extern const char GEOS_DLL *GEOSversion(); GEOSversion = _lgeos.GEOSversion GEOSversion.restype = c_char_p GEOSversion.argtypes = [] #define GEOS_CAPI_VERSION "@VERSION@-CAPI-@CAPI_VERSION@" 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] + 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] # Exceptions class ReadingError(Exception): pass class DimensionError(Exception): pass class TopologicalError(Exception): pass class PredicateError(Exception): pass # While this function can take any number of positional arguments when # called from Python and GEOS expects its error handler to accept any # number of arguments (like printf), I'm unable to get ctypes to make # a callback object from this function that will accept any number of # arguments. # # At the moment, functions in the GEOS C API only pass 0 or 1 arguments # to the error handler. We can deal with this, but when if that changes, # Shapely may break. def handler(level): def callback(fmt, *args): fmt = fmt.decode('ascii') conversions = re.findall(r'%.', fmt) log_vals = [] for spec, arg in zip(conversions, args): if spec == '%s' and arg is not None: log_vals.append(string_at(arg).decode('ascii')) else: LOG.error("An error occurred, but the format string " "'%s' could not be converted.", fmt) return getattr(LOG, level)(fmt, *log_vals) return callback error_handler = handler('error') notice_handler = handler('warning') 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 sys.version_info[0] >= 3: text = text.encode('ascii') geom = self._lgeos.GEOSWKTReader_read(self._reader, c_char_p(text)) if not geom: raise ReadingError("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 ReadingError("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 ReadingError("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, raising a TopologicalError if result is NULL""" if not result: raise TopologicalError("The operation '{0}' 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 = ftools.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 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 = ftools.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 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 if 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) Shapely-1.5.13/shapely/impl.py000066400000000000000000000126651260610516500162020ustar00rootroot00000000000000"""Implementation of the intermediary layer between Shapely and GEOS This is layer number 2 from the list below. 1) geometric objects: the Python OO API. 2) implementation map: an abstraction that permits different backends. 3) backend: callable objects that take Shapely geometric objects as arguments and, with GEOS as a backend, translate them to C data structures. 4) GEOS library: algorithms implemented in C++. Shapely 1.2 includes a GEOS backend and it is the default. """ from .ftools import wraps from shapely.algorithms import cga from shapely.coords import BoundsOp from shapely.geos import lgeos from shapely.linref import ProjectOp, InterpolateOp from shapely.predicates import BinaryPredicate, UnaryPredicate from shapely.topology import BinaryRealProperty, BinaryTopologicalOp from shapely.topology import UnaryRealProperty, UnaryTopologicalOp class ImplementationError( AttributeError, KeyError, NotImplementedError): """To be raised when the registered implementation does not support the requested method.""" def delegated(func): """A delegated method raises AttributeError in the absence of backend support.""" @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except KeyError: raise ImplementationError( "Method '%s' not provided by registered " "implementation '%s'" % (func.__name__, args[0].impl)) return wrapper # Map geometry methods to their GEOS delegates class BaseImpl(object): """Base class for registrable implementations.""" def __init__(self, values): self.map = dict(values) def update(self, values): self.map.update(values) def __getitem__(self, key): try: return self.map[key] except KeyError: raise ImplementationError( "Method '%s' not provided by registered " "implementation '%s'" % (key, self.map)) def __contains__(self, key): return key in self.map class GEOSImpl(BaseImpl): """GEOS implementation""" def __repr__(self): return '' % ( lgeos.geos_capi_version,) IMPL300 = { 'area': (UnaryRealProperty, 'area'), 'distance': (BinaryRealProperty, 'distance'), 'length': (UnaryRealProperty, 'length'), # 'boundary': (UnaryTopologicalOp, 'boundary'), 'bounds': (BoundsOp, None), 'centroid': (UnaryTopologicalOp, 'centroid'), 'representative_point': (UnaryTopologicalOp, 'representative_point'), 'envelope': (UnaryTopologicalOp, 'envelope'), 'convex_hull': (UnaryTopologicalOp, 'convex_hull'), 'buffer': (UnaryTopologicalOp, 'buffer'), # 'difference': (BinaryTopologicalOp, 'difference'), 'intersection': (BinaryTopologicalOp, 'intersection'), 'symmetric_difference': (BinaryTopologicalOp, 'symmetric_difference'), 'union': (BinaryTopologicalOp, 'union'), # 'has_z': (UnaryPredicate, 'has_z'), 'is_empty': (UnaryPredicate, 'is_empty'), 'is_ring': (UnaryPredicate, 'is_ring'), 'is_simple': (UnaryPredicate, 'is_simple'), 'is_valid': (UnaryPredicate, 'is_valid'), # 'relate': (BinaryPredicate, 'relate'), 'contains': (BinaryPredicate, 'contains'), 'crosses': (BinaryPredicate, 'crosses'), 'disjoint': (BinaryPredicate, 'disjoint'), 'equals': (BinaryPredicate, 'equals'), 'intersects': (BinaryPredicate, 'intersects'), 'overlaps': (BinaryPredicate, 'overlaps'), 'touches': (BinaryPredicate, 'touches'), 'within': (BinaryPredicate, 'within'), 'covers': (BinaryPredicate, 'covers'), 'equals_exact': (BinaryPredicate, 'equals_exact'), 'relate_pattern': (BinaryPredicate, 'relate_pattern'), # First pure Python implementation 'is_ccw': (cga.is_ccw_impl, 'is_ccw'), } IMPL310 = { 'simplify': (UnaryTopologicalOp, 'simplify'), 'topology_preserve_simplify': (UnaryTopologicalOp, 'topology_preserve_simplify'), 'prepared_disjoint': (BinaryPredicate, 'prepared_disjoint'), 'prepared_touches': (BinaryPredicate, 'prepared_touches'), 'prepared_crosses': (BinaryPredicate, 'prepared_crosses'), 'prepared_within': (BinaryPredicate, 'prepared_within'), 'prepared_overlaps': (BinaryPredicate, 'prepared_overlaps'), 'prepared_intersects': (BinaryPredicate, 'prepared_intersects'), 'prepared_contains': (BinaryPredicate, 'prepared_contains'), 'prepared_contains_properly': (BinaryPredicate, 'prepared_contains_properly'), 'prepared_covers': (BinaryPredicate, 'prepared_covers'), } IMPL311 = { } IMPL320 = { 'parallel_offset': (UnaryTopologicalOp, 'parallel_offset'), 'project_normalized': (ProjectOp, 'project_normalized'), 'project': (ProjectOp, 'project'), 'interpolate_normalized': (InterpolateOp, 'interpolate_normalized'), 'interpolate': (InterpolateOp, 'interpolate'), 'buffer_with_style': (UnaryTopologicalOp, 'buffer_with_style'), } IMPL330 = { 'is_closed': (UnaryPredicate, 'is_closed')} def impl_items(defs): return [(k, v[0](v[1])) for k, v in list(defs.items())] imp = GEOSImpl(dict(impl_items(IMPL300))) if lgeos.geos_version >= (3, 1, 0): imp.update(impl_items(IMPL310)) if lgeos.geos_version >= (3, 1, 1): imp.update(impl_items(IMPL311)) if lgeos.geos_version >= (3, 2, 0): imp.update(impl_items(IMPL320)) if lgeos.geos_version >= (3, 3, 0): imp.update(impl_items(IMPL330)) DefaultImplementation = imp Shapely-1.5.13/shapely/iterops.py000066400000000000000000000021551260610516500167170ustar00rootroot00000000000000""" Iterative forms of operations """ from shapely.geos import PredicateError from shapely.topology import Delegating class IterOp(Delegating): """A generating non-data descriptor. """ def __call__(self, context, iterator, value=True): if context._geom is None: raise ValueError("Null geometry supports no operations") for item in iterator: try: this_geom, ob = item except TypeError: this_geom = item ob = this_geom if not this_geom._geom: raise ValueError("Null geometry supports no operations") try: retval = self.fn(context._geom, this_geom._geom) except Exception as err: self._check_topology(err, context, this_geom) if bool(retval) == value: yield ob # utilities disjoint = IterOp('disjoint') touches = IterOp('touches') intersects = IterOp('intersects') crosses = IterOp('crosses') within = IterOp('within') contains = IterOp('contains') overlaps = IterOp('overlaps') equals = IterOp('equals') Shapely-1.5.13/shapely/linref.py000066400000000000000000000012451260610516500165100ustar00rootroot00000000000000"""Linear referencing """ from shapely.topology import Delegating class LinearRefBase(Delegating): def _validate_line(self, ob): super(LinearRefBase, self)._validate(ob) if not ob.geom_type in ['LinearRing', 'LineString', 'MultiLineString']: raise TypeError("Only linear types support this operation") class ProjectOp(LinearRefBase): def __call__(self, this, other): self._validate_line(this) self._validate(other) return self.fn(this._geom, other._geom) class InterpolateOp(LinearRefBase): def __call__(self, this, distance): self._validate_line(this) return self.fn(this._geom, distance) Shapely-1.5.13/shapely/ops.py000066400000000000000000000244471260610516500160430ustar00rootroot00000000000000"""Support for various GEOS geometry operations """ import sys if sys.version_info[0] < 3: from itertools import izip else: izip = zip from ctypes import byref, c_void_p, c_double from shapely.geos import lgeos from shapely.geometry.base import geom_factory, BaseGeometry from shapely.geometry import asShape, asLineString, asMultiLineString, Point __all__ = ['cascaded_union', 'linemerge', 'operator', 'polygonize', 'polygonize_full', 'transform', 'unary_union', 'triangulate'] class CollectionOperator(object): def shapeup(self, ob): if isinstance(ob, BaseGeometry): return ob else: try: return asShape(ob) except ValueError: return asLineString(ob) def polygonize(self, lines): """Creates polygons from a source of lines The source may be a MultiLineString, a sequence of LineString objects, or a sequence of objects than can be adapted to LineStrings. """ source = getattr(lines, 'geoms', None) or lines try: source = iter(source) except TypeError: source = [source] finally: obs = [self.shapeup(l) for l in source] geom_array_type = c_void_p * len(obs) geom_array = geom_array_type() for i, line in enumerate(obs): geom_array[i] = line._geom product = lgeos.GEOSPolygonize(byref(geom_array), len(obs)) collection = geom_factory(product) for g in collection.geoms: clone = lgeos.GEOSGeom_clone(g._geom) g = geom_factory(clone) g._other_owned = False yield g def polygonize_full(self, lines): """Creates polygons from a source of lines, returning the polygons and leftover geometries. The source may be a MultiLineString, a sequence of LineString objects, or a sequence of objects than can be adapted to LineStrings. Returns a tuple of objects: (polygons, dangles, cut edges, invalid ring lines). Each are a geometry collection. Dangles are edges which have one or both ends which are not incident on another edge endpoint. Cut edges are connected at both ends but do not form part of polygon. Invalid ring lines form rings which are invalid (bowties, etc). """ source = getattr(lines, 'geoms', None) or lines try: source = iter(source) except TypeError: source = [source] finally: obs = [self.shapeup(l) for l in source] L = len(obs) subs = (c_void_p * L)() for i, g in enumerate(obs): subs[i] = g._geom collection = lgeos.GEOSGeom_createCollection(5, subs, L) dangles = c_void_p() cuts = c_void_p() invalids = c_void_p() product = lgeos.GEOSPolygonize_full( collection, byref(dangles), byref(cuts), byref(invalids)) return ( geom_factory(product), geom_factory(dangles), geom_factory(cuts), geom_factory(invalids) ) def linemerge(self, lines): """Merges all connected lines from a source The source may be a MultiLineString, a sequence of LineString objects, or a sequence of objects than can be adapted to LineStrings. Returns a LineString or MultiLineString when lines are not contiguous. """ source = None if hasattr(lines, 'type') and lines.type == 'MultiLineString': source = lines elif hasattr(lines, '__iter__'): try: source = asMultiLineString([ls.coords for ls in lines]) except AttributeError: source = asMultiLineString(lines) if source is None: raise ValueError("Cannot linemerge %s" % lines) result = lgeos.GEOSLineMerge(source._geom) return geom_factory(result) def cascaded_union(self, geoms): """Returns the union of a sequence of geometries This is the most efficient method of dissolving many polygons. """ try: L = len(geoms) except TypeError: geoms = [geoms] L = 1 subs = (c_void_p * L)() for i, g in enumerate(geoms): subs[i] = g._geom collection = lgeos.GEOSGeom_createCollection(6, subs, L) return geom_factory(lgeos.methods['cascaded_union'](collection)) def unary_union(self, geoms): """Returns the union of a sequence of geometries This method replaces :meth:`cascaded_union` as the prefered method for dissolving many polygons. """ try: L = len(geoms) except TypeError: geoms = [geoms] L = 1 subs = (c_void_p * L)() for i, g in enumerate(geoms): subs[i] = g._geom collection = lgeos.GEOSGeom_createCollection(6, subs, L) return geom_factory(lgeos.methods['unary_union'](collection)) operator = CollectionOperator() polygonize = operator.polygonize polygonize_full = operator.polygonize_full linemerge = operator.linemerge cascaded_union = operator.cascaded_union unary_union = operator.unary_union def triangulate(geom, tolerance=0.0, edges=False): """Creates the Delaunay triangulation and returns a list of geometries The source may be any geometry type. All vertices of the geometry will be used as the points of the triangulation. From the GEOS documentation: tolerance is the snapping tolerance used to improve the robustness of the triangulation computation. A tolerance of 0.0 specifies that no snapping will take place. If edges is False, a list of Polygons (triangles) will be returned. Otherwise the list of LineString edges is returned. """ func = lgeos.methods['delaunay_triangulation'] gc = geom_factory(func(geom._geom, tolerance, int(edges))) return [g for g in gc.geoms] class ValidateOp(object): def __call__(self, this): return lgeos.GEOSisValidReason(this._geom) validate = ValidateOp() def transform(func, geom): """Applies `func` to all coordinates of `geom` and returns a new geometry of the same type from the transformed coordinates. `func` maps x, y, and optionally z to output xp, yp, zp. The input parameters may iterable types like lists or arrays or single values. The output shall be of the same type. Scalars in, scalars out. Lists in, lists out. For example, here is an identity function applicable to both types of input. def id_func(x, y, z=None): return tuple(filter(None, [x, y, z])) g2 = transform(id_func, g1) A partially applied transform function from pyproj satisfies the requirements for `func`. from functools import partial import pyproj project = partial( pyproj.transform, pyproj.Proj(init='epsg:4326'), pyproj.Proj(init='epsg:26913')) g2 = transform(project, g1) Lambda expressions such as the one in g2 = transform(lambda x, y, z=None: (x+1.0, y+1.0), g1) also satisfy the requirements for `func`. """ if geom.is_empty: return geom if geom.type in ('Point', 'LineString', 'LinearRing', 'Polygon'): # First we try to apply func to x, y, z sequences. When func is # optimized for sequences, this is the fastest, though zipping # the results up to go back into the geometry constructors adds # extra cost. try: if geom.type in ('Point', 'LineString', 'LinearRing'): return type(geom)(zip(*func(*izip(*geom.coords)))) elif geom.type == 'Polygon': shell = type(geom.exterior)( zip(*func(*izip(*geom.exterior.coords)))) holes = list(type(ring)(zip(*func(*izip(*ring.coords)))) for ring in geom.interiors) return type(geom)(shell, holes) # A func that assumes x, y, z are single values will likely raise a # TypeError, in which case we'll try again. except TypeError: if geom.type in ('Point', 'LineString', 'LinearRing'): return type(geom)([func(*c) for c in geom.coords]) elif geom.type == 'Polygon': shell = type(geom.exterior)( [func(*c) for c in geom.exterior.coords]) holes = list(type(ring)([func(*c) for c in ring.coords]) for ring in geom.interiors) return type(geom)(shell, holes) elif geom.type.startswith('Multi') or geom.type == 'GeometryCollection': return type(geom)([transform(func, part) for part in geom.geoms]) else: raise ValueError('Type %r not recognized' % geom.type) def nearest_points(g1, g2): """Returns the calculated nearest points in the input geometries The points are returned in the same order as the input geometries. """ seq = lgeos.methods['nearest_points'](g1._geom, g2._geom) if seq is None: if g1.is_empty: raise ValueError('The first input geometry is empty') else: raise ValueError('The second input geometry is empty') x1 = c_double() y1 = c_double() x2 = c_double() y2 = c_double() lgeos.GEOSCoordSeq_getX(seq, 0, byref(x1)) lgeos.GEOSCoordSeq_getY(seq, 0, byref(y1)) lgeos.GEOSCoordSeq_getX(seq, 1, byref(x2)) lgeos.GEOSCoordSeq_getY(seq, 1, byref(y2)) p1 = Point(x1.value, y1.value) p2 = Point(x2.value, y2.value) return (p1, p2) def snap(g1, g2, tolerance): """Snap one geometry to another with a given tolerance Vertices of the first geometry are snapped to vertices of the second geometry. The resulting snapped geometry is returned. The input geometries are not modified. Parameters ---------- g1 : geometry The first geometry g2 : geometry The second geometry tolerence : float The snapping tolerance Example ------- >>> square = Polygon([(1,1), (2, 1), (2, 2), (1, 2), (1, 1)]) >>> line = LineString([(0,0), (0.8, 0.8), (1.8, 0.95), (2.6, 0.5)]) >>> result = snap(line, square, 0.5) >>> result.wkt 'LINESTRING (0 0, 1 1, 2 1, 2.6 0.5)' """ return(geom_factory(lgeos.methods['snap'](g1._geom, g2._geom, tolerance))) Shapely-1.5.13/shapely/predicates.py000066400000000000000000000012041260610516500173470ustar00rootroot00000000000000""" Support for GEOS spatial predicates """ from shapely.geos import PredicateError, TopologicalError from shapely.topology import Delegating class BinaryPredicate(Delegating): def __call__(self, this, other, *args): self._validate(this) self._validate(other, stop_prepared=True) try: return self.fn(this._geom, other._geom, *args) except PredicateError as err: # Dig deeper into causes of errors. self._check_topology(err, this, other) class UnaryPredicate(Delegating): def __call__(self, this): self._validate(this) return self.fn(this._geom) Shapely-1.5.13/shapely/prepared.py000066400000000000000000000053201260610516500170310ustar00rootroot00000000000000""" Support for GEOS prepared geometry operations. """ from shapely.geos import lgeos from shapely.impl import DefaultImplementation, delegated class PreparedGeometry(object): """ A geometry prepared for efficient comparison to a set of other geometries. Example: >>> from shapely.geometry import Point, Polygon >>> triangle = Polygon(((0.0, 0.0), (1.0, 1.0), (1.0, -1.0))) >>> p = prep(triangle) >>> p.intersects(Point(0.5, 0.5)) True """ impl = DefaultImplementation def __init__(self, context): self.context = context self.__geom__ = lgeos.GEOSPrepare(self.context._geom) def __del__(self): if self.__geom__ is not None: try: lgeos.GEOSPreparedGeom_destroy(self.__geom__) except AttributeError: pass # lgeos might be empty on shutdown self.__geom__ = None self.context = None @property def _geom(self): return self.__geom__ @delegated def contains(self, other): """Returns True if the geometry contains the other, else False""" return bool(self.impl['prepared_contains'](self, other)) @delegated def contains_properly(self, other): """Returns True if the geometry properly contains the other, else False""" return bool(self.impl['prepared_contains_properly'](self, other)) @delegated def covers(self, other): """Returns True if the geometry covers the other, else False""" return bool(self.impl['prepared_covers'](self, other)) @delegated def crosses(self, other): """Returns True if the geometries cross, else False""" return bool(self.impl['prepared_crosses'](self, other)) @delegated def disjoint(self, other): """Returns True if geometries are disjoint, else False""" return bool(self.impl['prepared_disjoint'](self, other)) @delegated def intersects(self, other): """Returns True if geometries intersect, else False""" return bool(self.impl['prepared_intersects'](self, other)) @delegated def overlaps(self, other): """Returns True if geometries overlap, else False""" return bool(self.impl['prepared_overlaps'](self, other)) @delegated def touches(self, other): """Returns True if geometries touch, else False""" return bool(self.impl['prepared_touches'](self, other)) @delegated def within(self, other): """Returns True if geometry is within the other, else False""" return bool(self.impl['prepared_within'](self, other)) def prep(ob): """Creates and returns a prepared geometric object.""" return PreparedGeometry(ob) Shapely-1.5.13/shapely/speedups/000077500000000000000000000000001260610516500165055ustar00rootroot00000000000000Shapely-1.5.13/shapely/speedups/__init__.py000066400000000000000000000041041260610516500206150ustar00rootroot00000000000000import warnings from shapely.geometry import linestring, polygon from shapely import coords import shapely.affinity try: from shapely.speedups import _speedups available = True import_error_msg = None except ImportError: import sys available = False # TODO: This does not appear to do anything useful import_error_msg = sys.exc_info()[1] from ..ftools import wraps def method_wrapper(f): def wrapper(*args, **kwargs): return f(*args, **kwargs) return wraps(f)(wrapper) __all__ = ['available', 'enable', 'disable'] _orig = {} def enable(): if not available: warnings.warn("shapely.speedups not available", RuntimeWarning) return if _orig: return _orig['CoordinateSequence.ctypes'] = coords.CoordinateSequence.ctypes coords.CoordinateSequence.ctypes = property(_speedups.coordseq_ctypes) _orig['CoordinateSequence.__iter__'] = coords.CoordinateSequence.__iter__ coords.CoordinateSequence.__iter__ = method_wrapper(_speedups.coordseq_iter) _orig['geos_linestring_from_py'] = linestring.geos_linestring_from_py linestring.geos_linestring_from_py = _speedups.geos_linestring_from_py _orig['geos_linearring_from_py'] = polygon.geos_linearring_from_py polygon.geos_linearring_from_py = _speedups.geos_linearring_from_py _orig['affine_transform'] = shapely.affinity.affine_transform # copy docstring from original function def affine_transform(geom, matrix): return _speedups.affine_transform(geom, matrix) affine_transform.__doc__ = shapely.affinity.affine_transform.__doc__ shapely.affinity.affine_transform = affine_transform def disable(): if not _orig: return coords.CoordinateSequence.ctypes = _orig['CoordinateSequence.ctypes'] coords.CoordinateSequence.__iter__ = _orig['CoordinateSequence.__iter__'] linestring.geos_linestring_from_py = _orig['geos_linestring_from_py'] polygon.geos_linearring_from_py = _orig['geos_linearring_from_py'] shapely.affinity.affine_transform = _orig['affine_transform'] _orig.clear() Shapely-1.5.13/shapely/speedups/_speedups.pyx000066400000000000000000000412441260610516500212430ustar00rootroot00000000000000# geos_linestring_from_py was transcribed from shapely.geometry.linestring # geos_linearring_from_py was transcribed from shapely.geometry.polygon # coordseq_ctypes was transcribed from shapely.coords.CoordinateSequence.ctypes # # Copyright (c) 2007, Sean C. Gillies # Transcription to cython: Copyright (c) 2011, Oliver Tonnhofer import ctypes from shapely.coords import required from shapely.geos import lgeos from shapely.geometry import Point, LineString, LinearRing from shapely.geometry.base import geom_factory include "../_geos.pxi" from libc.stdint cimport uintptr_t cdef inline GEOSGeometry *cast_geom(uintptr_t geom_addr): return geom_addr cdef inline GEOSContextHandle_t cast_handle(uintptr_t handle_addr): return handle_addr cdef inline GEOSCoordSequence *cast_seq(uintptr_t handle_addr): return handle_addr def destroy(geom): GEOSGeom_destroy_r(cast_handle(lgeos.geos_handle), cast_geom(geom)) def geos_linestring_from_py(ob, update_geom=None, update_ndim=0): cdef double *cp cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef GEOSCoordSequence *cs cdef GEOSGeometry *g cdef double dx, dy, dz cdef int i, n, m, sm, sn # If a LineString is passed in, just clone it and return # If a LinearRing is passed in, clone the coord seq and return a LineString if isinstance(ob, LineString): g = cast_geom(ob._geom) if GEOSHasZ_r(handle, g): n = 3 else: n = 2 if type(ob) == LineString: return GEOSGeom_clone_r(handle, g), n else: cs = GEOSGeom_getCoordSeq_r(handle, g) cs = GEOSCoordSeq_clone_r(handle, cs) return GEOSGeom_createLineString_r(handle, cs), n # If numpy is present, we use numpy.require to ensure that we have a # C-continguous array that owns its data. View data will be copied. ob = required(ob) try: # From array protocol array = ob.__array_interface__ assert len(array['shape']) == 2 m = array['shape'][0] if m < 2: raise ValueError( "LineStrings must have at least 2 coordinate tuples") try: n = array['shape'][1] except IndexError: raise ValueError( "Input %s is the wrong shape for a LineString" % str(ob)) assert n == 2 or n == 3 # Make pointer to the coordinate array if isinstance(array['data'], ctypes.Array): cp = ctypes.addressof(array['data']) else: cp = array['data'][0] # Use strides to properly index into cp # ob[i, j] == cp[sm*i + sn*j] # Just to avoid a referenced before assignment warning. dx = 0 if array.get('strides', None): sm = array['strides'][0]/sizeof(dx) sn = array['strides'][1]/sizeof(dx) else: sm = n sn = 1 # Create a coordinate sequence if update_geom is not None: cs = GEOSGeom_getCoordSeq_r(handle, cast_geom(update_geom)) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = GEOSCoordSeq_create_r(handle, m, n) # add to coordinate sequence for i in xrange(m): dx = cp[sm*i] dy = cp[sm*i+sn] dz = 0 if n == 3: dz = cp[sm*i+2*sn] # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, i, dx) GEOSCoordSeq_setY_r(handle, cs, i, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, i, dz) except AttributeError: # Fall back on list try: m = len(ob) except TypeError: # Iterators, e.g. Python 3 zip ob = list(ob) m = len(ob) if m < 2: raise ValueError( "LineStrings must have at least 2 coordinate tuples") def _coords(o): if isinstance(o, Point): return o.coords[0] else: return o try: n = len(_coords(ob[0])) except TypeError: raise ValueError( "Input %s is the wrong shape for a LineString" % str(ob)) assert n == 2 or n == 3 # Create a coordinate sequence if update_geom is not None: cs = GEOSGeom_getCoordSeq_r(handle, cast_geom(update_geom)) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = GEOSCoordSeq_create_r(handle, m, n) # add to coordinate sequence for i in xrange(m): coords = _coords(ob[i]) dx = coords[0] dy = coords[1] dz = 0 if n == 3: if len(coords) != 3: raise ValueError("Inconsistent coordinate dimensionality") dz = coords[2] # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, i, dx) GEOSCoordSeq_setY_r(handle, cs, i, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, i, dz) if update_geom is not None: return None else: return GEOSGeom_createLineString_r(handle, cs), n def geos_linearring_from_py(ob, update_geom=None, update_ndim=0): cdef double *cp cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef GEOSGeometry *g cdef GEOSCoordSequence *cs cdef double dx, dy, dz cdef unsigned int m cdef int i, n, M, sm, sn # If a LinearRing is passed in, just clone it and return # If a LineString is passed in, clone the coord seq and return a LinearRing if isinstance(ob, LineString): g = cast_geom(ob._geom) if GEOSHasZ_r(handle, g): n = 3 else: n = 2 if type(ob) == LinearRing: return GEOSGeom_clone_r(handle, g), n else: cs = GEOSGeom_getCoordSeq_r(handle, g) GEOSCoordSeq_getSize_r(handle, cs, &m) if GEOSisClosed_r(handle, g) and m >= 4: cs = GEOSCoordSeq_clone_r(handle, cs) return GEOSGeom_createLinearRing_r(handle, cs), n # If numpy is present, we use numpy.require to ensure that we have a # C-continguous array that owns its data. View data will be copied. ob = required(ob) try: # From array protocol array = ob.__array_interface__ assert len(array['shape']) == 2 m = array['shape'][0] n = array['shape'][1] if m < 3: raise ValueError( "A LinearRing must have at least 3 coordinate tuples") assert n == 2 or n == 3 # Make pointer to the coordinate array if isinstance(array['data'], ctypes.Array): cp = ctypes.addressof(array['data']) else: cp = array['data'][0] # Use strides to properly index into cp # ob[i, j] == cp[sm*i + sn*j] dx = 0 # Just to avoid a referenced before assignment warning. if array.get('strides', None): sm = array['strides'][0]/sizeof(dx) sn = array['strides'][1]/sizeof(dx) else: sm = n sn = 1 # Add closing coordinates to sequence? # Check whether the first set of coordinates matches the last. # If not, we'll have to close the ring later if (cp[0] != cp[sm*(m-1)] or cp[sn] != cp[sm*(m-1)+sn] or (n == 3 and cp[2*sn] != cp[sm*(m-1)+2*sn])): M = m + 1 else: M = m # Create a coordinate sequence if update_geom is not None: cs = GEOSGeom_getCoordSeq_r(handle, cast_geom(update_geom)) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = GEOSCoordSeq_create_r(handle, M, n) # add to coordinate sequence for i in xrange(m): dx = cp[sm*i] dy = cp[sm*i+sn] dz = 0 if n == 3: dz = cp[sm*i+2*sn] # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, i, dx) GEOSCoordSeq_setY_r(handle, cs, i, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, i, dz) # Add closing coordinates to sequence? if M > m: dx = cp[0] dy = cp[sn] dz = 0 if n == 3: dz = cp[2*sn] # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, M-1, dx) GEOSCoordSeq_setY_r(handle, cs, M-1, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, M-1, dz) except AttributeError: # Fall back on list try: m = len(ob) except TypeError: # Iterators, e.g. Python 3 zip ob = list(ob) m = len(ob) n = len(ob[0]) if m < 3: raise ValueError( "A LinearRing must have at least 3 coordinate tuples") assert (n == 2 or n == 3) # Add closing coordinates if not provided if m == 3 or ob[0][0] != ob[-1][0] or ob[0][1] != ob[-1][1]: M = m + 1 else: M = m # Create a coordinate sequence if update_geom is not None: cs = GEOSGeom_getCoordSeq_r(handle, cast_geom(update_geom)) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = GEOSCoordSeq_create_r(handle, M, n) # add to coordinate sequence for i in xrange(m): coords = ob[i] dx = coords[0] dy = coords[1] dz = 0 if n == 3: dz = coords[2] # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, i, dx) GEOSCoordSeq_setY_r(handle, cs, i, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, i, dz) # Add closing coordinates to sequence? if M > m: coords = ob[0] dx = coords[0] dy = coords[1] dz = 0 if n == 3: dz = coords[2] # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, M-1, dx) GEOSCoordSeq_setY_r(handle, cs, M-1, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, M-1, dz) if update_geom is not None: return None else: return GEOSGeom_createLinearRing_r(handle, cs), n def coordseq_ctypes(self): cdef int i, n, m cdef double temp = 0 cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef GEOSCoordSequence *cs cdef double *data_p self._update() n = self._ndim m = self.__len__() array_type = ctypes.c_double * (m * n) data = array_type() cs = cast_seq(self._cseq) data_p = ctypes.addressof(data) for i in xrange(m): GEOSCoordSeq_getX_r(handle, cs, i, &temp) data_p[n*i] = temp GEOSCoordSeq_getY_r(handle, cs, i, &temp) data_p[n*i+1] = temp if n == 3: # TODO: use hasz GEOSCoordSeq_getZ_r(handle, cs, i, &temp) data_p[n*i+2] = temp return data def coordseq_iter(self): cdef int i cdef double dx cdef double dy cdef double dz cdef int has_z self._update() cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef GEOSCoordSequence *cs cs = cast_seq(self._cseq) has_z = self._ndim == 3 for i in range(self.__len__()): GEOSCoordSeq_getX_r(handle, cs, i, &dx) GEOSCoordSeq_getY_r(handle, cs, i, &dy) if has_z == 1: GEOSCoordSeq_getZ_r(handle, cs, i, &dz) yield (dx, dy, dz) else: yield (dx, dy) cdef GEOSCoordSequence* transform(GEOSCoordSequence* cs, int ndim, double a, double b, double c, double d, double e, double f, double g, double h, double i, double xoff, double yoff, double zoff): """Performs an affine transformation on a GEOSCoordSequence Returns the transformed coordinate sequence """ cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef unsigned int m cdef GEOSCoordSequence *cs_t cdef double x, y, z cdef double x_t, y_t, z_t # create a new coordinate sequence with the same size and dimensions GEOSCoordSeq_getSize_r(handle, cs, &m) cs_t = GEOSCoordSeq_create_r(handle, m, ndim) # perform the transform for n in range(0, m): GEOSCoordSeq_getX_r(handle, cs, n, &x) GEOSCoordSeq_getY_r(handle, cs, n, &y) x_t = a * x + b * y + xoff y_t = d * x + e * y + yoff GEOSCoordSeq_setX_r(handle, cs_t, n, x_t) GEOSCoordSeq_setY_r(handle, cs_t, n, y_t) if ndim == 3: for n in range(0, m): GEOSCoordSeq_getZ_r(handle, cs, n, &z) z_t = g * x + h * y + i * z + zoff GEOSCoordSeq_setZ_r(handle, cs_t, n, z_t) return cs_t cpdef affine_transform(geom, matrix): cdef double a, b, c, d, e, f, g, h, i, xoff, yoff, zoff if geom.is_empty: return geom if len(matrix) == 6: ndim = 2 a, b, d, e, xoff, yoff = matrix if geom.has_z: ndim = 3 i = 1.0 c = f = g = h = zoff = 0.0 matrix = a, b, c, d, e, f, g, h, i, xoff, yoff, zoff elif len(matrix) == 12: ndim = 3 a, b, c, d, e, f, g, h, i, xoff, yoff, zoff = matrix if not geom.has_z: ndim = 2 matrix = a, b, d, e, xoff, yoff else: raise ValueError("'matrix' expects either 6 or 12 coefficients") cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef GEOSCoordSequence *cs cdef GEOSCoordSequence *cs_t cdef GEOSGeometry *the_geom cdef GEOSGeometry *the_geom_t cdef int m, n cdef double x, y, z cdef double x_t, y_t, z_t # Process coordinates from each supported geometry type if geom.type in ('Point', 'LineString', 'LinearRing'): the_geom = cast_geom(geom._geom) cs = GEOSGeom_getCoordSeq_r(handle, the_geom) # perform the transformation cs_t = transform(cs, ndim, a, b, c, d, e, f, g, h, i, xoff, yoff, zoff) # create a new geometry from the coordinate sequence if geom.type == 'Point': the_geom_t = GEOSGeom_createPoint_r(handle, cs_t) elif geom.type == 'LineString': the_geom_t = GEOSGeom_createLineString_r(handle, cs_t) elif geom.type == 'LinearRing': the_geom_t = GEOSGeom_createLinearRing_r(handle, cs_t) # return the geometry as a Python object return geom_factory(the_geom_t) elif geom.type == 'Polygon': ring = geom.exterior shell = affine_transform(ring, matrix) holes = list(geom.interiors) for pos, ring in enumerate(holes): holes[pos] = affine_transform(ring, matrix) return type(geom)(shell, holes) elif geom.type.startswith('Multi') or geom.type == 'GeometryCollection': return type(geom)([affine_transform(part, matrix) for part in geom.geoms]) else: raise ValueError('Type %r not recognized' % geom.type) Shapely-1.5.13/shapely/strtree.py000066400000000000000000000034071260610516500167230ustar00rootroot00000000000000from shapely.geos import lgeos import ctypes class STRtree: """ STRtree is an R-tree that is created using the Sort-Tile-Recursive algorithm. STRtree takes a sequence of geometry objects as initialization parameter. After initialization the query method can be used to make a spatial query over those objects. >>> from shapely.geometry import Polygon >>> polys = [ Polygon(((0, 0), (1, 0), (1, 1))), Polygon(((0, 1), (0, 0), (1, 0))), Polygon(((100, 100), (101, 100), (101, 101))) ] >>> s = STRtree(polys) >>> query_geom = Polygon(((-1, -1), (2, 0), (2, 2), (-1, 2))) >>> result = s.query(query_geom) >>> polys[0] in result True >>> polys[1] in result True >>> polys[2] in result False >>> # Test empty tree >>> s = STRtree([]) >>> s.query(query_geom) [] >>> # Test tree with one object >>> s = STRtree([polys[0]]) >>> result = s.query(query_geom) >>> polys[0] in result True """ def __init__(self, geoms): self._n_geoms = len(geoms) # GEOS STRtree capacity has to be > 1 self._tree_handle = lgeos.GEOSSTRtree_create(max(2, len(geoms))) for geom in geoms: lgeos.GEOSSTRtree_insert(self._tree_handle, geom._geom, ctypes.py_object(geom)) def __del__(self): lgeos.GEOSSTRtree_destroy(self._tree_handle) def query(self, geom): if self._n_geoms == 0: return [] result = [] def callback(item, userdata): geom = ctypes.cast(item, ctypes.py_object).value result.append(geom) lgeos.GEOSSTRtree_query(self._tree_handle, geom._geom, lgeos.GEOSQueryCallback(callback), None) return result if __name__ == "__main__": import doctest doctest.testmod() Shapely-1.5.13/shapely/topology.py000066400000000000000000000043211260610516500171030ustar00rootroot00000000000000""" Intermediaries supporting GEOS topological operations These methods all take Shapely geometries and other Python objects and delegate to GEOS functions via ctypes. These methods return ctypes objects that should be recast by the caller. """ from ctypes import byref, c_double from shapely.geos import TopologicalError, lgeos class Validating(object): def _validate(self, ob, stop_prepared=False): if ob is None or ob._geom is None: raise ValueError("Null geometry supports no operations") if stop_prepared and not hasattr(ob, 'type'): raise ValueError("Prepared geometries cannot be operated on") class Delegating(Validating): def __init__(self, name): self.fn = lgeos.methods[name] def _check_topology(self, err, *geoms): """Raise TopologicalError if geoms are invalid. Else, raise original error. """ for geom in geoms: if not geom.is_valid: raise TopologicalError( "The operation '%s' could not be performed. " "Likely cause is invalidity of the geometry %s" % ( self.fn.__name__, repr(geom))) raise err class BinaryRealProperty(Delegating): def __call__(self, this, other): self._validate(this) self._validate(other, stop_prepared=True) d = c_double() retval = self.fn(this._geom, other._geom, byref(d)) return d.value class UnaryRealProperty(Delegating): def __call__(self, this): self._validate(this) d = c_double() retval = self.fn(this._geom, byref(d)) return d.value class BinaryTopologicalOp(Delegating): def __call__(self, this, other, *args): self._validate(this) self._validate(other, stop_prepared=True) product = self.fn(this._geom, other._geom, *args) if product is None: err = TopologicalError( "This operation could not be performed. Reason: unknown") self._check_topology(err, this, other) return product class UnaryTopologicalOp(Delegating): def __call__(self, this, *args): self._validate(this) return self.fn(this._geom, *args) Shapely-1.5.13/shapely/validation.py000066400000000000000000000002541260610516500173620ustar00rootroot00000000000000# TODO: allow for implementations using other than GEOS import sys from shapely.geos import lgeos def explain_validity(ob): return lgeos.GEOSisValidReason(ob._geom) Shapely-1.5.13/shapely/vectorized/000077500000000000000000000000001260610516500170335ustar00rootroot00000000000000Shapely-1.5.13/shapely/vectorized/__init__.py000066400000000000000000000001671260610516500211500ustar00rootroot00000000000000"""Provides multi-point element-wise operations such as ``contains``.""" from ._vectorized import (contains, touches) Shapely-1.5.13/shapely/vectorized/_vectorized.pyx000066400000000000000000000075371260610516500221260ustar00rootroot00000000000000import cython cimport cpython.array import numpy as np cimport numpy as np import shapely.prepared include "../_geos.pxi" # Define the predicate function, which returns True/False from functions such as GEOSPreparedContains_r # and GEOSPreparedWithin_r. ctypedef char (* predicate)(GEOSContextHandle_t, const GEOSPreparedGeometry *, const GEOSGeometry *) nogil def contains(geometry, x, y): """ Vectorized (element-wise) version of `contains` which checks whether multiple points are contained by a single geometry. Parameters ---------- geometry : PreparedGeometry or subclass of BaseGeometry The geometry which is to be checked to see whether each point is contained within. The geometry will be "prepared" if it is not already a PreparedGeometry instance. x : array The x coordinates of the points to check. y : array The y coordinates of the points to check. Returns ------- Mask of points contained by the given `geometry`. """ return _predicated_elementwise(geometry, x, y, GEOSPreparedContains_r) def touches(geometry, x, y): """ Vectorized (element-wise) version of `touches` which checks whether multiple points touch the exterior of a single geometry. Parameters ---------- geometry : PreparedGeometry or subclass of BaseGeometry The geometry which is to be checked to see whether each point is contained within. The geometry will be "prepared" if it is not already a PreparedGeometry instance. x : array The x coordinates of the points to check. y : array The y coordinates of the points to check. Returns ------- Mask of points which touch the exterior of the given `geometry`. """ return _predicated_elementwise(geometry, x, y, GEOSPreparedTouches_r) cdef _predicated_elementwise(geometry, x, y, predicate fn): """ Implements elementwise True/False testing, given a predicate function such as "contains" or "touches". x and y arrays can be of any type, order and dtype, and will be cast for appropriate calling to "_predicated_1d". """ # Support coordinate sequences and other array like objects. x, y = np.asanyarray(x), np.asanyarray(y) if x.shape != y.shape: raise ValueError('X and Y shapes must be equivalent.') x_1d = x.astype(np.float64, copy=False).ravel() y_1d = y.astype(np.float64, copy=False).ravel() result = _predicated_1d(geometry, x_1d, y_1d, fn) return result.reshape(x.shape) @cython.boundscheck(False) @cython.wraparound(False) cdef _predicated_1d(geometry, np.double_t[:] x, np.double_t[:] y, predicate fn): cdef Py_ssize_t idx cdef unsigned int n = x.size cdef np.ndarray[np.uint8_t, ndim=1, cast=True] result = np.empty(n, dtype=np.bool) cdef GEOSContextHandle_t geos_handle cdef GEOSPreparedGeometry *geos_prepared_geom cdef GEOSCoordSequence *cs cdef GEOSGeometry *point # Prepare the geometry if it hasn't already been prepared. if not isinstance(geometry, shapely.prepared.PreparedGeometry): geometry = shapely.prepared.prep(geometry) geos_h = get_geos_context_handle() geos_geom = geos_from_prepared(geometry) with nogil: for idx in xrange(n): # Construct a coordinate sequence with our x, y values. cs = GEOSCoordSeq_create_r(geos_h, 1, 2) GEOSCoordSeq_setX_r(geos_h, cs, 0, x[idx]) GEOSCoordSeq_setY_r(geos_h, cs, 0, y[idx]) # Construct a point with this sequence. p = GEOSGeom_createPoint_r(geos_h, cs) # Put the result of whether the point is "contained" by the # prepared geometry into the result array. result[idx] = fn(geos_h, geos_geom, p) GEOSGeom_destroy_r(geos_h, p) return result Shapely-1.5.13/shapely/wkb.py000066400000000000000000000017361260610516500160210ustar00rootroot00000000000000"""Load/dump geometries using the well-known binary (WKB) format """ from shapely import geos # Pickle-like convenience functions def loads(data, hex=False): """Load a geometry from a WKB byte string, or hex-encoded string if ``hex=True``. """ reader = geos.WKBReader(geos.lgeos) if hex: return reader.read_hex(data) else: return reader.read(data) def load(fp, hex=False): """Load a geometry from an open file.""" data = fp.read() return loads(data, hex=hex) def dumps(ob, hex=False, **kw): """Dump a WKB representation of a geometry to a byte string, or a hex-encoded string if ``hex=True``. See available keyword output settings in ``shapely.geos.WKBWriter``.""" writer = geos.WKBWriter(geos.lgeos, **kw) if hex: return writer.write_hex(ob) else: return writer.write(ob) def dump(ob, fp, hex=False, **kw): """Dump a geometry to an open file.""" fp.write(dumps(ob, hex=hex, **kw)) Shapely-1.5.13/shapely/wkt.py000066400000000000000000000012711260610516500160350ustar00rootroot00000000000000"""Load/dump geometries using the well-known text (WKT) format """ from shapely import geos # Pickle-like convenience functions def loads(data): """Load a geometry from a WKT string.""" return geos.WKTReader(geos.lgeos).read(data) def load(fp): """Load a geometry from an open file.""" data = fp.read() return loads(data) def dumps(ob, trim=False, **kw): """Dump a WKT representation of a geometry to a string. See available keyword output settings in ``shapely.geos.WKTWriter``. """ return geos.WKTWriter(geos.lgeos, trim=trim, **kw).write(ob) def dump(ob, fp, **settings): """Dump a geometry to an open file.""" fp.write(dumps(ob, **settings)) Shapely-1.5.13/tests/000077500000000000000000000000001260610516500143525ustar00rootroot00000000000000Shapely-1.5.13/tests/__init__.py000077500000000000000000000015011260610516500164630ustar00rootroot00000000000000import sys from shapely.geos import geos_version_string, lgeos, WKTWriter from shapely import speedups test_int_types = [int] try: import numpy numpy_version = numpy.version.version test_int_types.extend([int, numpy.int16, numpy.int32, numpy.int64]) except ImportError: numpy = False numpy_version = 'not available' # Show some diagnostic information; handy for Travis CI print('Python version: ' + sys.version.replace('\n', ' ')) print('GEOS version: ' + geos_version_string) print('Numpy version: ' + numpy_version) print('Cython speedups: ' + str(speedups.available)) if lgeos.geos_version >= (3, 3, 0): # Remove any WKT writer defaults to pass tests for all versions of GEOS WKTWriter.defaults = {} if sys.version_info[0:2] <= (2, 6): import unittest2 as unittest else: import unittest Shapely-1.5.13/tests/binascii_hex.txt000066400000000000000000000026161260610516500175450ustar00rootroot00000000000000Round-tripping geometries through hex-encoded binary ==================================================== Hex-encoded binary is the PostGIS geometry representation, and so this is a test of the ability to store Shapely geometries in PostGIS. Point ----- >>> from shapely.geometry import Point >>> point = Point(0.0, 0.0) >>> from binascii import a2b_hex, b2a_hex >>> x = b2a_hex(point.wkb) >>> from shapely import wkb >>> shape = wkb.loads(a2b_hex(x)) >>> shape # doctest: +ELLIPSIS LineString ---------- >>> from shapely.geometry import LineString >>> line = LineString(((0.0, 0.0), (1.0, 1.0))) >>> x = b2a_hex(line.wkb) >>> shape = wkb.loads(a2b_hex(x)) >>> shape # doctest: +ELLIPSIS Polygon ---------- >>> from shapely.geometry import Polygon >>> polygon = Polygon(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0))) >>> x = b2a_hex(polygon.wkb) >>> shape = wkb.loads(a2b_hex(x)) >>> shape # doctest: +ELLIPSIS Polygon with hole ----------------- >>> polygon = Polygon(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)), [((0.1,0.1), (0.1,0.2), (0.2,0.2), (0.2,0.1))]) >>> x = b2a_hex(polygon.wkb) >>> shape = wkb.loads(a2b_hex(x)) >>> shape # doctest: +ELLIPSIS Shapely-1.5.13/tests/conftest.py000066400000000000000000000010001260610516500165400ustar00rootroot00000000000000import sys import pytest def pytest_addoption(parser): parser.addoption("--with-speedups", action="store_true", default=False, help="Run tests with speedups.") def pytest_runtest_setup(item): if item.config.getoption("--with-speedups"): import shapely.speedups if not shapely.speedups.available: print("Speedups have been demanded but are unavailable") sys.exit(1) shapely.speedups.enable() print("Speedups enabled for %s." % item.name) Shapely-1.5.13/tests/rungrind.dist000066400000000000000000000002251260610516500170660ustar00rootroot00000000000000#!/bin/sh #export PYTHONPATH=YOUR_CUSTOM_PATH valgrind --tool=memcheck --leak-check=yes --suppressions=valgrind-python.supp python test_doctests.py Shapely-1.5.13/tests/test_affinity.py000077500000000000000000000260601260610516500176030ustar00rootroot00000000000000from . import unittest from math import pi from shapely import affinity from shapely.wkt import loads as load_wkt from shapely.geometry import Point class AffineTestCase(unittest.TestCase): def test_affine_params(self): g = load_wkt('LINESTRING(2.4 4.1, 2.4 3, 3 3)') self.assertRaises( TypeError, affinity.affine_transform, g, None) self.assertRaises( TypeError, affinity.affine_transform, g, '123456') self.assertRaises(ValueError, affinity.affine_transform, g, [1, 2, 3, 4, 5, 6, 7, 8, 9]) self.assertRaises(AttributeError, affinity.affine_transform, None, [1, 2, 3, 4, 5, 6]) def test_affine_geom_types(self): # identity matrices, which should result with no transformation matrix2d = (1, 0, 0, 1, 0, 0) matrix3d = (1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) # empty in, empty out empty2d = load_wkt('MULTIPOLYGON EMPTY') self.assertTrue(affinity.affine_transform(empty2d, matrix2d).is_empty) def test_geom(g2, g3=None): self.assertFalse(g2.has_z) a2 = affinity.affine_transform(g2, matrix2d) self.assertFalse(a2.has_z) self.assertTrue(g2.equals(a2)) if g3 is not None: self.assertTrue(g3.has_z) a3 = affinity.affine_transform(g3, matrix3d) self.assertTrue(a3.has_z) self.assertTrue(g3.equals(a3)) return pt2d = load_wkt('POINT(12.3 45.6)') pt3d = load_wkt('POINT(12.3 45.6 7.89)') test_geom(pt2d, pt3d) ls2d = load_wkt('LINESTRING(0.9 3.4, 0.7 2, 2.5 2.7)') ls3d = load_wkt('LINESTRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5)') test_geom(ls2d, ls3d) lr2d = load_wkt('LINEARRING(0.9 3.4, 0.7 2, 2.5 2.7, 0.9 3.4)') lr3d = load_wkt( 'LINEARRING(0.9 3.4 3.3, 0.7 2 2.3, 2.5 2.7 5.5, 0.9 3.4 3.3)') test_geom(lr2d, lr3d) test_geom(load_wkt('POLYGON((0.9 2.3, 0.5 1.1, 2.4 0.8, 0.9 2.3), ' '(1.1 1.7, 0.9 1.3, 1.4 1.2, 1.1 1.7), ' '(1.6 1.3, 1.7 1, 1.9 1.1, 1.6 1.3))')) test_geom(load_wkt( 'MULTIPOINT ((-300 300), (700 300), (-800 -1100), (200 -300))')) test_geom(load_wkt( 'MULTILINESTRING((0 0, -0.7 -0.7, 0.6 -1), ' '(-0.5 0.5, 0.7 0.6, 0 -0.6))')) test_geom(load_wkt( 'MULTIPOLYGON(((900 4300, -1100 -400, 900 -800, 900 4300)), ' '((1200 4300, 2300 4400, 1900 1000, 1200 4300)))')) test_geom(load_wkt('GEOMETRYCOLLECTION(POINT(20 70),' ' POLYGON((60 70, 13 35, 60 -30, 60 70)),' ' LINESTRING(60 70, 50 100, 80 100))')) def test_affine_2d(self): g = load_wkt('LINESTRING(2.4 4.1, 2.4 3, 3 3)') # custom scale and translate expected2d = load_wkt('LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)') matrix2d = (2, 0, 0, 2.5, -5, 4.1) a2 = affinity.affine_transform(g, matrix2d) self.assertTrue(a2.almost_equals(expected2d)) self.assertFalse(a2.has_z) # Make sure a 3D matrix does not make a 3D shape from a 2D input matrix3d = (2, 0, 0, 0, 2.5, 0, 0, 0, 10, -5, 4.1, 100) a3 = affinity.affine_transform(g, matrix3d) self.assertTrue(a3.almost_equals(expected2d)) self.assertFalse(a3.has_z) def test_affine_3d(self): g2 = load_wkt('LINESTRING(2.4 4.1, 2.4 3, 3 3)') g3 = load_wkt('LINESTRING(2.4 4.1 100.2, 2.4 3 132.8, 3 3 128.6)') # custom scale and translate matrix2d = (2, 0, 0, 2.5, -5, 4.1) matrix3d = (2, 0, 0, 0, 2.5, 0, 0, 0, 0.3048, -5, 4.1, 100) # Combinations of 2D and 3D geometries and matrices a22 = affinity.affine_transform(g2, matrix2d) a23 = affinity.affine_transform(g2, matrix3d) a32 = affinity.affine_transform(g3, matrix2d) a33 = affinity.affine_transform(g3, matrix3d) # Check dimensions self.assertFalse(a22.has_z) self.assertFalse(a23.has_z) self.assertTrue(a32.has_z) self.assertTrue(a33.has_z) # 2D equality checks expected2d = load_wkt('LINESTRING(-0.2 14.35, -0.2 11.6, 1 11.6)') expected3d = load_wkt('LINESTRING(-0.2 14.35 130.54096, ' '-0.2 11.6 140.47744, 1 11.6 139.19728)') expected32 = load_wkt('LINESTRING(-0.2 14.35 100.2, ' '-0.2 11.6 132.8, 1 11.6 128.6)') self.assertTrue(a22.almost_equals(expected2d)) self.assertTrue(a23.almost_equals(expected2d)) # Do explicit 3D check of coordinate values for a, e in zip(a32.coords, expected32.coords): for ap, ep in zip(a, e): self.assertAlmostEqual(ap, ep) for a, e in zip(a33.coords, expected3d.coords): for ap, ep in zip(a, e): self.assertAlmostEqual(ap, ep) class TransformOpsTestCase(unittest.TestCase): def test_rotate(self): ls = load_wkt('LINESTRING(240 400, 240 300, 300 300)') # counter-clockwise degrees rls = affinity.rotate(ls, 90) els = load_wkt('LINESTRING(220 320, 320 320, 320 380)') self.assertTrue(rls.equals(els)) # retest with named parameters for the same result rls = affinity.rotate(geom=ls, angle=90, origin='center') self.assertTrue(rls.equals(els)) # clockwise radians rls = affinity.rotate(ls, -pi/2, use_radians=True) els = load_wkt('LINESTRING(320 380, 220 380, 220 320)') self.assertTrue(rls.equals(els)) ## other `origin` parameters # around the centroid rls = affinity.rotate(ls, 90, origin='centroid') els = load_wkt('LINESTRING(182.5 320, 282.5 320, 282.5 380)') self.assertTrue(rls.equals(els)) # around the second coordinate tuple rls = affinity.rotate(ls, 90, origin=ls.coords[1]) els = load_wkt('LINESTRING(140 300, 240 300, 240 360)') self.assertTrue(rls.equals(els)) # around the absolute Point of origin rls = affinity.rotate(ls, 90, origin=Point(0, 0)) els = load_wkt('LINESTRING(-400 240, -300 240, -300 300)') self.assertTrue(rls.equals(els)) def test_scale(self): ls = load_wkt('LINESTRING(240 400 10, 240 300 30, 300 300 20)') # test defaults of 1.0 sls = affinity.scale(ls) self.assertTrue(sls.equals(ls)) # different scaling in different dimensions sls = affinity.scale(ls, 2, 3, 0.5) els = load_wkt('LINESTRING(210 500 5, 210 200 15, 330 200 10)') self.assertTrue(sls.equals(els)) # Do explicit 3D check of coordinate values for a, b in zip(sls.coords, els.coords): for ap, bp in zip(a, b): self.assertEqual(ap, bp) # retest with named parameters for the same result sls = affinity.scale(geom=ls, xfact=2, yfact=3, zfact=0.5, origin='center') self.assertTrue(sls.equals(els)) ## other `origin` parameters # around the centroid sls = affinity.scale(ls, 2, 3, 0.5, origin='centroid') els = load_wkt('LINESTRING(228.75 537.5, 228.75 237.5, 348.75 237.5)') self.assertTrue(sls.equals(els)) # around the second coordinate tuple sls = affinity.scale(ls, 2, 3, 0.5, origin=ls.coords[1]) els = load_wkt('LINESTRING(240 600, 240 300, 360 300)') self.assertTrue(sls.equals(els)) # around some other 3D Point of origin sls = affinity.scale(ls, 2, 3, 0.5, origin=Point(100, 200, 1000)) els = load_wkt('LINESTRING(380 800 505, 380 500 515, 500 500 510)') self.assertTrue(sls.equals(els)) # Do explicit 3D check of coordinate values for a, b in zip(sls.coords, els.coords): for ap, bp in zip(a, b): self.assertEqual(ap, bp) def test_skew(self): ls = load_wkt('LINESTRING(240 400 10, 240 300 30, 300 300 20)') # test default shear angles of 0.0 sls = affinity.skew(ls) self.assertTrue(sls.equals(ls)) # different shearing in x- and y-directions sls = affinity.skew(ls, 15, -30) els = load_wkt('LINESTRING (253.39745962155615 417.3205080756888, ' '226.60254037844385 317.3205080756888, ' '286.60254037844385 282.67949192431126)') self.assertTrue(sls.almost_equals(els)) # retest with radians for the same result sls = affinity.skew(ls, pi/12, -pi/6, use_radians=True) self.assertTrue(sls.almost_equals(els)) # retest with named parameters for the same result sls = affinity.skew(geom=ls, xs=15, ys=-30, origin='center', use_radians=False) self.assertTrue(sls.almost_equals(els)) ## other `origin` parameters # around the centroid sls = affinity.skew(ls, 15, -30, origin='centroid') els = load_wkt('LINESTRING(258.42150697963973 406.49519052838332, ' '231.6265877365273980 306.4951905283833185, ' '291.6265877365274264 271.8541743770057337)') self.assertTrue(sls.almost_equals(els)) # around the second coordinate tuple sls = affinity.skew(ls, 15, -30, origin=ls.coords[1]) els = load_wkt('LINESTRING(266.7949192431123038 400, 240 300, ' '300 265.3589838486224153)') self.assertTrue(sls.almost_equals(els)) # around the absolute Point of origin sls = affinity.skew(ls, 15, -30, origin=Point(0, 0)) els = load_wkt('LINESTRING(347.179676972449101 261.435935394489832, ' '320.3847577293367976 161.4359353944898317, ' '380.3847577293367976 126.7949192431122754)') self.assertTrue(sls.almost_equals(els)) def test_translate(self): ls = load_wkt('LINESTRING(240 400 10, 240 300 30, 300 300 20)') # test default offset of 0.0 tls = affinity.translate(ls) self.assertTrue(tls.equals(ls)) # test all offsets tls = affinity.translate(ls, 100, 400, -10) els = load_wkt('LINESTRING(340 800 0, 340 700 20, 400 700 10)') self.assertTrue(tls.equals(els)) # Do explicit 3D check of coordinate values for a, b in zip(tls.coords, els.coords): for ap, bp in zip(a, b): self.assertEqual(ap, bp) # retest with named parameters for the same result tls = affinity.translate(geom=ls, xoff=100, yoff=400, zoff=-10) self.assertTrue(tls.equals(els)) def test_suite(): loader = unittest.TestLoader() return unittest.TestSuite([ loader.loadTestsFromTestCase(AffineTestCase), loader.loadTestsFromTestCase(TransformOpsTestCase)]) Shapely-1.5.13/tests/test_box.py000066400000000000000000000013541260610516500165560ustar00rootroot00000000000000from . import unittest from shapely import geometry class BoxTestCase(unittest.TestCase): def test_ccw(self): b = geometry.box(0, 0, 1, 1, ccw=True) self.assertEqual(b.exterior.coords[0], (1.0, 0.0)) self.assertEqual(b.exterior.coords[1], (1.0, 1.0)) def test_ccw_default(self): b = geometry.box(0, 0, 1, 1) self.assertEqual(b.exterior.coords[0], (1.0, 0.0)) self.assertEqual(b.exterior.coords[1], (1.0, 1.0)) def test_cw(self): b = geometry.box(0, 0, 1, 1, ccw=False) self.assertEqual(b.exterior.coords[0], (0.0, 0.0)) self.assertEqual(b.exterior.coords[1], (0.0, 1.0)) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(BoxTestCase) Shapely-1.5.13/tests/test_cga.py000066400000000000000000000023721260610516500165210ustar00rootroot00000000000000from . import unittest from shapely.geometry.polygon import LinearRing, orient, Polygon class RingOrientationTestCase(unittest.TestCase): def test_ccw(self): ring = LinearRing([(1, 0), (0, 1), (0, 0)]) self.assertTrue(ring.is_ccw) def test_cw(self): ring = LinearRing([(0, 0), (0, 1), (1, 0)]) self.assertFalse(ring.is_ccw) class PolygonOrienterTestCase(unittest.TestCase): def test_no_holes(self): ring = LinearRing([(0, 0), (0, 1), (1, 0)]) polygon = Polygon(ring) self.assertFalse(polygon.exterior.is_ccw) polygon = orient(polygon, 1) self.assertTrue(polygon.exterior.is_ccw) def test_holes(self): polygon = Polygon([(0, 0), (0, 1), (1, 0)], [[(0.5, 0.25), (0.25, 0.5), (0.25, 0.25)]]) self.assertFalse(polygon.exterior.is_ccw) self.assertTrue(polygon.interiors[0].is_ccw) polygon = orient(polygon, 1) self.assertTrue(polygon.exterior.is_ccw) self.assertFalse(polygon.interiors[0].is_ccw) def test_suite(): loader = unittest.TestLoader() return unittest.TestSuite([ loader.loadTestsFromTestCase(RingOrientationTestCase), loader.loadTestsFromTestCase(PolygonOrienterTestCase)]) Shapely-1.5.13/tests/test_collection.py000066400000000000000000000015751260610516500201260ustar00rootroot00000000000000from . import unittest from shapely.geometry import LineString from shapely.geometry.collection import GeometryCollection class CollectionTestCase(unittest.TestCase): def test_array_interface(self): m = GeometryCollection() self.assertEqual(len(m), 0) self.assertEqual(m.geoms, []) def test_child_with_deleted_parent(self): # test that we can remove a collection while having # childs around a = LineString([(0, 0), (1, 1), (1, 2), (2, 2)]) b = LineString([(0, 0), (1, 1), (2, 1), (2, 2)]) collection = a.intersection(b) child = collection.geoms[0] # delete parent of child del collection # access geometry, this should not seg fault as 1.2.15 did self.assertIsNotNone(child.wkt) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(CollectionTestCase) Shapely-1.5.13/tests/test_coords.py000066400000000000000000000022411260610516500172530ustar00rootroot00000000000000from . import unittest, numpy from shapely import geometry class CoordsTestCase(unittest.TestCase): """ Shapely assumes contiguous C-order float64 data for internal ops. Data should be converted to contiguous float64 if numpy exists. c9a0707 broke this a little bit. """ @unittest.skipIf(not numpy, 'Numpy required') def test_data_promotion(self): coords = numpy.array([[ 12, 34 ], [ 56, 78 ]], dtype=numpy.float32) processed_coords = numpy.array( geometry.LineString(coords).coords ) self.assertEqual( coords.tolist(), processed_coords.tolist() ) @unittest.skipIf(not numpy, 'Numpy required') def test_data_destriding(self): coords = numpy.array([[ 12, 34 ], [ 56, 78 ]], dtype=numpy.float32) # Easy way to introduce striding: reverse list order processed_coords = numpy.array( geometry.LineString(coords[::-1]).coords ) self.assertEqual( coords[::-1].tolist(), processed_coords.tolist() ) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(CoordsTestCase) Shapely-1.5.13/tests/test_default_impl.py000066400000000000000000000011671260610516500204350ustar00rootroot00000000000000import pytest from shapely.geometry import Point from shapely.impl import delegated, ImplementationError def test_error(): with pytest.raises(ImplementationError): Point(0, 0).impl['bogus']() with pytest.raises(NotImplementedError): Point(0, 0).impl['bogus']() with pytest.raises(KeyError): Point(0, 0).impl['bogus']() def test_delegated(): class Poynt(Point): @delegated def bogus(self): return self.impl['bogus']() with pytest.raises(ImplementationError): Poynt(0, 0).bogus() with pytest.raises(AttributeError): Poynt(0, 0).bogus() Shapely-1.5.13/tests/test_delaunay.py000066400000000000000000000023031260610516500175630ustar00rootroot00000000000000try: import unittest2 as unittest except ImportError: import unittest from shapely.geometry import Polygon, LineString, Point from shapely.ops import triangulate from shapely.geos import geos_version @unittest.skipIf(geos_version < (3, 4, 0), "Delaunay triangulation not supported") class DelaunayTriangulation(unittest.TestCase): """ Only testing the number of triangles and their type here. This doesn't actually test the points in the resulting geometries. """ def setUp(self): self.p = Polygon([(0,0), (1,0), (1,1), (0,1)]) def test_polys(self): polys = triangulate(self.p) self.assertEqual(len(polys), 2) for p in polys: self.assert_(isinstance(p, Polygon)) def test_lines(self): polys = triangulate(self.p, edges=True) self.assertEqual(len(polys), 5) for p in polys: self.assert_(isinstance(p, LineString)) def test_point(self): p = Point(1,1) polys = triangulate(p) self.assertEqual(len(polys), 0) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(DelaunayTriangulation) if __name__ == '__main__': unittest.main() Shapely-1.5.13/tests/test_delegated.py000066400000000000000000000015651260610516500177100ustar00rootroot00000000000000from . import unittest from shapely.geometry import Point from shapely.impl import BaseImpl from shapely.geometry.base import delegated class Geometry(object): impl = BaseImpl({}) @property @delegated def foo(self): return self.impl['foo']() class WrapperTestCase(unittest.TestCase): """When the backend has no support for a method, we get an AttributeError """ def test_delegated(self): self.assertRaises(AttributeError, getattr, Geometry(), 'foo') def test_defaultimpl(self): project_impl = Point.impl.map.pop('project', None) try: self.assertRaises(AttributeError, Point(0, 0).project, 1.0) finally: if project_impl is not None: Point.impl.map['project'] = project_impl def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(WrapperTestCase) Shapely-1.5.13/tests/test_dlls.py000066400000000000000000000011531260610516500167210ustar00rootroot00000000000000import sys from . import unittest from shapely.geos import load_dll class LoadingTestCase(unittest.TestCase): def test_load(self): self.assertRaises(OSError, load_dll, 'geosh_c') @unittest.skipIf(sys.platform == "win32", "FIXME: adapt test for win32") def test_fallbacks(self): load_dll('geos_c', fallbacks=[ '/opt/local/lib/libgeos_c.dylib', # MacPorts '/usr/local/lib/libgeos_c.dylib', # homebrew (Mac OS X) 'libgeos_c.so.1', 'libgeos_c.so']) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(LoadingTestCase) Shapely-1.5.13/tests/test_doctests.py000066400000000000000000000017311260610516500176150ustar00rootroot00000000000000import os import doctest from . import unittest from glob import glob optionflags = (doctest.REPORT_ONLY_FIRST_FAILURE | doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS) def list_doctests(): print(__file__) source_files = glob(os.path.join(os.path.dirname(__file__), '*.txt')) return [filename for filename in source_files] def open_file(filename, mode='r'): """Helper function to open files from within the tests package.""" return open(os.path.join(os.path.dirname(__file__), filename), mode) def setUp(test): test.globs.update(dict(open_file=open_file,)) def test_suite(): return unittest.TestSuite( [doctest.DocFileSuite(os.path.basename(filename), optionflags=optionflags, setUp=setUp) for filename in list_doctests()]) if __name__ == "__main__": runner = unittest.TextTestRunner(verbosity=1) runner.run(test_suite()) Shapely-1.5.13/tests/test_emptiness.py000066400000000000000000000026551260610516500200020ustar00rootroot00000000000000from . import unittest from shapely.geometry.base import BaseGeometry import shapely.geometry as sgeom from shapely.geometry.polygon import LinearRing class EmptinessTestCase(unittest.TestCase): def test_empty_base(self): g = BaseGeometry() self.assertTrue(g._is_empty) def test_emptying_point(self): p = sgeom.Point(0, 0) self.assertFalse(p._is_empty) p.empty() self.assertTrue(p._is_empty) def test_none_geom(self): p = BaseGeometry() p._geom = None self.assertTrue(p.is_empty) def test_empty_point(self): self.assertTrue(sgeom.Point().is_empty) def test_empty_multipoint(self): self.assertTrue(sgeom.MultiPoint().is_empty) def test_empty_geometry_collection(self): self.assertTrue(sgeom.GeometryCollection().is_empty) def test_empty_linestring(self): self.assertTrue(sgeom.LineString().is_empty) def test_empty_multilinestring(self): self.assertTrue(sgeom.MultiLineString([]).is_empty) def test_empty_polygon(self): self.assertTrue(sgeom.Polygon().is_empty) def test_empty_multipolygon(self): self.assertTrue(sgeom.MultiPolygon([]).is_empty) def test_empty_linear_ring(self): self.assertTrue(LinearRing().is_empty) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(EmptinessTestCase) if __name__ == '__main__': unittest.main() Shapely-1.5.13/tests/test_equality.py000066400000000000000000000017131260610516500176220ustar00rootroot00000000000000from . import unittest from shapely import geometry class PointEqualityTestCase(unittest.TestCase): def test_equals_exact(self): p1 = geometry.Point(1.0, 1.0) p2 = geometry.Point(2.0, 2.0) self.assertFalse(p1.equals(p2)) self.assertFalse(p1.equals_exact(p2, 0.001)) def test_almost_equals_default(self): p1 = geometry.Point(1.0, 1.0) p2 = geometry.Point(1.0+1e-7, 1.0+1e-7) # almost equal to 6 places p3 = geometry.Point(1.0+1e-6, 1.0+1e-6) # not almost equal self.assertTrue(p1.almost_equals(p2)) self.assertFalse(p1.almost_equals(p3)) def test_almost_equals(self): p1 = geometry.Point(1.0, 1.0) p2 = geometry.Point(1.1, 1.1) self.assertFalse(p1.equals(p2)) self.assertTrue(p1.almost_equals(p2, 0)) self.assertFalse(p1.almost_equals(p2, 1)) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(PointEqualityTestCase) Shapely-1.5.13/tests/test_geointerface.py000066400000000000000000000054131260610516500204210ustar00rootroot00000000000000from . import unittest from shapely.geometry import asShape from shapely.geometry.multipoint import MultiPointAdapter from shapely.geometry.linestring import LineStringAdapter from shapely.geometry.multilinestring import MultiLineStringAdapter from shapely.geometry.polygon import PolygonAdapter from shapely.geometry.multipolygon import MultiPolygonAdapter class GeoThing(object): def __init__(self, d): self.__geo_interface__ = d class GeoInterfaceTestCase(unittest.TestCase): def test_geointerface(self): # Adapt a dictionary d = {"type": "Point", "coordinates": (0.0, 0.0)} shape = asShape(d) self.assertEqual(shape.geom_type, 'Point') self.assertEqual(tuple(shape.coords), ((0.0, 0.0),)) # Adapt an object that implements the geo protocol shape = None thing = GeoThing({"type": "Point", "coordinates": (0.0, 0.0)}) shape = asShape(thing) self.assertEqual(shape.geom_type, 'Point') self.assertEqual(tuple(shape.coords), ((0.0, 0.0),)) # Check line string shape = asShape( {'type': 'LineString', 'coordinates': ((-1.0, -1.0), (1.0, 1.0))}) self.assertIsInstance(shape, LineStringAdapter) self.assertEqual(tuple(shape.coords), ((-1.0, -1.0), (1.0, 1.0))) # polygon shape = asShape( {'type': 'Polygon', 'coordinates': (((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, -1.0), (0.0, 0.0)), ((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1), (0.1, 0.1)))} ) self.assertIsInstance(shape, PolygonAdapter) self.assertEqual( tuple(shape.exterior.coords), ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, -1.0), (0.0, 0.0))) self.assertEqual(len(shape.interiors), 1) # multi point shape = asShape({'type': 'MultiPoint', 'coordinates': ((1.0, 2.0), (3.0, 4.0))}) self.assertIsInstance(shape, MultiPointAdapter) self.assertEqual(len(shape.geoms), 2) # multi line string shape = asShape({'type': 'MultiLineString', 'coordinates': (((0.0, 0.0), (1.0, 2.0)),)}) self.assertIsInstance(shape, MultiLineStringAdapter) self.assertEqual(len(shape.geoms), 1) # multi polygon shape = asShape( {'type': 'MultiPolygon', 'coordinates': [(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)), ((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1), (0.1, 0.1)) )]}) self.assertIsInstance(shape, MultiPolygonAdapter) self.assertEqual(len(shape.geoms), 1) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(GeoInterfaceTestCase) Shapely-1.5.13/tests/test_geomseq.py000066400000000000000000000006111260610516500174210ustar00rootroot00000000000000from . import unittest from shapely import geometry class MultiLineTestCase(unittest.TestCase): def test_array_interface(self): m = geometry.MultiLineString([((0, 0), (1, 1)), ((2, 2), (3, 3))]) ai = m.geoms[0].__array_interface__ self.assertEqual(ai['shape'], (2, 2)) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(MultiLineTestCase) Shapely-1.5.13/tests/test_geos_err_handler.py000066400000000000000000000020551260610516500212670ustar00rootroot00000000000000import logging import pytest from shapely.geometry import LineString from shapely.geos import ReadingError from shapely.wkt import loads def test_error_handler(tmpdir): logger = logging.getLogger('shapely.geos') logger.setLevel(logging.DEBUG) logfile = str(tmpdir.join('test_error.log')) fh = logging.FileHandler(logfile) logger.addHandler(fh) # This operation calls error_handler with a format string that # has *no* conversion specifiers. LineString([(0, 0), (2, 2)]).project(LineString([(1, 1), (1.5, 1.5)])) # This calls error_handler with a format string of "%s" and one # value. with pytest.raises(ReadingError): loads('POINT (LOLWUT)') g = loads('MULTIPOLYGON (((10 20, 10 120, 60 70, 30 70, 30 40, 60 40, 60 70, 90 20, 10 20)))') assert g.is_valid == False log = open(logfile).read() assert "third argument of GEOSProject_r must be Point*" in log assert "Expected number but encountered word: 'LOLWUT'" in log assert "Ring Self-intersection at or near point 60 70" in log Shapely-1.5.13/tests/test_getitem.py000066400000000000000000000126551260610516500174320ustar00rootroot00000000000000from . import unittest from shapely import geometry class CoordsGetItemTestCase(unittest.TestCase): def test_index_2d_coords(self): c = [(float(x), float(-x)) for x in range(4)] g = geometry.LineString(c) for i in range(-4, 4): self.assertTrue(g.coords[i] == c[i]) self.assertRaises(IndexError, lambda: g.coords[4]) self.assertRaises(IndexError, lambda: g.coords[-5]) def test_index_3d_coords(self): c = [(float(x), float(-x), float(x*2)) for x in range(4)] g = geometry.LineString(c) for i in range(-4, 4): self.assertTrue(g.coords[i] == c[i]) self.assertRaises(IndexError, lambda: g.coords[4]) self.assertRaises(IndexError, lambda: g.coords[-5]) def test_index_coords_misc(self): g = geometry.LineString() # empty self.assertRaises(IndexError, lambda: g.coords[0]) self.assertRaises(TypeError, lambda: g.coords[0.0]) def test_slice_2d_coords(self): c = [(float(x), float(-x)) for x in range(4)] g = geometry.LineString(c) self.assertTrue(g.coords[1:] == c[1:]) self.assertTrue(g.coords[:-1] == c[:-1]) self.assertTrue(g.coords[::-1] == c[::-1]) self.assertTrue(g.coords[::2] == c[::2]) self.assertTrue(g.coords[:4] == c[:4]) self.assertTrue(g.coords[4:] == c[4:] == []) def test_slice_3d_coords(self): c = [(float(x), float(-x), float(x*2)) for x in range(4)] g = geometry.LineString(c) self.assertTrue(g.coords[1:] == c[1:]) self.assertTrue(g.coords[:-1] == c[:-1]) self.assertTrue(g.coords[::-1] == c[::-1]) self.assertTrue(g.coords[::2] == c[::2]) self.assertTrue(g.coords[:4] == c[:4]) self.assertTrue(g.coords[4:] == c[4:] == []) class MultiGeomGetItemTestCase(unittest.TestCase): def test_index_multigeom(self): c = [(float(x), float(-x)) for x in range(4)] g = geometry.MultiPoint(c) for i in range(-4, 4): self.assertTrue(g[i].equals(geometry.Point(c[i]))) self.assertRaises(IndexError, lambda: g[4]) self.assertRaises(IndexError, lambda: g[-5]) def test_index_multigeom_misc(self): g = geometry.MultiLineString() # empty self.assertRaises(IndexError, lambda: g[0]) self.assertRaises(TypeError, lambda: g[0.0]) def test_slice_multigeom(self): c = [(float(x), float(-x)) for x in range(4)] g = geometry.MultiPoint(c) self.assertEqual(type(g[:]), type(g)) self.assertEqual(len(g[:]), len(g)) self.assertTrue(g[1:].equals(geometry.MultiPoint(c[1:]))) self.assertTrue(g[:-1].equals(geometry.MultiPoint(c[:-1]))) self.assertTrue(g[::-1].equals(geometry.MultiPoint(c[::-1]))) self.assertTrue(g[4:].is_empty) class LinearRingGetItemTestCase(unittest.TestCase): def test_index_linearring(self): shell = geometry.polygon.LinearRing([(0.0, 0.0), (70.0, 120.0), (140.0, 0.0), (0.0, 0.0)]) holes = [geometry.polygon.LinearRing([(60.0, 80.0), (80.0, 80.0), (70.0, 60.0), (60.0, 80.0)]), geometry.polygon.LinearRing([(30.0, 10.0), (50.0, 10.0), (40.0, 30.0), (30.0, 10.0)]), geometry.polygon.LinearRing([(90.0, 10), (110.0, 10.0), (100.0, 30.0), (90.0, 10.0)])] g = geometry.Polygon(shell, holes) for i in range(-3, 3): self.assertTrue(g.interiors[i].equals(holes[i])) self.assertRaises(IndexError, lambda: g.interiors[3]) self.assertRaises(IndexError, lambda: g.interiors[-4]) def test_index_linearring_misc(self): g = geometry.Polygon() # empty self.assertRaises(IndexError, lambda: g.interiors[0]) self.assertRaises(TypeError, lambda: g.interiors[0.0]) def test_slice_linearring(self): shell = geometry.polygon.LinearRing([(0.0, 0.0), (70.0, 120.0), (140.0, 0.0), (0.0, 0.0)]) holes = [geometry.polygon.LinearRing([(60.0, 80.0), (80.0, 80.0), (70.0, 60.0), (60.0, 80.0)]), geometry.polygon.LinearRing([(30.0, 10.0), (50.0, 10.0), (40.0, 30.0), (30.0, 10.0)]), geometry.polygon.LinearRing([(90.0, 10), (110.0, 10.0), (100.0, 30.0), (90.0, 10.0)])] g = geometry.Polygon(shell, holes) t = [a.equals(b) for (a, b) in zip(g.interiors[1:], holes[1:])] self.assertTrue(all(t)) t = [a.equals(b) for (a, b) in zip(g.interiors[:-1], holes[:-1])] self.assertTrue(all(t)) t = [a.equals(b) for (a, b) in zip(g.interiors[::-1], holes[::-1])] self.assertTrue(all(t)) t = [a.equals(b) for (a, b) in zip(g.interiors[::2], holes[::2])] self.assertTrue(all(t)) t = [a.equals(b) for (a, b) in zip(g.interiors[:3], holes[:3])] self.assertTrue(all(t)) self.assertTrue(g.interiors[3:] == holes[3:] == []) def test_suite(): loader = unittest.TestLoader() return unittest.TestSuite([ loader.loadTestsFromTestCase(CoordsGetItemTestCase), loader.loadTestsFromTestCase(MultiGeomGetItemTestCase), loader.loadTestsFromTestCase(LinearRingGetItemTestCase)]) Shapely-1.5.13/tests/test_hash.py000066400000000000000000000012371260610516500167110ustar00rootroot00000000000000from shapely.geometry import Point, MultiPoint, Polygon, GeometryCollection def test_point(): g = Point(0, 0) try: assert hash(g) return False except TypeError: return True def test_multipoint(): g = MultiPoint([(0, 0)]) try: assert hash(g) return False except TypeError: return True def test_polygon(): g = Point(0, 0).buffer(1.0) try: assert hash(g) return False except TypeError: return True def test_collection(): g = GeometryCollection([Point(0, 0)]) try: assert hash(g) return False except TypeError: return True Shapely-1.5.13/tests/test_invalid_geometries.py000066400000000000000000000020121260610516500216270ustar00rootroot00000000000000'''Test recovery from operation on invalid geometries ''' from . import unittest from shapely.geometry import Polygon from shapely.topology import TopologicalError class InvalidGeometriesTestCase(unittest.TestCase): def test_invalid_intersection(self): # Make a self-intersecting polygon polygon_invalid = Polygon(((0, 0), (1, 1), (1, -1), (0, 1), (0, 0))) self.assertFalse(polygon_invalid.is_valid) # Intersect with a valid polygon polygon = Polygon(((-.5, -.5), (-.5, .5), (.5, .5), (.5, -5))) self.assertTrue(polygon.is_valid) self.assertTrue(polygon_invalid.intersects(polygon)) self.assertRaises(TopologicalError, polygon_invalid.intersection, polygon) self.assertRaises(TopologicalError, polygon.intersection, polygon_invalid) return def test_suite(): loader = unittest.TestLoader() return unittest.TestSuite([ loader.loadTestsFromTestCase(InvalidGeometriesTestCase)]) Shapely-1.5.13/tests/test_iterops.py000066400000000000000000000047651260610516500174640ustar00rootroot00000000000000"""Test operator iterations """ from . import unittest from shapely import iterops from shapely.geometry import Point, Polygon from shapely.geos import TopologicalError class IterOpsTestCase(unittest.TestCase): def test_iterops(self): coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)) polygon = Polygon(coords) points = [Point(0.5, 0.5), Point(2.0, 2.0)] # List of the points contained by the polygon self.assertTrue( all([isinstance(x, Point) for x in iterops.contains(polygon, points, True)])) # 'True' is the default value self.assertTrue( all([isinstance(x, Point) for x in iterops.contains(polygon, points)])) # Test a false value self.assertTrue( all([isinstance(x, Point) for x in iterops.contains(polygon, points, False)])) # If the provided iterator yields tuples, the second value will be # yielded self.assertEqual( list(iterops.contains(polygon, [(p, p.coords[:]) for p in points], False)), [[(2.0, 2.0)]]) # Just to demonstrate that the important thing is that the second # parameter is an iterator: self.assertEqual( list(iterops.contains(polygon, iter((p, p.coords[:]) for p in points))), [[(0.5, 0.5)]]) def test_err(self): # bowtie polygon. coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)) polygon = Polygon(coords) self.assertFalse(polygon.is_valid) points = [Point(0.5, 0.5).buffer(2.0), Point(2.0, 2.0).buffer(3.0)] # List of the points contained by the polygon self.assertTrue( all([isinstance(x, Polygon) for x in iterops.intersects(polygon, points, True)])) def test_topological_error(self): p1 = [(339, 346), (459, 346), (399, 311), (340, 277), (399, 173), (280, 242), (339, 415), (280, 381), (460, 207), (339, 346)] polygon1 = Polygon(p1) p2 = [(339, 207), (280, 311), (460, 138), (399, 242), (459, 277), (459, 415), (399, 381), (519, 311), (520, 242), (519, 173), (399, 450), (339, 207)] polygon2 = Polygon(p2) with self.assertRaises(TopologicalError): list(iterops.within(polygon1, [polygon2])) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(IterOpsTestCase) Shapely-1.5.13/tests/test_linear_referencing.py000066400000000000000000000060131260610516500216040ustar00rootroot00000000000000from . import unittest from shapely.geos import geos_version from shapely.geometry import Point, LineString, MultiLineString class LinearReferencingTestCase(unittest.TestCase): def setUp(self): self.point = Point(1, 1) self.line1 = LineString(([0, 0], [2, 0])) self.line2 = LineString(([3, 0], [3, 6])) self.multiline = MultiLineString([ list(self.line1.coords), list(self.line2.coords) ]) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_line1_project(self): self.assertEqual(self.line1.project(self.point), 1.0) self.assertEqual(self.line1.project(self.point, normalized=True), 0.5) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_line2_project(self): self.assertEqual(self.line2.project(self.point), 1.0) self.assertAlmostEqual( self.line2.project(self.point, normalized=True), 0.16666666666, 8) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_multiline_project(self): self.assertEqual(self.multiline.project(self.point), 1.0) self.assertEqual( self.multiline.project(self.point, normalized=True), 0.125) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_not_supported_project(self): with self.assertRaises(TypeError): self.point.buffer(1.0).project(self.point) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_not_on_line_project(self): # Points that aren't on the line project to 0. self.assertEqual(self.line1.project(Point(-10, -10)), 0.0) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_line1_interpolate(self): self.assertTrue(self.line1.interpolate(0.5).equals(Point(0.5, 0.0))) self.assertTrue( self.line1.interpolate(0.5, normalized=True).equals(Point(1, 0))) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_line2_interpolate(self): self.assertTrue(self.line2.interpolate(0.5).equals(Point(3.0, 0.5))) self.assertTrue( self.line2.interpolate(0.5, normalized=True).equals(Point(3, 3))) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_multiline_interpolate(self): self.assertTrue(self.multiline.interpolate(0.5).equals(Point(0.5, 0))) self.assertTrue( self.multiline.interpolate(0.5, normalized=True).equals( Point(3.0, 2.0))) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_line_ends_interpolate(self): # Distances greater than length of the line or less than # zero yield the line's ends. self.assertTrue(self.line1.interpolate(-1000).equals(Point(0.0, 0.0))) self.assertTrue(self.line1.interpolate(1000).equals(Point(2.0, 0.0))) def test_suite(): loader = unittest.TestLoader() return loader.loadTestsFromTestCase(LinearReferencingTestCase) Shapely-1.5.13/tests/test_linemerge.py000066400000000000000000000030641260610516500177350ustar00rootroot00000000000000from . import unittest from shapely.geometry import LineString, MultiLineString from shapely.ops import linemerge class LineMergeTestCase(unittest.TestCase): def test_linemerge(self): lines = MultiLineString( [((0, 0), (1, 1)), ((2, 0), (2, 1), (1, 1))]) result = linemerge(lines) self.assertIsInstance(result, LineString) self.assertFalse(result.is_ring) self.assertEqual(len(result.coords), 4) self.assertEqual(result.coords[0], (0.0, 0.0)) self.assertEqual(result.coords[3], (2.0, 0.0)) lines2 = MultiLineString( [((0, 0), (1, 1)), ((0, 0), (2, 0), (2, 1), (1, 1))]) result = linemerge(lines2) self.assertTrue(result.is_ring) self.assertEqual(len(result.coords), 5) lines3 = [ LineString(((0, 0), (1, 1))), LineString(((0, 0), (0, 1))), ] result = linemerge(lines3) self.assertFalse(result.is_ring) self.assertEqual(len(result.coords), 3) self.assertEqual(result.coords[0], (0.0, 1.0)) self.assertEqual(result.coords[2], (1.0, 1.0)) lines4 = [ ((0, 0), (1, 1)), ((0, 0), (0, 1)), ] self.assertTrue(result.equals(linemerge(lines4))) lines5 = [ ((0, 0), (1, 1)), ((1, 0), (0, 1)), ] result = linemerge(lines5) self.assertEqual(result.type, 'MultiLineString') def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(LineMergeTestCase) Shapely-1.5.13/tests/test_linestring.py000066400000000000000000000115221260610516500201420ustar00rootroot00000000000000from . import unittest, numpy from shapely.geos import lgeos from shapely.geometry import LineString, asLineString, Point, LinearRing class LineStringTestCase(unittest.TestCase): def test_linestring(self): # From coordinate tuples line = LineString(((1.0, 2.0), (3.0, 4.0))) self.assertEqual(len(line.coords), 2) self.assertEqual(line.coords[:], [(1.0, 2.0), (3.0, 4.0)]) # From Points line2 = LineString((Point(1.0, 2.0), Point(3.0, 4.0))) self.assertEqual(len(line2.coords), 2) self.assertEqual(line2.coords[:], [(1.0, 2.0), (3.0, 4.0)]) # From mix of tuples and Points line3 = LineString((Point(1.0, 2.0), (2.0, 3.0), Point(3.0, 4.0))) self.assertEqual(len(line3.coords), 3) self.assertEqual(line3.coords[:], [(1.0, 2.0), (2.0, 3.0), (3.0, 4.0)]) # Bounds self.assertEqual(line.bounds, (1.0, 2.0, 3.0, 4.0)) # Coordinate access self.assertEqual(tuple(line.coords), ((1.0, 2.0), (3.0, 4.0))) self.assertEqual(line.coords[0], (1.0, 2.0)) self.assertEqual(line.coords[1], (3.0, 4.0)) with self.assertRaises(IndexError): line.coords[2] # index out of range # Geo interface self.assertEqual(line.__geo_interface__, {'type': 'LineString', 'coordinates': ((1.0, 2.0), (3.0, 4.0))}) # Coordinate modification line.coords = ((-1.0, -1.0), (1.0, 1.0)) self.assertEqual(line.__geo_interface__, {'type': 'LineString', 'coordinates': ((-1.0, -1.0), (1.0, 1.0))}) # Adapt a coordinate list to a line string coords = [[5.0, 6.0], [7.0, 8.0]] la = asLineString(coords) self.assertEqual(la.coords[:], [(5.0, 6.0), (7.0, 8.0)]) # Test Non-operability of Null geometry l_null = LineString() self.assertEqual(l_null.wkt, 'GEOMETRYCOLLECTION EMPTY') self.assertEqual(l_null.length, 0.0) # Check that we can set coordinates of a null geometry l_null.coords = [(0, 0), (1, 1)] self.assertAlmostEqual(l_null.length, 1.4142135623730951) def test_from_linestring(self): line = LineString(((1.0, 2.0), (3.0, 4.0))) copy = LineString(line) self.assertEqual(len(copy.coords), 2) self.assertEqual(copy.coords[:], [(1.0, 2.0), (3.0, 4.0)]) self.assertEqual('LineString', lgeos.GEOSGeomType(copy._geom).decode('ascii')) def test_from_linestring_z(self): coords = [(1.0, 2.0, 3.0), (4.0, 5.0, 6.0)] line = LineString(coords) copy = LineString(line) self.assertEqual(len(copy.coords), 2) self.assertEqual(copy.coords[:], coords) self.assertEqual('LineString', lgeos.GEOSGeomType(copy._geom).decode('ascii')) def test_from_linearring(self): coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] ring = LinearRing(coords) copy = LineString(ring) self.assertEqual(len(copy.coords), 4) self.assertEqual(copy.coords[:], coords) self.assertEqual('LineString', lgeos.GEOSGeomType(copy._geom).decode('ascii')) @unittest.skipIf(not numpy, 'Numpy required') def test_numpy(self): from numpy import array, asarray from numpy.testing import assert_array_equal # Construct from a numpy array line = LineString(array([[0.0, 0.0], [1.0, 2.0]])) self.assertEqual(len(line.coords), 2) self.assertEqual(line.coords[:], [(0.0, 0.0), (1.0, 2.0)]) line = LineString(((1.0, 2.0), (3.0, 4.0))) la = asarray(line) expected = array([[1.0, 2.0], [3.0, 4.0]]) assert_array_equal(la, expected) # Coordinate sequences can be adapted as well la = asarray(line.coords) assert_array_equal(la, expected) # Adapt a Numpy array to a line string a = array([[1.0, 2.0], [3.0, 4.0]]) la = asLineString(a) assert_array_equal(la.context, a) self.assertEqual(la.coords[:], [(1.0, 2.0), (3.0, 4.0)]) # Now, the inverse self.assertEqual(la.__array_interface__, la.context.__array_interface__) pas = asarray(la) assert_array_equal(pas, array([[1.0, 2.0], [3.0, 4.0]])) # From Array.txt a = asarray([[0.0, 0.0], [2.0, 2.0], [1.0, 1.0]]) line = LineString(a) self.assertEqual(line.coords[:], [(0.0, 0.0), (2.0, 2.0), (1.0, 1.0)]) data = line.ctypes self.assertEqual(data[0], 0.0) self.assertEqual(data[5], 1.0) b = asarray(line) assert_array_equal(b, array([[0., 0.], [2., 2.], [1., 1.]])) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(LineStringTestCase) Shapely-1.5.13/tests/test_locale.py000066400000000000000000000025711260610516500172270ustar00rootroot00000000000000'''Test locale independence of WKT ''' from . import unittest import sys import locale from shapely.wkt import loads, dumps # Set locale to one that uses a comma as decimal seperator # TODO: try a few other common locales if sys.platform == 'win32': test_locales = { 'Portuguese': 'portuguese_brazil', } else: test_locales = { 'Portuguese': 'pt_BR.UTF-8', } do_test_locale = False def setUpModule(): global do_test_locale for name in test_locales: try: test_locale = test_locales[name] locale.setlocale(locale.LC_ALL, test_locale) do_test_locale = True break except: pass if not do_test_locale: raise unittest.SkipTest('test locale not found') def tearDownModule(): if sys.platform == 'win32': locale.setlocale(locale.LC_ALL, "") else: locale.resetlocale() class LocaleTestCase(unittest.TestCase): #@unittest.skipIf(not do_test_locale, 'test locale not found') def test_wkt_locale(self): # Test reading and writing p = loads('POINT (0.0 0.0)') self.assertEqual(p.x, 0.0) self.assertEqual(p.y, 0.0) wkt = dumps(p) self.assertTrue(wkt.startswith('POINT')) self.assertFalse(',' in wkt) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(LocaleTestCase) Shapely-1.5.13/tests/test_mapping.py000066400000000000000000000005521260610516500174200ustar00rootroot00000000000000from . import unittest from shapely.geometry import Point, mapping class MappingTestCase(unittest.TestCase): def test_point(self): m = mapping(Point(0, 0)) self.assertEqual(m['type'], 'Point') self.assertEqual(m['coordinates'], (0.0, 0.0)) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(MappingTestCase) Shapely-1.5.13/tests/test_multi.py000066400000000000000000000004431260610516500171160ustar00rootroot00000000000000from . import unittest, test_int_types class MultiGeometryTestCase(unittest.TestCase): def subgeom_access_test(self, cls, geoms): geom = cls(geoms) for t in test_int_types: for i, g in enumerate(geoms): self.assertEqual(geom[t(i)], geoms[i]) Shapely-1.5.13/tests/test_multilinestring.py000066400000000000000000000062001260610516500212120ustar00rootroot00000000000000from . import unittest, numpy, test_int_types from .test_multi import MultiGeometryTestCase from shapely.geos import lgeos from shapely.geometry import LineString, MultiLineString, asMultiLineString from shapely.geometry.base import dump_coords class MultiLineStringTestCase(MultiGeometryTestCase): def test_multilinestring(self): # From coordinate tuples geom = MultiLineString((((1.0, 2.0), (3.0, 4.0)),)) self.assertIsInstance(geom, MultiLineString) self.assertEqual(len(geom.geoms), 1) self.assertEqual(dump_coords(geom), [[(1.0, 2.0), (3.0, 4.0)]]) # From lines a = LineString(((1.0, 2.0), (3.0, 4.0))) ml = MultiLineString([a]) self.assertEqual(len(ml.geoms), 1) self.assertEqual(dump_coords(ml), [[(1.0, 2.0), (3.0, 4.0)]]) # From another multi-line ml2 = MultiLineString(ml) self.assertEqual(len(ml2.geoms), 1) self.assertEqual(dump_coords(ml2), [[(1.0, 2.0), (3.0, 4.0)]]) # Sub-geometry Access geom = MultiLineString([(((0.0, 0.0), (1.0, 2.0)))]) self.assertIsInstance(geom[0], LineString) self.assertEqual(dump_coords(geom[0]), [(0.0, 0.0), (1.0, 2.0)]) with self.assertRaises(IndexError): # index out of range geom.geoms[1] # Geo interface self.assertEqual(geom.__geo_interface__, {'type': 'MultiLineString', 'coordinates': (((0.0, 0.0), (1.0, 2.0)),)}) def test_from_multilinestring_z(self): coords1 = [(0.0, 1.0, 2.0), (3.0, 4.0, 5.0)] coords2 = [(6.0, 7.0, 8.0), (9.0, 10.0, 11.0)] # From coordinate tuples ml = MultiLineString([coords1, coords2]) copy = MultiLineString(ml) self.assertIsInstance(copy, MultiLineString) self.assertEqual('MultiLineString', lgeos.GEOSGeomType(copy._geom).decode('ascii')) self.assertEqual(len(copy.geoms), 2) self.assertEqual(dump_coords(copy.geoms[0]), coords1) self.assertEqual(dump_coords(copy.geoms[1]), coords2) @unittest.skipIf(not numpy, 'Numpy required') def test_numpy(self): from numpy import array from numpy.testing import assert_array_equal # Construct from a numpy array geom = MultiLineString([array(((0.0, 0.0), (1.0, 2.0)))]) self.assertIsInstance(geom, MultiLineString) self.assertEqual(len(geom.geoms), 1) self.assertEqual(dump_coords(geom), [[(0.0, 0.0), (1.0, 2.0)]]) # Adapt a sequence of Numpy arrays to a multilinestring a = [array(((1.0, 2.0), (3.0, 4.0)))] geoma = asMultiLineString(a) assert_array_equal(geoma.context, [array([[1., 2.], [3., 4.]])]) self.assertEqual(dump_coords(geoma), [[(1.0, 2.0), (3.0, 4.0)]]) # TODO: is there an inverse? def test_subgeom_access(self): line0 = LineString([(0.0, 1.0), (2.0, 3.0)]) line1 = LineString([(4.0, 5.0), (6.0, 7.0)]) self.subgeom_access_test(MultiLineString, [line0, line1]) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(MultiLineStringTestCase) Shapely-1.5.13/tests/test_multipoint.py000066400000000000000000000055131260610516500201730ustar00rootroot00000000000000from . import unittest, numpy, test_int_types from .test_multi import MultiGeometryTestCase from shapely.geometry import Point, MultiPoint, asMultiPoint from shapely.geometry.base import dump_coords class MultiPointTestCase(MultiGeometryTestCase): def test_multipoint(self): # From coordinate tuples geom = MultiPoint(((1.0, 2.0), (3.0, 4.0))) self.assertEqual(len(geom.geoms), 2) self.assertEqual(dump_coords(geom), [[(1.0, 2.0)], [(3.0, 4.0)]]) # From points geom = MultiPoint((Point(1.0, 2.0), Point(3.0, 4.0))) self.assertEqual(len(geom.geoms), 2) self.assertEqual(dump_coords(geom), [[(1.0, 2.0)], [(3.0, 4.0)]]) # From another multi-point geom2 = MultiPoint(geom) self.assertEqual(len(geom2.geoms), 2) self.assertEqual(dump_coords(geom2), [[(1.0, 2.0)], [(3.0, 4.0)]]) # Sub-geometry Access self.assertIsInstance(geom.geoms[0], Point) self.assertEqual(geom.geoms[0].x, 1.0) self.assertEqual(geom.geoms[0].y, 2.0) with self.assertRaises(IndexError): # index out of range geom.geoms[2] # Geo interface self.assertEqual(geom.__geo_interface__, {'type': 'MultiPoint', 'coordinates': ((1.0, 2.0), (3.0, 4.0))}) # Adapt a coordinate list to a line string coords = [[5.0, 6.0], [7.0, 8.0]] geoma = asMultiPoint(coords) self.assertEqual(dump_coords(geoma), [[(5.0, 6.0)], [(7.0, 8.0)]]) @unittest.skipIf(not numpy, 'Numpy required') def test_numpy(self): from numpy import array, asarray from numpy.testing import assert_array_equal # Construct from a numpy array geom = MultiPoint(array([[0.0, 0.0], [1.0, 2.0]])) self.assertIsInstance(geom, MultiPoint) self.assertEqual(len(geom.geoms), 2) self.assertEqual(dump_coords(geom), [[(0.0, 0.0)], [(1.0, 2.0)]]) # Geo interface (cont.) geom = MultiPoint((Point(1.0, 2.0), Point(3.0, 4.0))) assert_array_equal(array(geom), array([[1., 2.], [3., 4.]])) # Adapt a Numpy array to a multipoint a = array([[1.0, 2.0], [3.0, 4.0]]) geoma = asMultiPoint(a) assert_array_equal(geoma.context, array([[1., 2.], [3., 4.]])) self.assertEqual(dump_coords(geoma), [[(1.0, 2.0)], [(3.0, 4.0)]]) # Now, the inverse self.assertEqual(geoma.__array_interface__, geoma.context.__array_interface__) pas = asarray(geoma) assert_array_equal(pas, array([[1., 2.], [3., 4.]])) def test_subgeom_access(self): p0 = Point(1.0, 2.0) p1 = Point(3.0, 4.0) self.subgeom_access_test(MultiPoint, [p0, p1]) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(MultiPointTestCase) Shapely-1.5.13/tests/test_multipolygon.py000066400000000000000000000062271260610516500205340ustar00rootroot00000000000000from . import unittest, numpy, test_int_types from .test_multi import MultiGeometryTestCase from shapely.geometry import Polygon, MultiPolygon, asMultiPolygon from shapely.geometry.base import dump_coords class MultiPolygonTestCase(MultiGeometryTestCase): def test_multipolygon(self): # From coordinate tuples geom = MultiPolygon( [(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)), [((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))])]) self.assertIsInstance(geom, MultiPolygon) self.assertEqual(len(geom.geoms), 1) self.assertEqual( dump_coords(geom), [[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0), [(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)]]]) # Or from polygons p = Polygon(((0, 0), (0, 1), (1, 1), (1, 0)), [((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))]) geom = MultiPolygon([p]) self.assertEqual(len(geom.geoms), 1) self.assertEqual( dump_coords(geom), [[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0), [(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)]]]) # Or from another multi-polygon geom2 = MultiPolygon(geom) self.assertEqual(len(geom2.geoms), 1) self.assertEqual( dump_coords(geom2), [[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0), [(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)]]]) # Sub-geometry Access self.assertIsInstance(geom.geoms[0], Polygon) self.assertEqual( dump_coords(geom.geoms[0]), [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0), [(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)]]) with self.assertRaises(IndexError): # index out of range geom.geoms[1] # Geo interface self.assertEqual( geom.__geo_interface__, {'type': 'MultiPolygon', 'coordinates': [(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)), ((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)))]}) # Adapter coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)) holes_coords = [((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))] mpa = asMultiPolygon([(coords, holes_coords)]) self.assertEqual(len(mpa.geoms), 1) self.assertEqual(len(mpa.geoms[0].exterior.coords), 5) self.assertEqual(len(mpa.geoms[0].interiors), 1) self.assertEqual(len(mpa.geoms[0].interiors[0].coords), 5) def test_subgeom_access(self): poly0 = Polygon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]) poly1 = Polygon([(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25)]) self.subgeom_access_test(MultiPolygon, [poly0, poly1]) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(MultiPolygonTestCase) Shapely-1.5.13/tests/test_ndarrays.py000066400000000000000000000030711260610516500176070ustar00rootroot00000000000000# Tests of support for Numpy ndarrays. See # https://github.com/sgillies/shapely/issues/26 for discussion. # Requires numpy. import sys if sys.version_info[0] >= 3: from functools import reduce from . import unittest from shapely import geometry try: import numpy except ImportError: numpy = False class TransposeTestCase(unittest.TestCase): @unittest.skipIf(not numpy, 'numpy not installed') def test_multipoint(self): a = numpy.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]]) t = a.T s = geometry.asMultiPoint(t) coords = reduce(lambda x, y: x + y, [list(g.coords) for g in s]) self.assertEqual( coords, [(1.0, 3.0), (1.0, 4.0), (2.0, 4.0), (2.0, 3.0), (1.0, 3.0)] ) @unittest.skipIf(not numpy, 'numpy not installed') def test_linestring(self): a = numpy.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]]) t = a.T s = geometry.asLineString(t) self.assertEqual( list(s.coords), [(1.0, 3.0), (1.0, 4.0), (2.0, 4.0), (2.0, 3.0), (1.0, 3.0)] ) @unittest.skipIf(not numpy, 'numpy not installed') def test_polygon(self): a = numpy.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]]) t = a.T s = geometry.asPolygon(t) self.assertEqual( list(s.exterior.coords), [(1.0, 3.0), (1.0, 4.0), (2.0, 4.0), (2.0, 3.0), (1.0, 3.0)] ) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(TransposeTestCase) Shapely-1.5.13/tests/test_nearest.py000066400000000000000000000013011260610516500174170ustar00rootroot00000000000000from . import unittest from shapely.geometry import Point from shapely.geos import geos_version from shapely.ops import nearest_points @unittest.skipIf(geos_version < (3, 4, 0), 'GEOS 3.4.0 required') class Nearest(unittest.TestCase): def test_nearest(self): first, second = nearest_points( Point(0, 0).buffer(1.0), Point(3, 0).buffer(1.0)) self.assertAlmostEqual(first.x, 1.0, 7) self.assertAlmostEqual(second.x, 2.0, 7) self.assertAlmostEqual(first.y, 0.0, 7) self.assertAlmostEqual(second.y, 0.0, 7) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(Nearest) if __name__ == '__main__': unittest.main() Shapely-1.5.13/tests/test_operations.py000066400000000000000000000054151260610516500201530ustar00rootroot00000000000000from . import unittest import pytest from shapely.geometry import Point, Polygon, MultiPoint, GeometryCollection from shapely.wkt import loads from shapely.geos import TopologicalError class OperationsTestCase(unittest.TestCase): def test_operations(self): point = Point(0.0, 0.0) # General geometry self.assertEqual(point.area, 0.0) self.assertEqual(point.length, 0.0) self.assertAlmostEqual(point.distance(Point(-1.0, -1.0)), 1.4142135623730951) # Topology operations # Envelope self.assertIsInstance(point.envelope, Point) # Intersection self.assertIsInstance(point.intersection(Point(-1, -1)), GeometryCollection) # Buffer self.assertIsInstance(point.buffer(10.0), Polygon) self.assertIsInstance(point.buffer(10.0, 32), Polygon) # Simplify p = loads('POLYGON ((120 120, 121 121, 122 122, 220 120, 180 199, ' '160 200, 140 199, 120 120))') expected = loads('POLYGON ((120 120, 140 199, 160 200, 180 199, ' '220 120, 120 120))') s = p.simplify(10.0, preserve_topology=False) self.assertTrue(s.equals_exact(expected, 0.001)) p = loads('POLYGON ((80 200, 240 200, 240 60, 80 60, 80 200),' '(120 120, 220 120, 180 199, 160 200, 140 199, 120 120))') expected = loads( 'POLYGON ((80 200, 240 200, 240 60, 80 60, 80 200),' '(120 120, 220 120, 180 199, 160 200, 140 199, 120 120))') s = p.simplify(10.0, preserve_topology=True) self.assertTrue(s.equals_exact(expected, 0.001)) # Convex Hull self.assertIsInstance(point.convex_hull, Point) # Differences self.assertIsInstance(point.difference(Point(-1, 1)), Point) self.assertIsInstance(point.symmetric_difference(Point(-1, 1)), MultiPoint) # Boundary self.assertIsInstance(point.boundary, GeometryCollection) # Union self.assertIsInstance(point.union(Point(-1, 1)), MultiPoint) self.assertIsInstance(point.representative_point(), Point) self.assertIsInstance(point.centroid, Point) def test_relate(self): # Relate self.assertEqual(Point(0, 0).relate(Point(-1, -1)), 'FF0FFF0F2') # issue #294: should raise TopologicalError on exception invalid_polygon = loads('POLYGON ((40 100, 80 100, 80 60, 40 60, 40 100), (60 60, 80 60, 80 40, 60 40, 60 60))') assert(not invalid_polygon.is_valid) with pytest.raises(TopologicalError): invalid_polygon.relate(invalid_polygon) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(OperationsTestCase) Shapely-1.5.13/tests/test_operators.py000066400000000000000000000043501260610516500200030ustar00rootroot00000000000000from . import unittest from shapely.geometry import Point, MultiPoint, Polygon, LineString class OperatorsTestCase(unittest.TestCase): def test_point(self): point = Point(0, 0) point2 = Point(-1, 1) self.assertTrue(point.union(point2).equals(point | point2)) self.assertTrue((point & point2).is_empty) self.assertTrue(point.equals(point - point2)) self.assertTrue( point.symmetric_difference(point2).equals(point ^ point2)) self.assertNotEqual(point, point2) point_dupe = Point(0, 0) self.assertEqual(point, point_dupe) def test_multipoint(self): mp1 = MultiPoint(((0, 0), (1, 1))) mp1_dup = MultiPoint(((0, 0), (1, 1))) mp1_rev = MultiPoint(((1, 1), (0, 0))) mp2 = MultiPoint(((0, 0), (1, 1), (2, 2))) mp3 = MultiPoint(((0, 0), (1, 1), (2, 3))) self.assertEqual(mp1, mp1_dup) self.assertNotEqual(mp1, mp1_rev) # is this correct? self.assertNotEqual(mp1, mp2) self.assertNotEqual(mp2, mp3) p = Point(0, 0) mp = MultiPoint([(0, 0)]) self.assertNotEqual(p, mp) self.assertNotEqual(mp, p) def test_polygon(self): shell = ((0, 0), (3, 0), (3, 3), (0, 3)) hole = ((1, 1), (2, 1), (2, 2), (1, 2)) p_solid = Polygon(shell) p2_solid = Polygon(shell) p_hole = Polygon(shell, holes=[hole]) p2_hole = Polygon(shell, holes=[hole]) self.assertEqual(p_solid, p2_solid) self.assertEqual(p_hole, p2_hole) self.assertNotEqual(p_solid, p_hole) shell2 = ((-5, 2), (10.5, 3), (7, 3)) p3_hole = Polygon(shell2, holes=[hole]) self.assertNotEqual(p_hole, p3_hole) def test_linestring(self): line1 = LineString([(0,0), (1,1), (2,2)]) line2 = LineString([(0,0), (2,2)]) line2_dup = LineString([(0,0), (2,2)]) # .equals() indicates these are the same self.assertTrue(line1.equals(line2)) # but == indicates these are different self.assertNotEqual(line1, line2) # but dupes are the same with == self.assertEqual(line2, line2_dup) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(OperatorsTestCase) Shapely-1.5.13/tests/test_parallel_offset.py000066400000000000000000000031351260610516500211270ustar00rootroot00000000000000from . import unittest from shapely.geos import geos_version from shapely.geometry import LineString, LinearRing from shapely.wkt import loads class OperationsTestCase(unittest.TestCase): @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_parallel_offset_linestring(self): line1 = LineString([(0, 0), (10, 0)]) left = line1.parallel_offset(5, 'left') self.assertEqual(left, LineString([(0, 5), (10, 5)])) right = line1.parallel_offset(5, 'right') self.assertEqual(right, LineString([(10, -5), (0, -5)])) right = line1.parallel_offset(-5, 'left') self.assertEqual(right, LineString([(10, -5), (0, -5)])) left = line1.parallel_offset(-5, 'right') self.assertEqual(left, LineString([(0, 5), (10, 5)])) # by default, parallel_offset is right-handed self.assertEqual(line1.parallel_offset(5), right) line2 = LineString([(0, 0), (5, 0), (5, -5)]) self.assertEqual(line2.parallel_offset(2, 'left', resolution=1), LineString([(0, 2), (5, 2), (7, 0), (7, -5)])) self.assertEqual(line2.parallel_offset(2, 'left', join_style=2, resolution=1), LineString([(0, 2), (7, 2), (7, -5)])) @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required') def test_parallel_offset_linear_ring(self): lr1 = LinearRing([(0, 0), (5, 0), (5, 5), (0, 5), (0, 0)]) self.assertEqual(lr1.parallel_offset(2, 'left', resolution=1), LineString([(2, 2), (3, 2), (3, 3), (2, 3), (2, 2)])) Shapely-1.5.13/tests/test_persist.py000066400000000000000000000042261260610516500174600ustar00rootroot00000000000000"""Persistence tests """ from . import unittest import pickle from shapely import wkb, wkt from shapely.geometry import Point import struct class PersistTestCase(unittest.TestCase): @staticmethod def _byte(i): """Convert an integer in the range [0, 256) to a byte.""" if bytes == str: # Python 2 return chr(i) else: # Python 3 return int(i) def test_pickle(self): p = Point(0.0, 0.0) data = pickle.dumps(p) q = pickle.loads(data) self.assertTrue(q.equals(p)) def test_wkb(self): p = Point(0.0, 0.0) wkb_big_endian = wkb.dumps(p, big_endian=True) wkb_little_endian = wkb.dumps(p, big_endian=False) # Regardless of byte order, loads ought to correctly recover the # geometry self.assertTrue(p.equals(wkb.loads(wkb_big_endian))) self.assertTrue(p.equals(wkb.loads(wkb_little_endian))) def test_wkb_dumps_endianness(self): p = Point(0.5, 2.0) wkb_big_endian = wkb.dumps(p, big_endian=True) wkb_little_endian = wkb.dumps(p, big_endian=False) self.assertNotEqual(wkb_big_endian, wkb_little_endian) # According to WKB specification in section 3.3 of OpenGIS # Simple Features Specification for SQL, revision 1.1, the # first byte of a WKB representation indicates byte order. # Big-endian is 0, little-endian is 1. self.assertEqual(wkb_big_endian[0], self._byte(0)) self.assertEqual(wkb_little_endian[0], self._byte(1)) # Check that the doubles (0.5, 2.0) are in correct byte order double_size = struct.calcsize('d') self.assertEqual( wkb_big_endian[(-2 * double_size):], struct.pack('>2d', p.x, p.y)) self.assertEqual( wkb_little_endian[(-2 * double_size):], struct.pack('<2d', p.x, p.y)) def test_wkt(self): p = Point(0.0, 0.0) text = wkt.dumps(p) self.assertTrue(text.startswith('POINT')) pt = wkt.loads(text) self.assertTrue(pt.equals(p)) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(PersistTestCase) Shapely-1.5.13/tests/test_pickle.py000066400000000000000000000011021260610516500172240ustar00rootroot00000000000000from . import unittest from shapely import geometry import sys if sys.version_info[0] >= 3: from pickle import dumps, loads, HIGHEST_PROTOCOL else: from cPickle import dumps, loads, HIGHEST_PROTOCOL class TwoDeeTestCase(unittest.TestCase): def test_linestring(self): l = geometry.LineString(((0.0, 0.0), (0.0, 1.0), (1.0, 1.0))) self.assertEqual(l._ndim, 2) s = dumps(l, HIGHEST_PROTOCOL) t = loads(s) self.assertEqual(t._ndim, 2) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(TwoDeeTestCase) Shapely-1.5.13/tests/test_point.py000066400000000000000000000105661260610516500171240ustar00rootroot00000000000000from . import unittest, numpy from shapely.geometry import Point, asPoint from shapely.geos import DimensionError class LineStringTestCase(unittest.TestCase): def test_point(self): # Test 2D points p = Point(1.0, 2.0) self.assertEqual(p.x, 1.0) self.assertEqual(p.y, 2.0) self.assertEqual(p.coords[:], [(1.0, 2.0)]) self.assertEqual(str(p), p.wkt) self.assertFalse(p.has_z) with self.assertRaises(DimensionError): p.z # Check 3D p = Point(1.0, 2.0, 3.0) self.assertEqual(p.coords[:], [(1.0, 2.0, 3.0)]) self.assertEqual(str(p), p.wkt) self.assertTrue(p.has_z) self.assertEqual(p.z, 3.0) # From coordinate sequence p = Point((3.0, 4.0)) self.assertEqual(p.coords[:], [(3.0, 4.0)]) # From another point q = Point(p) self.assertEqual(q.coords[:], [(3.0, 4.0)]) # Coordinate access self.assertEqual(p.x, 3.0) self.assertEqual(p.y, 4.0) self.assertEqual(tuple(p.coords), ((3.0, 4.0),)) self.assertEqual(p.coords[0], (3.0, 4.0)) with self.assertRaises(IndexError): # index out of range p.coords[1] # Bounds self.assertEqual(p.bounds, (3.0, 4.0, 3.0, 4.0)) # Geo interface self.assertEqual(p.__geo_interface__, {'type': 'Point', 'coordinates': (3.0, 4.0)}) # Modify coordinates p.coords = (2.0, 1.0) self.assertEqual(p.__geo_interface__, {'type': 'Point', 'coordinates': (2.0, 1.0)}) # Alternate method p.coords = ((0.0, 0.0),) self.assertEqual(p.__geo_interface__, {'type': 'Point', 'coordinates': (0.0, 0.0)}) # Adapt a coordinate list to a point coords = [3.0, 4.0] pa = asPoint(coords) self.assertEqual(pa.coords[0], (3.0, 4.0)) self.assertEqual(pa.distance(p), 5.0) # Move the coordinates and watch the distance change coords[0] = 1.0 self.assertEqual(pa.coords[0], (1.0, 4.0)) self.assertAlmostEqual(pa.distance(p), 4.123105625617661) # Test Non-operability of Null geometry p_null = Point() self.assertEqual(p_null.wkt, 'GEOMETRYCOLLECTION EMPTY') self.assertEqual(p_null.coords[:], []) self.assertEqual(p_null.area, 0.0) # Check that we can set coordinates of a null geometry p_null.coords = (1, 2) self.assertEqual(p_null.coords[:], [(1.0, 2.0)]) @unittest.skipIf(not numpy, 'Numpy required') def test_numpy(self): from numpy import array, asarray from numpy.testing import assert_array_equal # Construct from a numpy array p = Point(array([1.0, 2.0])) self.assertEqual(p.coords[:], [(1.0, 2.0)]) # Adapt a Numpy array to a point a = array([1.0, 2.0]) pa = asPoint(a) assert_array_equal(pa.context, array([1.0, 2.0])) self.assertEqual(pa.coords[:], [(1.0, 2.0)]) # Now, the inverse self.assertEqual(pa.__array_interface__, pa.context.__array_interface__) pas = asarray(pa) assert_array_equal(pas, array([1.0, 2.0])) # Adapt a coordinate list to a point coords = [3.0, 4.0] pa = asPoint(coords) coords[0] = 1.0 # Now, the inverse (again?) self.assertIsNotNone(pa.__array_interface__) pas = asarray(pa) assert_array_equal(pas, array([1.0, 4.0])) # From Array.txt p = Point(0.0, 0.0, 1.0) coords = p.coords[0] self.assertEqual(coords, (0.0, 0.0, 1.0)) self.assertIsNotNone(p.ctypes) # Convert to Numpy array, passing through Python sequence a = asarray(coords) self.assertEqual(a.ndim, 1) self.assertEqual(a.size, 3) self.assertEqual(a.shape, (3,)) # Convert to Numpy array, passing through a ctypes array b = asarray(p) self.assertEqual(b.size, 3) self.assertEqual(b.shape, (3,)) assert_array_equal(b, array([0.0, 0.0, 1.0])) # Make a point from a Numpy array a = asarray([1.0, 1.0, 0.0]) p = Point(*list(a)) self.assertEqual(p.coords[:], [(1.0, 1.0, 0.0)]) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(LineStringTestCase) Shapely-1.5.13/tests/test_polygon.py000066400000000000000000000203251260610516500174540ustar00rootroot00000000000000"""Polygons and Linear Rings """ from . import unittest, numpy from shapely.wkb import loads as load_wkb from shapely.geos import lgeos from shapely.geometry import Point, Polygon, asPolygon from shapely.geometry.polygon import LinearRing, LineString, asLinearRing from shapely.geometry.base import dump_coords class PolygonTestCase(unittest.TestCase): def test_polygon(self): # Initialization # Linear rings won't usually be created by users, but by polygons coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)) ring = LinearRing(coords) self.assertEqual(len(ring.coords), 5) self.assertEqual(ring.coords[0], ring.coords[4]) self.assertEqual(ring.coords[0], ring.coords[-1]) self.assertTrue(ring.is_ring) # Coordinate modification ring.coords = ((0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0)) self.assertEqual( ring.__geo_interface__, {'type': 'LinearRing', 'coordinates': ((0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0))}) # Test ring adapter coords = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]] ra = asLinearRing(coords) self.assertTrue(ra.wkt.upper().startswith('LINEARRING')) self.assertEqual(dump_coords(ra), [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]) coords[3] = [2.0, -1.0] self.assertEqual(dump_coords(ra), [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, -1.0), (0.0, 0.0)]) # Construct a polygon, exterior ring only polygon = Polygon(coords) self.assertEqual(len(polygon.exterior.coords), 5) # Ring Access self.assertIsInstance(polygon.exterior, LinearRing) ring = polygon.exterior self.assertEqual(len(ring.coords), 5) self.assertEqual(ring.coords[0], ring.coords[4]) self.assertEqual(ring.coords[0], (0., 0.)) self.assertTrue(ring.is_ring) self.assertEqual(len(polygon.interiors), 0) # Create a new polygon from WKB data = polygon.wkb polygon = None ring = None polygon = load_wkb(data) ring = polygon.exterior self.assertEqual(len(ring.coords), 5) self.assertEqual(ring.coords[0], ring.coords[4]) self.assertEqual(ring.coords[0], (0., 0.)) self.assertTrue(ring.is_ring) polygon = None # Interior rings (holes) polygon = Polygon(coords, [((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))]) self.assertEqual(len(polygon.exterior.coords), 5) self.assertEqual(len(polygon.interiors[0].coords), 5) with self.assertRaises(IndexError): # index out of range polygon.interiors[1] # Test from another Polygon copy = Polygon(polygon) self.assertEqual(len(polygon.exterior.coords), 5) self.assertEqual(len(polygon.interiors[0].coords), 5) with self.assertRaises(IndexError): # index out of range polygon.interiors[1] # Coordinate getters and setters raise exceptions self.assertRaises(NotImplementedError, polygon._get_coords) with self.assertRaises(NotImplementedError): polygon.coords # Geo interface self.assertEqual( polygon.__geo_interface__, {'type': 'Polygon', 'coordinates': (((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, -1.0), (0.0, 0.0)), ((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25), (0.25, 0.25)))}) # Adapter hole_coords = [((0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25))] pa = asPolygon(coords, hole_coords) self.assertEqual(len(pa.exterior.coords), 5) self.assertEqual(len(pa.interiors), 1) self.assertEqual(len(pa.interiors[0].coords), 5) # Test Non-operability of Null rings r_null = LinearRing() self.assertEqual(r_null.wkt, 'GEOMETRYCOLLECTION EMPTY') self.assertEqual(r_null.length, 0.0) # Check that we can set coordinates of a null geometry r_null.coords = [(0, 0), (1, 1), (1, 0)] self.assertAlmostEqual(r_null.length, 3.414213562373095) # Error handling with self.assertRaises(ValueError): # A LinearRing must have at least 3 coordinate tuples Polygon([[1, 2], [2, 3]]) def test_linearring_from_closed_linestring(self): coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] line = LineString(coords) ring = LinearRing(line) self.assertEqual(len(ring.coords), 4) self.assertEqual(ring.coords[:], coords) self.assertEqual('LinearRing', lgeos.GEOSGeomType(ring._geom).decode('ascii')) def test_linearring_from_unclosed_linestring(self): coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] line = LineString(coords[:-1]) # Pass in unclosed line ring = LinearRing(line) self.assertEqual(len(ring.coords), 4) self.assertEqual(ring.coords[:], coords) self.assertEqual('LinearRing', lgeos.GEOSGeomType(ring._geom).decode('ascii')) @unittest.skipIf(not numpy, 'Numpy required') def test_numpy(self): from numpy import array, asarray from numpy.testing import assert_array_equal a = asarray(((0., 0.), (0., 1.), (1., 1.), (1., 0.), (0., 0.))) polygon = Polygon(a) self.assertEqual(len(polygon.exterior.coords), 5) self.assertEqual(dump_coords(polygon.exterior), [(0., 0.), (0., 1.), (1., 1.), (1., 0.), (0., 0.)]) self.assertEqual(len(polygon.interiors), 0) b = asarray(polygon.exterior) self.assertEqual(b.shape, (5, 2)) assert_array_equal( b, array([(0., 0.), (0., 1.), (1., 1.), (1., 0.), (0., 0.)])) def test_dimensions(self): # Background: see http://trac.gispython.org/lab/ticket/168 # http://lists.gispython.org/pipermail/community/2008-August/001859.html coords = ((0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0), (1.0, 0.0, 0.0)) polygon = Polygon(coords) self.assertEqual(polygon._ndim, 3) gi = polygon.__geo_interface__ self.assertEqual( gi['coordinates'], (((0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 0.0)),)) e = polygon.exterior self.assertEqual(e._ndim, 3) gi = e.__geo_interface__ self.assertEqual( gi['coordinates'], ((0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 0.0))) def test_attribute_chains(self): # Attribute Chaining # See also ticket #151. p = Polygon(((0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0))) self.assertEqual( list(p.boundary.coords), [(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0), (0.0, 0.0)]) ec = list(Point(0.0, 0.0).buffer(1.0, 1).exterior.coords) self.assertIsInstance(ec, list) # TODO: this is a poor test # Test chained access to interiors p = Polygon( ((0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)), [((-0.25, 0.25), (-0.25, 0.75), (-0.75, 0.75), (-0.75, 0.25))] ) self.assertEqual(p.area, 0.75) """Not so much testing the exact values here, which are the responsibility of the geometry engine (GEOS), but that we can get chain functions and properties using anonymous references. """ self.assertEqual( list(p.interiors[0].coords), [(-0.25, 0.25), (-0.25, 0.75), (-0.75, 0.75), (-0.75, 0.25), (-0.25, 0.25)]) xy = list(p.interiors[0].buffer(1).exterior.coords)[0] self.assertEqual(len(xy), 2) # Test multiple operators, boundary of a buffer ec = list(p.buffer(1).boundary.coords) self.assertIsInstance(ec, list) # TODO: this is a poor test def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(PolygonTestCase) Shapely-1.5.13/tests/test_polygonize.py000077500000000000000000000031661260610516500201730ustar00rootroot00000000000000from . import unittest from shapely.geos import geos_version from shapely.geometry import Point, LineString, Polygon from shapely.geometry.base import dump_coords from shapely.ops import polygonize, polygonize_full class PolygonizeTestCase(unittest.TestCase): def test_polygonize(self): lines = [ LineString(((0, 0), (1, 1))), LineString(((0, 0), (0, 1))), LineString(((0, 1), (1, 1))), LineString(((1, 1), (1, 0))), LineString(((1, 0), (0, 0))), LineString(((5, 5), (6, 6))), Point(0, 0), ] result = list(polygonize(lines)) self.assertTrue(all([isinstance(x, Polygon) for x in result])) @unittest.skipIf(geos_version < (3, 3, 0), 'GEOS 3.3.0 required') def test_polygonize_full(self): lines2 = [ ((0, 0), (1, 1)), ((0, 0), (0, 1)), ((0, 1), (1, 1)), ((1, 1), (1, 0)), ((1, 0), (0, 0)), ((5, 5), (6, 6)), ((1, 1), (100, 100)), ] result2, dangles, cuts, invalids = polygonize_full(lines2) self.assertEqual(len(result2), 2) self.assertTrue(all([isinstance(x, Polygon) for x in result2])) self.assertEqual(list(dangles.geoms), []) self.assertTrue(all([isinstance(x, LineString) for x in cuts.geoms])) self.assertEqual( dump_coords(cuts), [[(1.0, 1.0), (100.0, 100.0)], [(5.0, 5.0), (6.0, 6.0)]]) self.assertEqual(list(invalids.geoms), []) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(PolygonizeTestCase) Shapely-1.5.13/tests/test_predicates.py000066400000000000000000000047121260610516500201120ustar00rootroot00000000000000"""Test GEOS predicates """ from . import unittest from shapely.geometry import Point, Polygon from shapely.geos import TopologicalError, PredicateError import pytest class PredicatesTestCase(unittest.TestCase): def test_binary_predicates(self): point = Point(0.0, 0.0) self.assertTrue(point.disjoint(Point(-1.0, -1.0))) self.assertFalse(point.touches(Point(-1.0, -1.0))) self.assertFalse(point.crosses(Point(-1.0, -1.0))) self.assertFalse(point.within(Point(-1.0, -1.0))) self.assertFalse(point.contains(Point(-1.0, -1.0))) self.assertFalse(point.equals(Point(-1.0, -1.0))) self.assertFalse(point.touches(Point(-1.0, -1.0))) self.assertTrue(point.equals(Point(0.0, 0.0))) self.assertTrue(point.covers(Point(0.0, 0.0))) self.assertFalse(point.covers(Point(-1.0, -1.0))) def test_unary_predicates(self): point = Point(0.0, 0.0) self.assertFalse(point.is_empty) self.assertTrue(point.is_valid) self.assertTrue(point.is_simple) self.assertFalse(point.is_ring) self.assertFalse(point.has_z) def test_binary_predicate_exceptions(self): p1 = [(339, 346), (459,346), (399,311), (340, 277), (399, 173), (280, 242), (339, 415), (280, 381), (460, 207), (339, 346)] p2 = [(339, 207), (280, 311), (460, 138), (399, 242), (459, 277), (459, 415), (399, 381), (519, 311), (520, 242), (519, 173), (399, 450), (339, 207)] self.assertRaises(TopologicalError, Polygon(p1).within, Polygon(p2)) def test_relate_pattern(self): # a pair of partially overlapping polygons, and a nearby point g1 = Polygon([(0, 0), (0, 1), (3, 1), (3, 0), (0, 0)]) g2 = Polygon([(1, -1), (1, 2), (2, 2), (2, -1), (1, -1)]) g3 = Point(5, 5) assert(g1.relate(g2) == '212101212') assert(g1.relate_pattern(g2, '212101212')) assert(g1.relate_pattern(g2, '*********')) assert(g1.relate_pattern(g2, '2********')) assert(g1.relate_pattern(g2, 'T********')) assert(not g1.relate_pattern(g2, '112101212')) assert(not g1.relate_pattern(g2, '1********')) assert(g1.relate_pattern(g3, 'FF2FF10F2')) # an invalid pattern should raise an exception with pytest.raises(PredicateError): g1.relate_pattern(g2, 'fail') def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(PredicatesTestCase) Shapely-1.5.13/tests/test_prepared.py000066400000000000000000000045531260610516500175740ustar00rootroot00000000000000from . import unittest from shapely.geos import geos_version from shapely import prepared from shapely import geometry class PreparedGeometryTestCase(unittest.TestCase): @unittest.skipIf(geos_version < (3, 1, 0), 'GEOS 3.1.0 required') def test_prepared(self): polygon = geometry.Polygon([ (0, 0), (1, 0), (1, 1), (0, 1) ]) p = prepared.PreparedGeometry(polygon) self.assertTrue(p.contains(geometry.Point(0.5, 0.5))) self.assertFalse(p.contains(geometry.Point(0.5, 1.5))) @unittest.skipIf(geos_version < (3, 1, 0), 'GEOS 3.1.0 required') def test_op_not_allowed(self): p = prepared.PreparedGeometry(geometry.Point(0.0, 0.0).buffer(1.0)) self.assertRaises(ValueError, geometry.Point(0.0, 0.0).union, p) @unittest.skipIf(geos_version < (3, 1, 0), 'GEOS 3.1.0 required') def test_predicate_not_allowed(self): p = prepared.PreparedGeometry(geometry.Point(0.0, 0.0).buffer(1.0)) self.assertRaises(ValueError, geometry.Point(0.0, 0.0).contains, p) @unittest.skipIf(geos_version < (3, 1, 0), 'GEOS 3.1.0 required') def test_prepared_predicates(self): # check prepared predicates give the same result as regular predicates polygon1 = geometry.Polygon([ (0, 0), (0, 1), (1, 1), (1, 0), (0, 0) ]) polygon2 = geometry.Polygon([ (0.5, 0.5), (1.5, 0.5), (1.0, 1.0), (0.5, 0.5) ]) point2 = geometry.Point(0.5, 0.5) polygon_empty = geometry.Polygon() prepared_polygon1 = prepared.PreparedGeometry(polygon1) for geom2 in (polygon2, point2, polygon_empty): self.assertTrue(polygon1.disjoint(geom2) == prepared_polygon1.disjoint(geom2)) self.assertTrue(polygon1.touches(geom2) == prepared_polygon1.touches(geom2)) self.assertTrue(polygon1.intersects(geom2) == prepared_polygon1.intersects(geom2)) self.assertTrue(polygon1.crosses(geom2) == prepared_polygon1.crosses(geom2)) self.assertTrue(polygon1.within(geom2) == prepared_polygon1.within(geom2)) self.assertTrue(polygon1.contains(geom2) == prepared_polygon1.contains(geom2)) self.assertTrue(polygon1.overlaps(geom2) == prepared_polygon1.overlaps(geom2)) def test_suite(): loader = unittest.TestLoader() return loader.loadTestsFromTestCase(PreparedGeometryTestCase) Shapely-1.5.13/tests/test_products_z.py000066400000000000000000000010051260610516500201530ustar00rootroot00000000000000from . import unittest from shapely.geometry import LineString class ProductZTestCase(unittest.TestCase): def test_line_intersection(self): line1 = LineString([(0, 0, 0), (1, 1, 1)]) line2 = LineString([(0, 1, 1), (1, 0, 0)]) interxn = line1.intersection(line2) self.assertTrue(interxn.has_z) self.assertEqual(interxn._ndim, 3) self.assertTrue(0.0 <= interxn.z <= 1.0) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(ProductZTestCase) Shapely-1.5.13/tests/test_singularity.py000066400000000000000000000007621260610516500203420ustar00rootroot00000000000000from . import unittest from shapely.geometry import Polygon class PolygonTestCase(unittest.TestCase): def test_polygon_3(self): p = (1.0, 1.0) poly = Polygon([p, p, p]) self.assertEqual(poly.bounds, (1.0, 1.0, 1.0, 1.0)) def test_polygon_5(self): p = (1.0, 1.0) poly = Polygon([p, p, p, p, p]) self.assertEqual(poly.bounds, (1.0, 1.0, 1.0, 1.0)) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(PolygonTestCase) Shapely-1.5.13/tests/test_snap.py000066400000000000000000000020551260610516500167260ustar00rootroot00000000000000from . import unittest from shapely.geometry import LineString, Polygon from shapely.geos import geos_version from shapely.ops import snap @unittest.skipIf(geos_version < (3, 3, 0), 'GEOS 3.3.0 required') class Snap(unittest.TestCase): def test_snap(self): # input geometries square = Polygon([(1,1), (2, 1), (2, 2), (1, 2), (1, 1)]) line = LineString([(0,0), (0.8, 0.8), (1.8, 0.95), (2.6, 0.5)]) square_coords = square.exterior.coords[:] line_coords = line.coords[:] result = snap(line, square, 0.5) # test result is correct self.assertTrue(isinstance(result, LineString)) self.assertEqual(result.coords[:], [(0.0, 0.0), (1.0, 1.0), (2.0, 1.0), (2.6, 0.5)]) # test inputs have not been modified self.assertEqual(square.exterior.coords[:], square_coords) self.assertEqual(line.coords[:], line_coords) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(Snap) if __name__ == '__main__': unittest.main() Shapely-1.5.13/tests/test_strtree.py000066400000000000000000000012411260610516500174510ustar00rootroot00000000000000from . import unittest from shapely.strtree import STRtree from shapely.geometry import Point from shapely.geos import geos_version @unittest.skipIf(geos_version < (3, 4, 2), 'GEOS 3.4.2 required') class STRTestCase(unittest.TestCase): def test_query(self): points = [Point(i, i) for i in range(10)] tree = STRtree(points) results = tree.query(Point(2,2).buffer(0.99)) self.assertEqual(len(results), 1) results = tree.query(Point(2,2).buffer(1.0)) self.assertEqual(len(results), 3) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(STRTestCase) if __name__ == '__main__': unittest.main() Shapely-1.5.13/tests/test_styles.py000066400000000000000000000010531260610516500173050ustar00rootroot00000000000000from . import unittest from shapely.geometry import CAP_STYLE, JOIN_STYLE class StylesTest(unittest.TestCase): def test_cap(self): self.assertEqual(CAP_STYLE.round, 1) self.assertEqual(CAP_STYLE.flat, 2) self.assertEqual(CAP_STYLE.square, 3) def test_join(self): self.assertEqual(JOIN_STYLE.round, 1) self.assertEqual(JOIN_STYLE.mitre, 2) self.assertEqual(JOIN_STYLE.bevel, 3) def test_suite(): return unittest.TestSuite([ unittest.TestLoader().loadTestsFromTestCase(StylesTest)]) Shapely-1.5.13/tests/test_svg.py000066400000000000000000000176061260610516500165740ustar00rootroot00000000000000# Tests SVG output and validity import os from xml.dom.minidom import parseString as parse_xml_string from . import unittest from shapely.geometry import Point, MultiPoint, LineString, MultiLineString,\ Polygon, MultiPolygon from shapely.geometry.collection import GeometryCollection class SvgTestCase(unittest.TestCase): def assertSVG(self, geom, expected, **kwrds): """Helper function to check XML and debug SVG""" svg_elem = geom.svg(**kwrds) try: parse_xml_string(svg_elem) except: raise AssertionError( 'XML is not valid for SVG element: ' + str(svg_elem)) svg_doc = geom._repr_svg_() try: doc = parse_xml_string(svg_doc) except: raise AssertionError( 'XML is not valid for SVG doucment: ' + str(svg_doc)) svg_output_dir = None # svg_output_dir = '.' # useful for debugging SVG files if svg_output_dir: fname = geom.type if geom.is_empty: fname += '_empty' if not geom.is_valid: fname += '_invalid' if kwrds: fname += '_' + \ ','.join(str(k) + '=' + str(kwrds[k]) for k in kwrds) svg_path = os.path.join(svg_output_dir, fname + '.svg') with open(svg_path, 'w') as fp: fp.write(doc.toprettyxml()) self.assertEqual(svg_elem, expected) def test_point(self): # Empty self.assertSVG(Point(), '') # Valid g = Point(6, 7) self.assertSVG( g, '') self.assertSVG( g, '', scale_factor=5) def test_multipoint(self): # Empty self.assertSVG(MultiPoint(), '') # Valid g = MultiPoint([(6, 7), (3, 4)]) self.assertSVG( g, '' '') self.assertSVG( g, '' '', scale_factor=5) def test_linestring(self): # Empty self.assertSVG(LineString(), '') # Valid g = LineString([(5, 8), (496, -6), (530, 20)]) self.assertSVG( g, '') self.assertSVG( g, '', scale_factor=5) # Invalid self.assertSVG( LineString([(0, 0), (0, 0)]), '') def test_multilinestring(self): # Empty self.assertSVG(MultiLineString(), '') # Valid self.assertSVG( MultiLineString([[(6, 7), (3, 4)], [(2, 8), (9, 1)]]), '' '') # Invalid self.assertSVG( MultiLineString([[(2, 3), (2, 3)], [(2, 8), (9, 1)]]), '' '') def test_polygon(self): # Empty self.assertSVG(Polygon(), '') # Valid g = Polygon([(35, 10), (45, 45), (15, 40), (10, 20), (35, 10)], [[(20, 30), (35, 35), (30, 20), (20, 30)]]) self.assertSVG( g, '') self.assertSVG( g, '', scale_factor=5) # Invalid self.assertSVG( Polygon([(0, 40), (0, 0), (40, 40), (40, 0), (0, 40)]), '') def test_multipolygon(self): # Empty self.assertSVG(MultiPolygon(), '') # Valid self.assertSVG( MultiPolygon([ Polygon([(40, 40), (20, 45), (45, 30), (40, 40)]), Polygon([(20, 35), (10, 30), (10, 10), (30, 5), (45, 20), (20, 35)], [[(30, 20), (20, 15), (20, 25), (30, 20)]]) ]), '' '') # Invalid self.assertSVG( MultiPolygon([ Polygon([(140, 140), (120, 145), (145, 130), (140, 140)]), Polygon([(0, 40), (0, 0), (40, 40), (40, 0), (0, 40)]) ]), '' '') def test_collection(self): # Empty self.assertSVG(GeometryCollection(), '') # Valid self.assertSVG( Point(7, 3).union(LineString([(4, 2), (8, 4)])), '' '') # Invalid self.assertSVG( Point(7, 3).union(LineString([(4, 2), (4, 2)])), '' '') def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(SvgTestCase) Shapely-1.5.13/tests/test_transform.py000066400000000000000000000057101260610516500200010ustar00rootroot00000000000000from . import unittest from shapely import geometry from shapely.ops import transform class IdentityTestCase(unittest.TestCase): """New geometry/coordseq method 'xy' makes numpy interop easier""" def func(self, x, y, z=None): return tuple([c for c in [x, y, z] if c]) def test_empty(self): g = geometry.Point() h = transform(self.func, g) self.assertTrue(h.is_empty) def test_point(self): g = geometry.Point(0, 1) h = transform(self.func, g) self.assertEqual(h.geom_type, 'Point') self.assertEqual(list(h.coords), [(0, 1)]) def test_line(self): g = geometry.LineString(((0, 1), (2, 3))) h = transform(self.func, g) self.assertEqual(h.geom_type, 'LineString') self.assertEqual(list(h.coords), [(0, 1), (2, 3)]) def test_linearring(self): g = geometry.LinearRing(((0, 1), (2, 3), (2, 2), (0, 1))) h = transform(self.func, g) self.assertEqual(h.geom_type, 'LinearRing') self.assertEqual(list(h.coords), [(0, 1), (2, 3), (2, 2), (0, 1)]) def test_polygon(self): g = geometry.Point(0, 1).buffer(1.0) h = transform(self.func, g) self.assertEqual(h.geom_type, 'Polygon') self.assertAlmostEqual(g.area, h.area) def test_multipolygon(self): g = geometry.MultiPoint([(0, 1), (0, 4)]).buffer(1.0) h = transform(self.func, g) self.assertEqual(h.geom_type, 'MultiPolygon') self.assertAlmostEqual(g.area, h.area) class LambdaTestCase(unittest.TestCase): """New geometry/coordseq method 'xy' makes numpy interop easier""" def test_point(self): g = geometry.Point(0, 1) h = transform(lambda x, y, z=None: (x+1.0, y+1.0), g) self.assertEqual(h.geom_type, 'Point') self.assertEqual(list(h.coords), [(1.0, 2.0)]) def test_line(self): g = geometry.LineString(((0, 1), (2, 3))) h = transform(lambda x, y, z=None: (x+1.0, y+1.0), g) self.assertEqual(h.geom_type, 'LineString') self.assertEqual(list(h.coords), [(1.0, 2.0), (3.0, 4.0)]) def test_polygon(self): g = geometry.Point(0, 1).buffer(1.0) h = transform(lambda x, y, z=None: (x+1.0, y+1.0), g) self.assertEqual(h.geom_type, 'Polygon') self.assertAlmostEqual(g.area, h.area) self.assertAlmostEqual(h.centroid.x, 1.0) self.assertAlmostEqual(h.centroid.y, 2.0) def test_multipolygon(self): g = geometry.MultiPoint([(0, 1), (0, 4)]).buffer(1.0) h = transform(lambda x, y, z=None: (x+1.0, y+1.0), g) self.assertEqual(h.geom_type, 'MultiPolygon') self.assertAlmostEqual(g.area, h.area) self.assertAlmostEqual(h.centroid.x, 1.0) self.assertAlmostEqual(h.centroid.y, 3.5) def test_suite(): loader = unittest.TestLoader() return unittest.TestSuite([ loader.loadTestsFromTestCase(IdentityTestCase), loader.loadTestsFromTestCase(LambdaTestCase)]) Shapely-1.5.13/tests/test_union.py000066400000000000000000000046261260610516500171230ustar00rootroot00000000000000from . import unittest import random from itertools import islice from shapely.geos import geos_version from shapely.ftools import partial from shapely.geometry import Point, MultiPolygon from shapely.ops import cascaded_union, unary_union def halton(base): """Returns an iterator over an infinite Halton sequence""" def value(index): result = 0.0 f = 1.0 / base i = index while i > 0: result += f * (i % base) i = i // base f = f / base return result i = 1 while i > 0: yield value(i) i += 1 class UnionTestCase(unittest.TestCase): def test_cascaded_union(self): # Use a partial function to make 100 points uniformly distributed # in a 40x40 box centered on 0,0. r = partial(random.uniform, -20.0, 20.0) points = [Point(r(), r()) for i in range(100)] # Buffer the points, producing 100 polygon spots spots = [p.buffer(2.5) for p in points] # Perform a cascaded union of the polygon spots, dissolving them # into a collection of polygon patches u = cascaded_union(spots) self.assertTrue(u.geom_type in ('Polygon', 'MultiPolygon')) def setUp(self): # Instead of random points, use deterministic, pseudo-random Halton # sequences for repeatability sake. self.coords = zip( list(islice(halton(5), 20, 120)), list(islice(halton(7), 20, 120)), ) @unittest.skipIf(geos_version < (3, 3, 0), 'GEOS 3.3.0 required') def test_unary_union(self): patches = [Point(xy).buffer(0.05) for xy in self.coords] u = unary_union(patches) self.assertEqual(u.geom_type, 'MultiPolygon') self.assertAlmostEqual(u.area, 0.71857254056) @unittest.skipIf(geos_version < (3, 3, 0), 'GEOS 3.3.0 required') def test_unary_union_multi(self): # Test of multipart input based on comment by @schwehr at # https://github.com/Toblerity/Shapely/issues/47#issuecomment-21809308 patches = MultiPolygon([Point(xy).buffer(0.05) for xy in self.coords]) self.assertAlmostEqual(unary_union(patches).area, 0.71857254056) self.assertAlmostEqual(unary_union([patches, patches]).area, 0.71857254056) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(UnionTestCase) Shapely-1.5.13/tests/test_validation.py000066400000000000000000000005341260610516500201170ustar00rootroot00000000000000from . import unittest from shapely.geometry import Point from shapely.validation import explain_validity class ValidationTestCase(unittest.TestCase): def test_valid(self): self.assertEqual(explain_validity(Point(0, 0)), 'Valid Geometry') def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(ValidationTestCase) Shapely-1.5.13/tests/test_vectorized.py000066400000000000000000000076121260610516500201470ustar00rootroot00000000000000from . import unittest, numpy from shapely.geometry import Point, box, MultiPolygon from shapely.vectorized import contains, touches try: import numpy as np except ImportError: pass @unittest.skipIf(not numpy, 'numpy required') class VectorizedContainsTestCase(unittest.TestCase): def assertContainsResults(self, geom, x, y): result = contains(geom, x, y) x = np.asanyarray(x) y = np.asanyarray(y) self.assertIsInstance(result, np.ndarray) self.assertEqual(result.dtype, np.bool) result_flat = result.flat x_flat, y_flat = x.flat, y.flat # Do the equivalent operation, only slowly, comparing the result # as we go. for idx in range(x.size): self.assertEqual(result_flat[idx], geom.contains(Point(x_flat[idx], y_flat[idx]))) return result def construct_torus(self): point = Point(0, 0) return point.buffer(5).symmetric_difference(point.buffer(2.5)) def test_contains_poly(self): y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j] self.assertContainsResults(self.construct_torus(), x, y) def test_contains_point(self): y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j] self.assertContainsResults(Point(x[0], y[0]), x, y) def test_contains_linestring(self): y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j] self.assertContainsResults(Point(x[0], y[0]), x, y) def test_contains_multipoly(self): y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j] # Construct a geometry of the torus cut in half vertically. cut_poly = box(-1, -10, -2.5, 10) geom = self.construct_torus().difference(cut_poly) self.assertIsInstance(geom, MultiPolygon) self.assertContainsResults(geom, x, y) def test_y_array_order(self): y, x = np.mgrid[-10:10:5j, -5:15:5j] y = y.copy(order='f') self.assertContainsResults(self.construct_torus(), x, y) def test_x_array_order(self): y, x = np.mgrid[-10:10:5j, -5:15:5j] x = x.copy(order='f') self.assertContainsResults(self.construct_torus(), x, y) def test_xy_array_order(self): y, x = np.mgrid[-10:10:5j, -5:15:5j] x = x.copy(order='f') y = y.copy(order='f') result = self.assertContainsResults(self.construct_torus(), x, y) # We always return a C_CONTIGUOUS array. self.assertTrue(result.flags['C_CONTIGUOUS']) def test_array_dtype(self): y, x = np.mgrid[-10:10:5j], np.mgrid[-5:15:5j] x = x.astype(np.int16) self.assertContainsResults(self.construct_torus(), x, y) def test_array_2d(self): y, x = np.mgrid[-10:10:15j, -5:15:16j] result = self.assertContainsResults(self.construct_torus(), x, y) self.assertEqual(result.shape, x.shape) def test_shapely_xy_attr_contains(self): g = Point(0, 0).buffer(10.0) self.assertContainsResults(self.construct_torus(), *g.exterior.xy) @unittest.skipIf(not numpy, 'numpy required') class VectorizedTouchesTestCase(unittest.TestCase): def test_touches(self): y, x = np.mgrid[-2:3:6j, -1:3:5j] geom = box(0, -1, 2, 2) result = touches(geom, x, y) expected = np.array([[False, False, False, False, False], [False, True, True, True, False], [False, True, False, True, False], [False, True, False, True, False], [False, True, True, True, False], [False, False, False, False, False]], dtype=bool) from numpy.testing import assert_array_equal assert_array_equal(result, expected) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(VectorizedContainsTestCase) Shapely-1.5.13/tests/test_xy.py000066400000000000000000000007611260610516500164270ustar00rootroot00000000000000from . import unittest from shapely import geometry class XYTestCase(unittest.TestCase): """New geometry/coordseq method 'xy' makes numpy interop easier""" def test_arrays(self): x, y = geometry.LineString(((0, 0), (1, 1))).xy self.assertEqual(len(x), 2) self.assertEqual(list(x), [0.0, 1.0]) self.assertEqual(len(y), 2) self.assertEqual(list(y), [0.0, 1.0]) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(XYTestCase) Shapely-1.5.13/tests/threading_test.py000066400000000000000000000020101260610516500177210ustar00rootroot00000000000000import threading from binascii import b2a_hex def main(): num_threads = 10 use_threads = True if not use_threads: # Run core code runShapelyBuilding() else: threads = [threading.Thread(target=runShapelyBuilding, name=str(i), args=(i,)) for i in range(num_threads)] for t in threads: t.start() for t in threads: t.join() def runShapelyBuilding(num): print("%s: Running shapely tests on wkb" % num) import shapely.geos print("%s GEOS Handle: %s" % (num, shapely.geos.lgeos.geos_handle)) import shapely.wkt import shapely.wkb p = shapely.wkt.loads("POINT (0 0)") print("%s WKT: %s" % (num, shapely.wkt.dumps(p))) wkb = shapely.wkb.dumps(p) print("%s WKB: %s" % (num, b2a_hex(wkb))) for i in range(10): shapely.wkb.loads(wkb) print("%s GEOS Handle: %s" % (num, shapely.geos.lgeos.geos_handle)) print("Done %s" % num) if __name__ == '__main__': main() Shapely-1.5.13/tests/valgrind-python.supp000066400000000000000000000073531260610516500204200ustar00rootroot00000000000000# # This is a valgrind suppression file that should be used when using valgrind. # # Here's an example of running valgrind: # # cd python/dist/src # valgrind --tool=memcheck --suppressions=Misc/valgrind-python.supp \ # ./python -E -tt ./Lib/test/regrtest.py -u bsddb,network # # You must edit Objects/obmalloc.c and uncomment Py_USING_MEMORY_DEBUGGER # to use the preferred suppressions with Py_ADDRESS_IN_RANGE. # # If you do not want to recompile Python, you can uncomment # suppressions for PyObject_Free and PyObject_Realloc. # # See Misc/README.valgrind for more information. # all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif { ADDRESS_IN_RANGE/Invalid read of size 4 Memcheck:Addr4 fun:Py_ADDRESS_IN_RANGE } { ADDRESS_IN_RANGE/Invalid read of size 4 Memcheck:Value4 fun:Py_ADDRESS_IN_RANGE } { ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value Memcheck:Cond fun:Py_ADDRESS_IN_RANGE } { ADDRESS_IN_RANGE/Invalid read of size 4 Memcheck:Addr4 fun:PyObject_Free } { ADDRESS_IN_RANGE/Invalid read of size 4 Memcheck:Value4 fun:PyObject_Free } { ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value Memcheck:Cond fun:PyObject_Free } { ADDRESS_IN_RANGE/Invalid read of size 4 Memcheck:Addr4 fun:PyObject_Realloc } { ADDRESS_IN_RANGE/Invalid read of size 4 Memcheck:Value4 fun:PyObject_Realloc } { ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value Memcheck:Cond fun:PyObject_Realloc } ### ### All the suppressions below are for errors that occur within libraries ### that Python uses. The problems to not appear to be related to Python's ### use of the libraries. ### { GDBM problems, see test_gdbm Memcheck:Param write(buf) fun:write fun:gdbm_open } ### ### These occur from somewhere within the SSL, when running ### test_socket_sll. They are too general to leave on by default. ### ###{ ### somewhere in SSL stuff ### Memcheck:Cond ### fun:memset ###} ###{ ### somewhere in SSL stuff ### Memcheck:Value4 ### fun:memset ###} ### ###{ ### somewhere in SSL stuff ### Memcheck:Cond ### fun:MD5_Update ###} ### ###{ ### somewhere in SSL stuff ### Memcheck:Value4 ### fun:MD5_Update ###} # # All of these problems come from using test_socket_ssl # { from test_socket_ssl Memcheck:Cond fun:BN_bin2bn } { from test_socket_ssl Memcheck:Cond fun:BN_num_bits_word } { from test_socket_ssl Memcheck:Value4 fun:BN_num_bits_word } { from test_socket_ssl Memcheck:Cond fun:BN_mod_exp_mont_word } { from test_socket_ssl Memcheck:Cond fun:BN_mod_exp_mont } { from test_socket_ssl Memcheck:Param write(buf) fun:write obj:/usr/lib/libcrypto.so.0.9.7 } { from test_socket_ssl Memcheck:Cond fun:RSA_verify } { from test_socket_ssl Memcheck:Value4 fun:RSA_verify } { from test_socket_ssl Memcheck:Value4 fun:DES_set_key_unchecked } { from test_socket_ssl Memcheck:Value4 fun:DES_encrypt2 } { from test_socket_ssl Memcheck:Cond obj:/usr/lib/libssl.so.0.9.7 } { from test_socket_ssl Memcheck:Value4 obj:/usr/lib/libssl.so.0.9.7 } { from test_socket_ssl Memcheck:Cond fun:BUF_MEM_grow_clean } { from test_socket_ssl Memcheck:Cond fun:memcpy fun:ssl3_read_bytes } { from test_socket_ssl Memcheck:Cond fun:SHA1_Update } { from test_socket_ssl Memcheck:Value4 fun:SHA1_Update } # some extra lxml specific (?) suppressions.. { Test Memcheck:Param sigaction(act) fun:__libc_sigaction } { ld Memcheck:Cond obj:/lib/ld-2.6.so obj:/lib/ld-2.6.so obj:* } { ld Memcheck:Addr4 obj:/lib/ld-2.6.so obj:/lib/ld-2.6.so obj:* } Shapely-1.5.13/tools/000077500000000000000000000000001260610516500143505ustar00rootroot00000000000000Shapely-1.5.13/tools/README.txt000066400000000000000000000000001260610516500160340ustar00rootroot00000000000000