[python-geojson] 05/10: Imported Upstream version 1.0.9

Johan Van de Wauw johanvdw-guest at moszumanska.debian.org
Sat Jan 31 11:12:53 UTC 2015


This is an automated email from the git hooks/post-receive script.

johanvdw-guest pushed a commit to branch master
in repository python-geojson.

commit 2a1c111364222e7dd8cead8cf39bf3b198b92fa1
Author: Johan Van de Wauw <johan.vandewauw at gmail.com>
Date:   Thu Jan 29 15:04:06 2015 +0100

    Imported Upstream version 1.0.9
---
 .gitignore                             |   1 +
 .travis.yml                            |  10 ++-
 CHANGELOG.rst                          |  30 +++++++
 LICENSE.rst                            |   4 +-
 MANIFEST.in                            |   2 +-
 README.rst                             |  96 +++++++++++++++++-----
 geojson/__init__.py                    |   1 +
 geojson/base.py                        |  28 ++++++-
 geojson/codec.py                       |   5 +-
 geojson/geometry.py                    |  11 +--
 geojson/mapping.py                     |  35 +++++---
 geojson/utils.py                       |  35 ++++++++
 setup.cfg                              |  10 ---
 setup.py                               |  36 +++++++--
 tests/README.txt                       |  15 ----
 tests/blog.txt                         |  21 -----
 tests/c2c_features.txt                 |  45 -----------
 tests/crs.txt                          |  20 -----
 tests/data.geojson                     |   7 ++
 tests/fallsthrough_to_json.txt         |  47 -----------
 tests/feature-null-geom.txt            |   8 --
 tests/featurecollection.txt            |  84 -------------------
 tests/features.txt                     |  91 ---------------------
 tests/geometry.txt                     | 122 ----------------------------
 tests/geometrycollection.txt           |  54 -------------
 tests/including_additional_members.txt |  81 -------------------
 tests/objects.txt                      |  80 -------------------
 tests/strict_json.txt                  |  30 -------
 tests/test_base.py                     |  48 +++++++++++
 tests/test_coords.py                   |  57 +++++++++++++
 tests/test_crs.py                      |  30 +++++++
 tests/test_features.py                 |  79 ++++++++++++++++++
 tests/test_geo_interface.py            | 142 +++++++++++++++++++++++++++++++++
 tests/test_null_geometries.py          |  27 +++++++
 tests/test_strict_json.py              |  52 ++++++++++++
 35 files changed, 682 insertions(+), 762 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..567609b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+build/
diff --git a/.travis.yml b/.travis.yml
index a51075f..f9f06e7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,4 +4,12 @@ python:
    - "2.7"
    - "3.2"
    - "3.3"
-script: "python setup.py nosetests"
+   - "3.4"
+   - "pypy"
+   - "pypy3"
+install:
+   pip install coveralls 
+script: 
+   coverage run --source=geojson setup.py test
+after_success:
+   coveralls
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 127e99b..202de17 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,36 @@
 Changes
 =======
 
+1.0.9 (2014-10-05)
+------------------
+
+- Fix bug where unicode/non-string properties with a 'type' key cause a crash
+
+1.0.8 (2014-09-30)
+------------------
+
+- Fix bug where unicode keys don't get decoded properly
+- Add coords and map_coords utilities
+
+1.0.7 (2014-04-19)
+------------------
+
+- Compatibility with Python 3.4
+- Remove nose dependency
+- Convert doctests to unittests
+- Run tests using runtests.sh
+
+1.0.6 (2014-01-18)
+------------------
+
+- Update README.rst documentation (fix errors, add examples)
+- Allow simplejson to be used again
+
+1.0.5 (2013-11-16)
+------------------
+
+- Remove warning about RSTs in test/ upon install
+
 1.0.4 (2013-11-16)
 ------------------
 
diff --git a/LICENSE.rst b/LICENSE.rst
index 3f9a357..0caa10c 100644
--- a/LICENSE.rst
+++ b/LICENSE.rst
@@ -1,4 +1,4 @@
-Copyright © 2013, Corey Farwell
+Copyright © 2014, contributors of python-geojson
 
 All rights reserved.
 
@@ -8,4 +8,4 @@ Redistribution and use in source and binary forms, with or without modification,
 -  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 -  Neither the name of the python-geojson nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COREY FARWELL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERR [...]
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS OF PYTHON-GEOJSON BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFIT [...]
diff --git a/MANIFEST.in b/MANIFEST.in
index 65d7f18..c97e142 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,2 @@
 include *.rst
-recursive-include tests *.rst *.txt *.py
+recursive-include tests *.txt *.py
diff --git a/README.rst b/README.rst
index 879c230..e31128f 100644
--- a/README.rst
+++ b/README.rst
@@ -3,6 +3,8 @@ python-geojson
 
 .. image:: https://travis-ci.org/frewsxcv/python-geojson.png?branch=master
    :target: https://travis-ci.org/frewsxcv/python-geojson
+.. image:: https://coveralls.io/repos/frewsxcv/python-geojson/badge.png
+   :target: https://coveralls.io/r/frewsxcv/python-geojson
 
 This library contains:
 
@@ -10,10 +12,16 @@ This library contains:
 - Classes for all GeoJSON Objects
 - An implementation of the Python `__geo_interface__ Specification`_
 
+**Table of Contents**
+
+.. contents::
+   :backlinks: none
+   :local:
+
 Installation
 ------------
 
-python-geojson is compatible with Python 2.6, 2.7, 3.2, and 3.3. It is listed on `PyPi as 'geojson'`_. The recommended way to install is via pip_:
+python-geojson is compatible with Python 2.6, 2.7, 3.2, 3.3, and 3.4. It is listed on `PyPi as 'geojson'`_. The recommended way to install is via pip_:
 
 .. code::
 
@@ -39,7 +47,7 @@ Point
   >>> Point((-115.81, 37.24))  # doctest: +ELLIPSIS
   {"coordinates": [-115.8..., 37.2...], "type": "Point"}
 
-General information about Point can be found in `Section 2.1.2`_ and `Appendix A: Point`_ within `The GeoJSON Format Specification`_.
+Visualize the result of the example above `here <https://gist.github.com/frewsxcv/b5768a857f5598e405fa>`__. General information about Point can be found in `Section 2.1.2`_ and `Appendix A: Point`_ within `The GeoJSON Format Specification`_.
 
 .. _Section 2.1.2: http://www.geojson.org/geojson-spec.html#point
 .. _Appendix A\: Point: http://www.geojson.org/geojson-spec.html#id2
@@ -54,7 +62,7 @@ MultiPoint
   >>> MultiPoint([(-155.52, 19.61), (-156.22, 20.74), (-157.97, 21.46)])  # doctest: +ELLIPSIS
   {"coordinates": [[-155.5..., 19.6...], [-156.2..., 20.7...], [-157.9..., 21.4...]], "type": "MultiPoint"}
 
-General information about MultiPoint can be found in `Section 2.1.3`_ and `Appendix A: MultiPoint`_ within `The GeoJSON Format Specification`_.
+Visualize the result of the example above `here <https://gist.github.com/frewsxcv/be02025c1eb3aa2040ee>`__. General information about MultiPoint can be found in `Section 2.1.3`_ and `Appendix A: MultiPoint`_ within `The GeoJSON Format Specification`_.
 
 .. _Section 2.1.3: http://www.geojson.org/geojson-spec.html#multipoint
 .. _Appendix A\: MultiPoint: http://www.geojson.org/geojson-spec.html#id5
@@ -70,7 +78,7 @@ LineString
   >>> LineString([(8.919, 44.4074), (8.923, 44.4075)])  # doctest: +ELLIPSIS
   {"coordinates": [[8.91..., 44.407...], [8.92..., 44.407...]], "type": "LineString"}
 
-General information about LineString can be found in `Section 2.1.4`_ and `Appendix A: LineString`_ within `The GeoJSON Format Specification`_.
+Visualize the result of the example above `here <https://gist.github.com/frewsxcv/758563182ca49ce8e8bb>`__. General information about LineString can be found in `Section 2.1.4`_ and `Appendix A: LineString`_ within `The GeoJSON Format Specification`_.
 
 .. _Section 2.1.4: http://www.geojson.org/geojson-spec.html#linestring
 .. _Appendix A\: LineString: http://www.geojson.org/geojson-spec.html#id3
@@ -88,7 +96,7 @@ MultiLineString
   ... ])  # doctest: +ELLIPSIS
   {"coordinates": [[[3.7..., 9.2...], [-130.9..., 1.52...]], [[23.1..., -34.2...], [-1.3..., -4.6...], [3.4..., 77.9...]]], "type": "MultiLineString"}
 
-General information about MultiLineString can be found in `Section 2.1.5`_ and `Appendix A: MultiLineString`_ within `The GeoJSON Format Specification`_.
+Visualize the result of the example above `here <https://gist.github.com/frewsxcv/20b6522d8242ede00bb3>`__. General information about MultiLineString can be found in `Section 2.1.5`_ and `Appendix A: MultiLineString`_ within `The GeoJSON Format Specification`_.
 
 .. _Section 2.1.5: http://www.geojson.org/geojson-spec.html#multilinestring
 .. _Appendix A\: MultiLineString: http://www.geojson.org/geojson-spec.html#id6
@@ -100,10 +108,18 @@ Polygon
 
   >>> from geojson import Polygon
 
-  >>> Polygon([(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15)])  # doctest: +ELLIPSIS
-  {"coordinates": [[2.3..., 57.32...], [23.19..., -20.2...], [-120.4..., 19.1...]], "type": "Polygon"}
+  >>> # no hole within polygon
+  >>> Polygon([[(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.322)]])  # doctest: +ELLIPSIS
+  {"coordinates": [[[2.3..., 57.32...], [23.19..., -20.2...], [-120.4..., 19.1...]]], "type": "Polygon"}
+
+  >>> # hole within polygon
+  >>> Polygon([
+  ...     [(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.322)],
+  ...     [(-5.21, 23.51), (15.21, -10.81), (-20.51, 1.51), (-5.21, 23.51)]
+  ... ])  # doctest: +ELLIPSIS
+  {"coordinates": [[[2.3..., 57.32...], [23.19..., -20.2...], [-120.4..., 19.1...]], [[-5.2..., 23.5...], [15.2..., -10.8...], [-20.5..., 1.5...], [-5.2..., 23.5...]]], "type": "Polygon"}
 
-General information about Polygon can be found in `Section 2.1.6`_ and `Appendix A: Polygon`_ within `The GeoJSON Format Specification`_.
+Visualize the results of the example above `here <https://gist.github.com/frewsxcv/b2f5c31c10e399a63679>`__. General information about Polygon can be found in `Section 2.1.6`_ and `Appendix A: Polygon`_ within `The GeoJSON Format Specification`_.
 
 .. _Section 2.1.6: http://www.geojson.org/geojson-spec.html#polygon
 .. _Appendix A\: Polygon: http://www.geojson.org/geojson-spec.html#id4
@@ -116,12 +132,12 @@ MultiPolygon
   >>> from geojson import MultiPolygon
 
   >>> MultiPolygon([
-  ...     [(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234)],
-  ...     [(23.18, -34.29), (-1.31, -4.61), (3.41, 77.91)]
+  ...     ([(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)],),
+  ...     ([(23.18, -34.29), (-1.31, -4.61), (3.41, 77.91), (23.18, -34.29)],)
   ... ])  # doctest: +ELLIPSIS
-  {"coordinates": [[[3.7..., 9.2...], [-130.9..., 1.5...], [35.1..., 72.23...]], [[23.1..., -34.2...], [-1.3..., -4.6...], [3.4..., 77.9...]]], "type": "MultiPolygon"}
+  {"coordinates": [[[[3.7..., 9.2...], [-130.9..., 1.5...], [35.1..., 72.23...]]], [[[23.1..., -34.2...], [-1.3..., -4.6...], [3.4..., 77.9...]]]], "type": "MultiPolygon"}
 
-General information about MultiPolygon can be found in `Section 2.1.7`_ and `Appendix A: MultiPolygon`_ within `The GeoJSON Format Specification`_.
+Visualize the result of the example above `here <https://gist.github.com/frewsxcv/e0388485e28392870b74>`__. General information about MultiPolygon can be found in `Section 2.1.7`_ and `Appendix A: MultiPolygon`_ within `The GeoJSON Format Specification`_.
 
 .. _Section 2.1.7: http://www.geojson.org/geojson-spec.html#multipolygon
 .. _Appendix A\: MultiPolygon: http://www.geojson.org/geojson-spec.html#id7
@@ -140,9 +156,9 @@ GeometryCollection
   >>> GeometryCollection([my_point, my_line])  # doctest: +ELLIPSIS
   {"geometries": [{"coordinates": [23.53..., -63.1...], "type": "Point"}, {"coordinates": [[-152.6..., 51.2...], [5.2..., 10.6...]], "type": "LineString"}], "type": "GeometryCollection"}
 
-General information about GeometryCollection can be found in `Section 2.1.8`_ and `Appendix A: GeometryCollection`_ within `The GeoJSON Format Specification`_.
+Visualize the result of the example above `here <https://gist.github.com/frewsxcv/6ec8422e97d338a101b0>`__. General information about GeometryCollection can be found in `Section 2.1.8`_ and `Appendix A: GeometryCollection`_ within `The GeoJSON Format Specification`_.
 
-.. _Section 2.1.8: http://www.geojson.org/geojson-spec.html#geometrycollection
+.. _Section 2.1.8: http://www.geojson.org/geojson-spec.html#geometry-collection
 .. _Appendix A\: GeometryCollection: http://www.geojson.org/geojson-spec.html#geometrycollection
 
 Feature
@@ -152,18 +168,18 @@ Feature
 
   >>> from geojson import Feature, Point
 
-  >>> my_point = Point((43.24, -1.532))
+  >>> my_point = Point((-3.68, 40.41))
 
   >>> Feature(geometry=my_point)  # doctest: +ELLIPSIS
-  {"geometry": {"coordinates": [43.2..., -1.53...], "type": "Point"}, "id": null, "properties": {}, "type": "Feature"}
+  {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "id": null, "properties": {}, "type": "Feature"}
 
   >>> Feature(geometry=my_point, properties={"country": "Spain"})  # doctest: +ELLIPSIS
-  {"geometry": {"coordinates": [43.2..., -1.53...], "type": "Point"}, "id": null, "properties": {"country": "Spain"}, "type": "Feature"}
+  {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "id": null, "properties": {"country": "Spain"}, "type": "Feature"}
 
   >>> Feature(geometry=my_point, id=27)  # doctest: +ELLIPSIS
-  {"geometry": {"coordinates": [43.2..., -1.53...], "type": "Point"}, "id": 27, "properties": {}, "type": "Feature"}
+  {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "id": 27, "properties": {}, "type": "Feature"}
 
-General information about Feature can be found in `Section 2.2`_ within `The GeoJSON Format Specification`_.
+Visualize the results of the examples above `here <https://gist.github.com/frewsxcv/4488d30209d22685c075>`__. General information about Feature can be found in `Section 2.2`_ within `The GeoJSON Format Specification`_.
 
 .. _Section 2.2: http://geojson.org/geojson-spec.html#feature-objects
 
@@ -181,14 +197,14 @@ FeatureCollection
   >>> FeatureCollection([my_feature, my_other_feature])  # doctest: +ELLIPSIS
   {"features": [{"geometry": {"coordinates": [1.643..., -19.12...], "type": "Point"}, "id": null, "properties": {}, "type": "Feature"}, {"geometry": {"coordinates": [-80.23..., -22.53...], "type": "Point"}, "id": null, "properties": {}, "type": "Feature"}], "type": "FeatureCollection"}
 
-General information about FeatureCollection can be found in `Section 2.3`_ within `The GeoJSON Format Specification`_.
+Visualize the result of the example above `here <https://gist.github.com/frewsxcv/34513be6fb492771ef7b>`__. General information about FeatureCollection can be found in `Section 2.3`_ within `The GeoJSON Format Specification`_.
 
 .. _Section 2.3: http://geojson.org/geojson-spec.html#feature-collection-objects
 
 GeoJSON encoding/decoding
 -------------------------
 
-All of the GeoJSON Objects implemented in this library can be encoded and decoded into raw GeoJSON with the ``geosjon.dump``, ``geojson.dumps``, ``geojson.load``, and ``geojson.loads`` functions.
+All of the GeoJSON Objects implemented in this library can be encoded and decoded into raw GeoJSON with the ``geojson.dump``, ``geojson.dumps``, ``geojson.load``, and ``geojson.loads`` functions.
 
 .. code:: python
 
@@ -230,6 +246,44 @@ This encoding/decoding functionality shown in the previous can be extended to cu
   >>> geojson.dumps(point_instance, sort_keys=True)  # doctest: +ELLIPSIS
   '{"coordinates": [52.23..., -19.23...], "type": "Point"}'
 
+Helpful utilities
+-----------------
+
+coords
+~~~~~~
+
+:code:`geojson.utils.coords` yields all coordinate tuples from a geometry or feature object.
+
+.. code:: python
+
+  >>> import geojson
+
+  >>> my_line = LineString([(-152.62, 51.21), (5.21, 10.69)])
+
+  >>> my_feature = geojson.Feature(geometry=my_line)
+
+  >>> list(geojson.utils.coords(my_feature))  # doctest: +ELLIPSIS
+  [(-152.62..., 51.21...), (5.21..., 10.69...)]
+
+map_coords
+~~~~~~~~~~
+
+:code:`geojson.utils.map_coords` maps a function over all coordinate tuples and returns a geometry of the same type. Useful for translating a geometry in space or flipping coordinate order.
+
+.. code:: python
+
+  >>> import geojson
+
+  >>> new_point = geojson.utils.map_coords(lambda x: x/2, geojson.Point((-115.81, 37.24)))
+
+  >>> geojson.dumps(new_point, sort_keys=True)  # doctest: +ELLIPSIS
+  '{"coordinates": [-57.905..., 18.62...], "type": "Point"}'
+
+Development
+-----------
+
+To build this project, run :code:`python setup.py build`. To run the unit tests, run :code:`python setup.py test`.
+
 Credits
 -------
 
diff --git a/geojson/__init__.py b/geojson/__init__.py
index 7e8d5f7..4de7ca0 100644
--- a/geojson/__init__.py
+++ b/geojson/__init__.py
@@ -1,4 +1,5 @@
 from geojson.codec import dump, dumps, load, loads, GeoJSONEncoder
+from geojson.utils import coords, map_coords
 from geojson.geometry import Point, LineString, Polygon
 from geojson.geometry import MultiLineString, MultiPoint, MultiPolygon
 from geojson.geometry import GeometryCollection
diff --git a/geojson/base.py b/geojson/base.py
index fa27f9a..1bd69dd 100644
--- a/geojson/base.py
+++ b/geojson/base.py
@@ -15,16 +15,24 @@ class GeoJSON(dict):
     __str__ = __repr__
 
     def __setattr__(self, name, value):
+        """
+        Permit dictionary items to be set like object attributes
+        """
         self[name] = value
 
     def __getattr__(self, name):
+        """
+        Permit dictionary items to be retrieved like object attributes
+        """
         try:
-            v = self[name]
+            return self[name]
         except KeyError:
             raise AttributeError(name)
-        return v
 
     def __delattr__(self, name):
+        """
+        Permit dictionary items to be deleted like object attributes
+        """
         del self[name]
 
     @property
@@ -44,9 +52,23 @@ class GeoJSON(dict):
             instance = ob
         else:
             mapping = to_mapping(ob)
-            d = dict((str(k), mapping[k]) for k in mapping)
+            d = {}
+            for k in mapping:
+                try:
+                    str_key = str(k)
+                except (UnicodeEncodeError):
+                    str_key = unicode(k)
+                d[str_key] = mapping[k]
             try:
                 type_ = d.pop("type")
+                try:
+                    type_ = str(type_)
+                except (UnicodeEncodeError):
+                    # If the type contains non-ascii characters, we can assume
+                    # it's not a valid GeoJSON type
+                    raise AttributeError(
+                        unicode("{0} is not a GeoJSON type").format(
+                        unicode(type_)))
                 geojson_factory = getattr(geojson.factory, type_)
                 if not issubclass(geojson_factory, GeoJSON):
                     raise TypeError("""\
diff --git a/geojson/codec.py b/geojson/codec.py
index acae28f..eb8808b 100644
--- a/geojson/codec.py
+++ b/geojson/codec.py
@@ -1,4 +1,7 @@
-import json
+try:
+    import simplejson as json
+except ImportError:
+    import json
 
 import geojson
 import geojson.factory
diff --git a/geojson/geometry.py b/geojson/geometry.py
index eb19f0f..22ea24a 100644
--- a/geojson/geometry.py
+++ b/geojson/geometry.py
@@ -1,4 +1,5 @@
 from decimal import Decimal
+
 from geojson.base import GeoJSON
 
 
@@ -13,13 +14,13 @@ class Geometry(GeoJSON):
         if crs:
             self["crs"] = self.to_instance(crs, strict=True)
 
-    def clean_coordinates(self, coords):
+    @classmethod
+    def clean_coordinates(cls, coords):
         for coord in coords:
             if isinstance(coord, (list, tuple)):
-                self.clean_coordinates(coord)
-            else:
-                if not isinstance(coord, (float, int, Decimal)):
-                    raise ValueError("%r is not JSON compliant number", coord)
+                cls.clean_coordinates(coord)
+            elif not isinstance(coord, (float, int, Decimal)):
+                raise ValueError("%r is not JSON compliant number" % coord)
 
 
 class GeometryCollection(GeoJSON):
diff --git a/geojson/mapping.py b/geojson/mapping.py
index d0057d5..fb24c33 100644
--- a/geojson/mapping.py
+++ b/geojson/mapping.py
@@ -1,23 +1,32 @@
-import sys
+from collections import MutableMapping
+try:
+    import simplejson as json
+except ImportError:
+    import json
+
 import geojson
 
-import json
-from collections import MutableMapping
-is_mapping = lambda obj: isinstance(obj, MutableMapping)
+
 mapping_base = MutableMapping
 
 
 GEO_INTERFACE_MARKER = "__geo_interface__"
 
 
+def is_mapping(obj):
+    return isinstance(obj, MutableMapping)
+
+
 def to_mapping(obj):
     mapping = getattr(obj, GEO_INTERFACE_MARKER, None)
-    if mapping is None:
-        if is_mapping(obj):
-            mapping = obj
-        else:
-            if isinstance(obj, geojson.GeoJSON):
-                mapping = dict(obj)
-            else:
-                mapping = json.loads(json.dumps(obj))
-    return mapping
+
+    if mapping is not None:
+        return mapping
+
+    if is_mapping(obj):
+        return obj
+
+    if isinstance(obj, geojson.GeoJSON):
+        return dict(obj)
+
+    return json.loads(json.dumps(obj))
diff --git a/geojson/utils.py b/geojson/utils.py
new file mode 100644
index 0000000..068b1c3
--- /dev/null
+++ b/geojson/utils.py
@@ -0,0 +1,35 @@
+"""Coordinate utility functions."""
+
+def coords(obj):
+    """Yield all coordinate coordinate tuples from a geometry or feature."""
+    if isinstance(obj, (tuple, list)):
+        coordinates = obj
+    elif 'geometry' in obj:
+        coordinates = obj['geometry']['coordinates']
+    else:
+        coordinates = obj.get('coordinates', obj)
+    for e in coordinates:
+        if isinstance(e, (float, int)):
+            yield tuple(coordinates)
+            break
+        for f in coords(e):
+            yield f
+
+def map_coords(func, obj):
+    """Return coordinates, mapped pair-wise using the provided function."""
+    if obj['type'] == 'Point':
+        coordinates = tuple(map(func, obj['coordinates']))
+    elif obj['type'] in ['LineString', 'MultiPoint']:
+        coordinates = [tuple(map(func, c)) for c in obj['coordinates']]
+    elif obj['type'] in ['MultiLineString', 'Polygon']:
+        coordinates = [[
+            tuple(map(func, c)) for c in curve]
+                for curve in obj['coordinates']]
+    elif obj['type'] == 'MultiPolygon':
+        coordinates = [[[
+            tuple(map(func, c)) for c in curve]
+                for curve in part]
+                    for part in obj['coordinates']]
+    else:
+        raise ValueError("Invalid geometry object %s" % repr(obj))
+    return {'type': obj['type'], 'coordinates': coordinates}
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index f7d070c..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,10 +0,0 @@
-[nosetests]
-detailed-errors=TRUE
-with-coverage=TRUE
-cover-package=geojson
-cover-erase=TRUE
-with-doctest=TRUE
-doctest-extension=.rst
-
-# pdb=TRUE
-# pdb-failures=TRUE
diff --git a/setup.py b/setup.py
index fb7d28f..1ef9588 100644
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,21 @@
-import sys
 import io
 from setuptools import setup
+import sys
+
 
+with io.open("README.rst") as readme_file:
+    readme_text = readme_file.read()
 
-readme_text = io.open("README.rst", "r").read()
+def test_suite():
+    import doctest
+    try:
+        import unittest2 as unittest
+    except:
+        import unittest
+
+    suite = unittest.TestLoader().discover("tests")
+    suite.addTest(doctest.DocFileSuite("README.rst"))
+    return suite
 
 if sys.version_info[:2] not in [(2, 6), (2, 7)] and \
         sys.version_info[:1] not in [(3, )]:
@@ -11,13 +23,17 @@ if sys.version_info[:2] not in [(2, 6), (2, 7)] and \
                      "at this time.\n")
     exit(1)
 
+tests_require = []
+if sys.version_info[:2] == (2, 6):
+    tests_require.append("unittest2")
+
 # Get around this issue: http://bugs.python.org/issue15881
 # Appears to be a problem in older versions of Python 2.6 and 2.7
 import multiprocessing  # NOQA
 
 setup(
     name="geojson",
-    version="1.0.4",
+    version="1.0.9",
     description="Python bindings and utilities for GeoJSON",
     license="BSD",
     keywords="gis geography json",
@@ -25,15 +41,14 @@ setup(
     author_email="sgillies at frii.com",
     maintainer="Corey Farwell",
     maintainer_email="coreyf at rwell.org",
-    url="https://github.com/frewsxcv/geojson",
+    url="https://github.com/frewsxcv/python-geojson",
     long_description=readme_text,
     packages=["geojson"],
     package_dir={"geojson": "geojson"},
     package_data={"geojson": ["*.rst"]},
-    setup_requires=["nose==1.3.0"],
-    tests_require=["nose==1.3.0", "coverage==3.6"],
     install_requires=["setuptools"],
-    test_suite="nose.collector",
+    test_suite="setup.test_suite",
+    tests_require=tests_require,
     classifiers=[
         "Development Status :: 5 - Production/Stable",
         "Intended Audience :: Developers",
@@ -41,6 +56,13 @@ setup(
         "License :: OSI Approved :: BSD License",
         "Operating System :: OS Independent",
         "Programming Language :: Python",
+        "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 2.6",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.2",
+        "Programming Language :: Python :: 3.3",
+        "Programming Language :: Python :: 3.4",
         "Topic :: Scientific/Engineering :: GIS",
     ]
 )
diff --git a/tests/README.txt b/tests/README.txt
deleted file mode 100644
index 8cc546c..0000000
--- a/tests/README.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-
-To run the standard test suite:
-
-$python[version] setup.py nosetests
-
-Change options in setup.cfg under 'nosetests' as per nose documentation in order
-to add/change the test suite behaviour.
-
-For backwards compatiability, the command:
-
-$ python setup.py test
-
-will still work and run all doctests defined.
-
-
diff --git a/tests/blog.txt b/tests/blog.txt
deleted file mode 100644
index 616bc5e..0000000
--- a/tests/blog.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-Tests of the feature protocol embedded in an arbitrary text document
-====================================================================
-    >>> import geojson
-
-    >>> d = {"blog": {"posts": [{"atom:summary": "post 1", "type": "atom:item", "atom:description": "i love blogging"}, {"atom:summary": "post 2 from CA", "type": "atom:item", "location": {"type": "Point", "coordinates": [-120.2138, 40.1231]}, "atom:description": "geoblogging in California"}], "location": {"type": "Polygon", "coordinates": [[[-121.5627, 39.8173], [-119.5221, 39.8173], [-119.1232, 41.1231], [-121.5632, 41.3321], [-121.2156, 39.8103]]]}}}
-
-    Encoding
-
-    >>> text = geojson.dumps(d, sort_keys=True)
-    >>> text  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE 
-    '{"blog": {"location": {"coordinates": [[[-121..., 39...], [-119..., 39...], [-119..., 41...], [-121..., 41...], [-121..., 39...]]], "type": "Polygon"}, "posts": [{"atom:description": "i love blogging", "atom:summary": "post 1", "type": "atom:item"}, {"atom:description": "geoblogging in California", "atom:summary": "post 2 from CA", "location": {"coordinates": [-120..., 40...], "type": "Point"}, "type": "atom:item"}]}}'
-
-   Decoding
-
-   >>> o = geojson.loads(text)
-   >>> isinstance(o, dict)
-   True
-
-   >>> geojson.dumps(o, sort_keys=True)# doctest: +ELLIPSIS 
-   '{"blog": {"location": {"coordinates": [[[-121..., 39...], [-119..., 39...], [-119..., 41...], [-121..., 41...], [-121..., 39...]]], "type": "Polygon"}, "posts": [{"atom:description": "i love blogging", "atom:summary": "post 1", "type": "atom:item"}, {"atom:description": "geoblogging in California", "atom:summary": "post 2 from CA", "location": {"coordinates": [-120..., 40...], "type": "Point"}, "type": "atom:item"}]}}'
-
diff --git a/tests/c2c_features.txt b/tests/c2c_features.txt
deleted file mode 100644
index b210ed1..0000000
--- a/tests/c2c_features.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-Test c2c features
-=================
-
-  >>> class Feature(object):
-  ...     def __init__(self, id, geometry, **props):
-  ...         self.id = id
-  ...         self.geometry = geometry
-  ...         self.properties = {}
-  ...         for key, value in props.items():
-  ...             self.properties[key] = value
-  ...
-  ...     @property
-  ...     def __geo_interface__(self):
-  ...         return {
-  ...                'type': 'Feature',
-  ...                'id': self.id,
-  ...                'geometry': self.geometry,
-  ...                'properties': self.properties
-  ...                }
-
-  >>> class FeatureCollection(list):
-  ...     @property
-  ...     def __geo_interface__(self):
-  ...         return {
-  ...                'type': 'FeatureCollection',
-  ...                'features': list(f for f in self)
-  ...                }
-
-  >>> class Point(object):
-  ...    """Mock shapely point."""
-  ...    def __init__(self, x, y):
-  ...        self.x = x; self.y = y
-  ...    @property
-  ...    def __geo_interface__(self):
-  ...        return dict(type="Point", coordinates=[self.x, self.y])
-
-  >>> f = Feature(12, Point(49.132323, 55.341411), foo='bar')
-  >>> import geojson
-  >>> geojson.dumps(f, sort_keys=True) # doctest: +ELLIPSIS 
-  '{"geometry": {"coordinates": [49..., 55...], "type": "Point"}, "id": 12, "properties": {"foo": "bar"}, "type": "Feature"}'
-
-  >>> features = [f]
-  >>> c = FeatureCollection(features)
-  >>> geojson.dumps(c, sort_keys=True) # doctest: +ELLIPSIS
-  '{"features": [{"geometry": {"coordinates": [49..., 55...], "type": "Point"}, "id": 12, "properties": {"foo": "bar"}, "type": "Feature"}], "type": "FeatureCollection"}'
diff --git a/tests/crs.txt b/tests/crs.txt
deleted file mode 100644
index bd1f9da..0000000
--- a/tests/crs.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-CRS annotation
---------------
-
-   >>> import geojson
-   >>> import geojson.crs
-
-   By default, no CRS information is present. One should assume WGS84.
-   >>> geojson.Point([0.0, 0.0])
-   {"coordinates": [0.0, 0.0], "type": "Point"}
-
-   Including crs
-   >>> crs = geojson.crs.Named(properties=dict(name="urn:ogc:def:crs:EPSG::3785"))
-   >>> point = geojson.Point([0.0, 0.0], crs=crs)
-   >>> point
-   {"coordinates": [0.0, 0.0], "crs": {"properties": {"name": "urn:ogc:def:crs:EPSG::3785"}, "type": "name"}, "type": "Point"}
-
-   >>> geojson.dumps(point, sort_keys=True)
-   '{"coordinates": [0.0, 0.0], "crs": {"properties": {"name": "urn:ogc:def:crs:EPSG::3785"}, "type": "name"}, "type": "Point"}'
-
-
diff --git a/tests/data.geojson b/tests/data.geojson
new file mode 100644
index 0000000..f78bd62
--- /dev/null
+++ b/tests/data.geojson
@@ -0,0 +1,7 @@
+{
+   "properties": {
+      "Ã": "Ã"
+   }, 
+   "type": "Feature",
+   "geometry": null
+}
diff --git a/tests/fallsthrough_to_json.txt b/tests/fallsthrough_to_json.txt
deleted file mode 100644
index ec58c23..0000000
--- a/tests/fallsthrough_to_json.txt
+++ /dev/null
@@ -1,47 +0,0 @@
-Falling through to JSON
------------------------
-
-GeoJSON is a super set of JSON.
-Thus this test is a demonstration and regression test of what happens when you pass 
-geojson something that isn't GeoJSON.
-
-   >>> import geojson
-   >>> d = {"plain": ["old", "dict"]}
-   >>> geojson.dumps(d, sort_keys=True)
-   '{"plain": ["old", "dict"]}'
-
-   If you have 'type' member, then geojson will niavely assume this is GeoJSON
-   >>> d = {"type": "SomethingButNotGeo", "items": [1, 2, 3]}
-   >>> json = geojson.dumps(d, sort_keys=True)
-   >>> json
-   '{"items": [1, 2, 3], "type": "SomethingButNotGeo"}'
-
-   But in this case, "SomethingButNotGeo" isn't a GeoJSON type, so it is ignored.
-
-   This is mirrored by loading a round trip::
-
-   >>> obj = geojson.loads(json)
-   >>> geojson.dumps(obj, sort_keys=True)
-   '{"items": [1, 2, 3], "type": "SomethingButNotGeo"}'
-
-   >>> geojson.dumps(1)
-   '1'
-
-   >>> geojson.dumps("string")
-   '"string"'
-
-   >>> geojson.dumps([1, 2, 3])
-   '[1, 2, 3]'
-   
-   Something that gets recognised as GeoJSON, but isn't valid
-   >>> d = {"type": "Point", "members": [1, 2, 3]}
-
-   >>> json = geojson.dumps(d, sort_keys=True)
-   >>> json
-   '{"members": [1, 2, 3], "type": "Point"}'
-
-   Trying to use geojson.loads with something that looks like GeoJSON
-   will result in your returned JSON having the GeoJSON members::
-
-   >>> geojson.loads(json)
-   {"coordinates": [], "members": [1, 2, 3], "type": "Point"}
diff --git a/tests/feature-null-geom.txt b/tests/feature-null-geom.txt
deleted file mode 100644
index 498adef..0000000
--- a/tests/feature-null-geom.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-Test dump of null geometry features
-
-  >>> import geojson 
-  >>> geojson.dumps(geojson.Feature(id=12, geometry=None, properties={'foo': 'bar'}), sort_keys=True) # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE
-  '{"geometry": null, "id": 12, "properties": {"foo": "bar"}, "type": "Feature"}'
-
-  >>> geojson.dumps(geojson.Feature(id=12, properties={'foo': 'bar'}), sort_keys=True) # doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE
-  '{"geometry": null, "id": 12, "properties": {"foo": "bar"}, "type": "Feature"}'
diff --git a/tests/featurecollection.txt b/tests/featurecollection.txt
deleted file mode 100644
index a6af2ce..0000000
--- a/tests/featurecollection.txt
+++ /dev/null
@@ -1,84 +0,0 @@
-
-Tests of the Feature collections protocol
------------------------------------------
-
-A dictionary can satisfy the protocol
--------------------------------------
-
-  >>> fc = {"type": "FeatureCollection",
-  ...       "features": [{"type": "Feature",
-  ...                      "id": "1",
-  ...                      "geometry": {"type": "Point",
-  ...                                   "coordinates": [44.556, 67.192]}}]}
-
-
-  Encoding
-
-  >>> import geojson
-  >>> json = geojson.dumps(fc, sort_keys=True)
-  >>> json  # doctest: +ELLIPSIS
-  '{"features": [{"geometry": {"coordinates": [44..., 67...], "type": "Point"}, "id": "1", "type": "Feature"}], "type": "FeatureCollection"}'
-
-  Decoding
-  
-  >>> fcb = geojson.loads(json)
-  >>> geojson.dumps(fcb, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"features": [{"geometry": {"coordinates": [44..., 67...], "type": "Point"}, "id": "1", "properties": {}, "type": "Feature"}], "type": "FeatureCollection"}'
-
-
-
-GeoJSON types thmeselves satisfy the protocol (of course!)
------------------------------------------------------------
-  
-  >>> features = [geojson.Feature(id=1, geometry=geojson.Point(coordinates=(53.04781795911469, -4.10888671875)))]
-  >>> fco = geojson.FeatureCollection(features)
-  >>> fco.features  # doctest: +ELLIPSIS
-  [{"geometry": {"coordinates": [53..., -4...], "type": "Point"}, "id": 1, "properties": {}, "type": "Feature"}]
-
-
-  Encoding
-
-  >>> json = geojson.dumps(fco, sort_keys=True)
-  >>> json  # doctest: +ELLIPSIS
-  '{"features": [{"geometry": {"coordinates": [53..., -4...], "type": "Point"}, "id": 1, "properties": {}, "type": "Feature"}], "type": "FeatureCollection"}'
-
-   and can encode back into a instnace of the same kind, or supply your own hook:
-
-  >>> hook = lambda ob: geojson.GeoJSON.to_instance(ob, geojson.feature)
-  >>> fc = geojson.loads(json, object_hook=hook)
-  >>> fc.features  # doctest: +ELLIPSIS
-  [{"geometry": {"coordinates": [53..., -4...], "type": "Point"}, "id": 1, "properties": {}, "type": "Feature"}]
-  
-  >>> fc.features[0].geometry  # doctest: +ELLIPSIS
-  {"coordinates": [53..., -4...], "type": "Point"}
-
-  >>> len(fc["features"])
-  1
-
-  >>> len(fc.features)
-  1
-
-  Convert GeoJSON to regular dict
-  >>> dfc = dict(fc)
-  >>> dfc["type"] == "FeatureCollection"
-  True
-
-  >>> geometry = fc.features[0].geometry
-  >>> geometry.coordinates  # doctest: +ELLIPSIS
-  [53..., -4...]
- 
-
-- It may be used from any object, consider:
-
-  >>> class Point(object):
-  ...     """Mock shapely point."""
-  ...     def __init__(self, x, y):
-  ...         self.x = x
-  ...         self.y = y
-  ...     @property
-  ...     def __geo_interface__(self):
-  ...         return geojson.Point(coordinates=[self.x, self.y])
-
-  >>> p = Point(53.04781795911469, -4.10888671875)
-  >>> geojson.dumps(p, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"coordinates": [53..., -4...], "type": "Point"}'
diff --git a/tests/features.txt b/tests/features.txt
deleted file mode 100644
index fa828d7..0000000
--- a/tests/features.txt
+++ /dev/null
@@ -1,91 +0,0 @@
-Tests of the feature protocol
-=============================
-
-A dictionary can satisfy the protocol
--------------------------------------
-
-  >>> f = {
-  ...   'type': 'Feature',
-  ...   'id': '1',
-  ...   'geometry': {'type': 'Point', 'coordinates': [53.04781795911469, -4.10888671875]},
-  ...   'properties': {'title': 'Dict 1'},
-  ... }
-
-  >>> import geojson
-
-  Encoding
-
-  >>> json = geojson.dumps(f, sort_keys=True)
-  >>> json  # doctest: +ELLIPSIS
-  '{"geometry": {"coordinates": [53..., -4...], "type": "Point"}, "id": "1", "properties": {"title": "Dict 1"}, "type": "Feature"}'
-
-  Decoding
-
-  >>> o = geojson.loads(json)
-  >>> geojson.dumps(o, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"geometry": {"coordinates": [53..., -4...], "type": "Point"}, "id": "1", "properties": {"title": "Dict 1"}, "type": "Feature"}'
-
-
-feature class
----------------------
-
-  >>> from geojson.examples import SimpleWebFeature
-  >>> feature = SimpleWebFeature(id='1',
-  ...             geometry={'type': 'Point', 'coordinates': [53.04781795911469, -4.10888671875]},
-  ...             title='Feature 1', summary='The first feature',
-  ...             link='http://example.org/features/1')
-
-  It satisfies the feature protocol
-
-  >>> feature.id
-  '1'
-  >>> print(feature.properties['title'])
-  Feature 1
-  >>> print(feature.properties['summary'])
-  The first feature
-  >>> feature.properties['link']
-  'http://example.org/features/1'
-  >>> geojson.dumps(feature.geometry, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"coordinates": [53..., -4...], "type": "Point"}'
-
-  Encoding
-
-  >>> geojson.dumps(feature, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"geometry": {"coordinates": [53..., -4...], "type": "Point"}, "id": "1", "properties": {"link": "http://example.org/features/1", "summary": "The first feature", "title": "Feature 1"}, "type": "Feature"}'
-
-  Decoding
-
-  >>> factory = geojson.examples.createSimpleWebFeature 
-  >>> json = '{"geometry": {"type": "Point", "coordinates": [53.04781795911469, -4.10888671875]}, "id": "1", "properties": {"summary": "The first feature", "link": "http://example.org/features/1", "title": "Feature 1"}}'
-  >>> feature = geojson.loads(json, object_hook=factory, encoding="utf-8")
-  >>> type(feature)
-  <class 'geojson.examples.SimpleWebFeature'>
-  >>> feature.id
-  '1'
-  >>> print(feature.properties['title'])
-  Feature 1
-  >>> print(feature.properties['summary'])
-  The first feature
-  >>> feature.properties['link']
-  'http://example.org/features/1'
-  >>> geojson.dumps(feature.geometry, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"coordinates": [53..., -4...], "type": "Point"}'
-
-Test the geo interface
-----------------------
-
-  >>> class Thingy(object):
-  ...     def __init__(self, id, title, x, y):
-  ...         self.id = id
-  ...         self.title = title
-  ...         self.x = x
-  ...         self.y = y
-  ...     @property
-  ...     def __geo_interface__(self):
-  ...        return {"id": self.id, "properties": {"title": self.title}, "geometry": {"type": "Point", "coordinates": (self.x, self.y)}}
-
-  >>> ob = Thingy('1', 'thingy one', -106.0, 40.0)
-  >>> geojson.dumps(ob.__geo_interface__['geometry'], sort_keys=True)  # doctest: +ELLIPSIS
-  '{"coordinates": [-106..., 40...], "type": "Point"}'
-  >>> geojson.dumps(ob, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"geometry": {"coordinates": [-106..., 40...], "type": "Point"}, "id": "1", "properties": {"title": "thingy one"}}'
diff --git a/tests/geometry.txt b/tests/geometry.txt
deleted file mode 100644
index 4cc613b..0000000
--- a/tests/geometry.txt
+++ /dev/null
@@ -1,122 +0,0 @@
-
-Tests of the geometry protocol
-==============================
-
-A dictionary can satisfy the protocol
--------------------------------------
-
-  >>> g = {
-  ...    "type": "Point",
-  ...    "coordinates": [1.3, -54.23242]
-  ... }
-
-  >>> import geojson
-  >>> import geojson.geometry
-
-  Encoding
-
-  >>> json = geojson.dumps(g, sort_keys=True)
-  >>> json # doctest: +ELLIPSIS
-  '{"coordinates": [1..., -54...], "type": "Point"}'
-
-  Decoding
-
-  >>> o = geojson.loads(json)
-  >>> geojson.dumps(o, sort_keys=True) # doctest: +ELLIPSIS
-  '{"coordinates": [1..., -54...], "type": "Point"}'
-
-
-geometry class
---------------
-
-  >>> ls = geojson.geometry.LineString(((52.1, -34.131), (65.231, -34.234)))
-
-  >>> isinstance(ls.__geo_interface__, geojson.GeoJSON) # doctest: +ELLIPSIS
-  True
-
-  >>> geojson.dumps(ls, sort_keys=True) # doctest: +ELLIPSIS
-  '{"coordinates": [[52..., -34...], [65..., -34...]], "type": "LineString"}'
-
-  >>> ls.type
-  'LineString'
-  >>> ls.coordinates # doctest: +ELLIPSIS
-  ((52..., -34...), (65..., -34...))
-
-  Encoding
-
-  >>> json = geojson.dumps(ls, sort_keys=True)
-  >>> json # doctest: +ELLIPSIS
-  '{"coordinates": [[52..., -34...], [65..., -34...]], "type": "LineString"}'
-
-  Decoding
-  >>> obj = geojson.loads(json) 
-  >>> type(obj)
-  <class 'geojson.geometry.LineString'>
-
-  >>> geojson.dumps(obj, sort_keys=True) # doctest: +ELLIPSIS
-  '{"coordinates": [[52..., -34.131], [65..., -34...]], "type": "LineString"}'
-
-  >>> factory = lambda o: geojson.GeoJSON.to_instance(o, geojson.geometry)
-  >>> geom = geojson.loads(json, object_hook=factory)
-  >>> type(geom)
-  <class 'geojson.geometry.LineString'>
-  >>> geom.type
-  'LineString'
-  >>> geom.coordinates # doctest: +ELLIPSIS
-  [[52..., -34...], [65..., -34...]]
-
-
-  Test custom crs 
- 
-  >>> from geojson.crs import Named
-
-  >>> coords = ((-1918145.0108183471, -4098018.9166399641), (-680004.67204747663, -3864394.3196185972))
-
-  >>> ls = geojson.geometry.LineString(coords, crs=Named(properties=dict(name="EPSG:4326")))
-
-  >>> isinstance(ls, geojson.GeoJSON) and hasattr(ls, "__geo_interface__")
-  True
-
-  >>> ls # doctest: +ELLIPSIS
-  {"coordinates": [[-1918145..., -4098018...], [-680004..., -3864394...]], "crs": {"properties": {"name": "EPSG:4326"}, "type": "name"}, "type": "LineString"}
-
-  It satisfies the geometry protocol
-
-  >>> json = geojson.dumps(ls, sort_keys=True)
-  >>> json # doctest: +ELLIPSIS
-  '{"coordinates": [[-1918145..., -4098018...], [-680004..., -3864394...]], "crs": {"properties": {"name": "EPSG:4326"}, "type": "name"}, "type": "LineString"}'
-
-  Decoding
-  >>> obj = geojson.loads(json) 
-  >>> type(obj)
-  <class 'geojson.geometry.LineString'>
-
-  >>> geojson.dumps(obj, sort_keys=True) # doctest: +ELLIPSIS
-  '{"coordinates": [[-1918145..., -4098018...], [-680004..., -3864394...]], "crs": {"properties": {"name": "EPSG:4326"}, "type": "name"}, "type": "LineString"}'
- 
-
-  >>> factory = lambda o: geojson.GeoJSON.to_instance(o, geojson.geometry)
-  >>> geom = geojson.loads(json, object_hook=factory)
-  >>> type(geom)
-  <class 'geojson.geometry.LineString'>
-  >>> geom.type
-  'LineString'
-  >>> geom.coordinates # doctest: +ELLIPSIS
-  [[-1918145..., -4098018...], [-680004..., -3864394...]]
- 
-Test the geo interface
-----------------------
-
-  >>> class PointThingy(object):
-  ...     def __init__(self, x, y):
-  ...         self.x = x
-  ...         self.y = y
-  ...     @property
-  ...     def __geo_interface__(self):
-  ...        return {"type": "Point", "coordinates": (self.x, self.y)}
-
-  >>> ob = PointThingy(-106.0, 40.0)
-  >>> ob.__geo_interface__['coordinates']  # doctest: +ELLIPSIS
-  (-106..., 40...)
-  >>> geojson.dumps(ob, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"coordinates": [-106..., 40...], "type": "Point"}'
diff --git a/tests/geometrycollection.txt b/tests/geometrycollection.txt
deleted file mode 100644
index 2bab3ee..0000000
--- a/tests/geometrycollection.txt
+++ /dev/null
@@ -1,54 +0,0 @@
-
-Tests of the Geometry collections protocol
------------------------------------------
-
-A dictionary can satisfy the protocol
--------------------------------------
-
-  >>> gc = {"type": "GeometryCollection", 
-  ...       "geometries": [{"type": "Point", "coordinates": [44.556, 67.192]}]
-  ... }
-
-  Encoding
-
-  >>> import geojson
-  >>> json = geojson.dumps(gc, sort_keys=True)
-  >>> json  # doctest: +ELLIPSIS
-  '{"geometries": [{"coordinates": [44..., 67...], "type": "Point"}], "type": "GeometryCollection"}'
-
-
-  Decoding
-  
-  >>> gcb = geojson.loads(json)
-  >>> geojson.dumps(gcb, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"geometries": [{"coordinates": [44..., 67...], "type": "Point"}], "type": "GeometryCollection"}'
-
-
-GeoJSON types thmeselves satisfy the protocol (of course!)
------------------------------------------------------------
-  
-  >>> geometries = [geojson.Point(coordinates=[53.04781795911469, -4.10888671875])]
-  >>> gc2 = geojson.GeometryCollection(geometries)
-  >>> gc2.geometries # doctest: +ELLIPSIS
-  [{"coordinates": [53..., -4...], "type": "Point"}]
-
-  Encoding
-
-  >>> json = geojson.dumps(gc2, sort_keys=True)
-  >>> json  # doctest: +ELLIPSIS
-  '{"geometries": [{"coordinates": [53..., -4...], "type": "Point"}], "type": "GeometryCollection"}'
-  
-  and can decode back into a instance of the same kind, or supply your own hook:
-
-  >>> gc = geojson.loads(json, object_hook=geojson.GeoJSON.to_instance)
-  >>> gc.geometries  # doctest: +ELLIPSIS
-  [{"coordinates": [53..., -4...], "type": "Point"}]
-  
-  >>> geometry = gc.geometries[0]  
-  >>> geometry # doctest: +ELLIPSIS
-  {"coordinates": [53..., -4...], "type": "Point"}
-
-  >>> geometry.coordinates  # doctest: +ELLIPSIS
-  [53..., -4...]
- 
-
diff --git a/tests/including_additional_members.txt b/tests/including_additional_members.txt
deleted file mode 100644
index cc6689e..0000000
--- a/tests/including_additional_members.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-
-Tests that addional members are allowed in any level of GeoJSON
-===============================================================
-
-A dictionary can satisfy the protocol
--------------------------------------
-
-  >>> f = {
-  ...     "type": "Feature",
-  ...     "geometry": {
-  ...                  "type": "LineString",
-  ...                  "coordinates": [[100.0, 0.0], [101.0, 1.0]]
-  ...     },
-  ...     "properties": {
-  ...                  "prop0": "value0",
-  ...                  "prop1": "value1"
-  ...     },
-  ...     "foo": "bar"
-  ... }
-
-  >>> import geojson
-
-  Encoding
-
-  >>> json = geojson.dumps(f, sort_keys=True)
-  
-  >>> json # doctest: +ELLIPSIS
-  '{"foo": "bar", "geometry": {"coordinates": [[100..., 0...], [101..., 1...]], "type": "LineString"}, "properties": {"prop0": "value0", "prop1": "value1"}, "type": "Feature"}'
-
-  Decoding
-
-  >>> o = geojson.loads(json)
-  >>> type(o)
-  <class 'geojson.feature.Feature'>
-
-  >>> o # doctest: +ELLIPSIS
-  {"foo": "bar", "geometry": {"coordinates": [[100..., 0...], [101..., 1...]], "type": "LineString"}, "id": null, "properties": {"prop0": "value0", "prop1": "value1"}, "type": "Feature"}
-
-Custom objects can be decoded into a json structure containing valid geojson
-----------------------------------------------------------------------------
-
-  >>> class Route(object):
-  ...    def __init__(self, title, description, waypoints):
-  ...        self.title = title
-  ...        self.descrpition = description
-  ...        self.waypoints = waypoints
-  ...    @property
-  ...    def __geo_interface__(self):
-  ...        return dict(type="Feature", geometry=dict(type="LineString", coordinates=self.waypoints),
-  ...                    title=self.title, description=self.descrpition)
-  ...
-
-  >>> r = Route("Snowdonia circular", "A nice bike ride around some mountains", ((1.0, 2.0), (2.0, 3.2)))
-  
-  >>> json = geojson.dumps(r, sort_keys=True)
-  
-  >>> json  # doctest: +ELLIPSIS
-  '{"description": "A nice bike ride around some mountains", "geometry": {"coordinates": [[1..., 2...], [2..., 3...]], "type": "LineString"}, "title": "Snowdonia circular", "type": "Feature"}'
-
-  >>> r = geojson.loads(json) 
-  >>> print(r["description"])
-  A nice bike ride around some mountains
-
-  >>> print(r["title"])
-  Snowdonia circular
-
-  >>> print(r["type"])
-  Feature
-
-  >>> r["geometry"]["coordinates"]# doctest: +ELLIPSIS
-  [[1..., 2...], [2..., 3...]]
-
-  >>> print(r["id"])
-  None
-
-  >>> print(r["geometry"]["type"])
-  LineString
-
-  >>> r["properties"]
-  {}
-
diff --git a/tests/objects.txt b/tests/objects.txt
deleted file mode 100644
index ccab21b..0000000
--- a/tests/objects.txt
+++ /dev/null
@@ -1,80 +0,0 @@
-Encoding objects with __geo_interface__
-------------------------------------------
-  >>> import geojson
-  >>> dumps = lambda **kwargs: geojson.dumps(sort_keys=True, **kwargs)
-
-  >>> class LatLon(object):
-  ...     
-  ...    def __init__(self, lat, lon):
-  ...        super(LatLon, self).__init__()
-  ...        self.lat = lat
-  ...        self.lon = lon
-  ... 
-  ...    @property
-  ...    def __geo_interface__(self):
-  ...        return dict(type="Point", coordinates=(self.lon, self.lat))
-
-  >>> lat_lon = LatLon(-54.1231, 4.53242)
-  
-  Can be encoded into geojson geometry:
-
-  >>> json = geojson.dumps(lat_lon, sort_keys=True)
-  >>> json # doctest: +ELLIPSIS
-  '{"coordinates": [4..., -54...], "type": "Point"}'
-
-  Objects with a __geo_interface__ attribute or property can be nested in geojson feature:
-
-  >>> f = geojson.Feature(geometry=lat_lon)
-  >>> f # doctest: +ELLIPSIS
-  {"geometry": {"coordinates": [4..., -54...], "type": "Point"}, "id": null, "properties": {}, "type": "Feature"}
-
-
-  And feature will encode:
-  >>> json = geojson.dumps(f, sort_keys=True)
-  >>> json # doctest: +ELLIPSIS
-  '{"geometry": {"coordinates": [4..., -54...], "type": "Point"}, "id": null, "properties": {}, "type": "Feature"}'
-
-  geojson types can be used to implemented a __geo_interface__:
-
-  >>> class LatLon2(LatLon):
-  ...     @property
-  ...     def __geo_interface__(self):
-  ...             return geojson.Point((self.lon, self.lat))
-  ... 
-
-
-  >>> class LatLon2(LatLon):
-  ...     @property
-  ...     def __geo_interface__(self):
-  ...         return geojson.Point((self.lon, self.lat))
-  ...     
-  ...   
-
-  >>> ll2 = LatLon2(-54.1231, 4.53242)
-  >>> json2 = geojson.dumps(ll2, sort_keys=True)
-  >>> json2 # doctest: +ELLIPSIS
-  '{"coordinates": [4..., -54...], "type": "Point"}'
-  
-  Decoding
-    - to a dict
-
-  >>> feature_dict = geojson.loads(json)
-  >>> geojson.dumps(feature_dict, sort_keys=True)  # doctest: +ELLIPSIS
-  '{"geometry": {"coordinates": [4..., -54...], "type": "Point"}, "id": null, "properties": {}, "type": "Feature"}'
-
-  - or to an object, via a factory. Here we'll create GeoJSON object.
-
-  >>> ll2 = LatLon2(43.3,-154.1) 
-  >>> json = geojson.dumps(ll2, sort_keys=True) 
-  >>> json # doctest: +ELLIPSIS
-  '{"coordinates": [-154..., 43...], "type": "Point"}'
-  
-  >>> o = geojson.loads(json) # doctest: +ELLIPSIS
-  >>> geojson.dumps(o, sort_keys=True) # doctest: +ELLIPSIS
-  '{"coordinates": [-154..., 43...], "type": "Point"}'
-  
-  >>> factory = lambda ob: geojson.GeoJSON.to_instance(ob)
-  >>> geometry = geojson.loads(json, object_hook=factory)
-  >>> geometry   # doctest: +ELLIPSIS     
-  {"coordinates": [-154..., 43...], "type": "Point"}
- 
diff --git a/tests/strict_json.txt b/tests/strict_json.txt
deleted file mode 100644
index 34c4754..0000000
--- a/tests/strict_json.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-GeoJSON enforces strict JSON
-----------------------------
-  GeoJSON produces and consumes only strict JSON.
-  NaN and Infinity are not permissible values according to the JSON specification.
-
-  >>> import geojson
-  >>> geojson.dumps({"type": "Point", "coordinates": [float("nan"), 1.0]}) # doctest: +ELLIPSIS
-  Traceback (most recent call last):
-              ...
-  ValueError:...not JSON compliant...
-
-  
-  >>> geojson.dumps({"type": "Point", "coordinates": [float("nan"), 1.0]}) # doctest: +ELLIPSIS
-  Traceback (most recent call last):
-              ...
-  ValueError:...not JSON compliant...
-
-  >>> json = '{"type": "Point", "coordinates": [1.0, NaN]}'
-  >>> geojson.loads(json) # doctest: +ELLIPSIS
-  Traceback (most recent call last):
-              ...
-  ValueError:...not JSON compliant...
-
-  >>> json = '{"type": "Point", "coordinates": [Infinity, -Infinity]}'
-  >>> geojson.loads(json) # doctest: +ELLIPSIS
-  Traceback (most recent call last):
-              ...
-  ValueError:...not JSON compliant...
- 
-  
diff --git a/tests/test_base.py b/tests/test_base.py
new file mode 100644
index 0000000..aabbfe7
--- /dev/null
+++ b/tests/test_base.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for geojson/base.py
+"""
+
+import unittest
+
+import geojson
+
+
+class TypePropertyTestCase(unittest.TestCase):
+    def test_type_property(self):
+        json_str = '{"type": "Feature", "geometry": null, "id": 1, "properties": {"type": "é"}}'
+        geojson_obj = geojson.loads(json_str)
+        self.assertTrue(isinstance(geojson_obj, geojson.GeoJSON))
+        self.assertTrue("type" in geojson_obj.properties)
+
+        json_str = '{"type": "Feature", "geometry": null, "id": 1, "properties": {"type": null}}'
+        geojson_obj = geojson.loads(json_str)
+        self.assertTrue(isinstance(geojson_obj, geojson.GeoJSON))
+        self.assertTrue("type" in geojson_obj.properties)
+
+        json_str = '{"type": "Feature", "geometry": null, "id": 1, "properties": {"type": "meow"}}'
+        geojson_obj = geojson.loads(json_str)
+        self.assertTrue(isinstance(geojson_obj, geojson.GeoJSON))
+        self.assertTrue("type" in geojson_obj.properties)
+
+
+class OperatorOverloadingTestCase(unittest.TestCase):
+    """
+    Tests for operator overloading
+    """
+
+    def setUp(self):
+        self.coords = (12, -5)
+        self.point = geojson.Point(self.coords)
+
+    def test_setattr(self):
+        new_coords = (27, 42)
+        self.point.coordinates = new_coords
+        self.assertEqual(self.point['coordinates'], new_coords)
+
+    def test_getattr(self):
+        self.assertEqual(self.point['coordinates'], self.point.coordinates)
+
+    def test_delattr(self):
+        del self.point.coordinates
+        self.assertFalse(hasattr(self.point, 'coordinates'))
diff --git a/tests/test_coords.py b/tests/test_coords.py
new file mode 100644
index 0000000..5575158
--- /dev/null
+++ b/tests/test_coords.py
@@ -0,0 +1,57 @@
+import unittest
+
+import geojson
+from geojson.utils import coords, map_coords
+
+
+class CoordsTestCase(unittest.TestCase):
+    def test_point(self):
+        itr = coords(geojson.Point((-115.81, 37.24)))
+        self.assertEqual(next(itr), (-115.81, 37.24))
+
+    def test_dict(self):
+        itr = coords({'type': 'Point', 'coordinates': [-115.81, 37.24]})
+        self.assertEqual(next(itr), (-115.81, 37.24))
+
+    def test_point_feature(self):
+        itr = coords(geojson.Feature(geometry=geojson.Point((-115.81, 37.24))))
+        self.assertEqual(next(itr), (-115.81, 37.24))
+
+    def test_multipolygon(self):
+        g = geojson.MultiPolygon([
+            ([(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)],),
+            ([(23.18, -34.29), (-1.31, -4.61), (3.41, 77.91), (23.18, -34.29)],)])
+        itr = coords(g)
+        pairs = list(itr)
+        self.assertEqual(pairs[0], (3.78, 9.28))
+        self.assertEqual(pairs[-1], (23.18, -34.29))
+
+    def test_map_point(self):
+        result = map_coords(lambda x: x, geojson.Point((-115.81, 37.24)))
+        self.assertEqual(result['type'], 'Point')
+        self.assertEqual(result['coordinates'], (-115.81, 37.24))
+
+    def test_map_linestring(self):
+        g = geojson.LineString(
+            [(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)])
+        result = map_coords(lambda x: x, g)
+        self.assertEqual(result['type'], 'LineString')
+        self.assertEqual(result['coordinates'][0], (3.78, 9.28))
+        self.assertEqual(result['coordinates'][-1], (3.78, 9.28))
+
+    def test_map_polygon(self):
+        g = geojson.Polygon([
+            [(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)],])
+        result = map_coords(lambda x: x, g)
+        self.assertEqual(result['type'], 'Polygon')
+        self.assertEqual(result['coordinates'][0][0], (3.78, 9.28))
+        self.assertEqual(result['coordinates'][0][-1], (3.78, 9.28))
+
+    def test_map_multipolygon(self):
+        g = geojson.MultiPolygon([
+            ([(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)],),
+            ([(23.18, -34.29), (-1.31, -4.61), (3.41, 77.91), (23.18, -34.29)],)])
+        result = map_coords(lambda x: x, g)
+        self.assertEqual(result['type'], 'MultiPolygon')
+        self.assertEqual(result['coordinates'][0][0][0], (3.78, 9.28))
+        self.assertEqual(result['coordinates'][-1][-1][-1], (23.18, -34.29))
diff --git a/tests/test_crs.py b/tests/test_crs.py
new file mode 100644
index 0000000..d6bcca5
--- /dev/null
+++ b/tests/test_crs.py
@@ -0,0 +1,30 @@
+import unittest
+
+import geojson
+
+
+class CRSTest(unittest.TestCase):
+
+    def setUp(self):
+        self.crs = geojson.crs.Named(
+            properties = {
+                "name": "urn:ogc:def:crs:EPSG::3785",
+            }
+        )
+
+    def test_crs_repr(self):
+        actual = repr(self.crs)
+        expected = '{"properties": {"name": "urn:ogc:def:crs:EPSG::3785"}, ' \
+                   '"type": "name"}'
+        self.assertEqual(actual, expected)
+
+    def test_crs_encode(self):
+        actual = geojson.dumps(self.crs, sort_keys=True)
+        expected = '{"properties": {"name": "urn:ogc:def:crs:EPSG::3785"}, ' \
+                   '"type": "name"}'
+        self.assertEqual(actual, expected)
+
+    def test_crs_decode(self):
+        dumped = geojson.dumps(self.crs)
+        actual = geojson.loads(dumped)
+        self.assertEqual(actual, self.crs)
\ No newline at end of file
diff --git a/tests/test_features.py b/tests/test_features.py
new file mode 100644
index 0000000..4dbf51d
--- /dev/null
+++ b/tests/test_features.py
@@ -0,0 +1,79 @@
+from io import BytesIO
+import unittest
+
+import geojson
+
+
+class FeaturesTest(unittest.TestCase):
+    def test_protocol(self):
+        """
+        A dictionary can satisfy the protocol
+        """
+        f = {
+          'type': 'Feature',
+          'id': '1',
+          'geometry': {'type': 'Point', 'coordinates': [53, -4]},
+          'properties': {'title': 'Dict 1'},
+        }
+
+        json = geojson.dumps(f, sort_keys=True)
+        self.assertEqual(json, '{"geometry": {"coordinates": [53, -4], "type": "Point"}, "id": "1", "properties": {"title": "Dict 1"}, "type": "Feature"}')
+
+        o = geojson.loads(json)
+        output = geojson.dumps(o, sort_keys=True)
+        self.assertEqual(output, '{"geometry": {"coordinates": [53, -4], "type": "Point"}, "id": "1", "properties": {"title": "Dict 1"}, "type": "Feature"}')
+
+    def test_unicode_properties(self):
+        with open("tests/data.geojson") as file_:
+            obj = geojson.load(file_)
+        geojson.dumps(obj)
+
+    def test_feature_class(self):
+        """
+        Test the Feature class
+        """
+
+        from geojson.examples import SimpleWebFeature
+        feature = SimpleWebFeature(
+            id='1',
+            geometry={'type': 'Point', 'coordinates': [53, -4]},
+            title='Feature 1', summary='The first feature',
+            link='http://example.org/features/1'
+        )
+
+        # It satisfies the feature protocol
+        self.assertEqual(feature.id, '1')
+        self.assertEqual(feature.properties['title'], 'Feature 1')
+        self.assertEqual(feature.properties['summary'], 'The first feature')
+        self.assertEqual(feature.properties['link'], 'http://example.org/features/1')
+        self.assertEqual(geojson.dumps(feature.geometry, sort_keys=True), '{"coordinates": [53, -4], "type": "Point"}')
+
+        # Encoding
+        self.assertEqual(geojson.dumps(feature, sort_keys=True), '{"geometry": {"coordinates": [53, -4], "type": "Point"}, "id": "1", "properties": {"link": "http://example.org/features/1", "summary": "The first feature", "title": "Feature 1"}, "type": "Feature"}')
+
+        # Decoding
+        factory = geojson.examples.createSimpleWebFeature
+        json = '{"geometry": {"type": "Point", "coordinates": [53, -4]}, "id": "1", "properties": {"summary": "The first feature", "link": "http://example.org/features/1", "title": "Feature 1"}}'
+        feature = geojson.loads(json, object_hook=factory, encoding="utf-8")
+        self.assertEqual(repr(type(feature)), "<class 'geojson.examples.SimpleWebFeature'>")
+        self.assertEqual(feature.id, '1')
+        self.assertEqual(feature.properties['title'], 'Feature 1')
+        self.assertEqual(feature.properties['summary'], 'The first feature')
+        self.assertEqual(feature.properties['link'], 'http://example.org/features/1')
+        self.assertEqual(geojson.dumps(feature.geometry, sort_keys=True), '{"coordinates": [53, -4], "type": "Point"}')
+
+    def test_geo_interface(self):
+        class Thingy(object):
+            def __init__(self, id, title, x, y):
+                self.id = id
+                self.title = title
+                self.x = x
+                self.y = y
+
+            @property
+            def __geo_interface__(self):
+               return {"id": self.id, "properties": {"title": self.title}, "geometry": {"type": "Point", "coordinates": (self.x, self.y)}}
+
+        ob = Thingy('1', 'thingy one', -106, 40)
+        self.assertEqual(geojson.dumps(ob.__geo_interface__['geometry'], sort_keys=True), '{"coordinates": [-106, 40], "type": "Point"}')
+        self.assertEqual(geojson.dumps(ob, sort_keys=True), '{"geometry": {"coordinates": [-106, 40], "type": "Point"}, "id": "1", "properties": {"title": "thingy one"}}')
diff --git a/tests/test_geo_interface.py b/tests/test_geo_interface.py
new file mode 100644
index 0000000..0eafe32
--- /dev/null
+++ b/tests/test_geo_interface.py
@@ -0,0 +1,142 @@
+"""
+Encoding/decoding custom objects with __geo_interface__
+"""
+import unittest
+
+import geojson
+
+
+class EncodingDecodingTest(unittest.TestCase):
+
+    def setUp(self):
+        class Restaurant(object):
+            """
+            Basic Restaurant class
+            """
+            def __init__(self, name, latlng):
+                super(Restaurant, self).__init__()
+                self.name = name
+                self.latlng = latlng
+
+        class Restaurant1(Restaurant):
+            """
+            Extends Restaurant with __geo_interface__ returning dict
+            """
+            @property
+            def __geo_interface__(self):
+                return {'type': "Point", 'coordinates': self.latlng}
+
+        class Restaurant2(Restaurant):
+            """
+            Extends Restaurant with __geo_interface__ returning another
+            __geo_interface__ object
+            """
+            @property
+            def __geo_interface__(self):
+                return geojson.Point(self.latlng)
+
+        class RestaurantFeature1(Restaurant):
+            """
+            Extends Restaurant with __geo_interface__ returning dict
+            """
+            @property
+            def __geo_interface__(self):
+                return {
+                    'geometry': {
+                        'type': "Point",
+                        'coordinates': self.latlng,
+                    },
+                    'id': None,
+                    'type': "Feature",
+                    'properties': {
+                        'name': self.name,
+                    },
+                }
+
+        class RestaurantFeature2(Restaurant):
+            """
+            Extends Restaurant with __geo_interface__ returning another
+            __geo_interface__ object
+            """
+            @property
+            def __geo_interface__(self):
+                return geojson.Feature(
+                    geometry=geojson.Point(self.latlng),
+                    properties={'name': self.name})
+
+        self.name = "In N Out Burger"
+        self.latlng = [-54, 4]
+
+        self.restaurant_nogeo = Restaurant(self.name, self.latlng)
+
+        self.restaurant1 = Restaurant1(self.name, self.latlng)
+        self.restaurant2 = Restaurant2(self.name, self.latlng)
+
+        self.restaurant_str = (
+            '{'
+                '"coordinates": [-54, 4], '
+                '"type": "Point"'
+            '}'
+        )
+
+        self.restaurant_feature1 = RestaurantFeature1(self.name, self.latlng)
+        self.restaurant_feature2 = RestaurantFeature2(self.name, self.latlng)
+
+        self.restaurant_feature_str = (
+            '{'
+                '"geometry": {'
+                    '"coordinates": [-54, 4], '
+                    '"type": "Point"'
+                '}, '
+                '"id": null, '
+                '"properties": {"name": "In N Out Burger"}, '
+                '"type": "Feature"'
+            '}'
+        )
+
+
+    def test_encode(self):
+        """
+        Ensure objects that implement __geo_interface__ can be encoded into
+        GeoJSON strings
+        """
+        actual = geojson.dumps(self.restaurant1, sort_keys=True)
+        self.assertEqual(actual, self.restaurant_str)
+
+        actual = geojson.dumps(self.restaurant2, sort_keys=True)
+        self.assertEqual(actual, self.restaurant_str)
+
+        # Classes that don't implement geo interface should raise TypeError
+        self.assertRaises(
+            TypeError, geojson.dumps, self.restaurant_nogeo, sort_keys=True)
+
+    def test_encode_nested(self):
+        """
+        Ensure nested objects that implement __geo_interface__ can be encoded
+        into GeoJSON strings
+        """
+        actual = geojson.dumps(self.restaurant_feature1, sort_keys=True)
+        self.assertEqual(actual, self.restaurant_feature_str)
+
+        actual = geojson.dumps(self.restaurant_feature2, sort_keys=True)
+        self.assertEqual(actual, self.restaurant_feature_str)
+
+    def test_decode(self):
+        """
+        Ensure a GeoJSON string can be decoded into GeoJSON objects
+        """
+        actual = geojson.loads(self.restaurant_str)
+        expected = self.restaurant1.__geo_interface__
+        self.assertEqual(expected, actual)
+
+    def test_decode_nested(self):
+        """
+        Ensure a GeoJSON string can be decoded into nested GeoJSON objects
+        """
+        actual = geojson.loads(self.restaurant_feature_str)
+        expected = self.restaurant_feature1.__geo_interface__
+        self.assertEqual(expected, actual)
+
+        nested = expected['geometry']
+        expected = self.restaurant1.__geo_interface__
+        self.assertEqual(nested, expected)
diff --git a/tests/test_null_geometries.py b/tests/test_null_geometries.py
new file mode 100644
index 0000000..5d90cac
--- /dev/null
+++ b/tests/test_null_geometries.py
@@ -0,0 +1,27 @@
+import unittest
+
+import geojson
+
+
+class NullGeometriesTest(unittest.TestCase):
+
+    def test_null_geometry_explicit(self):
+        feature = geojson.Feature(
+            id=12,
+            geometry=None,
+            properties={'foo': 'bar'}
+        )
+        actual = geojson.dumps(feature, sort_keys=True)
+        expected = ('{"geometry": null, "id": 12, "properties": {"foo": '
+                    '"bar"}, "type": "Feature"}')
+        self.assertEqual(actual, expected)
+
+    def test_null_geometry_implicit(self):
+        feature = geojson.Feature(
+            id=12,
+            properties={'foo': 'bar'}
+        )
+        actual = geojson.dumps(feature, sort_keys=True)
+        expected = ('{"geometry": null, "id": 12, "properties": {"foo": '
+                    '"bar"}, "type": "Feature"}')
+        self.assertEqual(actual, expected)
diff --git a/tests/test_strict_json.py b/tests/test_strict_json.py
new file mode 100644
index 0000000..2dfdfd9
--- /dev/null
+++ b/tests/test_strict_json.py
@@ -0,0 +1,52 @@
+"""
+GeoJSON produces and consumes only strict JSON. NAN and Infinity are not
+permissible values according to the JSON specification.
+"""
+import unittest
+
+import geojson
+
+
+class StrictJsonTest(unittest.TestCase):
+    def test_encode_nan(self):
+        """
+        Ensure Error is raised when encoding nan
+        """
+        unstrict = {
+            "type": "Point",
+            "coordinates": (float("nan"), 1.0),
+        }
+        self.assertRaises(ValueError, geojson.dumps, unstrict)
+
+    def test_encode_inf(self):
+        """
+        Ensure Error is raised when encoding inf or -inf
+        """
+        unstrict = {
+            "type": "Point",
+            "coordinates": (float("inf"), 1.0),
+        }
+        self.assertRaises(ValueError, geojson.dumps, unstrict)
+
+        unstrict = {
+            "type": "Point",
+            "coordinates": (float("-inf"), 1.0),
+        }
+        self.assertRaises(ValueError, geojson.dumps, unstrict)
+
+    def test_decode_nan(self):
+        """
+        Ensure Error is raised when decoding NaN
+        """
+        unstrict = '{"type": "Point", "coordinates": [1.0, NaN]}'
+        self.assertRaises(ValueError, geojson.loads, unstrict)
+
+    def test_decode_inf(self):
+        """
+        Ensure Error is raised when decoding Infinity or -Infinity
+        """
+        unstrict = '{"type": "Point", "coordinates": [1.0, Infinity]}'
+        self.assertRaises(ValueError, geojson.loads, unstrict)
+
+        unstrict = '{"type": "Point", "coordinates": [1.0, -Infinity]}'
+        self.assertRaises(ValueError, geojson.loads, unstrict)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/python-geojson.git



More information about the Pkg-grass-devel mailing list