[python-shapely] 111/148: Imported Upstream version 1.4.1
Sebastiaan Couwenberg
sebastic at moszumanska.debian.org
Thu Aug 20 17:42:09 UTC 2015
This is an automated email from the git hooks/post-receive script.
sebastic pushed a commit to branch master
in repository python-shapely.
commit 0e09ebf016dadca96d49f089af02d0dd73f8411f
Author: Pietro Battiston <me at pietrobattiston.it>
Date: Fri Sep 26 14:36:42 2014 +0200
Imported Upstream version 1.4.1
---
.gitignore | 4 +-
.travis.yml | 12 +-
CHANGES.txt | 34 ++
CREDITS.txt | 5 +
MANIFEST.in | 4 +-
README.rst | 93 ++++--
docs/code/triangulate.py | 24 ++
docs/conf.py | 6 +-
docs/{design.txt => design.rst} | 0
docs/{index.txt => index.rst} | 0
docs/{manual.txt => manual.rst} | 343 +++++++++++++--------
docs/{project.txt => project.rst} | 2 +
requirements-dev.txt | 7 +-
setup.py | 109 +++----
shapely/__init__.py | 2 +-
shapely/_geos.pxi | 58 ++++
shapely/affinity.py | 2 +-
shapely/coords.py | 9 +-
shapely/ctypes_declarations.py | 36 ++-
shapely/examples/world.py | 21 --
shapely/geometry/base.py | 86 +++++-
shapely/geometry/linestring.py | 50 ++-
shapely/geometry/multilinestring.py | 47 ++-
shapely/geometry/multipoint.py | 40 ++-
shapely/geometry/multipolygon.py | 46 ++-
shapely/geometry/point.py | 29 +-
shapely/geometry/polygon.py | 38 ++-
shapely/geometry/proxy.py | 6 +-
shapely/geos.py | 55 +++-
shapely/impl.py | 1 +
shapely/ops.py | 84 ++++-
shapely/speedups/__init__.py | 10 +
shapely/speedups/_speedups.pyx | 160 +++++++---
shapely/strtree.py | 59 ++++
shapely/tests/__init__.py | 81 -----
shapely/tests/test_speedups.py | 39 ---
shapely/vectorized/__init__.py | 3 +
shapely/vectorized/_vectorized.pyx | 120 +++++++
shapely/wkt.py | 4 +-
tests/__init__.py | 25 ++
{shapely/tests => tests}/binascii_hex.txt | 0
tests/conftest.py | 16 +
{shapely/tests => tests}/rungrind.dist | 0
{shapely/tests => tests}/test_affinity.py | 4 +
{shapely/tests => tests}/test_box.py | 0
{shapely/tests => tests}/test_cga.py | 0
{shapely/tests => tests}/test_collection.py | 0
tests/test_delaunay.py | 43 +++
{shapely/tests => tests}/test_delegated.py | 0
{shapely/tests => tests}/test_dlls.py | 0
{shapely/tests => tests}/test_doctests.py | 0
{shapely/tests => tests}/test_emptiness.py | 0
{shapely/tests => tests}/test_equality.py | 0
{shapely/tests => tests}/test_geointerface.py | 0
{shapely/tests => tests}/test_geomseq.py | 0
{shapely/tests => tests}/test_getitem.py | 0
.../tests => tests}/test_invalid_geometries.py | 0
{shapely/tests => tests}/test_iterops.py | 0
.../tests => tests}/test_linear_referencing.py | 0
{shapely/tests => tests}/test_linemerge.py | 0
{shapely/tests => tests}/test_linestring.py | 46 ++-
{shapely/tests => tests}/test_locale.py | 0
{shapely/tests => tests}/test_mapping.py | 0
{shapely/tests => tests}/test_multilinestring.py | 19 +-
{shapely/tests => tests}/test_multipoint.py | 0
{shapely/tests => tests}/test_multipolygon.py | 0
{shapely/tests => tests}/test_ndarrays.py | 0
tests/test_nearest.py | 22 ++
{shapely/tests => tests}/test_operations.py | 0
{shapely/tests => tests}/test_operators.py | 0
{shapely/tests => tests}/test_persist.py | 0
{shapely/tests => tests}/test_pickle.py | 0
{shapely/tests => tests}/test_point.py | 0
{shapely/tests => tests}/test_polygon.py | 31 +-
{shapely/tests => tests}/test_polygonize.py | 0
{shapely/tests => tests}/test_predicates.py | 0
{shapely/tests => tests}/test_prepared.py | 0
{shapely/tests => tests}/test_products_z.py | 0
{shapely/tests => tests}/test_singularity.py | 0
tests/test_strtree.py | 23 ++
{shapely/tests => tests}/test_styles.py | 0
{shapely/tests => tests}/test_transform.py | 6 +
{shapely/tests => tests}/test_union.py | 0
{shapely/tests => tests}/test_validation.py | 0
tests/test_vectorized.py | 107 +++++++
{shapely/tests => tests}/test_xy.py | 0
{shapely/tests => tests}/threading_test.py | 0
{shapely/tests => tests}/valgrind-python.supp | 0
88 files changed, 1580 insertions(+), 491 deletions(-)
diff --git a/.gitignore b/.gitignore
index 8be8256..7662bd5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,12 @@
*.pyc
+*.c
+*.so
+
VERSION.txt
Shapely.egg-info/
build/
dist/
docs/_build/
-shapely/speedups/_speedups.*
docs/shapely.*.txt
docs/shapely.txt
docs/modules.txt
diff --git a/.travis.yml b/.travis.yml
index 0cdaccf..f92f690 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,20 +3,24 @@ language: python
python:
- "2.6"
- "2.7"
- - "3.2"
- "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
+ - sudo apt-get install -qq libgeos-dev python-numpy cython
- if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install unittest2; fi
- pip install -r requirements-dev.txt
install:
- - python setup.py build
+ - pip install -e .
-script: "python setup.py test"
+script: "py.test tests ${TRAVIS_SPEEDUP_OPTS}"
notifications:
email: false
diff --git a/CHANGES.txt b/CHANGES.txt
index 99eb0fb..47399f9 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,40 @@
Changes
=======
+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.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.
diff --git a/CREDITS.txt b/CREDITS.txt
index 569280f..a838183 100644
--- a/CREDITS.txt
+++ b/CREDITS.txt
@@ -11,6 +11,7 @@ Shapely is written by:
Patches contributed by:
* Allan Adair (https://github.com/allanadair)
+* Joshua Arnott (https://github.com/snorfalorpagus)
* Howard Butler
* Gabi Davar (https://github.com/mindw)
* Phil Elson (https://github.com/pelson)
@@ -20,8 +21,10 @@ Patches contributed by:
* Kelsey Jordahl (https://github.com/kjordahl)
* Fr |eaigue| d |eaigue| 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)
@@ -31,6 +34,8 @@ Patches contributed by:
* 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.
diff --git a/MANIFEST.in b/MANIFEST.in
index 7e709ac..86e4a78 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,8 +5,8 @@ recursive-exclude docs *
recursive-exclude DLLs_AMD64 *
recursive-exclude DLLs_x86 *
include CHANGES.txt CREDITS.txt LICENSE.txt README.rst VERSION.txt
-recursive-include shapely/tests *.py *.txt
+recursive-include tests *.py *.txt
recursive-include shapely/examples *.py
recursive-exclude shapely/speedups *.pyx
-include docs/manual.txt
+include docs/*.rst
exclude MANIFEST.in
diff --git a/README.rst b/README.rst
index effd748..3495165 100644
--- a/README.rst
+++ b/README.rst
@@ -4,6 +4,9 @@ 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
@@ -20,7 +23,7 @@ integrated with packages that are. For more details, see:
Requirements
============
-Shapely 1.3 requires
+Shapely 1.4 requires
* Python >=2.6 (including Python 3.x)
* libgeos_c >=3.1 (3.0 and below have not been tested, YMMV)
@@ -30,13 +33,14 @@ Installation
Windows users should use the executable installer, which contains the required
GEOS DLL. Other users should acquire libgeos_c by any means, make sure that it
-is on the system library path, and install from the Python package index::
+is on the system library path, and install from the Python package index.
- $ pip install Shapely
+.. code-block:: console
-or from a source distribution with the setup script::
+ $ pip install shapely
- $ python setup.py install
+Shapely is also provided by popular Python distributions like Enthought Canopy
+and Continuum Analytics Anaconda.
.. warning:: Windows users:
do not under any circumstances use pip (or easy_install) to uninstall
@@ -48,7 +52,9 @@ Usage
=====
Here is the canonical example of building an approximately circular patch by
-buffering a point::
+buffering a point.
+
+.. code-block:: pycon
>>> from shapely.geometry import Point
>>> patch = Point(0.0, 0.0).buffer(10.0)
@@ -65,40 +71,51 @@ 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.::
+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)'
All linear objects, such as the rings of a polygon (like ``patch`` above),
-provide the Numpy array interface.::
+provide the Numpy array interface.
+
+.. code-block:: pycon
- >>> from numpy import asarray
- >>> ag = asarray(patch.exterior)
- >>> ag
+ >>> import numpy as np
+ >>> np.array(patch.exterior)
array([[ 1.00000000e+01, 0.00000000e+00],
[ 9.95184727e+00, -9.80171403e-01],
[ 9.80785280e+00, -1.95090322e+00],
...
[ 1.00000000e+01, 0.00000000e+00]])
-That yields a Numpy array of [x, y] arrays. This is not always exactly what one
-wants for plotting shapes with Matplotlib (for example), so Shapely 1.2 adds
-a `xy` property for obtaining separate arrays of coordinate x and y values.::
+That yields a Numpy array of `[x, y]` arrays. This is not always exactly what one
+wants for plotting shapes with Matplotlib (for example), so Shapely adds
+a `xy` property for obtaining separate arrays of coordinate x and y values.
+
+.. code-block:: pycon
>>> x, y = patch.exterior.xy
- >>> ax = asarray(x)
- >>> ax
+ >>> np.array(x)
array([ 1.00000000e+01, 9.95184727e+00, 9.80785280e+00, ...])
-Numpy arrays can also be adapted to Shapely linestrings::
+Numpy arrays of `[x, y]` arrays can also be adapted to Shapely linestrings.
+
+.. code-block:: pycon
- >>> from shapely.geometry import asLineString
- >>> asLineString(ag).length
+ >>> from shapely.geometry import LineString
+ >>> LineString(np.array(patch.exterior)).length
62.806623139095073
- >>> asLineString(ag).wkt
- 'LINESTRING (10.0000000000000000 0.0000000000000000, ...)'
+
+Numpy arrays of x and y must be transposed.
+
+.. code-block::
+
+ >>> LineString(np.transpose(np.array(patch.exterior.xy))).length
+ 62.80662313909507
Shapely can also integrate with other Python GIS packages using data modeled
after GeoJSON.
@@ -116,22 +133,36 @@ after GeoJSON.
Development and Testing
=======================
-Dependecies for developing Shapely are listed in requirements-dev.txt. Cython
+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.::
+Use of a virtual environment is strongly recommended.
+
+.. code-block:: console
$ virtualenv .
$ source bin/activate
(env)$ pip install -r requirements-dev.txt
- (env)$ python setup.py develop
+ (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 uses a Zope-stye suite of unittests and doctests, exercised via
-setup.py.::
+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.
- (env)$ python setup.py test
+Shapely 1.3.x is a maintenance-only branch supporting Pythons 2.7 and 3+.
-Nosetests won't run the tests properly; Zope doctest suites are not currently
-supported well by nose.
+"Shapely 3000" is the name of the next milestone. New features will include
+vectorized operations, better integration with IPython Notebook, support for
+fixed precision models, and more. Less ctypes and more Cython is another theme
+in this branch. A 1.4 release should come out of this by Summer, 2014.
Support
=======
@@ -141,12 +172,12 @@ http://lists.gispython.org/mailman/listinfo/community.
Bugs may be reported at https://github.com/Toblerity/Shapely.
-.. include:: ../CREDITS.txt
+.. include:: CREDITS.txt
.. _JTS: http://www.vividsolutions.com/jts/jtshome.htm
.. _PostGIS: http://postgis.org
.. _GEOS: http://trac.osgeo.org/geos/
-.. _example apps: https://github.com/sgillies/shapely/tree/master/shapely/examples
+.. _example apps: https://github.com/Toblerity/shapely/tree/master/shapely/examples
.. _manual: http://toblerity.github.com/shapely/manual.html
.. _Pleiades: http://pleiades.stoa.org
diff --git a/docs/code/triangulate.py b/docs/code/triangulate.py
new file mode 100644
index 0000000..29d5002
--- /dev/null
+++ b/docs/code/triangulate.py
@@ -0,0 +1,24 @@
+from 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()
diff --git a/docs/conf.py b/docs/conf.py
index e66ac12..fe3b911 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -35,15 +35,15 @@ extensions = [
'matplotlib.sphinxext.only_directives',
'matplotlib.sphinxext.plot_directive',
'sphinx.ext.autodoc',
- 'sphinx.ext.pngmath', # <----- pick one, not both
- #'sphinx.ext.mathjax', # <--/
+ #'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 = '.txt'
+source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8'
diff --git a/docs/design.txt b/docs/design.rst
similarity index 100%
rename from docs/design.txt
rename to docs/design.rst
diff --git a/docs/index.txt b/docs/index.rst
similarity index 100%
rename from docs/index.txt
rename to docs/index.rst
diff --git a/docs/manual.txt b/docs/manual.rst
similarity index 91%
rename from docs/manual.txt
rename to docs/manual.rst
index 8ca9f41..87933db 100644
--- a/docs/manual.txt
+++ b/docs/manual.rst
@@ -193,7 +193,7 @@ General Attributes and Methods
Returns a string specifying the `Geometry Type` of the object in accordance
with [1]_.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> print Point(0, 0).geom_type
Point
@@ -202,7 +202,7 @@ General Attributes and Methods
Returns the minimum distance (``float``) to the `other` geometric object.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(0,0).distance(Point(1,1))
1.4142135623730951
@@ -215,7 +215,7 @@ General Attributes and Methods
.. note::
This is not in general the same as the centroid.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> donut = Point(0, 0).buffer(2.0).difference(Point(0, 0).buffer(1.0))
>>> donut.centroid.wkt
@@ -233,7 +233,7 @@ Points
The `Point` constructor takes positional coordinate values or point tuple
parameters.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import Point
>>> point = Point(0.0, 0.0)
@@ -241,7 +241,7 @@ Points
A `Point` has zero area and zero length.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> point.area
0.0
@@ -250,14 +250,14 @@ A `Point` has zero area and zero length.
Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> point.bounds
(0.0, 0.0, 0.0, 0.0)
Coordinate values are accessed via `coords`, `x`, `y`, and `z` properties.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> list(point.coords)
[(0.0, 0.0)]
@@ -268,7 +268,7 @@ Coordinate values are accessed via `coords`, `x`, `y`, and `z` properties.
Coordinates may also be sliced. `New in version 1.2.14`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> point.coords[:]
[(0.0, 0.0)]
@@ -276,7 +276,7 @@ Coordinates may also be sliced. `New in version 1.2.14`.
The `Point` constructor also accepts another `Point` instance, thereby making
a copy.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(point)
<shapely.geometry.point.Point object at 0x...>
@@ -304,7 +304,7 @@ that describe the lines are shown in grey.
A `LineString` has zero area and non-zero length.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import LineString
>>> line = LineString([(0, 0), (1, 1)])
@@ -315,14 +315,14 @@ A `LineString` has zero area and non-zero length.
Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> line.bounds
(0.0, 0.0, 1.0, 1.0)
The defining coordinate values are accessed via the `coords` property.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> len(line.coords)
2
@@ -331,7 +331,7 @@ The defining coordinate values are accessed via the `coords` property.
Coordinates may also be sliced. `New in version 1.2.14`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> point.coords[:]
[(0.0, 0.0), (1.0, 1.0)]
@@ -341,13 +341,19 @@ Coordinates may also be sliced. `New in version 1.2.14`.
The constructor also accepts another `LineString` instance, thereby making a
copy.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LineString(line)
<shapely.geometry.linestring.LineString object at 0x...>
-A sequence of `Point` instances is not a valid constructor parameter. A
-`LineString` is described by points, but is not composed of `Point` instances.
+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)])
+ <shapely.geometry.linestring.LineString object at 0x...>
.. _linearrings:
@@ -378,7 +384,7 @@ grey. A ring's boundary is `empty`.
A `LinearRing` has zero area and non-zero length.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry.polygon import LinearRing
>>> ring = LinearRing([(0, 0), (1, 1), (1, 0)])
@@ -389,14 +395,14 @@ A `LinearRing` has zero area and non-zero length.
Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> ring.bounds
(0.0, 0.0, 1.0, 1.0)
Defining coordinate values are accessed via the `coords` property.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> len(ring.coords)
4
@@ -406,7 +412,7 @@ Defining coordinate values are accessed via the `coords` property.
The `LinearRing` constructor also accepts another `LineString` or `LinearRing`
instance, thereby making a copy.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LinearRring(ring)
<shapely.geometry.polygon.LinearRing object at 0x...>
@@ -446,7 +452,7 @@ interior rings touch along a line, and on the right, a `Polygon` that is
A `Polygon` has non-zero area and non-zero length.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import Polygon
>>> polygon = Polygon([(0, 0), (1, 1), (1, 0)])
@@ -457,14 +463,14 @@ A `Polygon` has non-zero area and non-zero length.
Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> polygon.bounds
(0.0, 0.0, 1.0, 1.0)
Component rings are accessed via `exterior` and `interiors` properties.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> list(polygon.exterior.coords)
[(0.0, 0.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
@@ -474,7 +480,7 @@ Component rings are accessed via `exterior` and `interiors` properties.
The `Polygon` constructor also accepts instances of `LineString` and
`LinearRing`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> coords = [(0, 0), (1, 1), (1, 0)]
>>> r = LinearRing(coords)
@@ -497,7 +503,7 @@ the :func:`shapely.geometry.box()` function.
For example:
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import box
>>> b = box(0.0, 0.0, 1.0, 1.0)
@@ -530,7 +536,7 @@ 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.
-.. sourcecode:: python
+.. code-block:: python
>>> a = LineString([(0, 0), (1, 1), (1,2), (2,2)])
>>> b = LineString([(0, 0), (1, 1), (2,1), (2,2)])
@@ -552,7 +558,7 @@ single point; b) the intersection (in blue) is a collection containing one
Members of a `GeometryCollection` are accessed via the `geoms` property or via
the iterator protocol using ``in`` or ``list()``.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> pprint(list(x.geoms))
[<shapely.geometry.point.Point object at 0x...>,
@@ -564,7 +570,7 @@ the iterator protocol using ``in`` or ``list()``.
Homogeneous collections can also be sliced, resulting in a new object of the
same type.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import MultiPoint
>>> m = MultiPoint([(0, 0), (1, 1), (1,2), (2,2)])
@@ -594,7 +600,7 @@ Collections of Points
A `MultiPoint` has zero area and zero length.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import MultiPoint
>>> points = MultiPoint([(0.0, 0.0), (1.0, 1.0)])
@@ -605,7 +611,7 @@ A `MultiPoint` has zero area and zero length.
Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> points.bounds
(0.0, 0.0, 1.0, 1.0)
@@ -613,7 +619,7 @@ Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple.
Members of a multi-point collection are accessed via the ``geoms`` property or
via the iterator protocol using ``in`` or :func:`list`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> import pprint
>>> pprint.pprint(list(points.geoms))
@@ -626,7 +632,7 @@ via the iterator protocol using ``in`` or :func:`list`.
The constructor also accepts another `MultiPoint` instance or an unordered
sequence of `Point` instances, thereby making copies.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> MultiPoint([Point(0, 0), Point(1, 1)])
<shapely.geometry.multipoint.MultiPoint object at 0x...>
@@ -649,7 +655,7 @@ shown in gray, the boundaries of the objects in black.
A `MultiLineString` has zero area and non-zero length.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import MultiLineString
>>> coords = [((0, 0), (1, 1)), ((-1, 0), (1, 0))]
@@ -661,7 +667,7 @@ A `MultiLineString` has zero area and non-zero length.
Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> lines.bounds
(-1.0, 0.0, 1.0, 1.0)
@@ -669,7 +675,7 @@ Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple.
Its members are instances of `LineString` and are accessed via the ``geoms``
property or via the iterator protocol using ``in`` or ``list()``.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> len(lines.geoms)
2
@@ -683,7 +689,7 @@ property or via the iterator protocol using ``in`` or ``list()``.
The constructor also accepts another instance of `MultiLineString` or an
unordered sequence of `LineString` instances, thereby making copies.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> MultiLineString(lines)
<shapely.geometry.multilinestring.MultiLineString object at 0x...>
@@ -703,7 +709,7 @@ Collections of Polygons
More clearly, the constructor also accepts an unordered sequence of `Polygon`
instances, thereby making copies.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> polygons = MultiPolygon([polygon, s, t])
>>> len(polygons.geoms)
@@ -711,13 +717,13 @@ instances, thereby making copies.
.. plot:: code/multipolygon.py
-Figure 7. On the right, a `valid` `MultiPolygon` with 2 members, and on the
+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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> polygons.bounds
(-1.0, -1.0, 2.0, 2.0)
@@ -725,7 +731,7 @@ Its `x-y` bounding box is a ``(minx, miny, maxx, maxy)`` tuple.
Its members are instances of `Polygon` and are accessed via the ``geoms``
property or via the iterator protocol using ``in`` or ``list()``.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> len(polygons.geoms)
3
@@ -742,7 +748,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> line = LineString()
>>> line.is_empty
@@ -757,7 +763,7 @@ by empty features.
The coordinates of a empty feature can be set, after which the geometry is no
longer empty.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> line.coords = [(0, 0), (1, 1)]
>>> line.is_empty
@@ -787,7 +793,7 @@ point at a given distance along the object.
If the `normalized` arg is ``True``, the distance will be interpreted as a
fraction of the geometric object's length.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> ip = LineString([(0, 0), (0, 1), (1, 1)]).interpolate(1.5)
>>> ip
@@ -806,7 +812,7 @@ 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`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LineString([(0, 0), (0, 1), (1, 1)]).project(ip)
1.5
@@ -816,7 +822,7 @@ length of the object. The :meth:`project` method is the inverse of
For example, the linear referencing methods might be used to cut lines at a
specified distance.
-.. sourcecode:: python
+.. code-block:: python
def cut(line, distance):
# Cuts a line in two at a distance from its starting point
@@ -835,7 +841,7 @@ specified distance.
LineString(coords[:i] + [(cp.x, cp.y)]),
LineString([(cp.x, cp.y)] + coords[i:])]
-.. sourcecode:: pycon
+.. 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)])
@@ -867,7 +873,7 @@ example will be shown for each.
Returns ``True`` if the feature has not only `x` and `y`, but also `z`
coordinates for 3D (or so-called, 2.5D) geometries.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(0, 0).has_z
False
@@ -882,14 +888,14 @@ example will be shown for each.
`New in version 1.2.10`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LinearRing([(1,0), (1,1), (0,0)]).is_ccw
True
A ring with an undesired orientation can be reversed like this:
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> ring = LinearRing([(0,0), (1,1), (1,0)])
>>> ring.is_ccw
@@ -903,7 +909,7 @@ A ring with an undesired orientation can be reversed like this:
Returns ``True`` if the feature's `interior` and `boundary` (in point set
terms) coincide with the empty set.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point().is_empty
True
@@ -916,7 +922,7 @@ A ring with an undesired orientation can be reversed like this:
unary predicates such as ``is_empty`` can be easily used as predicates for
the built in :func:`filter` or :func:`itertools.ifilter`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from operator import attrgetter
>>> empties = filter(attrgetter('is_empty'), [Point(), Point(0, 0)])
@@ -928,7 +934,7 @@ A ring with an undesired orientation can be reversed like this:
Returns ``True`` if the feature is closed. A closed feature's `boundary`
coincides with the empty set.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LineString([(0, 0), (1, 1), (1, -1)]).is_ring
False
@@ -946,7 +952,7 @@ meaningless for others.
The simplicity test is meaningful only for `LineStrings` and `LinearRings`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LineString([(0, 0), (1, 1), (1, -1), (0, 1)]).is_simple
False
@@ -962,7 +968,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> MultiPolygon([Point(0, 0).buffer(2.0), Point(1, 1).buffer(2.0)]).is_valid
False
@@ -976,7 +982,7 @@ buffer operations (explained in a following section) overlap.
could ensure that only valid objects are returned from a constructor
function.
-.. sourcecode:: python
+.. code-block:: python
from functools import wraps
def validate(func):
@@ -989,7 +995,7 @@ buffer operations (explained in a following section) overlap.
return ob
return wrapper
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> @validate
... def ring(coordinates):
@@ -1027,7 +1033,7 @@ See also :meth:`equals`.
This predicate applies to all types, and is inverse to :meth:`within`. The
expression ``a.contains(b) == b.within(a)`` always evaluates to ``True``.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> coords = [(0, 0), (1, 1)]
>>> LineString(coords).contains(Point(0.5, 0.5))
@@ -1037,7 +1043,7 @@ expression ``a.contains(b) == b.within(a)`` always evaluates to ``True``.
A line's endpoints are part of its `boundary` and are therefore not contained.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LineString(coords).contains(Point(1.0, 1.0))
False
@@ -1047,7 +1053,7 @@ A line's endpoints are part of its `boundary` and are therefore not contained.
Binary predicates can be used directly as predicates for ``filter()`` or
``itertools.ifilter()``.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> line = LineString(coords)
>>> contained = filter(line.contains, [Point(), Point(0.5, 0.5)])
@@ -1062,14 +1068,14 @@ A line's endpoints are part of its `boundary` and are therefore not contained.
the other but does not contain it, and the dimension of the intersection is
less than the dimension of the one or the other.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LineString(coords).crosses(LineString([(0, 1), (1, 0)]))
True
A line does not cross a point that it contains.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LineString(coords).crosses(Point(0.5, 0.5))
False
@@ -1079,7 +1085,7 @@ A line does not cross a point that it contains.
Returns ``True`` if the `boundary` and `interior` of the object do not
intersect at all with those of the other.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(0, 0).disjoint(Point(1, 1))
True
@@ -1096,7 +1102,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> a = LineString([(0, 0), (1, 1)])
>>> b = LineString([(0, 0), (0.5, 0.5), (1, 1)])
@@ -1125,7 +1131,7 @@ This predicate is equivalent to the OR-ing of :meth:`contains`, :meth:`crosses`,
Overlapping features do not therefore `touch`, another potential "gotcha". For
example, the following lines touch at ``(1, 1)``, but do not overlap.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> a = LineString([(0, 0), (1, 1)])
>>> b = LineString([(1, 1), (2, 2)])
@@ -1144,7 +1150,7 @@ 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
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> a = Point(2, 2)
>>> b = Polygon([[1, 1], [1, 3], [3, 3], [3, 1]])
@@ -1153,7 +1159,7 @@ point contained by none
and that copies of these are collected into a list
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> features = [c, a, d, b, c]
@@ -1163,9 +1169,7 @@ 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.
-.. sourcecode:: python
-
- from shapely.geometry import asShape
+.. code-block:: python
class Within(object):
def __init__(self, o):
@@ -1178,7 +1182,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> d < c
True
@@ -1187,7 +1191,7 @@ sorting. That's what we'll rely on to spatially sort, and the reason why we use
It also works on the list of features, producing the order we want.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> [d, c, c, b, a] == sorted(features, key=Within, reverse=True)
True
@@ -1213,7 +1217,7 @@ 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).
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(0, 0).relate(Point(1, 1))
'FF0FFF0F2'
@@ -1221,7 +1225,7 @@ of the matrix).
The matrix for a line and a point on the line has more "true" (not ``F``)
elements.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(0, 0).relate(LineString([(0, 0), (1, 1)]))
'F0FFFF102'
@@ -1254,7 +1258,7 @@ available as a read-only attribute.
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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>> coords = [((0, 0), (1, 1)), ((-1, 0), (1, 0))]
>>> lines = MultiLineString(coords)
@@ -1277,7 +1281,7 @@ illustration of lines and their boundaries.
Returns a representation of the object's geometric centroid (point).
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> LineString([(0, 0), (1, 1)]).centroid
<shapely.geometry.point.Point object at 0x...>
@@ -1294,7 +1298,7 @@ illustration of lines and their boundaries.
Returns a representation of the points making up this geometric object that
do not make up the *other* object.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> a = Point(1, 1).buffer(1.5)
>>> b = Point(2, 1).buffer(1.5)
@@ -1323,7 +1327,7 @@ Figure 8. Differences between two approximately circular polygons.
Returns a representation of the intersection of this object with the `other`
geometric object.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> a = Point(1, 1).buffer(1.5)
>>> b = Point(2, 1).buffer(1.5)
@@ -1337,7 +1341,7 @@ See the figure under :meth:`symmetric_difference` below.
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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> a = Point(1, 1).buffer(1.5)
>>> b = Point(2, 1).buffer(1.5)
@@ -1355,7 +1359,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> a = Point(1, 1).buffer(1.5)
>>> b = Point(2, 1).buffer(1.5)
@@ -1366,7 +1370,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> a.union(b).boundary
<shapely.geometry.polygon.LinearRing object at 0x...>
@@ -1387,7 +1391,7 @@ 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=1.0)
+.. 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.
@@ -1395,7 +1399,7 @@ derived from set-theoretic analysis.
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).
@@ -1420,7 +1424,7 @@ derived from set-theoretic analysis.
bevel 3
========= =====
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import CAP_STYLE, JOIN_STYLE
>>> CAP_STYLE.flat
@@ -1432,7 +1436,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> line = LineString([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)])
>>> dilated = line.buffer(0.5)
@@ -1446,7 +1450,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> p = Point(0, 0).buffer(10.0)
>>> len(p.exterior.coords)
@@ -1456,7 +1460,7 @@ The default (`resolution` of 16) buffer of a point is a polygonal patch with
With a `resolution` of 1, the buffer is a square patch.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> q = Point(0, 0).buffer(10.0, 1)
>>> len(q.exterior.coords)
@@ -1467,7 +1471,7 @@ With a `resolution` of 1, the buffer is a square patch.
Passed a `distance` of 0, :meth:`buffer` can be used to "clean" self-touching
or self-crossing polygons such as the classic "bowtie".
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> coords = [(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)]
>>> bowtie = Polygon(coords)
@@ -1494,7 +1498,7 @@ Buffering splits the polygon in two at the point where they touch.
three. For two points, the convex hull collapses to a `LineString`; for 1, a
`Point`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(0, 0).convex_hull
<shapely.geometry.point.Point object at 0x...>
@@ -1512,14 +1516,14 @@ Figure 10. Convex hull (blue) of 2 points (left) and of 6 points (right).
Returns a representation of the point or smallest rectangular polygon (with
sides parallel to the coordinate axes) that contains the object.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(0, 0).envelope
<shapely.geometry.point.Point object at 0x...>
>>> MultiPoint([(0, 0), (1, 1)]).envelope
<shapely.geometry.polygon.Polygon object at 0x...>
-.. method:: object.parallel_offset(distance, side, resolution=16, join_style=1, mitre_limit=1.0)
+.. 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.
@@ -1562,7 +1566,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> p = Point(0.0, 0.0)
>>> x = p.buffer(1.0)
@@ -1677,7 +1681,7 @@ preserved or supported by 3D affine transformations.
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}
- .. sourcecode:: pycon
+ .. code-block:: pycon
>>> from shapely import affinity
>>> line = LineString([(1, 3), (1, 1), (4, 1)])
@@ -1716,7 +1720,7 @@ preserved or supported by 3D affine transformations.
y_\mathrm{off} &= y_0 - y_0 y_\mathrm{fact} \\
z_\mathrm{off} &= z_0 - z_0 z_\mathrm{fact}
- .. sourcecode:: pycon
+ .. code-block:: pycon
>>> triangle = Polygon([(1, 1), (2, 3), (3, 1)])
>>> triangle_a = affinity.scale(triangle, xfact=1.5, yfact=-1)
@@ -1799,7 +1803,7 @@ Shapely supports map projections and other arbitrary transformations of geometri
For example, here is an identity function applicable to both types of input
(scalar or array).
-.. sourcecode:: python
+.. code-block:: python
def id_func(x, y, z=None):
return tuple(filter(None, [x, y, z]))
@@ -1809,21 +1813,22 @@ For example, here is an identity function applicable to both types of input
A partially applied transform function from pyproj satisfies the requirements
for `func`.
-.. sourcecode:: python
+.. code-block:: python
+ from shapely.ops import transform
from functools import partial
import pyproj
project = partial(
pyproj.transform,
- pyproj.Proj(init='espg:4326'),
+ pyproj.Proj(init='epsg:4326'),
pyproj.Proj(init='epsg:26913'))
g2 = transform(project, g1)
Lambda expressions such as the one in
-.. sourcecode:: python
+.. code-block:: python
g2 = transform(lambda x, y, z=None: (x+1.0, y+1.0), g1)
@@ -1846,7 +1851,7 @@ using functions in the :mod:`shapely.ops` module.
As with the :class:`MultiLineString` constructor, the input elements may be
any line-like object.
- .. sourcecode:: pycon
+ .. code-block:: pycon
>>> from shapely.ops import polygonize
>>> lines = [
@@ -1878,7 +1883,7 @@ using functions in the :mod:`shapely.ops` module.
`New in version 1.2.18.`
- .. sourcecode:: pycon
+ .. code-block:: pycon
>>> lines = [
... ((0, 0), (1, 1)),
@@ -1905,7 +1910,7 @@ using functions in the :mod:`shapely.ops` module.
As with :func:`shapely.ops.polygonize`, the input elements may be any
line-like object.
-.. sourcecode:: python
+.. code-block:: python
>>> from shapely.ops import linemerge
>>> linemerge(lines)
@@ -1927,7 +1932,7 @@ efficient than accumulating with :meth:`union`.
Returns a representation of the union of the given geometric objects.
- .. sourcecode:: pycon
+ .. code-block:: pycon
>>> from shapely.ops import cascaded_union
>>> polygons = [Point(i, 0).buffer(0.7) for i in range(5)]
@@ -1936,7 +1941,7 @@ efficient than accumulating with :meth:`union`.
The function is particularly useful in dissolving `MultiPolygons`.
- .. sourcecode:: pycon
+ .. code-block:: pycon
>>> m = MultiPolygon(polygons)
>>> m.area
@@ -1955,6 +1960,65 @@ efficient than accumulating with :meth:`union`.
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.
+
Prepared Geometry Operations
----------------------------
@@ -1968,7 +2032,7 @@ batches of operations.
To test one polygon containment against a large batch of points, one should
first use the :func:`prepared.prep` function.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import Point
>>> from shapely.prepared import prep
@@ -1996,7 +2060,7 @@ Diagnostics
The messages may or may not have a representation of a problem point that can
be parsed out.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> coords = [(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)]
>>> p = Polygon(coords)
@@ -2009,7 +2073,7 @@ accessible via :attr:`shapely.__version__`,
:attr:`shapely.geos.geos_version_string`, and
:attr:`shapely.geos.geos_capi_version`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> import shapely
>>> shapely.__version__
@@ -2021,6 +2085,37 @@ accessible via :attr:`shapely.__version__`,
'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
==============
@@ -2034,7 +2129,7 @@ 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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(0, 0).wkt
'POINT (0.0000000000000000 0.0000000000000000)'
@@ -2055,7 +2150,7 @@ appropriate type, use ``loads()``.
Returns a geometric object from a WKB representation `wkb`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>> from shapely.wkb import dumps, loads
>>> wkb = dumps(Point(0, 0))
@@ -2074,7 +2169,7 @@ All of Shapely's geometry types are supported by these functions.
Returns a geometric object from a WKT representation `wkt`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>> wkt = dumps(Point(0, 0))
>>> print wkt
@@ -2089,7 +2184,7 @@ All geometric objects with coordinate sequences (`Point`, `LinearRing`,
`LineString`) provide the Numpy array interface and can thereby be converted or
adapted to Numpy arrays.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from numpy import array
>>> array(Point(0, 0))
@@ -2108,7 +2203,7 @@ price of slower Numpy access to the coordinates of Shapely objects.
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.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> Point(0, 0).xy
(array('d', [0.0]), array('d', [0.0]))
@@ -2119,7 +2214,7 @@ 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
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import asPoint
>>> pa = asPoint(array([0.0, 0.0]))
@@ -2128,7 +2223,7 @@ maintaining their original storage. A 1 x 2 array can be adapted to a point
and a N x 2 array can be adapted to a line string
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import asLineString
>>> la = asLineString(array([[1.0, 2.0], [3.0, 4.0]]))
@@ -2156,28 +2251,28 @@ adapted and used as a Shapely geometry using the
For example, a dictionary:
-.. sourcecode:: pycon
+.. code-block:: pycon
- >>> from shapely.geometry import asShape
- >>> d = {"type": "Point", "coordinates": (0.0, 0.0)}
- >>> shape = asShape(d)
- >>> shape.geom_type
+ >>> from shapely.geometry import shape
+ >>> data = {"type": "Point", "coordinates": (0.0, 0.0)}
+ >>> geom = shape(data)
+ >>> geom.geom_type
'Point'
- >>> list(shape.coords)
+ >>> list(geom.coords)
[(0.0, 0.0)]
Or a simple placemark-type object:
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> class GeoThing(object):
... def __init__(self, d):
... self.__geo_interface__ = d
>>> thing = GeoThing({"type": "Point", "coordinates": (0.0, 0.0)})
- >>> shape = asShape(thing)
- >>> shape.geom_type
+ >>> geom = shape(thing)
+ >>> geom.geom_type
'Point'
- >>> list(shape.coords)
+ >>> list(geom.coords)
[(0.0, 0.0)]
The GeoJSON-like mapping of a geometric object can be obtained using
@@ -2192,7 +2287,7 @@ The GeoJSON-like mapping of a geometric object can be obtained using
For example, using the same `GeoThing` class:
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely.geometry import mapping
>>> thing = GeoThing({"type": "Point", "coordinates": (0.0, 0.0)})
@@ -2222,7 +2317,7 @@ 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`.
-.. sourcecode:: pycon
+.. code-block:: pycon
>>> from shapely import speedups
>>> speedups.available
diff --git a/docs/project.txt b/docs/project.rst
similarity index 66%
rename from docs/project.txt
rename to docs/project.rst
index cba758f..ce70278 100644
--- a/docs/project.txt
+++ b/docs/project.rst
@@ -1,4 +1,6 @@
.. include:: ../README.rst
+.. include:: ../CREDITS.txt
+
.. include:: ../CHANGES.txt
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 6f1d903..b16d71b 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,4 +1,5 @@
-setuptools >= 0.9.8
-Numpy==1.8.0
-Cython==0.19.2
+setuptools
+Numpy>=1.8.0
+Cython>=0.19
descartes==1.0.1
+pytest
diff --git a/setup.py b/setup.py
index 413a474..ea428c0 100755
--- a/setup.py
+++ b/setup.py
@@ -14,6 +14,7 @@ except ImportError:
from distutils.cmd import Command
from distutils.errors import CCompilerError, DistutilsExecError, \
DistutilsPlatformError
+from distutils.sysconfig import get_config_var
import errno
import glob
import os
@@ -22,40 +23,6 @@ import shutil
import subprocess
import sys
-class test(Command):
- """Run unit tests after in-place build"""
- description = __doc__
- user_options = []
-
- def initialize_options(self):
- pass
-
- def finalize_options(self):
- pass
-
- def run(self):
- # Force an in-place build for testing speedups
- cmd = self.reinitialize_command('build_ext')
- setattr(cmd, 'inplace', 1)
- self.run_command('build_ext')
-
- if sys.version_info[0:2] <= (2, 6):
- try:
- from unittest2 import TextTestRunner, TestLoader
- except ImportError:
- raise ImportError(
- "unittest2 is required to run tests with python-%d.%d" %
- sys.version_info[0:2])
- else:
- from unittest import TextTestRunner, TestLoader
-
- import shapely.tests
- tests = TestLoader().loadTestsFromName('test_suite', shapely.tests)
- runner = TextTestRunner(verbosity=2)
- result = runner.run(tests)
-
- if not result.wasSuccessful():
- sys.exit(1)
# Get the version from the shapely module
version = None
@@ -67,17 +34,22 @@ with open('shapely/__init__.py', 'r') as fp:
if version is None:
raise ValueError("Could not determine Shapely's version")
-with open('VERSION.txt', 'w') as fp:
+# 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(version)
-with open('README.rst', 'r') as fp:
+with open('README.rst', 'r', **open_kwds) as fp:
readme_text = fp.read()
readme_text = readme_text.replace(".. include:: CREDITS.txt", "")
-with open('CREDITS.txt', 'r') as fp:
+with open('CREDITS.txt', 'r', **open_kwds) as fp:
credits = fp.read()
-with open('CHANGES.txt', 'r') as fp:
+with open('CHANGES.txt', 'r', **open_kwds) as fp:
changes_text = fp.read()
setup_args = dict(
@@ -99,9 +71,9 @@ setup_args = dict(
'shapely.algorithms',
'shapely.examples',
'shapely.speedups',
- 'shapely.tests',
+ 'shapely.vectorized',
],
- cmdclass = {'test': test},
+ cmdclass = {},
classifiers = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
@@ -113,6 +85,7 @@ setup_args = dict(
'Programming Language :: Python :: 3',
'Topic :: Scientific/Engineering :: GIS',
],
+ data_files = [('shapely', ['shapely/_geos.pxi'])]
)
# Add DLLs for Windows
@@ -152,20 +125,23 @@ class BuildFailed(Exception):
pass
-class build_ext(distutils_build_ext):
- # This class allows C extension building to fail.
+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 run(self):
- try:
- distutils_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
- def build_extension(self, ext):
- try:
- distutils_build_ext.build_extension(self, ext)
- except ext_errors as x:
- raise BuildFailed(x)
if (hasattr(platform, 'python_implementation')
and platform.python_implementation() == 'PyPy'):
@@ -177,6 +153,7 @@ elif sys.platform == 'win32':
else:
libraries = ['geos_c']
+
if os.path.exists("MANIFEST.in"):
pyx_file = "shapely/speedups/_speedups.pyx"
c_file = "shapely/speedups/_speedups.c"
@@ -202,12 +179,36 @@ ext_modules = [
Extension(
"shapely.speedups._speedups",
["shapely/speedups/_speedups.c"],
- libraries=libraries)
+ libraries=libraries,
+ include_dirs=[get_config_var('INCLUDEDIR')],),
]
try:
+ import numpy as np
+ from Cython.Distutils import build_ext as cython_build_ext
+ from distutils.extension import Extension as DistutilsExtension
+
+ cmd_classes = setup_args.setdefault('cmdclass', {})
+ if 'build_ext' in cmd_classes:
+ raise ValueError('We need to put the Cython build_ext in '
+ 'cmd_classes, but it is already defined.')
+ cmd_classes['build_ext'] = cython_build_ext
+
+ ext_modules.append(DistutilsExtension("shapely.vectorized._vectorized",
+ sources=["shapely/vectorized/_vectorized.pyx"],
+ libraries=libraries + [np.get_include()],
+ include_dirs=[get_config_var('INCLUDEDIR'),
+ np.get_include()],
+ ))
+except ImportError:
+ print("Numpy or Cython not available, shapely.vectorized submodule not "
+ "being built.")
+
+
+try:
# try building with speedups
- setup_args['cmdclass']['build_ext'] = build_ext
+ 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
diff --git a/shapely/__init__.py b/shapely/__init__.py
index 67bc602..bf25615 100644
--- a/shapely/__init__.py
+++ b/shapely/__init__.py
@@ -1 +1 @@
-__version__ = "1.3.0"
+__version__ = "1.4.1"
diff --git a/shapely/_geos.pxi b/shapely/_geos.pxi
new file mode 100644
index 0000000..fc22a57
--- /dev/null
+++ b/shapely/_geos.pxi
@@ -0,0 +1,58 @@
+# 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 *, 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 <GEOSContextHandle_t>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 <GEOSPreparedGeometry *>geos_geom
diff --git a/shapely/affinity.py b/shapely/affinity.py
index 794bb30..a45ca3c 100755
--- a/shapely/affinity.py
+++ b/shapely/affinity.py
@@ -77,7 +77,7 @@ def affine_transform(geom, matrix):
yield (xp, yp, zp)
# Process coordinates from each supported geometry type
- if geom.type in ('Point', 'LineString'):
+ if geom.type in ('Point', 'LineString', 'LinearRing'):
return type(geom)(list(affine_pts(geom.coords)))
elif geom.type == 'Polygon':
ring = geom.exterior
diff --git a/shapely/coords.py b/shapely/coords.py
index b2c5dda..c37ffc0 100644
--- a/shapely/coords.py
+++ b/shapely/coords.py
@@ -21,8 +21,13 @@ 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 has_numpy and hasattr(ob, '__array_interface__'):
- return numpy.require(ob, numpy.float64, ["C", "OWNDATA"])
+ if (hasattr(ob, '__array_interface__')
+ and ob.__array_interface__.get('strides')):
+ if has_numpy:
+ return numpy.require(ob, numpy.float64, ["C", "OWNDATA"])
+ else:
+ # raise an error if strided. See issue #52.
+ raise ValueError("C-contiguous data is required")
else:
return ob
diff --git a/shapely/ctypes_declarations.py b/shapely/ctypes_declarations.py
index c41e922..2c0a305 100644
--- a/shapely/ctypes_declarations.py
+++ b/shapely/ctypes_declarations.py
@@ -4,7 +4,7 @@ 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
+ 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)
@@ -66,6 +66,9 @@ def prototype(lgeos, geos_version):
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]
@@ -206,6 +209,11 @@ def prototype(lgeos, geos_version):
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]
@@ -268,6 +276,9 @@ def prototype(lgeos, geos_version):
lgeos.GEOSisRing.restype = c_byte
lgeos.GEOSisRing.argtypes = [c_void_p]
+ lgeos.GEOSisClosed.restype = c_byte
+ lgeos.GEOSisClosed.argtypes = [c_void_p]
+
lgeos.GEOSHasZ.restype = c_byte
lgeos.GEOSHasZ.argtypes = [c_void_p]
@@ -453,3 +464,26 @@ def prototype(lgeos, geos_version):
lgeos.GEOSFree.restype = None
lgeos.GEOSFree.argtypes = [c_void_p]
+
+ 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
+
diff --git a/shapely/examples/world.py b/shapely/examples/world.py
deleted file mode 100644
index 81b9af8..0000000
--- a/shapely/examples/world.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import ogr
-import pylab
-from numpy import asarray
-
-from shapely.wkb import loads
-
-source = ogr.Open("/var/gis/data/world/world_borders.shp")
-borders = source.GetLayerByName("world_borders")
-
-fig = pylab.figure(1, figsize=(4,2), dpi=300)
-
-while 1:
- feature = borders.GetNextFeature()
- if not feature:
- break
-
- geom = loads(feature.GetGeometryRef().ExportToWkb())
- a = asarray(geom)
- pylab.plot(a[:,0], a[:,1])
-
-pylab.show()
diff --git a/shapely/geometry/base.py b/shapely/geometry/base.py
index ed1f4cf..a62b40f 100644
--- a/shapely/geometry/base.py
+++ b/shapely/geometry/base.py
@@ -115,6 +115,25 @@ def geom_to_wkb(ob):
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)
@@ -160,7 +179,7 @@ class BaseGeometry(object):
# _crs : object
# Coordinate reference system. Available for Shapely extensions, but
# not implemented here.
- # _owned : bool
+ # _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
@@ -168,7 +187,7 @@ class BaseGeometry(object):
_ctypes_data = None
_ndim = None
_crs = None
- _owned = False
+ _other_owned = False
# Backend config
impl = DefaultImplementation
@@ -180,19 +199,18 @@ class BaseGeometry(object):
# a reference to the so/dll proxy to preserve access during clean up
_lgeos = lgeos
- def empty(self):
+ def empty(self, val=EMPTY):
# TODO: defer cleanup to the implementation. We shouldn't be
# explicitly calling a lgeos method here.
- if not (self._owned or self._is_empty):
+ 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.__geom__ = EMPTY
+ self.__geom__ = val
def __del__(self):
- self.empty()
- self.__geom__ = None
+ self.empty(val=None)
self.__p__ = None
def __str__(self):
@@ -324,6 +342,36 @@ class BaseGeometry(object):
"""WKB hex representation of the geometry"""
return WKBWriter(lgeos).write_hex(self)
+ def svg(self, scale_factor=1.):
+ """
+ SVG representation of the geometry. Scale factor is multiplied by
+ the size of the SVG symbol so it can be scaled consistently for a
+ consistent appearance based on the canvas size.
+ """
+ raise NotImplementedError
+
+ def _repr_svg_(self):
+ """SVG representation for iPython notebook"""
+ #Pick an arbitrary size for the SVG canvas
+
+
+ xmin, ymin, xmax, ymax = self.buffer(1).bounds
+ x_size = min([max([100., xmax - xmin]), 300])
+ y_size = min([max([100., ymax - ymin]), 300])
+ try:
+ scale_factor = max([xmax - xmin, ymax - ymin]) / max([x_size, y_size])
+ except ZeroDivisionError:
+ scale_factor = 1
+ buffered_box = "{0} {1} {2} {3}".format(xmin, ymin, xmax - xmin, ymax - ymin)
+ return """<svg
+ preserveAspectRatio="xMinYMin meet"
+ viewBox="{0}"
+ width="{1}"
+ height="{2}"
+ transform="translate(0, {1}),scale(1, -1)">
+ {3}
+ </svg>""".format(buffered_box, x_size, y_size, self.svg(scale_factor))
+
@property
def geom_type(self):
"""Name of the geometry's type, such as 'Point'"""
@@ -394,7 +442,7 @@ class BaseGeometry(object):
def buffer(self, distance, resolution=16, quadsegs=None,
cap_style=CAP_STYLE.round, join_style=JOIN_STYLE.round,
- mitre_limit=0):
+ mitre_limit=5.0):
"""Returns a geometry with an envelope at a distance from the object's
envelope
@@ -433,7 +481,6 @@ class BaseGeometry(object):
>>> g.buffer(1.0, cap_style='square').area
4.0
"""
-
if quadsegs is not None:
warn(
"The `quadsegs` argument is deprecated. Use `resolution`.",
@@ -441,7 +488,9 @@ class BaseGeometry(object):
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))
@@ -510,6 +559,11 @@ class BaseGeometry(object):
return bool(self.impl['is_ring'](self))
@property
+ def is_closed(self):
+ """True if the geometry is closed, else False"""
+ return bool(self.impl['is_closed'](self))
+
+ @property
def is_simple(self):
"""True if the geometry is simple, meaning that any self-intersections
are only at boundary points, else False"""
@@ -657,6 +711,14 @@ class BaseMultipartGeometry(BaseGeometry):
else:
return ()[index]
+ def svg(self, scale_factor=1.):
+ """
+ SVG representation of the geometry. Scale factor is multiplied by
+ the size of the SVG symbol so it can be scaled consistently for a
+ consistent appearance based on the canvas size.
+ """
+ return "\n".join([g.svg(scale_factor) for g in self])
+
class GeometrySequence(object):
"""
@@ -688,7 +750,7 @@ class GeometrySequence(object):
def _get_geom_item(self, i):
g = self.shape_factory()
- g._owned = True
+ g._other_owned = True
g._geom = lgeos.GEOSGetGeometryN(self._geom, i)
g._ndim = self._ndim
g.__p__ = self
@@ -746,7 +808,7 @@ class HeterogeneousGeometrySequence(GeometrySequence):
def _get_geom_item(self, i):
sub = lgeos.GEOSGetGeometryN(self._geom, i)
g = geom_factory(sub, parent=self)
- g._owned = True
+ g._other_owned = True
return g
diff --git a/shapely/geometry/linestring.py b/shapely/geometry/linestring.py
index 3af8197..dc39d8b 100644
--- a/shapely/geometry/linestring.py
+++ b/shapely/geometry/linestring.py
@@ -10,8 +10,11 @@ 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
+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']
@@ -52,6 +55,24 @@ class LineString(BaseGeometry):
'coordinates': tuple(self.coords)
}
+ def svg(self, scale_factor=1.):
+ """
+ SVG representation of the geometry. Scale factor is multiplied by
+ the size of the SVG symbol so it can be scaled consistently for a
+ consistent appearance based on the canvas size.
+ """
+ pnt_format = " ".join(["{0},{1}".format(*c) for c in self.coords])
+ return """<polyline
+ fill="none"
+ stroke="{2}"
+ stroke-width={1}
+ points="{0}"
+ opacity=".8"
+ />""".format(
+ pnt_format,
+ 2.*scale_factor,
+ "#66cc99" if self.is_valid else "#ff3333")
+
@property
def ctypes(self):
if not self._ctypes_data:
@@ -87,7 +108,7 @@ class LineString(BaseGeometry):
def parallel_offset(
self, distance, side,
- resolution=16, join_style=JOIN_STYLE.round, mitre_limit=1.0):
+ 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.
@@ -107,12 +128,14 @@ class LineString(BaseGeometry):
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,
bool(side == 'left')))
- except WindowsError:
+ except OSError:
raise TopologicalError()
@@ -157,6 +180,14 @@ def asLineString(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)
@@ -221,8 +252,15 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
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(ob[0])
+ n = len(_coords(ob[0]))
except TypeError:
raise ValueError(
"Input %s is the wrong shape for a LineString" % str(ob))
@@ -240,7 +278,7 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
# add to coordinate sequence
for i in range(m):
- coords = ob[i]
+ 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])
diff --git a/shapely/geometry/multilinestring.py b/shapely/geometry/multilinestring.py
index b78d174..497a405 100644
--- a/shapely/geometry/multilinestring.py
+++ b/shapely/geometry/multilinestring.py
@@ -9,8 +9,8 @@ if sys.version_info[0] < 3:
from ctypes import c_double, c_void_p, cast, POINTER
from shapely.geos import lgeos
-from shapely.geometry.base import BaseMultipartGeometry
-from shapely.geometry.linestring import LineString, geos_linestring_from_py
+from shapely.geometry.base import BaseMultipartGeometry, geos_geom_from_py
+from shapely.geometry import linestring
from shapely.geometry.proxy import CachingGeometryProxy
__all__ = ['MultiLineString', 'asMultiLineString']
@@ -52,7 +52,7 @@ class MultiLineString(BaseMultipartGeometry):
self._geom, self._ndim = geos_multilinestring_from_py(lines)
def shape_factory(self, *args):
- return LineString(*args)
+ return linestring.LineString(*args)
@property
def __geo_interface__(self):
@@ -61,11 +61,32 @@ class MultiLineString(BaseMultipartGeometry):
'coordinates': tuple(tuple(c for c in g.coords) for g in self.geoms)
}
+ def svg(self, scale_factor=1.):
+ """
+ SVG representation of the geometry. Scale factor is multiplied by
+ the size of the SVG symbol so it can be scaled consistently for a
+ consistent appearance based on the canvas size.
+ """
+ parts = []
+ for part in self.geoms:
+ pnt_format = " ".join(["{0},{1}".format(*c) for c in part.coords])
+ parts.append("""<polyline
+ fill="none"
+ stroke="{2}"
+ stroke-width={1}
+ points="{0}"
+ opacity=".8"
+ />""".format(
+ pnt_format,
+ 2.*scale_factor,
+ "#66cc99" if self.is_valid else "#ff3333"))
+ return "\n".join(parts)
+
class MultiLineStringAdapter(CachingGeometryProxy, MultiLineString):
context = None
- _owned = False
+ _other_owned = False
def __init__(self, context):
self.context = context
@@ -90,7 +111,12 @@ def asMultiLineString(context):
def geos_multilinestring_from_py(ob):
- # ob must be either a sequence or array of sequences or arrays
+ # ob must be either a MultiLineString, a sequence, or
+ # array of sequences or arrays
+
+ if isinstance(ob, MultiLineString):
+ return geos_geom_from_py(ob)
+
try:
# From array protocol
array = ob.__array_interface__
@@ -102,9 +128,14 @@ def geos_multilinestring_from_py(ob):
subs = (c_void_p * L)()
for l in range(L):
- geom, ndims = geos_linestring_from_py(array['data'][l])
+ geom, ndims = linestring.geos_linestring_from_py(array['data'][l])
subs[i] = cast(geom, c_void_p)
- N = lgeos.GEOSGeom_getDimensions(subs[0])
+
+ if lgeos.GEOSHasZ(subs[0]):
+ N = 3
+ else:
+ N = 2
+
except (NotImplementedError, AttributeError):
obs = getattr(ob, 'geoms', ob)
L = len(obs)
@@ -121,7 +152,7 @@ def geos_multilinestring_from_py(ob):
# add to coordinate sequence
for l in range(L):
- geom, ndims = geos_linestring_from_py(obs[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)
diff --git a/shapely/geometry/multipoint.py b/shapely/geometry/multipoint.py
index 1e43117..633c65b 100644
--- a/shapely/geometry/multipoint.py
+++ b/shapely/geometry/multipoint.py
@@ -11,8 +11,10 @@ from ctypes import ArgumentError
from shapely.coords import required
from shapely.geos import lgeos
-from shapely.geometry.base import BaseMultipartGeometry, exceptNull
-from shapely.geometry.point import Point, geos_point_from_py
+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']
@@ -58,7 +60,7 @@ class MultiPoint(BaseMultipartGeometry):
self._geom, self._ndim = geos_multipoint_from_py(points)
def shape_factory(self, *args):
- return Point(*args)
+ return point.Point(*args)
@property
def __geo_interface__(self):
@@ -67,6 +69,29 @@ class MultiPoint(BaseMultipartGeometry):
'coordinates': tuple([g.coords[0] for g in self.geoms])
}
+ def svg(self, scale_factor=1.):
+ """
+ SVG representation of the geometry. Scale factor is multiplied by
+ the size of the SVG symbol so it can be scaled consistently for a
+ consistent appearance based on the canvas size.
+ """
+
+ parts = []
+ for part in self.geoms:
+ parts.append("""<circle
+ cx="{0.x}"
+ cy="{0.y}"
+ r="{1}"
+ stroke="#555555"
+ stroke-width="{2}"
+ fill="{3}"
+ opacity=".6"
+ />""".format(
+ part,
+ 3*scale_factor,
+ 1*scale_factor, "#66cc99" if self.is_valid else "#ff3333"))
+ return "\n".join(parts)
+
@property
@exceptNull
def ctypes(self):
@@ -101,7 +126,7 @@ class MultiPoint(BaseMultipartGeometry):
class MultiPointAdapter(CachingGeometryProxy, MultiPoint):
context = None
- _owned = False
+ _other_owned = False
def __init__(self, context):
self.context = context
@@ -134,6 +159,9 @@ def asMultiPoint(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)
@@ -157,7 +185,7 @@ def geos_multipoint_from_py(ob):
subs = (c_void_p * m)()
for i in range(m):
- geom, ndims = geos_point_from_py(cp[n*i:n*i+2])
+ geom, ndims = point.geos_point_from_py(cp[n*i:n*i+2])
subs[i] = cast(geom, c_void_p)
except AttributeError:
@@ -175,7 +203,7 @@ def geos_multipoint_from_py(ob):
# add to coordinate sequence
for i in range(m):
coords = ob[i]
- geom, ndims = geos_point_from_py(coords)
+ geom, ndims = point.geos_point_from_py(coords)
subs[i] = cast(geom, c_void_p)
return lgeos.GEOSGeom_createCollection(4, subs, m), n
diff --git a/shapely/geometry/multipolygon.py b/shapely/geometry/multipolygon.py
index e2d0f58..3e645de 100644
--- a/shapely/geometry/multipolygon.py
+++ b/shapely/geometry/multipolygon.py
@@ -9,8 +9,8 @@ if sys.version_info[0] < 3:
from ctypes import c_void_p, cast
from shapely.geos import lgeos
-from shapely.geometry.base import BaseMultipartGeometry
-from shapely.geometry.polygon import Polygon, geos_polygon_from_py
+from shapely.geometry.base import BaseMultipartGeometry, geos_geom_from_py
+from shapely.geometry import polygon
from shapely.geometry.proxy import CachingGeometryProxy
__all__ = ['MultiPolygon', 'asMultiPolygon']
@@ -64,7 +64,7 @@ class MultiPolygon(BaseMultipartGeometry):
self._geom, self._ndim = geos_multipolygon_from_py(polygons)
def shape_factory(self, *args):
- return Polygon(*args)
+ return polygon.Polygon(*args)
@property
def __geo_interface__(self):
@@ -80,11 +80,35 @@ class MultiPolygon(BaseMultipartGeometry):
'coordinates': allcoords
}
+ def svg(self, scale_factor=1.):
+ """
+ SVG representation of the geometry. Scale factor is multiplied by
+ the size of the SVG symbol so it can be scaled consistently for a
+ consistent appearance based on the canvas size.
+ """
+ parts = []
+ for part in self.geoms:
+ exterior_coords = [["{0},{1}".format(*c) for c in part.exterior.coords]]
+ interior_coords = [
+ ["{0},{1}".format(*c) for c in interior.coords]
+ for interior in part.interiors ]
+ path = " ".join([
+ "M {0} L {1} z".format(coords[0], " L ".join(coords[1:]))
+ for coords in exterior_coords + interior_coords ])
+ parts.append(
+ """<g fill-rule="evenodd" fill="{2}" stroke="#555555"
+ stroke-width="{0}" opacity="0.6">
+ <path d="{1}" /></g>""".format(
+ 2. * scale_factor,
+ path,
+ "#66cc99" if self.is_valid else "#ff3333"))
+ return "\n".join(parts)
+
class MultiPolygonAdapter(CachingGeometryProxy, MultiPolygon):
context = None
- _owned = False
+ _other_owned = False
def __init__(self, context, context_type='polygons'):
self.context = context
@@ -121,13 +145,21 @@ def geos_multipolygon_from_py(ob):
subs = (c_void_p * L)()
for l in range(L):
- geom, ndims = geos_polygon_from_py(ob[l][0], ob[l][1:])
+ 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 sequence or array of sequences or arrays."""
+ """
+ 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
@@ -148,7 +180,7 @@ def geos_multipolygon_from_polygons(ob):
holes = getattr(obs[l], 'interiors', None)
if holes is None:
holes = obs[l][1]
- geom, ndims = geos_polygon_from_py(shell, holes)
+ geom, ndims = polygon.geos_polygon_from_py(shell, holes)
subs[l] = cast(geom, c_void_p)
return (lgeos.GEOSGeom_createCollection(6, subs, L), N)
diff --git a/shapely/geometry/point.py b/shapely/geometry/point.py
index e8f0605..d7660d6 100644
--- a/shapely/geometry/point.py
+++ b/shapely/geometry/point.py
@@ -6,7 +6,7 @@ from ctypes import cast, POINTER
from shapely.coords import required
from shapely.geos import lgeos, DimensionError
-from shapely.geometry.base import BaseGeometry
+from shapely.geometry.base import BaseGeometry, geos_geom_from_py
from shapely.geometry.proxy import CachingGeometryProxy
__all__ = ['Point', 'asPoint']
@@ -74,6 +74,26 @@ class Point(BaseGeometry):
'coordinates': self.coords[0]
}
+ def svg(self, scale_factor=1.):
+ """
+ SVG representation of the geometry. Scale factor is multiplied by
+ the size of the SVG symbol so it can be scaled consistently for a
+ consistent appearance based on the canvas size.
+ """
+ return """<circle
+ cx="{0.x}"
+ cy="{0.y}"
+ r="{1}"
+ stroke="#555555"
+ stroke-width="{2}"
+ fill="{3}"
+ opacity=".6"
+ />""".format(
+ self,
+ 3 * scale_factor,
+ 1 * scale_factor,
+ "#66cc99" if self.is_valid else "#ff3333")
+
@property
def ctypes(self):
if not self._ctypes_data:
@@ -126,7 +146,7 @@ class Point(BaseGeometry):
class PointAdapter(CachingGeometryProxy, Point):
- _owned = False
+ _other_owned = False
def __init__(self, context):
self.context = context
@@ -166,11 +186,14 @@ def asPoint(context):
def geos_point_from_py(ob, update_geom=None, update_ndim=0):
- """Create a GEOS geom from an object that is a coordinate sequence
+ """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)
diff --git a/shapely/geometry/polygon.py b/shapely/geometry/polygon.py
index d4e1f43..4e6355b 100644
--- a/shapely/geometry/polygon.py
+++ b/shapely/geometry/polygon.py
@@ -13,7 +13,7 @@ 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
+from shapely.geometry.base import BaseGeometry, geos_geom_from_py
from shapely.geometry.linestring import LineString, LineStringAdapter
from shapely.geometry.proxy import PolygonProxy
@@ -178,7 +178,7 @@ class InteriorRingSequence(object):
ring = LinearRing()
ring.__geom__ = g
ring.__p__ = self
- ring._owned = True
+ ring._other_owned = True
ring._ndim = self._ndim
self.__rings__[i] = weakref.ref(ring)
return self.__rings__[i]()
@@ -237,7 +237,7 @@ class Polygon(BaseGeometry):
ring = LinearRing()
ring.__geom__ = g
ring.__p__ = self
- ring._owned = True
+ ring._other_owned = True
ring._ndim = self._ndim
self._exterior = weakref.ref(ring)
return self._exterior()
@@ -282,6 +282,26 @@ class Polygon(BaseGeometry):
'coordinates': tuple(coords)
}
+ def svg(self, scale_factor=1.):
+ """
+ SVG representation of the geometry. Scale factor is multiplied by
+ the size of the SVG symbol so it can be scaled consistently for a
+ consistent appearance based on the canvas size.
+ """
+ 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 """
+ <g fill-rule="evenodd" fill="{2}" stroke="#555555"
+ stroke-width="{0}" opacity="0.6">
+ <path d="{1}" />
+ </g>""".format(
+ 2.*scale_factor, path, "#66cc99" if self.is_valid else "#ff3333")
+
class PolygonAdapter(PolygonProxy, Polygon):
@@ -324,6 +344,15 @@ def orient(polygon, sign=1.0):
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)
@@ -441,6 +470,9 @@ 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:
diff --git a/shapely/geometry/proxy.py b/shapely/geometry/proxy.py
index a89c690..c9cad3a 100644
--- a/shapely/geometry/proxy.py
+++ b/shapely/geometry/proxy.py
@@ -19,10 +19,10 @@ class CachingGeometryProxy(object):
def _is_empty(self):
return self.__geom__ in [EMPTY, None]
- def empty(self):
- if not self._is_empty:
+ def empty(self, val=EMPTY):
+ if not self._is_empty and self.__geom__:
lgeos.GEOSGeom_destroy(self.__geom__)
- self.__geom__ = EMPTY
+ self.__geom__ = val
@property
def _geom(self):
diff --git a/shapely/geos.py b/shapely/geos.py
index 5cb15a4..6061ed3 100644
--- a/shapely/geos.py
+++ b/shapely/geos.py
@@ -36,19 +36,22 @@ else:
def load_dll(libname, fallbacks=None):
lib = find_library(libname)
if lib is not None:
- return CDLL(lib)
- else:
- if fallbacks is not None:
- for name in fallbacks:
- try:
- return CDLL(name)
- except OSError:
- # move on to the next fallback
- pass
- # the end
- raise OSError(
- "Could not find library %s or load any of its variants %s" % (
- libname, fallbacks or []))
+ try:
+ return CDLL(lib)
+ except OSError:
+ pass
+ if fallbacks is not None:
+ for name in fallbacks:
+ try:
+ return CDLL(name)
+ except OSError:
+ # move on to the next fallback
+ pass
+ # No shared library was loaded. Raise OSError.
+ raise OSError(
+ "Could not find library %s or load any of its variants %s" % (
+ libname, fallbacks or []))
+
if sys.platform.startswith('linux'):
_lgeos = load_dll('geos_c', fallbacks=['libgeos_c.so.1', 'libgeos_c.so'])
@@ -542,6 +545,7 @@ class LGEOS300(LGEOSBase):
self.GEOSisValid,
self.GEOSisSimple,
self.GEOSisRing,
+ self.GEOSisClosed,
self.GEOSHasZ):
pred.errcheck = errcheck_predicate
@@ -557,6 +561,7 @@ class LGEOS300(LGEOSBase):
self.methods['has_z'] = self.GEOSHasZ
self.methods['is_empty'] = self.GEOSisEmpty
self.methods['is_ring'] = self.GEOSisRing
+ self.methods['is_closed'] = self.GEOSisClosed
self.methods['is_simple'] = self.GEOSisSimple
self.methods['is_valid'] = self.GEOSisValid
self.methods['disjoint'] = self.GEOSDisjoint
@@ -617,6 +622,7 @@ class LGEOS310(LGEOSBase):
self.GEOSisValid,
self.GEOSisSimple,
self.GEOSisRing,
+ self.GEOSisClosed,
self.GEOSHasZ):
pred.func.errcheck = errcheck_predicate
@@ -634,6 +640,7 @@ class LGEOS310(LGEOSBase):
self.methods['has_z'] = self.GEOSHasZ
self.methods['is_empty'] = self.GEOSisEmpty
self.methods['is_ring'] = self.GEOSisRing
+ self.methods['is_closed'] = self.GEOSisClosed
self.methods['is_simple'] = self.GEOSisSimple
self.methods['is_valid'] = self.GEOSisValid
self.methods['disjoint'] = self.GEOSDisjoint
@@ -710,7 +717,21 @@ class LGEOS330(LGEOS320):
self.methods['cascaded_union'] = self.methods['unary_union']
-if geos_version >= (3, 3, 0):
+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
@@ -723,7 +744,7 @@ else:
lgeos = L(_lgeos)
+def cleanup(proxy):
+ del proxy
- at atexit.register
-def cleanup():
- lgeos.__del__()
+atexit.register(cleanup, lgeos)
diff --git a/shapely/impl.py b/shapely/impl.py
index c71518e..bc804b7 100644
--- a/shapely/impl.py
+++ b/shapely/impl.py
@@ -71,6 +71,7 @@ IMPL300 = {
'has_z': (UnaryPredicate, 'has_z'),
'is_empty': (UnaryPredicate, 'is_empty'),
'is_ring': (UnaryPredicate, 'is_ring'),
+ 'is_closed': (UnaryPredicate, 'is_closed'),
'is_simple': (UnaryPredicate, 'is_simple'),
'is_valid': (UnaryPredicate, 'is_valid'),
#
diff --git a/shapely/ops.py b/shapely/ops.py
index 1f2387a..fa5be14 100644
--- a/shapely/ops.py
+++ b/shapely/ops.py
@@ -8,14 +8,14 @@ if sys.version_info[0] < 3:
else:
izip = zip
-from ctypes import byref, c_void_p
+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
+from shapely.geometry import asShape, asLineString, asMultiLineString, Point
__all__ = ['cascaded_union', 'linemerge', 'operator', 'polygonize',
- 'polygonize_full', 'transform', 'unary_union']
+ 'polygonize_full', 'transform', 'unary_union', 'triangulate']
class CollectionOperator(object):
@@ -36,7 +36,12 @@ class CollectionOperator(object):
or a sequence of objects than can be adapted to LineStrings.
"""
source = getattr(lines, 'geoms', None) or lines
- obs = [self.shapeup(l) for l in source]
+ 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):
@@ -65,8 +70,12 @@ class CollectionOperator(object):
(bowties, etc).
"""
source = getattr(lines, 'geoms', None) or lines
- obs = [self.shapeup(l) for l in source]
-
+ 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):
@@ -109,7 +118,11 @@ class CollectionOperator(object):
This is the most efficient method of dissolving many polygons.
"""
- L = len(geoms)
+ 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
@@ -123,7 +136,11 @@ class CollectionOperator(object):
prefered method for dissolving many polygons.
"""
- L = len(geoms)
+ 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
@@ -138,6 +155,25 @@ 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)
@@ -170,7 +206,7 @@ def transform(func, geom):
project = partial(
pyproj.transform,
- pyproj.Proj(init='espg:4326'),
+ pyproj.Proj(init='epsg:4326'),
pyproj.Proj(init='epsg:26913'))
g2 = transform(project, g1)
@@ -183,14 +219,14 @@ def transform(func, geom):
"""
if geom.is_empty:
return geom
- if geom.type in ('Point', 'LineString', 'Polygon'):
+ 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'):
+ if geom.type in ('Point', 'LineString', 'LinearRing'):
return type(geom)(zip(*func(*izip(*geom.coords))))
elif geom.type == 'Polygon':
shell = type(geom.exterior)(
@@ -202,7 +238,7 @@ def transform(func, geom):
# 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'):
+ 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)(
@@ -215,3 +251,27 @@ def transform(func, geom):
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)
diff --git a/shapely/speedups/__init__.py b/shapely/speedups/__init__.py
index a3d3e5f..a472aaf 100644
--- a/shapely/speedups/__init__.py
+++ b/shapely/speedups/__init__.py
@@ -13,6 +13,12 @@ except ImportError:
# 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 = {}
@@ -27,6 +33,9 @@ def enable():
_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
@@ -38,6 +47,7 @@ def disable():
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']
_orig.clear()
diff --git a/shapely/speedups/_speedups.pyx b/shapely/speedups/_speedups.pyx
index 6ba3623..bf3d505 100644
--- a/shapely/speedups/_speedups.pyx
+++ b/shapely/speedups/_speedups.pyx
@@ -7,29 +7,16 @@
import ctypes
from shapely.geos import lgeos
+from shapely.geometry import Point, LineString, LinearRing
-cdef extern from "geos_c.h":
- ctypedef struct GEOSCoordSequence
- ctypedef struct GEOSGeometry
- cdef struct GEOSContextHandle_HS
- GEOSCoordSequence *GEOSCoordSeq_create_r(GEOSContextHandle_HS *,double, double)
- GEOSCoordSequence *GEOSGeom_getCoordSeq_r(GEOSContextHandle_HS *, GEOSGeometry *)
- int GEOSCoordSeq_getSize_r(GEOSContextHandle_HS *, GEOSCoordSequence *, int *)
- int GEOSCoordSeq_setX_r(GEOSContextHandle_HS *, GEOSCoordSequence *, int, double)
- int GEOSCoordSeq_setY_r(GEOSContextHandle_HS *, GEOSCoordSequence *, int, double)
- int GEOSCoordSeq_setZ_r(GEOSContextHandle_HS *, GEOSCoordSequence *, int, double)
- int GEOSCoordSeq_getX_r(GEOSContextHandle_HS *, GEOSCoordSequence *, int, double *)
- int GEOSCoordSeq_getY_r(GEOSContextHandle_HS *, GEOSCoordSequence *, int, double *)
- int GEOSCoordSeq_getZ_r(GEOSContextHandle_HS *, GEOSCoordSequence *, int, double *)
- GEOSGeometry *GEOSGeom_createLineString_r(GEOSContextHandle_HS *, GEOSCoordSequence *)
- GEOSGeometry *GEOSGeom_createLinearRing_r(GEOSContextHandle_HS *, GEOSCoordSequence *)
- void GEOSGeom_destroy_r(GEOSContextHandle_HS *, GEOSGeometry *)
+include "../_geos.pxi"
+
cdef inline GEOSGeometry *cast_geom(unsigned long geom_addr):
return <GEOSGeometry *>geom_addr
-cdef inline GEOSContextHandle_HS *cast_handle(unsigned long handle_addr):
- return <GEOSContextHandle_HS *>handle_addr
+cdef inline GEOSContextHandle_t cast_handle(unsigned long handle_addr):
+ return <GEOSContextHandle_t>handle_addr
cdef inline GEOSCoordSequence *cast_seq(unsigned long handle_addr):
return <GEOSCoordSequence *>handle_addr
@@ -39,10 +26,28 @@ def destroy(geom):
def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
cdef double *cp
- cdef GEOSContextHandle_HS *handle = cast_handle(lgeos.geos_handle)
+ 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
+ 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 <unsigned long>GEOSGeom_clone_r(handle, g), n
+ else:
+ cs = GEOSGeom_getCoordSeq_r(handle, g)
+ cs = GEOSCoordSeq_clone_r(handle, cs)
+ return <unsigned long>GEOSGeom_createLineString_r(handle, cs), n
+
try:
# From array protocol
array = ob.__array_interface__
@@ -64,6 +69,17 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
else:
cp = <double *><unsigned long>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))
@@ -76,11 +92,11 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
# add to coordinate sequence
for i in xrange(m):
- dx = cp[n*i]
- dy = cp[n*i+1]
+ dx = cp[sm*i]
+ dy = cp[sm*i+sn]
dz = 0
if n == 3:
- dz = cp[n*i+2]
+ dz = cp[sm*i+2*sn]
# Because of a bug in the GEOS C API,
# always set X before Y
@@ -88,15 +104,26 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
GEOSCoordSeq_setY_r(handle, cs, i, dy)
if n == 3:
GEOSCoordSeq_setZ_r(handle, cs, i, dz)
-
+
except AttributeError:
# Fall back on list
- m = len(ob)
+ 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(ob[0])
+ n = len(_coords(ob[0]))
except TypeError:
raise ValueError(
"Input %s is the wrong shape for a LineString" % str(ob))
@@ -114,7 +141,7 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
# add to coordinate sequence
for i in xrange(m):
- coords = ob[i]
+ coords = _coords(ob[i])
dx = coords[0]
dy = coords[1]
dz = 0
@@ -138,10 +165,30 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
cdef double *cp
- cdef GEOSContextHandle_HS *handle = cast_handle(lgeos.geos_handle)
+ cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle)
+ cdef GEOSGeometry *g
cdef GEOSCoordSequence *cs
cdef double dx, dy, dz
- cdef int i, n, m, M
+ cdef int i, n, m, 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 <unsigned long>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 <unsigned long>GEOSGeom_createLinearRing_r(handle, cs), n
+
try:
# From array protocol
array = ob.__array_interface__
@@ -159,8 +206,21 @@ def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
else:
cp = <double *><unsigned long>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?
- if cp[0] != cp[m*n-n] or cp[1] != cp[m*n-n+1]:
+ # 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
@@ -177,11 +237,11 @@ def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
# add to coordinate sequence
for i in xrange(m):
- dx = cp[n*i]
- dy = cp[n*i+1]
+ dx = cp[sm*i]
+ dy = cp[sm*i+sn]
dz = 0
if n == 3:
- dz = cp[n*i+2]
+ dz = cp[sm*i+2*sn]
# Because of a bug in the GEOS C API,
# always set X before Y
@@ -193,10 +253,10 @@ def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
# Add closing coordinates to sequence?
if M > m:
dx = cp[0]
- dy = cp[1]
+ dy = cp[sn]
dz = 0
if n == 3:
- dz = cp[2]
+ dz = cp[2*sn]
# Because of a bug in the GEOS C API,
# always set X before Y
@@ -207,7 +267,11 @@ def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
except AttributeError:
# Fall back on list
- m = len(ob)
+ 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(
@@ -271,7 +335,7 @@ def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
def coordseq_ctypes(self):
cdef int i, n, m
cdef double temp = 0
- cdef GEOSContextHandle_HS *handle = cast_handle(lgeos.geos_handle)
+ cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle)
cdef GEOSCoordSequence *cs
cdef double *data_p
self._update()
@@ -293,3 +357,25 @@ def coordseq_ctypes(self):
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)
diff --git a/shapely/strtree.py b/shapely/strtree.py
new file mode 100644
index 0000000..3c0235e
--- /dev/null
+++ b/shapely/strtree.py
@@ -0,0 +1,59 @@
+from 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()
diff --git a/shapely/tests/__init__.py b/shapely/tests/__init__.py
deleted file mode 100755
index a714388..0000000
--- a/shapely/tests/__init__.py
+++ /dev/null
@@ -1,81 +0,0 @@
-import sys
-from shapely.geos import geos_version_string, lgeos, WKTWriter
-from shapely import speedups
-
-try:
- import numpy
- numpy_version = numpy.version.version
-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
-
-from . import test_doctests, test_prepared, test_equality, test_geomseq, \
- test_point, test_linestring, test_polygon, test_multipoint, \
- test_multilinestring, test_multipolygon, test_geointerface, test_locale, \
- test_xy, test_persist, test_collection, test_emptiness, test_singularity, \
- test_validation, test_mapping, test_delegated, test_dlls, \
- test_linear_referencing, test_products_z, test_box, test_speedups, \
- test_cga, test_getitem, test_ndarrays, test_pickle, \
- test_affinity, test_transform, test_invalid_geometries, test_styles, \
- test_operations, test_operators, test_iterops, test_predicates, \
- test_linemerge, test_polygonize, test_union
-
-
-def test_suite():
- suite = unittest.TestSuite()
- suite.addTest(test_point.test_suite())
- suite.addTest(test_linestring.test_suite())
- suite.addTest(test_polygon.test_suite())
- suite.addTest(test_multipoint.test_suite())
- suite.addTest(test_multilinestring.test_suite())
- suite.addTest(test_multipolygon.test_suite())
- suite.addTest(test_doctests.test_suite())
- suite.addTest(test_prepared.test_suite())
- suite.addTest(test_emptiness.test_suite())
- suite.addTest(test_equality.test_suite())
- suite.addTest(test_geomseq.test_suite())
- suite.addTest(test_geointerface.test_suite())
- suite.addTest(test_locale.test_suite())
- suite.addTest(test_xy.test_suite())
- suite.addTest(test_persist.test_suite())
- suite.addTest(test_collection.test_suite())
- suite.addTest(test_singularity.test_suite())
- suite.addTest(test_validation.test_suite())
- suite.addTest(test_mapping.test_suite())
- suite.addTest(test_delegated.test_suite())
- suite.addTest(test_dlls.test_suite())
- suite.addTest(test_linear_referencing.test_suite())
- suite.addTest(test_products_z.test_suite())
- suite.addTest(test_box.test_suite())
- suite.addTest(test_speedups.test_suite())
- suite.addTest(test_cga.test_suite())
- suite.addTest(test_getitem.test_suite())
- suite.addTest(test_ndarrays.test_suite())
- suite.addTest(test_pickle.test_suite())
- suite.addTest(test_affinity.test_suite())
- suite.addTest(test_transform.test_suite())
- suite.addTest(test_invalid_geometries.test_suite())
- suite.addTest(test_styles.test_suite())
- suite.addTest(test_operations.test_suite())
- suite.addTest(test_operators.test_suite())
- suite.addTest(test_iterops.test_suite())
- suite.addTest(test_predicates.test_suite())
- suite.addTest(test_linemerge.test_suite())
- suite.addTest(test_polygonize.test_suite())
- suite.addTest(test_union.test_suite())
- return suite
diff --git a/shapely/tests/test_speedups.py b/shapely/tests/test_speedups.py
deleted file mode 100644
index 705d0ab..0000000
--- a/shapely/tests/test_speedups.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from . import unittest
-
-from shapely import speedups
-from shapely.geometry import LineString, Polygon
-
-
-class SpeedupsTestCase(unittest.TestCase):
-
- def setUp(self):
- self.assertFalse(speedups._orig)
- if speedups.available:
- speedups.enable()
- self.assertTrue(speedups._orig)
-
- def tearDown(self):
- if speedups.available:
- self.assertTrue(speedups._orig)
- speedups.disable()
- self.assertFalse(speedups._orig)
-
- @unittest.skipIf(not speedups.available, 'speedups not available')
- def test_create_linestring(self):
- ls = LineString([(0, 0), (1, 0), (1, 2)])
- self.assertEqual(ls.length, 3)
-
- @unittest.skipIf(not speedups.available, 'speedups not available')
- def test_create_polygon(self):
- p = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
- self.assertEqual(p.length, 8)
-
- @unittest.skipIf(not speedups.available, 'speedups not available')
- def test_create_polygon_from_linestring(self):
- ls = LineString([(0, 0), (2, 0), (2, 2), (0, 2)])
- p = Polygon(ls)
- self.assertEqual(p.length, 8)
-
-
-def test_suite():
- return unittest.TestLoader().loadTestsFromTestCase(SpeedupsTestCase)
diff --git a/shapely/vectorized/__init__.py b/shapely/vectorized/__init__.py
new file mode 100644
index 0000000..8898712
--- /dev/null
+++ b/shapely/vectorized/__init__.py
@@ -0,0 +1,3 @@
+"""Provides multi-point element-wise operations such as ``contains``."""
+
+from ._vectorized import (contains, touches)
diff --git a/shapely/vectorized/_vectorized.pyx b/shapely/vectorized/_vectorized.pyx
new file mode 100644
index 0000000..4586823
--- /dev/null
+++ b/shapely/vectorized/_vectorized.pyx
@@ -0,0 +1,120 @@
+import 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)
+
+
+
+ at cython.boundscheck(False)
+ at 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] = <np.uint8_t> fn(geos_h, geos_geom, p)
+ GEOSGeom_destroy_r(geos_h, p)
+
+ return result
diff --git a/shapely/wkt.py b/shapely/wkt.py
index 51ffbfa..a6f3a46 100644
--- a/shapely/wkt.py
+++ b/shapely/wkt.py
@@ -14,12 +14,12 @@ def load(fp):
data = fp.read()
return loads(data)
-def dumps(ob, **kw):
+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, **kw).write(ob)
+ return geos.WKTWriter(geos.lgeos, trim=trim, **kw).write(ob)
def dump(ob, fp, **settings):
"""Dump a geometry to an open file."""
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100755
index 0000000..b4cf8ab
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,25 @@
+import sys
+from shapely.geos import geos_version_string, lgeos, WKTWriter
+from shapely import speedups
+
+try:
+ import numpy
+ numpy_version = numpy.version.version
+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
diff --git a/shapely/tests/binascii_hex.txt b/tests/binascii_hex.txt
similarity index 100%
rename from shapely/tests/binascii_hex.txt
rename to tests/binascii_hex.txt
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..3fe9d4e
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,16 @@
+import 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)
diff --git a/shapely/tests/rungrind.dist b/tests/rungrind.dist
similarity index 100%
rename from shapely/tests/rungrind.dist
rename to tests/rungrind.dist
diff --git a/shapely/tests/test_affinity.py b/tests/test_affinity.py
similarity index 98%
rename from shapely/tests/test_affinity.py
rename to tests/test_affinity.py
index 1f3b9ba..f997e0a 100755
--- a/shapely/tests/test_affinity.py
+++ b/tests/test_affinity.py
@@ -51,6 +51,10 @@ class AffineTestCase(unittest.TestCase):
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))'))
diff --git a/shapely/tests/test_box.py b/tests/test_box.py
similarity index 100%
rename from shapely/tests/test_box.py
rename to tests/test_box.py
diff --git a/shapely/tests/test_cga.py b/tests/test_cga.py
similarity index 100%
rename from shapely/tests/test_cga.py
rename to tests/test_cga.py
diff --git a/shapely/tests/test_collection.py b/tests/test_collection.py
similarity index 100%
rename from shapely/tests/test_collection.py
rename to tests/test_collection.py
diff --git a/tests/test_delaunay.py b/tests/test_delaunay.py
new file mode 100644
index 0000000..ef91780
--- /dev/null
+++ b/tests/test_delaunay.py
@@ -0,0 +1,43 @@
+try:
+ 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
+
+ at 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()
diff --git a/shapely/tests/test_delegated.py b/tests/test_delegated.py
similarity index 100%
rename from shapely/tests/test_delegated.py
rename to tests/test_delegated.py
diff --git a/shapely/tests/test_dlls.py b/tests/test_dlls.py
similarity index 100%
rename from shapely/tests/test_dlls.py
rename to tests/test_dlls.py
diff --git a/shapely/tests/test_doctests.py b/tests/test_doctests.py
similarity index 100%
rename from shapely/tests/test_doctests.py
rename to tests/test_doctests.py
diff --git a/shapely/tests/test_emptiness.py b/tests/test_emptiness.py
similarity index 100%
rename from shapely/tests/test_emptiness.py
rename to tests/test_emptiness.py
diff --git a/shapely/tests/test_equality.py b/tests/test_equality.py
similarity index 100%
rename from shapely/tests/test_equality.py
rename to tests/test_equality.py
diff --git a/shapely/tests/test_geointerface.py b/tests/test_geointerface.py
similarity index 100%
rename from shapely/tests/test_geointerface.py
rename to tests/test_geointerface.py
diff --git a/shapely/tests/test_geomseq.py b/tests/test_geomseq.py
similarity index 100%
rename from shapely/tests/test_geomseq.py
rename to tests/test_geomseq.py
diff --git a/shapely/tests/test_getitem.py b/tests/test_getitem.py
similarity index 100%
rename from shapely/tests/test_getitem.py
rename to tests/test_getitem.py
diff --git a/shapely/tests/test_invalid_geometries.py b/tests/test_invalid_geometries.py
similarity index 100%
rename from shapely/tests/test_invalid_geometries.py
rename to tests/test_invalid_geometries.py
diff --git a/shapely/tests/test_iterops.py b/tests/test_iterops.py
similarity index 100%
rename from shapely/tests/test_iterops.py
rename to tests/test_iterops.py
diff --git a/shapely/tests/test_linear_referencing.py b/tests/test_linear_referencing.py
similarity index 100%
rename from shapely/tests/test_linear_referencing.py
rename to tests/test_linear_referencing.py
diff --git a/shapely/tests/test_linemerge.py b/tests/test_linemerge.py
similarity index 100%
rename from shapely/tests/test_linemerge.py
rename to tests/test_linemerge.py
diff --git a/shapely/tests/test_linestring.py b/tests/test_linestring.py
similarity index 69%
rename from shapely/tests/test_linestring.py
rename to tests/test_linestring.py
index ab9fc47..d7c7d4e 100644
--- a/shapely/tests/test_linestring.py
+++ b/tests/test_linestring.py
@@ -1,5 +1,6 @@
from . import unittest, numpy
-from shapely.geometry import LineString, asLineString
+from shapely.geos import lgeos
+from shapely.geometry import LineString, asLineString, Point, LinearRing
class LineStringTestCase(unittest.TestCase):
@@ -11,10 +12,15 @@ class LineStringTestCase(unittest.TestCase):
self.assertEqual(len(line.coords), 2)
self.assertEqual(line.coords[:], [(1.0, 2.0), (3.0, 4.0)])
- # From lines
- copy = LineString(line)
- self.assertEqual(len(copy.coords), 2)
- self.assertEqual(copy.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))
@@ -51,6 +57,36 @@ class LineStringTestCase(unittest.TestCase):
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):
diff --git a/shapely/tests/test_locale.py b/tests/test_locale.py
similarity index 100%
rename from shapely/tests/test_locale.py
rename to tests/test_locale.py
diff --git a/shapely/tests/test_mapping.py b/tests/test_mapping.py
similarity index 100%
rename from shapely/tests/test_mapping.py
rename to tests/test_mapping.py
diff --git a/shapely/tests/test_multilinestring.py b/tests/test_multilinestring.py
similarity index 76%
rename from shapely/tests/test_multilinestring.py
rename to tests/test_multilinestring.py
index f4930b9..61b6bc3 100644
--- a/shapely/tests/test_multilinestring.py
+++ b/tests/test_multilinestring.py
@@ -1,11 +1,12 @@
from . import unittest, numpy
+from shapely.geos import lgeos
from shapely.geometry import LineString, MultiLineString, asMultiLineString
from shapely.geometry.base import dump_coords
class MultiLineStringTestCase(unittest.TestCase):
- def test_multipoint(self):
+ def test_multilinestring(self):
# From coordinate tuples
geom = MultiLineString((((1.0, 2.0), (3.0, 4.0)),))
@@ -36,6 +37,22 @@ class MultiLineStringTestCase(unittest.TestCase):
{'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):
diff --git a/shapely/tests/test_multipoint.py b/tests/test_multipoint.py
similarity index 100%
rename from shapely/tests/test_multipoint.py
rename to tests/test_multipoint.py
diff --git a/shapely/tests/test_multipolygon.py b/tests/test_multipolygon.py
similarity index 100%
rename from shapely/tests/test_multipolygon.py
rename to tests/test_multipolygon.py
diff --git a/shapely/tests/test_ndarrays.py b/tests/test_ndarrays.py
similarity index 100%
rename from shapely/tests/test_ndarrays.py
rename to tests/test_ndarrays.py
diff --git a/tests/test_nearest.py b/tests/test_nearest.py
new file mode 100644
index 0000000..0d3f13f
--- /dev/null
+++ b/tests/test_nearest.py
@@ -0,0 +1,22 @@
+from . import unittest
+
+from shapely.geometry import Point
+from shapely.geos import geos_version
+from shapely.ops import nearest_points
+
+ at 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()
diff --git a/shapely/tests/test_operations.py b/tests/test_operations.py
similarity index 100%
rename from shapely/tests/test_operations.py
rename to tests/test_operations.py
diff --git a/shapely/tests/test_operators.py b/tests/test_operators.py
similarity index 100%
rename from shapely/tests/test_operators.py
rename to tests/test_operators.py
diff --git a/shapely/tests/test_persist.py b/tests/test_persist.py
similarity index 100%
rename from shapely/tests/test_persist.py
rename to tests/test_persist.py
diff --git a/shapely/tests/test_pickle.py b/tests/test_pickle.py
similarity index 100%
rename from shapely/tests/test_pickle.py
rename to tests/test_pickle.py
diff --git a/shapely/tests/test_point.py b/tests/test_point.py
similarity index 100%
rename from shapely/tests/test_point.py
rename to tests/test_point.py
diff --git a/shapely/tests/test_polygon.py b/tests/test_polygon.py
similarity index 85%
rename from shapely/tests/test_polygon.py
rename to tests/test_polygon.py
index d973786..2d07a3a 100644
--- a/shapely/tests/test_polygon.py
+++ b/tests/test_polygon.py
@@ -2,8 +2,9 @@
"""
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, asLinearRing
+from shapely.geometry.polygon import LinearRing, LineString, asLinearRing
from shapely.geometry.base import dump_coords
@@ -73,6 +74,13 @@ class PolygonTestCase(unittest.TestCase):
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):
@@ -107,6 +115,27 @@ class PolygonTestCase(unittest.TestCase):
# 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):
diff --git a/shapely/tests/test_polygonize.py b/tests/test_polygonize.py
similarity index 100%
rename from shapely/tests/test_polygonize.py
rename to tests/test_polygonize.py
diff --git a/shapely/tests/test_predicates.py b/tests/test_predicates.py
similarity index 100%
rename from shapely/tests/test_predicates.py
rename to tests/test_predicates.py
diff --git a/shapely/tests/test_prepared.py b/tests/test_prepared.py
similarity index 100%
rename from shapely/tests/test_prepared.py
rename to tests/test_prepared.py
diff --git a/shapely/tests/test_products_z.py b/tests/test_products_z.py
similarity index 100%
rename from shapely/tests/test_products_z.py
rename to tests/test_products_z.py
diff --git a/shapely/tests/test_singularity.py b/tests/test_singularity.py
similarity index 100%
rename from shapely/tests/test_singularity.py
rename to tests/test_singularity.py
diff --git a/tests/test_strtree.py b/tests/test_strtree.py
new file mode 100644
index 0000000..7de3921
--- /dev/null
+++ b/tests/test_strtree.py
@@ -0,0 +1,23 @@
+from . import unittest
+
+from shapely.strtree import STRtree
+from shapely.geometry import Point
+from shapely.geos import geos_version
+
+
+ at 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()
diff --git a/shapely/tests/test_styles.py b/tests/test_styles.py
similarity index 100%
rename from shapely/tests/test_styles.py
rename to tests/test_styles.py
diff --git a/shapely/tests/test_transform.py b/tests/test_transform.py
similarity index 91%
rename from shapely/tests/test_transform.py
rename to tests/test_transform.py
index c1b8d94..8c8cb55 100644
--- a/shapely/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -26,6 +26,12 @@ class IdentityTestCase(unittest.TestCase):
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)
diff --git a/shapely/tests/test_union.py b/tests/test_union.py
similarity index 100%
rename from shapely/tests/test_union.py
rename to tests/test_union.py
diff --git a/shapely/tests/test_validation.py b/tests/test_validation.py
similarity index 100%
rename from shapely/tests/test_validation.py
rename to tests/test_validation.py
diff --git a/tests/test_vectorized.py b/tests/test_vectorized.py
new file mode 100644
index 0000000..00673ad
--- /dev/null
+++ b/tests/test_vectorized.py
@@ -0,0 +1,107 @@
+from . import unittest, numpy
+from shapely.geometry import Point, box, MultiPolygon
+from shapely.vectorized import contains, touches
+
+try:
+ import numpy as np
+except ImportError:
+ pass
+
+
+ at 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)
+
+
+ at 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)
diff --git a/shapely/tests/test_xy.py b/tests/test_xy.py
similarity index 100%
rename from shapely/tests/test_xy.py
rename to tests/test_xy.py
diff --git a/shapely/tests/threading_test.py b/tests/threading_test.py
similarity index 100%
rename from shapely/tests/threading_test.py
rename to tests/threading_test.py
diff --git a/shapely/tests/valgrind-python.supp b/tests/valgrind-python.supp
similarity index 100%
rename from shapely/tests/valgrind-python.supp
rename to tests/valgrind-python.supp
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/python-shapely.git
More information about the Pkg-grass-devel
mailing list