Scroll down on http://pypi.python.org/pypi/Shapely/1.2b6 to see the links for installers for Python versions 2.4/5/6. These are produced by Jakko Salli and contain GEOS 3.2.0 DLLs.
Busy day in the labo. Version 0.6 of Rtree, the N-dimensional R-tree package for Python, is ready.
It is my pleasure to announce the release of Rtree 0.6.0.
Source code and a complete Windows self-installer (with libspatialindex 1.5.0) are provided. Version 0.6.0 requires the use of libspatialindex 1.5.0, and it will not work without it.
The following updates were made for this release:
- 0.6.0 relies on libspatialindex 1.5.0+.
- Intersection and nearest methods return iterators over results instead of lists.
- Number of results for nearest() defaults to 1.
- libsidx C library of 0.5.0 removed and included in libspatialindex
- objects="raw" to return the object sent in (for speed).
- .count() method to return the intersection count without the overhead of returning a list. (thanks Leonard Norrgård)
- Improved bulk loading performance
- Supposedly no memory leaks :)
- Many other performance tweaks (see docs).
- Bulk loader supports interleaved coordinates
- Leaf queries. You can return the box and ids of the leaf nodes of the index. Useful for visualization, etc.
- Many more docstrings, sphinx docs, etc
Howard, Brent, and Sean
Permanent link to the announcement: http://lists.gispython.org/pipermail/community/2010-April/002500.html.
GitHub users might be interested in forking http://github.com/sgillies/rtree. I'll keep it up date with and push contributions to the canonical Rtree Subversion repository.
New C API and 64-bit support for the N-dimensional R/MVR/TPR-tree library:
It is my pleasure to announce the release of libspatialindex 1.5.0. You can obtain source and compiled windows versions from:
Additionally, I have also updated the OSGeo4W release with a 1.5.0 version as well for people using it through that avenue.
The big items addressed in 1.5.0 were:
- Do not depend on size_t for storages. This change is backward incompatible, but it allows stored indexes to be 32/64 platform agnostic. http://trac.gispython.org/spatialindex/ticket/20
- A C API has been added. The C API provides a nice fuzzy blanket for this wishing to insulate themselves from righteous C++ programming. In exchange for holding your hand and making choices for you at almost every level, it is rather easy to use and provides some good example code for those looking to do different things with the library. http://trac.gispython.org/spatialindex/ticket/23
More detail provided at http://trac.gispython.org/spatialindex/query?status=closed&group=resolution&milestone=1.5
This library originated in work by Hadjieleftheriou  and is used by Rtree. Permanent link to the announcement: http://lists.gispython.org/pipermail/spatialindex/2010-April/000186.html.
|||M. Hadjieleftheriou, E. Hoel, and V.J. Tsotras, “Sail: A spatial index library for efficient application integration,” GEOINFORMATICA, vol. 9, 2005, pp. 367--389 [PDF]|
Update (2010-04-28): 1.2b7 has another important bug fix. Links are updated below.
Update (2010-04-13): 1.2b6 has an important bug fix. Links are updated below.
Shapely 1.2b5 is uploaded to http://pypi.python.org/pypi/Shapely and http://gispython.org/dist/. It contains a few enhancements that suggested themselves while I was writing example code for the user manual.
To install and try it out (in a virtualenv):
A sneak preview of the (still in progress) manual for 1.2 is online at a non-permanent URI: http://gispython.org/shapely-1.2/manual/manual.html. Feedback on the HTML has been positive. I hope that the content is (or will be) even better. There are copious code examples and figures (with code) made using descartes. A lot of the discussion is based on Martin Davis's JTS docs, without which I'd be nowhere.
In other Shapely news, the project in which it originated has been refunded and will be the biggest part of my job over the next few years. Count on improvements to the code and the docs.
Update (2010-04-10): http://pypi.python.org/pypi/descartes/0.1.2 adds support for GeoJSON-like objects and objects that provide the Python geo interface.
Update (2010-04-08): I've uploaded descartes 0.1, which now supports GeoJSON-like dicts (also known as "Python geo interface"), to http://pypi.python.org/pypi/descartes/0.1.
I've been making figures for the upcoming new version of the Shapely manual and developed some Shapely/matplotlib utility code that might be useful to others. The PolygonPatch class from descartes.patch takes care of all the fussy details of path creation, reducing the plotting of any polygon to a single line of code.
The plot on the left shows a line (grey) dilated to produce a polygon (blue). The plot on the right shows that same polygon (gray) eroded to produce a new polygon (blue).
from matplotlib import pyplot from shapely.geometry import LineString from descartes.patch import PolygonPatch fig = pyplot.figure(1, figsize=(10, 4), dpi=180) # Plot 1: dilating a line line = LineString([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) ax = fig.add_subplot(121) dilated = line.buffer(0.5) patch1 = PolygonPatch(dilated, facecolor='#99ccff', edgecolor='#6699cc') ax.add_patch(patch1) x, y = line.xy ax.plot(x, y, color='#999999') ax.set_xlim(-1, 4) ax.set_ylim(-1, 3) # Plot 2: eroding the polygon from 1 ax = fig.add_subplot(122) patch2a = PolygonPatch(dilated, facecolor='#cccccc', edgecolor='#999999') ax.add_patch(patch2a) eroded = dilated.buffer(-0.3) patch2b = PolygonPatch(eroded, facecolor='#99ccff', edgecolor='#6699cc') ax.add_patch(patch2b) ax.set_xlim(-1, 4) ax.set_ylim(-1, 3) pyplot.show()
See http://bitbucket.org/sgillies/descartes/ for code and more examples.
Update (2010-04-15): See also http://sgillies.net/blog/1014/descartes.
With the help of this old matplotlib-users post, I've figured out how to use Path and PathPatch to draw polygons with truly empty holes.
The code below constructs an approximately circular, doubly-holed region by buffering a point and taking the difference with a buffered pair of points, converts its boundaries to a matplotlib path, and renders this as a patch. Shapely (or rather GEOS) does the right thing with coordinate ordering, making it rather easy and efficient.
from matplotlib import pyplot from matplotlib.path import Path from matplotlib.patches import PathPatch from numpy import asarray, concatenate, ones from shapely.geometry import * def ring_coding(ob): # The codes will be all "LINETO" commands, except for "MOVETO"s at the # beginning of each subpath n = len(ob.coords) codes = ones(n, dtype=Path.code_type) * Path.LINETO codes = Path.MOVETO return codes def pathify(polygon): # Convert coordinates to path vertices. Objects produced by Shapely's # analytic methods have the proper coordinate order, no need to sort. vertices = concatenate( [asarray(polygon.exterior)] + [asarray(r) for r in polygon.interiors]) codes = concatenate( [ring_coding(polygon.exterior)] + [ring_coding(r) for r in polygon.interiors]) return Path(vertices, codes) polygon = Point(0, 0).buffer(10.0).difference( MultiPoint([(-5, 0), (5, 0)]).buffer(3.0)) fig = pyplot.figure(num=1, figsize=(4, 4), dpi=180) ax = fig.add_subplot(111) path = pathify(polygon) patch = PathPatch(path, facecolor='#cccccc', edgecolor='#999999') ax.add_patch(patch) ax.set_xlim(-15, 15) ax.set_ylim(-15, 15) ax.set_aspect(1.0) fig.savefig('polygon-holes.png')
No more faking it.
Here are my notes on starting a brand new, versioned, readily distributed Python project. Examples show a bash session, but Python, virtualenv, pip, distribute, paster, and hg all work on Windows (from whence more and more Python GIS programmers come) as well.
1. Create a fresh virtual environment. Why? So you don't clutter your system Python with in-development code, and to keep possibly conflicting versions of dependencies out of your development environment. It's probably even more useful for working on code that we may clone from another repository than it is for starting from scratch.
$ virtualenv --distribute foo New python executable in /tmp/foo/bin/python2.6 Also creating executable in /tmp/foo/bin/python Installing distribute....done.
2. Install paster if it wasn't already installed under our original Python (or if you used the --no-site-packages option). It's a script that creates a basic, normal source layout for a new package, prompts us for essential project metadata, and writes a working setup.py.
$ cd foo $ ./bin/pip install PasteScript Downloading/unpacking PasteScript Downloading PasteScript-1.7.3.tar.gz (127Kb): 127Kb downloaded Running setup.py egg_info for package PasteScript Downloading/unpacking Paste>=1.3 (from PasteScript) Downloading Paste-1.7.2.tar.gz (373Kb): 373Kb downloaded Running setup.py egg_info for package Paste Downloading/unpacking PasteDeploy (from PasteScript) Downloading PasteDeploy-1.3.3.tar.gz Running setup.py egg_info for package PasteDeploy warning: no files found matching 'docs/*.html' warning: no previously-included files found matching 'docs/rebuild' Installing collected packages: Paste, PasteDeploy, PasteScript ... Successfully installed Paste PasteDeploy PasteScript
3. Create the new project.
$ ./bin/paster create -t basic_package foogis Selected and implied templates: PasteScript#basic_package A basic setuptools-enabled package Variables: egg: foogis package: foogis project: foogis Enter version (Version (like 0.1)) ['']: 0.1 Enter description (One-line description of the package) ['']: FooGIS Enter long_description (Multi-line description (in reST)) ['']: Enter keywords (Space-separated keywords/tags) ['']: gis Enter author (Author name) ['']: Sean Gillies Enter author_email (Author email) ['']: email@example.com Enter url (URL of homepage) ['']: http://example.com/foogis Enter license_name (License name) ['']: DWTFYWWI Enter zip_safe (True/False: if the package can be distributed as a .zip file) [False]: Creating template basic_package Creating directory ./foogis Recursing into +package+ Creating ./foogis/foogis/ Copying __init__.py to ./foogis/foogis/__init__.py Copying setup.cfg to ./foogis/setup.cfg Copying setup.py_tmpl to ./foogis/setup.py Running /tmp/foo/bin/python2.6 setup.py egg_info
What we get is
$ find foogis foogis foogis/foogis foogis/foogis/__init__.py foogis/foogis.egg-info foogis/foogis.egg-info/dependency_links.txt foogis/foogis.egg-info/entry_points.txt foogis/foogis.egg-info/not-zip-safe foogis/foogis.egg-info/PKG-INFO foogis/foogis.egg-info/SOURCES.txt foogis/foogis.egg-info/top_level.txt foogis/setup.cfg foogis/setup.py
The package code iself is in foogis/foogis. The foogis directory holds distribution files. Metadata, README, etc.
4. This is a good time to get everything under revision control (except the egg-info, as Tarek points out).
$ cd foogis $ hg init $ hg add --exclude *egg-info adding foogis/__init__.py adding setup.cfg adding setup.py $ hg commit -m "Start of the FooGIS project"
$ cd .. $ ./bin/pip install nose Downloading/unpacking nose ... Successfully installed nose $ ./bin/pip install coverage Downloading/unpacking coverage ... Successfully installed coverage
6. Write some tests. The sooner we start testing, the better. Few things are more painful than writing tests a few hundred lines of code down the road.
Here's the first test, taking advantage of nose's conventions for finding tests.
Nose lets you start testing immediately, avoiding the intricacies of unittest until you need them. Before we run the tests, we'll fully activate the virtual environment, adjusting executable paths so that we don't have to be explicit about them (It's true, as pointed out in comments, that we could have done this at the outset).
Without any code, the tests fail, of course.
$ nosetests foogis E ====================================================================== ERROR: Failure: ImportError (cannot import name Point) ---------------------------------------------------------------------- Traceback (most recent call last): File "/private/tmp/foo/lib/python2.6/site-packages/nose/loader.py", line 382, in loadTestsFromName addr.filename, addr.module) File "/private/tmp/foo/lib/python2.6/site-packages/nose/importer.py", line 39, in importFromPath return self.importFromDir(dir_path, fqname) File "/private/tmp/foo/lib/python2.6/site-packages/nose/importer.py", line 86, in importFromDir mod = load_module(part_fqname, fh, filename, desc) File "/private/tmp/foo/foogis/foogis/tests.py", line 1, in <module> from foogis import Point ImportError: cannot import name Point ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (errors=1)
7. Write code and test.
class Point(object): def __init__(self, x, y): self.x = float(x) self.y = float(y) def __repr__(self): return 'Point (%s %s)' % (self.x, self.y)
Now, we run nosetests again with the coverage module:
$ nosetests --with-coverage foogis . Name Stmts Exec Cover Missing -------------------------------------- foogis 6 5 83% 6 ---------------------------------------------------------------------- Ran 1 test in 0.002s OK
The tests pass, but we're missing a test of the __repr__ method on line 6. Let's add one.
from foogis import Point def test_foogis(): assert Point(0.0, 0.0).x == 0.0 assert repr(Point(0.0, 0.0)) == 'Point (0.0 0.0)'
and re-run the tests.
$ nosetests --with-coverage foogis . Name Stmts Exec Cover Missing -------------------------------------- foogis 6 6 100% ---------------------------------------------------------------------- Ran 1 test in 0.002s OK
8. Commit the changes and make a distribution.
$ hg add foogis/tests.py $ hg commit -m "Added a Point class, with tests" $ python setup.py sdist running sdist running egg_info writing foogis.egg-info/PKG-INFO writing top-level names to foogis.egg-info/top_level.txt writing dependency_links to foogis.egg-info/dependency_links.txt writing entry points to foogis.egg-info/entry_points.txt reading manifest file 'foogis.egg-info/SOURCES.txt' writing manifest file 'foogis.egg-info/SOURCES.txt' creating foogis-0.1dev creating foogis-0.1dev/foogis creating foogis-0.1dev/foogis.egg-info making hard links in foogis-0.1dev... hard linking setup.cfg -> foogis-0.1dev hard linking setup.py -> foogis-0.1dev hard linking foogis/__init__.py -> foogis-0.1dev/foogis hard linking foogis/tests.py -> foogis-0.1dev/foogis hard linking foogis.egg-info/PKG-INFO -> foogis-0.1dev/foogis.egg-info hard linking foogis.egg-info/SOURCES.txt -> foogis-0.1dev/foogis.egg-info hard linking foogis.egg-info/dependency_links.txt -> foogis-0.1dev/foogis.egg-info hard linking foogis.egg-info/entry_points.txt -> foogis-0.1dev/foogis.egg-info hard linking foogis.egg-info/not-zip-safe -> foogis-0.1dev/foogis.egg-info hard linking foogis.egg-info/top_level.txt -> foogis-0.1dev/foogis.egg-info copying setup.cfg -> foogis-0.1dev Writing foogis-0.1dev/setup.cfg creating dist tar -cf dist/foogis-0.1dev.tar foogis-0.1dev gzip -f9 dist/foogis-0.1dev.tar removing 'foogis-0.1dev' (and everything under it)
The file at dist/foogis-0.1dev.tar.gz is ready to be distributed to users of our package. Let's get them to install it using pip.
$ pip install dist/foogis-0.1dev.tar.gz Unpacking ./dist/foogis-0.1dev.tar.gz Running setup.py egg_info for package from file:///private/tmp/foo/foogis/dist/foogis-0.1dev.tar.gz Installing collected packages: foogis Running setup.py install for foogis Successfully installed foogis
Do read the comment below about disabling (in setup.cfg) the "dev" tag in the distribution version string. Paste's "basic_package" template isn't the optimal template for every developer community. I'm familiar with the many additional features of the "ZopeSkel" template from Zope and Plone. I can imagine that commercial or semi-commercial efforts to grow Python developer communities (particularly thinking of ESRI here) might also be served well by specialized project templates.
As part of my continuing education about the theory and methods underlying Shapely, GEOS, JTS, and the OGC's Simple Features specs, I've written a small package of utilities for working with DE-9IM  matrices and patterns: http://bitbucket.org/sgillies/de9im/. Shapely provides the standard predicates (these are probably my favorite OGC standards) as geometry class methods,
>>> from shapely.wkt import loads >>> p = loads('POLYGON ((1.0 0.0, 0.0 -1.0, -1.0 0.0, 0.0 1.0, 1.0 0.0))') >>> q = loads('POLYGON ((3.0 0.0, 2.0 -1.0, 1.0 0.0, 2.0 1.0, 3.0 0.0))') >>> p.disjoint(q) False >>> p.intersects(q) True >>> p.touches(q) True
but what if you wanted to test whether the features touched at exactly one point only? A "side hug", you might say. Instead of computing the intersection and checking its geometry type, you can use the de9im package to define a DE-9IM matrix pattern and test it against the relation matrix for the two features. The 0 in the pattern below requires that the intersection of the boundaries of the features be a 0-dimensional figure. In other words: a point.
>>> from de9im import pattern >>> side_hug = pattern('FF*F0****') >>> im = p.relate(q) >>> print im FF2F01212 >>> side_hug.matches(im) True
The de9im package is 100% tested, which gives me a good starting point for experimenting with more optimal implementations.
There seems to be almost enough standardization between GeoDjango, Shapely, and SQLAlchemy that I could make these patterns callable, and call the relate method on a pair of objects:
to use like so:
|||C. Strobl, “Dimensionally Extended Nine-Intersection Model (DE-9IM),” Encyclopedia of GIS, S. Shekhar and H. Xiong, Eds., Springer, 2008, pp. 240-245. [PDF]|
Stuart Charlton says stuff about Building a RESTful Hypermedia Agent, Part 1:
Building a hypermedia-aware client is rather different from building a typical client in a client/server system. It may not be immediately intuitive. But, I believe the notions are rooted in (quite literally) decades of experience in other computing domains that are agent-oriented. Game behaviour engines, control systems, reactive or event-driven systems all have been developed with this programming approach in mind.
He points to a diagram from Artifical Intelligence: A Modern Approach  and adapts it to the RESTful web. Agent sensors become HTTP's "safe" methods (GET), effectors the "unsafe" methods (POST, PUT, DELETE). The HTTP protocol and content type definitions make up the agent's model of the evolving, mutable state of its environment (the web). I'm enjoying thinking about RESTful client-server interactions on the web in these well-reasoned terms. The REST style, to me, is all about enabling software agents. Not just web browsers or search index crawlers, but agents that might mine your texts, geocode your news, or ETL your spatial data (and wash your socks). I'm looking forward to the next installment.
|||S.J. Russell and P. Norvig, Artificial Intelligence, Prentice Hall, 2009.|
I'm trying to track down the origin of the geometry collection concept. Did it originate inside or outside GIS? JSTOR has nothing for me. Via Martin Davis, I've found a paper by Egenhofer and Herring  [PDF] which mentions a "complex line" on page 6:
– A complex line is a line with more than two disconnected boundaries (Figure 1d).
but makes no mention of multipoints, multilines, or multipolygons. Figure 1d in that paper shows a forking line, like a lower-case Greek lamba: λ.
Anybody have a good reference?
|||M.J. Egenhofer and John R. Herring, Categorizing Binary Topological Relations Between Regions, Lines, and Points in Geographic Databases, Orono, ME: University of Maine, 1991.|