| Author: | Sean Gillies |
|---|---|
| Address: | sgillies@frii.com |
| Revision: | 1.0.5 |
| Date: | 20 May 2008 |
| Copyright: | This work is licensed under a Creative Commons Attribution 3.0 United States License. |
| abstract: | This document describes the Shapely Python package for programming with geospatial geometries. |
|---|
Shapely is a Python package for programming with geospatial geometries. It is based on the GEOS library, a port of the Java Topology Suite. Please refer to the JTS overview for definitions and illustrations of the various geometry types, operations, and predicates. See also the Shapely wiki and Python Package Index record.
The basic, standard GIS geometry model consists of single points, line strings, and polygons, homogeneous multi-point, multi-line string, and multi-polygon collections, and heterogeneous geometry collections. See the JTS illustration.
Shapely 1.0 does not provide circles, arcs, splines or the like.
Geometries can be created in the typical Python fashion, using the geometry classes themselves as factories.
Pseudo-code blocks in this section will use the following notation. Let a be a Cartesian x, y, and optional z coordinate sequence. The coordinates values must be numeric types. Let (a1, ..., aM) and (b1, ..., bN) be ordered sequences of M and N such coordinate sequences, defining lines or rings.
The point factory Point takes a coordinate sequence parameter
>>> from shapely.geometry import Point >>> point = Point(a)
The alternate form is to pass individual coordinate parameters
>>> point = Point(x0, y0 [, z0])
To create a line string, pass in an ordered sequence of coordinate sequences:
>>> from shapely.geometry import LineString >>> line = LineString((a1, ..., aM))
A polygon with only an exterior boundary and no holes is created by passing the sequence representation of a closed ring
>>> from shapely.geometry import Polygon >>> polygon = Polygon((a1, ..., aM))
If a1 is not exactly equal to aM, the factory will close the ring. The following (unit square) polygons are therefore topologically equal
>>> polygon1 = Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0))) >>> polygon2 = Polygon(((0, 0), (0, 1), (1, 1), (1, 0)))
To create a polygon with interior boundaries pass a sequence of rings to the second parameter (holes)
>>> polygon = Polygon((a1, ..., aM), [(b1, ..., bN), ...])
Rings must be non-crossing, ordered coordinate sequences. The order may be clockwise or counter-clockwise. The resulting topology is independent of the order.
An N-point geometry is created by passing an unordered sequence of coordinate sequences [c1, ..., cN]
>>> from shapely.geometry import MultiPoint >>> points = MultiPoint([c1, ..., cN])
A multi-line geometry is created by passing a sequence of representations of lines
>>> from shapely.geometry import MultiLineString >>> lines = MultiLineString([(a1, ..., aM), (b1, ..., bN), ...])
A multi-polygon geometry is created by passing a sequence of exterior ring and hole list tuples
>>> from shapely.geometry import MultiPolygon >>> lines = MultiPolygon([((a1, ..., aM), [(b1, ..., bN), ...]), ...])
More explicit notation for the exterior and interior boundaries (or shells and holes) makes usage more clear
>>> shell = (a1, ..., aM) >>> holes = [(b1, ..., bN), ...] >>> lines = MultiPolygon([(shell, holes), ...])
Null geometries can be created by calling the factories with no arguments, but almost nothing can be done with a null geometry.
>>> line_null = LineString() >>> line_null.length Traceback (most recent call last): ... ValueError: Null geometry supports no operations
The coordinates of a null geometry can be set (see Section 3), after which the geometry is no longer null.
>>> l_null.coords = [(0, 0), (1, 1)] >>> print l_null.length 1.414...
There are methods of geometry classes that also serve as factories for new geometries. It is important to note that these are topological and not point-wise operations, and therefore may produce results that are not what one might expect from operations on Python sets.
See also the JTS illustration.
>>> polygon = Polygon(((-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0), (1.0, -1.0))) >>> point_r = Point(-1.5, 1.2) >>> point_g = Point(-1.0, 1.0) >>> point_b = Point(-0.5, 0.5) >>> line_r = LineString(((-0.5, 0.5), (0.5, 0.5))) >>> line_g = LineString(((1.0, -1.0), (1.8, 0.5))) >>> line_b = LineString(((-1.8, -1.2), (1.8, 0.5)))
The default result of buffering a point is an N-gon approximation of a circle:
>>> buffered = point_r.buffer(1.0) >>> buffered <shapely.geometry.polygon.Polygon object at ...> >>> buffered.length 6.2806623139097271 >>> buffered.area 3.1365484905463727 >>> len(buffered.exterior.coords) 66
>>> polygon.boundary <shapely.geometry.linestring.LineString object at ...> >>> line_b.boundary <shapely.geometry.multipoint.MultiPoint object at ...> >>> point_r.boundary.is_empty True
>>> centroid_point = polygon.centroid >>> centroid_point.wkt 'POINT (-0.0000000000000000 -0.0000000000000000)'
For example, collect the three points into a multi-point geometry, and get the triangular polygon that is their convex hull:
>>> multi_point = point_r.union(point_g) >>> multi_point = multi_point.union(point_b) >>> multi_point.convex_hull <shapely.geometry.polygon.Polygon object at ...>
>>> hull = multi_point.convex_hull >>> polygon.difference(hull) <shapely.geometry.polygon.Polygon object at ...>
>>> polygon.envelope <shapely.geometry.polygon.Polygon object at ...>
>>> polygon.intersection(hull) <shapely.geometry.polygon.Polygon object at ...>
>>> polygon.symmetric_difference(hull) <shapely.geometry.multipolygon.MultiPolygon object at ...>
Point unions were demonstrated above under convex hull. The union of polygons will be a polygon or a multi-polygon depending on whether they intersect or not:
>>> hull.union(polygon) <shapely.geometry.polygon.Polygon object at ...>
>>> from shapely.ops import polygonize >>> lines = [ ... ((0, 0), (1, 1)), ... ((0, 0), (0, 1)), ... ((0, 1), (1, 1)), ... ((1, 1), (1, 0)), ... ((1, 0), (0, 0)) ... ] >>> result = polygonize(lines) >>> list(result.geoms) [<shapely.geometry.polygon.Polygon object at ...>, <shapely.geometry.polygon.Polygon object at ...>]
These are implemented as Python attributes.
>>> polygon.is_empty False >>> polygon.is_valid True >>> polygon.is_ring False >>> polygon.boundary.is_ring True >>> polygon.has_z False
(Note: that last return value exposes a bug in GEOS 2.2.3.)
All of these methods take a single positional argument, an other geometry. It is important to note that these are topological and not point-wise operations, and therefore may produce results that are not what one might expect from operations on Python.
>>> polygon.contains(point_b) True
>>> line_b.crosses(polygon) True
>>> polygon.disjoint(point_r) True
>>> polygon.intersects(point_b) True
>>> polygon.touches(line_g) True >>> polygon.touches(line_b) False
>>> Point(0,0).distance(Point(1,1)) 1.4142135623730951
>>> polygon.area 4.0 >>> polygon.bounds (-1.0, -1.0, 1.0, 1.0) >>> polygon.length 8.0 >>> line_r.length 1.0 >>> line_b.length 3.9812058474788765
The coordinates of points, line strings, and polygon rings can be accessed through the coords attribute of a geometry. Coords is an iterator over coordinate tuples.
>>> point_r.coords <shapely.geometry.base.CoordinateSequence object at ...> >>> len(point_r.coords) 1 >>> point_r.coords[0] (-1.5, 1.2) >>> list(point_r.coords) [(-1.5, 1.2)]
The coordinate sequence can be modifed by assigning a sequence (a1, ..., aM) to the coords attribute.
>>> point_new = Point(0, 0) >>> point_new.coords = (1, 1) >>> list(point_new.coords) [(1.0, 1.0)]
For line strings:
>>> line_new = LineString([(0,0), (1,1)]) >>> line_new.coords = [(1,1), (2,2)] >>> list(line_new.coords) [(1.0, 1.0), (2.0, 2.0)]
The exterior boundary of a polygon can be accessed through the exterior attribute of the geometry object.
>>> polygon.exterior <shapely.geometry.polygon.LinearRing object at ...> >>> list(polygon.exterior.coords) [(-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0), (1.0, -1.0), (-1.0, -1.0)]
The interior boundaries (or holes) of a polygon can be accessed through the interiors attribute, which is a list of rings.
The parts of a multi-part geometry can be accessed through the geoms attribute of the geometry object, which is an iterator over the sub-geometries:
>>> multi_point.geoms <shapely.geometry.base.GeometrySequence object at ...> >>> len(multi_point.geoms) 3 >>> from pprint import pprint >>> pprint(list(multi_point.geoms)) [<shapely.geometry.point.Point object at ...>, <shapely.geometry.point.Point object at ...>, <shapely.geometry.point.Point object at ...>]
The coordinate sequences of these sub-geometries can then be accessed as described above.
For the sake of convenience the coordinate values of points can be accessed read-only via x, y, and z attributes:
>>> point = Point(1.0, 1.0) >>> point.x 1.0 >>> point.y 1.0
Shapely provides 4 avenues for interoperation with other Python and GIS software.
The WKT representation of any geometry object can be had via the wkt attribute:
>>> point_r.wkt 'POINT (-1.5000000000000000 1.2000000000000000)'
Hex-encode that string and you have a value that can be conveniently inserted directly into PostGIS
>>> point_r.wkt.encode('hex') '504f494e5420282d312e3530303030303030303030303030303020312e3230303030303030303030303030303029'
New geometries can be created from WKT representations using the shapely.wkt.loads factory (inspired by the pickle module)
>>> from shapely.wkt import loads >>> loads('POINT (0 0)') <shapely.geometry.point.Point object at ...>
The WKB representation of any geometry object can be had via the wkb attribute. New geometries can be created from WKB data using the shapely.wkb.loads factory. Use this format to interoperate with ogr.py:
>>> import ogr >>> from shapely.wkb import loads >>> source = ogr.Open("/tmp/world_borders.shp") >>> borders = source.GetLayerByName("world_borders") >>> feature = borders.GetNextFeature() >>> loads(feature.GetGeometryRef().ExportToWkb()) <shapely.geometry.polygon.Polygon object at ...>
Python sequence data can be analyzed as Shapely geometries using the shapely.geometry.as* adapters while leaving the data in its original storage. A pair of float can be treated as a point with asPoint:
>>> from shapely.geometry import asPoint >>> coords = [3.0, 4.0] >>> pa = asPoint(coords) >>> pa.wkt 'POINT (3.0000000000000000 4.0000000000000000)'
Move the coordinates and watch the geometry adapter change
>>> coords[0] = 1.0 >>> pa.wkt 'POINT (1.0000000000000000 4.0000000000000000)'
The asLineString adapter works much the same. The asPolygon adapter is used like
>>> from shapely.geometry import asPolygon >>> coords = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]] >>> hole_coords = [((0.1,0.1), (0.1,0.2), (0.2,0.2), (0.2,0.1))] >>> pa = asPolygon(coords, hole_coords) >>> len(pa.exterior.coords) 5 >>> len(pa.interiors) 1 >>> len(pa.interiors[0].coords) 5
Shapely geometries provide the Numpy array interface which means that points, line strings, and polygon rings can be used as Numpy arrays:
>>> from numpy import array >>> a = array(polygon.exterior) >>> a array([[-1., -1.], [-1., 1.], [ 1., 1.], [ 1., -1.], [-1., -1.]])
The numpy.asarray function does not copy coordinate values at the price of slower numpy access to coordinates.
The shapely.geometry.as* functions can also be used to wrap numpy arrays, which can then be analyzed using Shapely while maintaining their original storage. A 1 x 2 array can be adapted to a point
>>> a = array([1.0, 2.0]) >>> pa = asPoint(a) >>> pa.wkt 'POINT (1.0000000000000000 2.0000000000000000)'
and a N x 2 array can be adapted to a line string
>>> from shapely.geometry import asLineString >>> a = array([[1.0, 2.0], [3.0, 4.0]]) >>> la = asLineString(a) >>> la.wkt 'LINESTRING (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)'
There is no Numpy array representation of a polygon.
Any object that provides the GeoJSON-like Python geo interface can be adapted and used as a Shapely geometry using the shapely.geometry.asShape function. For example, a dictionary:
>>> from shapely.geometry import asShape >>> d = {"type": "Point", "coordinates": (0.0, 0.0)} >>> shape = asShape(d) >>> shape.geom_type 'Point' >>> list(shape.coords) [(0.0, 0.0)]
Or a simple placemark-type object:
>>> 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 'Point' >>> list(shape.coords) [(0.0, 0.0)]
If you want to copy coordinate data to a new geometry, use the shapely.geometry.shape function instead.
Shapely provides functions for efficient operations on large sets of geometries.
To find the subset of points that are contained within a polygon, use shapely.iterops.contains:
>>> from shapely.geometry import Polygon >>> from shapely.geometry import Point >>> coords = ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)) >>> polygon = Polygon(coords) >>> points = [Point(0.5, 0.5), Point(2.0, 2.0)] >>> from shapely import iterops >>> list(iterops.contains(polygon, points, True)) [<shapely.geometry.point.Point object at ...>]
The second parameter to iterops.contains can be any kind of iterator, even a generator of objects. If it yields tuples, then the second element of the tuple will be ultimately yielded from iterops.contains.
>>> list(iterops.contains(polygon, iter((p, p.wkt) for p in points))) ['POINT (0.5000000000000000 0.5000000000000000)']
Shapely is written by Sean Gillies with contributions from Howard Butler, Kai Lautaportti (Hexagon IT), Frédéric Junod (Camptocamp SA), Eric Lemoine (Camptocamp SA) and ctypes tips from Justin Bronn (GeoDjango).