[python-geojson] 01/10: Imported Upstream version 1.0.4

Johan Van de Wauw johanvdw-guest at moszumanska.debian.org
Sat Jan 31 11:12:52 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 749d182832cf77d739a24fc866381b573cd1f05d
Author: Johan Van de Wauw <johan.vandewauw at gmail.com>
Date:   Thu Jan 29 12:50:56 2015 +0100

    Imported Upstream version 1.0.4
---
 .travis.yml                            |   7 +
 CHANGELOG.rst                          |  52 +++++++
 LICENSE.rst                            |  11 ++
 MANIFEST.in                            |   2 +
 README.rst                             | 243 +++++++++++++++++++++++++++++++++
 geojson/__init__.py                    |   6 +
 geojson/base.py                        |  64 +++++++++
 geojson/codec.py                       |  56 ++++++++
 geojson/crs.py                         |  30 ++++
 geojson/examples.py                    |  45 ++++++
 geojson/factory.py                     |   9 ++
 geojson/feature.py                     |  29 ++++
 geojson/geometry.py                    |  61 +++++++++
 geojson/mapping.py                     |  23 ++++
 setup.cfg                              |  10 ++
 setup.py                               |  46 +++++++
 tests/README.txt                       |  15 ++
 tests/__init__.py                      |  12 ++
 tests/blog.txt                         |  21 +++
 tests/c2c_features.txt                 |  45 ++++++
 tests/crs.txt                          |  20 +++
 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 ++++
 30 files changed, 1404 insertions(+)

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..a51075f
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: "python"
+python:
+   - "2.6"
+   - "2.7"
+   - "3.2"
+   - "3.3"
+script: "python setup.py nosetests"
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..127e99b
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,52 @@
+Changes
+=======
+
+1.0.4 (2013-11-16)
+------------------
+
+- Flake8 everything
+- Transition all documentation to reStructuredText
+- Start using Travis CI
+- Support Python 3
+- Fix broken testcase when run using Python 2.6
+
+1.0.3 (2009-11-25)
+------------------
+
+- Fixed #186
+- Internal code simplification
+
+1.0.2 (2009-11-24)
+------------------
+
+- Use nose test framework instead of rolling our own.
+
+1.0.1 (2008-12-19)
+------------------
+
+- Handle features with null geometries (#174).
+
+1.0 (2008-08-01)
+----------------
+
+- Final 1.0 release.
+- Rename PyGFPEncoder to GeoJSONEncoder and expose it from the geojson module.
+
+1.0rc1 (2008-07-11)
+-------------------
+
+- Release candidate.
+
+1.0b1 (2008-07-02)
+------------------
+
+- Rename encoding module to codec.
+
+1.0a4 (2008-04-27)
+------------------
+
+- Get in step with GeoJSON draft version 6.
+- Made all code work with Python 2.4.3, 2.5.1, will test with all variations.
+  (see tests/rundoctests.dist)
+- Made tests use ELLIPSIS to avoid output transmogification due to floating
+  point representation. 
diff --git a/LICENSE.rst b/LICENSE.rst
new file mode 100644
index 0000000..3f9a357
--- /dev/null
+++ b/LICENSE.rst
@@ -0,0 +1,11 @@
+Copyright © 2013, Corey Farwell
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+-  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+-  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 [...]
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..65d7f18
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.rst
+recursive-include tests *.rst *.txt *.py
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..879c230
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,243 @@
+python-geojson
+==============
+
+.. image:: https://travis-ci.org/frewsxcv/python-geojson.png?branch=master
+   :target: https://travis-ci.org/frewsxcv/python-geojson
+
+This library contains:
+
+- Functions for encoding and decoding GeoJSON_ formatted data
+- Classes for all GeoJSON Objects
+- An implementation of the Python `__geo_interface__ Specification`_
+
+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_:
+
+.. code::
+
+  pip install geojson
+
+.. _PyPi as 'geojson': https://pypi.python.org/pypi/geojson/
+.. _pip: http://www.pip-installer.org
+
+GeoJSON Objects
+---------------
+
+This library implements all the `GeoJSON Objects`_ described in `The GeoJSON Format Specification`_.
+
+.. _GeoJSON Objects: http://www.geojson.org/geojson-spec.html#geojson-objects
+
+Point
+~~~~~
+
+.. code:: python
+
+  >>> from geojson import 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`_.
+
+.. _Section 2.1.2: http://www.geojson.org/geojson-spec.html#point
+.. _Appendix A\: Point: http://www.geojson.org/geojson-spec.html#id2
+
+MultiPoint
+~~~~~~~~~~
+
+.. code:: python
+
+  >>> from geojson import 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`_.
+
+.. _Section 2.1.3: http://www.geojson.org/geojson-spec.html#multipoint
+.. _Appendix A\: MultiPoint: http://www.geojson.org/geojson-spec.html#id5
+
+
+LineString
+~~~~~~~~~~
+
+.. code:: python
+
+  >>> from geojson import 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`_.
+
+.. _Section 2.1.4: http://www.geojson.org/geojson-spec.html#linestring
+.. _Appendix A\: LineString: http://www.geojson.org/geojson-spec.html#id3
+
+MultiLineString
+~~~~~~~~~~~~~~~
+
+.. code:: python
+
+  >>> from geojson import MultiLineString
+
+  >>> MultiLineString([
+  ...     [(3.75, 9.25), (-130.95, 1.52)],
+  ...     [(23.15, -34.25), (-1.35, -4.65), (3.45, 77.95)]
+  ... ])  # 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`_.
+
+.. _Section 2.1.5: http://www.geojson.org/geojson-spec.html#multilinestring
+.. _Appendix A\: MultiLineString: http://www.geojson.org/geojson-spec.html#id6
+
+Polygon
+~~~~~~~
+
+.. code:: python
+
+  >>> 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"}
+
+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
+
+MultiPolygon
+~~~~~~~~~~~~
+
+.. code:: python
+
+  >>> 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)]
+  ... ])  # 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"}
+
+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
+
+GeometryCollection
+~~~~~~~~~~~~~~~~~~
+
+.. code:: python
+
+  >>> from geojson import GeometryCollection, Point, LineString
+
+  >>> my_point = Point((23.532, -63.12))
+
+  >>> my_line = LineString([(-152.62, 51.21), (5.21, 10.69)])
+
+  >>> 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`_.
+
+.. _Section 2.1.8: http://www.geojson.org/geojson-spec.html#geometrycollection
+.. _Appendix A\: GeometryCollection: http://www.geojson.org/geojson-spec.html#geometrycollection
+
+Feature
+~~~~~~~
+
+.. code:: python
+
+  >>> from geojson import Feature, Point
+
+  >>> my_point = Point((43.24, -1.532))
+
+  >>> Feature(geometry=my_point)  # doctest: +ELLIPSIS
+  {"geometry": {"coordinates": [43.2..., -1.53...], "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"}
+
+  >>> Feature(geometry=my_point, id=27)  # doctest: +ELLIPSIS
+  {"geometry": {"coordinates": [43.2..., -1.53...], "type": "Point"}, "id": 27, "properties": {}, "type": "Feature"}
+
+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
+
+FeatureCollection
+~~~~~~~~~~~~~~~~~
+
+.. code:: python
+
+  >>> from geojson import Feature, Point, FeatureCollection
+
+  >>> my_feature = Feature(geometry=Point((1.6432, -19.123)))
+
+  >>> my_other_feature = Feature(geometry=Point((-80.234, -22.532)))
+
+  >>> 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`_.
+
+.. _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.
+
+.. code:: python
+
+  >>> import geojson
+
+  >>> my_point = geojson.Point((43.24, -1.532))
+
+  >>> my_point  # doctest: +ELLIPSIS
+  {"coordinates": [43.2..., -1.53...], "type": "Point"}
+
+  >>> dump = geojson.dumps(my_point, sort_keys=True)
+
+  >>> dump  # doctest: +ELLIPSIS
+  '{"coordinates": [43.2..., -1.53...], "type": "Point"}'
+
+  >>> geojson.loads(dump)  # doctest: +ELLIPSIS
+  {"coordinates": [43.2..., -1.53...], "type": "Point"}
+
+Custom classes
+~~~~~~~~~~~~~~
+
+This encoding/decoding functionality shown in the previous can be extended to custom classes using the interface described by the `__geo_interface__ Specification`_.
+
+.. code:: python
+
+  >>> import geojson
+
+  >>> class MyPoint():
+  ...     def __init__(self, x, y):
+  ...         self.x = x
+  ...         self.y = y
+  ...
+  ...     @property
+  ...     def __geo_interface__(self):
+  ...         return {'type': 'Point', 'coordinates': (self.x, self.y)}
+
+  >>> point_instance = MyPoint(52.235, -19.234)
+
+  >>> geojson.dumps(point_instance, sort_keys=True)  # doctest: +ELLIPSIS
+  '{"coordinates": [52.23..., -19.23...], "type": "Point"}'
+
+Credits
+-------
+
+* Sean Gillies <sgillies at frii.com>
+* Matthew Russell <matt at sanoodi.com>
+* Corey Farwell <coreyf at rwell.org>
+
+
+.. _GeoJSON: http://geojson.org/
+.. _The GeoJSON Format Specification: http://www.geojson.org/geojson-spec.html
+.. _\_\_geo\_interface\_\_ Specification: https://gist.github.com/sgillies/2217756
diff --git a/geojson/__init__.py b/geojson/__init__.py
new file mode 100644
index 0000000..7e8d5f7
--- /dev/null
+++ b/geojson/__init__.py
@@ -0,0 +1,6 @@
+from geojson.codec import dump, dumps, load, loads, GeoJSONEncoder
+from geojson.geometry import Point, LineString, Polygon
+from geojson.geometry import MultiLineString, MultiPoint, MultiPolygon
+from geojson.geometry import GeometryCollection
+from geojson.feature import Feature, FeatureCollection
+from geojson.base import GeoJSON
diff --git a/geojson/base.py b/geojson/base.py
new file mode 100644
index 0000000..fa27f9a
--- /dev/null
+++ b/geojson/base.py
@@ -0,0 +1,64 @@
+import geojson
+from geojson.mapping import to_mapping
+
+
+class GeoJSON(dict):
+
+    def __init__(self, iterable=(), **extra):
+        super(GeoJSON, self).__init__(iterable)
+        self["type"] = getattr(self, "type", type(self).__name__)
+        self.update(extra)
+
+    def __repr__(self):
+        return geojson.dumps(self, sort_keys=True)
+
+    __str__ = __repr__
+
+    def __setattr__(self, name, value):
+        self[name] = value
+
+    def __getattr__(self, name):
+        try:
+            v = self[name]
+        except KeyError:
+            raise AttributeError(name)
+        return v
+
+    def __delattr__(self, name):
+        del self[name]
+
+    @property
+    def __geo_interface__(self):
+        if self.type != "GeoJSON":
+            return self
+
+    @classmethod
+    def to_instance(cls, ob, default=None, strict=False):
+        """Encode a GeoJSON dict into an GeoJSON object.
+
+        Assumes the caller knows that the dict should satisfy a GeoJSON type.
+        """
+        if ob is None and default is not None:
+            instance = default()
+        elif isinstance(ob, GeoJSON):
+            instance = ob
+        else:
+            mapping = to_mapping(ob)
+            d = dict((str(k), mapping[k]) for k in mapping)
+            try:
+                type_ = d.pop("type")
+                geojson_factory = getattr(geojson.factory, type_)
+                if not issubclass(geojson_factory, GeoJSON):
+                    raise TypeError("""\
+                    Not a valid GeoJSON type:
+                    %r (geojson_factory: %r, cls: %r)
+                    """ % (type_, geojson_factory, cls))
+                instance = geojson_factory(**d)
+            except (AttributeError, KeyError) as invalid:
+                if not strict:
+                    instance = ob
+                else:
+                    msg = "Cannot coerce %r into a valid GeoJSON structure: %s"
+                    msg %= (ob, invalid)
+                    raise ValueError(msg)
+        return instance
diff --git a/geojson/codec.py b/geojson/codec.py
new file mode 100644
index 0000000..acae28f
--- /dev/null
+++ b/geojson/codec.py
@@ -0,0 +1,56 @@
+import json
+
+import geojson
+import geojson.factory
+from geojson.mapping import to_mapping
+
+
+class GeoJSONEncoder(json.JSONEncoder):
+
+    def default(self, obj):
+        return geojson.factory.GeoJSON.to_instance(obj)
+
+
+# Wrap the functions from json, providing encoder, decoders, and
+# object creation hooks.
+# Here the defaults are set to only permit valid JSON as per RFC 4267
+
+def _enforce_strict_numbers(obj):
+    if isinstance(obj, (int, float)):
+        raise ValueError("Number %r is not JSON compliant" % obj)
+
+
+def dump(obj, fp, cls=GeoJSONEncoder, allow_nan=False, **kwargs):
+    return json.dump(to_mapping(obj),
+                     fp, cls=cls, allow_nan=allow_nan, **kwargs)
+
+
+def dumps(obj, cls=GeoJSONEncoder, allow_nan=False, **kwargs):
+    return json.dumps(to_mapping(obj),
+                      cls=cls, allow_nan=allow_nan, **kwargs)
+
+
+def load(fp,
+         cls=json.JSONDecoder,
+         parse_constant=_enforce_strict_numbers,
+         object_hook=geojson.base.GeoJSON.to_instance,
+         **kwargs):
+    return json.load(fp,
+                     cls=cls, object_hook=object_hook,
+                     parse_constant=parse_constant,
+                     **kwargs)
+
+
+def loads(s,
+          cls=json.JSONDecoder,
+          parse_constant=_enforce_strict_numbers,
+          object_hook=geojson.base.GeoJSON.to_instance,
+          **kwargs):
+    return json.loads(s,
+                      cls=cls, object_hook=object_hook,
+                      parse_constant=parse_constant,
+                      **kwargs)
+
+
+# Backwards compatibility
+PyGFPEncoder = GeoJSONEncoder
diff --git a/geojson/crs.py b/geojson/crs.py
new file mode 100644
index 0000000..792adec
--- /dev/null
+++ b/geojson/crs.py
@@ -0,0 +1,30 @@
+from geojson.base import GeoJSON
+
+
+class CoordinateReferenceSystem(GeoJSON):
+
+    def __init__(self, properties=None, **extra):
+        super(CoordinateReferenceSystem, self).__init__(**extra)
+        self["properties"] = properties or {}
+
+
+class Named(CoordinateReferenceSystem):
+
+    def __init__(self, properties=None, **extra):
+        super(Named, self).__init__(properties=properties, **extra)
+        self["type"] = "name"
+
+    def __repr__(self):
+        return super(Named, self).__repr__()
+
+
+class Linked(CoordinateReferenceSystem):
+
+    def __init__(self, properties=None, **extra):
+        super(Linked, self).__init__(properties=properties, **extra)
+        self["type"] = "link"
+
+
+class Default(object):
+
+    """GeoJSON default, long/lat WGS84, is not serialized."""
diff --git a/geojson/examples.py b/geojson/examples.py
new file mode 100644
index 0000000..81dd88d
--- /dev/null
+++ b/geojson/examples.py
@@ -0,0 +1,45 @@
+
+class SimpleWebFeature(object):
+
+    """
+    A simple, Atom-ish, single geometry (WGS84) GIS feature.
+    """
+
+    def __init__(self, id=None, geometry=None, title=None, summary=None,
+                 link=None):
+        """Initialize."""
+        self.id = id
+        self.geometry = geometry
+        self.properties = {}
+        self.properties['title'] = title
+        self.properties['summary'] = summary
+        self.properties['link'] = link
+
+    def as_dict(self):
+        return {
+            "type": "Feature",
+            "id": self.id,
+            "properties": self.properties,
+            "geometry": self.geometry
+            }
+
+    __geo_interface__ = property(as_dict)
+
+
+def createSimpleWebFeature(o):
+    """Create an instance of SimpleWebFeature from a dict, o. If o does not
+    match a Python feature object, simply return o. This function serves as a
+    json decoder hook. See coding.load()."""
+    try:
+        id = o['id']
+        g = o['geometry']
+        p = o['properties']
+        return SimpleWebFeature(str(id), {
+            'type': str(g.get('type')),
+            'coordinates': g.get('coordinates', [])},
+            title=p.get('title'),
+            summary=p.get('summary'),
+            link=str(p.get('link')))
+    except (KeyError, TypeError):
+        pass
+    return o
diff --git a/geojson/factory.py b/geojson/factory.py
new file mode 100644
index 0000000..aa79dd9
--- /dev/null
+++ b/geojson/factory.py
@@ -0,0 +1,9 @@
+from geojson.geometry import Point, LineString, Polygon
+from geojson.geometry import MultiLineString, MultiPoint, MultiPolygon
+from geojson.geometry import GeometryCollection
+from geojson.feature import Feature, FeatureCollection
+from geojson.base import GeoJSON
+from geojson.crs import Named, Linked
+
+name = Named
+link = Linked
diff --git a/geojson/feature.py b/geojson/feature.py
new file mode 100644
index 0000000..48db6c0
--- /dev/null
+++ b/geojson/feature.py
@@ -0,0 +1,29 @@
+"""
+SimpleWebFeature is a working example of a class that satisfies the Python geo
+interface.
+"""
+
+from geojson.base import GeoJSON
+
+
+class Feature(GeoJSON):
+
+    """A (WGS84) GIS Feature."""
+
+    def __init__(self, id=None, geometry=None, properties=None, **extra):
+        super(Feature, self).__init__(**extra)
+        self["id"] = id
+        if geometry:
+            self["geometry"] = self.to_instance(geometry, strict=True)
+        else:
+            self["geometry"] = None
+        self["properties"] = properties or {}
+
+
+class FeatureCollection(GeoJSON):
+
+    """A collection of Features."""
+
+    def __init__(self, features, **extra):
+        super(FeatureCollection, self).__init__(**extra)
+        self["features"] = features
diff --git a/geojson/geometry.py b/geojson/geometry.py
new file mode 100644
index 0000000..eb19f0f
--- /dev/null
+++ b/geojson/geometry.py
@@ -0,0 +1,61 @@
+from decimal import Decimal
+from geojson.base import GeoJSON
+
+
+class Geometry(GeoJSON):
+
+    """A (WGS84) GIS geometry."""
+
+    def __init__(self, coordinates=None, crs=None, **extra):
+        super(Geometry, self).__init__(**extra)
+        self["coordinates"] = coordinates or []
+        self.clean_coordinates(self["coordinates"])
+        if crs:
+            self["crs"] = self.to_instance(crs, strict=True)
+
+    def clean_coordinates(self, 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)
+
+
+class GeometryCollection(GeoJSON):
+
+    """A collection of (WGS84) GIS geometries."""
+
+    def __init__(self, geometries=None, **extra):
+        super(GeometryCollection, self).__init__(**extra)
+        self["geometries"] = geometries or []
+
+
+# Marker classes.
+
+class Point(Geometry):
+    pass
+
+
+class MultiPoint(Geometry):
+    pass
+
+
+class LineString(MultiPoint):
+    pass
+
+
+class MultiLineString(Geometry):
+    pass
+
+
+class Polygon(Geometry):
+    pass
+
+
+class MultiPolygon(Geometry):
+    pass
+
+
+class Default(object):
+    """GeoJSON default."""
diff --git a/geojson/mapping.py b/geojson/mapping.py
new file mode 100644
index 0000000..d0057d5
--- /dev/null
+++ b/geojson/mapping.py
@@ -0,0 +1,23 @@
+import sys
+import geojson
+
+import json
+from collections import MutableMapping
+is_mapping = lambda obj: isinstance(obj, MutableMapping)
+mapping_base = MutableMapping
+
+
+GEO_INTERFACE_MARKER = "__geo_interface__"
+
+
+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
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..f7d070c
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,10 @@
+[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
new file mode 100644
index 0000000..fb7d28f
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,46 @@
+import sys
+import io
+from setuptools import setup
+
+
+readme_text = io.open("README.rst", "r").read()
+
+if sys.version_info[:2] not in [(2, 6), (2, 7)] and \
+        sys.version_info[:1] not in [(3, )]:
+    sys.stderr.write("Sorry, only Python 2.6, 2.7, and 3.x are supported "
+                     "at this time.\n")
+    exit(1)
+
+# 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",
+    description="Python bindings and utilities for GeoJSON",
+    license="BSD",
+    keywords="gis geography json",
+    author="Sean Gillies",
+    author_email="sgillies at frii.com",
+    maintainer="Corey Farwell",
+    maintainer_email="coreyf at rwell.org",
+    url="https://github.com/frewsxcv/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",
+    classifiers=[
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Developers",
+        "Intended Audience :: Science/Research",
+        "License :: OSI Approved :: BSD License",
+        "Operating System :: OS Independent",
+        "Programming Language :: Python",
+        "Topic :: Scientific/Engineering :: GIS",
+    ]
+)
diff --git a/tests/README.txt b/tests/README.txt
new file mode 100644
index 0000000..8cc546c
--- /dev/null
+++ b/tests/README.txt
@@ -0,0 +1,15 @@
+
+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/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..4efcb7b
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,12 @@
+import doctest
+import glob
+import os
+
+optionflags = (doctest.REPORT_ONLY_FIRST_FAILURE |
+               doctest.NORMALIZE_WHITESPACE |
+               doctest.ELLIPSIS)
+
+_basedir = os.path.dirname(__file__)
+paths = glob.glob("%s/*.txt" % _basedir)
+test_suite = doctest.DocFileSuite(*paths, **dict(module_relative=False,
+                                                 optionflags=optionflags))
diff --git a/tests/blog.txt b/tests/blog.txt
new file mode 100644
index 0000000..616bc5e
--- /dev/null
+++ b/tests/blog.txt
@@ -0,0 +1,21 @@
+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
new file mode 100644
index 0000000..b210ed1
--- /dev/null
+++ b/tests/c2c_features.txt
@@ -0,0 +1,45 @@
+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
new file mode 100644
index 0000000..bd1f9da
--- /dev/null
+++ b/tests/crs.txt
@@ -0,0 +1,20 @@
+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/fallsthrough_to_json.txt b/tests/fallsthrough_to_json.txt
new file mode 100644
index 0000000..ec58c23
--- /dev/null
+++ b/tests/fallsthrough_to_json.txt
@@ -0,0 +1,47 @@
+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
new file mode 100644
index 0000000..498adef
--- /dev/null
+++ b/tests/feature-null-geom.txt
@@ -0,0 +1,8 @@
+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
new file mode 100644
index 0000000..a6af2ce
--- /dev/null
+++ b/tests/featurecollection.txt
@@ -0,0 +1,84 @@
+
+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
new file mode 100644
index 0000000..fa828d7
--- /dev/null
+++ b/tests/features.txt
@@ -0,0 +1,91 @@
+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
new file mode 100644
index 0000000..4cc613b
--- /dev/null
+++ b/tests/geometry.txt
@@ -0,0 +1,122 @@
+
+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
new file mode 100644
index 0000000..2bab3ee
--- /dev/null
+++ b/tests/geometrycollection.txt
@@ -0,0 +1,54 @@
+
+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
new file mode 100644
index 0000000..cc6689e
--- /dev/null
+++ b/tests/including_additional_members.txt
@@ -0,0 +1,81 @@
+
+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
new file mode 100644
index 0000000..ccab21b
--- /dev/null
+++ b/tests/objects.txt
@@ -0,0 +1,80 @@
+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
new file mode 100644
index 0000000..34c4754
--- /dev/null
+++ b/tests/strict_json.txt
@@ -0,0 +1,30 @@
+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...
+ 
+  

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