[python-geojson] 01/04: New upstream version 2.2.0

Bas Couwenberg sebastic at debian.org
Sun Sep 17 16:31:09 UTC 2017


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

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

commit 01ccef171bf5bdba3be268a5f601e114324b62be
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sun Sep 17 18:25:43 2017 +0200

    New upstream version 2.2.0
---
 CHANGELOG.rst               |  7 +++++
 README.rst                  | 12 ++++++--
 geojson/_version.py         |  2 +-
 geojson/codec.py            |  5 ++--
 geojson/geometry.py         | 54 +++++++++++++++++++++++++---------
 geojson/utils.py            |  8 ++----
 tests/test_constructor.py   | 33 +++++++++++++++++++++
 tests/test_coords.py        |  4 +++
 tests/test_features.py      |  7 +++--
 tests/test_geo_interface.py | 15 ++++++++--
 tests/test_strict_json.py   | 34 ++++++++++++----------
 tests/test_validation.py    | 70 +++++++++++++++++++++++++++++++++------------
 12 files changed, 188 insertions(+), 63 deletions(-)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index d863f6d..1449f32 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,13 @@
 Changes
 =======
 
+2.2.0 (2017-09-17)
+------------------
+
+- Allow constructing geojson objects from geojson objects
+
+  - https://github.com/frewsxcv/python-geojson/pull/104
+
 2.1.0 (2017-08-29)
 ------------------
 
diff --git a/README.rst b/README.rst
index f1be0e7..d71aaa1 100644
--- a/README.rst
+++ b/README.rst
@@ -194,9 +194,14 @@ FeatureCollection
 
   >>> my_other_feature = Feature(geometry=Point((-80.234, -22.532)))
 
-  >>> FeatureCollection([my_feature, my_other_feature])  # doctest: +ELLIPSIS
+  >>> feature_collection = FeatureCollection([my_feature, my_other_feature])
+
+  >>> feature_collection # doctest: +ELLIPSIS
   {"features": [{"geometry": {"coordinates": [1.643..., -19.12...], "type": "Point"}, "properties": {}, "type": "Feature"}, {"geometry": {"coordinates": [-80.23..., -22.53...], "type": "Point"}, "properties": {}, "type": "Feature"}], "type": "FeatureCollection"}
 
+  >>> feature_collection.errors()
+  []
+
 Visualize the result of the example above `here <https://gist.github.com/frewsxcv/34513be6fb492771ef7b>`__. General information about FeatureCollection can be found in `Section 3.3`_ within `The GeoJSON Format Specification`_.
 
 .. _Section 3.3: https://tools.ietf.org/html/rfc7946#section-3.3
@@ -300,7 +305,7 @@ validation
 
   >>> obj = geojson.Point((-3.68,40.41,25.14,10.34))
   >>> obj.errors()
-  'the "coordinates" member must be a single position'
+  'a position must have exactly 2 or 3 values'
 
 generate_random
 ~~~~~~~~~~~~~~~
@@ -314,6 +319,9 @@ generate_random
   >>> geojson.utils.generate_random("LineString")  # doctest: +ELLIPSIS
   {"coordinates": [...], "type": "LineString"}
 
+  >>> geojson.utils.generate_random("Polygon")  # doctest: +ELLIPSIS
+  {"coordinates": [...], "type": "Polygon"}
+
 
 Development
 -----------
diff --git a/geojson/_version.py b/geojson/_version.py
index 4edb268..4bde90e 100644
--- a/geojson/_version.py
+++ b/geojson/_version.py
@@ -1,2 +1,2 @@
-__version__ = "2.1.0"
+__version__ = "2.2.0"
 __version_info__ = tuple(map(int, __version__.split(".")))
diff --git a/geojson/codec.py b/geojson/codec.py
index eb8808b..703db08 100644
--- a/geojson/codec.py
+++ b/geojson/codec.py
@@ -11,7 +11,7 @@ from geojson.mapping import to_mapping
 class GeoJSONEncoder(json.JSONEncoder):
 
     def default(self, obj):
-        return geojson.factory.GeoJSON.to_instance(obj)
+        return geojson.factory.GeoJSON.to_instance(obj) # NOQA
 
 
 # Wrap the functions from json, providing encoder, decoders, and
@@ -19,8 +19,7 @@ class GeoJSONEncoder(json.JSONEncoder):
 # 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)
+    raise ValueError("Number %r is not JSON compliant" % obj)
 
 
 def dump(obj, fp, cls=GeoJSONEncoder, allow_nan=False, **kwargs):
diff --git a/geojson/geometry.py b/geojson/geometry.py
index 8226c9c..1bcc006 100644
--- a/geojson/geometry.py
+++ b/geojson/geometry.py
@@ -4,17 +4,18 @@ from decimal import Decimal
 from geojson.base import GeoJSON
 
 
+if sys.version_info[0] == 3:
+    # Python 3.x has no long type
+    _JSON_compliant_types = (float, int, Decimal)
+else:
+    _JSON_compliant_types = (float, int, Decimal, long)  # noqa
+
+
 class Geometry(GeoJSON):
     """
     Represents an abstract base class for a WGS84 geometry.
     """
 
-    if sys.version_info[0] == 3:
-        # Python 3.x has no long type
-        __JSON_compliant_types = (float, int, Decimal)
-    else:
-        __JSON_compliant_types = (float, int, Decimal, long)  # noqa
-
     def __init__(self, coordinates=None, crs=None, validate=False, **extra):
         """
         Initialises a Geometry object.
@@ -26,8 +27,8 @@ class Geometry(GeoJSON):
         """
 
         super(Geometry, self).__init__(**extra)
-        self["coordinates"] = coordinates or []
-        self.clean_coordinates(self["coordinates"])
+        self["coordinates"] = self.clean_coordinates(coordinates or [])
+
         if validate:
             errors = self.errors()
             if errors:
@@ -37,11 +38,22 @@ class Geometry(GeoJSON):
 
     @classmethod
     def clean_coordinates(cls, coords):
+        if isinstance(coords, cls):
+            return coords['coordinates']
+
+        new_coords = []
+        if isinstance(coords, Geometry):
+            coords = [coords]
         for coord in coords:
             if isinstance(coord, (list, tuple)):
-                cls.clean_coordinates(coord)
-            elif not isinstance(coord, cls.__JSON_compliant_types):
-                raise ValueError("%r is not JSON compliant number" % coord)
+                new_coords.append(cls.clean_coordinates(coord))
+            elif isinstance(coord, Geometry):
+                new_coords.append(coord['coordinates'])
+            elif isinstance(coord, _JSON_compliant_types):
+                new_coords.append(coord)
+            else:
+                raise ValueError("%r is not a JSON compliant number" % coord)
+        return new_coords
 
 
 class GeometryCollection(GeoJSON):
@@ -61,8 +73,10 @@ class GeometryCollection(GeoJSON):
 # Marker classes.
 
 def check_point(coord):
+    if not isinstance(coord, list):
+        return 'each position must be a list'
     if len(coord) not in (2, 3):
-        return 'the "coordinates" member must be a single position'
+        return 'a position must have exactly 2 or 3 values'
 
 
 class Point(Geometry):
@@ -76,9 +90,15 @@ class MultiPoint(Geometry):
 
 
 def check_line_string(coord):
+    if not isinstance(coord, list):
+        return 'each line must be a list of positions'
     if len(coord) < 2:
         return ('the "coordinates" member must be an array of '
                 'two or more positions')
+    for pos in coord:
+        error = check_point(pos)
+        if error:
+            return error
 
 
 class LineString(MultiPoint):
@@ -92,13 +112,19 @@ class MultiLineString(Geometry):
 
 
 def check_polygon(coord):
+    if not isinstance(coord, list):
+        return 'Each polygon must be a list of linear rings'
+
+    if not all(isinstance(elem, list) for elem in coord):
+        return "Each element of a polygon's coordinates must be a list"
+
     lengths = all(len(elem) >= 4 for elem in coord)
     if lengths is False:
-        return 'LinearRing must contain with 4 or more positions'
+        return 'Each linear ring must contain at least 4 positions'
 
     isring = all(elem[0] == elem[-1] for elem in coord)
     if isring is False:
-        return 'The first and last positions in LinearRing must be equivalent'
+        return 'Each linear ring must end where it started'
 
 
 class Polygon(Geometry):
diff --git a/geojson/utils.py b/geojson/utils.py
index 15ff320..e6acb21 100644
--- a/geojson/utils.py
+++ b/geojson/utils.py
@@ -5,7 +5,7 @@ def coords(obj):
     """
     Yields the coordinates from a Feature or Geometry.
 
-    :param obj: A geometry or feature to extract the coordinates from."
+    :param obj: A geometry or feature to extract the coordinates from.
     :type obj: Feature, Geometry
     :return: A generator with coordinate tuples from the geometry or feature.
     :rtype: generator
@@ -78,6 +78,7 @@ def generate_random(featureType, numberVertices=3,
     :rtype: object
     :raises ValueError: if there is no featureType provided.
     """
+
     from geojson import Point, LineString, Polygon
     import random
     import math
@@ -98,10 +99,7 @@ def generate_random(featureType, numberVertices=3,
         return Point((randomLon(), randomLat()))
 
     def createLine():
-        coords = []
-        for i in range(0, numberVertices):
-            coords.append((randomLon(), randomLat()))
-        return LineString(coords)
+        return LineString([createPoint() for unused in range(numberVertices)])
 
     def createPoly():
         aveRadius = 60
diff --git a/tests/test_constructor.py b/tests/test_constructor.py
new file mode 100644
index 0000000..aab36bd
--- /dev/null
+++ b/tests/test_constructor.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for geojson object constructor
+"""
+
+import unittest
+
+import geojson
+
+
+class TestGeoJSONConstructor(unittest.TestCase):
+
+    def test_copy_construction(self):
+        coords = [1, 2]
+        pt = geojson.Point(coords)
+        self.assertEquals(geojson.Point(pt), pt)
+
+    def test_nested_constructors(self):
+        a = [5, 6]
+        b = [9, 10]
+        c = [-5, 12]
+        mp = geojson.MultiPoint([geojson.Point(a), b])
+        self.assertEquals(mp.coordinates, [a, b])
+
+        mls = geojson.MultiLineString([geojson.LineString([a, b]), [a, c]])
+        self.assertEquals(mls.coordinates, [[a, b], [a, c]])
+
+        outer = [a, b, c, a]
+        poly = geojson.Polygon(geojson.MultiPoint(outer))
+        other = [[1, 1], [1, 2], [2, 1], [1, 1]]
+        poly2 = geojson.Polygon([outer, other])
+        self.assertEquals(geojson.MultiPolygon([poly, poly2]).coordinates,
+                          [[outer], [outer, other]])
diff --git a/tests/test_coords.py b/tests/test_coords.py
index 3435786..fe242bd 100644
--- a/tests/test_coords.py
+++ b/tests/test_coords.py
@@ -57,3 +57,7 @@ class CoordsTestCase(unittest.TestCase):
         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))
+
+    def test_map_invalid(self):
+        with self.assertRaises(ValueError):
+            map_coords(lambda x: x, {"type": ""})
diff --git a/tests/test_features.py b/tests/test_features.py
index fe295d4..f735a0b 100644
--- a/tests/test_features.py
+++ b/tests/test_features.py
@@ -1,4 +1,7 @@
-from io import BytesIO  # NOQA
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO
 import unittest
 
 import geojson
@@ -36,7 +39,7 @@ class FeaturesTest(unittest.TestCase):
     def test_unicode_properties(self):
         with open("tests/data.geojson") as file_:
             obj = geojson.load(file_)
-        geojson.dumps(obj)
+        geojson.dump(obj, StringIO())
 
     def test_feature_class(self):
         """
diff --git a/tests/test_geo_interface.py b/tests/test_geo_interface.py
index 7e7cb54..42aff28 100644
--- a/tests/test_geo_interface.py
+++ b/tests/test_geo_interface.py
@@ -4,6 +4,7 @@ Encoding/decoding custom objects with __geo_interface__
 import unittest
 
 import geojson
+from geojson.mapping import to_mapping
 
 
 class EncodingDecodingTest(unittest.TestCase):
@@ -96,8 +97,8 @@ class EncodingDecodingTest(unittest.TestCase):
         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)
+        with self.assertRaises(TypeError):
+            geojson.dumps(self.restaurant_nogeo)
 
     def test_encode_nested(self):
         """
@@ -129,3 +130,13 @@ class EncodingDecodingTest(unittest.TestCase):
         nested = expected['geometry']
         expected = self.restaurant1.__geo_interface__
         self.assertEqual(nested, expected)
+
+    def test_invalid(self):
+        with self.assertRaises(ValueError) as cm:
+            geojson.loads('{"type":"Point", "coordinates":[[-Infinity, 4]]}')
+
+        self.assertIn('is not JSON compliant', str(cm.exception))
+
+    def test_mapping(self):
+        self.assertEquals(to_mapping(geojson.Point([1, 2])),
+                          {"coordinates": [1, 2], "type": "Point"})
diff --git a/tests/test_strict_json.py b/tests/test_strict_json.py
index 2dfdfd9..1aeb8f5 100644
--- a/tests/test_strict_json.py
+++ b/tests/test_strict_json.py
@@ -12,41 +12,45 @@ class StrictJsonTest(unittest.TestCase):
         """
         Ensure Error is raised when encoding nan
         """
-        unstrict = {
+        self._raises_on_dump({
             "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 = {
+        self._raises_on_dump({
             "type": "Point",
             "coordinates": (float("inf"), 1.0),
-        }
-        self.assertRaises(ValueError, geojson.dumps, unstrict)
+        })
 
-        unstrict = {
+        self._raises_on_dump({
             "type": "Point",
             "coordinates": (float("-inf"), 1.0),
-        }
-        self.assertRaises(ValueError, geojson.dumps, unstrict)
+        })
+
+    def _raises_on_dump(self, unstrict):
+        with 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)
+        self._raises_on_load('{"type": "Point", "coordinates": [1.0, NaN]}')
 
     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)
+        self._raises_on_load(
+            '{"type": "Point", "coordinates": [1.0, Infinity]}')
+
+        self._raises_on_load(
+            '{"type": "Point", "coordinates": [1.0, -Infinity]}')
 
-        unstrict = '{"type": "Point", "coordinates": [1.0, -Infinity]}'
-        self.assertRaises(ValueError, geojson.loads, unstrict)
+    def _raises_on_load(self, unstrict):
+        with self.assertRaises(ValueError):
+            geojson.loads(unstrict)
diff --git a/tests/test_validation.py b/tests/test_validation.py
index d104d89..d3abf4c 100644
--- a/tests/test_validation.py
+++ b/tests/test_validation.py
@@ -7,33 +7,38 @@ import unittest
 
 import geojson
 
+INVALID_POS = (10, 20, 30, 40)
+VALID_POS = (10, 20)
+VALID_POS_WITH_ELEVATION = (10, 20, 30)
+
 
 class TestValidationGeometry(unittest.TestCase):
 
     def test_invalid_geometry_with_validate(self):
-        self.assertRaises(
-            ValueError, geojson.Point, (10, 20, 30, 40), validate=True)
+        with self.assertRaises(ValueError):
+            geojson.Point(INVALID_POS, validate=True)
 
     def test_invalid_geometry_without_validate(self):
-        try:
-            geojson.Point((10, 20, 30))
-            geojson.Point((10, 20, 30), validate=False)
-        except ValueError:
-            self.fail("Point raised ValueError unexpectedly")
+        geojson.Point(INVALID_POS)
+        geojson.Point(INVALID_POS, validate=False)
 
     def test_valid_geometry(self):
-        try:
-            geojson.Point((10, 20), validate=True)
-            geojson.Point((10, 20), validate=False)
-        except ValueError:
-            self.fail("Point raised ValueError unexpectedly")
+        geojson.Point(VALID_POS, validate=True)
 
     def test_valid_geometry_with_elevation(self):
-        try:
-            geojson.Point((10, 20, 30), validate=True)
-            geojson.Point((10, 20, 30), validate=False)
-        except ValueError:
-            self.fail("Point raised ValueError unexpectedly")
+        geojson.Point(VALID_POS_WITH_ELEVATION, validate=True)
+
+    def test_not_sequence(self):
+        with self.assertRaises(ValueError) as cm:
+            geojson.MultiPoint([5], validate=True)
+
+        self.assertIn('each position must be a list', str(cm.exception))
+
+    def test_not_number(self):
+        with self.assertRaises(ValueError) as cm:
+            geojson.MultiPoint([[1, '?']], validate=False)
+
+        self.assertIn('is not a JSON compliant number', str(cm.exception))
 
 
 class TestValidationGeoJSONObject(unittest.TestCase):
@@ -78,8 +83,17 @@ class TestValidationMultipoint(unittest.TestCase):
 class TestValidationLineString(unittest.TestCase):
 
     def test_invalid_linestring(self):
-        ls = geojson.LineString([(8.919, 44.4074)])
-        self.assertEqual(ls.is_valid, False)
+        with self.assertRaises(ValueError) as cm:
+            geojson.LineString([(8.919, 44.4074)], validate=True)
+
+        self.assertIn('must be an array of two or more positions',
+                      str(cm.exception))
+
+        with self.assertRaises(ValueError) as cm:
+            geojson.LineString([(8.919, 44.4074), [3]], validate=True)
+
+        self.assertIn('a position must have exactly 2 or 3 values',
+                      str(cm.exception))
 
     def test_valid_linestring(self):
         ls = geojson.LineString([(10, 5), (4, 3)])
@@ -89,6 +103,12 @@ class TestValidationLineString(unittest.TestCase):
 class TestValidationMultiLineString(unittest.TestCase):
 
     def test_invalid_multilinestring(self):
+        with self.assertRaises(ValueError) as cm:
+            geojson.MultiLineString([1], validate=True)
+
+        self.assertIn('each line must be a list of positions',
+                      str(cm.exception))
+
         mls = geojson.MultiLineString([[(10, 5), (20, 1)], []])
         self.assertEqual(mls.is_valid, False)
 
@@ -102,6 +122,12 @@ class TestValidationMultiLineString(unittest.TestCase):
 class TestValidationPolygon(unittest.TestCase):
 
     def test_invalid_polygon(self):
+        with self.assertRaises(ValueError) as cm:
+            geojson.Polygon([1], validate=True)
+
+        self.assertIn("Each element of a polygon's coordinates must be a list",
+                      str(cm.exception))
+
         poly1 = geojson.Polygon(
             [[(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15)]])
         self.assertEqual(poly1.is_valid, False)
@@ -120,6 +146,12 @@ class TestValidationPolygon(unittest.TestCase):
 class TestValidationMultiPolygon(unittest.TestCase):
 
     def test_invalid_multipolygon(self):
+        with self.assertRaises(ValueError) as cm:
+            geojson.MultiPolygon([1], validate=True)
+
+        self.assertIn("Each polygon must be a list of linear rings",
+                      str(cm.exception))
+
         poly1 = [(2.38, 57.322), (23.194, -20.28),
                  (-120.43, 19.15), (25.44, -17.91)]
         poly2 = [(2.38, 57.322), (23.194, -20.28),

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