[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