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 ee97e9d8ee9eee07d54e9cdf33040f456b027e96 Author: Johan Van de Wauw <johan.vandew...@gmail.com> Date: Tue Jun 23 15:52:43 2015 +0200 Imported Upstream version 1.2.0 --- .gitignore | 1 + .travis.yml | 13 +++--- CHANGELOG.rst | 14 ++++++ README.rst | 30 ++++++++++--- geojson/__init__.py | 10 +++++ geojson/base.py | 77 +++++++++++++++++++++++-------- geojson/crs.py | 9 ++++ geojson/examples.py | 34 ++++++++++++-- geojson/factory.py | 6 +++ geojson/feature.py | 37 +++++++++++---- geojson/geometry.py | 23 +++++++--- geojson/mapping.py | 9 ++++ geojson/utils.py | 31 ++++++++++--- geojson/validation.py | 97 +++++++++++++++++++++++++++++++++++++++ setup.py | 13 ++---- tests/test_base.py | 15 +++++-- tests/test_coords.py | 8 ++-- tests/test_crs.py | 12 ++--- tests/test_features.py | 71 ++++++++++++++++++++++------- tests/test_geo_interface.py | 27 ++++------- tests/test_validation.py | 107 ++++++++++++++++++++++++++++++++++++++++++++ 21 files changed, 536 insertions(+), 108 deletions(-) diff --git a/.gitignore b/.gitignore index 567609b..edd9d60 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build/ +dist/ diff --git a/.travis.yml b/.travis.yml index f9f06e7..151293a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: "python" python: - - "2.6" - "2.7" - "3.2" - "3.3" @@ -8,8 +7,12 @@ python: - "pypy" - "pypy3" install: - pip install coveralls -script: - coverage run --source=geojson setup.py test + - pip install flake8 + - pip install codecov +before_script: + flake8 . +script: + - coverage run --branch --source=geojson setup.py test + - coverage xml after_success: - coveralls + - codecov diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 202de17..4f1b9b1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,20 @@ Changes ======= +1.2.0 (2015-06-19) +------------------ + +- Utility function to validate GeoJSON objects + + - https://github.com/frewsxcv/python-geojson/pull/56 + +1.1.0 (2015-06-08) +------------------ + +- Stop outputting invalid GeoJSON value id=null on Features + + - https://github.com/frewsxcv/python-geojson/pull/53 + 1.0.9 (2014-10-05) ------------------ diff --git a/README.rst b/README.rst index e31128f..b08b9a0 100644 --- a/README.rst +++ b/README.rst @@ -1,10 +1,10 @@ python-geojson ============== -.. image:: https://travis-ci.org/frewsxcv/python-geojson.png?branch=master +.. image:: https://img.shields.io/travis/frewsxcv/python-geojson.svg :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 +.. image:: https://img.shields.io/codecov/c/github/frewsxcv/python-geojson.svg + :target: https://codecov.io/github/frewsxcv/python-geojson?branch=master This library contains: @@ -171,10 +171,10 @@ Feature >>> my_point = Point((-3.68, 40.41)) >>> Feature(geometry=my_point) # doctest: +ELLIPSIS - {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "id": null, "properties": {}, "type": "Feature"} + {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "properties": {}, "type": "Feature"} >>> Feature(geometry=my_point, properties={"country": "Spain"}) # doctest: +ELLIPSIS - {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "id": null, "properties": {"country": "Spain"}, "type": "Feature"} + {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "properties": {"country": "Spain"}, "type": "Feature"} >>> Feature(geometry=my_point, id=27) # doctest: +ELLIPSIS {"geometry": {"coordinates": [-3.68..., 40.4...], "type": "Point"}, "id": 27, "properties": {}, "type": "Feature"} @@ -195,7 +195,7 @@ FeatureCollection >>> 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"} + {"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"} 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`_. @@ -279,6 +279,21 @@ map_coords >>> geojson.dumps(new_point, sort_keys=True) # doctest: +ELLIPSIS '{"coordinates": [-57.905..., 18.62...], "type": "Point"}' +validation +~~~~~~~~~~ +:code:`geojson.is_valid` provides validation of GeoJSON objects. + +.. code:: python + + >>> import geojson + + >>> validation = geojson.is_valid(geojson.Point((-3.68,40.41,25.14))) + >>> validation['valid'] + 'no' + >>> validation['message'] + 'the "coordinates" member must be a single position' + + Development ----------- @@ -290,6 +305,9 @@ Credits * Sean Gillies <sgill...@frii.com> * Matthew Russell <m...@sanoodi.com> * Corey Farwell <cor...@rwell.org> +* Blake Grotewold <he...@grotewold.me> +* Zsolt Ero <zsolt....@gmail.com> +* Sergey Romanov <xxsmo...@gmail.com> .. _GeoJSON: http://geojson.org/ diff --git a/geojson/__init__.py b/geojson/__init__.py index 4de7ca0..f46f5cc 100644 --- a/geojson/__init__.py +++ b/geojson/__init__.py @@ -5,3 +5,13 @@ 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.validation import is_valid + +__all__ = ([dump, dumps, load, loads, GeoJSONEncoder] + + [coords, map_coords] + + [Point, LineString, Polygon] + + [MultiLineString, MultiPoint, MultiPolygon] + + [GeometryCollection] + + [Feature, FeatureCollection] + + [GeoJSON] + + [is_valid]) diff --git a/geojson/base.py b/geojson/base.py index 1bd69dd..692141f 100644 --- a/geojson/base.py +++ b/geojson/base.py @@ -1,10 +1,24 @@ +from __future__ import unicode_literals + import geojson from geojson.mapping import to_mapping class GeoJSON(dict): + """ + A class representing a GeoJSON object. + """ def __init__(self, iterable=(), **extra): + """ + Initialises a GeoJSON object + + :param iterable: iterable from which to draw the content of the GeoJSON + object. + :type iterable: dict, array, tuple + :return: a GeoJSON object + :rtype: GeoJSON + """ super(GeoJSON, self).__init__(iterable) self["type"] = getattr(self, "type", type(self).__name__) self.update(extra) @@ -14,25 +28,38 @@ 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 + + :param name: attribute name + :type name: str, int + :return: dictionary value """ try: return self[name] except KeyError: raise AttributeError(name) + def __setattr__(self, name, value): + """ + Permit dictionary items to be set like object attributes. + + :param name: key of item to be set + :type name: str + :param value: value to set item to + """ + + self[name] = value + def __delattr__(self, name): """ Permit dictionary items to be deleted like object attributes + + :param name: key of item to be deleted + :type name: str """ + del self[name] @property @@ -43,8 +70,28 @@ class GeoJSON(dict): @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. + + :param cls: Dict containing the elements to be encoded into a GeoJSON + object. + :type cls: dict + :param ob: GeoJSON object into which to encode the dict provided in + `cls`. + :type ob: GeoJSON + :param default: A default instance to append the content of the dict + to if none is provided. + :type default: GeoJSON + :param strict: Raise error if unable to coerce particular keys or + attributes to a valid GeoJSON structure. + :type strict: bool + :return: A GeoJSON object with the dict's elements as its constituents. + :rtype: GeoJSON + :raises TypeError: If the input dict contains items that are not valid + GeoJSON types. + :raises UnicodeEncodeError: If the input dict contains items of a type + that contain non-ASCII characters. + :raises AttributeError: If the input dict contains items that are not + valid GeoJSON types. """ if ob is None and default is not None: instance = default() @@ -54,21 +101,16 @@ class GeoJSON(dict): mapping = to_mapping(ob) d = {} for k in mapping: - try: - str_key = str(k) - except (UnicodeEncodeError): - str_key = unicode(k) - d[str_key] = mapping[k] + d[k] = mapping[k] try: type_ = d.pop("type") try: type_ = str(type_) - except (UnicodeEncodeError): + 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_))) + "{0} is not a GeoJSON type").format(type_) geojson_factory = getattr(geojson.factory, type_) if not issubclass(geojson_factory, GeoJSON): raise TypeError("""\ @@ -77,10 +119,9 @@ class GeoJSON(dict): """ % (type_, geojson_factory, cls)) instance = geojson_factory(**d) except (AttributeError, KeyError) as invalid: - if not strict: - instance = ob - else: + if strict: msg = "Cannot coerce %r into a valid GeoJSON structure: %s" msg %= (ob, invalid) raise ValueError(msg) + instance = ob return instance diff --git a/geojson/crs.py b/geojson/crs.py index 792adec..41a60e2 100644 --- a/geojson/crs.py +++ b/geojson/crs.py @@ -2,6 +2,9 @@ from geojson.base import GeoJSON class CoordinateReferenceSystem(GeoJSON): + """ + Represents a CRS. + """ def __init__(self, properties=None, **extra): super(CoordinateReferenceSystem, self).__init__(**extra) @@ -9,6 +12,9 @@ class CoordinateReferenceSystem(GeoJSON): class Named(CoordinateReferenceSystem): + """ + Represents a named CRS. + """ def __init__(self, properties=None, **extra): super(Named, self).__init__(properties=properties, **extra) @@ -19,6 +25,9 @@ class Named(CoordinateReferenceSystem): class Linked(CoordinateReferenceSystem): + """ + Represents a linked CRS. + """ def __init__(self, properties=None, **extra): super(Linked, self).__init__(properties=properties, **extra) diff --git a/geojson/examples.py b/geojson/examples.py index 81dd88d..2a75c72 100644 --- a/geojson/examples.py +++ b/geojson/examples.py @@ -7,7 +7,22 @@ class SimpleWebFeature(object): def __init__(self, id=None, geometry=None, title=None, summary=None, link=None): - """Initialize.""" + """ + Initialises a SimpleWebFeature from the parameters provided. + + :param id: Identifier assigned to the object. + :type id: int, str + :param geometry: The geometry on which the object is based. + :type geometry: Geometry + :param title: Name of the object + :type title: str + :param summary: Short summary associated with the object. + :type summary: str + :param link: Link associated with the object. + :type link: str + :return: A SimpleWebFeature object + :rtype: SimpleWebFeature + """ self.id = id self.geometry = geometry self.properties = {} @@ -25,11 +40,24 @@ class SimpleWebFeature(object): __geo_interface__ = property(as_dict) + """ + 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(). + """ + def createSimpleWebFeature(o): - """Create an instance of SimpleWebFeature from a dict, o. If o does not + """ + 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().""" + json decoder hook. See coding.load(). + + :param o: A dict to create the SimpleWebFeature from. + :type o: dict + :return: A SimpleWebFeature from the dict provided. + :rtype: SimpleWebFeature + """ try: id = o['id'] g = o['geometry'] diff --git a/geojson/factory.py b/geojson/factory.py index aa79dd9..bd0493e 100644 --- a/geojson/factory.py +++ b/geojson/factory.py @@ -5,5 +5,11 @@ from geojson.feature import Feature, FeatureCollection from geojson.base import GeoJSON from geojson.crs import Named, Linked +__all__ = ([Point, LineString, Polygon] + + [MultiLineString, MultiPoint, MultiPolygon] + + [GeometryCollection] + + [Feature, FeatureCollection] + + [GeoJSON]) + name = Named link = Linked diff --git a/geojson/feature.py b/geojson/feature.py index 48db6c0..dc9625d 100644 --- a/geojson/feature.py +++ b/geojson/feature.py @@ -7,23 +7,42 @@ from geojson.base import GeoJSON class Feature(GeoJSON): - - """A (WGS84) GIS Feature.""" + """ + Represents a WGS84 GIS feature. + """ def __init__(self, id=None, geometry=None, properties=None, **extra): + """ + Initialises a Feature object with the given parameters. + + :param id: Feature identifier, such as a sequential number. + :type id: str, int + :param geometry: Geometry corresponding to the feature. + :param properties: Dict containing properties of the feature. + :type properties: dict + :return: Feature object + :rtype: Feature + """ super(Feature, self).__init__(**extra) - self["id"] = id - if geometry: - self["geometry"] = self.to_instance(geometry, strict=True) - else: - self["geometry"] = None + if id is not None: + self["id"] = id + self["geometry"] = (self.to_instance(geometry, strict=True) + if geometry else None) self["properties"] = properties or {} class FeatureCollection(GeoJSON): - - """A collection of Features.""" + """ + Represents a FeatureCollection, a set of multiple Feature objects. + """ def __init__(self, features, **extra): + """ + Initialises a FeatureCollection object from the + :param features: List of features to constitute the FeatureCollection. + :type features: list + :return: FeatureCollection object + :rtype: FeatureCollection + """ super(FeatureCollection, self).__init__(**extra) self["features"] = features diff --git a/geojson/geometry.py b/geojson/geometry.py index 22ea24a..30facc3 100644 --- a/geojson/geometry.py +++ b/geojson/geometry.py @@ -4,10 +4,20 @@ from geojson.base import GeoJSON class Geometry(GeoJSON): - - """A (WGS84) GIS geometry.""" + """ + Represents an abstract base class for a WGS84 geometry. + """ def __init__(self, coordinates=None, crs=None, **extra): + """ + Initialises a Geometry object. + + :param coordinates: Coordinates of the Geometry object. + :type coordinates: tuple + :param crs: CRS + :type crs: CRS object + """ + super(Geometry, self).__init__(**extra) self["coordinates"] = coordinates or [] self.clean_coordinates(self["coordinates"]) @@ -24,8 +34,9 @@ class Geometry(GeoJSON): class GeometryCollection(GeoJSON): - - """A collection of (WGS84) GIS geometries.""" + """ + Represents an abstract base class for collections of WGS84 geometries. + """ def __init__(self, geometries=None, **extra): super(GeometryCollection, self).__init__(**extra) @@ -59,4 +70,6 @@ class MultiPolygon(Geometry): class Default(object): - """GeoJSON default.""" + """ + GeoJSON default object. + """ diff --git a/geojson/mapping.py b/geojson/mapping.py index fb24c33..efee073 100644 --- a/geojson/mapping.py +++ b/geojson/mapping.py @@ -14,10 +14,19 @@ GEO_INTERFACE_MARKER = "__geo_interface__" def is_mapping(obj): + """ + Checks if the object is an instance of MutableMapping. + + :param obj: Object to be checked. + :return: Truth value of whether the object is an instance of + MutableMapping. + :rtype: bool + """ return isinstance(obj, MutableMapping) def to_mapping(obj): + mapping = getattr(obj, GEO_INTERFACE_MARKER, None) if mapping is not None: diff --git a/geojson/utils.py b/geojson/utils.py index 068b1c3..2639e45 100644 --- a/geojson/utils.py +++ b/geojson/utils.py @@ -1,7 +1,16 @@ """Coordinate utility functions.""" + def coords(obj): - """Yield all coordinate coordinate tuples from a geometry or feature.""" + """ + Yields the coordinates from a Feature or Geometry. + + :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 + """ + if isinstance(obj, (tuple, list)): coordinates = obj elif 'geometry' in obj: @@ -15,8 +24,20 @@ def coords(obj): for f in coords(e): yield f + def map_coords(func, obj): - """Return coordinates, mapped pair-wise using the provided function.""" + """ + Returns the coordinates from a Geometry after applying the provided + function to the tuples. + + :param obj: A geometry or feature to extract the coordinates from. + :type obj: Point, LineString, MultiPoint, MultiLineString, Polygon, + MultiPolygon + :return: The result of applying the function to each coordinate array. + :rtype: list + :raises ValueError: if the provided object is not a Geometry. + """ + if obj['type'] == 'Point': coordinates = tuple(map(func, obj['coordinates'])) elif obj['type'] in ['LineString', 'MultiPoint']: @@ -24,12 +45,12 @@ def map_coords(func, obj): elif obj['type'] in ['MultiLineString', 'Polygon']: coordinates = [[ tuple(map(func, c)) for c in curve] - for curve in obj['coordinates']] + 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']] + 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/geojson/validation.py b/geojson/validation.py new file mode 100644 index 0000000..25a4103 --- /dev/null +++ b/geojson/validation.py @@ -0,0 +1,97 @@ +import geojson + + +def is_valid(obj): + """ IsValid provides validation for GeoJSON objects + All of error messages obtained from the offical site + http://geojson.org/geojson-spec.html + + Args: + obj(geoJSON object): check validation + + Returns: + dict of two paremeters 'valid' and 'message'. + In the case if geoJSON object is valid, returns + {valid: 'yes', message: ''} + If json objects is not valid, returns + {valid: 'no', message:'explanation, why this object is not valid'} + + Example: + >> point = Point((10,20), (30,40)) + >> IsValid(point) + >> {valid: 'yes', message: ''} + """ + + if not isinstance(obj, geojson.GeoJSON): + return output('this is not GeoJSON object') + + if isinstance(obj, geojson.Point): + if len(obj['coordinates']) != 2: + return output('the "coordinates" member must be a single position') + + if isinstance(obj, geojson.MultiPoint): + if checkListOfObjects(obj['coordinates'], lambda x: len(x) == 2): + return output( + 'the "coordinates" member must be an array of positions' + ) + + if isinstance(obj, geojson.LineString): + if len(obj['coordinates']) < 2: + return output('the "coordinates" member must be an array ' + 'of two or more positions') + + if isinstance(obj, geojson.MultiLineString): + coord = obj['coordinates'] + if checkListOfObjects(coord, lambda x: len(x) >= 2): + return output('the "coordinates" member must be an array ' + 'of LineString coordinate arrays') + + if isinstance(obj, geojson.Polygon): + coord = obj['coordinates'] + lengths = all([len(elem) >= 4 for elem in coord]) + if lengths is False: + return output('LinearRing must contain with 4 or more positions') + + isring = all([elem[0] == elem[-1] for elem in coord]) + if isring is False: + return output('The first and last positions in LinearRing' + 'must be equivalent') + + if isinstance(obj, geojson.MultiPolygon): + if checkListOfObjects(obj['coordinates'], + lambda x: len(x) >= 4 and x[0] == x[-1]): + return output('the "coordinates" member must be an array' + 'of Polygon coordinate arrays') + + return output('') + + +def checkListOfObjects(coord, pred): + """ This method provides checking list of geojson objects such Multipoint or + MultiLineString that each element of the list is valid geojson object. + This is helpful method for IsValid. + + Args: + coord(list): List of coordinates + pred(function): Predicate to check validation of each + member in the coord + + Returns: + True if list contains valid objects, False otherwise + """ + return not isinstance(coord, list) or not all([pred(ls) for ls in coord]) + + +def output(message): + """ Output result for IsValid + + Args: + message - If message is not empty, + object is not valid + """ + result = {'valid': 'no', 'message': ''} + if message != '': + result['message'] = message + return result + result['valid'] = 'yes' + return result diff --git a/setup.py b/setup.py index 1ef9588..031562a 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ import sys with io.open("README.rst") as readme_file: readme_text = readme_file.read() + def test_suite(): import doctest try: @@ -19,21 +20,17 @@ def test_suite(): 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 " + sys.stderr.write("Sorry, only Python 2.7, and 3.x are supported " "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 +# Appears to be a problem in older versions of Python 2.7 import multiprocessing # NOQA setup( name="geojson", - version="1.0.9", + version="1.2.0", description="Python bindings and utilities for GeoJSON", license="BSD", keywords="gis geography json", @@ -48,7 +45,6 @@ setup( package_data={"geojson": ["*.rst"]}, install_requires=["setuptools"], test_suite="setup.test_suite", - tests_require=tests_require, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -57,7 +53,6 @@ setup( "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", diff --git a/tests/test_base.py b/tests/test_base.py index aabbfe7..a80355b 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -10,17 +10,26 @@ import geojson class TypePropertyTestCase(unittest.TestCase): def test_type_property(self): - json_str = '{"type": "Feature", "geometry": null, "id": 1, "properties": {"type": "é"}}' + 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}}' + 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"}}' + 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) diff --git a/tests/test_coords.py b/tests/test_coords.py index 5575158..3435786 100644 --- a/tests/test_coords.py +++ b/tests/test_coords.py @@ -20,7 +20,8 @@ class CoordsTestCase(unittest.TestCase): 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)],)]) + ([(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)) @@ -41,7 +42,7 @@ class CoordsTestCase(unittest.TestCase): def test_map_polygon(self): g = geojson.Polygon([ - [(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)],]) + [(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)) @@ -50,7 +51,8 @@ class CoordsTestCase(unittest.TestCase): 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)],)]) + ([(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)) diff --git a/tests/test_crs.py b/tests/test_crs.py index d6bcca5..cbcc106 100644 --- a/tests/test_crs.py +++ b/tests/test_crs.py @@ -7,24 +7,24 @@ class CRSTest(unittest.TestCase): def setUp(self): self.crs = geojson.crs.Named( - properties = { + 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"}' + 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"}' + 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 + self.assertEqual(actual, self.crs) diff --git a/tests/test_features.py b/tests/test_features.py index 4dbf51d..9f791fa 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -1,4 +1,4 @@ -from io import BytesIO +from io import BytesIO # NOQA import unittest import geojson @@ -10,18 +10,28 @@ class FeaturesTest(unittest.TestCase): A dictionary can satisfy the protocol """ f = { - 'type': 'Feature', - 'id': '1', - 'geometry': {'type': 'Point', 'coordinates': [53, -4]}, - 'properties': {'title': 'Dict 1'}, + '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"}') + 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"}') + 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_: @@ -45,22 +55,40 @@ class FeaturesTest(unittest.TestCase): 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"}') + 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"}') + json = ('{"geometry": {"coordinates": [53, -4],' + ' "type": "Point"},' + ' "id": "1",' + ' "properties":' + ' {"link": "http://example.org/features/1",' + ' "summary": "The first feature",' + ' "title": "Feature 1"},' + ' "type": "Feature"}') + self.assertEqual(geojson.dumps(feature, sort_keys=True), json) # 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"}}' + 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(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"}') + 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): @@ -72,8 +100,17 @@ class FeaturesTest(unittest.TestCase): @property def __geo_interface__(self): - return {"id": self.id, "properties": {"title": self.title}, "geometry": {"type": "Point", "coordinates": (self.x, self.y)}} + 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"}}') + 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 index 0eafe32..7e7cb54 100644 --- a/tests/test_geo_interface.py +++ b/tests/test_geo_interface.py @@ -46,7 +46,6 @@ class EncodingDecodingTest(unittest.TestCase): 'type': "Point", 'coordinates': self.latlng, }, - 'id': None, 'type': "Feature", 'properties': { 'name': self.name, @@ -72,28 +71,18 @@ class EncodingDecodingTest(unittest.TestCase): self.restaurant1 = Restaurant1(self.name, self.latlng) self.restaurant2 = Restaurant2(self.name, self.latlng) - self.restaurant_str = ( - '{' - '"coordinates": [-54, 4], ' - '"type": "Point"' - '}' - ) + 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"' - '}' - ) - + self.restaurant_feature_str = ('{"geometry":' + ' {"coordinates": [-54, 4],' + ' "type": "Point"},' + ' "properties":' + ' {"name": "In N Out Burger"},' + ' "type": "Feature"}') def test_encode(self): """ diff --git a/tests/test_validation.py b/tests/test_validation.py new file mode 100644 index 0000000..4086d52 --- /dev/null +++ b/tests/test_validation.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +""" +Tests for geojson/validation +""" + +import unittest + +import geojson + +is_valid = geojson.is_valid +YES = 'yes' +NO = 'no' + + +class TestValidationGeoJSONObject(unittest.TestCase): + + def test_invalid_jsonobject(self): + obj = [1, 2, 3] + self.assertEqual(is_valid(obj)['valid'], NO) + + def test_valid_jsonobject(self): + point = geojson.Point((-10.52, 2.33)) + self.assertEqual(is_valid(point)['valid'], YES) + + +class TestValidationPoint(unittest.TestCase): + + def test_invalid_point(self): + point = geojson.Point((10, 20, 30)) + self.assertEqual(is_valid(point)['valid'], NO) + + def test_valid_point(self): + point = geojson.Point((-3.68, 40.41)) + self.assertEqual(is_valid(point)['valid'], YES) + + +class TestValidationMultipoint(unittest.TestCase): + + def test_invalid_multipoint(self): + mpoint = geojson.MultiPoint( + [(3.5887, 10.44558), (2.5555, 3.887), (2.44, 3.44, 2.555)]) + self.assertEqual(is_valid(mpoint)['valid'], NO) + + def test_valid_multipoint(self): + mpoint = geojson.MultiPoint([(10, 20), (30, 40)]) + self.assertEqual(is_valid(mpoint)['valid'], YES) + + +class TestValidationLineString(unittest.TestCase): + + def test_invalid_linestring(self): + ls = geojson.LineString([(8.919, 44.4074)]) + self.assertEqual(is_valid(ls)['valid'], NO) + + def test_valid_linestring(self): + ls = geojson.LineString([(10, 5), (4, 3)]) + self.assertEqual(is_valid(ls)['valid'], YES) + + +class TestValidationMultiLineString(unittest.TestCase): + + def test_invalid_multilinestring(self): + mls = geojson.MultiLineString([[(10, 5), (20, 1)], []]) + self.assertEqual(is_valid(mls)['valid'], NO) + + def test_valid_multilinestring(self): + ls1 = [(3.75, 9.25), (-130.95, 1.52)] + ls2 = [(23.15, -34.25), (-1.35, -4.65), (3.45, 77.95)] + mls = geojson.MultiLineString([ls1, ls2]) + self.assertEqual(is_valid(mls)['valid'], YES) + + +class TestValidationPolygon(unittest.TestCase): + + def test_invalid_polygon(self): + poly1 = geojson.Polygon( + [[(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15)]]) + self.assertEqual(is_valid(poly1)['valid'], NO) + poly2 = geojson.Polygon( + [[(2.38, 57.322), (23.194, -20.28), + (-120.43, 19.15), (2.38, 57.323)]]) + self.assertEqual(is_valid(poly2)['valid'], NO) + + def test_valid_polygon(self): + poly = geojson.Polygon( + [[(2.38, 57.322), (23.194, -20.28), + (-120.43, 19.15), (2.38, 57.322)]]) + self.assertEqual(is_valid(poly)['valid'], YES) + + +class TestValidationMultiPolygon(unittest.TestCase): + + def test_invalid_multipolygon(self): + 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), + (-120.43, 19.15), (2.38, 57.322)] + multipoly = geojson.MultiPolygon([poly1, poly2]) + self.assertEqual(is_valid(multipoly)['valid'], NO) + + def test_valid_multipolygon(self): + poly1 = [(2.38, 57.322), (23.194, -20.28), + (-120.43, 19.15), (2.38, 57.322)] + poly2 = [(-5.34, 3.71), (28.74, 31.44), (28.55, 19.10), (-5.34, 3.71)] + poly3 = [(3.14, 23.17), (51.34, 27.14), (22, -18.11), (3.14, 23.17)] + multipoly = geojson.MultiPolygon([poly1, poly2, poly3]) + self.assertEqual(is_valid(multipoly)['valid'], YES) -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/python-geojson.git _______________________________________________ Pkg-grass-devel mailing list Pkg-grass-devel@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel