pax_global_header00006660000000000000000000000064135137706100014515gustar00rootroot0000000000000052 comment=9e8e69ac2d1aab0ef50c3dace558a1a5b313c346 python-geojson-2.5.0/000077500000000000000000000000001351377061000145045ustar00rootroot00000000000000python-geojson-2.5.0/.gitignore000066400000000000000000000001031351377061000164660ustar00rootroot00000000000000__pycache__/ *.py[cod] build/ dist/ sdist/ *.egg-info/ *.egg .tox/ python-geojson-2.5.0/.travis.yml000066400000000000000000000015711351377061000166210ustar00rootroot00000000000000language: python dist: xenial python: - 2.7 - 3.5 - 3.6 - 3.7 - pypy2.7-6.0 - pypy3.5-6.0 install: - pip install codecov script: - coverage run --branch --source=geojson setup.py test - coverage xml after_success: - codecov stages: - lint - test - deploy jobs: include: - stage: lint env: TOXENV=flake8 python: 3.7 install: pip install flake8 script: flake8 . - stage: deploy env: python: 3.7 install: skip script: skip deploy: provider: pypi server: https://jazzband.co/projects/python-geojson/upload distributions: sdist bdist_wheel user: jazzband password: secure: V2dsQfSMdChZ+G9w4cBcoa/Gmo/kJf2BMaN3sb7pDcAkTHuPYHppfSfj3vWDfpZFNobg8kTed4xjeYdKJ/oxFMpSGBWhGGpzQauzXO8n6LFXM44r/b5uXzyOyzdwwnjWXBn3NkubNU/n3dSjnRr9ZTeXGjiOGTPW1wAWjARJvgg= on: tags: true repo: jazzband/python-geojson python-geojson-2.5.0/CHANGELOG.rst000066400000000000000000000115071351377061000165310ustar00rootroot00000000000000Changes ======= 2.5.0 (2019-07-18) ------------------ - Add "precision" parameter to GeoJSON Object classes with default precision of 6 (0.1m) - https://github.com/jazzband/python-geojson/pull/131 - Fix bug where `map_geometries()` util was not preserving Feature IDs - https://github.com/jazzband/python-geojson/pull/128 - https://github.com/jazzband/python-geojson/pull/130 - Remove `crs` module and features to conform to official WGS84-only GeoJSON spec - https://github.com/jazzband/python-geojson/pull/124 - Set up semi-automatic PyPi releases via Travis/Jazzband - https://github.com/jazzband/python-geojson/pull/123 2.4.2 (2019-03-12) ------------------ - Tie Travis CI to jazzband instance - Remove EOL 3.3 and 3.4 version support - https://github.com/jazzband/python-geojson/pull/120 2.4.1 (2018-10-17) ------------------ - Allow ``FeatureCollections`` to be passed to ``coords`` - https://github.com/frewsxcv/python-geojson/pull/117 2.4.0 (2018-05-21) ------------------ - Additional functional maps for GeoJSON entities - https://github.com/frewsxcv/python-geojson/pull/112 2.3.0 (2017-09-18) ------------------ - Add ``__getitem__`` methods to sequence-like objects - https://github.com/frewsxcv/python-geojson/pull/103 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) ------------------ - Implement validation for GeometryCollection - https://github.com/frewsxcv/python-geojson/pull/102 2.0.0 (2017-07-28) ------------------ - Rewrite of validation mechanism (breaking change). - https://github.com/frewsxcv/python-geojson/pull/98 1.3.5 (2017-04-24) ------------------ - Changed the validator to allow elevation - https://github.com/frewsxcv/python-geojson/pull/92 1.3.4 (2017-02-11) ------------------ - Remove runtime dependency on setuptools - https://github.com/frewsxcv/python-geojson/pull/90 1.3.3 (2016-07-21) ------------------ - Add validate parameter to GeoJSON constructors - https://github.com/frewsxcv/python-geojson/pull/78 1.3.2 (2016-01-28) ------------------ - Add __version__ and __version_info__ attributes - https://github.com/frewsxcv/python-geojson/pull/74 1.3.1 (2015-10-12) ------------------ - Fix validation bug for MultiPolygons - https://github.com/frewsxcv/python-geojson/pull/63 1.3.0 (2015-08-11) ------------------ - Add utility to generate geometries with random data - https://github.com/frewsxcv/python-geojson/pull/60 1.2.2 (2015-07-13) ------------------ - Fix tests by including test file into build - https://github.com/frewsxcv/python-geojson/issues/61 - Build universal wheels - https://packaging.python.org/en/latest/distributing.html#universal-wheels 1.2.1 (2015-06-25) ------------------ - Encode long types correctly with Python 2.x - https://github.com/frewsxcv/python-geojson/pull/57 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) ------------------ - Fix bug where unicode/non-string properties with a 'type' key cause a crash 1.0.8 (2014-09-30) ------------------ - Fix bug where unicode keys don't get decoded properly - Add coords and map_coords utilities 1.0.7 (2014-04-19) ------------------ - Compatibility with Python 3.4 - Remove nose dependency - Convert doctests to unittests - Run tests using runtests.sh 1.0.6 (2014-01-18) ------------------ - Update README.rst documentation (fix errors, add examples) - Allow simplejson to be used again 1.0.5 (2013-11-16) ------------------ - Remove warning about RSTs in test/ upon install 1.0.4 (2013-11-16) ------------------ - 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. python-geojson-2.5.0/LICENSE.rst000066400000000000000000000027271351377061000163300ustar00rootroot00000000000000Copyright © 2014, contributors of python-geojson 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 THE CONTRIBUTORS OF PYTHON-GEOJSON BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-geojson-2.5.0/MANIFEST.in000066400000000000000000000000731351377061000162420ustar00rootroot00000000000000include *.rst recursive-include tests *.txt *.py *.geojson python-geojson-2.5.0/README.rst000066400000000000000000000345201351377061000161770ustar00rootroot00000000000000python-geojson ============== .. image:: https://img.shields.io/travis/jazzband/python-geojson.svg :target: https://travis-ci.org/jazzband/python-geojson .. image:: https://img.shields.io/codecov/c/github/jazzband/python-geojson.svg :target: https://codecov.io/github/jazzband/python-geojson?branch=master This library contains: - Functions for encoding and decoding GeoJSON_ formatted data - Classes for all GeoJSON Objects - An implementation of the Python `__geo_interface__ Specification`_ **Table of Contents** .. contents:: :backlinks: none :local: Installation ------------ python-geojson is compatible with Python 2.7, 3.5, 3.6 and 3.7. 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: https://tools.ietf.org/html/rfc7946#section-3 All object keys can also be used as attributes. The objects contained in GeometryCollection and FeatureCollection can be indexed directly. Point ~~~~~ .. code:: python >>> from geojson import Point >>> Point((-115.81, 37.24)) # doctest: +ELLIPSIS {"coordinates": [-115.8..., 37.2...], "type": "Point"} Visualize the result of the example above `here `__. General information about Point can be found in `Section 3.1.2`_ and `Appendix A: Points`_ within `The GeoJSON Format Specification`_. .. _Section 3.1.2: https://tools.ietf.org/html/rfc7946#section-3.1.2 .. _Appendix A\: Points: https://tools.ietf.org/html/rfc7946#appendix-A.1 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"} Visualize the result of the example above `here `__. General information about MultiPoint can be found in `Section 3.1.3`_ and `Appendix A: MultiPoints`_ within `The GeoJSON Format Specification`_. .. _Section 3.1.3: https://tools.ietf.org/html/rfc7946#section-3.1.3 .. _Appendix A\: MultiPoints: https://tools.ietf.org/html/rfc7946#appendix-A.4 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"} Visualize the result of the example above `here `__. General information about LineString can be found in `Section 3.1.4`_ and `Appendix A: LineStrings`_ within `The GeoJSON Format Specification`_. .. _Section 3.1.4: https://tools.ietf.org/html/rfc7946#section-3.1.4 .. _Appendix A\: LineStrings: https://tools.ietf.org/html/rfc7946#appendix-A.2 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"} Visualize the result of the example above `here `__. General information about MultiLineString can be found in `Section 3.1.5`_ and `Appendix A: MultiLineStrings`_ within `The GeoJSON Format Specification`_. .. _Section 3.1.5: https://tools.ietf.org/html/rfc7946#section-3.1.5 .. _Appendix A\: MultiLineStrings: https://tools.ietf.org/html/rfc7946#appendix-A.5 Polygon ~~~~~~~ .. code:: python >>> from geojson import Polygon >>> # no hole within polygon >>> Polygon([[(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.322)]]) # doctest: +ELLIPSIS {"coordinates": [[[2.3..., 57.32...], [23.19..., -20.2...], [-120.4..., 19.1...]]], "type": "Polygon"} >>> # hole within polygon >>> Polygon([ ... [(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.322)], ... [(-5.21, 23.51), (15.21, -10.81), (-20.51, 1.51), (-5.21, 23.51)] ... ]) # doctest: +ELLIPSIS {"coordinates": [[[2.3..., 57.32...], [23.19..., -20.2...], [-120.4..., 19.1...]], [[-5.2..., 23.5...], [15.2..., -10.8...], [-20.5..., 1.5...], [-5.2..., 23.5...]]], "type": "Polygon"} Visualize the results of the example above `here `__. General information about Polygon can be found in `Section 3.1.6`_ and `Appendix A: Polygons`_ within `The GeoJSON Format Specification`_. .. _Section 3.1.6: https://tools.ietf.org/html/rfc7946#section-3.1.6 .. _Appendix A\: Polygons: https://tools.ietf.org/html/rfc7946#appendix-A.3 MultiPolygon ~~~~~~~~~~~~ .. code:: python >>> from geojson import MultiPolygon >>> 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)],) ... ]) # 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"} Visualize the result of the example above `here `__. General information about MultiPolygon can be found in `Section 3.1.7`_ and `Appendix A: MultiPolygons`_ within `The GeoJSON Format Specification`_. .. _Section 3.1.7: https://tools.ietf.org/html/rfc7946#section-3.1.7 .. _Appendix A\: MultiPolygons: https://tools.ietf.org/html/rfc7946#appendix-A.6 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)]) >>> geo_collection = GeometryCollection([my_point, my_line]) >>> geo_collection # doctest: +ELLIPSIS {"geometries": [{"coordinates": [23.53..., -63.1...], "type": "Point"}, {"coordinates": [[-152.6..., 51.2...], [5.2..., 10.6...]], "type": "LineString"}], "type": "GeometryCollection"} >>> geo_collection[1] {"coordinates": [[-152.62, 51.21], [5.21, 10.69]], "type": "LineString"} >>> geo_collection[0] == geo_collection.geometries[0] True Visualize the result of the example above `here `__. General information about GeometryCollection can be found in `Section 3.1.8`_ and `Appendix A: GeometryCollections`_ within `The GeoJSON Format Specification`_. .. _Section 3.1.8: https://tools.ietf.org/html/rfc7946#section-3.1.8 .. _Appendix A\: GeometryCollections: https://tools.ietf.org/html/rfc7946#appendix-A.7 Feature ~~~~~~~ .. code:: python >>> from geojson import Feature, Point >>> my_point = Point((-3.68, 40.41)) >>> Feature(geometry=my_point) # doctest: +ELLIPSIS {"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"}, "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"} Visualize the results of the examples above `here `__. General information about Feature can be found in `Section 3.2`_ within `The GeoJSON Format Specification`_. .. _Section 3.2: https://tools.ietf.org/html/rfc7946#section-3.2 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))) >>> 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() [] >>> (feature_collection[0] == feature_collection['features'][0], feature_collection[1] == my_other_feature) (True, True) Visualize the result of the example above `here `__. 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 GeoJSON encoding/decoding ------------------------- All of the GeoJSON Objects implemented in this library can be encoded and decoded into raw GeoJSON with the ``geojson.dump``, ``geojson.dumps``, ``geojson.load``, and ``geojson.loads`` functions. Note that each of these functions is a wrapper around the core `json` function with the same name, and will pass through any additional arguments. This allows you to control the JSON formatting or parsing behavior with the underlying core `json` 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"}' Default and custom precision ~~~~~~~~~~~~~~~~~ GeoJSON Object-based classes in this package have an additional `precision` attribute which rounds off coordinates to 6 decimal places (roughly 0.1 meters) by default and can be customized per object instance. .. code:: python >>> from geojson import Point >>> Point((-115.123412341234, 37.123412341234)) # rounded to 6 decimal places by default {"coordinates": [-115.123412, 37.123412], "type": "Point"} >>> Point((-115.12341234, 37.12341234), precision=8) # rounded to 8 decimal places {"coordinates": [-115.12341234, 37.12341234], "type": "Point"} Helpful utilities ----------------- coords ~~~~~~ :code:`geojson.utils.coords` yields all coordinate tuples from a geometry or feature object. .. code:: python >>> import geojson >>> my_line = LineString([(-152.62, 51.21), (5.21, 10.69)]) >>> my_feature = geojson.Feature(geometry=my_line) >>> list(geojson.utils.coords(my_feature)) # doctest: +ELLIPSIS [(-152.62..., 51.21...), (5.21..., 10.69...)] map_coords ~~~~~~~~~~ :code:`geojson.utils.map_coords` maps a function over all coordinate values and returns a geometry of the same type. Useful for scaling a geometry. .. code:: python >>> import geojson >>> new_point = geojson.utils.map_coords(lambda x: x/2, geojson.Point((-115.81, 37.24))) >>> geojson.dumps(new_point, sort_keys=True) # doctest: +ELLIPSIS '{"coordinates": [-57.905..., 18.62...], "type": "Point"}' map_tuples ~~~~~~~~~~ :code:`geojson.utils.map_tuples` maps a function over all coordinates and returns a geometry of the same type. Useful for changing coordinate order or applying coordinate transforms. .. code:: python >>> import geojson >>> new_point = geojson.utils.map_tuples(lambda c: (c[1], c[0]), geojson.Point((-115.81, 37.24))) >>> geojson.dumps(new_point, sort_keys=True) # doctest: +ELLIPSIS '{"coordinates": [37.24..., -115.81], "type": "Point"}' map_geometries ~~~~~~~~~~ :code:`geojson.utils.map_geometries` maps a function over each geometry in the input. .. code:: python >>> import geojson >>> new_point = geojson.utils.map_geometries(lambda g: geojson.MultiPoint([g["coordinates"]]), geojson.GeometryCollection([geojson.Point((-115.81, 37.24))])) >>> geojson.dumps(new_point, sort_keys=True) '{"geometries": [{"coordinates": [[-115.81, 37.24]], "type": "MultiPoint"}], "type": "GeometryCollection"}' validation ~~~~~~~~~~ :code:`is_valid` property provides simple validation of GeoJSON objects. .. code:: python >>> import geojson >>> obj = geojson.Point((-3.68,40.41,25.14,10.34)) >>> obj.is_valid False :code:`errors` method provides collection of errors when validation GeoJSON objects. .. code:: python >>> import geojson >>> obj = geojson.Point((-3.68,40.41,25.14,10.34)) >>> obj.errors() 'a position must have exactly 2 or 3 values' generate_random ~~~~~~~~~~~~~~~ :code:`geojson.utils.generate_random` yields a geometry type with random data .. code:: python >>> import geojson >>> geojson.utils.generate_random("LineString") # doctest: +ELLIPSIS {"coordinates": [...], "type": "LineString"} >>> geojson.utils.generate_random("Polygon") # doctest: +ELLIPSIS {"coordinates": [...], "type": "Polygon"} Development ----------- To build this project, run :code:`python setup.py build`. To run the unit tests, run :code:`python setup.py test`. To run the style checks, run :code:`flake8` (install `flake8` if needed). Credits ------- * Sean Gillies * Matthew Russell * Corey Farwell * Blake Grotewold * Zsolt Ero * Sergey Romanov * Ray Riga .. _GeoJSON: http://geojson.org/ .. _The GeoJSON Format Specification: https://tools.ietf.org/html/rfc7946 .. _\_\_geo\_interface\_\_ Specification: https://gist.github.com/sgillies/2217756 python-geojson-2.5.0/geojson/000077500000000000000000000000001351377061000161505ustar00rootroot00000000000000python-geojson-2.5.0/geojson/__init__.py000066400000000000000000000013771351377061000202710ustar00rootroot00000000000000from geojson.codec import dump, dumps, load, loads, GeoJSONEncoder from geojson.utils import coords, map_coords from geojson.geometry import Point, LineString, Polygon from geojson.geometry import MultiLineString, MultiPoint, MultiPolygon from geojson.geometry import GeometryCollection from geojson.feature import Feature, FeatureCollection from geojson.base import GeoJSON from geojson._version import __version__, __version_info__ __all__ = ([dump, dumps, load, loads, GeoJSONEncoder] + [coords, map_coords] + [Point, LineString, Polygon] + [MultiLineString, MultiPoint, MultiPolygon] + [GeometryCollection] + [Feature, FeatureCollection] + [GeoJSON] + [__version__, __version_info__]) python-geojson-2.5.0/geojson/_version.py000066400000000000000000000001211351377061000203400ustar00rootroot00000000000000__version__ = "2.5.0" __version_info__ = tuple(map(int, __version__.split("."))) python-geojson-2.5.0/geojson/base.py000066400000000000000000000107171351377061000174420ustar00rootroot00000000000000from __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) def __repr__(self): return geojson.dumps(self, sort_keys=True) __str__ = __repr__ 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 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. :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() elif isinstance(ob, GeoJSON): instance = ob else: mapping = to_mapping(ob) d = {} for k in mapping: d[k] = mapping[k] try: type_ = d.pop("type") try: type_ = str(type_) except UnicodeEncodeError: # If the type contains non-ascii characters, we can assume # it's not a valid GeoJSON type raise AttributeError( "{0} is not a GeoJSON type").format(type_) geojson_factory = getattr(geojson.factory, type_) instance = geojson_factory(**d) except (AttributeError, KeyError) as invalid: if strict: msg = "Cannot coerce %r into a valid GeoJSON structure: %s" msg %= (ob, invalid) raise ValueError(msg) instance = ob return instance @property def is_valid(self): return not self.errors() def check_list_errors(self, checkFunc, lst): """Validation helper function.""" # check for errors on each subitem, filter only subitems with errors results = (checkFunc(i) for i in lst) return [err for err in results if err] def errors(self): """Return validation errors (if any). Implement in each subclass. """ # make sure that each subclass implements it's own validation function if self.__class__ != GeoJSON: raise NotImplementedError(self.__class__) python-geojson-2.5.0/geojson/codec.py000066400000000000000000000031121351377061000175740ustar00rootroot00000000000000try: import simplejson as json except ImportError: 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) # NOQA # 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): 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 python-geojson-2.5.0/geojson/examples.py000066400000000000000000000042021351377061000203360ustar00rootroot00000000000000 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): """ 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 = {'title': title, 'summary': summary, 'link': link} def as_dict(self): return { "type": "Feature", "id": self.id, "properties": self.properties, "geometry": self.geometry } __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 create_simple_web_feature(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(). :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'] 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 python-geojson-2.5.0/geojson/factory.py000066400000000000000000000007151351377061000201740ustar00rootroot00000000000000from 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 __all__ = ([Point, LineString, Polygon] + [MultiLineString, MultiPoint, MultiPolygon] + [GeometryCollection] + [Feature, FeatureCollection] + [GeoJSON]) python-geojson-2.5.0/geojson/feature.py000066400000000000000000000035341351377061000201620ustar00rootroot00000000000000""" SimpleWebFeature is a working example of a class that satisfies the Python geo interface. """ from geojson.base import GeoJSON class Feature(GeoJSON): """ 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) if id is not None: self["id"] = id self["geometry"] = (self.to_instance(geometry, strict=True) if geometry else None) self["properties"] = properties or {} def errors(self): geo = self.get('geometry') return geo.errors() if geo else None class FeatureCollection(GeoJSON): """ 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 def errors(self): return self.check_list_errors(lambda x: x.errors(), self.features) def __getitem__(self, key): try: return self.get("features", ())[key] except (KeyError, TypeError, IndexError): return super(GeoJSON, self).__getitem__(key) python-geojson-2.5.0/geojson/geometry.py000066400000000000000000000104011351377061000203510ustar00rootroot00000000000000import sys 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. """ def __init__(self, coordinates=None, validate=False, precision=6, **extra): """ Initialises a Geometry object. :param coordinates: Coordinates of the Geometry object. :type coordinates: tuple or list of tuple :param validate: Raise exception if validation errors are present? :type validate: boolean :param precision: Number of decimal places for lat/lon coords. :type precision: integer """ super(Geometry, self).__init__(**extra) self["coordinates"] = self.clean_coordinates( coordinates or [], precision) if validate: errors = self.errors() if errors: raise ValueError('{}: {}'.format(errors, coordinates)) @classmethod def clean_coordinates(cls, coords, precision): if isinstance(coords, cls): return coords['coordinates'] new_coords = [] if isinstance(coords, Geometry): coords = [coords] for coord in coords: if isinstance(coord, (list, tuple)): new_coords.append(cls.clean_coordinates(coord, precision)) elif isinstance(coord, Geometry): new_coords.append(coord['coordinates']) elif isinstance(coord, _JSON_compliant_types): new_coords.append(round(coord, precision)) else: raise ValueError("%r is not a JSON compliant number" % coord) return new_coords class GeometryCollection(GeoJSON): """ Represents an abstract base class for collections of WGS84 geometries. """ def __init__(self, geometries=None, **extra): super(GeometryCollection, self).__init__(**extra) self["geometries"] = geometries or [] def errors(self): errors = [geom.errors() for geom in self['geometries']] return [err for err in errors if err] def __getitem__(self, key): try: return self.get("geometries", ())[key] except (KeyError, TypeError, IndexError): return super(GeoJSON, self).__getitem__(key) # 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 'a position must have exactly 2 or 3 values' class Point(Geometry): def errors(self): return check_point(self['coordinates']) class MultiPoint(Geometry): def errors(self): return self.check_list_errors(check_point, self['coordinates']) 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): def errors(self): return check_line_string(self['coordinates']) class MultiLineString(Geometry): def errors(self): return self.check_list_errors(check_line_string, self['coordinates']) 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 'Each linear ring must contain at least 4 positions' isring = all(elem[0] == elem[-1] for elem in coord) if isring is False: return 'Each linear ring must end where it started' class Polygon(Geometry): def errors(self): return check_polygon(self['coordinates']) class MultiPolygon(Geometry): def errors(self): return self.check_list_errors(check_polygon, self['coordinates']) class Default(object): """ GeoJSON default object. """ python-geojson-2.5.0/geojson/mapping.py000066400000000000000000000014541351377061000201610ustar00rootroot00000000000000try: from collections.abc import MutableMapping except ImportError: from collections import MutableMapping try: import simplejson as json except ImportError: import json import geojson 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: return mapping if is_mapping(obj): return obj if isinstance(obj, geojson.GeoJSON): return dict(obj) return json.loads(json.dumps(obj)) python-geojson-2.5.0/geojson/utils.py000066400000000000000000000161121351377061000176630ustar00rootroot00000000000000"""Coordinate utility functions.""" def coords(obj): """ 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 """ # Handle recursive case first if 'features' in obj: for f in obj['features']: # For Python 2 compatibility # See https://www.reddit.com/r/learnpython/comments/4rc15s/yield_from_and_python_27/ # noqa: E501 for c in coords(f): yield c else: if isinstance(obj, (tuple, list)): coordinates = obj elif 'geometry' in obj: coordinates = obj['geometry']['coordinates'] else: coordinates = obj.get('coordinates', obj) for e in coordinates: if isinstance(e, (float, int)): yield tuple(coordinates) break for f in coords(e): yield f def map_coords(func, obj): """ Returns the mapped coordinates from a Geometry after applying the provided function to each dimension in tuples list (ie, linear scaling). :param func: Function to apply to individual coordinate values independently :type func: function :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 dimension in the array. :rtype: list :raises ValueError: if the provided object is not GeoJSON. """ def tuple_func(coord): return (func(coord[0]), func(coord[1])) return map_tuples(tuple_func, obj) def map_tuples(func, obj): """ Returns the mapped coordinates from a Geometry after applying the provided function to each coordinate. :param func: Function to apply to tuples :type func: function :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 dimension in the array. :rtype: list :raises ValueError: if the provided object is not GeoJSON. """ if obj['type'] == 'Point': coordinates = tuple(func(obj['coordinates'])) elif obj['type'] in ['LineString', 'MultiPoint']: coordinates = [tuple(func(c)) for c in obj['coordinates']] elif obj['type'] in ['MultiLineString', 'Polygon']: coordinates = [[ tuple(func(c)) for c in curve] for curve in obj['coordinates']] elif obj['type'] == 'MultiPolygon': coordinates = [[[ tuple(func(c)) for c in curve] for curve in part] for part in obj['coordinates']] elif obj['type'] in ['Feature', 'FeatureCollection', 'GeometryCollection']: return map_geometries(lambda g: map_tuples(func, g), obj) else: raise ValueError("Invalid geometry object %s" % repr(obj)) return {'type': obj['type'], 'coordinates': coordinates} def map_geometries(func, obj): """ Returns the result of passing every geometry in the given geojson object through func. :param func: Function to apply to tuples :type func: function :param obj: A geometry or feature to extract the coordinates from. :type obj: GeoJSON :return: The result of applying the function to each geometry :rtype: list :raises ValueError: if the provided object is not geojson. """ simple_types = [ 'Point', 'LineString', 'MultiPoint', 'MultiLineString', 'Polygon', 'MultiPolygon', ] if obj['type'] in simple_types: return func(obj) elif obj['type'] == 'GeometryCollection': geoms = [func(geom) if geom else None for geom in obj['geometries']] return {'type': obj['type'], 'geometries': geoms} elif obj['type'] == 'Feature': obj['geometry'] = func(obj['geometry']) if obj['geometry'] else None return obj elif obj['type'] == 'FeatureCollection': feats = [map_geometries(func, feat) for feat in obj['features']] return {'type': obj['type'], 'features': feats} else: raise ValueError("Invalid GeoJSON object %s" % repr(obj)) def generate_random(featureType, numberVertices=3, boundingBox=[-180.0, -90.0, 180.0, 90.0]): """ Generates random geojson features depending on the parameters passed through. The bounding box defaults to the world - [-180.0, -90.0, 180.0, 90.0]. The number of vertices defaults to 3. :param featureType: A geometry type :type featureType: Point, LineString, Polygon :param numberVertices: The number vertices that a linestring or polygon will have :type numberVertices: int :param boundingBox: A bounding box in which features will be restricted to :type boundingBox: list :return: The resulting random geojson object or geometry collection. :rtype: object :raises ValueError: if there is no featureType provided. """ from geojson import Point, LineString, Polygon import random import math lonMin = boundingBox[0] lonMax = boundingBox[2] def randomLon(): return random.uniform(lonMin, lonMax) latMin = boundingBox[1] latMax = boundingBox[3] def randomLat(): return random.uniform(latMin, latMax) def createPoint(): return Point((randomLon(), randomLat())) def createLine(): return LineString([createPoint() for unused in range(numberVertices)]) def createPoly(): aveRadius = 60 ctrX = 0.1 ctrY = 0.2 irregularity = clip(0.1, 0, 1) * 2 * math.pi / numberVertices spikeyness = clip(0.5, 0, 1) * aveRadius angleSteps = [] lower = (2 * math.pi / numberVertices) - irregularity upper = (2 * math.pi / numberVertices) + irregularity sum = 0 for i in range(numberVertices): tmp = random.uniform(lower, upper) angleSteps.append(tmp) sum = sum + tmp k = sum / (2 * math.pi) for i in range(numberVertices): angleSteps[i] = angleSteps[i] / k points = [] angle = random.uniform(0, 2 * math.pi) for i in range(numberVertices): r_i = clip(random.gauss(aveRadius, spikeyness), 0, 2 * aveRadius) x = ctrX + r_i * math.cos(angle) y = ctrY + r_i * math.sin(angle) points.append((int(x), int(y))) angle = angle + angleSteps[i] firstVal = points[0] points.append(firstVal) return Polygon([points]) def clip(x, min, max): if(min > max): return x elif(x < min): return min elif(x > max): return max else: return x if featureType == 'Point': return createPoint() if featureType == 'LineString': return createLine() if featureType == 'Polygon': return createPoly() python-geojson-2.5.0/setup.cfg000066400000000000000000000000341351377061000163220ustar00rootroot00000000000000[bdist_wheel] universal = 1 python-geojson-2.5.0/setup.py000066400000000000000000000044331351377061000162220ustar00rootroot00000000000000import io from setuptools import setup import sys import re with io.open("README.rst") as readme_file: readme_text = readme_file.read() VERSIONFILE = "geojson/_version.py" verstrline = open(VERSIONFILE, "rt").read() VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" mo = re.search(VSRE, verstrline, re.M) if mo: verstr = mo.group(1) else: raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,)) def test_suite(): import doctest try: import unittest2 as unittest except ImportError: import unittest suite = unittest.TestLoader().discover("tests") suite.addTest(doctest.DocFileSuite("README.rst")) return suite major_version, minor_version = sys.version_info[:2] if not ((major_version == 2 and minor_version == 7) or (major_version == 3 and minor_version >= 5)): sys.stderr.write("Sorry, only Python 2.7, 3.5, 3.6 and 3.7 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.7 import multiprocessing # NOQA setup( name="geojson", version=verstr, description="Python bindings and utilities for GeoJSON", license="BSD", keywords="gis geography json", author="Sean Gillies", author_email="sgillies@frii.com", maintainer="Ray Riga", maintainer_email="ray@strongoutput.com", url="https://github.com/jazzband/python-geojson", long_description=readme_text, packages=["geojson"], package_dir={"geojson": "geojson"}, package_data={"geojson": ["*.rst"]}, install_requires=[], test_suite="setup.test_suite", 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", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Scientific/Engineering :: GIS", ] ) python-geojson-2.5.0/tests/000077500000000000000000000000001351377061000156465ustar00rootroot00000000000000python-geojson-2.5.0/tests/__init__.py000066400000000000000000000006121351377061000177560ustar00rootroot00000000000000import 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)) python-geojson-2.5.0/tests/data.geojson000066400000000000000000000001311351377061000201400ustar00rootroot00000000000000{ "properties": { "Ã": "Ã" }, "type": "Feature", "geometry": null } python-geojson-2.5.0/tests/test_base.py000066400000000000000000000043461351377061000202000ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Tests for geojson/base.py """ import unittest import geojson class TypePropertyTestCase(unittest.TestCase): def test_type_property(self): json_str = ('{"type": "Feature",' ' "geometry": null,' ' "id": 1,' ' "properties": {"type": "é"}}') geojson_obj = geojson.loads(json_str) self.assertTrue(isinstance(geojson_obj, geojson.GeoJSON)) self.assertTrue("type" in geojson_obj.properties) json_str = ('{"type": "Feature",' ' "geometry": null,' ' "id": 1,' ' "properties": {"type": null}}') geojson_obj = geojson.loads(json_str) self.assertTrue(isinstance(geojson_obj, geojson.GeoJSON)) self.assertTrue("type" in geojson_obj.properties) json_str = ('{"type": "Feature",' ' "geometry": null,' ' "id": 1,' ' "properties": {"type": "meow"}}') geojson_obj = geojson.loads(json_str) self.assertTrue(isinstance(geojson_obj, geojson.GeoJSON)) self.assertTrue("type" in geojson_obj.properties) class OperatorOverloadingTestCase(unittest.TestCase): """ Tests for operator overloading """ def setUp(self): self.coords = (12, -5) self.point = geojson.Point(self.coords) def test_setattr(self): new_coords = (27, 42) self.point.coordinates = new_coords self.assertEqual(self.point['coordinates'], new_coords) def test_getattr(self): self.assertEqual(self.point['coordinates'], self.point.coordinates) def test_delattr(self): del self.point.coordinates self.assertFalse(hasattr(self.point, 'coordinates')) class BaseTestCase(unittest.TestCase): def test_to_instance(self): FAKE = 'fake' self.assertEqual(FAKE, geojson.GeoJSON.to_instance( None, (lambda: FAKE))) with self.assertRaises(ValueError): geojson.GeoJSON.to_instance({"type": "Not GeoJSON"}, strict=True) def test_errors(self): class Fake(geojson.GeoJSON): pass with self.assertRaises(NotImplementedError): Fake().errors() python-geojson-2.5.0/tests/test_constructor.py000066400000000000000000000016701351377061000216500ustar00rootroot00000000000000# -*- 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.assertEqual(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.assertEqual(mp.coordinates, [a, b]) mls = geojson.MultiLineString([geojson.LineString([a, b]), [a, c]]) self.assertEqual(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.assertEqual(geojson.MultiPolygon([poly, poly2]).coordinates, [[outer], [outer, other]]) python-geojson-2.5.0/tests/test_coords.py000066400000000000000000000066671351377061000205670ustar00rootroot00000000000000import unittest import geojson from geojson.utils import coords, map_coords TOO_PRECISE = (1.12341234, -2.12341234) class CoordsTestCase(unittest.TestCase): def test_point(self): itr = coords(geojson.Point((-115.81, 37.24))) self.assertEqual(next(itr), (-115.81, 37.24)) def test_point_rounding(self): itr = coords(geojson.Point(TOO_PRECISE)) self.assertEqual(next(itr), tuple([round(c, 6) for c in TOO_PRECISE])) def test_dict(self): itr = coords({'type': 'Point', 'coordinates': [-115.81, 37.24]}) self.assertEqual(next(itr), (-115.81, 37.24)) def test_point_feature(self): itr = coords(geojson.Feature(geometry=geojson.Point((-115.81, 37.24)))) self.assertEqual(next(itr), (-115.81, 37.24)) def test_multipolygon(self): g = geojson.MultiPolygon([ ([(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)],), ([(23.18, -34.29), (-1.31, -4.61), (3.41, 77.91), (23.18, -34.29)],)]) itr = coords(g) pairs = list(itr) self.assertEqual(pairs[0], (3.78, 9.28)) self.assertEqual(pairs[-1], (23.18, -34.29)) def test_featurecollection(self): p1 = geojson.Feature(geometry=geojson.Point((-115.11, 37.11))) p2 = geojson.Feature(geometry=geojson.Point((-115.22, 37.22))) itr = coords(geojson.FeatureCollection([p1, p2])) pairs = list(itr) self.assertEqual(pairs[0], (-115.11, 37.11)) self.assertEqual(pairs[1], (-115.22, 37.22)) def test_map_point(self): result = map_coords(lambda x: x, geojson.Point((-115.81, 37.24))) self.assertEqual(result['type'], 'Point') self.assertEqual(result['coordinates'], (-115.81, 37.24)) def test_map_linestring(self): g = geojson.LineString( [(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)]) result = map_coords(lambda x: x, g) self.assertEqual(result['type'], 'LineString') self.assertEqual(result['coordinates'][0], (3.78, 9.28)) self.assertEqual(result['coordinates'][-1], (3.78, 9.28)) def test_map_polygon(self): g = geojson.Polygon([ [(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)], ]) result = map_coords(lambda x: x, g) self.assertEqual(result['type'], 'Polygon') self.assertEqual(result['coordinates'][0][0], (3.78, 9.28)) self.assertEqual(result['coordinates'][0][-1], (3.78, 9.28)) def test_map_multipolygon(self): g = geojson.MultiPolygon([ ([(3.78, 9.28), (-130.91, 1.52), (35.12, 72.234), (3.78, 9.28)],), ([(23.18, -34.29), (-1.31, -4.61), (3.41, 77.91), (23.18, -34.29)],)]) result = map_coords(lambda x: x, g) self.assertEqual(result['type'], 'MultiPolygon') self.assertEqual(result['coordinates'][0][0][0], (3.78, 9.28)) self.assertEqual(result['coordinates'][-1][-1][-1], (23.18, -34.29)) def test_map_feature(self): g = geojson.Feature( id='123', geometry=geojson.Point([-115.81, 37.24]) ) result = map_coords(lambda x: x, g) self.assertEqual(result['type'], 'Feature') self.assertEqual(result['id'], '123') self.assertEqual(result['geometry']['coordinates'], (-115.81, 37.24)) def test_map_invalid(self): with self.assertRaises(ValueError): map_coords(lambda x: x, {"type": ""}) python-geojson-2.5.0/tests/test_features.py000066400000000000000000000113561351377061000211030ustar00rootroot00000000000000try: from StringIO import StringIO except ImportError: from io import StringIO import unittest import geojson class FeaturesTest(unittest.TestCase): def test_protocol(self): """ A dictionary can satisfy the protocol """ f = { 'type': 'Feature', 'id': '1', 'geometry': {'type': 'Point', 'coordinates': [53.0, -4.0]}, 'properties': {'title': 'Dict 1'}, } json = geojson.dumps(f, sort_keys=True) self.assertEqual(json, '{"geometry":' ' {"coordinates": [53.0, -4.0],' ' "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.0, -4.0],' ' "type": "Point"},' ' "id": "1",' ' "properties": {"title": "Dict 1"},' ' "type": "Feature"}') def test_unicode_properties(self): with open("tests/data.geojson") as file_: obj = geojson.load(file_) geojson.dump(obj, StringIO()) def test_feature_class(self): """ Test the Feature class """ from geojson.examples import SimpleWebFeature feature = SimpleWebFeature( id='1', geometry={'type': 'Point', 'coordinates': [53.0, -4.0]}, title='Feature 1', summary='The first feature', link='http://example.org/features/1' ) # It satisfies the feature protocol self.assertEqual(feature.id, '1') self.assertEqual(feature.properties['title'], 'Feature 1') self.assertEqual(feature.properties['summary'], 'The first feature') self.assertEqual(feature.properties['link'], 'http://example.org/features/1') self.assertEqual(geojson.dumps(feature.geometry, sort_keys=True), '{"coordinates": [53.0, -4.0], "type": "Point"}') # Encoding json = ('{"geometry": {"coordinates": [53.0, -4.0],' ' "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.create_simple_web_feature json = ('{"geometry": {"type": "Point",' ' "coordinates": [53.0, -4.0]},' ' "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)), "") 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.0, -4.0], "type": "Point"}') def test_geo_interface(self): class Thingy(object): def __init__(self, id, title, x, y): self.id = id self.title = title self.x = x self.y = y @property def __geo_interface__(self): return ({"id": self.id, "properties": {"title": self.title}, "geometry": {"type": "Point", "coordinates": (self.x, self.y)}}) ob = Thingy('1', 'thingy one', -106.0, 40.0) self.assertEqual(geojson.dumps(ob.__geo_interface__['geometry'], sort_keys=True), '{"coordinates": [-106.0, 40.0], "type": "Point"}') self.assertEqual(geojson.dumps(ob, sort_keys=True), ('{"geometry": {"coordinates": [-106.0, 40.0],' ' "type": "Point"},' ' "id": "1",' ' "properties": {"title": "thingy one"}}')) python-geojson-2.5.0/tests/test_geo_interface.py000066400000000000000000000120621351377061000220520ustar00rootroot00000000000000""" Encoding/decoding custom objects with __geo_interface__ """ import unittest import geojson from geojson.mapping import to_mapping class EncodingDecodingTest(unittest.TestCase): def setUp(self): class Restaurant(object): """ Basic Restaurant class """ def __init__(self, name, latlng): super(Restaurant, self).__init__() self.name = name self.latlng = latlng class Restaurant1(Restaurant): """ Extends Restaurant with __geo_interface__ returning dict """ @property def __geo_interface__(self): return {'type': "Point", 'coordinates': self.latlng} class Restaurant2(Restaurant): """ Extends Restaurant with __geo_interface__ returning another __geo_interface__ object """ @property def __geo_interface__(self): return geojson.Point(self.latlng) class RestaurantFeature1(Restaurant): """ Extends Restaurant with __geo_interface__ returning dict """ @property def __geo_interface__(self): return { 'geometry': { 'type': "Point", 'coordinates': self.latlng, }, 'type': "Feature", 'properties': { 'name': self.name, }, } class RestaurantFeature2(Restaurant): """ Extends Restaurant with __geo_interface__ returning another __geo_interface__ object """ @property def __geo_interface__(self): return geojson.Feature( geometry=geojson.Point(self.latlng), properties={'name': self.name}) self.name = "In N Out Burger" self.latlng = [-54.0, 4.0] self.restaurant_nogeo = Restaurant(self.name, self.latlng) self.restaurant1 = Restaurant1(self.name, self.latlng) self.restaurant2 = Restaurant2(self.name, self.latlng) self.restaurant_str = ('{"coordinates": [-54.0, 4.0],' ' "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.0, 4.0],' ' "type": "Point"},' ' "properties":' ' {"name": "In N Out Burger"},' ' "type": "Feature"}') def test_encode(self): """ Ensure objects that implement __geo_interface__ can be encoded into GeoJSON strings """ actual = geojson.dumps(self.restaurant1, sort_keys=True) self.assertEqual(actual, self.restaurant_str) actual = geojson.dumps(self.restaurant2, sort_keys=True) self.assertEqual(actual, self.restaurant_str) # Classes that don't implement geo interface should raise TypeError with self.assertRaises(TypeError): geojson.dumps(self.restaurant_nogeo) def test_encode_nested(self): """ Ensure nested objects that implement __geo_interface__ can be encoded into GeoJSON strings """ actual = geojson.dumps(self.restaurant_feature1, sort_keys=True) self.assertEqual(actual, self.restaurant_feature_str) actual = geojson.dumps(self.restaurant_feature2, sort_keys=True) self.assertEqual(actual, self.restaurant_feature_str) def test_decode(self): """ Ensure a GeoJSON string can be decoded into GeoJSON objects """ actual = geojson.loads(self.restaurant_str) expected = self.restaurant1.__geo_interface__ self.assertEqual(expected, actual) def test_decode_nested(self): """ Ensure a GeoJSON string can be decoded into nested GeoJSON objects """ actual = geojson.loads(self.restaurant_feature_str) expected = self.restaurant_feature1.__geo_interface__ self.assertEqual(expected, actual) nested = expected['geometry'] expected = self.restaurant1.__geo_interface__ self.assertEqual(nested, expected) def test_invalid(self): with self.assertRaises(ValueError) as cm: geojson.loads('{"type":"Point", "coordinates":[[-Infinity, 4.0]]}') self.assertIn('is not JSON compliant', str(cm.exception)) def test_mapping(self): self.assertEqual(to_mapping(geojson.Point([1.0, 2.0])), {"coordinates": [1.0, 2.0], "type": "Point"}) def test_GeoJSON(self): self.assertEqual(None, geojson.GeoJSON().__geo_interface__) self.assertEqual({"type": "GeoJSON"}, to_mapping(geojson.GeoJSON())) python-geojson-2.5.0/tests/test_null_geometries.py000066400000000000000000000015141351377061000224550ustar00rootroot00000000000000import unittest import geojson class NullGeometriesTest(unittest.TestCase): def test_null_geometry_explicit(self): feature = geojson.Feature( id=12, geometry=None, properties={'foo': 'bar'} ) actual = geojson.dumps(feature, sort_keys=True) expected = ('{"geometry": null, "id": 12, "properties": {"foo": ' '"bar"}, "type": "Feature"}') self.assertEqual(actual, expected) def test_null_geometry_implicit(self): feature = geojson.Feature( id=12, properties={'foo': 'bar'} ) actual = geojson.dumps(feature, sort_keys=True) expected = ('{"geometry": null, "id": 12, "properties": {"foo": ' '"bar"}, "type": "Feature"}') self.assertEqual(actual, expected) python-geojson-2.5.0/tests/test_strict_json.py000066400000000000000000000027711351377061000216270ustar00rootroot00000000000000""" GeoJSON produces and consumes only strict JSON. NAN and Infinity are not permissible values according to the JSON specification. """ import unittest import geojson class StrictJsonTest(unittest.TestCase): def test_encode_nan(self): """ Ensure Error is raised when encoding nan """ self._raises_on_dump({ "type": "Point", "coordinates": (float("nan"), 1.0), }) def test_encode_inf(self): """ Ensure Error is raised when encoding inf or -inf """ self._raises_on_dump({ "type": "Point", "coordinates": (float("inf"), 1.0), }) self._raises_on_dump({ "type": "Point", "coordinates": (float("-inf"), 1.0), }) 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 """ self._raises_on_load('{"type": "Point", "coordinates": [1.0, NaN]}') def test_decode_inf(self): """ Ensure Error is raised when decoding Infinity or -Infinity """ self._raises_on_load( '{"type": "Point", "coordinates": [1.0, Infinity]}') self._raises_on_load( '{"type": "Point", "coordinates": [1.0, -Infinity]}') def _raises_on_load(self, unstrict): with self.assertRaises(ValueError): geojson.loads(unstrict) python-geojson-2.5.0/tests/test_validation.py000066400000000000000000000145641351377061000214230ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Tests for geojson/validation """ 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): with self.assertRaises(ValueError): geojson.Point(INVALID_POS, validate=True) def test_invalid_geometry_without_validate(self): geojson.Point(INVALID_POS) geojson.Point(INVALID_POS, validate=False) def test_valid_geometry(self): geojson.Point(VALID_POS, validate=True) def test_valid_geometry_with_elevation(self): 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): def test_valid_jsonobject(self): point = geojson.Point((-10.52, 2.33)) self.assertEqual(point.is_valid, True) class TestValidationPoint(unittest.TestCase): def test_invalid_point(self): point = geojson.Point((10, 20, 30, 40)) self.assertEqual(point.is_valid, False) def test_valid_point(self): point = geojson.Point((-3.68, 40.41)) self.assertEqual(point.is_valid, True) def test_valid_point_with_elevation(self): point = geojson.Point((-3.68, 40.41, 3.45)) self.assertEqual(point.is_valid, True) class TestValidationMultipoint(unittest.TestCase): def test_invalid_multipoint(self): mpoint = geojson.MultiPoint( [(3.5887,), (3.5887, 10.44558), (2.5555, 3.887, 4.56), (2.44, 3.44, 2.555, 4.56)]) self.assertEqual(mpoint.is_valid, False) def test_valid_multipoint(self): mpoint = geojson.MultiPoint([(10, 20), (30, 40)]) self.assertEqual(mpoint.is_valid, True) def test_valid_multipoint_with_elevation(self): mpoint = geojson.MultiPoint([(10, 20, 30), (30, 40, 50)]) self.assertEqual(mpoint.is_valid, True) class TestValidationLineString(unittest.TestCase): def test_invalid_linestring(self): 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)]) self.assertEqual(ls.is_valid, True) 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) 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(mls.is_valid, True) 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) poly2 = geojson.Polygon( [[(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.323)]]) self.assertEqual(poly2.is_valid, False) 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(poly.is_valid, True) 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), (-120.43, 19.15), (2.38, 57.322)] multipoly = geojson.MultiPolygon([poly1, poly2]) self.assertEqual(multipoly.is_valid, False) 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(multipoly.is_valid, True) class TestValidationGeometryCollection(unittest.TestCase): def test_invalid_geometrycollection(self): point = geojson.Point((10, 20)) bad_poly = geojson.Polygon([[(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (25.44, -17.91)]]) geom_collection = geojson.GeometryCollection( geometries=[point, bad_poly] ) self.assertFalse(geom_collection.is_valid) def test_valid_geometrycollection(self): point = geojson.Point((10, 20)) poly = geojson.Polygon([[(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.322)]]) geom_collection = geojson.GeometryCollection( geometries=[point, poly] ) self.assertTrue(geom_collection.is_valid) python-geojson-2.5.0/tox.ini000066400000000000000000000005201351377061000160140ustar00rootroot00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py{27,35,36,37}, pypy, pypy3 [testenv] commands = {envpython} setup.py test