[python-shapely] 14/148: Imported Upstream version 1.2.1

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Thu Aug 20 17:41:58 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 0d703048a7bc834d3ba2d40999301d58654c417b
Author: Pietro Battiston <me at pietrobattiston.it>
Date:   Thu Jun 24 15:04:08 2010 +0200

    Imported Upstream version 1.2.1
---
 ._CHANGES.txt                                     | Bin 186 -> 0 bytes
 ._CREDITS.txt                                     | Bin 185 -> 0 bytes
 ._HISTORY.txt                                     | Bin 184 -> 0 bytes
 ._LICENSE.txt                                     | Bin 184 -> 0 bytes
 ._MANIFEST.in                                     | Bin 186 -> 0 bytes
 ._README.txt                                      | Bin 187 -> 0 bytes
 CHANGES.txt                                       |  89 ++-
 CREDITS.txt                                       |  33 +-
 GEOS-C-API.txt                                    |  55 --
 HISTORY.txt                                       |  24 -
 MANIFEST.in                                       |   8 +-
 PKG-INFO                                          | 201 +++--
 README.txt                                        | 191 +++--
 Shapely.egg-info/PKG-INFO                         | 201 +++--
 Shapely.egg-info/SOURCES.txt                      |  64 +-
 Shapely.egg-info/requires.txt                     |   1 -
 examples/._geoms.py                               | Bin 184 -> 0 bytes
 examples/._world.py                               | Bin 184 -> 0 bytes
 examples/dissolve.py                              |  53 ++
 examples/geoms.py                                 |  51 --
 examples/intersect.py                             |  81 ++
 examples/world.py                                 |  21 -
 manual/manual.txt                                 | 875 ----------------------
 setup.py                                          |  63 +-
 shapely/.___init__.py                             | Bin 185 -> 0 bytes
 shapely/._ctypes_declarations.py                  | Bin 187 -> 0 bytes
 shapely/._iterops.py                              | Bin 184 -> 0 bytes
 shapely/._ops.py                                  | Bin 184 -> 0 bytes
 shapely/._predicates.py                           | Bin 186 -> 0 bytes
 shapely/._topology.py                             | Bin 187 -> 0 bytes
 shapely/._wkb.py                                  | Bin 187 -> 0 bytes
 shapely/__init__.py                               |   2 +-
 shapely/coords.py                                 | 167 +++++
 shapely/ctypes_declarations.py                    | 100 ++-
 shapely/geometry/._base.py                        | Bin 184 -> 0 bytes
 shapely/geometry/._linestring.py                  | Bin 184 -> 0 bytes
 shapely/geometry/__init__.py                      |  12 +
 shapely/geometry/base.py                          | 786 ++++++++++---------
 shapely/geometry/collection.py                    |  16 +-
 shapely/geometry/geo.py                           |   8 +-
 shapely/geometry/linestring.py                    | 257 +++----
 shapely/geometry/multilinestring.py               | 160 ++--
 shapely/geometry/multipoint.py                    | 155 ++--
 shapely/geometry/multipolygon.py                  | 153 ++--
 shapely/geometry/point.py                         | 245 +++---
 shapely/geometry/polygon.py                       | 450 ++++++-----
 shapely/geometry/proxy.py                         |  32 +-
 shapely/geos.py                                   | 296 +++++++-
 shapely/impl.py                                   |  81 ++
 shapely/iterops.py                                |  11 +-
 shapely/linref.py                                 |  26 +
 shapely/ops.py                                    | 110 ++-
 shapely/predicates.py                             |  81 +-
 shapely/prepared.py                               |  54 ++
 {tests => shapely/tests}/Array.txt                |   0
 {tests => shapely/tests}/GeoInterface.txt         |   0
 {tests => shapely/tests}/IterOps.txt              |   0
 {tests => shapely/tests}/LineString.txt           |  16 +-
 {tests => shapely/tests}/MultiLineString.txt      |  19 +-
 {tests => shapely/tests}/MultiPoint.txt           |  18 +-
 {tests => shapely/tests}/MultiPolygon.txt         |  18 +-
 {tests => shapely/tests}/Operations.txt           |  16 +
 {tests => shapely/tests}/Persist.txt              |   0
 {tests => shapely/tests}/Point.txt                |  20 +-
 {tests => shapely/tests}/Polygon.txt              |  10 +-
 {tests => shapely/tests}/Predicates.txt           |   0
 shapely/tests/__init__.py                         |  18 +
 {tests => shapely/tests}/attribute-chains.txt     |  16 +-
 {tests => shapely/tests}/binascii_hex.txt         |   0
 shapely/tests/cascaded_union.txt                  |  24 +
 {tests => shapely/tests}/dimensions.txt           |   0
 {tests => shapely/tests}/invalid_intersection.txt |   6 +-
 shapely/tests/linear-referencing.txt              |  58 ++
 shapely/tests/linemerge.txt                       |  61 ++
 {tests => shapely/tests}/polygonize.txt           |   0
 shapely/tests/test_collection.py                  |  10 +
 shapely/tests/test_doctests.py                    |  35 +
 shapely/tests/test_emptiness.py                   |  19 +
 shapely/tests/test_equality.py                    |  30 +
 shapely/tests/test_geomseq.py                     |  11 +
 shapely/tests/test_prepared.py                    |  15 +
 shapely/tests/test_singularity.py                 |  15 +
 shapely/tests/test_validation.py                  |  10 +
 shapely/tests/test_xy.py                          |  14 +
 shapely/tests/threading_test.py                   |  37 +
 {tests => shapely/tests}/wkt_locale.txt           |   0
 shapely/topology.py                               | 120 ++-
 shapely/validation.py                             |   7 +
 shapely/wkb.py                                    |  33 +-
 shapely/wkt.py                                    |  20 +-
 tests/._Array.txt                                 | Bin 184 -> 0 bytes
 tests/._GeoInterface.txt                          | Bin 184 -> 0 bytes
 tests/._Persist.txt                               | Bin 184 -> 0 bytes
 tests/._Point.txt                                 | Bin 184 -> 0 bytes
 tests/._Predicates.txt                            | Bin 184 -> 0 bytes
 tests/._binascii_hex.txt                          | Bin 184 -> 0 bytes
 96 files changed, 3112 insertions(+), 2777 deletions(-)

diff --git a/._CHANGES.txt b/._CHANGES.txt
deleted file mode 100644
index e4be6ea..0000000
Binary files a/._CHANGES.txt and /dev/null differ
diff --git a/._CREDITS.txt b/._CREDITS.txt
deleted file mode 100644
index 2da06ff..0000000
Binary files a/._CREDITS.txt and /dev/null differ
diff --git a/._HISTORY.txt b/._HISTORY.txt
deleted file mode 100644
index 963bf98..0000000
Binary files a/._HISTORY.txt and /dev/null differ
diff --git a/._LICENSE.txt b/._LICENSE.txt
deleted file mode 100644
index 1dd305d..0000000
Binary files a/._LICENSE.txt and /dev/null differ
diff --git a/._MANIFEST.in b/._MANIFEST.in
deleted file mode 100644
index 74268d9..0000000
Binary files a/._MANIFEST.in and /dev/null differ
diff --git a/._README.txt b/._README.txt
deleted file mode 100644
index 5881da3..0000000
Binary files a/._README.txt and /dev/null differ
diff --git a/CHANGES.txt b/CHANGES.txt
index 0f3ef96..9e0bb95 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,15 +1,74 @@
 All tickets are children of http://trac.gispython.org/lab/ticket.
 
-1.0.14 (2009-10-05)
+1.2.1 (2010-06-23)
+------------------
+- Fixed bounds of singular polygons.
+- Added shapely.validation.explain_validity function (#226).
+
+1.2 (2010-05-27)
+----------------
+- Final release.
+
+1.2rc2 (2010-05-26)
 -------------------
-- Proper prototyping of WKB writer, and avoidance of errors on 64-bit systems
-  (#191).
+- Add examples and tests to MANIFEST.in.
+- Release candidate 2.
 
-1.0.13 (2009-09-29)
+1.2rc1 (2010-05-25)
 -------------------
+- Release candidate.
+
+1.2b7 (2010-04-22)
+------------------
+- Memory leak associated with new empty geometry state fixed.
+
+1.2b6 (2010-04-13)
+------------------
+- Broken GeometryCollection fixed.
+
+1.2b5 (2010-04-09)
+------------------
+- Objects can be constructed from others of the same type, thereby making
+  copies. Collections can be constructed from sequences of objects, also making
+  copies.
+- Collections are now iterators over their component objects.
+- New code for manual figures, using the descartes package.
+
+1.2b4 (2010-03-19)
+------------------
+- Adds support for the "sunos5" platform.
+
+1.2b3 (2010-02-28)
+------------------
+- Only provide simplification implementations for GEOS C API >= 1.5.
+
+1.2b2 (2010-02-19)
+------------------
+- Fix cascaded_union bug introduced in 1.2b1 (#212).
+
+1.2b1 (2010-02-18)
+------------------
+- Update the README. Remove cruft from setup.py. Add some version 1.2 metadata
+  regarding required Python version (>=2.5,<3) and external dependency
+  (libgeos_c >= 3.1).
+
+1.2a6 (2010-02-09)
+------------------
+- Add accessor for separate arrays of X and Y values (#210).
+
+TODO: fill gap here
+
+1.2a1 (2010-01-20)
+------------------
+- Proper prototyping of WKB writer, and avoidance of errors on 64-bit systems
+  (#191).
 - Prototype libgeos_c functions in a way that lets py2exe apps import shapely
   (#189).
-  
+
+=========================
+1.2 Branched (2009-09-19)
+=========================
+
 1.0.12 (2009-04-09)
 -------------------
 - Fix for references held by topology and predicate descriptors.
@@ -65,3 +124,23 @@ All tickets are children of http://trac.gispython.org/lab/ticket.
 1.0.2 (2008-02-26)
 ------------------
 - Fix loss of dimensionality in polygon rings (#155).
+
+1.0.1 (2008-02-08)
+------------------
+- Allow chaining expressions involving coordinate sequences and geometry parts
+  (#151).
+- Protect against abnormal use of coordinate accessors (#152).
+- Coordinate sequences now implement the numpy array protocol (#153).
+
+1.0 (2008-01-18)
+----------------
+- Final release.
+
+1.0 RC2 (2008-01-16)
+--------------------
+- Added temporary solution for #149.
+
+1.0 RC1 (2008-01-14)
+--------------------
+- First release candidate
+
diff --git a/CREDITS.txt b/CREDITS.txt
index 9f374e2..8967542 100644
--- a/CREDITS.txt
+++ b/CREDITS.txt
@@ -1,9 +1,24 @@
-Sean Gillies (Pleiades)
-Howard Butler (Hobu, Inc.)
-Kai Lautaportti (Hexagon IT)
-Frédéric Junod (Camptocamp SA)
-Eric Lemoine (Camptocamp SA)
-Justin Bronn (GeoDjango) for ctypes inspiration
-
-Some of this work was supported by a grant from the U.S. National Endowment 
-for the Humanities (http://www.neh.gov).
+Shapely is written by:
+
+* Sean Gillies
+* Aron Bierbaum
+* Kai Lautaportti
+
+Patches contributed by:
+
+* Howard Butler
+* Fr |eaigue| d |eaigue| ric Junod
+* Eric Lemoine
+* Jonathan Tartley
+* Kristian Thy
+* Oliver Tonnhofer
+
+Additional help from:
+
+* Justin Bronn (GeoDjango) for ctypes inspiration
+* Martin Davis (JTS)
+* Jaakko Salli for the Windows distributions
+* Sandro Santilli, Mateusz Loskot, Paul Ramsey, et al (GEOS Project)
+
+Major portions of this work were supported by a grant (for Pleiades_) from the
+U.S. National Endowment for the Humanities (http://www.neh.gov).
diff --git a/GEOS-C-API.txt b/GEOS-C-API.txt
deleted file mode 100644
index c4b5ac3..0000000
--- a/GEOS-C-API.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-Ctypes declarations for functions present in GEOS C API 1.4, but not present in
-1.3, are listed below:
-
-lgeos.GEOS_getWKBOutputDims.restype = ctypes.c_int
-lgeos.GEOS_getWKBByteOrder.restype = ctypes.c_int
-lgeos.GEOS_setWKBByteOrder.restype = ctypes.c_int
-lgeos.GEOS_setWKBByteOrder.argtypes = [ctypes.c_int]
-lgeos.GEOSGeomFromHEX_buf.restype = ctypes.c_void_p
-lgeos.GEOSGeomFromHEX_buf.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
-lgeos.GEOSSimplify.restype = ctypes.c_void_p
-lgeos.GEOSSimplify.argtypes = [ctypes.c_void_p, ctypes.c_double]
-lgeos.GEOSTopologyPreserveSimplify.restype = ctypes.c_void_p
-lgeos.GEOSTopologyPreserveSimplify.argtypes = [ctypes.c_void_p, ctypes.c_double]
-lgeos.GEOSEqualsExact.restype = ctypes.c_int
-lgeos.GEOSEqualsExact.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_double]
-lgeos.GEOSNormalize.restype = ctypes.c_int
-lgeos.GEOSNormalize.argtypes = [ctypes.c_void_p]
-lgeos.GEOSWKTReader_create.restype = ctypes.c_void_p
-lgeos.GEOSWKTReader_destroy.restype = None
-lgeos.GEOSWKTReader_destroy.argtypes = [ctypes.c_void_p]
-lgeos.GEOSWKTReader_read.restype = ctypes.c_void_p
-lgeos.GEOSWKTReader_read.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
-lgeos.GEOSWKTWriter_create.restype = ctypes.c_void_p
-lgeos.GEOSWKTWriter_destroy.restype = None
-lgeos.GEOSWKTWriter_destroy.argtypes = [ctypes.c_void_p]
-lgeos.GEOSWKTWriter_write.restype = ctypes.c_char_p
-lgeos.GEOSWKTWriter_write.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-lgeos.GEOSWKBReader_create.restype = ctypes.c_void_p
-lgeos.GEOSWKBReader_destroy.restype = None
-lgeos.GEOSWKBReader_destroy.argtypes = [ctypes.c_void_p]
-lgeos.GEOSWKBReader_read.restype = ctypes.c_void_p
-lgeos.GEOSWKBReader_read.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t]
-lgeos.GEOSWKBReader_readHEX.restype = ctypes.c_void_p
-lgeos.GEOSWKBReader_readHEX.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t]
-lgeos.GEOSWKBWriter_create.restype = ctypes.c_void_p
-lgeos.GEOSWKBWriter_destroy.restype = None
-lgeos.GEOSWKBWriter_destroy.argtypes = [ctypes.c_void_p]
-lgeos.GEOSWKBWriter_getOutputDimension.restype = ctypes.c_int
-lgeos.GEOSWKBWriter_getOutputDimension.argtypes = [ctypes.c_void_p]
-lgeos.GEOSWKBWriter_setOutputDimension.restype = None
-lgeos.GEOSWKBWriter_setOutputDimension.argtypes = [ctypes.c_void_p, ctypes.c_int]
-lgeos.GEOSWKBWriter_getByteOrder.restype = ctypes.c_int
-lgeos.GEOSWKBWriter_getByteOrder.argtypes = [ctypes.c_void_p]
-lgeos.GEOSWKBWriter_setByteOrder.restype = None
-lgeos.GEOSWKBWriter_setByteOrder.argtypes = [ctypes.c_void_p, ctypes.c_int]
-lgeos.GEOSWKBWriter_getIncludeSRID.restype = ctypes.c_char
-lgeos.GEOSWKBWriter_getIncludeSRID.argtypes = [ctypes.c_void_p]
-lgeos.GEOSWKBWriter_setIncludeSRID.restype = None
-lgeos.GEOSWKBWriter_setIncludeSRID.argtypes = [ctypes.c_void_p, ctypes.c_char]
-
-Furthermore, the following unneeded declarations are removed to avoid problems
-with Debian's 2.2.3 package:
-
-lgeos.GEOSCoordSeq_getOrdinate.restype = ctypes.c_int
-lgeos.GEOSCoordSeq_getOrdinate.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p]
diff --git a/HISTORY.txt b/HISTORY.txt
deleted file mode 100644
index d782205..0000000
--- a/HISTORY.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-All ticket numbers are rooted at http://trac.gispython.org/projects/PCL/ticket/
-
-1.0.1: 8 February 2008
-----------------------
-- Allow chaining expressions involving coordinate sequences and geometry parts
-  (#151).
-- Protect against abnormal use of coordinate accessors (#152).
-- Coordinate sequences now implement the numpy array protocol (#153).
-
-
-1.0: 18 January 2008
---------------------
-- Final release.
-
-
-1.0 RC2: 16 January 2008
-------------------------
-- Added temporary solution for #149.
-
-
-1.0 RC1: 14 January 2008
-------------------------
-- First release candidate
-
diff --git a/MANIFEST.in b/MANIFEST.in
index c4bf1f8..c34208b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,6 @@
-include *.txt
-recursive-include examples *.py
+exclude *.txt
+recursive-exclude manual *
+recursive-exclude debian *
+include CHANGES.txt CREDITS.txt LICENSE.txt README.txt
+recursive-include shapely/tests *.py *.txt
+recursive-include shapely/examples *.py
diff --git a/PKG-INFO b/PKG-INFO
index 6764f2a..38b4232 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,46 +1,39 @@
 Metadata-Version: 1.0
 Name: Shapely
-Version: 1.0.15
-Summary: Geospatial geometries, predicates, and operations
+Version: 1.2.1
+Summary: Geometric objects, predicates, and operations
 Home-page: http://trac.gispython.org/lab/wiki/Shapely
 Author: Sean Gillies
-Author-email: sgillies at frii.com
+Author-email: sean.gillies at gmail.com
 License: BSD
-Description: Shapely
+Description: =======
+        Shapely
         =======
         
-        Shapely is a Python package for manipulation and analysis of 2D geospatial
-        geometries. It is based on GEOS (http://geos.refractions.net).  Shapely 1.0 is
-        not concerned with data formats or coordinate reference systems.
-        Responsibility for reading and writing data and projecting coordinates is left
-        to other packages like WorldMill_ and pyproj_. For more information, see:
+        .. image:: http://farm3.static.flickr.com/2738/4511827859_b5822043b7_o_d.png
+        :width: 800
+        :height: 400
         
-        * Shapely wiki_
-        * Shapely manual_
-        
-        Shapely requires Python 2.4+. (I've also begun to port it to Python 3.0:
-        http://sgillies.net/blog/564/shapely-for-python-3-0/.)
-        
-        .. note::
-        We've switched to Windows GEOS DLLs based on MinGW in versions >= 1.0.6.
-        Please contact us if you experience difficulties.
+        Shapely is a BSD-licensed Python package for manipulation and analysis of
+        planar geometric objects. It is not concerned with data formats or coordinate
+        systems.  It is based on the widely deployed GEOS_ (the engine of PostGIS_) and
+        JTS_ (from which GEOS is ported) libraries. This C dependency is traded for the
+        ability to execute with blazing speed.
         
-        See also CHANGES.txt_ and HISTORY.txt_.
-        
-        .. _CHANGES.txt: http://trac.gispython.org/lab/browser/Shapely/trunk/CHANGES.txt
-        .. _HISTORY.txt: http://trac.gispython.org/lab/browser/Shapely/trunk/HISTORY.txt
-        .. _WorldMill: http://pypi.python.org/pypi/WorldMill
-        .. _pyproj: http://pypi.python.org/pypi/pyproj
+        In a nutshell: Shapely lets you do PostGIS-ish stuff outside the context of a
+        database using idiomatic Python. For more details, see:
         
+        * Shapely wiki_
+        * Shapely manual_
+        * Shapely `example apps`_
         
         Dependencies
         ------------
         
-        * libgeos_c (2.2.3 or 3.0.0+)
-        * Python ctypes_ (standard in Python 2.5+)
-        
-        .. _ctypes: http://pypi.python.org/pypi/ctypes/
+        Shapely 1.2 depends on:
         
+        * Python >=2.5,<3
+        * libgeos_c >=3.1 (3.0 and below have not been tested, YMMV)
         
         Installation
         ------------
@@ -49,123 +42,127 @@ Description: Shapely
         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::
         
-        $ sudo easy_install Shapely
+        $ pip install Shapely
         
-        or with the setup script::
-        
-        $ sudo python setup.py install
+        or from a source distribution with the setup script::
         
+        $ python setup.py install
         
         Usage
         -----
         
-        To buffer a point::
+        Here is the canonical example of building an approximately circular patch by
+        buffering a point::
         
         >>> from shapely.geometry import Point
-        >>> point = Point(-106.0, 40.0) # longitude, latitude
-        >>> point.buffer(10.0)
-        <shapely.geometry.polygon.Polygon object at ...>
-        
-        See the manual_ for comprehensive examples of usage. See also Operations.txt
-        and Predicates.txt under tests/ for more examples of the spatial operations and
-        predicates provided by Shapely. See also Point.txt, LineString.txt, etc for
-        examples of the geometry APIs.
+        >>> patch = Point(0.0, 0.0).buffer(10.0)
+        >>> patch
+        <shapely.geometry.polygon.Polygon object at 0x...>
+        >>> patch.area
+        313.65484905459385
         
+        See the manual_ for comprehensive usage snippets and the dissolve.py and
+        intersect.py `example apps`_.
         
-        Numpy integration
-        -----------------
+        Integration
+        -----------
         
-        All Shapely geometry instances provide the Numpy array interface::
-        
-        >>> from numpy import asarray
-        >>> a = asarray(point)
-        >>> a.size
-        3
-        >>> a.shape
-        (2,)
-        
-        Numpy arrays can also be adapted to Shapely points and linestrings::
-        
-        >>> from shapely.geometry import asLineString
-        >>> a = array([[1.0, 2.0], [3.0, 4.0]])
-        >>> line = asLineString(a)
-        >>> line.wkt
-        'LINESTRING (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)'
+        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.::
         
+        >>> from shapely.wkt import dumps, loads
+        >>> dumps(loads('POINT (0 0)'))
+        'POINT (0.0000000000000000 0.0000000000000000)'
         
-        Python Geo Interface
-        --------------------
+        All linear objects, such as the rings of a polygon (like ``patch`` above),
+        provide the Numpy array interface.::
         
-        Any object that provides the Python geo interface can be adapted to a Shapely
-        geometry with the asShape factory::
+        >>> from numpy import asarray
+        >>> ag = asarray(patch.exterior)
+        >>> ag
+        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]])
         
-        >>> d = {"type": "Point", "coordinates": (0.0, 0.0)}
-        >>> from shapely.geometry import asShape
-        >>> shape = asShape(d)
-        >>> shape.geom_type
-        'Point'
-        >>> tuple(shape.coords)
-        ((0.0, 0.0),)
+        That yields a numpy array of [x, y] arrays. This is not always exactly what one
+        wants for plotting shapes with Matplotlib, so Shapely 1.2 adds a `xy` property
+        for getting separate arrays of coordinate x and y values.::
         
-        >>> 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'
-        >>> tuple(shape.coords)
-        ((0.0, 0.0),)
+        >>> x, y = patch.exterior.xy
+        >>> ax = asarray(x)
+        >>> ax
+        array([  1.00000000e+01,   9.95184727e+00,   9.80785280e+00,  ...])
         
-        See http://trac.gispython.org/lab/wiki/PythonGeoInterface for more
-        details on the interface.
+        Numpy arrays can also be adapted to Shapely linestrings::
         
+        >>> from shapely.geometry import asLineString
+        >>> asLineString(ag).length
+        62.806623139095073
+        >>> asLineString(ag).wkt
+        'LINESTRING (10.0000000000000000 0.0000000000000000, ...)'
         
         Testing
         -------
         
-        Several of the modules have docstring doctests::
-        
-        $ cd shapely
-        $ python point.py
-        
-        There are also two test runners under tests/. test_doctests.py requires
-        zope.testing. runalldoctests.py does not. Perhaps the easiest way to run the
-        tests is::
+        Shapely uses a Zope-stye suite of unittests and doctests, excercised via
+        setup.py.::
         
         $ python setup.py test
         
+        Nosetests won't run the tests properly; Zope doctest suites are not currently
+        supported well by nose.
         
         Support
         -------
         
         For current information about this project, see the wiki_.
         
-        .. _wiki: http://trac.gispython.org/lab/wiki/Shapely
-        .. _manual: http://gispython.org/shapely/manual.html
-        
         If you have questions, please consider joining our community list:
         
         http://trac.gispython.org/projects/PCL/wiki/CommunityList
         
-        
         Credits
         -------
         
-        * Sean Gillies (Pleiades)
-        * Howard Butler (Hobu, Inc.)
-        * Kai Lautaportti (Hexagon IT)
-        * Fr |eaigue| d |eaigue| ric Junod (Camptocamp SA)
-        * Eric Lemoine (Camptocamp SA)
+        Shapely is written by:
+        
+        * Sean Gillies
+        * Aron Bierbaum
+        * Kai Lautaportti
+        
+        Patches contributed by:
+        
+        * Howard Butler
+        * Fr |eaigue| d |eaigue| ric Junod
+        * Eric Lemoine
+        * Jonathan Tartley
+        * Kristian Thy
+        * Oliver Tonnhofer
+        
+        Additional help from:
+        
         * Justin Bronn (GeoDjango) for ctypes inspiration
+        * Martin Davis (JTS)
+        * Jaakko Salli for the Windows distributions
+        * Sandro Santilli, Mateusz Loskot, Paul Ramsey, et al (GEOS Project)
         
+        Major portions of this work were supported by a grant (for Pleiades_) from the
+        U.S. National Endowment for the Humanities (http://www.neh.gov).
+        
+        .. _JTS: http://www.vividsolutions.com/jts/jtshome.htm
+        .. _PostGIS: http://postgis.org
+        .. _GEOS: http://trac.osgeo.org/geos/
+        .. _example apps: http://trac.gispython.org/lab/wiki/Examples
+        .. _wiki: http://trac.gispython.org/lab/wiki/Shapely
+        .. _manual: http://gispython.org/shapely/docs/1.2
         .. |eaigue| unicode:: U+00E9
         :trim:
+        .. _Pleiades: http://pleiades.stoa.org
         
-        Major portions of this work were supported by a grant (to Pleiades) from the
-        U.S.  National Endowment for the Humanities (http://www.neh.gov).
-        
-Keywords: geometry topology
+Keywords: geometry topology gis
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
diff --git a/README.txt b/README.txt
index bc23ca1..e89f2f4 100644
--- a/README.txt
+++ b/README.txt
@@ -1,38 +1,31 @@
+=======
 Shapely
 =======
 
-Shapely is a Python package for manipulation and analysis of 2D geospatial
-geometries. It is based on GEOS (http://geos.refractions.net).  Shapely 1.0 is
-not concerned with data formats or coordinate reference systems.
-Responsibility for reading and writing data and projecting coordinates is left
-to other packages like WorldMill_ and pyproj_. For more information, see:
-
-* Shapely wiki_
-* Shapely manual_
-
-Shapely requires Python 2.4+. (I've also begun to port it to Python 3.0:
-http://sgillies.net/blog/564/shapely-for-python-3-0/.)
-
-.. note::
-   We've switched to Windows GEOS DLLs based on MinGW in versions >= 1.0.6.
-   Please contact us if you experience difficulties.
+.. image:: http://farm3.static.flickr.com/2738/4511827859_b5822043b7_o_d.png
+   :width: 800
+   :height: 400
 
-See also CHANGES.txt_ and HISTORY.txt_.
+Shapely is a BSD-licensed Python package for manipulation and analysis of
+planar geometric objects. It is not concerned with data formats or coordinate
+systems.  It is based on the widely deployed GEOS_ (the engine of PostGIS_) and
+JTS_ (from which GEOS is ported) libraries. This C dependency is traded for the
+ability to execute with blazing speed.
 
-.. _CHANGES.txt: http://trac.gispython.org/lab/browser/Shapely/trunk/CHANGES.txt
-.. _HISTORY.txt: http://trac.gispython.org/lab/browser/Shapely/trunk/HISTORY.txt
-.. _WorldMill: http://pypi.python.org/pypi/WorldMill
-.. _pyproj: http://pypi.python.org/pypi/pyproj
+In a nutshell: Shapely lets you do PostGIS-ish stuff outside the context of a
+database using idiomatic Python. For more details, see:
 
+* Shapely wiki_
+* Shapely manual_
+* Shapely `example apps`_
 
 Dependencies
 ------------
 
-* libgeos_c (2.2.3 or 3.0.0+)
-* Python ctypes_ (standard in Python 2.5+)
-
-.. _ctypes: http://pypi.python.org/pypi/ctypes/
+Shapely 1.2 depends on:
 
+* Python >=2.5,<3
+* libgeos_c >=3.1 (3.0 and below have not been tested, YMMV)
 
 Installation
 ------------
@@ -41,118 +34,122 @@ 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::
 
-  $ sudo easy_install Shapely
-
-or with the setup script::
+  $ pip install Shapely
 
-  $ sudo python setup.py install
+or from a source distribution with the setup script::
 
+  $ python setup.py install
 
 Usage
 -----
 
-To buffer a point::
+Here is the canonical example of building an approximately circular patch by
+buffering a point::
 
   >>> from shapely.geometry import Point
-  >>> point = Point(-106.0, 40.0) # longitude, latitude
-  >>> point.buffer(10.0)
-  <shapely.geometry.polygon.Polygon object at ...>
-
-See the manual_ for comprehensive examples of usage. See also Operations.txt
-and Predicates.txt under tests/ for more examples of the spatial operations and
-predicates provided by Shapely. See also Point.txt, LineString.txt, etc for
-examples of the geometry APIs.
-
+  >>> patch = Point(0.0, 0.0).buffer(10.0)
+  >>> patch
+  <shapely.geometry.polygon.Polygon object at 0x...>
+  >>> patch.area
+  313.65484905459385
 
-Numpy integration
------------------
+See the manual_ for comprehensive usage snippets and the dissolve.py and
+intersect.py `example apps`_.
 
-All Shapely geometry instances provide the Numpy array interface::
+Integration 
+-----------
 
-  >>> from numpy import asarray
-  >>> a = asarray(point)
-  >>> a.size
-  3
-  >>> a.shape
-  (2,)
-
-Numpy arrays can also be adapted to Shapely points and linestrings::
-
-  >>> from shapely.geometry import asLineString
-  >>> a = array([[1.0, 2.0], [3.0, 4.0]])
-  >>> line = asLineString(a)
-  >>> line.wkt
-  'LINESTRING (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)'
+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.::
 
+  >>> from shapely.wkt import dumps, loads
+  >>> dumps(loads('POINT (0 0)'))
+  'POINT (0.0000000000000000 0.0000000000000000)'
 
-Python Geo Interface
---------------------
+All linear objects, such as the rings of a polygon (like ``patch`` above),
+provide the Numpy array interface.::
 
-Any object that provides the Python geo interface can be adapted to a Shapely
-geometry with the asShape factory::
+  >>> from numpy import asarray
+  >>> ag = asarray(patch.exterior)
+  >>> ag
+  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]])
 
-  >>> d = {"type": "Point", "coordinates": (0.0, 0.0)}
-  >>> from shapely.geometry import asShape
-  >>> shape = asShape(d)
-  >>> shape.geom_type
-  'Point'
-  >>> tuple(shape.coords)
-  ((0.0, 0.0),)
+That yields a numpy array of [x, y] arrays. This is not always exactly what one
+wants for plotting shapes with Matplotlib, so Shapely 1.2 adds a `xy` property
+for getting separate arrays of coordinate x and y values.::
 
-  >>> 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'
-  >>> tuple(shape.coords)
-  ((0.0, 0.0),)
+  >>> x, y = patch.exterior.xy
+  >>> ax = asarray(x)
+  >>> ax
+  array([  1.00000000e+01,   9.95184727e+00,   9.80785280e+00,  ...])
 
-See http://trac.gispython.org/lab/wiki/PythonGeoInterface for more
-details on the interface.
+Numpy arrays can also be adapted to Shapely linestrings::
 
+  >>> from shapely.geometry import asLineString
+  >>> asLineString(ag).length
+  62.806623139095073
+  >>> asLineString(ag).wkt
+  'LINESTRING (10.0000000000000000 0.0000000000000000, ...)'
 
 Testing
 -------
 
-Several of the modules have docstring doctests::
-
-  $ cd shapely
-  $ python point.py
-
-There are also two test runners under tests/. test_doctests.py requires
-zope.testing. runalldoctests.py does not. Perhaps the easiest way to run the 
-tests is::
+Shapely uses a Zope-stye suite of unittests and doctests, excercised via
+setup.py.::
 
   $ python setup.py test
 
+Nosetests won't run the tests properly; Zope doctest suites are not currently
+supported well by nose.
 
 Support
 -------
 
 For current information about this project, see the wiki_.
 
-.. _wiki: http://trac.gispython.org/lab/wiki/Shapely
-.. _manual: http://gispython.org/shapely/manual.html
-
 If you have questions, please consider joining our community list:
 
 http://trac.gispython.org/projects/PCL/wiki/CommunityList
 
-
 Credits
 -------
 
-* Sean Gillies (Pleiades)
-* Howard Butler (Hobu, Inc.)
-* Kai Lautaportti (Hexagon IT)
-* Fr |eaigue| d |eaigue| ric Junod (Camptocamp SA)
-* Eric Lemoine (Camptocamp SA)
+Shapely is written by:
+
+* Sean Gillies
+* Aron Bierbaum
+* Kai Lautaportti
+
+Patches contributed by:
+
+* Howard Butler
+* Fr |eaigue| d |eaigue| ric Junod
+* Eric Lemoine
+* Jonathan Tartley
+* Kristian Thy
+* Oliver Tonnhofer
+
+Additional help from:
+
 * Justin Bronn (GeoDjango) for ctypes inspiration
+* Martin Davis (JTS)
+* Jaakko Salli for the Windows distributions
+* Sandro Santilli, Mateusz Loskot, Paul Ramsey, et al (GEOS Project)
 
+Major portions of this work were supported by a grant (for Pleiades_) from the
+U.S. National Endowment for the Humanities (http://www.neh.gov).
+
+.. _JTS: http://www.vividsolutions.com/jts/jtshome.htm
+.. _PostGIS: http://postgis.org
+.. _GEOS: http://trac.osgeo.org/geos/
+.. _example apps: http://trac.gispython.org/lab/wiki/Examples
+.. _wiki: http://trac.gispython.org/lab/wiki/Shapely
+.. _manual: http://gispython.org/shapely/docs/1.2
 .. |eaigue| unicode:: U+00E9
    :trim:
-
-Major portions of this work were supported by a grant (to Pleiades) from the
-U.S.  National Endowment for the Humanities (http://www.neh.gov).
+.. _Pleiades: http://pleiades.stoa.org
diff --git a/Shapely.egg-info/PKG-INFO b/Shapely.egg-info/PKG-INFO
index 6764f2a..38b4232 100644
--- a/Shapely.egg-info/PKG-INFO
+++ b/Shapely.egg-info/PKG-INFO
@@ -1,46 +1,39 @@
 Metadata-Version: 1.0
 Name: Shapely
-Version: 1.0.15
-Summary: Geospatial geometries, predicates, and operations
+Version: 1.2.1
+Summary: Geometric objects, predicates, and operations
 Home-page: http://trac.gispython.org/lab/wiki/Shapely
 Author: Sean Gillies
-Author-email: sgillies at frii.com
+Author-email: sean.gillies at gmail.com
 License: BSD
-Description: Shapely
+Description: =======
+        Shapely
         =======
         
-        Shapely is a Python package for manipulation and analysis of 2D geospatial
-        geometries. It is based on GEOS (http://geos.refractions.net).  Shapely 1.0 is
-        not concerned with data formats or coordinate reference systems.
-        Responsibility for reading and writing data and projecting coordinates is left
-        to other packages like WorldMill_ and pyproj_. For more information, see:
+        .. image:: http://farm3.static.flickr.com/2738/4511827859_b5822043b7_o_d.png
+        :width: 800
+        :height: 400
         
-        * Shapely wiki_
-        * Shapely manual_
-        
-        Shapely requires Python 2.4+. (I've also begun to port it to Python 3.0:
-        http://sgillies.net/blog/564/shapely-for-python-3-0/.)
-        
-        .. note::
-        We've switched to Windows GEOS DLLs based on MinGW in versions >= 1.0.6.
-        Please contact us if you experience difficulties.
+        Shapely is a BSD-licensed Python package for manipulation and analysis of
+        planar geometric objects. It is not concerned with data formats or coordinate
+        systems.  It is based on the widely deployed GEOS_ (the engine of PostGIS_) and
+        JTS_ (from which GEOS is ported) libraries. This C dependency is traded for the
+        ability to execute with blazing speed.
         
-        See also CHANGES.txt_ and HISTORY.txt_.
-        
-        .. _CHANGES.txt: http://trac.gispython.org/lab/browser/Shapely/trunk/CHANGES.txt
-        .. _HISTORY.txt: http://trac.gispython.org/lab/browser/Shapely/trunk/HISTORY.txt
-        .. _WorldMill: http://pypi.python.org/pypi/WorldMill
-        .. _pyproj: http://pypi.python.org/pypi/pyproj
+        In a nutshell: Shapely lets you do PostGIS-ish stuff outside the context of a
+        database using idiomatic Python. For more details, see:
         
+        * Shapely wiki_
+        * Shapely manual_
+        * Shapely `example apps`_
         
         Dependencies
         ------------
         
-        * libgeos_c (2.2.3 or 3.0.0+)
-        * Python ctypes_ (standard in Python 2.5+)
-        
-        .. _ctypes: http://pypi.python.org/pypi/ctypes/
+        Shapely 1.2 depends on:
         
+        * Python >=2.5,<3
+        * libgeos_c >=3.1 (3.0 and below have not been tested, YMMV)
         
         Installation
         ------------
@@ -49,123 +42,127 @@ Description: Shapely
         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::
         
-        $ sudo easy_install Shapely
+        $ pip install Shapely
         
-        or with the setup script::
-        
-        $ sudo python setup.py install
+        or from a source distribution with the setup script::
         
+        $ python setup.py install
         
         Usage
         -----
         
-        To buffer a point::
+        Here is the canonical example of building an approximately circular patch by
+        buffering a point::
         
         >>> from shapely.geometry import Point
-        >>> point = Point(-106.0, 40.0) # longitude, latitude
-        >>> point.buffer(10.0)
-        <shapely.geometry.polygon.Polygon object at ...>
-        
-        See the manual_ for comprehensive examples of usage. See also Operations.txt
-        and Predicates.txt under tests/ for more examples of the spatial operations and
-        predicates provided by Shapely. See also Point.txt, LineString.txt, etc for
-        examples of the geometry APIs.
+        >>> patch = Point(0.0, 0.0).buffer(10.0)
+        >>> patch
+        <shapely.geometry.polygon.Polygon object at 0x...>
+        >>> patch.area
+        313.65484905459385
         
+        See the manual_ for comprehensive usage snippets and the dissolve.py and
+        intersect.py `example apps`_.
         
-        Numpy integration
-        -----------------
+        Integration
+        -----------
         
-        All Shapely geometry instances provide the Numpy array interface::
-        
-        >>> from numpy import asarray
-        >>> a = asarray(point)
-        >>> a.size
-        3
-        >>> a.shape
-        (2,)
-        
-        Numpy arrays can also be adapted to Shapely points and linestrings::
-        
-        >>> from shapely.geometry import asLineString
-        >>> a = array([[1.0, 2.0], [3.0, 4.0]])
-        >>> line = asLineString(a)
-        >>> line.wkt
-        'LINESTRING (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)'
+        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.::
         
+        >>> from shapely.wkt import dumps, loads
+        >>> dumps(loads('POINT (0 0)'))
+        'POINT (0.0000000000000000 0.0000000000000000)'
         
-        Python Geo Interface
-        --------------------
+        All linear objects, such as the rings of a polygon (like ``patch`` above),
+        provide the Numpy array interface.::
         
-        Any object that provides the Python geo interface can be adapted to a Shapely
-        geometry with the asShape factory::
+        >>> from numpy import asarray
+        >>> ag = asarray(patch.exterior)
+        >>> ag
+        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]])
         
-        >>> d = {"type": "Point", "coordinates": (0.0, 0.0)}
-        >>> from shapely.geometry import asShape
-        >>> shape = asShape(d)
-        >>> shape.geom_type
-        'Point'
-        >>> tuple(shape.coords)
-        ((0.0, 0.0),)
+        That yields a numpy array of [x, y] arrays. This is not always exactly what one
+        wants for plotting shapes with Matplotlib, so Shapely 1.2 adds a `xy` property
+        for getting separate arrays of coordinate x and y values.::
         
-        >>> 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'
-        >>> tuple(shape.coords)
-        ((0.0, 0.0),)
+        >>> x, y = patch.exterior.xy
+        >>> ax = asarray(x)
+        >>> ax
+        array([  1.00000000e+01,   9.95184727e+00,   9.80785280e+00,  ...])
         
-        See http://trac.gispython.org/lab/wiki/PythonGeoInterface for more
-        details on the interface.
+        Numpy arrays can also be adapted to Shapely linestrings::
         
+        >>> from shapely.geometry import asLineString
+        >>> asLineString(ag).length
+        62.806623139095073
+        >>> asLineString(ag).wkt
+        'LINESTRING (10.0000000000000000 0.0000000000000000, ...)'
         
         Testing
         -------
         
-        Several of the modules have docstring doctests::
-        
-        $ cd shapely
-        $ python point.py
-        
-        There are also two test runners under tests/. test_doctests.py requires
-        zope.testing. runalldoctests.py does not. Perhaps the easiest way to run the
-        tests is::
+        Shapely uses a Zope-stye suite of unittests and doctests, excercised via
+        setup.py.::
         
         $ python setup.py test
         
+        Nosetests won't run the tests properly; Zope doctest suites are not currently
+        supported well by nose.
         
         Support
         -------
         
         For current information about this project, see the wiki_.
         
-        .. _wiki: http://trac.gispython.org/lab/wiki/Shapely
-        .. _manual: http://gispython.org/shapely/manual.html
-        
         If you have questions, please consider joining our community list:
         
         http://trac.gispython.org/projects/PCL/wiki/CommunityList
         
-        
         Credits
         -------
         
-        * Sean Gillies (Pleiades)
-        * Howard Butler (Hobu, Inc.)
-        * Kai Lautaportti (Hexagon IT)
-        * Fr |eaigue| d |eaigue| ric Junod (Camptocamp SA)
-        * Eric Lemoine (Camptocamp SA)
+        Shapely is written by:
+        
+        * Sean Gillies
+        * Aron Bierbaum
+        * Kai Lautaportti
+        
+        Patches contributed by:
+        
+        * Howard Butler
+        * Fr |eaigue| d |eaigue| ric Junod
+        * Eric Lemoine
+        * Jonathan Tartley
+        * Kristian Thy
+        * Oliver Tonnhofer
+        
+        Additional help from:
+        
         * Justin Bronn (GeoDjango) for ctypes inspiration
+        * Martin Davis (JTS)
+        * Jaakko Salli for the Windows distributions
+        * Sandro Santilli, Mateusz Loskot, Paul Ramsey, et al (GEOS Project)
         
+        Major portions of this work were supported by a grant (for Pleiades_) from the
+        U.S. National Endowment for the Humanities (http://www.neh.gov).
+        
+        .. _JTS: http://www.vividsolutions.com/jts/jtshome.htm
+        .. _PostGIS: http://postgis.org
+        .. _GEOS: http://trac.osgeo.org/geos/
+        .. _example apps: http://trac.gispython.org/lab/wiki/Examples
+        .. _wiki: http://trac.gispython.org/lab/wiki/Shapely
+        .. _manual: http://gispython.org/shapely/docs/1.2
         .. |eaigue| unicode:: U+00E9
         :trim:
+        .. _Pleiades: http://pleiades.stoa.org
         
-        Major portions of this work were supported by a grant (to Pleiades) from the
-        U.S.  National Endowment for the Humanities (http://www.neh.gov).
-        
-Keywords: geometry topology
+Keywords: geometry topology gis
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
diff --git a/Shapely.egg-info/SOURCES.txt b/Shapely.egg-info/SOURCES.txt
index f13dc9e..01c238d 100644
--- a/Shapely.egg-info/SOURCES.txt
+++ b/Shapely.egg-info/SOURCES.txt
@@ -1,26 +1,28 @@
 CHANGES.txt
 CREDITS.txt
-GEOS-C-API.txt
-HISTORY.txt
 LICENSE.txt
 MANIFEST.in
 README.txt
+setup.cfg
 setup.py
 Shapely.egg-info/PKG-INFO
 Shapely.egg-info/SOURCES.txt
 Shapely.egg-info/dependency_links.txt
-Shapely.egg-info/requires.txt
 Shapely.egg-info/top_level.txt
-examples/geoms.py
-examples/world.py
-manual/manual.txt
+examples/dissolve.py
+examples/intersect.py
 shapely/__init__.py
+shapely/coords.py
 shapely/ctypes_declarations.py
 shapely/geos.py
+shapely/impl.py
 shapely/iterops.py
+shapely/linref.py
 shapely/ops.py
 shapely/predicates.py
+shapely/prepared.py
 shapely/topology.py
+shapely/validation.py
 shapely/wkb.py
 shapely/wkt.py
 shapely/geometry/__init__.py
@@ -34,21 +36,35 @@ shapely/geometry/multipolygon.py
 shapely/geometry/point.py
 shapely/geometry/polygon.py
 shapely/geometry/proxy.py
-tests/Array.txt
-tests/GeoInterface.txt
-tests/IterOps.txt
-tests/LineString.txt
-tests/MultiLineString.txt
-tests/MultiPoint.txt
-tests/MultiPolygon.txt
-tests/Operations.txt
-tests/Persist.txt
-tests/Point.txt
-tests/Polygon.txt
-tests/Predicates.txt
-tests/attribute-chains.txt
-tests/binascii_hex.txt
-tests/dimensions.txt
-tests/invalid_intersection.txt
-tests/polygonize.txt
-tests/wkt_locale.txt
\ No newline at end of file
+shapely/tests/Array.txt
+shapely/tests/GeoInterface.txt
+shapely/tests/IterOps.txt
+shapely/tests/LineString.txt
+shapely/tests/MultiLineString.txt
+shapely/tests/MultiPoint.txt
+shapely/tests/MultiPolygon.txt
+shapely/tests/Operations.txt
+shapely/tests/Persist.txt
+shapely/tests/Point.txt
+shapely/tests/Polygon.txt
+shapely/tests/Predicates.txt
+shapely/tests/__init__.py
+shapely/tests/attribute-chains.txt
+shapely/tests/binascii_hex.txt
+shapely/tests/cascaded_union.txt
+shapely/tests/dimensions.txt
+shapely/tests/invalid_intersection.txt
+shapely/tests/linear-referencing.txt
+shapely/tests/linemerge.txt
+shapely/tests/polygonize.txt
+shapely/tests/test_collection.py
+shapely/tests/test_doctests.py
+shapely/tests/test_emptiness.py
+shapely/tests/test_equality.py
+shapely/tests/test_geomseq.py
+shapely/tests/test_prepared.py
+shapely/tests/test_singularity.py
+shapely/tests/test_validation.py
+shapely/tests/test_xy.py
+shapely/tests/threading_test.py
+shapely/tests/wkt_locale.txt
\ No newline at end of file
diff --git a/Shapely.egg-info/requires.txt b/Shapely.egg-info/requires.txt
deleted file mode 100644
index 8b6d003..0000000
--- a/Shapely.egg-info/requires.txt
+++ /dev/null
@@ -1 +0,0 @@
-setuptools
\ No newline at end of file
diff --git a/examples/._geoms.py b/examples/._geoms.py
deleted file mode 100644
index b703ef1..0000000
Binary files a/examples/._geoms.py and /dev/null differ
diff --git a/examples/._world.py b/examples/._world.py
deleted file mode 100644
index b255630..0000000
Binary files a/examples/._world.py and /dev/null differ
diff --git a/examples/dissolve.py b/examples/dissolve.py
new file mode 100644
index 0000000..3f1d624
--- /dev/null
+++ b/examples/dissolve.py
@@ -0,0 +1,53 @@
+# dissolve.py
+#
+# Demonstrate how Shapely can be used to build up a collection of patches by 
+# dissolving circular regions and how Shapely supports plotting of the results.
+
+from functools import partial
+import random
+
+import pylab
+
+from shapely.geometry import Point
+from shapely.ops import cascaded_union
+
+# Use a partial function to make 100 points uniformly distributed in a 40x40 
+# box centered on 0,0.
+r = partial(random.uniform, -20.0, 20.0)
+points = [Point(r(), r()) for i in range(100)]
+
+# Buffer the points, producing 100 polygon spots
+spots = [p.buffer(2.5) for p in points]
+
+# Perform a cascaded union of the polygon spots, dissolving them into a 
+# collection of polygon patches
+patches = cascaded_union(spots)
+
+if __name__ == "__main__":
+    # Illustrate the results using matplotlib's pylab interface
+    pylab.figure(num=None, figsize=(4, 4), dpi=180)
+    
+    for patch in patches.geoms:
+        assert patch.geom_type in ['Polygon']
+        assert patch.is_valid
+    
+        # Fill and outline each patch
+        x, y = patch.exterior.xy
+        pylab.fill(x, y, color='#cccccc', aa=True) 
+        pylab.plot(x, y, color='#666666', aa=True, lw=1.0)
+    
+        # Do the same for the holes of the patch
+        for hole in patch.interiors:
+            x, y = hole.xy
+            pylab.fill(x, y, color='#ffffff', aa=True) 
+            pylab.plot(x, y, color='#999999', aa=True, lw=1.0)
+    
+    # Plot the original points
+    pylab.plot([p.x for p in points], [p.y for p in points], 'b,', alpha=0.75)
+    
+    # Write the number of patches and the total patch area to the figure
+    pylab.text(-25, 25, 
+        "Patches: %d, total area: %.2f" % (len(patches.geoms), patches.area))
+    
+    pylab.savefig('dissolve.png')
+    
diff --git a/examples/geoms.py b/examples/geoms.py
deleted file mode 100644
index 164ab72..0000000
--- a/examples/geoms.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from numpy import asarray
-import pylab
-from shapely.geometry import Point, LineString, Polygon
-
-polygon = Polygon(((-1.0, -1.0), (-1.0, 1.0), (1.0, 1.0), (1.0, -1.0)))
-
-point_r = Point(-1.5, 1.2)
-point_g = Point(-1.0, 1.0)
-point_b = Point(-0.5, 0.5)
-
-line_r = LineString(((-0.5, 0.5), (0.5, 0.5)))
-line_g = LineString(((1.0, -1.0), (1.8, 0.5)))
-line_b = LineString(((-1.8, -1.2), (1.8, 0.5)))
-
-def plot_point(g, o, l):
-    pylab.plot([g.x], [g.y], o, label=l)
-
-def plot_line(g, o):
-    a = asarray(g)
-    pylab.plot(a[:,0], a[:,1], o)
-
-def fill_polygon(g, o):
-    a = asarray(g.exterior)
-    pylab.fill(a[:,0], a[:,1], o, alpha=0.5)
-
-def fill_multipolygon(g, o):
-    for g in g.geoms:
-        fill_polygon(g, o)
-
-if __name__ == "__main__":
-    from numpy import asarray
-    import pylab
-    
-    fig = pylab.figure(1, figsize=(4, 3), dpi=150)
-    #pylab.axis([-2.0, 2.0, -1.5, 1.5])
-    pylab.axis('tight')
-
-    a = asarray(polygon.exterior)
-    pylab.fill(a[:,0], a[:,1], 'c')
-
-    plot_point(point_r, 'ro', 'b')
-    plot_point(point_g, 'go', 'c')
-    plot_point(point_b, 'bo', 'd')
-
-    plot_line(line_r, 'r')
-    plot_line(line_g, 'g')
-    plot_line(line_b, 'b')
-
-    pylab.show()
-
-
diff --git a/examples/intersect.py b/examples/intersect.py
new file mode 100644
index 0000000..76fab15
--- /dev/null
+++ b/examples/intersect.py
@@ -0,0 +1,81 @@
+# intersect.py
+#
+# Demonstrate how Shapely can be used to analyze and plot the intersection of
+# a trajectory and regions in space.
+
+from functools import partial
+import random
+
+import pylab
+
+from shapely.geometry import LineString, Point
+from shapely.ops import cascaded_union
+
+# Build patches as in dissolved.py
+r = partial(random.uniform, -20.0, 20.0)
+points = [Point(r(), r()) for i in range(100)]
+spots = [p.buffer(2.5) for p in points]
+patches = cascaded_union(spots)
+
+# Represent the following geolocation parameters
+#
+# initial position: -25, -25
+# heading: 45.0
+# speed: 50*sqrt(2)
+#
+# as a line
+vector = LineString(((-25.0, -25.0), (25.0, 25.0)))
+
+# Find intercepted and missed patches. List the former so we can count them
+# later
+intercepts = [patch for patch in patches.geoms if vector.intersects(patch)]
+misses = (patch for patch in patches.geoms if not vector.intersects(patch))
+
+# Plot the intersection
+intersection = vector.intersection(patches)
+assert intersection.geom_type in ['MultiLineString']
+
+if __name__ == "__main__":
+    # Illustrate the results using matplotlib's pylab interface
+    pylab.figure(num=None, figsize=(4, 4), dpi=180)
+    
+    # Plot the misses
+    for spot in misses:
+        x, y = spot.exterior.xy
+        pylab.fill(x, y, color='#cccccc', aa=True) 
+        pylab.plot(x, y, color='#999999', aa=True, lw=1.0)
+    
+        # Do the same for the holes of the patch
+        for hole in spot.interiors:
+            x, y = hole.xy
+            pylab.fill(x, y, color='#ffffff', aa=True) 
+            pylab.plot(x, y, color='#999999', aa=True, lw=1.0)
+    
+    # Plot the intercepts
+    for spot in intercepts:
+        x, y = spot.exterior.xy
+        pylab.fill(x, y, color='red', alpha=0.25, aa=True) 
+        pylab.plot(x, y, color='red', alpha=0.5, aa=True, lw=1.0)
+    
+        # Do the same for the holes of the patch
+        for hole in spot.interiors:
+            x, y = hole.xy
+            pylab.fill(x, y, color='#ffffff', aa=True) 
+            pylab.plot(x, y, color='red', alpha=0.5, aa=True, lw=1.0)
+    
+    # Draw the projected trajectory
+    pylab.arrow(-25, -25, 50, 50, color='#999999', aa=True,
+        head_width=1.0, head_length=1.0)
+    
+    for segment in intersection.geoms:
+        x, y = segment.xy
+        pylab.plot(x, y, color='red', aa=True, lw=1.5)
+    
+    # Write the number of patches and the total patch area to the figure
+    pylab.text(-28, 25, 
+        "Patches: %d/%d (%d), total length: %.1f" \
+         % (len(intercepts), len(patches.geoms), 
+            len(intersection.geoms), intersection.length))
+    
+    pylab.savefig('intersect.png')
+    
diff --git a/examples/world.py b/examples/world.py
deleted file mode 100644
index 81b9af8..0000000
--- a/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/manual/manual.txt b/manual/manual.txt
deleted file mode 100644
index 10e2a2b..0000000
--- a/manual/manual.txt
+++ /dev/null
@@ -1,875 +0,0 @@
-==================
-The Shapely Manual
-==================
-
-:Author: Sean Gillies
-:address: sgillies at 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`__.
-
-.. __: http://creativecommons.org/licenses/by/3.0/us/
-
-:abstract: This document describes the Shapely Python package for programming
-  with geospatial geometries.
-
-.. sectnum::
-
-.. contents::
-
-
-Background
-==========
-
-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_.
-
-.. _GEOS: http://geos.refractions.net
-.. _Java Topology Suite: http://www.jump-project.org/project.php?PID=JTS&SID=OVER
-.. _record: http://pypi.python.org/pypi/Shapely
-.. _wiki: http://trac.gispython.org/projects/PCL/wiki/Shapely
-
-
-Geometries
-==========
-
-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_.
-
-.. _illustration: http://www.jump-project.org/project.php?PID=JTS&SID=OVER#spatialdatatypes
-
-Shapely 1.0 does not provide circles, arcs, splines or the like.
-
-
-Factories
----------
-
-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 (**a**\ 1, ..., **a**\ M) and (**b**\ 1, ...,
-**b**\ N) be ordered sequences of *M* and *N* such coordinate sequences,
-defining lines or rings.
-
-Points
-++++++
-
-The point factory *Point* takes a coordinate sequence parameter
-
-.. code-block:: python
-
-  >>> from shapely.geometry import Point
-  >>> point = Point(a)
-
-The alternate form is to pass individual coordinate parameters
-
-.. code-block:: python
-
-  >>> point = Point(x0, y0 [, z0])
-
-LineStrings
-+++++++++++
-
-To create a line string, pass in an ordered sequence of coordinate sequences:
-
-.. code-block:: python
-
-  >>> from shapely.geometry import LineString
-  >>> line = LineString((a1, ..., aM))
-
-Polygons
-++++++++
-
-A polygon with only an exterior boundary and no holes is created by passing the sequence representation of a closed ring
-
-.. code-block:: python
-
-  >>> from shapely.geometry import Polygon
-  >>> polygon = Polygon((a1, ..., aM))
-
-If **a**\ 1 is not exactly equal to **a**\ M, the factory will close the ring.
-The following (unit square) polygons are therefore topologically equal
-
-.. code-block:: python
-
-  >>> 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*)
-
-.. code-block:: python
-
-  >>> 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.
-
-
-Multipart Geometry Factories
-----------------------------
-
-MultiPoints
-+++++++++++
-
-An *N*\ -point geometry is created by passing an unordered sequence of
-coordinate sequences [**c**\ 1, ..., **c**\ N]
-
-.. code-block:: python
-
-  >>> from shapely.geometry import MultiPoint
-  >>> points = MultiPoint([c1, ..., cN])
-
-MultiLineStrings
-++++++++++++++++
-
-A multi-line geometry is created by passing a sequence of representations of
-lines
-
-.. code-block:: python
-
-  >>> from shapely.geometry import MultiLineString
-  >>> lines = MultiLineString([(a1, ..., aM), (b1, ..., bN), ...])
-
-MultiPolygons
-+++++++++++++
-
-A multi-polygon geometry is created by passing a sequence of exterior ring and
-hole list tuples 
-
-.. code-block:: python
-
-  >>> 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
-
-.. code-block:: python
-
-  >>> shell = (a1, ..., aM)
-  >>> holes = [(b1, ..., bN), ...]
-  >>> lines = MultiPolygon([(shell, holes), ...])
-
-
-Null Geometries
----------------
-
-Null geometries can be created by calling the factories with no arguments, but
-almost nothing can be done with a null geometry.
-
-.. code-block:: python
-
-  >>> 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.
-
-.. code-block:: python
-
-  >>> l_null.coords = [(0, 0), (1, 1)]
-  >>> print l_null.length
-  1.414...
-
-
-Constructive Spatial Analysis Methods
--------------------------------------
-
-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 |illustration2|_.
-
-.. |illustration2| replace:: illustration
-.. _illustration2: http://www.jump-project.org/project.php?PID=JTS&SID=OVER#spatialanalysismethods
-
-Example Geometries
-++++++++++++++++++
-
-.. code-block:: python
-
-  >>> 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)))
-
-Buffer
-++++++
-
-.buffer(width, quadsegs=16) : geometry
-  Returns a buffer region having the given width and with a specified number of
-  segments used to approximate curves.
-  
-The default result of buffering a point is an N-gon approximation of a circle:
-
-.. code-block:: python
-
-  >>> 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
-
-Boundary
-++++++++
-
-.boundary : geometry
-  Returns a lower dimension geometry. The boundary of a polygon is a line, the
-  boundary of a line is a collection of points. The boundary of a point is an
-  empty (null) collection.
-
-.. code-block:: python
-
-  >>> polygon.boundary
-  <shapely.geometry.linestring.LineString object at ...>
-  >>> line_b.boundary
-  <shapely.geometry.multipoint.MultiPoint object at ...>
-  >>> point_r.boundary.is_empty
-  True
-
-Centroid
-++++++++
-
-.centroid : geometry
-  Returns the centroid, or geometric center of the polygon.
-
-.. code-block:: python
-
-  >>> centroid_point = polygon.centroid
-  >>> centroid_point.wkt
-  'POINT (-0.0000000000000000 -0.0000000000000000)'
-
-Convex Hull
-+++++++++++
-
-.convex_hull : geometry
-  Imagine an elastic band stretched around the geometry: that's a convex hull,
-  more or less.
-
-For example, collect the three points into a multi-point geometry, and get the
-triangular polygon that is their convex hull:
-
-.. code-block:: python
-
-  >>> multi_point = point_r.union(point_g)
-  >>> multi_point = multi_point.union(point_b)
-  >>> multi_point.convex_hull
-  <shapely.geometry.polygon.Polygon object at ...>
-
-Difference
-++++++++++
-
-.difference(other) : geometry
-  Returns a geometry representing the points making up this geometry that do
-  not make up *other*. Note that A.difference(B) is not necessarily equal to
-  B.difference(A).
-
-.. code-block:: python
-
-  >>> hull = multi_point.convex_hull
-  >>> polygon.difference(hull)
-  <shapely.geometry.polygon.Polygon object at ...>
-
-Envelope
-++++++++
-
-.envelope : geometry
-  Returns the geometry's rectangular polygon envelope.
-
-.. code-block:: python
-
-  >>> polygon.envelope
-  <shapely.geometry.polygon.Polygon object at ...>
-
-Intersection
-++++++++++++
-
-.intersection(other) : geometry
-  Returns the intersection of one geometry and the *other* geometry.
-
-.. code-block:: python
-
-  >>> polygon.intersection(hull)
-  <shapely.geometry.polygon.Polygon object at ...>
-
-Symmetric Difference
-++++++++++++++++++++
-
-.symmetric_difference(other) : geometry
-  Returns a geometry combining the points in this geometry not in *other*, and
-  the points in *other* not in this geometry.
-
-.. code-block:: python
-
-  >>> polygon.symmetric_difference(hull)
-  <shapely.geometry.multipolygon.MultiPolygon object at ...>
-
-Union
-+++++
-
-.union(other) : geometry
-  Returns the union of one geometry and the *other* geometry.
-
-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:
-
-.. code-block:: python
-
-  >>> hull.union(polygon)
-  <shapely.geometry.polygon.Polygon object at ...>
-
-
-Other Operations
-----------------
-
-Polygonization
-++++++++++++++
-
-shapely.ops.polygonize(lines) : iterator
-  Returns an iterator over polygons constructed from the *lines* iterator. The
-  elements of *lines* may be Shapely geometries, objects that provide the geo
-  interface, or Numpy arrays or Python sequences shaped like LineStrings.
-
-.. code-block:: python
-
-  >>> 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 ...>]
-
-
-Unary Spatial Predicates
-------------------------
-
-These are implemented as Python attributes.
-
-Is Empty
-++++++++
-
-.is_empty : bool
-  True if the set of points in this geometry is empty, else False. For more
-  details, see
-  http://geos.refractions.net/ro/doxygen_docs/html/classgeos_1_1geom_1_1Geometry.html#a17.  
-
-Is Valid
-++++++++
-
-.is_valid : bool
-  True if the geometry is valid (definition depends on sub-class), else False.
-  For more details, see
-  http://geos.refractions.net/ro/doxygen_docs/html/classgeos_1_1geom_1_1Geometry.html#a16.
-
-Is Ring
-+++++++
-
-.is_ring : bool
-  True if the geometry is a closed ring, else False.
-
-Has Z
-+++++
-
-.has_z : bool
-  True if the geometry's coordinate sequence(s) have z values (are
-  3-dimensional)
-
-Examples
-++++++++
-
-.. code-block:: python
-
-  >>> 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.)
-
-
-Binary Spatial Predicates
--------------------------
-
-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.
-
-Contains
-++++++++
-
-.contains(other) : bool
-  True if the geometry is spatially within, without touching. Applies to all
-  types of geometries.
-
-.. code-block:: python
-
-  >>> polygon.contains(point_b)
-  True
-
-Crosses
-+++++++
-
-.crosses(other) : bool
-  Only linear geometries (lines, rings, polygon boundaries) may ever cross. No
-  geometry may ever cross a point.
-
-.. code-block:: python
-
-  >>> line_b.crosses(polygon)
-  True
-
-Disjoint
-++++++++
-
-.disjoint(other) : bool
-  True if geometries do not spatially relate in any way, else False. See the
-  complementary *intersects*. 
-
-.. code-block:: python
-
-  >>> polygon.disjoint(point_r)
-  True
-
-Equals
-++++++
-
-.equals(other) : bool
-  Two geometries are topologically equal if their interiors intersect and no
-  part of the interior or boundary of one geometry intersects the exterior of
-  the other. Not to be confused with Python's *__equals__*.
-
-Intersects
-++++++++++
-
-.intersects(other) : bool
-  This predicate is the complement of *disjoint*: geometries that do not
-  intersect are disjoint. Intersects is the most inclusive predicate.
-
-.. code-block:: python
-
-  >>> polygon.intersects(point_b)
-  True
-
-Touches
-+++++++
-
-.touches(other) : bool
-  True if geometries *only* touch. The least inclusive predicate.
-
-.. code-block:: python
-
-  >>> polygon.touches(line_g)
-  True
-  >>> polygon.touches(line_b)
-  False
-
-Within
-++++++
-
-.within(other): bool
-  The inverse of *contains*.
-
-
-General Methods
----------------
-
-Distance
-++++++++
-
-.distance(other) : geometry
-  The minimum distance from one geometry to the other.
-
-.. code-block:: python
-
-  >>> Point(0,0).distance(Point(1,1))
-  1.4142135623730951
-
-
-Scalar Properties
------------------
-
-Area
-++++
-
-.area : float
-  Area of the geometry, unitless. Non-zero only for surfaces (polygons,
-  multi-polygons).
-
-Bounds
-++++++
-
-.bounds : tuple
-  The geometry's (minx, miny, maxx, maxy) bounding box.
-
-Length
-++++++
-
-.length : float
-  Length of the geometry, unitless. Non-zero only for linear geometries
-  (line strings, rings, polygon boundaries)
-
-Examples
-++++++++
-
-.. code-block:: python
-
-  >>> 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
-
-
-Geometry Parts and Coordinates
-==============================
-
-Coordinate Sequences
---------------------
-
-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.
-
-.. code-block:: python
-
-  >>> 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 (**a**\ 1, ...,
-**a**\ M) to the coords attribute.
-
-.. code-block:: python
-
-  >>> point_new = Point(0, 0)
-  >>> point_new.coords = (1, 1)
-  >>> list(point_new.coords)
-  [(1.0, 1.0)]
-
-For line strings:
-
-.. code-block:: python
-
-  >>> 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)]
-
-
-Polygon Rings
--------------
-
-The exterior boundary of a polygon can be accessed through the *exterior*
-attribute of the geometry object.
-
-.. code-block:: python
-
-  >>> 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.
-
-
-Sub-geometries
---------------
-
-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:
-
-.. code-block:: python
-
-  >>> 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.
-
-
-Point Coordinates
------------------
-
-For the sake of convenience the coordinate values of points can be accessed
-read-only via **x**, **y**, and **z** attributes:
-
-.. code-block:: python
-
-  >>> point = Point(1.0, 1.0)
-  >>> point.x
-  1.0
-  >>> point.y
-  1.0
-
-
-Interoperation
-==============
-
-Shapely provides 4 avenues for interoperation with other Python and GIS
-software.
-
-Well-known Formats
-------------------
-
-Well-known Text (WKT)
-+++++++++++++++++++++
-
-The WKT representation of any geometry object can be had via the **wkt**
-attribute:
-
-.. code-block:: python
-
-  >>> 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
-
-.. code-block:: python
-
-  >>> 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) 
-
-.. code-block:: python
-
-  >>> from shapely.wkt import loads
-  >>> loads('POINT (0 0)')
-  <shapely.geometry.point.Point object at ...>
-
-Well-known Binary (WKB)
-+++++++++++++++++++++++
-
-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:
-
-.. code-block:: python
-
-  >>> 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 Sequences
-----------------
-
-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**:
-
-.. code-block:: python
-
-  >>> 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
-
-.. code-block:: python
-
-  >>> coords[0] = 1.0
-  >>> pa.wkt
-  'POINT (1.0000000000000000 4.0000000000000000)'
-
-The **asLineString** adapter works much the same. The **asPolygon** adapter is
-used like
-
-.. code-block:: python
-
-  >>> 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
-
-
-Numpy Array Interface
----------------------
-
-Shapely geometries provide the Numpy array interface which means that points,
-line strings, and polygon rings can be used as Numpy arrays:
-
-.. code-block:: python
-
-  >>> 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
-
-.. code-block:: python
-
-  >>> 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
-
-.. code-block:: python
-
-  >>> 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.
-
-
-Python Geo Interface
---------------------
-
-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:
-
-.. code-block:: python
-
-  >>> 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:
-
-.. code-block:: python
-
-  >>> 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.
-
-.. _Python geo interface: http://trac.gispython.org/projects/PCL/wiki/PythonGeoInterface
-
-
-Advanced Features
-=================
-
-Iterative Operations
---------------------
-
-Shapely provides functions for efficient operations on large sets of
-geometries.
-
-Contains
-++++++++
-
-To find the subset of points that are contained within a polygon, use
-*shapely.iterops.contains*:
-
-.. code-block:: python
-
-  >>> 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*.
-
-.. code-block:: python
-
-  >>> list(iterops.contains(polygon, iter((p, p.wkt) for p in points)))
-  ['POINT (0.5000000000000000 0.5000000000000000)']
-
-
-Credits
-=======
-
-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).
-
-
diff --git a/setup.py b/setup.py
index 14e3b5e..0ed51f1 100644
--- a/setup.py
+++ b/setup.py
@@ -1,30 +1,37 @@
-from setuptools import setup, Extension
-from sys import version_info
+import warnings
 
-# Require ctypes egg only for Python < 2.5
-install_requires = ['setuptools']
-if version_info[:2] < (2,5):
-    install_requires.append('ctypes')
+try:
+    from distribute_setup import use_setuptools
+    use_setuptools()
+except:
+    warnings.warn(
+    "Failed to import distribute_setup, continuing without distribute.", 
+    Warning)
+
+from setuptools import setup, find_packages
+import sys
 
-# Get text from README.txt
 readme_text = file('README.txt', 'rb').read()
 
-setup(name          = 'Shapely',
-      version       = '1.0.15',
-      description   = 'Geospatial geometries, predicates, and operations',
-      license       = 'BSD',
-      keywords      = 'geometry topology',
-      author        = 'Sean Gillies',
-      author_email  = 'sgillies at frii.com',
-      maintainer    = 'Sean Gillies',
-      maintainer_email  = 'sgillies at frii.com',
-      url   = 'http://trac.gispython.org/lab/wiki/Shapely',
-      long_description = readme_text,
-      packages      = ['shapely', 'shapely.geometry'],
-      install_requires = install_requires,
-      #tests_require = ['numpy'], -- not working with "tests" command
-      test_suite = 'tests.test_suite',
-      classifiers   = [
+setup_args = dict(
+    metadata_version    = '1.2',
+    name                = 'Shapely',
+    version             = '1.2.1',
+    requires_python     = '>=2.5,<3',
+    requires_external   = 'libgeos_c (>=3.1)', 
+    description         = 'Geometric objects, predicates, and operations',
+    license             = 'BSD',
+    keywords            = 'geometry topology gis',
+    author              = 'Sean Gillies',
+    author_email        = 'sean.gillies at gmail.com',
+    maintainer          = 'Sean Gillies',
+    maintainer_email    = 'sean.gillies at gmail.com',
+    url                 = 'http://trac.gispython.org/lab/wiki/Shapely',
+    long_description    = readme_text,
+    packages            = ['shapely', 'shapely.geometry'],
+    scripts             = ['examples/dissolve.py', 'examples/intersect.py'],
+    test_suite          = 'shapely.tests.test_suite',
+    classifiers         = [
         'Development Status :: 5 - Production/Stable',
         'Intended Audience :: Developers',
         'Intended Audience :: Science/Research',
@@ -33,4 +40,12 @@ setup(name          = 'Shapely',
         'Programming Language :: Python',
         'Topic :: Scientific/Engineering :: GIS',
         ],
-)
+    )
+
+# Add DLLs for Windows
+if sys.platform == 'win32':
+    setup_args.update(
+        data_files=[('DLLs', ['DLLs/geos.dll', 'DLLs/libgeos-3-0-0.dll']),]
+        )
+
+setup(**setup_args)
diff --git a/shapely/.___init__.py b/shapely/.___init__.py
deleted file mode 100644
index a69f6cc..0000000
Binary files a/shapely/.___init__.py and /dev/null differ
diff --git a/shapely/._ctypes_declarations.py b/shapely/._ctypes_declarations.py
deleted file mode 100644
index 8078108..0000000
Binary files a/shapely/._ctypes_declarations.py and /dev/null differ
diff --git a/shapely/._iterops.py b/shapely/._iterops.py
deleted file mode 100644
index 69476e9..0000000
Binary files a/shapely/._iterops.py and /dev/null differ
diff --git a/shapely/._ops.py b/shapely/._ops.py
deleted file mode 100644
index 6011f43..0000000
Binary files a/shapely/._ops.py and /dev/null differ
diff --git a/shapely/._predicates.py b/shapely/._predicates.py
deleted file mode 100644
index b3329f2..0000000
Binary files a/shapely/._predicates.py and /dev/null differ
diff --git a/shapely/._topology.py b/shapely/._topology.py
deleted file mode 100644
index b7dcb87..0000000
Binary files a/shapely/._topology.py and /dev/null differ
diff --git a/shapely/._wkb.py b/shapely/._wkb.py
deleted file mode 100644
index 18c1290..0000000
Binary files a/shapely/._wkb.py and /dev/null differ
diff --git a/shapely/__init__.py b/shapely/__init__.py
index 792d600..5bb534f 100644
--- a/shapely/__init__.py
+++ b/shapely/__init__.py
@@ -1 +1 @@
-#
+# package
diff --git a/shapely/coords.py b/shapely/coords.py
new file mode 100644
index 0000000..005115e
--- /dev/null
+++ b/shapely/coords.py
@@ -0,0 +1,167 @@
+"""Coordinate sequence utilities
+"""
+
+from array import array
+from ctypes import string_at, byref, c_char_p, c_double, c_void_p
+from ctypes import c_int, c_size_t, c_uint
+import sys
+
+from shapely.geos import lgeos
+from shapely.topology import Validating
+
+
+class CoordinateSequence(object):
+    """
+    Iterative access to coordinate tuples from the parent geometry's coordinate
+    sequence.
+
+    Example:
+
+      >>> from shapely.wkt import loads
+      >>> g = loads('POINT (0.0 0.0)')
+      >>> list(g.coords)
+      [(0.0, 0.0)]
+
+    """
+
+    # Attributes
+    # ----------
+    # _cseq : c_void_p
+    #     Ctypes pointer to GEOS coordinate sequence
+    # _ndim : int
+    #     Number of dimensions (2 or 3, generally)
+    # __p__ : object
+    #     Parent (Shapely) geometry    
+    _cseq = None
+    _ndim = None
+    __p__ = None
+
+    def __init__(self, parent):
+        self.__p__ = parent
+
+    def _update(self):
+        self._ndim = self.__p__._ndim
+        self._cseq = lgeos.GEOSGeom_getCoordSeq(self.__p__._geom)
+    
+    def __len__(self):
+        self._update()
+        cs_len = c_uint(0)
+        lgeos.GEOSCoordSeq_getSize(self._cseq, byref(cs_len))
+        return cs_len.value
+
+    def __iter__(self):
+        self._update()
+        dx = c_double()
+        dy = c_double()
+        dz = c_double()
+        for i in range(self.__len__()):
+            lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(dx))
+            lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(dy))
+            if self._ndim == 3: # TODO: use hasz
+                lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(dz))
+                yield (dx.value, dy.value, dz.value)
+            else:
+                yield (dx.value, dy.value)
+
+    def __getitem__(self, i):
+        self._update()
+        M = self.__len__()
+        if i + M < 0 or i >= M:
+            raise IndexError("index out of range")
+        if i < 0:
+            ii = M + i
+        else:
+            ii = i
+        dx = c_double()
+        dy = c_double()
+        dz = c_double()
+        lgeos.GEOSCoordSeq_getX(self._cseq, ii, byref(dx))
+        lgeos.GEOSCoordSeq_getY(self._cseq, ii, byref(dy))
+        if self._ndim == 3: # TODO: use hasz
+            lgeos.GEOSCoordSeq_getZ(self._cseq, ii, byref(dz))
+            return (dx.value, dy.value, dz.value)
+        else:
+            return (dx.value, dy.value)
+
+    @property
+    def ctypes(self):
+        self._update()
+        n = self._ndim
+        m = self.__len__()
+        array_type = c_double * (m * n)
+        data = array_type()
+        temp = c_double()
+        for i in xrange(m):
+            lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(temp))
+            data[n*i] = temp.value
+            lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(temp))
+            data[n*i+1] = temp.value
+            if n == 3: # TODO: use hasz
+                lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(temp))
+                data[n*i+2] = temp.value
+        return data
+
+    def array_interface(self):
+        """Provide the Numpy array protocol."""
+        if sys.byteorder == 'little':
+            typestr = '<f8'
+        elif sys.byteorder == 'big':
+            typestr = '>f8'
+        else:
+            raise ValueError(
+                "Unsupported byteorder: neither little nor big-endian")
+        ai = {
+            'version': 3,
+            'typestr': typestr,
+            'data': self.ctypes,
+            }
+        ai.update({'shape': (len(self), self._ndim)})
+        return ai
+    
+    __array_interface__ = property(array_interface)
+    
+    @property
+    def xy(self):
+        """X and Y arrays"""
+        self._update()
+        m = self.__len__()
+        x = array('d')
+        y = array('d')
+        temp = c_double()
+        for i in xrange(m):
+            lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(temp))
+            x.append(temp.value)
+            lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(temp))
+            y.append(temp.value)
+        return x, y
+            
+
+class BoundsOp(Validating):
+
+    def __init__(self, *args):
+        pass
+
+    def __call__(self, this):
+        self._validate(this)
+        env = this.envelope
+        if env.geom_type == 'Point':
+            return env.bounds
+        cs = lgeos.GEOSGeom_getCoordSeq(env.exterior._geom)
+        cs_len = c_uint(0)
+        lgeos.GEOSCoordSeq_getSize(cs, byref(cs_len))
+        minx = 1.e+20
+        maxx = -1e+20
+        miny = 1.e+20
+        maxy = -1e+20
+        temp = c_double()
+        for i in xrange(cs_len.value):
+            lgeos.GEOSCoordSeq_getX(cs, i, byref(temp))
+            x = temp.value
+            if x < minx: minx = x
+            if x > maxx: maxx = x
+            lgeos.GEOSCoordSeq_getY(cs, i, byref(temp))
+            y = temp.value
+            if y < miny: miny = y
+            if y > maxy: maxy = y
+        return (minx, miny, maxx, maxy)
+
diff --git a/shapely/ctypes_declarations.py b/shapely/ctypes_declarations.py
index 60e169c..6d1971c 100644
--- a/shapely/ctypes_declarations.py
+++ b/shapely/ctypes_declarations.py
@@ -3,7 +3,10 @@
 
 import ctypes
 
-def prototype(lgeos):
+class allocated_c_char_p(ctypes.c_char_p):
+    pass
+
+def prototype(lgeos, geosVersion):
 
     lgeos.initGEOS.restype = None
 
@@ -14,7 +17,7 @@ def prototype(lgeos):
     lgeos.GEOSGeomFromWKT.restype = ctypes.c_void_p
     lgeos.GEOSGeomFromWKT.argtypes = [ctypes.c_char_p]
 
-    lgeos.GEOSGeomToWKT.restype = ctypes.c_char_p
+    lgeos.GEOSGeomToWKT.restype = allocated_c_char_p
     lgeos.GEOSGeomToWKT.argtypes = [ctypes.c_void_p]
 
     lgeos.GEOS_setWKBOutputDims.restype = ctypes.c_int
@@ -23,10 +26,9 @@ def prototype(lgeos):
     lgeos.GEOSGeomFromWKB_buf.restype = ctypes.c_void_p
     lgeos.GEOSGeomFromWKB_buf.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
 
-    lgeos.GEOSGeomToWKB_buf.restype = ctypes.c_void_p
+    lgeos.GEOSGeomToWKB_buf.restype = allocated_c_char_p
     lgeos.GEOSGeomToWKB_buf.argtypes = [ctypes.c_void_p , ctypes.POINTER(ctypes.c_size_t)]
 
-
     lgeos.GEOSCoordSeq_create.restype = ctypes.c_void_p
     lgeos.GEOSCoordSeq_create.argtypes = [ctypes.c_uint, ctypes.c_uint]
 
@@ -93,6 +95,12 @@ def prototype(lgeos):
     lgeos.GEOSBuffer.restype = ctypes.c_void_p
     lgeos.GEOSBuffer.argtypes = [ctypes.c_void_p, ctypes.c_double, ctypes.c_int]
 
+    lgeos.GEOSSimplify.restype = ctypes.c_void_p
+    lgeos.GEOSSimplify.argtypes = [ctypes.c_void_p, ctypes.c_double]
+
+    lgeos.GEOSTopologyPreserveSimplify.restype = ctypes.c_void_p
+    lgeos.GEOSTopologyPreserveSimplify.argtypes = [ctypes.c_void_p, ctypes.c_double]
+
     lgeos.GEOSConvexHull.restype = ctypes.c_void_p
     lgeos.GEOSConvexHull.argtypes = [ctypes.c_void_p]
 
@@ -114,7 +122,7 @@ def prototype(lgeos):
     lgeos.GEOSGetCentroid.restype = ctypes.c_void_p
     lgeos.GEOSGetCentroid.argtypes = [ctypes.c_void_p]
 
-    lgeos.GEOSRelate.restype = ctypes.c_char_p
+    lgeos.GEOSRelate.restype = allocated_c_char_p
     lgeos.GEOSRelate.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
 
     lgeos.GEOSPolygonize.restype = ctypes.c_void_p
@@ -126,43 +134,49 @@ def prototype(lgeos):
     lgeos.GEOSRelatePattern.restype = ctypes.c_char
     lgeos.GEOSRelatePattern.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p]
 
-    lgeos.GEOSDisjoint.restype = ctypes.c_int
+    lgeos.GEOSDisjoint.restype = ctypes.c_byte
     lgeos.GEOSDisjoint.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
 
-    lgeos.GEOSTouches.restype = ctypes.c_int
+    lgeos.GEOSTouches.restype = ctypes.c_byte
     lgeos.GEOSTouches.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
 
-    lgeos.GEOSIntersects.restype = ctypes.c_int
+    lgeos.GEOSIntersects.restype = ctypes.c_byte
     lgeos.GEOSIntersects.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
 
-    lgeos.GEOSCrosses.restype = ctypes.c_int
+    lgeos.GEOSCrosses.restype = ctypes.c_byte
     lgeos.GEOSCrosses.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
 
-    lgeos.GEOSWithin.restype = ctypes.c_int
+    lgeos.GEOSWithin.restype = ctypes.c_byte
     lgeos.GEOSWithin.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
 
-    lgeos.GEOSContains.restype = ctypes.c_int
+    lgeos.GEOSContains.restype = ctypes.c_byte
     lgeos.GEOSContains.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
 
-    lgeos.GEOSOverlaps.restype = ctypes.c_int
+    lgeos.GEOSOverlaps.restype = ctypes.c_byte
     lgeos.GEOSOverlaps.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
 
-    lgeos.GEOSEquals.restype = ctypes.c_int
+    lgeos.GEOSEquals.restype = ctypes.c_byte
     lgeos.GEOSEquals.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
 
-    lgeos.GEOSisEmpty.restype = ctypes.c_int
+    lgeos.GEOSEqualsExact.restype = ctypes.c_byte
+    lgeos.GEOSEqualsExact.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_double]
+
+    lgeos.GEOSisEmpty.restype = ctypes.c_byte
     lgeos.GEOSisEmpty.argtypes = [ctypes.c_void_p]
 
-    lgeos.GEOSisValid.restype = ctypes.c_int
+    lgeos.GEOSisValid.restype = ctypes.c_byte
     lgeos.GEOSisValid.argtypes = [ctypes.c_void_p]
 
-    lgeos.GEOSisSimple.restype = ctypes.c_int
+    lgeos.GEOSisValidReason.restype = allocated_c_char_p
+    lgeos.GEOSisValidReason.argtypes = [ctypes.c_void_p]
+
+    lgeos.GEOSisSimple.restype = ctypes.c_byte
     lgeos.GEOSisSimple.argtypes = [ctypes.c_void_p]
 
-    lgeos.GEOSisRing.restype = ctypes.c_int
+    lgeos.GEOSisRing.restype = ctypes.c_byte
     lgeos.GEOSisRing.argtypes = [ctypes.c_void_p]
 
-    lgeos.GEOSHasZ.restype = ctypes.c_int
+    lgeos.GEOSHasZ.restype = ctypes.c_byte
     lgeos.GEOSHasZ.argtypes = [ctypes.c_void_p]
 
     lgeos.GEOSGeomType.restype = ctypes.c_char_p
@@ -210,3 +224,53 @@ def prototype(lgeos):
     lgeos.GEOSDistance.restype = ctypes.c_int
     lgeos.GEOSDistance.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
 
+    if geosVersion >= (1, 5, 0):
+
+        if hasattr(lgeos, 'GEOSFree'):
+            lgeos.GEOSFree.restype = None
+            lgeos.GEOSFree.argtypes = [ctypes.c_void_p]
+
+        # Prepared geometry, GEOS C API 1.5.0+
+        lgeos.GEOSPrepare.restype = ctypes.c_void_p
+        lgeos.GEOSPrepare.argtypes = [ctypes.c_void_p]
+
+        lgeos.GEOSPreparedGeom_destroy.restype = None
+        lgeos.GEOSPreparedGeom_destroy.argtypes = [ctypes.c_void_p]
+
+        lgeos.GEOSPreparedIntersects.restype = ctypes.c_int
+        lgeos.GEOSPreparedIntersects.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+        lgeos.GEOSPreparedContains.restype = ctypes.c_int
+        lgeos.GEOSPreparedContains.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+        lgeos.GEOSPreparedContainsProperly.restype = ctypes.c_int
+        lgeos.GEOSPreparedContainsProperly.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+        lgeos.GEOSPreparedCovers.restype = ctypes.c_int
+        lgeos.GEOSPreparedCovers.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+    # Other, GEOS C API 1.5.0+
+    if geosVersion >= (1, 5, 0):
+        lgeos.GEOSUnionCascaded.restype = ctypes.c_void_p
+        lgeos.GEOSUnionCascaded.argtypes = [ctypes.c_void_p]
+
+    # 1.6.0
+    if geosVersion >= (1, 6, 0):
+        # Linear referencing features aren't found in versions 1.5,
+        # but not in all libs versioned 1.6.0 either!
+        if hasattr(lgeos, 'GEOSProject'):
+            lgeos.GEOSProject.restype = ctypes.c_double
+            lgeos.GEOSProject.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+            lgeos.GEOSProjectNormalized.restype = ctypes.c_double
+            lgeos.GEOSProjectNormalized.argtypes = [ctypes.c_void_p, 
+                                                    ctypes.c_void_p]
+
+            lgeos.GEOSInterpolate.restype = ctypes.c_void_p
+            lgeos.GEOSInterpolate.argtypes = [ctypes.c_void_p, 
+                                              ctypes.c_double]
+
+            lgeos.GEOSInterpolateNormalized.restype = ctypes.c_void_p
+            lgeos.GEOSInterpolateNormalized.argtypes = [ctypes.c_void_p, 
+                                                        ctypes.c_double]
+
diff --git a/shapely/geometry/._base.py b/shapely/geometry/._base.py
deleted file mode 100644
index 8fe3660..0000000
Binary files a/shapely/geometry/._base.py and /dev/null differ
diff --git a/shapely/geometry/._linestring.py b/shapely/geometry/._linestring.py
deleted file mode 100644
index 01bc948..0000000
Binary files a/shapely/geometry/._linestring.py and /dev/null differ
diff --git a/shapely/geometry/__init__.py b/shapely/geometry/__init__.py
index b66efc0..f39e02b 100644
--- a/shapely/geometry/__init__.py
+++ b/shapely/geometry/__init__.py
@@ -1,3 +1,6 @@
+"""Geometry classes and factories
+"""
+
 from geo import shape, asShape
 from point import Point, asPoint
 from linestring import LineString, asLineString
@@ -6,3 +9,12 @@ from multipoint import MultiPoint, asMultiPoint
 from multilinestring import MultiLineString, asMultiLineString
 from multipolygon import MultiPolygon, asMultiPolygon
 from collection import GeometryCollection
+
+__all__ = [
+    'shape', 'asShape', 'Point', 'asPoint', 'LineString', 'asLineString',
+    'Polygon', 'asPolygon', 'MultiPoint', 'asMultiPoint',
+    'MultiLineString', 'asMultiLineString', 'MultiPolygon', 'asMultiPolygon',
+    'GeometryCollection'
+    ]
+
+
diff --git a/shapely/geometry/base.py b/shapely/geometry/base.py
index 3cde024..828d3a8 100644
--- a/shapely/geometry/base.py
+++ b/shapely/geometry/base.py
@@ -1,14 +1,14 @@
-"""
-Base geometry class and utilities.
+"""Base geometry class and utilities
 """
 
-from ctypes import string_at, byref, c_int, c_size_t, c_char_p, c_double, c_void_p
+from functools import wraps
 import sys
+import warnings
 
-from shapely.geos import lgeos, free, allocated_c_char_p
-from shapely.predicates import BinaryPredicate, UnaryPredicate
-from shapely.topology import BinaryTopologicalOp, UnaryTopologicalOp
-
+from shapely.coords import CoordinateSequence
+from shapely.geos import lgeos
+from shapely.impl import DefaultImplementation
+from shapely import wkb, wkt
 
 GEOMETRY_TYPES = [
     'Point',
@@ -23,14 +23,13 @@ GEOMETRY_TYPES = [
 
 def geometry_type_name(g):
     if g is None:
-        raise ValueError, "Null geometry has no type"
+        raise ValueError("Null geometry has no type")
     return GEOMETRY_TYPES[lgeos.GEOSGeomTypeId(g)]
 
-# Abstract geometry factory for use with topological methods below
-
 def geom_factory(g, parent=None):
+    # Abstract geometry factory for use with topological methods below
     if not g:
-        raise ValueError, "No Shapely geometry can be created from this null value"
+        raise ValueError("No Shapely geometry can be created from null value")
     ob = BaseGeometry()
     geom_type = geometry_type_name(g)
     # TODO: check cost of dynamic import by profiling
@@ -46,222 +45,63 @@ def geom_factory(g, parent=None):
     ob._ndim = 2 # callers should be all from 2D worlds
     return ob
 
-
-class CoordinateSequence(object):
-    
-    _geom = None
-    _cseq = None
-    _ndim = None
-    _length = 0
-    index = 0
-    __p__ = None
-
-    def __init__(self, parent):
-        self.__p__ = parent
-        self._geom = parent._geom
-        self._ndim = parent._ndim
-        self.update_cseq()
-
-    def update_cseq(self):
-        self._cseq = lgeos.GEOSGeom_getCoordSeq(self._geom)
-        
-    def __iter__(self):
-        self.index = 0
-        self.update_cseq()
-        self._length = self.__len__()
-        return self
-
-    def next(self):
-        dx = c_double()
-        dy = c_double()
-        dz = c_double()
-        i = self.index
-        if i < self._length:
-            lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(dx))
-            lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(dy))
-            if self._ndim == 3: # TODO: use hasz
-                lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(dz))
-                self.index += 1
-                return (dx.value, dy.value, dz.value)
-            else:
-                self.index += 1
-                return (dx.value, dy.value)
-        else:
-            raise StopIteration 
-
-    def __len__(self):
-        cs_len = c_int(0)
-        lgeos.GEOSCoordSeq_getSize(self._cseq, byref(cs_len))
-        return cs_len.value
-    
-    def __getitem__(self, i):
-        self.update_cseq()
-        M = self.__len__()
-        if i + M < 0 or i >= M:
-            raise IndexError, "index out of range"
-        if i < 0:
-            ii = M + i
-        else:
-            ii = i
-        dx = c_double()
-        dy = c_double()
-        dz = c_double()
-        lgeos.GEOSCoordSeq_getX(self._cseq, ii, byref(dx))
-        lgeos.GEOSCoordSeq_getY(self._cseq, ii, byref(dy))
-        if self._ndim == 3: # TODO: use hasz
-            lgeos.GEOSCoordSeq_getZ(self._cseq, ii, byref(dz))
-            return (dx.value, dy.value, dz.value)
-        else:
-            return (dx.value, dy.value)
-
-    @property
-    def ctypes(self):
-        self.update_cseq()
-        n = self._ndim
-        m = self.__len__()
-        array_type = c_double * (m * n)
-        data = array_type()
-        temp = c_double()
-
-        for i in xrange(m):
-            lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(temp))
-            data[n*i] = temp.value
-            lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(temp))
-            data[n*i+1] = temp.value
-            if n == 3: # TODO: use hasz
-                lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(temp))
-                data[n*i+2] = temp.value
-        return data
-
-    def array_interface(self):
-        """Provide the Numpy array protocol."""
-        if sys.byteorder == 'little':
-            typestr = '<f8'
-        elif sys.byteorder == 'big':
-            typestr = '>f8'
-        else:
-            raise ValueError, \
-            "Unsupported byteorder: neither little nor big-endian"
-        ai = {
-            'version': 3,
-            'typestr': typestr,
-            'data': self.ctypes,
-            }
-        ai.update({'shape': (len(self), self._ndim)})
-        return ai
-    __array_interface__ = property(array_interface)
-
-
-class GeometrySequence(object):
-
-    _factory = None
-    _geom = None
-    __p__ = None
-    _ndim = None
-    _index = 0
-    _length = 0
-
-    def __init__(self, parent, type):
-        self._factory = type
-        self.__p__ = parent
-        self._geom = parent._geom
-        self._ndim = parent._ndim
-
-    def __iter__(self):
-        self._index = 0
-        self._length = self.__len__()
-        return self
-
-    def next(self):
-        if self._index < self.__len__():
-            g = self._factory()
-            g._owned = True
-            g._geom = lgeos.GEOSGetGeometryN(self._geom, self._index)
-            self._index += 1
-            return g
-        else:
-            raise StopIteration 
-
-    def __len__(self):
-        return lgeos.GEOSGetNumGeometries(self._geom)
-
-    def __getitem__(self, i):
-        M = self.__len__()
-        if i + M < 0 or i >= M:
-            raise IndexError, "index out of range"
-        if i < 0:
-            ii = M + i
-        else:
-            ii = i
-        g = self._factory()
-        g._owned = True
-        g._geom = lgeos.GEOSGetGeometryN(self._geom, ii)
-        return g
-
-    @property
-    def _longest(self):
-        max = 0
-        for g in iter(self):
-            l = len(g.coords)
-            if l > max:
-                max = l
-
-
-class HeterogeneousGeometrySequence(GeometrySequence):
-
-    def __init__(self, parent):
-        self.__p__ = parent
-        self._geom = parent._geom
-        self._ndim = parent._ndim
-
-    def next(self):
-        if self._index < self.__len__():
-            sub = lgeos.GEOSGetGeometryN(self._geom, self._index)
-            g = geom_factory(sub)
-            g._owned = True
-            self._index += 1
-            return g
-        else:
-            raise StopIteration 
-    
-
 def exceptNull(func):
     """Decorator which helps avoid GEOS operations on null pointers."""
+    @wraps(func)
     def wrapper(*args, **kwargs):
-        # self is the first arg
-        if not args[0]._geom:
-            raise ValueError, "Null geometry supports no operations"
-        return func(*args, **kwargs)
-    return wrapper
-
-def exceptEitherNull(func):
-    """Decorator which avoids GEOS operations on one or more null pointers."""
-    def wrapper(*args, **kwargs):
-        # self is the first arg
-        if not args[0]._geom or not args[1]._geom:
-            raise ValueError, "Null geometry supports no operations"
+        if not args[0]._geom or args[0].is_empty:
+            raise ValueError("Null/empty geometry supports no operations")
         return func(*args, **kwargs)
     return wrapper
 
+EMPTY = wkb.deserialize('010700000000000000'.decode('hex'))
 
 class BaseGeometry(object):
-    
-    """Provides GEOS spatial predicates and topological operations.
+    """
+    Provides GEOS spatial predicates and topological operations.
+
     """
 
-    __geom__ = None # See _geom property below
+    # Attributes
+    # ----------
+    # __geom__ : c_void_p
+    #     Cached ctypes pointer to GEOS geometry. Not to be accessed.
+    # _geom : c_void_p
+    #     Property by which the GEOS geometry is accessed.
+    # __p__ : object
+    #     Parent (Shapely) geometry
+    # _ctypes_data : object
+    #     Cached ctypes data buffer
+    # _ndim : int
+    #     Number of dimensions (2 or 3, generally)
+    # _crs : object
+    #     Coordinate reference system. Available for Shapely extensions, but
+    #     not implemented here.
+    # _owned : bool
+    #     True if this object's GEOS geometry is owned by another as in the case
+    #     of a multipart geometry member.
+    __geom__ = EMPTY
     __p__ = None
     _ctypes_data = None
     _ndim = None
     _crs = None
     _owned = False
+    
+    # Backend config
+    impl = DefaultImplementation
 
-    def __init__(self):
-        self.__geom__ = None
+    @property
+    def _is_empty(self):
+        return self.__geom__ in [EMPTY, None]
 
-    def __del__(self):
-        if self.__geom__ is not None and not self._owned:
+    def empty(self):
+        if not (self._owned or self._is_empty):
+            from shapely.geos import lgeos
             lgeos.GEOSGeom_destroy(self.__geom__)
+        self.__geom__ = EMPTY
+
+    def __del__(self):
+        self.empty()
         self.__geom__ = None
         self.__p__ = None
 
@@ -269,34 +109,27 @@ class BaseGeometry(object):
         return self.to_wkt()
 
     # To support pickling
-
     def __reduce__(self):
         return (self.__class__, (), self.to_wkb())
 
     def __setstate__(self, state):
-        self.__geom__ = lgeos.GEOSGeomFromWKB_buf(
-                        c_char_p(state), 
-                        c_size_t(len(state))
-                        )
-
-    # _geom has been made a property with the GEOS geometry pointer stored
-    # in __geom so that geometries and geometry adapters can share __del__
-
+        self.empty()
+        self.__geom__ = wkb.deserialize(state)
+    
+    # The _geom property
     def _get_geom(self):
         return self.__geom__
-
     def _set_geom(self, val):
+        self.empty()
         self.__geom__ = val
-    
     _geom = property(_get_geom, _set_geom)
 
     # Array and ctypes interfaces
+    # ---------------------------
 
     @property
     def ctypes(self):
-        """Return a ctypes representation.
-        
-        To be overridden by extension classes."""
+        """Return ctypes buffer"""
         raise NotImplementedError
 
     @property
@@ -306,7 +139,8 @@ class BaseGeometry(object):
         elif sys.byteorder == 'big':
             typestr = '>f8'
         else:
-            raise ValueError, "Unsupported byteorder: neither little nor big-endian"
+            raise ValueError(
+                  "Unsupported byteorder: neither little nor big-endian")
         return {
             'version': 3,
             'typestr': typestr,
@@ -318,191 +152,429 @@ class BaseGeometry(object):
         """Provide the Numpy array protocol."""
         raise NotImplementedError
 
+    # Coordinate access
+    # -----------------
+
     @exceptNull
     def _get_coords(self):
+        """Access to geometry's coordinates (CoordinateSequence)"""
         return CoordinateSequence(self)
 
     def _set_coords(self, ob):
-        raise NotImplementedError, \
-            "set_coords must be provided by derived classes"
+        raise NotImplementedError(
+            "set_coords must be provided by derived classes")
 
     coords = property(_get_coords, _set_coords)
 
+    @property
+    def xy(self):
+        """Separate arrays of X and Y coordinate values"""
+        raise NotImplementedError
+
     # Python feature protocol
 
     @property
     def __geo_interface__(self):
+        """Dictionary representation of the geometry"""
         raise NotImplementedError
 
-    @property
-    def type(self):
-        return self.geometryType()
-
     # Type of geometry and its representations
+    # ----------------------------------------
 
     @exceptNull
     def geometryType(self):
-        """Returns a string representing the geometry type, e.g. 'Polygon'."""
         return geometry_type_name(self._geom)
+    
+    @property
+    def type(self):
+        return self.geometryType()
 
-    @exceptNull
     def to_wkb(self):
-        """Returns a WKB byte string representation of the geometry."""
-        func = lgeos.GEOSGeomToWKB_buf
-        size = c_size_t()
-        def errcheck(result, func, argtuple):
-            if not result: return None
-            retval = string_at(result, size.value)[:]
-            free(result)
-            return retval
-        func.errcheck = errcheck
-        return func(c_void_p(self._geom), byref(size))
+        return wkb.dumps(self)
 
-    @exceptNull
     def to_wkt(self):
-        """Returns a WKT string representation of the geometry."""
-        func = lgeos.GEOSGeomToWKT
-        def errcheck(result, func, argtuple):
-            retval = result.value
-            free(result)
-            return retval
-        func.restype = allocated_c_char_p
-        func.errcheck = errcheck
-        return lgeos.GEOSGeomToWKT(self._geom)
-
-    geom_type = property(geometryType)
-    wkt = property(to_wkt)
-    wkb = property(to_wkb)
-
-    # Basic geometry properties
+        return wkt.dumps(self)
+
+    geom_type = property(geometryType, 
+        doc="""Name of the geometry's type, such as 'Point'"""
+        )
+    wkt = property(to_wkt,
+        doc="""WKT representation of the geometry""")
+    wkb = property(to_wkb,
+        doc="""WKB representation of the geometry""")
+
+    # Real-valued properties and methods
+    # ----------------------------------
 
     @property
-    @exceptNull
     def area(self):
-        a = c_double()
-        retval =  lgeos.GEOSArea(self._geom, byref(a))
-        return a.value
+        """Unitless area of the geometry (float)"""
+        return self.impl['area'](self)
+
+    def distance(self, other):
+        """Unitless distance to other geometry (float)"""
+        return self.impl['distance'](self, other)
 
     @property
-    @exceptNull
     def length(self):
-        len = c_double()
-        retval =  lgeos.GEOSLength(self._geom, byref(len))
-        return len.value
+        """Unitless length of the geometry (float)"""
+        return self.impl['length'](self)
 
-    @exceptEitherNull
-    def distance(self, other):
-        d = c_double()
-        retval =  lgeos.GEOSDistance(self._geom, other._geom, byref(d))
-        return d.value
-
-    # Topology operations
-    #
-    # These use descriptors to reduce the amount of boilerplate.
-   
-    envelope = UnaryTopologicalOp(lgeos.GEOSEnvelope, geom_factory)
-    intersection = BinaryTopologicalOp(lgeos.GEOSIntersection, geom_factory)
-    convex_hull = UnaryTopologicalOp(lgeos.GEOSConvexHull, geom_factory)
-    difference = BinaryTopologicalOp(lgeos.GEOSDifference, geom_factory)
-    symmetric_difference = BinaryTopologicalOp(lgeos.GEOSSymDifference, 
-                                               geom_factory)
-    boundary = UnaryTopologicalOp(lgeos.GEOSBoundary, geom_factory)
-    union = BinaryTopologicalOp(lgeos.GEOSUnion, geom_factory)
-    centroid = UnaryTopologicalOp(lgeos.GEOSGetCentroid, geom_factory)
-
-    # Buffer has a unique distance argument, so not a descriptor
-    @exceptNull
-    def buffer(self, distance, quadsegs=16):
-        return geom_factory(
-            lgeos.GEOSBuffer(self._geom, c_double(distance), c_int(quadsegs))
-            )
+    # Topological properties
+    # ----------------------
 
-    # Relate has a unique string return value
-    @exceptNull
-    def relate(self, other):
-        func = lgeos.GEOSRelate
-        def errcheck(result, func, argtuple):
-            retval = result.value
-            free(result)
-            return retval
-        func.restype = allocated_c_char_p
-        func.errcheck = errcheck
-        return lgeos.GEOSRelate(self._geom, other._geom)
+    @property
+    def boundary(self):
+        """Returns a lower dimension geometry that bounds the object
+        
+        The boundary of a polygon is a line, the boundary of a line is a
+        collection of points. The boundary of a point is an empty (null)
+        collection.
+        """
+        return geom_factory(self.impl['boundary'](self))
 
-    # Binary predicates
-    #
-    # These use descriptors to reduce the amount of boilerplate.
-
-    # TODO: Relate Pattern?
-    disjoint = BinaryPredicate(lgeos.GEOSDisjoint)
-    touches = BinaryPredicate(lgeos.GEOSTouches)
-    intersects = BinaryPredicate(lgeos.GEOSIntersects)
-    crosses = BinaryPredicate(lgeos.GEOSCrosses)
-    within = BinaryPredicate(lgeos.GEOSWithin)
-    contains = BinaryPredicate(lgeos.GEOSContains)
-    overlaps = BinaryPredicate(lgeos.GEOSOverlaps)
-    equals = BinaryPredicate(lgeos.GEOSEquals)
+    @property
+    def bounds(self):
+        """Returns minimum bounding region (minx, miny, maxx, maxy)"""
+        if self.is_empty:
+            return ()
+        else:
+            return self.impl['bounds'](self)
+            
+    @property
+    def centroid(self):
+        """Returns the geometric center of the polygon"""
+        return geom_factory(self.impl['centroid'](self))
+
+    @property
+    def convex_hull(self):
+        """Imagine an elastic band stretched around the geometry: that's a 
+        convex hull, more or less
+
+        The convex hull of a three member multipoint, for example, is a
+        triangular polygon.
+        """ 
+        return geom_factory(self.impl['convex_hull'](self))
+
+    @property
+    def envelope(self):
+        """A figure that envelopes the geometry"""
+        return geom_factory(self.impl['envelope'](self))
+
+    def buffer(self, distance, resolution=16, quadsegs=None):
+        """Returns a geometry with an envelope at a distance from the object's 
+        envelope
+        
+        A negative distance has a "shrink" effect. A zero distance may be used
+        to "tidy" a polygon. The resolution of the buffer around each vertex of
+        the object increases by increasing the resolution keyword parameter
+        or second positional parameter. Note: the use of a `quadsegs` parameter
+        is deprecated and will be gone from the next major release.
+
+        Example:
+
+          >>> from shapely.wkt import loads
+          >>> g = loads('POINT (0.0 0.0)')
+          >>> g.buffer(1.0).area        # 16-gon approx of a unit radius circle
+          3.1365484905459389
+          >>> g.buffer(1.0, 128).area   # 128-gon approximation
+          3.1415138011443009
+          >>> g.buffer(1.0, 3).area     # triangle approximation
+          3.0
+        """
+        if quadsegs is not None:
+            warnings.warn(
+                "The `quadsegs` argument is deprecated. Use `resolution`.", 
+                DeprecationWarning)
+            res = quadsegs
+        else:
+            res = resolution
+        return geom_factory(self.impl['buffer'](self, distance, res))
+
+    def simplify(self, tolerance, preserve_topology=True):
+        """Returns a simplified geometry produced by the Douglas-Puecker 
+        algorithm
+
+        Coordinates of the simplified geometry will be no more than the
+        tolerance distance from the original. Unless the topology preserving
+        option is used, the algorithm may produce self-intersecting or
+        otherwise invalid geometries.
+        """
+        if preserve_topology:
+            op = self.impl['topology_preserve_simplify']
+        else:
+            op = self.impl['simplify']
+        return geom_factory(op(self, tolerance))
+
+    # Binary operations
+    # -----------------
+
+    def difference(self, other):
+        """Returns the difference of the geometries"""
+        return geom_factory(self.impl['difference'](self, other))
+    
+    def intersection(self, other):
+        """Returns the intersection of the geometries"""
+        return geom_factory(self.impl['intersection'](self, other))
+
+    def symmetric_difference(self, other):
+        """Returns the symmetric difference of the geometries 
+        (Shapely geometry)"""
+        return geom_factory(self.impl['symmetric_difference'](self, other))
+
+    def union(self, other):
+        """Returns the union of the geometries (Shapely geometry)"""
+        return geom_factory(self.impl['union'](self, other))
 
     # Unary predicates
-    #
-    # These use descriptors to reduce the amount of boilerplate.
+    # ----------------
+
+    @property
+    def has_z(self):
+        """True if the geometry's coordinate sequence(s) have z values (are
+        3-dimensional)"""
+        return bool(self.impl['has_z'](self))
 
-    is_empty = UnaryPredicate(lgeos.GEOSisEmpty)
-    is_valid = UnaryPredicate(lgeos.GEOSisValid)
-    is_simple = UnaryPredicate(lgeos.GEOSisSimple)
-    is_ring = UnaryPredicate(lgeos.GEOSisRing)
-    has_z = UnaryPredicate(lgeos.GEOSHasZ)
+    @property
+    def is_empty(self):
+        """True if the set of points in this geometry is empty, else False"""
+        return bool(self.impl['is_empty'](self)) or (self._geom is None)
 
     @property
-    @exceptNull
-    def bounds(self):
-        env = self.envelope
-        if env.geom_type != 'Polygon':
-            raise ValueError, env.wkt
-        cs = lgeos.GEOSGeom_getCoordSeq(env.exterior._geom)
-        cs_len = c_int(0)
-        lgeos.GEOSCoordSeq_getSize(cs, byref(cs_len))
+    def is_ring(self):
+        """True if the geometry is a closed ring, else False"""
+        return bool(self.impl['is_ring'](self))
+
+    @property
+    def is_simple(self):
+        """True if the geometry is simple, meaning that any self-intersections 
+        are only at boundary points, else False"""
+        return bool(self.impl['is_simple'](self))
+
+    @property
+    def is_valid(self):
+        """True if the geometry is valid (definition depends on sub-class), 
+        else False"""
+        return bool(self.impl['is_valid'](self))
+
+    # Binary predicates
+    # -----------------
+
+    def relate(self, other):
+        """Returns the DE-9IM intersection matrix for the two geometries 
+        (string)"""
+        return self.impl['relate'](self, other)
+
+    def contains(self, other):
+        """Returns True if the geometry contains the other, else False"""
+        return bool(self.impl['contains'](self, other))
+
+    def crosses(self, other):
+        """Returns True if the geometries cross, else False"""
+        return bool(self.impl['crosses'](self, other))
+
+    def disjoint(self, other):
+        """Returns True if geometries are disjoint, else False"""
+        return bool(self.impl['disjoint'](self, other))
+
+    def equals(self, other):
+        """Returns True if geometries are equal, else False"""
+        return bool(self.impl['equals'](self, other))
+
+    def intersects(self, other):
+        """Returns True if geometries intersect, else False"""
+        return bool(self.impl['intersects'](self, other))
+
+    def overlaps(self, other):
+        """Returns True if geometries overlap, else False"""
+        return bool(self.impl['overlaps'](self, other))
+
+    def touches(self, other):
+        """Returns True if geometries touch, else False"""
+        return bool(self.impl['touches'](self, other))
+
+    def within(self, other):
+        """Returns True if geometry is within the other, else False"""
+        return bool(self.impl['within'](self, other))
+
+    def equals_exact(self, other, tolerance):
+        """Returns True if geometries are equal to within a specified 
+        tolerance"""
+        # return BinaryPredicateOp('equals_exact', self)(other, tolerance)
+        return bool(self.impl['equals_exact'](self, other, tolerance))
+
+    def almost_equals(self, other, decimal=6):
+        """Returns True if geometries are equal at all coordinates to a 
+        specified decimal place"""
+        return self.equals_exact(other, 0.5 * 10**(-decimal))
+
+    # Linear referencing
+    # ------------------
+
+    def project(self, other, normalized=False):
+        """Returns the distance along this geometry to a point nearest the 
+        specified point
         
-        minx = 1.e+20
-        maxx = -1e+20
-        miny = 1.e+20
-        maxy = -1e+20
-        temp = c_double()
-        for i in xrange(cs_len.value):
-            lgeos.GEOSCoordSeq_getX(cs, i, byref(temp))
-            x = temp.value
-            if x < minx: minx = x
-            if x > maxx: maxx = x
-            lgeos.GEOSCoordSeq_getY(cs, i, byref(temp))
-            y = temp.value
-            if y < miny: miny = y
-            if y > maxy: maxy = y
+        If the normalized arg is True, return the distance normalized to the
+        length of the linear geometry.
+        """ 
+        if normalized:
+            op = self.impl['project_normalized']
+        else:
+            op = self.impl['project']
+        return op(self, other)
+
+    def interpolate(self, distance, normalized=False):
+        """Return a point at the specified distance along a linear geometry
         
-        return (minx, miny, maxx, maxy)
+        If the normalized arg is True, the distance will be interpreted as a
+        fraction of the geometry's length.
+        """
+        if normalized:
+            op = self.impl['interpolate_normalized']
+        else:
+            op = self.impl['interpolate']
+        return geom_factory(op(self, distance))
+
 
+class BaseMultipartGeometry(BaseGeometry):
 
-class BaseMultiPartGeometry(BaseGeometry):
+    def shape_factory(self, *args):
+        # Factory for part instances, usually a geometry class
+        raise NotImplementedError("To be implemented by derived classes")
 
     @property
     def ctypes(self):
-        raise NotImplementedError, \
-        "Multi-part geometries have no ctypes representations"
+        raise NotImplementedError(
+        "Multi-part geometries have no ctypes representations")
 
     @property
     def __array_interface__(self):
         """Provide the Numpy array protocol."""
-        raise NotImplementedError, \
-        "Multi-part geometries do not themselves provide the array interface"
+        raise NotImplementedError(
+        "Multi-part geometries do not themselves provide the array interface")
 
     def _get_coords(self):
-        raise NotImplementedError, \
-        "Sub-geometries may have coordinate sequences, but collections do not"
+        raise NotImplementedError(
+        "Sub-geometries may have coordinate sequences, but collections do not")
 
     def _set_coords(self, ob):
-        raise NotImplementedError, \
-        "Sub-geometries may have coordinate sequences, but collections do not"
+        raise NotImplementedError(
+        "Sub-geometries may have coordinate sequences, but collections do not")
 
     @property
     def coords(self):
-        raise NotImplementedError, \
-        "Multi-part geometries do not provide a coordinate sequence"
+        raise NotImplementedError(
+        "Multi-part geometries do not provide a coordinate sequence")
+
+    @property
+    @exceptNull
+    def geoms(self):
+        return GeometrySequence(self, self.shape_factory)
+
+    def __iter__(self):
+        if not self.is_empty:
+            return iter(self.geoms)
+        else:
+            return iter([])
+
+    def __len__(self):
+        if not self.is_empty:
+            return len(self.geoms)
+        else:
+            return 0
+
+    def __getitem__(self, index):
+        if not self.is_empty:
+            return self.geoms[index]
+        else:
+            return ()[index]
+
+
+class GeometrySequence(object):
+    """
+    Iterative access to members of a homogeneous multipart geometry.
+    """
+
+    # Attributes
+    # ----------
+    # _factory : callable
+    #     Returns instances of Shapely geometries
+    # _geom : c_void_p
+    #     Ctypes pointer to the parent's GEOS geometry
+    # _ndim : int
+    #     Number of dimensions (2 or 3, generally)
+    # __p__ : object
+    #     Parent (Shapely) geometry
+    shape_factory = None
+    _geom = None
+    __p__ = None
+    _ndim = None
+
+    def __init__(self, parent, type):
+        self.shape_factory = type
+        self.__p__ = parent
+
+    def _update(self):
+        self._geom = self.__p__._geom
+        self._ndim = self.__p__._ndim
+        
+    def _get_geom_item(self, i):
+        g = self.shape_factory()
+        g._owned = True
+        g._geom = lgeos.GEOSGetGeometryN(self._geom, i)
+        g._ndim = self._ndim
+        g.__p__ = self
+        return g
+
+    def __iter__(self):
+        self._update()
+        for i in range(self.__len__()):
+            yield self._get_geom_item(i)
+
+    def __len__(self):
+        self._update()
+        return lgeos.GEOSGetNumGeometries(self._geom)
+
+    def __getitem__(self, i):
+        self._update()
+        M = self.__len__()
+        if i + M < 0 or i >= M:
+            raise IndexError("index out of range")
+        if i < 0:
+            ii = M + i
+        else:
+            ii = i
+        return self._get_geom_item(i)
+
+    @property
+    def _longest(self):
+        max = 0
+        for g in iter(self):
+            l = len(g.coords)
+            if l > max:
+                max = l
+
+
+class HeterogeneousGeometrySequence(GeometrySequence):
+    """
+    Iterative access to a heterogeneous sequence of geometries.
+    """
+
+    def __init__(self, parent):
+        super(HeterogeneousGeometrySequence, self).__init__(parent, None)
+
+    def _get_geom_item(self, i):
+        sub = lgeos.GEOSGetGeometryN(self._geom, i)
+        g = geom_factory(sub)
+        g._owned = True
+        return g
+
+
+# Test runner
+def _test():
+    import doctest
+    doctest.testmod()
+
+if __name__ == "__main__":
+    _test()
diff --git a/shapely/geometry/collection.py b/shapely/geometry/collection.py
index fd66f5c..4afd80d 100644
--- a/shapely/geometry/collection.py
+++ b/shapely/geometry/collection.py
@@ -1,18 +1,22 @@
-"""
-Geometry collections.
+"""Multi-part collections of geometries
 """
 
-from shapely.geometry.base import BaseMultiPartGeometry
+from shapely.geometry.base import BaseMultipartGeometry
 from shapely.geometry.base import HeterogeneousGeometrySequence, exceptNull
 
 
-class GeometryCollection(BaseMultiPartGeometry):
+class GeometryCollection(BaseMultipartGeometry):
+
+    """A heterogenous collection of geometries
 
-    """A geometry collection.
+    Attributes
+    ----------
+    geoms : sequence
+        A sequence of Shapely geometry instances
     """
 
     def __init__(self):
-        BaseMultiPartGeometry.__init__(self)
+        BaseMultipartGeometry.__init__(self)
 
     @property
     def __geo_interface__(self):
diff --git a/shapely/geometry/geo.py b/shapely/geometry/geo.py
index 349ed4a..5c78d0c 100644
--- a/shapely/geometry/geo.py
+++ b/shapely/geometry/geo.py
@@ -1,5 +1,5 @@
 """
-Geometry factories based on the geo interface.
+Geometry factories based on the geo interface
 """
 
 from point import Point, asPoint
@@ -32,7 +32,7 @@ def shape(context):
     elif geom_type == "multipolygon":
         return MultiPolygon(ob["coordinates"], context_type='geojson')
     else:
-        raise ValueError, "Unknown geometry type: %s" % geom_type
+        raise ValueError("Unknown geometry type: %s" % geom_type)
 
 def asShape(context):
     """Adapts the context to a geometry interface. The coordinates remain
@@ -46,7 +46,7 @@ def asShape(context):
     try:
         geom_type = ob.get("type").lower()
     except AttributeError:
-        raise ValueError, "Context does not provide geo interface"
+        raise ValueError("Context does not provide geo interface")
 
     if geom_type == "point":
         return asPoint(ob["coordinates"])
@@ -61,4 +61,4 @@ def asShape(context):
     elif geom_type == "multipolygon":
         return MultiPolygonAdapter(ob["coordinates"], context_type='geojson')
     else:
-        raise ValueError, "Unknown geometry type: %s" % geom_type
+        raise ValueError("Unknown geometry type: %s" % geom_type)
diff --git a/shapely/geometry/linestring.py b/shapely/geometry/linestring.py
index 81820cb..44faf6c 100644
--- a/shapely/geometry/linestring.py
+++ b/shapely/geometry/linestring.py
@@ -1,14 +1,125 @@
-"""
-Line strings.
+"""Line strings and related utilities
 """
 
-from ctypes import byref, c_double, c_int, cast, POINTER, pointer
+from ctypes import c_double, cast, POINTER
 from ctypes import ArgumentError
 
 from shapely.geos import lgeos
-from shapely.geometry.base import BaseGeometry, exceptNull
+from shapely.geometry.base import BaseGeometry
 from shapely.geometry.proxy import CachingGeometryProxy
 
+__all__ = ['LineString', 'asLineString']
+
+
+class LineString(BaseGeometry):
+    """
+    A one-dimensional figure comprising one or more line segments
+    
+    A LineString has non-zero length and zero area. It may approximate a curve
+    and need not be straight. Unlike a LinearRing, a LineString is not closed.
+    """
+
+    def __init__(self, coordinates=None):
+        """
+        Parameters
+        ----------
+        coordinates : sequence
+            A sequence of (x, y [,z]) numeric coordinate pairs or triples or
+            an object that provides the numpy array interface, including another
+            instance of LineString.
+
+        Example
+        -------
+        Create a line with two segments
+
+          >>> a = LineString([[0, 0], [1, 0], [1, 1]])
+          >>> a.length
+          2.0
+        """
+        BaseGeometry.__init__(self)
+        if coordinates is not None:
+            self._set_coords(coordinates)
+
+    @property
+    def __geo_interface__(self):
+        return {
+            'type': 'LineString',
+            'coordinates': tuple(self.coords)
+            }
+
+    @property
+    def ctypes(self):
+        if not self._ctypes_data:
+            self._ctypes_data = self.coords.ctypes
+        return self._ctypes_data
+
+    def array_interface(self):
+        """Provide the Numpy array protocol."""
+        return self.coords.array_interface()
+    
+    __array_interface__ = property(array_interface)
+
+    # Coordinate access
+    def _set_coords(self, coordinates):
+        self.empty()
+        self._geom, self._ndim = geos_linestring_from_py(coordinates)
+
+    coords = property(BaseGeometry._get_coords, _set_coords)
+
+    @property
+    def xy(self):
+        """Separate arrays of X and Y coordinate values
+        
+        Example:
+        
+          >>> x, y = LineString(((0, 0), (1, 1))).xy
+          >>> list(x)
+          [0.0, 1.0]
+          >>> list(y)
+          [0.0, 1.0]
+        """
+        return self.coords.xy
+        
+
+class LineStringAdapter(CachingGeometryProxy, LineString):
+
+    def __init__(self, context):
+        self.context = context
+        self.factory = geos_linestring_from_py
+
+    @property
+    def _ndim(self):
+        try:
+            # From array protocol
+            array = self.context.__array_interface__
+            n = array['shape'][1]
+            assert n == 2 or n == 3
+            return n
+        except AttributeError:
+            # Fall back on list
+            return len(self.context[0])
+
+    @property
+    def __array_interface__(self):
+        """Provide the Numpy array protocol."""
+        try:
+            return self.context.__array_interface__
+        except AttributeError:
+            return self.array_interface()
+
+    _get_coords = BaseGeometry._get_coords
+
+    def _set_coords(self, ob):
+        raise NotImplementedError(
+            "Adapters can not modify their coordinate sources")
+
+    coords = property(_get_coords)
+
+
+def asLineString(context):
+    """Adapt an object the LineString interface"""
+    return LineStringAdapter(context)
+
 
 def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
     try:
@@ -17,11 +128,13 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
         assert len(array['shape']) == 2
         m = array['shape'][0]
         if m < 2:
-            raise ValueError, "LineStrings must have at least 2 coordinate tuples"
+            raise ValueError(
+                "LineStrings must have at least 2 coordinate tuples")
         try:
             n = array['shape'][1]
         except IndexError:
-            raise ValueError, "Input %s is the wrong shape for a LineString" % str(ob)
+            raise ValueError(
+                "Input %s is the wrong shape for a LineString" % str(ob))
         assert n == 2 or n == 3
 
         # Make pointer to the coordinate array
@@ -34,9 +147,9 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
         if update_geom is not None:
             cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
             if n != update_ndim:
-                raise ValueError, \
+                raise ValueError(
                 "Wrong coordinate dimensions; this geometry has dimensions: %d" \
-                % update_ndim
+                % update_ndim)
         else:
             cs = lgeos.GEOSCoordSeq_create(m, n)
 
@@ -49,7 +162,7 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
                 try:
                     dz = c_double(cp[n*i+2])
                 except IndexError:
-                    raise ValueError, "Inconsistent coordinate dimensionality"
+                    raise ValueError("Inconsistent coordinate dimensionality")
 
             # Because of a bug in the GEOS C API, 
             # always set X before Y
@@ -62,20 +175,22 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
         # Fall back on list
         m = len(ob)
         if m < 2:
-            raise ValueError, "LineStrings must have at least 2 coordinate tuples"
+            raise ValueError(
+                "LineStrings must have at least 2 coordinate tuples")
         try:
             n = len(ob[0])
         except TypeError:
-            raise ValueError, "Input %s is the wrong shape for a LineString" % str(ob)
+            raise ValueError(
+                "Input %s is the wrong shape for a LineString" % str(ob))
         assert n == 2 or n == 3
 
         # Create a coordinate sequence
         if update_geom is not None:
             cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
             if n != update_ndim:
-                raise ValueError, \
+                raise ValueError(
                 "Wrong coordinate dimensions; this geometry has dimensions: %d" \
-                % update_ndim
+                % update_ndim)
         else:
             cs = lgeos.GEOSCoordSeq_create(m, n)
         
@@ -89,7 +204,7 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
                 try:
                     dz = c_double(coords[2])
                 except IndexError:
-                    raise ValueError, "Inconsistent coordinate dimensionality"
+                    raise ValueError("Inconsistent coordinate dimensionality")
         
             # Because of a bug in the GEOS C API, 
             # always set X before Y
@@ -106,124 +221,10 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0):
 def update_linestring_from_py(geom, ob):
     geos_linestring_from_py(ob, geom._geom, geom._ndim)
 
-
-class LineString(BaseGeometry):
-
-    """A line string, also known as a polyline.
-    
-    """
-
-    def __init__(self, coordinates=None):
-        """Initialize.
-
-        Parameters
-        ----------
-        
-        coordinates : sequence or array
-            This may be an object that satisfies the numpy array protocol,
-            providing an M x 2 or M x 3 (with z) array, or it may be a sequence
-            of x, y (,z) coordinate sequences.
-
-        Example
-        -------
-
-        >>> line = LineString([[0.0, 0.0], [1.0, 2.0]])
-        >>> line = LineString(array([[0.0, 0.0], [1.0, 2.0]]))
-        
-        Each result in a line string from (0.0, 0.0) to (1.0, 2.0).
-        """
-        BaseGeometry.__init__(self)
-        self._init_geom(coordinates)
-
-    def _init_geom(self, coordinates):
-        if coordinates is None:
-            # allow creation of null lines, to support unpickling
-            pass
-        else:
-            self._geom, self._ndim = geos_linestring_from_py(coordinates)
-
-    @property
-    def __geo_interface__(self):
-        return {
-            'type': 'LineString',
-            'coordinates': tuple(self.coords)
-            }
-
-    @property
-    @exceptNull
-    def ctypes(self):
-        if not self._ctypes_data:
-            self._ctypes_data = self.coords.ctypes
-        return self._ctypes_data
-
-    def array_interface(self):
-        """Provide the Numpy array protocol."""
-        return self.coords.array_interface()
-    
-    __array_interface__ = property(array_interface)
-
-    # Coordinate access
-
-    def _set_coords(self, coordinates):
-        if self._geom is None:
-            self._init_geom(coordinates)
-        update_linestring_from_py(self, coordinates)
-
-    coords = property(BaseGeometry._get_coords, _set_coords)
-
-
-class LineStringAdapter(CachingGeometryProxy, LineString):
-
-    """Adapts a Python coordinate pair or a numpy array to the line string
-    interface.
-    """
-    
-    context = None
-    _owned = False
-
-    def __init__(self, context):
-        self.context = context
-        self.factory = geos_linestring_from_py
-
-    @property
-    def _ndim(self):
-        try:
-            # From array protocol
-            array = self.context.__array_interface__
-            n = array['shape'][1]
-            assert n == 2 or n == 3
-            return n
-        except AttributeError:
-            # Fall back on list
-            return len(self.context[0])
-
-    @property
-    def __array_interface__(self):
-        """Provide the Numpy array protocol."""
-        try:
-            return self.context.__array_interface__
-        except AttributeError:
-            return self.array_interface()
-
-    _get_coords = BaseGeometry._get_coords
-
-    def _set_coords(self, ob):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
-
-    coords = property(_get_coords)
-
-
-def asLineString(context):
-    """Factory for PointAdapter instances."""
-    return LineStringAdapter(context)
-
-    
 # Test runner
 def _test():
     import doctest
     doctest.testmod()
 
-
 if __name__ == "__main__":
     _test()
diff --git a/shapely/geometry/multilinestring.py b/shapely/geometry/multilinestring.py
index a831b58..ebf78a4 100644
--- a/shapely/geometry/multilinestring.py
+++ b/shapely/geometry/multilinestring.py
@@ -1,83 +1,53 @@
-"""
-Multi-part collection of linestrings.
+"""Collections of linestrings and related utilities
 """
 
-from ctypes import byref, c_double, c_int, c_void_p, cast, POINTER, pointer
+from ctypes import c_double, c_void_p, cast, POINTER
 
 from shapely.geos import lgeos
-from shapely.geometry.base import BaseGeometry, GeometrySequence, exceptNull
+from shapely.geometry.base import BaseMultipartGeometry
 from shapely.geometry.linestring import LineString, geos_linestring_from_py
 from shapely.geometry.proxy import CachingGeometryProxy
 
-
-def geos_multilinestring_from_py(ob):
-    """ob must be either a sequence or array of sequences or arrays."""
-    try:
-        # From array protocol
-        array = ob.__array_interface__
-        assert len(array['shape']) == 1
-        L = array['shape'][0]
-        assert L >= 1
-
-        # Make pointer to the coordinate array
-        cp = cast(array['data'][0], POINTER(c_double))
-
-        # Array of pointers to sub-geometries
-        subs = (c_void_p * L)()
-
-        for l in xrange(L):
-            geom, ndims = geos_linestring_from_py(array['data'][l])
-            subs[i] = cast(geom, c_void_p)
-        N = lgeos.GEOSGeom_getDimensions(subs[0])
-
-    except AttributeError:
-        # Fall back on list
-        L = len(ob)
-        N = len(ob[0][0])
-        assert L >= 1
-        assert N == 2 or N == 3
-
-        # Array of pointers to point geometries
-        subs = (c_void_p * L)()
-        
-        # add to coordinate sequence
-        for l in xrange(L):
-            geom, ndims = geos_linestring_from_py(ob[l])
-            subs[l] = cast(geom, c_void_p)
-            
-    return (lgeos.GEOSGeom_createCollection(5, subs, L), N)
+__all__ = ['MultiLineString', 'asMultiLineString']
 
 
-class MultiLineString(BaseGeometry):
-
-    """a multiple linestring geometry.
+class MultiLineString(BaseMultipartGeometry):
     """
+    A collection of one or more line strings
+    
+    A MultiLineString has non-zero length and zero area.
 
-    def __init__(self, coordinates=None):
-        """Initialize.
+    Attributes
+    ----------
+    geoms : sequence
+        A sequence of LineStrings
+    """
 
+    def __init__(self, lines=None):
+        """
         Parameters
         ----------
-        
-        coordinates : sequence
-            Contains coordinate sequences or objects that provide the numpy
-            array protocol, providing an M x 2 or M x 3 (with z) array.
+        lines : sequence
+            A sequence of line-like coordinate sequences or objects that
+            provide the numpy array interface, including instances of
+            LineString.
 
         Example
         -------
+        Construct a collection containing one line string.
 
-        >>> geom = MultiLineString( [[[0.0, 0.0], [1.0, 2.0]]] )
-        >>> geom = MultiLineString( [ array([[0.0, 0.0], [1.0, 2.0]]) ] )
-        
-        Each result in a collection containing one line string.
+          >>> lines = MultiLineString( [[[0.0, 0.0], [1.0, 2.0]]] )
         """
-        BaseGeometry.__init__(self)
+        super(MultiLineString, self).__init__()
 
-        if coordinates is None:
+        if lines is None:
             # allow creation of null lines, to support unpickling
             pass
         else:
-            self._geom, self._ndim = geos_multilinestring_from_py(coordinates)
+            self._geom, self._ndim = geos_multilinestring_from_py(lines)
+
+    def shape_factory(self, *args):
+        return LineString(*args)
 
     @property
     def __geo_interface__(self):
@@ -86,41 +56,8 @@ class MultiLineString(BaseGeometry):
             'coordinates': tuple(tuple(c for c in g.coords) for g in self.geoms)
             }
 
-    @property
-    def ctypes(self):
-        raise NotImplementedError, \
-        "Multi-part geometries have no ctypes representations"
-
-    @property
-    def __array_interface__(self):
-        """Provide the Numpy array protocol."""
-        raise NotImplementedError, \
-        "Multi-part geometries do not themselves provide the array interface"
-
-    def _get_coords(self):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
-
-    def _set_coords(self, ob):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
-
-    @property
-    def coords(self):
-        raise NotImplementedError, \
-        "Multi-part geometries do not provide a coordinate sequence"
-
-    @property
-    @exceptNull
-    def geoms(self):
-        return GeometrySequence(self, LineString)
-
 
 class MultiLineStringAdapter(CachingGeometryProxy, MultiLineString):
-
-    """Adapts sequences of sequences or numpy arrays to the multilinestring
-    interface.
-    """
     
     context = None
     _owned = False
@@ -143,15 +80,54 @@ class MultiLineStringAdapter(CachingGeometryProxy, MultiLineString):
 
 
 def asMultiLineString(context):
-    """Factory for MultiLineStringAdapter instances."""
+    """Adapts a sequence of objects to the MultiLineString interface"""
     return MultiLineStringAdapter(context)
 
 
+def geos_multilinestring_from_py(ob):
+    # ob must be either a sequence or array of sequences or arrays
+    try:
+        # From array protocol
+        array = ob.__array_interface__
+        assert len(array['shape']) == 1
+        L = array['shape'][0]
+        assert L >= 1
+
+        # Make pointer to the coordinate array
+        cp = cast(array['data'][0], POINTER(c_double))
+
+        # Array of pointers to sub-geometries
+        subs = (c_void_p * L)()
+
+        for l in xrange(L):
+            geom, ndims = geos_linestring_from_py(array['data'][l])
+            subs[i] = cast(geom, c_void_p)
+        N = lgeos.GEOSGeom_getDimensions(subs[0])
+    except (NotImplementedError, AttributeError):
+        obs = getattr(ob, 'geoms', ob)
+        L = len(obs)
+        exemplar = obs[0]
+        try:
+            N = len(exemplar[0])
+        except TypeError:
+            N = exemplar._ndim
+        assert L >= 1
+        assert N == 2 or N == 3
+
+        # Array of pointers to point geometries
+        subs = (c_void_p * L)()
+        
+        # add to coordinate sequence
+        for l in xrange(L):
+            geom, ndims = geos_linestring_from_py(obs[l])
+            subs[l] = cast(geom, c_void_p)
+            
+    return (lgeos.GEOSGeom_createCollection(5, subs, L), N)
+
 # Test runner
 def _test():
     import doctest
     doctest.testmod()
 
-
 if __name__ == "__main__":
     _test()
diff --git a/shapely/geometry/multipoint.py b/shapely/geometry/multipoint.py
index 9dbb887..6a36a92 100644
--- a/shapely/geometry/multipoint.py
+++ b/shapely/geometry/multipoint.py
@@ -1,88 +1,60 @@
-"""
-Multiple points.
+"""Collections of points and related utilities
 """
 
-from ctypes import byref, c_double, c_int, c_void_p, cast, POINTER, pointer
+from ctypes import byref, c_double, c_void_p, cast, POINTER
+from ctypes import ArgumentError
 
 from shapely.geos import lgeos
-from shapely.geometry.base import BaseGeometry, GeometrySequence, exceptNull
+from shapely.geometry.base import BaseMultipartGeometry, exceptNull
 from shapely.geometry.point import Point, geos_point_from_py
 from shapely.geometry.proxy import CachingGeometryProxy
 
+__all__ = ['MultiPoint', 'asMultiPoint']
 
-def geos_multipoint_from_py(ob):
-    try:
-        # From array protocol
-        array = ob.__array_interface__
-        assert len(array['shape']) == 2
-        m = array['shape'][0]
-        n = array['shape'][1]
-        assert m >= 1
-        assert n == 2 or n == 3
-
-        # Make pointer to the coordinate array
-        cp = cast(array['data'][0], POINTER(c_double))
-
-        # Array of pointers to sub-geometries
-        subs = (c_void_p * m)()
-
-        for i in xrange(m):
-            geom, ndims = geos_point_from_py(cp[n*i:n*i+2])
-            subs[i] = cast(geom, c_void_p)
-
-    except AttributeError:
-        # Fall back on list
-        m = len(ob)
-        n = len(ob[0])
-        assert n == 2 or n == 3
 
-        # Array of pointers to point geometries
-        subs = (c_void_p * m)()
-        
-        # add to coordinate sequence
-        for i in xrange(m):
-            coords = ob[i]
-            geom, ndims = geos_point_from_py(coords)
-            subs[i] = cast(geom, c_void_p)
-            
-    return lgeos.GEOSGeom_createCollection(4, subs, m), n
+class MultiPoint(BaseMultipartGeometry):
 
+    """A collection of one or more points
 
-class MultiPoint(BaseGeometry):
+    A MultiPoint has zero area and zero length.
 
-    """A multiple point geometry.
+    Attributes
+    ----------
+    geoms : sequence
+        A sequence of Points
     """
 
-    def __init__(self, coordinates=None):
-        """Initialize.
-
+    def __init__(self, points=None):
+        """
         Parameters
         ----------
-        
-        coordinates : sequence or array
-            This may be an object that satisfies the numpy array protocol,
-            providing an M x 2 or M x 3 (with z) array, or it may be a sequence
-            of x, y (,z) coordinate sequences.
+        points : sequence
+            A sequence of (x, y [,z]) numeric coordinate pairs or triples or a
+            sequence of objects that implement the numpy array interface,
+            including instaces of Point.
 
         Example
         -------
+        Construct a 2 point collection
 
-        >>> geom = MultiPoint([[0.0, 0.0], [1.0, 2.0]])
-        >>> geom = MultiPoint(array([[0.0, 0.0], [1.0, 2.0]]))
-        
-        Each result in a line string from (0.0, 0.0) to (1.0, 2.0).
+          >>> ob = MultiPoint([[0.0, 0.0], [1.0, 2.0]])
+          >>> len(ob.geoms)
+          2
+          >>> type(ob.geoms[0]) == Point
+          True
         """
-        BaseGeometry.__init__(self)
+        super(MultiPoint, self).__init__()
 
-        if coordinates is None:
-            # allow creation of null lines, to support unpickling
+        if points is None:
+            # allow creation of empty multipoints, to support unpickling
             pass
         else:
-            self._geom, self._ndim = geos_multipoint_from_py(coordinates)
+            self._geom, self._ndim = geos_multipoint_from_py(points)
 
+    def shape_factory(self, *args):
+        return Point(*args)
 
     @property
-    @exceptNull
     def __geo_interface__(self):
         return {
             'type': 'MultiPoint',
@@ -119,31 +91,9 @@ class MultiPoint(BaseGeometry):
         return ai
     __array_interface__ = property(array_interface)
 
-    def _get_coords(self):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
-
-    def _set_coords(self, ob):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
-
-    @property
-    def coords(self):
-        raise NotImplementedError, \
-        "Multipart geometries do not themselves provide coordinate sequences"
-
-    @property
-    @exceptNull
-    def geoms(self):
-        return GeometrySequence(self, Point)
-        
 
 class MultiPointAdapter(CachingGeometryProxy, MultiPoint):
 
-    """Adapts a Python coordinate pair or a numpy array to the multipoint
-    interface.
-    """
-    
     context = None
     _owned = False
 
@@ -173,10 +123,53 @@ class MultiPointAdapter(CachingGeometryProxy, MultiPoint):
 
 
 def asMultiPoint(context):
-    """Factory for MultiPointAdapter instances."""
+    """Adapt a sequence of objects to the MultiPoint interface"""
     return MultiPointAdapter(context)
 
 
+def geos_multipoint_from_py(ob):
+    try:
+        # From array protocol
+        array = ob.__array_interface__
+        assert len(array['shape']) == 2
+        m = array['shape'][0]
+        n = array['shape'][1]
+        assert m >= 1
+        assert n == 2 or n == 3
+
+        # Make pointer to the coordinate array
+        try:
+            cp = cast(array['data'][0], POINTER(c_double))
+        except ArgumentError:
+            cp = array['data']
+
+        # Array of pointers to sub-geometries
+        subs = (c_void_p * m)()
+
+        for i in xrange(m):
+            geom, ndims = geos_point_from_py(cp[n*i:n*i+2])
+            subs[i] = cast(geom, c_void_p)
+
+    except AttributeError:
+        # Fall back on list
+        m = len(ob)
+        try:
+            n = len(ob[0])
+        except TypeError:
+            n = ob[0]._ndim
+        assert n == 2 or n == 3
+
+        # Array of pointers to point geometries
+        subs = (c_void_p * m)()
+        
+        # add to coordinate sequence
+        for i in xrange(m):
+            coords = ob[i]
+            geom, ndims = geos_point_from_py(coords)
+            subs[i] = cast(geom, c_void_p)
+            
+    return lgeos.GEOSGeom_createCollection(4, subs, m), n
+
 # Test runner
 def _test():
     import doctest
diff --git a/shapely/geometry/multipolygon.py b/shapely/geometry/multipolygon.py
index dd78aed..6625d68 100644
--- a/shapely/geometry/multipolygon.py
+++ b/shapely/geometry/multipolygon.py
@@ -1,71 +1,54 @@
-"""
-Multi-part collection of polygons.
+"""Collections of polygons and related utilities
 """
 
-from ctypes import byref, c_double, c_int, c_void_p, cast, POINTER, pointer
+from ctypes import c_void_p, cast
 
 from shapely.geos import lgeos
-from shapely.geometry.base import BaseGeometry, GeometrySequence, exceptNull
+from shapely.geometry.base import BaseMultipartGeometry
 from shapely.geometry.polygon import Polygon, geos_polygon_from_py
 from shapely.geometry.proxy import CachingGeometryProxy
 
-
-def geos_multipolygon_from_py(ob):
-    """ob must provide Python geo interface coordinates."""
-    L = len(ob)
-    N = len(ob[0][0][0])
-    assert L >= 1
-    assert N == 2 or N == 3
-
-    subs = (c_void_p * L)()
-    for l in xrange(L):
-        geom, ndims = 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."""
-    L = len(ob)
-    N = len(ob[0][0][0])
-    assert L >= 1
-    assert N == 2 or N == 3
-
-    subs = (c_void_p * L)()
-    for l in xrange(L):
-        geom, ndims = 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)
-
+__all__ = ['MultiPolygon', 'asMultiPolygon']
 
 
-class MultiPolygon(BaseGeometry):
+class MultiPolygon(BaseMultipartGeometry):
 
-    """a multiple polygon geometry.
+    """A collection of one or more polygons
+    
+    If component polygons overlap the collection is `invalid` and some
+    operations on it may fail.
+    
+    Attributes
+    ----------
+    geoms : sequence
+        A sequence of `Polygon` instances
     """
 
     def __init__(self, polygons=None, context_type='polygons'):
-        """Initialize.
-
+        """
         Parameters
         ----------
-        
         polygons : sequence
             A sequence of (shell, holes) tuples where shell is the sequence
             representation of a linear ring (see linearring.py) and holes is
-            a sequence of such linear rings.
+            a sequence of such linear rings
 
         Example
         -------
-        >>> geom = MultiPolygon( [
-        ...     (
-        ...     ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)), 
-        ...     [((0.1,0.1), (0.1,0.2), (0.2,0.2), (0.2,0.1))]
-        ...     )
-        ... ] )
+        Construct a collection from a sequence of coordinate tuples
+
+          >>> ob = MultiPolygon( [
+          ...     (
+          ...     ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)), 
+          ...     [((0.1,0.1), (0.1,0.2), (0.2,0.2), (0.2,0.1))]
+          ...     )
+          ... ] )
+          >>> len(ob.geoms)
+          1
+          >>> type(ob.geoms[0]) == Polygon
+          True
         """
-        BaseGeometry.__init__(self)
+        super(MultiPolygon, self).__init__()
 
         if polygons is None:
             # allow creation of null collections, to support unpickling
@@ -75,6 +58,9 @@ class MultiPolygon(BaseGeometry):
         elif context_type == 'geojson':
             self._geom, self._ndim = geos_multipolygon_from_py(polygons)
 
+    def shape_factory(self, *args):
+        return Polygon(*args)
+
     @property
     def __geo_interface__(self):
         allcoords = []
@@ -89,41 +75,8 @@ class MultiPolygon(BaseGeometry):
             'coordinates': allcoords
             }
 
-    @property
-    def ctypes(self):
-        raise NotImplementedError, \
-        "Multi-part geometries have no ctypes representations"
-
-    @property
-    def __array_interface__(self):
-        """Provide the Numpy array protocol."""
-        raise NotImplementedError, \
-        "Multi-part geometries do not themselves provide the array interface"
-
-    def _get_coords(self):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
-
-    def _set_coords(self, ob):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
-
-    @property
-    def coords(self):
-        raise NotImplementedError, \
-        "Multi-part geometries do not provide a coordinate sequence"
-
-    @property
-    @exceptNull
-    def geoms(self):
-        return GeometrySequence(self, Polygon)
-
 
 class MultiPolygonAdapter(CachingGeometryProxy, MultiPolygon):
-
-    """Adapts sequences of sequences or numpy arrays to the multipolygon
-    interface.
-    """
     
     context = None
     _owned = False
@@ -149,15 +102,53 @@ class MultiPolygonAdapter(CachingGeometryProxy, MultiPolygon):
 
 
 def asMultiPolygon(context):
-    """Factory for MultiLineStringAdapter instances."""
+    """Adapts a sequence of objects to the MultiPolygon interface"""
     return MultiPolygonAdapter(context)
 
 
+def geos_multipolygon_from_py(ob):
+    """ob must provide Python geo interface coordinates."""
+    L = len(ob)
+    N = len(ob[0][0][0])
+    assert L >= 1
+    assert N == 2 or N == 3
+
+    subs = (c_void_p * L)()
+    for l in xrange(L):
+        geom, ndims = 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."""
+    obs = getattr(ob, 'geoms', None) or ob
+    L = len(obs)
+    exemplar = obs[0]
+    try:
+        N = len(exemplar[0][0])
+    except TypeError:
+        N = exemplar._ndim
+    assert L >= 1
+    assert N == 2 or N == 3
+
+    subs = (c_void_p * L)()
+    for l in xrange(L):
+        shell = getattr(obs[l], 'exterior', None)
+        if shell is None:
+            shell = obs[l][0]
+        holes = getattr(obs[l], 'interiors', None)
+        if holes is None:
+            holes =  obs[l][1]
+        geom, ndims = geos_polygon_from_py(shell, holes)
+        subs[l] = cast(geom, c_void_p)
+            
+    return (lgeos.GEOSGeom_createCollection(6, subs, L), N)
+
 # Test runner
 def _test():
     import doctest
     doctest.testmod()
 
-
 if __name__ == "__main__":
     _test()
diff --git a/shapely/geometry/point.py b/shapely/geometry/point.py
index af00131..9f5e8d4 100644
--- a/shapely/geometry/point.py
+++ b/shapely/geometry/point.py
@@ -1,80 +1,21 @@
-"""
-Points.
+"""Points and related utilities
 """
 
-from ctypes import string_at, create_string_buffer, \
-    c_char_p, c_double, c_float, c_int, c_uint, c_size_t, c_ubyte, \
-    c_void_p, byref
+from ctypes import c_double
 from ctypes import cast, POINTER
 
 from shapely.geos import lgeos, DimensionError
-from shapely.geometry.base import BaseGeometry, CoordinateSequence
-from shapely.geometry.base import exceptNull
+from shapely.geometry.base import BaseGeometry
 from shapely.geometry.proxy import CachingGeometryProxy
 
-
-def geos_point_from_py(ob, update_geom=None, update_ndim=0):
-    """Create a GEOS geom from an object that is a coordinate sequence
-    or that provides the array interface.
-
-    Returns the GEOS geometry and the number of its dimensions.
-    """
-    try:
-        # From array protocol
-        array = ob.__array_interface__
-        assert len(array['shape']) == 1
-        n = array['shape'][0]
-        assert n == 2 or n == 3
-
-        cdata = array['data'][0]
-        cp = cast(cdata, POINTER(c_double))
-        dx = c_double(cp[0])
-        dy = c_double(cp[1])
-        dz = None
-        if n == 3:
-            dz = c_double(cp[2])
-            ndim = 3
-    except AttributeError:
-        # Fall back on the case of Python sequence data
-        # Accept either (x, y) or [(x, y)]
-        if type(ob[0]) == type(tuple()):
-            coords = ob[0]
-        else:
-            coords = ob
-        n = len(coords)
-        dx = c_double(coords[0])
-        dy = c_double(coords[1])
-        dz = None
-        if n == 3:
-            dz = c_double(coords[2])
-
-    if update_geom:
-        cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
-        if n != update_ndim:
-            raise ValueError, \
-            "Wrong coordinate dimensions; this geometry has dimensions: %d" \
-            % update_ndim
-    else:
-        cs = lgeos.GEOSCoordSeq_create(1, n)
-    
-    # Because of a bug in the GEOS C API, always set X before Y
-    lgeos.GEOSCoordSeq_setX(cs, 0, dx)
-    lgeos.GEOSCoordSeq_setY(cs, 0, dy)
-    if n == 3:
-        lgeos.GEOSCoordSeq_setZ(cs, 0, dz)
-   
-    if update_geom:
-        return None
-    else:
-        return lgeos.GEOSGeom_createPoint(cs), n
-
-def update_point_from_py(geom, ob):
-    geos_point_from_py(ob, geom._geom, geom._ndim)
+__all__ = ['Point', 'asPoint']
 
 
 class Point(BaseGeometry):
+    """
+    A zero dimensional feature
 
-    """A point geometry.
+    A point has zero length and zero area. 
     
     Attributes
     ----------
@@ -83,24 +24,20 @@ class Point(BaseGeometry):
 
     Example
     -------
-    >>> p = Point(1.0, -1.0)
-    >>> str(p)
-    'POINT (1.0000000000000000 -1.0000000000000000)'
-    >>> p.y = 0.0
-    >>> p.y
-    0.0
-    >>> p.x
-    1.0
-    >>> p.array
-    [[1.0, 0.0]]
+    
+      >>> p = Point(1.0, -1.0)
+      >>> print p
+      POINT (1.0000000000000000 -1.0000000000000000)
+      >>> p.y
+      -1.0
+      >>> p.x
+      1.0
     """
 
     def __init__(self, *args):
-        """This *copies* the given data to a new GEOS geometry.
-        
+        """
         Parameters
         ----------
-        
         There are 2 cases:
 
         1) 1 parameter: this must satisfy the numpy array protocol.
@@ -108,51 +45,29 @@ class Point(BaseGeometry):
             Easting, northing, and elevation.
         """
         BaseGeometry.__init__(self)
-        self._init_geom(*args)
-
-    def _init_geom(self, *args):
-        if len(args) == 0:
-            # allow creation of null points, to support unpickling
-            pass
-        else:
-            if len(args) == 1:
-                self._geom, self._ndim = geos_point_from_py(args[0])
-            else:
-                self._geom, self._ndim = geos_point_from_py(tuple(args))
+        if len(args) > 0:
+            self._set_coords(*args)
 
     # Coordinate getters and setters
 
     @property
-    @exceptNull
     def x(self):
         """Return x coordinate."""
-        cs = lgeos.GEOSGeom_getCoordSeq(self._geom)
-        d = c_double()
-        lgeos.GEOSCoordSeq_getX(cs, 0, byref(d))
-        return d.value
+        return self.coords[0][0]
     
     @property
-    @exceptNull
     def y(self):
         """Return y coordinate."""
-        cs = lgeos.GEOSGeom_getCoordSeq(self._geom)
-        d = c_double()
-        lgeos.GEOSCoordSeq_getY(cs, 0, byref(d))
-        return d.value
+        return self.coords[0][1]
     
     @property
-    @exceptNull
     def z(self):
         """Return z coordinate."""
         if self._ndim != 3:
-            raise DimensionError, "This point has no z coordinate."
-        cs = lgeos.GEOSGeom_getCoordSeq(self._geom)
-        d = c_double()
-        lgeos.GEOSCoordSeq_getZ(cs, 0, byref(d))
-        return d.value
+            raise DimensionError("This point has no z coordinate.")
+        return self.coords[0][2]
     
     @property
-    @exceptNull
     def __geo_interface__(self):
         return {
             'type': 'Point',
@@ -160,15 +75,15 @@ class Point(BaseGeometry):
             }
 
     @property
-    @exceptNull
     def ctypes(self):
         if not self._ctypes_data:
             array_type = c_double * self._ndim
             array = array_type()
-            array[0] = self.x
-            array[1] = self.y
+            xy = self.coords[0]
+            array[0] = xy[0]
+            array[1] = xy[1]
             if self._ndim == 3:
-                array[2] = self.z
+                array[2] = xy[2]
             self._ctypes_data = array
         return self._ctypes_data
 
@@ -180,30 +95,37 @@ class Point(BaseGeometry):
     __array_interface__ = property(array_interface)
 
     @property
-    @exceptNull
     def bounds(self):
-        cs = lgeos.GEOSGeom_getCoordSeq(self._geom)
-        x = c_double()
-        y = c_double()
-        lgeos.GEOSCoordSeq_getX(cs, 0, byref(x))
-        lgeos.GEOSCoordSeq_getY(cs, 0, byref(y))
-        return (x.value, y.value, x.value, y.value)
+        xy = self.coords[0]
+        return (xy[0], xy[1], xy[0], xy[1])
 
     # Coordinate access
 
-    def _set_coords(self, coordinates):
-        if self._geom is None:
-            self._init_geom(coordinates)
+    def _set_coords(self, *args):
+        self.empty()
+        if len(args) == 1:
+            self._geom, self._ndim = geos_point_from_py(args[0])
         else:
-            update_point_from_py(self, coordinates)
+            self._geom, self._ndim = geos_point_from_py(tuple(args))
 
     coords = property(BaseGeometry._get_coords, _set_coords)
 
+    @property
+    def xy(self):
+        """Separate arrays of X and Y coordinate values
+        
+        Example:
+        
+          >>> x, y = Point(0, 0).xy
+          >>> list(x)
+          [0.0]
+          >>> list(y)
+          [0.0]
+        """
+        return self.coords.xy
 
-class PointAdapter(CachingGeometryProxy, Point):
 
-    """Adapts a Python coordinate pair or a numpy array to the point interface.
-    """
+class PointAdapter(CachingGeometryProxy, Point):
 
     _owned = False
 
@@ -223,8 +145,6 @@ class PointAdapter(CachingGeometryProxy, Point):
             # Fall back on list
             return len(self.context)
 
-    # TODO: reimplement x, y, z properties without calling invoking _geom
-
     @property
     def __array_interface__(self):
         """Provide the Numpy array protocol."""
@@ -236,17 +156,82 @@ class PointAdapter(CachingGeometryProxy, Point):
     _get_coords = BaseGeometry._get_coords
 
     def _set_coords(self, ob):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
+        raise NotImplementedError("Adapters can not modify their sources")
 
     coords = property(_get_coords)
 
 
 def asPoint(context):
-    """Factory for PointAdapter instances."""
+    """Adapt an object to the Point interface"""
     return PointAdapter(context)
 
 
+def geos_point_from_py(ob, update_geom=None, update_ndim=0):
+    """Create a GEOS geom from an object that is a coordinate sequence
+    or that provides the array interface.
+
+    Returns the GEOS geometry and the number of its dimensions.
+    """
+    try:
+        # From array protocol
+        array = ob.__array_interface__
+        assert len(array['shape']) == 1
+        n = array['shape'][0]
+        assert n == 2 or n == 3
+
+        dz = None
+        da = array['data']
+        if type(da) == type((0,)):
+            cdata = da[0]
+            cp = cast(cdata, POINTER(c_double))
+            dx = c_double(cp[0])
+            dy = c_double(cp[1])
+            if n == 3:
+                dz = c_double(cp[2])
+                ndim = 3
+        else:
+            dx, dy = da[0:2]
+            if n == 3:
+                dz = da[2]
+                ndim = 3
+
+    except AttributeError:
+        # Fall back on the case of Python sequence data
+        # Accept either (x, y) or [(x, y)]
+        if type(ob[0]) == type(tuple()):
+            coords = ob[0]
+        else:
+            coords = ob
+        n = len(coords)
+        dx = c_double(coords[0])
+        dy = c_double(coords[1])
+        dz = None
+        if n == 3:
+            dz = c_double(coords[2])
+
+    if update_geom:
+        cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
+        if n != update_ndim:
+            raise ValueError(
+            "Wrong coordinate dimensions; this geometry has dimensions: %d" \
+            % update_ndim)
+    else:
+        cs = lgeos.GEOSCoordSeq_create(1, n)
+    
+    # Because of a bug in the GEOS C API, always set X before Y
+    lgeos.GEOSCoordSeq_setX(cs, 0, dx)
+    lgeos.GEOSCoordSeq_setY(cs, 0, dy)
+    if n == 3:
+        lgeos.GEOSCoordSeq_setZ(cs, 0, dz)
+   
+    if update_geom:
+        return None
+    else:
+        return lgeos.GEOSGeom_createPoint(cs), n
+
+def update_point_from_py(geom, ob):
+    geos_point_from_py(ob, geom._geom, geom._ndim)
+
 # Test runner
 def _test():
     import doctest
diff --git a/shapely/geometry/polygon.py b/shapely/geometry/polygon.py
index 56eadb4..2a74021 100644
--- a/shapely/geometry/polygon.py
+++ b/shapely/geometry/polygon.py
@@ -1,181 +1,48 @@
-"""
-Polygons and their linear ring components.
+"""Polygons and their linear ring components
 """
 
-from ctypes import byref, c_double, c_int, c_void_p, cast, POINTER, pointer
+from ctypes import c_double, c_void_p, cast, POINTER
+from ctypes import ArgumentError
 import weakref
 from shapely.geos import lgeos
 from shapely.geometry.base import BaseGeometry, exceptNull
 from shapely.geometry.linestring import LineString, LineStringAdapter
 from shapely.geometry.proxy import PolygonProxy
 
-
-def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
-    try:
-        # From array protocol
-        array = ob.__array_interface__
-        assert len(array['shape']) == 2
-        m = array['shape'][0]
-        n = array['shape'][1]
-        if m < 3:
-            raise ValueError, "A LinearRing must have at least 3 coordinate tuples"
-        assert n == 2 or n == 3
-
-        # Make pointer to the coordinate array
-        try:
-            cp = cast(array['data'][0], POINTER(c_double))
-        except ArgumentError:
-            cp = array['data']
-
-        # Add closing coordinates to sequence?
-        if cp[0] != cp[m*n-n] or cp[1] != cp[m*n-n+1]:
-            M = m + 1
-        else:
-            M = m
-
-        # Create a coordinate sequence
-        if update_geom is not None:
-            cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
-            if n != update_ndim:
-                raise ValueError, \
-                "Wrong coordinate dimensions; this geometry has dimensions: %d" \
-                % update_ndim
-        else:
-            cs = lgeos.GEOSCoordSeq_create(M, n)
-
-        # add to coordinate sequence
-        for i in xrange(m):
-            dx = c_double(cp[n*i])
-            dy = c_double(cp[n*i+1])
-            dz = None
-            if n == 3:
-                dz = c_double(cp[n*i+2])
-        
-            # Because of a bug in the GEOS C API, 
-            # always set X before Y
-            lgeos.GEOSCoordSeq_setX(cs, i, dx)
-            lgeos.GEOSCoordSeq_setY(cs, i, dy)
-            if n == 3:
-                lgeos.GEOSCoordSeq_setZ(cs, i, dz)
-
-        # Add closing coordinates to sequence?
-        if M > m:
-            dx = c_double(cp[0])
-            dy = c_double(cp[1])
-            dz = None
-            if n == 3:
-                dz = c_double(cp[2])
-        
-            # Because of a bug in the GEOS C API, 
-            # always set X before Y
-            lgeos.GEOSCoordSeq_setX(cs, M-1, dx)
-            lgeos.GEOSCoordSeq_setY(cs, M-1, dy)
-            if n == 3:
-                lgeos.GEOSCoordSeq_setZ(cs, M-1, dz)
-            
-    except AttributeError:
-        # Fall back on list
-        m = len(ob)
-        n = len(ob[0])
-        if m < 3:
-            raise ValueError, "A LinearRing must have at least 3 coordinate tuples"
-        assert (n == 2 or n == 3)
-
-        # Add closing coordinates if not provided
-        if ob[0][0] != ob[-1][0] or ob[0][1] != ob[-1][1]:
-            M = m + 1
-        else:
-            M = m
-
-        # Create a coordinate sequence
-        if update_geom is not None:
-            cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
-            if n != update_ndim:
-                raise ValueError, \
-                "Wrong coordinate dimensions; this geometry has dimensions: %d" \
-                % update_ndim
-        else:
-            cs = lgeos.GEOSCoordSeq_create(M, n)
-        
-        # add to coordinate sequence
-        for i in xrange(m):
-            coords = ob[i]
-            dx = c_double(coords[0])
-            dy = c_double(coords[1])
-            dz = None
-            if n == 3:
-                dz = c_double(coords[2])
-        
-            # Because of a bug in the GEOS C API, 
-            # always set X before Y
-            lgeos.GEOSCoordSeq_setX(cs, i, dx)
-            lgeos.GEOSCoordSeq_setY(cs, i, dy)
-            if n == 3:
-                lgeos.GEOSCoordSeq_setZ(cs, i, dz)
-
-        # Add closing coordinates to sequence?
-        if M > m:
-            coords = ob[0]
-            dx = c_double(coords[0])
-            dy = c_double(coords[1])
-            dz = None
-            if n == 3:
-                dz = c_double(coords[2])
-        
-            # Because of a bug in the GEOS C API, 
-            # always set X before Y
-            lgeos.GEOSCoordSeq_setX(cs, M-1, dx)
-            lgeos.GEOSCoordSeq_setY(cs, M-1, dy)
-            if n == 3:
-                lgeos.GEOSCoordSeq_setZ(cs, M-1, dz)
-
-    if update_geom is not None:
-        return None
-    else:
-        return lgeos.GEOSGeom_createLinearRing(cs), n
-
-def update_linearring_from_py(geom, ob):
-    geos_linearring_from_py(ob, geom._geom, geom._ndim)
+__all__ = ['Polygon', 'asPolygon', 'LinearRing', 'asLinearRing']
 
 
 class LinearRing(LineString):
-
-    """A linear ring.
     """
+    A closed one-dimensional feature comprising one or more line segments
 
-    _ndim = None
-    __geom__ = None
-    __p__ = None
-    _owned = False
-
+    A LinearRing that crosses itself or touches itself at a single point is
+    invalid and operations on it may fail.
+    """
+    
     def __init__(self, coordinates=None):
-        """Initialize.
-
+        """
         Parameters
         ----------
-        coordinates : sequence or array
-            This may be an object that satisfies the numpy array protocol,
-            providing an M x 2 or M x 3 (with z) array, or it may be a sequence
-            of x, y (,z) coordinate sequences.
+        coordinates : sequence
+            A sequence of (x, y [,z]) numeric coordinate pairs or triples
 
         Rings are implicitly closed. There is no need to specific a final
         coordinate pair identical to the first.
 
         Example
         -------
-        >>> ring = LinearRing( ((0.,0.), (0.,1.), (1.,1.), (1.,0.)) )
+        Construct a square ring.
 
-        Produces a 1x1 square.
+          >>> ring = LinearRing( ((0, 0), (0, 1), (1 ,1 ), (1 , 0)) )
+          >>> ring.is_closed
+          True
+          >>> ring.length
+          4.0
         """
         BaseGeometry.__init__(self)
-        self._init_geom(coordinates)
-
-    def _init_geom(self, coordinates):
-        if coordinates is None:
-            # allow creation of null lines, to support unpickling
-            pass
-        else:
-            self._geom, self._ndim = geos_linearring_from_py(coordinates)
+        if coordinates is not None:
+            self._set_coords(coordinates)
 
     @property
     def __geo_interface__(self):
@@ -189,27 +56,19 @@ class LinearRing(LineString):
     _get_coords = BaseGeometry._get_coords
 
     def _set_coords(self, coordinates):
-        if self._geom is None:
-            self._init_geom(coordinates)
-        update_linearring_from_py(self, coordinates)
+        self.empty()
+        self._geom, self._ndim = geos_linearring_from_py(coordinates)
 
     coords = property(_get_coords, _set_coords)
 
 
 class LinearRingAdapter(LineStringAdapter):
 
-    context = None
-    __geom__ = None
     __p__ = None
-    _owned = False
 
-    @property
-    def _geom(self):
-        """Keeps the GEOS geometry in synch with the context."""
-        if self.__geom__ is not None:
-            lgeos.GEOSGeom_destroy(self.__geom)
-        self.__geom, n = geos_linearring_from_py(self.context)
-        return self.__geom
+    def __init__(self, context):
+        self.context = context
+        self.factory = geos_linearring_from_py
 
     @property
     def __geo_interface__(self):
@@ -222,6 +81,7 @@ class LinearRingAdapter(LineStringAdapter):
 
 
 def asLinearRing(context):
+    """Adapt an object to the LinearRing interface"""
     return LinearRingAdapter(context)
 
 
@@ -260,7 +120,7 @@ class InteriorRingSequence(object):
     def __getitem__(self, i):
         M = self.__len__()
         if i + M < 0 or i >= M:
-            raise IndexError, "index out of range"
+            raise IndexError("index out of range")
         if i < 0:
             ii = M + i
         else:
@@ -293,62 +153,44 @@ class InteriorRingSequence(object):
         return self.__rings__[i]()
         
 
-def geos_polygon_from_py(shell, holes=None):
-    if shell is not None:
-        geos_shell, ndim = geos_linearring_from_py(shell)
-        if holes:
-            ob = holes
-            L = len(ob)
-            N = len(ob[0][0])
-            assert L >= 1
-            assert N == 2 or N == 3
-
-            # Array of pointers to ring geometries
-            geos_holes = (c_void_p * L)()
-    
-            # add to coordinate sequence
-            for l in xrange(L):
-                geom, ndim = geos_linearring_from_py(ob[l])
-                geos_holes[l] = cast(geom, c_void_p)
-
-        else:
-            geos_holes = POINTER(c_void_p)()
-            L = 0
-
-        return (
-            lgeos.GEOSGeom_createPolygon(
-                        c_void_p(geos_shell),
-                        geos_holes,
-                        L
-                        ),
-            ndim
-            )
-
 class Polygon(BaseGeometry):
-
-    """A line string, also known as a polyline.
+    """
+    A two-dimensional figure bounded by a linear ring
+
+    A polygon has a non-zero area. It may have one or more negative-space
+    "holes" which are also bounded by linear rings. If any rings cross each
+    other, the feature is invalid and operations on it may fail.
+
+    Attributes
+    ----------
+    exterior : LinearRing
+        The ring which bounds the positive space of the polygon.
+    interiors : sequence
+        A sequence of rings which bound all existing holes.
     """
 
     _exterior = None
     _interiors = []
     _ndim = 2
-    __geom__ = None
-    _owned = False
 
     def __init__(self, shell=None, holes=None):
-        """Initialize.
-
+        """
         Parameters
         ----------
-        exterior : sequence or array
-            This may be an object that satisfies the numpy array protocol,
-            providing an M x 2 or M x 3 (with z) array, or it may be a sequence
-            of x, y (,z) coordinate sequences.
+        shell : sequence
+            A sequence of (x, y [,z]) numeric coordinate pairs or triples
+        holes : sequence
+            A sequence of objects which satisfy the same requirements as the
+            shell parameters above
 
         Example
         -------
-        >>> coords = ((0., 0.), (0., 1.), (1., 1.), (1., 0.), (0., 0.))
-        >>> polygon = Polygon(coords)
+        Create a square polygon with no holes
+
+          >>> coords = ((0., 0.), (0., 1.), (1., 1.), (1., 0.), (0., 0.))
+          >>> polygon = Polygon(coords)
+          >>> polygon.area
+          1.0
         """
         BaseGeometry.__init__(self)
 
@@ -381,21 +223,21 @@ class Polygon(BaseGeometry):
 
     @property
     def __array_interface__(self):
-        raise NotImplementedError, \
-        "A polygon does not itself provide the array interface. Its rings do."
+        raise NotImplementedError(
+        "A polygon does not itself provide the array interface. Its rings do.")
 
     def _get_coords(self):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
+        raise NotImplementedError(
+        "Component rings have coordinate sequences, but the polygon does not")
 
     def _set_coords(self, ob):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
+        raise NotImplementedError(
+        "Component rings have coordinate sequences, but the polygon does not")
 
     @property
     def coords(self):
-        raise NotImplementedError, \
-        "Component rings have coordinate sequences, but the polygon does not"
+        raise NotImplementedError(
+        "Component rings have coordinate sequences, but the polygon does not")
 
     @property
     def __geo_interface__(self):
@@ -409,15 +251,7 @@ class Polygon(BaseGeometry):
 
 
 class PolygonAdapter(PolygonProxy, Polygon):
-
-    """Adapts sequences of sequences or numpy arrays to the polygon
-    interface.
-    """
     
-    context = None
-    __geom__ = None
-    _owned = False
-
     def __init__(self, shell, holes=None):
         self.shell = shell
         self.holes = holes
@@ -438,10 +272,172 @@ class PolygonAdapter(PolygonProxy, Polygon):
 
 
 def asPolygon(shell, holes=None):
-    """Factory for PolygonAdapter instances."""
+    """Adapt objects to the Polygon interface"""
     return PolygonAdapter(shell, holes)
 
 
+def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
+    try:
+        # From array protocol
+        array = ob.__array_interface__
+        assert len(array['shape']) == 2
+        m = array['shape'][0]
+        n = array['shape'][1]
+        if m < 3:
+            raise ValueError(
+                "A LinearRing must have at least 3 coordinate tuples")
+        assert n == 2 or n == 3
+
+        # Make pointer to the coordinate array
+        try:
+            cp = cast(array['data'][0], POINTER(c_double))
+        except ArgumentError:
+            cp = array['data']
+
+        # Add closing coordinates to sequence?
+        if cp[0] != cp[m*n-n] or cp[1] != cp[m*n-n+1]:
+            M = m + 1
+        else:
+            M = m
+
+        # Create a coordinate sequence
+        if update_geom is not None:
+            cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
+            if n != update_ndim:
+                raise ValueError(
+                "Wrong coordinate dimensions; this geometry has dimensions: %d" \
+                % update_ndim)
+        else:
+            cs = lgeos.GEOSCoordSeq_create(M, n)
+
+        # add to coordinate sequence
+        for i in xrange(m):
+            dx = c_double(cp[n*i])
+            dy = c_double(cp[n*i+1])
+            dz = None
+            if n == 3:
+                dz = c_double(cp[n*i+2])
+        
+            # Because of a bug in the GEOS C API, 
+            # always set X before Y
+            lgeos.GEOSCoordSeq_setX(cs, i, dx)
+            lgeos.GEOSCoordSeq_setY(cs, i, dy)
+            if n == 3:
+                lgeos.GEOSCoordSeq_setZ(cs, i, dz)
+
+        # Add closing coordinates to sequence?
+        if M > m:
+            dx = c_double(cp[0])
+            dy = c_double(cp[1])
+            dz = None
+            if n == 3:
+                dz = c_double(cp[2])
+        
+            # Because of a bug in the GEOS C API, 
+            # always set X before Y
+            lgeos.GEOSCoordSeq_setX(cs, M-1, dx)
+            lgeos.GEOSCoordSeq_setY(cs, M-1, dy)
+            if n == 3:
+                lgeos.GEOSCoordSeq_setZ(cs, M-1, dz)
+            
+    except AttributeError:
+        # Fall back on list
+        m = len(ob)
+        n = len(ob[0])
+        if m < 3:
+            raise ValueError(
+                "A LinearRing must have at least 3 coordinate tuples")
+        assert (n == 2 or n == 3)
+
+        # Add closing coordinates if not provided
+        if m == 3 or ob[0][0] != ob[-1][0] or ob[0][1] != ob[-1][1]:
+            M = m + 1
+        else:
+            M = m
+
+        # Create a coordinate sequence
+        if update_geom is not None:
+            cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
+            if n != update_ndim:
+                raise ValueError(
+                "Wrong coordinate dimensions; this geometry has dimensions: %d" \
+                % update_ndim)
+        else:
+            cs = lgeos.GEOSCoordSeq_create(M, n)
+        
+        # add to coordinate sequence
+        for i in xrange(m):
+            coords = ob[i]
+            dx = c_double(coords[0])
+            dy = c_double(coords[1])
+            dz = None
+            if n == 3:
+                dz = c_double(coords[2])
+        
+            # Because of a bug in the GEOS C API, 
+            # always set X before Y
+            lgeos.GEOSCoordSeq_setX(cs, i, dx)
+            lgeos.GEOSCoordSeq_setY(cs, i, dy)
+            if n == 3:
+                lgeos.GEOSCoordSeq_setZ(cs, i, dz)
+
+        # Add closing coordinates to sequence?
+        if M > m:
+            coords = ob[0]
+            dx = c_double(coords[0])
+            dy = c_double(coords[1])
+            dz = None
+            if n == 3:
+                dz = c_double(coords[2])
+        
+            # Because of a bug in the GEOS C API, 
+            # always set X before Y
+            lgeos.GEOSCoordSeq_setX(cs, M-1, dx)
+            lgeos.GEOSCoordSeq_setY(cs, M-1, dy)
+            if n == 3:
+                lgeos.GEOSCoordSeq_setZ(cs, M-1, dz)
+
+    if update_geom is not None:
+        return None
+    else:
+        return lgeos.GEOSGeom_createLinearRing(cs), n
+
+def update_linearring_from_py(geom, ob):
+    geos_linearring_from_py(ob, geom._geom, geom._ndim)
+
+def geos_polygon_from_py(shell, holes=None):
+    if shell is not None:
+        geos_shell, ndim = geos_linearring_from_py(shell)
+        if holes:
+            ob = holes
+            L = len(ob)
+            exemplar = ob[0]
+            try:
+                N = len(exemplar[0])
+            except TypeError:
+                N = exemplar._ndim
+            assert L >= 1
+            assert N == 2 or N == 3
+
+            # Array of pointers to ring geometries
+            geos_holes = (c_void_p * L)()
+    
+            # add to coordinate sequence
+            for l in xrange(L):
+                geom, ndim = geos_linearring_from_py(ob[l])
+                geos_holes[l] = cast(geom, c_void_p)
+        else:
+            geos_holes = POINTER(c_void_p)()
+            L = 0
+        return (
+            lgeos.GEOSGeom_createPolygon(
+                        c_void_p(geos_shell),
+                        geos_holes,
+                        L
+                        ),
+            ndim
+            )
+
 # Test runner
 def _test():
     import doctest
diff --git a/shapely/geometry/proxy.py b/shapely/geometry/proxy.py
index 27367d5..85ceb25 100644
--- a/shapely/geometry/proxy.py
+++ b/shapely/geometry/proxy.py
@@ -1,29 +1,38 @@
-"""
-Proxy for coordinates stored outside Shapely geometries.
+"""Proxy for coordinates stored outside Shapely geometries
 """
 
 from shapely.geos import lgeos
+from shapely import wkb
+
+EMPTY = wkb.deserialize('010700000000000000'.decode('hex'))
 
 
 class CachingGeometryProxy(object):
 
     context = None
     factory = None
-    __geom__ = None
+    __geom__ = EMPTY
     _gtag = None
 
     def __init__(self, context):
         self.context = context
 
     @property
+    def _is_empty(self):
+        return self.__geom__ in [EMPTY, None]
+
+    def empty(self):
+        if not self._is_empty:
+            from shapely.geos import lgeos
+            lgeos.GEOSGeom_destroy(self.__geom__)
+        self.__geom__ = EMPTY
+
+    @property
     def _geom(self):
         """Keeps the GEOS geometry in synch with the context."""
         gtag = self.gtag()
-        if gtag != self._gtag:
-            if self.__geom__ is not None:
-                lgeos.GEOSGeom_destroy(self.__geom__)
-            self.__geom__, n = self.factory(self.context)
-        elif self.__geom__ is None:
+        if gtag != self._gtag or self._is_empty:
+            self.empty()
             self.__geom__, n = self.factory(self.context)
         self._gtag = gtag
         return self.__geom__
@@ -38,11 +47,8 @@ class PolygonProxy(CachingGeometryProxy):
     def _geom(self):
         """Keeps the GEOS geometry in synch with the context."""
         gtag = self.gtag()
-        if gtag != self._gtag:
-            if self.__geom__ is not None:
-                lgeos.GEOSGeom_destroy(self.__geom__)
-            self.__geom__, n = self.factory(self.context[0], self.context[1])
-        elif self.__geom__ is None:
+        if gtag != self._gtag or self._is_empty:
+            self.empty()
             self.__geom__, n = self.factory(self.context[0], self.context[1])
         self._gtag = gtag
         return self.__geom__
diff --git a/shapely/geos.py b/shapely/geos.py
index e3d518b..ef096ce 100644
--- a/shapely/geos.py
+++ b/shapely/geos.py
@@ -1,21 +1,32 @@
 """
-Exports the libgeos_c shared lib, GEOS-specific exceptions, and utilities.
+Proxies for the libgeos_c shared lib, GEOS-specific exceptions, and utilities
 """
 
 import atexit
-from ctypes import cdll, CDLL, PyDLL, CFUNCTYPE, c_char_p, c_void_p
-from ctypes.util import find_library
+import functools
+import logging
 import os
 import sys
+import time
+import threading
+import ctypes
+from ctypes import cdll, CDLL, PyDLL, CFUNCTYPE, c_char_p, c_void_p, string_at
+from ctypes.util import find_library
 
 from ctypes_declarations import prototype
 
+# Begin by creating a do-nothing handler and adding to this module's logger.
+class NullHandler(logging.Handler):
+    def emit(self, record):
+        pass
+LOG = logging.getLogger(__name__)
+LOG.addHandler(NullHandler())
 
 # Find and load the GEOS and C libraries
 # If this ever gets any longer, we'll break it into separate modules
 
 if sys.platform == 'linux2':
-    lgeos = PyDLL(find_library('geos_c'))
+    _lgeos = CDLL(find_library('geos_c'))
     free = CDLL(find_library('c')).free
     free.argtypes = [c_void_p]
     free.restype = None
@@ -36,7 +47,7 @@ elif sys.platform == 'darwin':
                 break
     if lib is None:
         raise ImportError("Could not find geos_c library")
-    lgeos = PyDLL(lib)
+    _lgeos = CDLL(lib)
     free = CDLL(find_library('c')).free
     free.argtypes = [c_void_p]
     free.restype = None
@@ -46,7 +57,7 @@ elif sys.platform == 'win32':
         local_dlls = os.path.abspath(os.__file__ + "../../../DLLs")
         original_path = os.environ['PATH']
         os.environ['PATH'] = "%s;%s" % (local_dlls, original_path)
-        lgeos = PyDLL("geos.dll")
+        _lgeos = CDLL("geos.dll")
     except (ImportError, WindowsError):
         raise
     def free(m):
@@ -60,9 +71,9 @@ elif sys.platform == 'sunos5':
     # Try the major versioned name first, falling back on the unversioned
     # name.
     try:
-        lgeos = PyDLL('libgeos_c.so.1')
+        _lgeos = CDLL('libgeos_c.so.1')
     except (OSError, ImportError):
-        lgeos = PyDLL('libgeos_c.so')
+        _lgeos = CDLL('libgeos_c.so')
     except:
         raise
     free = CDLL('libc.so.1').free
@@ -73,22 +84,56 @@ else: # other *nix systems
     # Try the major versioned name first, falling back on the unversioned
     # name.
     try:
-        lgeos = PyDLL('libgeos_c.so.1')
+        _lgeos = CDLL('libgeos_c.so.1')
     except (OSError, ImportError):
-        lgeos = PyDLL('libgeos_c.so')
+        _lgeos = CDLL('libgeos_c.so')
     except:
         raise
     free = CDLL('libc.so.6').free
     free.argtypes = [c_void_p]
     free.restype = None
 
-# Prototype the libgeos_c functions using new code from `tarley` in
-# http://trac.gispython.org/lab/ticket/189
-prototype(lgeos)
+def _geos_c_version():
+    func = _lgeos.GEOSversion
+    func.restype = c_char_p
+    v = func().split('-')[2]
+    return tuple(int(n) for n in v.split('.'))
 
+geos_capi_version = geos_c_version = _geos_c_version()
 
-class allocated_c_char_p(c_char_p):
-    pass
+# If we have the new interface, then record a baseline so that we know what
+# additional functions are declared in ctypes_declarations.
+if geos_c_version >= (1,5,0):
+    start_set = set(_lgeos.__dict__)
+
+# Apply prototypes for the libgeos_c functions
+prototype(_lgeos, geos_c_version)
+
+# If we have the new interface, automatically detect all function
+# declarations, and declare their re-entrant counterpart.
+if geos_c_version >= (1,5,0):
+    end_set = set(_lgeos.__dict__)
+    new_func_names = end_set - start_set
+    
+    for func_name in new_func_names:
+        new_func_name = "%s_r" % func_name
+        if hasattr(_lgeos, new_func_name):
+            new_func = getattr(_lgeos, new_func_name)
+            old_func = getattr(_lgeos, func_name)
+            new_func.restype = old_func.restype
+            if old_func.argtypes is None:
+                # Handle functions that didn't take an argument before,
+                # finishGEOS.
+                new_func.argtypes = [c_void_p]
+            else:
+                new_func.argtypes = [c_void_p] + old_func.argtypes
+            if old_func.errcheck is not None:
+                new_func.errcheck = old_func.errcheck
+    
+    # Handle special case.
+    _lgeos.initGEOS_r.restype = c_void_p
+    _lgeos.initGEOS_r.argtypes = [c_void_p, c_void_p]
+    _lgeos.finishGEOS_r.argtypes = [c_void_p]
 
 # Exceptions
 
@@ -104,23 +149,224 @@ class TopologicalError(Exception):
 class PredicateError(Exception):
     pass
 
-
-# GEOS error handlers, which currently do nothing.
-
 def error_handler(fmt, list):
-    pass
+    LOG.error("%s", list)
 error_h = CFUNCTYPE(None, c_char_p, c_char_p)(error_handler)
 
 def notice_handler(fmt, list):
-    pass
+    LOG.warning("%s", list)
 notice_h = CFUNCTYPE(None, c_char_p, c_char_p)(notice_handler)
 
-# Register a cleanup function
-
 def cleanup():
-    if lgeos is not None:
-        lgeos.finishGEOS()
+    if _lgeos is not None :
+        _lgeos.finishGEOS()
 
 atexit.register(cleanup)
 
-lgeos.initGEOS(notice_h, error_h)
+# Errcheck functions
+
+def errcheck_wkb(result, func, argtuple):
+    if not result:
+        return None
+    size_ref = argtuple[-1]
+    size = size_ref._obj
+    retval = ctypes.string_at(result, size.value)[:]
+    lgeos.GEOSFree(result)
+    return retval
+
+def errcheck_just_free(result, func, argtuple):
+    retval = string_at(result)
+    lgeos.GEOSFree(result)
+    return retval
+
+def errcheck_predicate(result, func, argtuple):
+    if result == 2:
+        raise PredicateError("Failed to evaluate %s" % repr(func))
+    return result
+
+
+class LGEOSBase(threading.local):
+    """Proxy for the GEOS_C DLL/SO
+
+    This is a base class. Do not instantiate.
+    """
+    methods = {}
+    def __init__(self, dll):
+        self._lgeos = dll
+        self.geos_handle = None
+        
+
+class LGEOS14(LGEOSBase):    
+    """Proxy for the GEOS_C DLL/SO API version 1.4
+    """
+    geos_capi_version = (1, 4, 0)
+    def __init__(self, dll):
+        super(LGEOS14, self).__init__(dll)
+        self.geos_handle = self._lgeos.initGEOS(notice_h, error_h)
+        keys = self._lgeos.__dict__.keys()
+        for key in keys:
+            setattr(self, key, getattr(self._lgeos, key))
+        self.GEOSFree = self._lgeos.free
+        self.GEOSGeomToWKB_buf.errcheck = errcheck_wkb
+        self.GEOSGeomToWKT.errcheck = errcheck_just_free
+        self.GEOSRelate.errcheck = errcheck_just_free
+        for pred in ( self.GEOSDisjoint,
+              self.GEOSTouches,
+              self.GEOSIntersects,
+              self.GEOSCrosses,
+              self.GEOSWithin,
+              self.GEOSContains,
+              self.GEOSOverlaps,
+              self.GEOSEquals,
+              self.GEOSEqualsExact,
+              self.GEOSisEmpty,
+              self.GEOSisValid,
+              self.GEOSisSimple,
+              self.GEOSisRing,
+              self.GEOSHasZ
+              ):
+            pred.errcheck = errcheck_predicate
+
+        self.methods['area'] = self.GEOSArea
+        self.methods['boundary'] = self.GEOSBoundary
+        self.methods['buffer'] = self.GEOSBuffer
+        self.methods['centroid'] = self.GEOSGetCentroid
+        self.methods['convex_hull'] = self.GEOSConvexHull
+        self.methods['distance'] = self.GEOSDistance
+        self.methods['envelope'] = self.GEOSEnvelope
+        self.methods['length'] = self.GEOSLength
+        self.methods['has_z'] = self.GEOSHasZ
+        self.methods['is_empty'] = self.GEOSisEmpty
+        self.methods['is_ring'] = self.GEOSisRing
+        self.methods['is_simple'] = self.GEOSisSimple
+        self.methods['is_valid'] = self.GEOSisValid
+        self.methods['disjoint'] = self.GEOSDisjoint
+        self.methods['touches'] = self.GEOSTouches
+        self.methods['intersects'] = self.GEOSIntersects
+        self.methods['crosses'] = self.GEOSCrosses
+        self.methods['within'] = self.GEOSWithin
+        self.methods['contains'] = self.GEOSContains
+        self.methods['overlaps'] = self.GEOSOverlaps
+        self.methods['equals'] = self.GEOSEquals
+        self.methods['equals_exact'] = self.GEOSEqualsExact
+        self.methods['relate'] = self.GEOSRelate
+        self.methods['difference'] = self.GEOSDifference
+        self.methods['symmetric_difference'] = self.GEOSSymDifference
+        self.methods['union'] = self.GEOSUnion
+        self.methods['intersection'] = self.GEOSIntersection
+        self.methods['simplify'] = self.GEOSSimplify
+        self.methods['topology_preserve_simplify'] = \
+            self.GEOSTopologyPreserveSimplify
+
+
+class LGEOS15(LGEOSBase):    
+    """Proxy for the reentrant GEOS_C DLL/SO API version 1.5
+    """
+    geos_capi_version = (1, 5, 0)    
+    def __init__(self, dll):
+        super(LGEOS15, self).__init__(dll)
+        self.geos_handle = self._lgeos.initGEOS_r(notice_h, error_h)
+        keys = self._lgeos.__dict__.keys()
+        for key in filter(lambda x: not x.endswith('_r'), keys):
+            if key + '_r' in keys:
+                reentr_func = getattr(self._lgeos, key + '_r')
+                attr = functools.partial(reentr_func, self.geos_handle)
+                attr.__name__ = reentr_func.__name__
+                setattr(self, key, attr)
+            else:
+                setattr(self, key, getattr(self._lgeos, key))
+        if not hasattr(self, 'GEOSFree'):
+            self.GEOSFree = self._lgeos.free
+        self.GEOSGeomToWKB_buf.func.errcheck = errcheck_wkb
+        self.GEOSGeomToWKT.func.errcheck = errcheck_just_free
+        self.GEOSRelate.func.errcheck = errcheck_just_free
+        for pred in ( self.GEOSDisjoint,
+              self.GEOSTouches,
+              self.GEOSIntersects,
+              self.GEOSCrosses,
+              self.GEOSWithin,
+              self.GEOSContains,
+              self.GEOSOverlaps,
+              self.GEOSEquals,
+              self.GEOSEqualsExact,
+              self.GEOSisEmpty,
+              self.GEOSisValid,
+              self.GEOSisSimple,
+              self.GEOSisRing,
+              self.GEOSHasZ
+              ):
+            pred.func.errcheck = errcheck_predicate
+
+        self.methods['area'] = self.GEOSArea
+        self.methods['boundary'] = self.GEOSBoundary
+        self.methods['buffer'] = self.GEOSBuffer
+        self.methods['centroid'] = self.GEOSGetCentroid
+        self.methods['convex_hull'] = self.GEOSConvexHull
+        self.methods['distance'] = self.GEOSDistance
+        self.methods['envelope'] = self.GEOSEnvelope
+        self.methods['length'] = self.GEOSLength
+        self.methods['has_z'] = self.GEOSHasZ
+        self.methods['is_empty'] = self.GEOSisEmpty
+        self.methods['is_ring'] = self.GEOSisRing
+        self.methods['is_simple'] = self.GEOSisSimple
+        self.methods['is_valid'] = self.GEOSisValid
+        self.methods['disjoint'] = self.GEOSDisjoint
+        self.methods['touches'] = self.GEOSTouches
+        self.methods['intersects'] = self.GEOSIntersects
+        self.methods['crosses'] = self.GEOSCrosses
+        self.methods['within'] = self.GEOSWithin
+        self.methods['contains'] = self.GEOSContains
+        self.methods['overlaps'] = self.GEOSOverlaps
+        self.methods['equals'] = self.GEOSEquals
+        self.methods['equals_exact'] = self.GEOSEqualsExact
+        self.methods['relate'] = self.GEOSRelate
+        self.methods['difference'] = self.GEOSDifference
+        self.methods['symmetric_difference'] = self.GEOSSymDifference
+        self.methods['union'] = self.GEOSUnion
+        self.methods['intersection'] = self.GEOSIntersection
+        self.methods['prepared_intersects'] = self.GEOSPreparedIntersects
+        self.methods['prepared_contains'] = self.GEOSPreparedContains
+        self.methods['prepared_contains_properly'] = \
+            self.GEOSPreparedContainsProperly
+        self.methods['prepared_covers'] = self.GEOSPreparedCovers
+        self.methods['simplify'] = self.GEOSSimplify
+        self.methods['topology_preserve_simplify'] = \
+            self.GEOSTopologyPreserveSimplify
+
+class LGEOS16(LGEOS15):
+    """Proxy for the reentrant GEOS_C DLL/SO API version 1.6
+    """
+    geos_capi_version = (1, 6, 0)
+    def __init__(self, dll):
+        super(LGEOS16, self).__init__(dll)
+
+
+class LGEOS16LR(LGEOS16):    
+    """Proxy for the reentrant GEOS_C DLL/SO API version 1.6 with linear
+    referencing
+    """
+    geos_capi_version = geos_c_version
+    def __init__(self, dll):
+        super(LGEOS16LR, self).__init__(dll)
+
+        self.GEOSisValidReason.func.errcheck = errcheck_just_free
+        
+        self.methods['project'] = self.GEOSProject
+        self.methods['project_normalized'] = self.GEOSProjectNormalized
+        self.methods['interpolate'] = self.GEOSInterpolate
+        self.methods['interpolate_normalized'] = \
+            self.GEOSInterpolateNormalized
+
+
+if geos_c_version >= (1, 6, 0):
+    if hasattr(_lgeos, 'GEOSProject'):
+        L = LGEOS16LR
+    else:
+        L = LGEOS16
+elif geos_c_version >= (1, 5, 0):
+        L = LGEOS15
+else:
+        L = LGEOS14
+
+lgeos = L(_lgeos)
+
diff --git a/shapely/impl.py b/shapely/impl.py
new file mode 100644
index 0000000..063925b
--- /dev/null
+++ b/shapely/impl.py
@@ -0,0 +1,81 @@
+"""Implementation of the intermediary layer between Shapely and GEOS
+"""
+
+from shapely.coords import BoundsOp
+from shapely.geos import lgeos
+from shapely.linref import ProjectOp, InterpolateOp
+from shapely.predicates import BinaryPredicate, UnaryPredicate
+from shapely.topology import BinaryRealProperty, BinaryTopologicalOp
+from shapely.topology import UnaryRealProperty, UnaryTopologicalOp
+
+
+# Map geometry methods to their GEOS delegates
+
+IMPL14 = {
+    'area': (UnaryRealProperty, 'area'),
+    'distance': (BinaryRealProperty, 'distance'),
+    'length': (UnaryRealProperty, 'length'),
+    #
+    'boundary': (UnaryTopologicalOp, 'boundary'),
+    'bounds': (BoundsOp, None),
+    'centroid': (UnaryTopologicalOp, 'centroid'),
+    'envelope': (UnaryTopologicalOp, 'envelope'),
+    'convex_hull': (UnaryTopologicalOp, 'convex_hull'),
+    'buffer': (UnaryTopologicalOp, 'buffer'),
+    #
+    'difference': (BinaryTopologicalOp, 'difference'),
+    'intersection': (BinaryTopologicalOp, 'intersection'),
+    'symmetric_difference': (BinaryTopologicalOp, 'symmetric_difference'),
+    'union': (BinaryTopologicalOp, 'union'),
+    #
+    'has_z': (UnaryPredicate, 'has_z'),
+    'is_empty': (UnaryPredicate, 'is_empty'),
+    'is_ring': (UnaryPredicate, 'is_ring'),
+    'is_simple': (UnaryPredicate, 'is_simple'),
+    'is_valid': (UnaryPredicate, 'is_valid'),
+    #
+    'relate': (BinaryPredicate, 'relate'),
+    'contains': (BinaryPredicate, 'contains'),
+    'crosses': (BinaryPredicate, 'crosses'),
+    'disjoint': (BinaryPredicate, 'disjoint'),
+    'equals': (BinaryPredicate, 'equals'),
+    'intersects': (BinaryPredicate, 'intersects'),
+    'overlaps': (BinaryPredicate, 'overlaps'),
+    'touches': (BinaryPredicate, 'touches'),
+    'within': (BinaryPredicate, 'within'),
+    'equals_exact': (BinaryPredicate, 'equals_exact'),
+    }
+
+IMPL15 = {
+    'simplify': (UnaryTopologicalOp, 'simplify'),
+    'topology_preserve_simplify': 
+        (UnaryTopologicalOp, 'topology_preserve_simplify'),
+    'prepared_intersects': (BinaryPredicate, 'prepared_intersects'),
+    'prepared_contains': (BinaryPredicate, 'prepared_contains'),
+    'prepared_contains_properly': 
+        (BinaryPredicate, 'prepared_contains_properly'),
+    'prepared_covers': (BinaryPredicate, 'prepared_covers'),
+    }
+
+IMPL16 = {
+    }
+
+IMPL16LR = {
+    'project_normalized': (ProjectOp, 'project_normalized'),
+    'project': (ProjectOp, 'project'),
+    'interpolate_normalized': (InterpolateOp, 'interpolate_normalized'),
+    'interpolate': (InterpolateOp, 'interpolate'),
+    }
+
+def build(defs):
+    return [(k, v[0](v[1])) for k, v in defs.items()]
+
+imp = dict(build(IMPL14))
+if lgeos.geos_capi_version >= (1, 5, 0):
+    imp.update(build(IMPL15))
+if lgeos.geos_capi_version >= (1, 6, 0):
+    imp.update(build(IMPL16))
+    if 'project' in lgeos.methods:
+        imp.update(build(IMPL16LR))
+
+DefaultImplementation = imp
diff --git a/shapely/iterops.py b/shapely/iterops.py
index e556632..70b6f5d 100644
--- a/shapely/iterops.py
+++ b/shapely/iterops.py
@@ -1,5 +1,5 @@
 """
-Iterative forms of operations.
+Iterative forms of operations
 """
 
 from ctypes import c_char_p, c_size_t
@@ -24,7 +24,7 @@ class IterOp(object):
     
     def __call__(self, context, iterator, value=True):
         if context._geom is None:
-            raise ValueError, "Null geometry supports no operations"
+            raise ValueError("Null geometry supports no operations")
         for item in iterator:
             try:
                 this_geom, ob = item
@@ -32,11 +32,11 @@ class IterOp(object):
                 this_geom = item
                 ob = this_geom
             if not this_geom._geom:
-                raise ValueError, "Null geometry supports no operations"
+                raise ValueError("Null geometry supports no operations")
             retval = self.fn(context._geom, this_geom._geom)
             if retval == 2:
-                raise PredicateError, \
-                    "Failed to evaluate %s" % repr(self.fn)
+                raise PredicateError(
+                    "Failed to evaluate %s" % repr(self.fn))
             elif bool(retval) == value:
                 yield ob
 
@@ -50,3 +50,4 @@ within = IterOp(lgeos.GEOSWithin)
 contains = IterOp(lgeos.GEOSContains)
 overlaps = IterOp(lgeos.GEOSOverlaps)
 equals = IterOp(lgeos.GEOSEquals)
+
diff --git a/shapely/linref.py b/shapely/linref.py
new file mode 100644
index 0000000..fc3d0f4
--- /dev/null
+++ b/shapely/linref.py
@@ -0,0 +1,26 @@
+"""Linear referencing
+"""
+
+from shapely.topology import Delegating
+
+
+class LinearRefBase(Delegating):
+    def _validate_line(self, ob):
+        super(LinearRefBase, self)._validate(ob)
+        try:
+            assert ob.geom_type in ['LineString', 'MultiLineString']
+        except AssertionError:
+            raise TypeError("Only linear types support this operation")
+
+class ProjectOp(LinearRefBase):
+    def __call__(self, this, other):
+        self._validate_line(this)
+        self._validate(other)
+        return self.fn(this._geom, other._geom)
+
+class InterpolateOp(LinearRefBase):
+    def __call__(self, this, distance):
+        self._validate_line(this)
+        return self.fn(this._geom, distance)
+
+
diff --git a/shapely/ops.py b/shapely/ops.py
index 3a2966b..71b77e8 100644
--- a/shapely/ops.py
+++ b/shapely/ops.py
@@ -1,29 +1,87 @@
+"""Support for various GEOS geometry operations
+"""
+
+from ctypes import byref, c_void_p
+
 from shapely.geos import lgeos
 from shapely.geometry.base import geom_factory, BaseGeometry
-from shapely.geometry import asShape, asLineString
-from ctypes import byref, c_void_p
+from shapely.geometry import asShape, asLineString, asMultiLineString
+
+__all__= ['operator', 'polygonize', 'linemerge', 'cascaded_union']
+
+
+class CollectionOperator(object):
+
+    def shapeup(self, ob):
+        if isinstance(ob, BaseGeometry):
+            return ob
+        else:
+            try:
+                return asShape(ob)
+            except ValueError:
+                return asLineString(ob)
+
+    def polygonize(self, lines):
+        """Creates polygons from a source of lines
+        
+        The source may be a MultiLineString, a sequence of LineString objects,
+        or a sequence of objects than can be adapted to LineStrings.
+        """
+        source = getattr(lines, 'geoms', None) or lines
+        obs = [self.shapeup(l) for l in source]
+        geom_array_type = c_void_p * len(obs)
+        geom_array = geom_array_type()
+        for i, line in enumerate(obs):
+            geom_array[i] = line._geom
+        product = lgeos.GEOSPolygonize(byref(geom_array), len(obs))
+        collection = geom_factory(product)
+        for g in collection.geoms:
+            clone = lgeos.GEOSGeom_clone(g._geom)
+            g = geom_factory(clone)
+            g._owned = False
+            yield g
+
+    def linemerge(self, lines): 
+        """Merges all connected lines from a source
+        
+        The source may be a MultiLineString, a sequence of LineString objects,
+        or a sequence of objects than can be adapted to LineStrings.  Returns a
+        LineString or MultiLineString when lines are not contiguous. 
+        """ 
+        source = None 
+        if hasattr(lines, 'type') and lines.type == 'MultiLineString': 
+            source = lines 
+        elif hasattr(lines, '__iter__'): 
+            try: 
+                source = asMultiLineString([ls.coords for ls in lines]) 
+            except AttributeError: 
+                source = asMultiLineString(lines) 
+        if source is None: 
+            raise ValueError("Cannot linemerge %s" % lines)
+        result = lgeos.GEOSLineMerge(source._geom) 
+        return geom_factory(result)   
+
+    def cascaded_union(self, geoms):
+        """Returns the union of a sequence of geometries
+        
+        This is the most efficient method of dissolving many polygons.
+        """
+        L = len(geoms)
+        subs = (c_void_p * L)()
+        for i, g in enumerate(geoms):
+            subs[i] = g._geom
+        collection = lgeos.GEOSGeom_createCollection(6, subs, L)
+        return geom_factory(lgeos.GEOSUnionCascaded(collection))
+
+
+operator = CollectionOperator()
+polygonize = operator.polygonize
+linemerge = operator.linemerge
+cascaded_union = operator.cascaded_union
+
+class ValidateOp(object):
+    def __call__(self, this):
+        return lgeos.GEOSisValidReason(this._geom)
+
+validate = ValidateOp()
 
-def shapeup(ob):
-    if isinstance(ob, BaseGeometry):
-        return ob
-    else:
-        try:
-            return asShape(ob)
-        except ValueError:
-            return asLineString(ob)
-
-def polygonize(iterator):
-    """Creates polygons from a list of LineString objects.
-    """
-    lines = [shapeup(ob) for ob in iterator]
-    geom_array_type = c_void_p * len(lines)
-    geom_array = geom_array_type()
-    for i, line in enumerate(lines):
-        geom_array[i] = line._geom
-    product = lgeos.GEOSPolygonize(byref(geom_array), len(lines))
-    collection = geom_factory(product)
-    for g in collection.geoms:
-        clone = lgeos.GEOSGeom_clone(g._geom)
-        g = geom_factory(clone)
-        g._owned = False
-        yield g
diff --git a/shapely/predicates.py b/shapely/predicates.py
index ed12158..bc28ba7 100644
--- a/shapely/predicates.py
+++ b/shapely/predicates.py
@@ -1,65 +1,24 @@
 """
-Support for GEOS spatial predicates.
+Support for GEOS spatial predicates
 """
 
-from shapely.geos import PredicateError
-
-
-class OpWrapper(object):
-    
-    def __init__(self, fn, context):
-        self.fn = fn
-        self.context = context
-        
-    def __call__(self, other):
-        if not other._geom:
-            raise ValueError, "Null geometry can not be operated upon"
-        return bool(self.fn(self.context._geom, other._geom))
-
-
-class BinaryPredicate(object):
-    
-    """A callable non-data descriptor.
-    """
-   
-    fn = None
-    context = None
-
-    def __init__(self, fn):
-        self.fn = fn
-        def errcheck(result, func, argtuple):
-            if result == 2:
-                raise PredicateError, "Failed to evaluate %s" % repr(self.fn)
-            return result
-        self.fn.errcheck = errcheck
-
-    def __get__(self, obj, objtype=None):
-        if not obj._geom:
-            raise ValueError, "Null geometry supports no operations"
-        return OpWrapper(self.fn, obj)
-
-
-# A data descriptor
-class UnaryPredicate(object):
-
-    """A data descriptor.
-    """
-
-    fn = None
-
-    def __init__(self, fn):
-        self.fn = fn
-        def errcheck(result, func, argtuple):
-            if result == 2:
-                raise PredicateError, "Failed to evaluate %s" % repr(self.fn)
-            return result
-        self.fn.errcheck = errcheck
-
-    def __get__(self, obj, objtype=None):
-        if not obj._geom:
-            raise ValueError, "Null geometry supports no operations"
-        return bool(self.fn(obj._geom))
-    
-    def __set__(self, obj, value=None):
-        raise AttributeError, "Attribute is read-only"
+from shapely.geos import PredicateError, lgeos
+from shapely.topology import Delegating
+
+class BinaryPredicate(Delegating):
+    def __call__(self, this, other, *args):
+        self._validate(this)
+        self._validate(other)
+        return self.fn(this._geom, other._geom, *args)
+
+class RelateOp(Delegating):
+    def __call__(self, this, other):
+        self._validate(this)
+        self._validate(other)
+        return self.fn(this._geom, other._geom)
+
+class UnaryPredicate(Delegating):
+    def __call__(self, this):
+        self._validate(this)
+        return self.fn(this._geom)
 
diff --git a/shapely/prepared.py b/shapely/prepared.py
new file mode 100644
index 0000000..0f41406
--- /dev/null
+++ b/shapely/prepared.py
@@ -0,0 +1,54 @@
+"""
+Support for GEOS prepared geometry operations.
+"""
+
+from shapely.geos import lgeos
+from shapely.impl import DefaultImplementation
+
+
+class PreparedGeometry(object):
+    """
+    A geometry prepared for efficient comparison to a set of other geometries.
+    
+    Example:
+      
+      >>> from shapely.geometry import Point, Polygon
+      >>> triangle = Polygon(((0.0, 0.0), (1.0, 1.0), (1.0, -1.0)))
+      >>> p = prep(triangle)
+      >>> p.intersects(Point(0.5, 0.5))
+      True
+    """
+   
+    impl = DefaultImplementation
+    
+    def __init__(self, context):
+        self.context = context
+        self.__geom__ = lgeos.GEOSPrepare(self.context._geom)
+    
+    def __del__(self):
+        if self.__geom__ is not None:
+            lgeos.GEOSPreparedGeom_destroy(self.__geom__)
+        self.__geom__ = None
+        self.context = None
+    
+    @property
+    def _geom(self):
+        return self.__geom__
+    
+    def intersects(self, other):
+        return bool(self.impl['prepared_intersects'](self, other))
+
+    def contains(self, other):
+        return bool(self.impl['prepared_contains'](self, other))
+
+    def contains_properly(self, other):
+        return bool(self.impl['prepared_contains_properly'](self, other))
+
+    def covers(self, other):
+        return bool(self.impl['prepared_covers'](self, other))
+
+
+def prep(ob):
+    """Creates and returns a prepared geometric object."""
+    return PreparedGeometry(ob)
+
diff --git a/tests/Array.txt b/shapely/tests/Array.txt
similarity index 100%
rename from tests/Array.txt
rename to shapely/tests/Array.txt
diff --git a/tests/GeoInterface.txt b/shapely/tests/GeoInterface.txt
similarity index 100%
rename from tests/GeoInterface.txt
rename to shapely/tests/GeoInterface.txt
diff --git a/tests/IterOps.txt b/shapely/tests/IterOps.txt
similarity index 100%
rename from tests/IterOps.txt
rename to shapely/tests/IterOps.txt
diff --git a/tests/LineString.txt b/shapely/tests/LineString.txt
similarity index 91%
rename from tests/LineString.txt
rename to shapely/tests/LineString.txt
index 7e3cd7f..b6aeaf2 100644
--- a/tests/LineString.txt
+++ b/shapely/tests/LineString.txt
@@ -23,6 +23,14 @@ From coordinate tuples
     >>> line.wkt
     'LINESTRING (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)'
 
+From lines
+
+    >>> copy = LineString(line)
+    >>> len(copy.coords)
+    2
+    >>> print copy.wkt
+    LINESTRING (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)
+
 
 Numpy Support
 -------------
@@ -113,14 +121,10 @@ Test Non-operability of Null geometry
 
     >>> l_null = LineString()
     >>> l_null.wkt # doctest: +ELLIPSIS
-    Traceback (most recent call last):
-    ...
-    ValueError: Null geometry supports no operations
+    'GEOMETRYCOLLECTION EMPTY'
 
     >>> l_null.length # doctest: +ELLIPSIS
-    Traceback (most recent call last):
-    ...
-    ValueError: Null geometry supports no operations
+    0.0
 
 Check that we can set coordinates of a null geometry
 
diff --git a/tests/MultiLineString.txt b/shapely/tests/MultiLineString.txt
similarity index 76%
rename from tests/MultiLineString.txt
rename to shapely/tests/MultiLineString.txt
index 3c41eee..71c1951 100644
--- a/tests/MultiLineString.txt
+++ b/shapely/tests/MultiLineString.txt
@@ -4,7 +4,7 @@ MultiLineStrings
 Initialization
 --------------
 
-  >>> from shapely.geometry import MultiLineString
+  >>> from shapely.geometry import LineString, MultiLineString
 
 From coordinate tuples
 
@@ -27,6 +27,23 @@ Construct from a numpy array
   >>> geom.wkt
   'MULTILINESTRING ((0.0000000000000000 0.0000000000000000, 1.0000000000000000 2.0000000000000000))'
 
+From lines
+
+  >>> a = LineString(((1.0, 2.0), (3.0, 4.0)))
+  >>> ml = MultiLineString([a])
+  >>> len(ml.geoms)
+  1
+  >>> print ml.wkt
+  MULTILINESTRING ((1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000))
+
+From another multi-line
+
+  >>> ml2 = MultiLineString(ml)
+  >>> len(ml2.geoms)
+  1
+  >>> print ml2.wkt
+  MULTILINESTRING ((1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000))
+
 
 Sub-geometry Access
 -------------------
diff --git a/tests/MultiPoint.txt b/shapely/tests/MultiPoint.txt
similarity index 80%
rename from tests/MultiPoint.txt
rename to shapely/tests/MultiPoint.txt
index bd0c5d5..4e5df0b 100644
--- a/tests/MultiPoint.txt
+++ b/shapely/tests/MultiPoint.txt
@@ -4,7 +4,7 @@ MultiPoints
 Initialization
 --------------
 
-  >>> from shapely.geometry import MultiPoint
+  >>> from shapely.geometry import Point, MultiPoint
 
 Construct from a numpy array
 
@@ -25,6 +25,22 @@ From coordinate tuples
   >>> geom.wkt
   'MULTIPOINT (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)'
 
+From points
+
+  >>> geom = MultiPoint((Point(1.0, 2.0), Point(3.0, 4.0)))
+  >>> len(geom.geoms)
+  2
+  >>> print geom.wkt
+  MULTIPOINT (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)
+
+From another multi-point
+
+  >>> geom2 = MultiPoint(geom)
+  >>> len(geom2.geoms)
+  2
+  >>> print geom2.wkt
+  MULTIPOINT (1.0000000000000000 2.0000000000000000, 3.0000000000000000 4.0000000000000000)
+
 
 Sub-geometry Access
 -------------------
diff --git a/tests/MultiPolygon.txt b/shapely/tests/MultiPolygon.txt
similarity index 65%
rename from tests/MultiPolygon.txt
rename to shapely/tests/MultiPolygon.txt
index 6edecaf..8b8723a 100644
--- a/tests/MultiPolygon.txt
+++ b/shapely/tests/MultiPolygon.txt
@@ -4,7 +4,7 @@ MultiPolygons
 Initialization
 --------------
 
-  >>> from shapely.geometry import MultiPolygon
+  >>> from shapely.geometry import Polygon, MultiPolygon
 
 From coordinate tuples
 
@@ -18,6 +18,22 @@ From coordinate tuples
   >>> geom.wkt
   'MULTIPOLYGON (((0.0000000000000000 0.0000000000000000, 0.0000000000000000 1.0000000000000000, 1.0000000000000000 1.0000000000000000, 1.0000000000000000 0.0000000000000000, 0.0000000000000000 0.0000000000000000), (0.1000000000000000 0.1000000000000000, 0.1000000000000000 0.2000000000000000, 0.2000000000000000 0.2000000000000000, 0.2000000000000000 0.1000000000000000, 0.1000000000000000 0.1000000000000000)))'
 
+Or from polygons
+
+  >>> p = Polygon(((0, 0), (0, 1), (1, 1), (1, 0)), [((0.1,0.1), (0.1,0.2), (0.2,0.2), (0.2,0.1))])
+  >>> geom = MultiPolygon([p])
+  >>> len(geom.geoms)
+  1
+  >>> print geom.wkt
+  MULTIPOLYGON (((0.0000000000000000 0.0000000000000000, 0.0000000000000000 1.0000000000000000, 1.0000000000000000 1.0000000000000000, 1.0000000000000000 0.0000000000000000, 0.0000000000000000 0.0000000000000000), (0.1000000000000000 0.1000000000000000, 0.1000000000000000 0.2000000000000000, 0.2000000000000000 0.2000000000000000, 0.2000000000000000 0.1000000000000000, 0.1000000000000000 0.1000000000000000)))
+
+Or from another multi-polygon
+
+  >>> geom2 = MultiPolygon(geom)
+  >>> len(geom2.geoms)
+  1
+  >>> print geom2.wkt
+  MULTIPOLYGON (((0.0000000000000000 0.0000000000000000, 0.0000000000000000 1.0000000000000000, 1.0000000000000000 1.0000000000000000, 1.0000000000000000 0.0000000000000000, 0.0000000000000000 0.0000000000000000), (0.1000000000000000 0.1000000000000000, 0.1000000000000000 0.2000000000000000, 0.2000000000000000 0.2000000000000000, 0.2000000000000000 0.1000000000000000, 0.1000000000000000 0.1000000000000000)))
 
 Sub-geometry Access
 -------------------
diff --git a/tests/Operations.txt b/shapely/tests/Operations.txt
similarity index 67%
rename from tests/Operations.txt
rename to shapely/tests/Operations.txt
index 1bfd325..8ff4c2b 100644
--- a/tests/Operations.txt
+++ b/shapely/tests/Operations.txt
@@ -37,6 +37,22 @@ Topology operations
   >>> point.buffer(10.0, 32) #doctest: +ELLIPSIS
   <shapely.geometry.polygon.Polygon object at ...>
 
+  Simplify
+
+  >>> from shapely.wkt import loads
+  >>> p = loads('POLYGON ((120 120, 121 121, 122 122, 220 120, 180 199, 160 200, 140 199, 120 120))')
+  >>> expected = loads('POLYGON ((120 120, 140 199, 160 200, 180 199, 220 120, 120 120))')
+  >>> s = p.simplify(10.0, preserve_topology = False)
+  >>> s.equals_exact(expected, 0.001)
+  True
+
+  >>> from shapely.wkt import loads
+  >>> p = loads('POLYGON ((80 200, 240 200, 240 60, 80 60, 80 200),(120 120, 220 120, 180 199, 160 200, 140 199, 120 120))')
+  >>> expected = loads('POLYGON ((80 200, 240 200, 240 60, 80 60, 80 200),(120 120, 220 120, 180 199, 160 200, 140 199, 120 120))')
+  >>> s = p.simplify(10.0, preserve_topology = True)
+  >>> s.equals_exact(expected, 0.001)
+  True
+
   Convex Hull
 
   >>> point.convex_hull #doctest: +ELLIPSIS
diff --git a/tests/Persist.txt b/shapely/tests/Persist.txt
similarity index 100%
rename from tests/Persist.txt
rename to shapely/tests/Persist.txt
diff --git a/tests/Point.txt b/shapely/tests/Point.txt
similarity index 90%
rename from tests/Point.txt
rename to shapely/tests/Point.txt
index 0a4596b..2325afe 100644
--- a/tests/Point.txt
+++ b/shapely/tests/Point.txt
@@ -1,8 +1,9 @@
+Testing Point
+=============
 
   >>> from shapely.geometry import Point
 
 Test 2D points
---------------
 
   >>> p = Point(0.0, 0.0)
   >>> str(p)
@@ -11,7 +12,6 @@ Test 2D points
   'POINT (0.0000000000000000 0.0000000000000000)'
     
 Check 3D
---------
 
   >>> p = Point(0.0, 0.0, 1.0)
   >>> p.z
@@ -22,7 +22,6 @@ Check 3D
   'POINT (0.0000000000000000 0.0000000000000000)'
 
 Construct from a numpy array
-----------------------------
 
   >>> from numpy import array
   >>> p = Point(array([1.0, 2.0]))
@@ -30,12 +29,17 @@ Construct from a numpy array
   'POINT (1.0000000000000000 2.0000000000000000)'
 
 From coordinate sequence
-------------------------
 
   >>> p = Point((3.0, 4.0))
   >>> p.wkt
   'POINT (3.0000000000000000 4.0000000000000000)'
 
+From another point
+
+  >>> q = Point(p)
+  >>> print p.wkt
+  POINT (3.0000000000000000 4.0000000000000000)
+
 Coordinate access
 -----------------
 
@@ -131,14 +135,10 @@ Test Non-operability of Null geometry
 
   >>> p_null = Point()
   >>> p_null.wkt # doctest: +ELLIPSIS
-  Traceback (most recent call last):
-  ...
-  ValueError: Null geometry supports no operations
+  'GEOMETRYCOLLECTION EMPTY'
 
   >>> p_null.area # doctest: +ELLIPSIS
-  Traceback (most recent call last):
-  ...
-  ValueError: Null geometry supports no operations
+  0.0
 
 Check that we can set coordinates of a null geometry
 
diff --git a/tests/Polygon.txt b/shapely/tests/Polygon.txt
similarity index 94%
rename from tests/Polygon.txt
rename to shapely/tests/Polygon.txt
index 1b93d69..4a9e5dd 100644
--- a/tests/Polygon.txt
+++ b/shapely/tests/Polygon.txt
@@ -123,14 +123,10 @@ Test Non-operability of Null rings
 
   >>> r_null = LinearRing()
   >>> r_null.wkt # doctest: +ELLIPSIS
-  Traceback (most recent call last):
-  ...
-  ValueError: Null geometry supports no operations
+  'GEOMETRYCOLLECTION EMPTY'
 
-  >>> r_null.length # doctest: +ELLIPSIS
-  Traceback (most recent call last):
-  ...
-  ValueError: Null geometry supports no operations
+  >>> r_null.length
+  0.0
 
 Check that we can set coordinates of a null geometry
 
diff --git a/tests/Predicates.txt b/shapely/tests/Predicates.txt
similarity index 100%
rename from tests/Predicates.txt
rename to shapely/tests/Predicates.txt
diff --git a/shapely/tests/__init__.py b/shapely/tests/__init__.py
new file mode 100644
index 0000000..5f68983
--- /dev/null
+++ b/shapely/tests/__init__.py
@@ -0,0 +1,18 @@
+from unittest import TestSuite
+
+import test_doctests, test_prepared, test_equality, test_geomseq, test_xy
+import test_collection, test_emptiness, test_singularity, test_validation
+
+def test_suite():
+    suite = TestSuite()
+    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_xy.test_suite())
+    suite.addTest(test_collection.test_suite())
+    suite.addTest(test_singularity.test_suite())
+    suite.addTest(test_validation.test_suite())
+    return suite
+
diff --git a/tests/attribute-chains.txt b/shapely/tests/attribute-chains.txt
similarity index 65%
rename from tests/attribute-chains.txt
rename to shapely/tests/attribute-chains.txt
index a853587..9b7933d 100644
--- a/tests/attribute-chains.txt
+++ b/shapely/tests/attribute-chains.txt
@@ -9,8 +9,8 @@ See also ticket #151.
     [(0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0), (0.0, 0.0)]
 
     >>> from shapely.geometry import Point
-    >>> print list(Point(0.0, 0.0).buffer(1.0).exterior.coords)
-    [(0.0, -1.0), (-1.0, -1.0), ...
+    >>> print list(Point(0.0, 0.0).buffer(1.0, 1).exterior.coords)
+    [(1.0, 0.0), ..., (1.0, 0.0)]
 
 Test chained access to interiors
 
@@ -20,10 +20,18 @@ Test chained access to interiors
     ...     )
     >>> p.area
     0.75
+
+Not so much testing the exact values here, which are the responsibility of the
+geometry engine (GEOS), but that we can get chain functions and properties using
+anonymous references.
+
     >>> print list(p.interiors[0].coords)
     [(-0.25, 0.25), (-0.25, 0.75), (-0.75, 0.75), (-0.75, 0.25), (-0.25, 0.25)]
-    >>> print list(p.interiors[0].buffer(1).exterior.coords)
-    [(-0.25, -0.75), (-0.75, -0.75), ...
+    >>> xy = list(p.interiors[0].buffer(1).exterior.coords)[0]
+    >>> xy[0] + 0.25 <= 1.0e-7
+    True
+    >>> xy[1] + 0.75 <= 1.0e-7
+    True
 
 Test multiple operators, boundary of a buffer
 
diff --git a/tests/binascii_hex.txt b/shapely/tests/binascii_hex.txt
similarity index 100%
rename from tests/binascii_hex.txt
rename to shapely/tests/binascii_hex.txt
diff --git a/shapely/tests/cascaded_union.txt b/shapely/tests/cascaded_union.txt
new file mode 100644
index 0000000..63adaed
--- /dev/null
+++ b/shapely/tests/cascaded_union.txt
@@ -0,0 +1,24 @@
+Cascaded Union
+==============
+
+  >>> from functools import partial
+  >>> import random
+  >>> from shapely.geometry import Point
+  >>> from shapely.ops import cascaded_union
+
+Use a partial function to make 100 points uniformly distributed in a 40x40 
+box centered on 0,0.
+
+  >>> r = partial(random.uniform, -20.0, 20.0)
+  >>> points = [Point(r(), r()) for i in range(100)]
+
+Buffer the points, producing 100 polygon spots
+
+  >>> spots = [p.buffer(2.5) for p in points]
+
+Perform a cascaded union of the polygon spots, dissolving them into a 
+collection of polygon patches
+
+  >>> cascaded_union(spots) # doctest: +ELLIPSIS
+  <shapely.geometry.multipolygon.MultiPolygon object at 0x...>
+
diff --git a/tests/dimensions.txt b/shapely/tests/dimensions.txt
similarity index 100%
rename from tests/dimensions.txt
rename to shapely/tests/dimensions.txt
diff --git a/tests/invalid_intersection.txt b/shapely/tests/invalid_intersection.txt
similarity index 63%
rename from tests/invalid_intersection.txt
rename to shapely/tests/invalid_intersection.txt
index 331377d..9c65d08 100644
--- a/tests/invalid_intersection.txt
+++ b/shapely/tests/invalid_intersection.txt
@@ -5,7 +5,7 @@ Test recovery from operation on invalid geometries
 
   >>> from shapely.geometry import Polygon
   >>> polygon_invalid = Polygon(((0, 0), (1, 1), (1, -1), (0, 1), (0, 0)))
-  >>> polygon_invalid.is_valid
+  >>> print polygon_invalid.is_valid # doctest: +ELLIPSIS
   False
 
   Intersect with a valid polygon
@@ -18,12 +18,12 @@ Test recovery from operation on invalid geometries
   >>> result = polygon_invalid.intersection(polygon) # doctest: +ELLIPSIS
   Traceback (most recent call last):
   ...
-  TopologicalError: The operation 'GEOSIntersection' produced a null geometry. Likely cause is invalidity of the geometry <shapely.geometry.polygon.Polygon object at ...>
+  TopologicalError: The operation 'GEOSIntersection_r' produced a null geometry. Likely cause is invalidity of the geometry <shapely.geometry.polygon.Polygon object at ...>
 
   Check exception symmetry
 
   >>> result = polygon.intersection(polygon_invalid) # doctest: +ELLIPSIS
   Traceback (most recent call last):
   ...
-  TopologicalError: The operation 'GEOSIntersection' produced a null geometry. Likely cause is invalidity of the 'other' geometry <shapely.geometry.polygon.Polygon object at ...>
+  TopologicalError: The operation 'GEOSIntersection_r' produced a null geometry. Likely cause is invalidity of the 'other' geometry <shapely.geometry.polygon.Polygon object at ...>
 
diff --git a/shapely/tests/linear-referencing.txt b/shapely/tests/linear-referencing.txt
new file mode 100644
index 0000000..3943915
--- /dev/null
+++ b/shapely/tests/linear-referencing.txt
@@ -0,0 +1,58 @@
+First, tests of projection
+
+  >>> from shapely.geometry import Point, LineString, MultiLineString
+  
+  >>> point = Point(1, 1)
+  >>> line1 = LineString(([0, 0], [2, 0]))
+  >>> line1.project(point)
+  1.0
+  >>> line1.project(point, normalized=True)
+  0.5
+
+  >>> line2 = LineString(([3, 0], [3, 6]))
+  >>> line2.project(point)
+  1.0
+  >>> line2.project(point, normalized=True)
+  0.16666666666666666
+
+  >>> multiline = MultiLineString([list(line1.coords), list(line2.coords)]) 
+  >>> multiline.project(point)
+  1.0
+  >>> multiline.project(point, normalized=True)
+  0.125
+
+  >>> point.buffer(1.0).project(point) # doctest: +ELLIPSIS
+  Traceback (most recent call last):
+  ...
+  TypeError: Only linear types support this operation
+
+Points that aren't on the line project to 0.
+
+  >>> line1.project(Point(-10,-10))
+  0.0
+
+Now tests of interpolation
+
+  >>> line1.interpolate(0.5).wkt
+  'POINT (0.5000000000000000 0.0000000000000000)'
+  >>> line1.interpolate(0.5, normalized=True).wkt
+  'POINT (1.0000000000000000 0.0000000000000000)'
+
+  >>> line2.interpolate(0.5).wkt
+  'POINT (3.0000000000000000 0.5000000000000000)'
+  >>> line2.interpolate(0.5, normalized=True).wkt
+  'POINT (3.0000000000000000 3.0000000000000000)'
+
+  >>> multiline.interpolate(0.5).wkt
+  'POINT (0.5000000000000000 0.0000000000000000)'
+  >>> multiline.interpolate(0.5, normalized=True).wkt
+  'POINT (3.0000000000000000 2.0000000000000000)'
+
+Distances greater than length of the line or less than zero yield the line's
+ends.
+
+  >>> line1.interpolate(-1000.0).wkt
+  'POINT (0.0000000000000000 0.0000000000000000)'
+  >>> line1.interpolate(1000.0).wkt
+  'POINT (2.0000000000000000 0.0000000000000000)'
+
diff --git a/shapely/tests/linemerge.txt b/shapely/tests/linemerge.txt
new file mode 100644
index 0000000..50c94af
--- /dev/null
+++ b/shapely/tests/linemerge.txt
@@ -0,0 +1,61 @@
+Linemerge 
+========= 
+
+  >>> from shapely.geometry import LineString, MultiLineString, Polygon 
+  >>> from shapely.ops import linemerge 
+    
+  >>> lines = MultiLineString([ 
+  ...     ((0, 0), (1, 1)), 
+  ...     ((2, 0), (2, 1), (1, 1)) 
+  ...     ]) 
+  >>> result = linemerge(lines)
+  >>> result # docstring: +ELLIPSIS
+  <shapely.geometry.linestring.LineString object at 0x...>
+  >>> result.is_ring 
+  False 
+  >>> len(result.coords) 
+  4 
+  >>> result.coords[0] 
+  (0.0, 0.0) 
+  >>> result.coords[3] 
+  (2.0, 0.0) 
+    
+  >>> lines2 = MultiLineString([ 
+  ...     ((0, 0), (1, 1)), 
+  ...     ((0, 0), (2, 0), (2, 1), (1, 1)) 
+  ...     ]) 
+  >>> result = linemerge(lines2) 
+  >>> result.is_ring 
+  True 
+  >>> len(result.coords) 
+  5 
+
+  >>> lines3 = [ 
+  ...     LineString(((0, 0), (1, 1))), 
+  ...     LineString(((0, 0), (0, 1))), 
+  ...     ] 
+  >>> result = linemerge(lines3) 
+  >>> result.is_ring 
+  False 
+  >>> len(result.coords) 
+  3 
+  >>> result.coords[0] 
+  (0.0, 1.0) 
+  >>> result.coords[2] 
+  (1.0, 1.0) 
+   
+  >>> lines4 = [ 
+  ...     ((0, 0), (1, 1)), 
+  ...     ((0, 0), (0, 1)), 
+  ...     ] 
+  >>> result.equals(linemerge(lines4)) 
+  True 
+   
+  >>> lines5 = [ 
+  ...     ((0, 0), (1, 1)), 
+  ...     ((1, 0), (0, 1)), 
+  ...     ] 
+  >>> result = linemerge(lines5) 
+  >>> result.type == 'MultiLineString' 
+  True
+
diff --git a/tests/polygonize.txt b/shapely/tests/polygonize.txt
similarity index 100%
rename from tests/polygonize.txt
rename to shapely/tests/polygonize.txt
diff --git a/shapely/tests/test_collection.py b/shapely/tests/test_collection.py
new file mode 100644
index 0000000..65b504f
--- /dev/null
+++ b/shapely/tests/test_collection.py
@@ -0,0 +1,10 @@
+import unittest
+from shapely.geometry.collection import GeometryCollection
+
+class CollectionTestCase(unittest.TestCase):
+    def test_array_interface(self):
+        m = GeometryCollection()
+        self.failUnlessEqual(len(m), 0)
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(CollectionTestCase)
diff --git a/shapely/tests/test_doctests.py b/shapely/tests/test_doctests.py
new file mode 100644
index 0000000..25d5f7a
--- /dev/null
+++ b/shapely/tests/test_doctests.py
@@ -0,0 +1,35 @@
+import doctest
+import unittest
+import glob
+import os
+
+optionflags = (doctest.REPORT_ONLY_FIRST_FAILURE |
+               doctest.NORMALIZE_WHITESPACE |
+               doctest.ELLIPSIS)
+
+def list_doctests():
+    print __file__
+    return [filename
+            for filename
+            in glob.glob(os.path.join(os.path.dirname(__file__), '*.txt'))]
+
+def open_file(filename, mode='r'):
+    """Helper function to open files from within the tests package."""
+    return open(os.path.join(os.path.dirname(__file__), filename), mode)
+
+def setUp(test):
+    test.globs.update(dict(
+            open_file = open_file,
+            ))
+
+def test_suite():
+    return unittest.TestSuite(
+        [doctest.DocFileSuite(os.path.basename(filename),
+                              optionflags=optionflags,
+                              setUp=setUp)
+         for filename
+         in list_doctests()])
+
+if __name__ == "__main__":
+    runner = unittest.TextTestRunner(verbosity=1)
+    runner.run(test_suite())
diff --git a/shapely/tests/test_emptiness.py b/shapely/tests/test_emptiness.py
new file mode 100644
index 0000000..137f292
--- /dev/null
+++ b/shapely/tests/test_emptiness.py
@@ -0,0 +1,19 @@
+import unittest
+from shapely.geometry.base import BaseGeometry
+from shapely.geometry import Point
+
+class EmptinessTestCase(unittest.TestCase):
+    def test_empty_base(self):
+        g = BaseGeometry()
+        self.failUnless(g._is_empty, True)
+    def test_empty_point(self):
+        p = Point()
+        self.failUnless(p._is_empty, True)
+    def test_emptying_point(self):
+        p = Point(0, 0)
+        self.failIf(p._is_empty, False)
+        p.empty()
+        self.failUnless(p._is_empty, True)
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(EmptinessTestCase)
diff --git a/shapely/tests/test_equality.py b/shapely/tests/test_equality.py
new file mode 100644
index 0000000..5ade663
--- /dev/null
+++ b/shapely/tests/test_equality.py
@@ -0,0 +1,30 @@
+import unittest
+from shapely import geometry
+
+
+class PointEqualityTestCase(unittest.TestCase):
+    
+    def test_equals_exact(self):
+        p1 = geometry.Point(1.0, 1.0)
+        p2 = geometry.Point(2.0, 2.0)
+        self.failIf(p1.equals(p2))
+        self.failIf(p1.equals_exact(p2, 0.001))
+
+    def test_almost_equals_default(self):
+        p1 = geometry.Point(1.0, 1.0)
+        p2 = geometry.Point(1.0+1e-7, 1.0+1e-7) # almost equal to 6 places
+        p3 = geometry.Point(1.0+1e-6, 1.0+1e-6) # not almost equal
+        self.failUnless(p1.almost_equals(p2))
+        self.failIf(p1.almost_equals(p3))
+
+    def test_almost_equals(self):
+        p1 = geometry.Point(1.0, 1.0)
+        p2 = geometry.Point(1.1, 1.1)
+        self.failIf(p1.equals(p2))
+        self.failUnless(p1.almost_equals(p2, 0))
+        self.failIf(p1.almost_equals(p2, 1))
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(
+                                    PointEqualityTestCase
+                                    )
diff --git a/shapely/tests/test_geomseq.py b/shapely/tests/test_geomseq.py
new file mode 100644
index 0000000..f384e62
--- /dev/null
+++ b/shapely/tests/test_geomseq.py
@@ -0,0 +1,11 @@
+import unittest
+from shapely import geometry
+
+class MultiLineTestCase(unittest.TestCase):
+    def test_array_interface(self):
+        m = geometry.MultiLineString([((0, 0), (1, 1)), ((2, 2), (3, 3))])
+        ai = m.geoms[0].__array_interface__
+        self.failUnlessEqual(ai['shape'], (2, 2))
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(MultiLineTestCase)
diff --git a/shapely/tests/test_prepared.py b/shapely/tests/test_prepared.py
new file mode 100644
index 0000000..d8bbca4
--- /dev/null
+++ b/shapely/tests/test_prepared.py
@@ -0,0 +1,15 @@
+import unittest
+from shapely import prepared
+from shapely import geometry
+
+
+class PreparedGeometryTestCase(unittest.TestCase):
+    
+    def test_prepared(self):
+        p = prepared.PreparedGeometry(geometry.Point(0.0, 0.0))
+
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(
+                                    PreparedGeometryTestCase
+                                    )
diff --git a/shapely/tests/test_singularity.py b/shapely/tests/test_singularity.py
new file mode 100644
index 0000000..a2d2493
--- /dev/null
+++ b/shapely/tests/test_singularity.py
@@ -0,0 +1,15 @@
+import unittest
+from shapely.geometry import Polygon
+
+class PolygonTestCase(unittest.TestCase):
+    def test_polygon_3(self):
+        p = (1.0, 1.0)
+        poly = Polygon([p, p, p])
+        self.failUnlessEqual(poly.bounds, (1.0, 1.0, 1.0, 1.0))
+    def test_polygon_5(self):
+        p = (1.0, 1.0)
+        poly = Polygon([p, p, p, p, p])
+        self.failUnlessEqual(poly.bounds, (1.0, 1.0, 1.0, 1.0))
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(PolygonTestCase)
diff --git a/shapely/tests/test_validation.py b/shapely/tests/test_validation.py
new file mode 100644
index 0000000..0a008de
--- /dev/null
+++ b/shapely/tests/test_validation.py
@@ -0,0 +1,10 @@
+import unittest
+from shapely.geometry import *
+from shapely.validation import explain_validity
+
+class ValidationTestCase(unittest.TestCase):
+    def test_valid(self):
+        self.failUnlessEqual(explain_validity(Point(0, 0)), 'Valid Geometry')
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(ValidationTestCase)
diff --git a/shapely/tests/test_xy.py b/shapely/tests/test_xy.py
new file mode 100644
index 0000000..0bed93f
--- /dev/null
+++ b/shapely/tests/test_xy.py
@@ -0,0 +1,14 @@
+import unittest
+from shapely import geometry
+
+class XYTestCase(unittest.TestCase):
+    """New geometry/coordseq method 'xy' makes numpy interop easier"""
+    def test_arrays(self):
+        x, y = geometry.LineString(((0, 0), (1, 1))).xy
+        self.failUnless(len(x) == 2)
+        self.failUnless(list(x) == [0.0, 1.0])
+        self.failUnless(len(y) == 2)
+        self.failUnless(list(y) == [0.0, 1.0])
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(XYTestCase)
diff --git a/shapely/tests/threading_test.py b/shapely/tests/threading_test.py
new file mode 100644
index 0000000..fd3b4bf
--- /dev/null
+++ b/shapely/tests/threading_test.py
@@ -0,0 +1,37 @@
+import threading
+
+def main():
+    num_threads = 10
+    use_threads = True
+    
+    if not use_threads:
+        # Run core code
+        runShapelyBuilding()
+    else:
+        threads = [threading.Thread(target=runShapelyBuilding, name=str(i), args=(i,)) for i in range(num_threads)]
+        for t in threads:
+            t.start()
+        for t in threads:
+            t.join()
+        
+def runShapelyBuilding(num):
+    print "%s: Running shapely tests on wkb" % num
+    import shapely.geos
+    print "%s GEOS Handle: %s" % (num, shapely.geos.lgeos.geos_handle)
+    import shapely.wkt
+    import shapely.wkb
+    p = shapely.wkt.loads("POINT (0 0)")
+    print "%s WKT: %s" % (num, shapely.wkt.dumps(p))
+    wkb = shapely.wkb.dumps(p)
+    print "%s WKB: %s" % (num, wkb.encode('hex'))
+    
+    for i in xrange(10):
+        obj = shapely.wkb.loads(wkb)
+    
+    print "%s GEOS Handle: %s" % (num, shapely.geos.lgeos.geos_handle)
+    print "Done %s" % num
+    
+# Run main
+if __name__ == '__main__':
+   main()   
+
diff --git a/tests/wkt_locale.txt b/shapely/tests/wkt_locale.txt
similarity index 100%
rename from tests/wkt_locale.txt
rename to shapely/tests/wkt_locale.txt
diff --git a/shapely/topology.py b/shapely/topology.py
index 97e94c1..5def1fa 100644
--- a/shapely/topology.py
+++ b/shapely/topology.py
@@ -1,74 +1,62 @@
 """
-Support for GEOS topological operations.
+Intermediaries supporting GEOS topological operations
+
+These methods all take Shapely geometries and other Python objects and delegate
+to GEOS functions via ctypes.
+
+These methods return ctypes objects that should be recast by the caller.
 """
 
-from shapely.geos import TopologicalError
+from ctypes import byref, c_double
+from shapely.geos import TopologicalError, lgeos
 
 
-class OpWrapper(object):
-    
-    def __init__(self, fn, context, factory):
-        self.fn = fn
-        self.context = context
-        self.factory = factory
-        
-    def __call__(self, other):
-        context = self.context
-        if other._geom is None:
-            raise ValueError, "Null geometry supports no operations"
-        product = self.fn(context._geom, other._geom)
-        if not product:
-            # Check validity of geometries
-            if not context.is_valid:
-                raise TopologicalError, \
-                "The operation '%s' produced a null geometry. Likely cause is invalidity of the geometry %s" % (self.fn.__name__, repr(context))
-            elif not other.is_valid:
-                raise TopologicalError, \
-                "The operation '%s' produced a null geometry. Likely cause is invalidity of the 'other' geometry %s" % (self.fn.__name__, repr(other))
-        return self.factory(product)
-        
-                    
-class BinaryTopologicalOp(object):
-    
-    """A non-data descriptor that returns a callable.
-    
-    Wraps a GEOS function. The factory is a callable which wraps results in
-    the appropriate shapely geometry class.
-    """
-    
-    fn = None
-    factory = None
-    
-    def __init__(self, fn, factory):
-        self.fn = fn
-        self.factory = factory
-    
-    def __get__(self, obj, objtype=None):
-        if not obj._geom:
-            raise ValueError, "Null geometry supports no operations"
-        return OpWrapper(self.fn, obj, self.factory)
+class Validating(object):
+    def _validate(self, ob):
+        try:
+            assert ob is not None
+            assert ob._geom is not None
+        except AssertionError:
+            raise ValueError("Null geometry supports no operations")
+
+class Delegating(Validating):
+    def __init__(self, name):
+        self.fn = lgeos.methods[name]
 
+class BinaryRealProperty(Delegating):
+    def __call__(self, this, other):
+        self._validate(this)
+        self._validate(other)
+        d = c_double()
+        retval = self.fn(this._geom, other._geom, byref(d))
+        return d.value
+
+class UnaryRealProperty(Delegating):
+    def __call__(self, this):
+        self._validate(this)
+        d = c_double()
+        retval = self.fn(this._geom, byref(d))
+        return d.value
+
+class BinaryTopologicalOp(Delegating):
+    def __call__(self, this, other, *args):
+        self._validate(this)
+        self._validate(other)
+        product = self.fn(this._geom, other._geom, *args)
+        if product is None:
+            if not this.is_valid:
+                raise TopologicalError(
+                    "The operation '%s' produced a null geometry. Likely cause is invalidity of the geometry %s" % (self.fn.__name__, repr(this)))
+            elif not other.is_valid:
+                raise TopologicalError(
+                    "The operation '%s' produced a null geometry. Likely cause is invalidity of the 'other' geometry %s" % (self.fn.__name__, repr(other)))
+            else:
+                raise TopologicalError(
+                    "This operation produced a null geometry. Reason: unknown")
+        return product
 
-class UnaryTopologicalOp(object):
-    
-    """A data descriptor.
-    
-    Wraps a GEOS function. The factory is a callable which wraps results in
-    the appropriate shapely geometry class.
-    """
-    
-    fn = None
-    factory = None
-    
-    def __init__(self, fn, factory):
-        self.fn = fn
-        self.factory = factory
-    
-    def __get__(self, obj, objtype=None):
-        if obj._geom is None:
-            raise ValueError, "Null geometry supports no operations"
-        return self.factory(self.fn(obj._geom), obj)
-    
-    def __set__(self, obj, value=None):
-        raise AttributeError, "Attribute is read-only"
+class UnaryTopologicalOp(Delegating):
+    def __call__(self, this, *args):
+        self._validate(this)
+        return self.fn(this._geom, *args)
 
diff --git a/shapely/validation.py b/shapely/validation.py
new file mode 100644
index 0000000..69dde86
--- /dev/null
+++ b/shapely/validation.py
@@ -0,0 +1,7 @@
+#
+
+from shapely.geos import lgeos
+
+def explain_validity(ob):
+    return lgeos.GEOSisValidReason(ob._geom)
+
diff --git a/shapely/wkb.py b/shapely/wkb.py
index b6315c9..6e3869b 100644
--- a/shapely/wkb.py
+++ b/shapely/wkb.py
@@ -1,23 +1,25 @@
-"""
-Load/dump geometries using the well-known binary (WKB) format.
+"""Load/dump geometries using the well-known binary (WKB) format
 """
 
-from ctypes import byref, c_int, c_size_t, c_char_p, string_at
+from ctypes import byref, c_size_t, c_char_p, string_at
 from ctypes import c_void_p, c_size_t
 
-from shapely.geos import lgeos, free, ReadingError
-from shapely.geometry.base import geom_factory
+from shapely.geos import lgeos, ReadingError
 
 
 # Pickle-like convenience functions
 
-def loads(data):
-    """Load a geometry from a WKB string."""
+def deserialize(data):
     geom = lgeos.GEOSGeomFromWKB_buf(c_char_p(data), c_size_t(len(data)));
     if not geom:
-        raise ReadingError, \
-        "Could not create geometry because of errors while reading input."
-    return geom_factory(geom)
+        raise ReadingError(
+            "Could not create geometry because of errors while reading input.")
+    return geom
+
+def loads(data):
+    """Load a geometry from a WKB string."""
+    from shapely.geometry.base import geom_factory
+    return geom_factory(deserialize(data))
 
 def load(fp):
     """Load a geometry from an open file."""
@@ -26,15 +28,10 @@ def load(fp):
 
 def dumps(ob):
     """Dump a WKB representation of a geometry to a byte string."""
-    func = lgeos.GEOSGeomToWKB_buf
+    if ob is None or ob._geom is None:
+        raise ValueError("Null geometry supports no operations")
     size = c_size_t()
-    def errcheck(result, func, argtuple):
-        if not result: return None
-        retval = string_at(result, size.value)[:]
-        free(result)
-        return retval
-    func.errcheck = errcheck
-    return func(c_void_p(ob._geom), byref(size))
+    return lgeos.GEOSGeomToWKB_buf(c_void_p(ob._geom), byref(size))
 
 def dump(ob, fp):
     """Dump a geometry to an open file."""
diff --git a/shapely/wkt.py b/shapely/wkt.py
index 5c016a7..c5650ed 100644
--- a/shapely/wkt.py
+++ b/shapely/wkt.py
@@ -1,17 +1,16 @@
-"""
-Load/dump geometries using the well-known text (WKT) format.
+"""Load/dump geometries using the well-known text (WKT) format
 """
 
-from ctypes import byref, c_int, c_size_t, c_char_p, string_at
+from ctypes import c_char_p
 
-from shapely.geos import lgeos, free, allocated_c_char_p, ReadingError
-from shapely.geometry.base import geom_factory
+from shapely.geos import lgeos, ReadingError
 
 
 # Pickle-like convenience functions
 
 def loads(data):
     """Load a geometry from a WKT string."""
+    from shapely.geometry.base import geom_factory
     geom = lgeos.GEOSGeomFromWKT(c_char_p(data))
     if not geom:
         raise ReadingError, \
@@ -25,14 +24,9 @@ def load(fp):
 
 def dumps(ob):
     """Dump a WKB representation of a geometry to a byte string."""
-    func = lgeos.GEOSGeomToWKT
-    def errcheck(result, func, argtuple):
-        retval = result.value
-        free(result)
-        return retval
-    func.restype = allocated_c_char_p
-    func.errcheck = errcheck
-    return func(ob._geom)
+    if ob is None or ob._geom is None:
+        raise ValueError("Null geometry supports no operations")
+    return lgeos.GEOSGeomToWKT(ob._geom)
 
 def dump(ob, fp):
     """Dump a geometry to an open file."""
diff --git a/tests/._Array.txt b/tests/._Array.txt
deleted file mode 100644
index 3c85056..0000000
Binary files a/tests/._Array.txt and /dev/null differ
diff --git a/tests/._GeoInterface.txt b/tests/._GeoInterface.txt
deleted file mode 100644
index 96f164d..0000000
Binary files a/tests/._GeoInterface.txt and /dev/null differ
diff --git a/tests/._Persist.txt b/tests/._Persist.txt
deleted file mode 100644
index 3ba76d6..0000000
Binary files a/tests/._Persist.txt and /dev/null differ
diff --git a/tests/._Point.txt b/tests/._Point.txt
deleted file mode 100644
index 2939fa5..0000000
Binary files a/tests/._Point.txt and /dev/null differ
diff --git a/tests/._Predicates.txt b/tests/._Predicates.txt
deleted file mode 100644
index 6a0a63d..0000000
Binary files a/tests/._Predicates.txt and /dev/null differ
diff --git a/tests/._binascii_hex.txt b/tests/._binascii_hex.txt
deleted file mode 100644
index 787b15c..0000000
Binary files a/tests/._binascii_hex.txt and /dev/null differ

-- 
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