pax_global_header00006660000000000000000000000064147316135260014522gustar00rootroot0000000000000052 comment=68901cb26528faeb263e539e6e9bf48bb2525fc2 geojson-3.2.0/000077500000000000000000000000001473161352600131705ustar00rootroot00000000000000geojson-3.2.0/.flake8000066400000000000000000000000371473161352600143430ustar00rootroot00000000000000[flake8] max-line-length = 100 geojson-3.2.0/.github/000077500000000000000000000000001473161352600145305ustar00rootroot00000000000000geojson-3.2.0/.github/workflows/000077500000000000000000000000001473161352600165655ustar00rootroot00000000000000geojson-3.2.0/.github/workflows/lint.yml000066400000000000000000000004541473161352600202610ustar00rootroot00000000000000name: Lint on: [push, pull_request, workflow_dispatch] permissions: contents: read jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.x" - uses: pre-commit/action@v3.0.0 geojson-3.2.0/.github/workflows/release.yml000066400000000000000000000020351473161352600207300ustar00rootroot00000000000000name: Release on: push: tags: - '*' jobs: build: if: github.repository_owner == 'jazzband' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.x" cache: pip cache-dependency-path: setup.py - name: Install dependencies run: | python -m pip install -U pip python -m pip install -U setuptools twine wheel - name: Build package run: | python setup.py --version python setup.py sdist --format=gztar bdist_wheel twine check dist/* - name: Upload packages to Jazzband if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 with: user: jazzband password: ${{ secrets.JAZZBAND_RELEASE_KEY }} repository_url: https://jazzband.co/projects/geojson/upload geojson-3.2.0/.github/workflows/test.yml000066400000000000000000000015451473161352600202740ustar00rootroot00000000000000name: Test on: [push, pull_request, workflow_dispatch] env: FORCE_COLOR: 1 jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ['pypy3.9', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} allow-prereleases: true cache: pip cache-dependency-path: tox.ini - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install --upgrade tox - name: Tests run: | tox -e py - name: Upload coverage uses: codecov/codecov-action@v3 with: name: Python ${{ matrix.python-version }} geojson-3.2.0/.gitignore000066400000000000000000000001471473161352600151620ustar00rootroot00000000000000__pycache__/ *.py[cod] build/ dist/ sdist/ *.egg-info/ *.egg .tox/ .coverage coverage.xml .idea .venv* geojson-3.2.0/.pre-commit-config.yaml000066400000000000000000000007231473161352600174530ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: check-case-conflict - id: check-merge-conflict - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/tox-dev/tox-ini-fmt rev: 1.4.1 hooks: - id: tox-ini-fmt - repo: https://github.com/PyCQA/flake8 rev: 7.1.1 hooks: - id: flake8 ci: autoupdate_schedule: quarterly geojson-3.2.0/.readthedocs.yaml000066400000000000000000000004441473161352600164210ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 build: os: ubuntu-22.04 tools: python: "3.10" sphinx: configuration: docs/source/conf.py python: install: - method: pip path: . geojson-3.2.0/CHANGELOG.rst000066400000000000000000000146041473161352600152160ustar00rootroot00000000000000Changes ======= 3.2.0 ---------- - Add support for Python 3.13 - https://github.com/jazzband/geojson/pull/228 - Code modernization - https://github.com/jazzband/geojson/pull/218 - https://github.com/jazzband/geojson/pull/229 - RtD bugfix - https://github.com/jazzband/geojson/pull/227 3.1.0 ---------- - Add support for Python 3.12 - https://github.com/jazzband/geojson/pull/222 - https://github.com/jazzband/geojson/pull/211 - CI improvements - https://github.com/jazzband/geojson/pull/217 - https://github.com/jazzband/geojson/pull/212 - https://github.com/jazzband/geojson/pull/207 - Unit test improvements - https://github.com/jazzband/geojson/pull/215 - https://github.com/jazzband/geojson/pull/210 - https://github.com/jazzband/geojson/pull/209 3.0.1 (2023-02-15) ------------------ - Add Support for Python 3.11.x minor revisions - https://github.com/jazzband/geojson/pull/198 3.0.0 (2023-01-26) ------------------ - Support for Python versions 3.7-3.11 (Python 2 no longer supported) - Primary development branch renamed from `master` to `main` - Handle all real numbers as coordinates - https://github.com/jazzband/geojson/pull/188 - Default precision improvements - https://github.com/jazzband/geojson/pull/177 - CI improvements - https://github.com/jazzband/geojson/pull/172 - https://github.com/jazzband/geojson/pull/155 - utf-8 support added to `geojson.dumps()` - https://github.com/jazzband/geojson/pull/165 - Polygons now constrained to bounding box - https://github.com/jazzband/geojson/pull/147 - Better GeometryCollection handling in `util.coords()` - https://github.com/jazzband/geojson/pull/146 - Improved point validation - https://github.com/jazzband/geojson/pull/144 2.5.0 (2019-07-18) ------------------ - Add "precision" parameter to GeoJSON Object classes with default precision of 6 (0.1m) - https://github.com/jazzband/geojson/pull/131 - Fix bug where `map_geometries()` util was not preserving Feature IDs - https://github.com/jazzband/geojson/pull/128 - https://github.com/jazzband/geojson/pull/130 - Remove `crs` module and features to conform to official WGS84-only GeoJSON spec - https://github.com/jazzband/geojson/pull/124 - Set up semi-automatic PyPi releases via Travis/Jazzband - https://github.com/jazzband/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/geojson/pull/120 2.4.1 (2018-10-17) ------------------ - Allow ``FeatureCollections`` to be passed to ``coords`` - https://github.com/jazzband/geojson/pull/117 2.4.0 (2018-05-21) ------------------ - Additional functional maps for GeoJSON entities - https://github.com/jazzband/geojson/pull/112 2.3.0 (2017-09-18) ------------------ - Add ``__getitem__`` methods to sequence-like objects - https://github.com/jazzband/geojson/pull/103 2.2.0 (2017-09-17) ------------------ - Allow constructing geojson objects from geojson objects - https://github.com/jazzband/geojson/pull/104 2.1.0 (2017-08-29) ------------------ - Implement validation for GeometryCollection - https://github.com/jazzband/geojson/pull/102 2.0.0 (2017-07-28) ------------------ - Rewrite of validation mechanism (breaking change). - https://github.com/jazzband/geojson/pull/98 1.3.5 (2017-04-24) ------------------ - Changed the validator to allow elevation - https://github.com/jazzband/geojson/pull/92 1.3.4 (2017-02-11) ------------------ - Remove runtime dependency on setuptools - https://github.com/jazzband/geojson/pull/90 1.3.3 (2016-07-21) ------------------ - Add validate parameter to GeoJSON constructors - https://github.com/jazzband/geojson/pull/78 1.3.2 (2016-01-28) ------------------ - Add __version__ and __version_info__ attributes - https://github.com/jazzband/geojson/pull/74 1.3.1 (2015-10-12) ------------------ - Fix validation bug for MultiPolygons - https://github.com/jazzband/geojson/pull/63 1.3.0 (2015-08-11) ------------------ - Add utility to generate geometries with random data - https://github.com/jazzband/geojson/pull/60 1.2.2 (2015-07-13) ------------------ - Fix tests by including test file into build - https://github.com/jazzband/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/jazzband/geojson/pull/57 1.2.0 (2015-06-19) ------------------ - Utility function to validate GeoJSON objects - https://github.com/jazzband/geojson/pull/56 1.1.0 (2015-06-08) ------------------ - Stop outputting invalid GeoJSON value id=null on Features - https://github.com/jazzband/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. geojson-3.2.0/CODE_OF_CONDUCT.md000066400000000000000000000045071473161352600157750ustar00rootroot00000000000000# Code of Conduct As contributors and maintainers of the Jazzband projects, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in the Jazzband a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery - Personal attacks - Trolling or insulting/derogatory comments - Public or private harassment - Publishing other's private information, such as physical or electronic addresses, without explicit permission - Other unethical or unprofessional conduct The Jazzband roadies have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. By adopting this Code of Conduct, the roadies commit themselves to fairly and consistently applying these principles to every aspect of managing the jazzband projects. Roadies who do not follow or enforce the Code of Conduct may be permanently removed from the Jazzband roadies. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Roadies are obligated to maintain confidentiality with regard to the reporter of an incident. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version] [homepage]: https://contributor-covenant.org [version]: https://contributor-covenant.org/version/1/3/0/ geojson-3.2.0/CONTRIBUTING.rst000066400000000000000000000005221473161352600156300ustar00rootroot00000000000000.. image:: https://jazzband.co/static/img/jazzband.svg :target: https://jazzband.co/ :alt: Jazzband This is a `Jazzband `_ project. By contributing you agree to abide by the `Contributor Code of Conduct `_ and follow the `guidelines `_. geojson-3.2.0/LICENSE.rst000066400000000000000000000027071473161352600150120ustar00rootroot00000000000000Copyright © 2007-2019, contributors of 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 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 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. geojson-3.2.0/MANIFEST.in000066400000000000000000000000731473161352600147260ustar00rootroot00000000000000include *.rst recursive-include tests *.txt *.py *.geojson geojson-3.2.0/README.rst000066400000000000000000000362711473161352600146700ustar00rootroot00000000000000geojson ============== .. image:: https://github.com/jazzband/geojson/actions/workflows/test.yml/badge.svg :target: https://github.com/jazzband/geojson/actions/workflows/test.yml :alt: GitHub Actions .. image:: https://img.shields.io/codecov/c/github/jazzband/geojson.svg :target: https://codecov.io/github/jazzband/geojson?branch=main :alt: Codecov .. image:: https://jazzband.co/static/img/badge.svg :target: https://jazzband.co/ :alt: Jazzband .. image:: https://img.shields.io/pypi/dm/geojson.svg :target: https://pypi.org/project/geojson/ :alt: PyPI This Python 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 ------------ geojson is compatible with Python 3.7 - 3.13. The recommended way to install is via pip_: .. code:: pip install geojson .. _PyPi as 'geojson': https://pypi.python.org/pypi/geojson/ .. _pip: https://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), (-120.43, 19.15), (23.194, -20.28), (2.38, 57.322)]]) # doctest: +ELLIPSIS {"coordinates": [[[2.3..., 57.32...], [-120.4..., 19.1...], [23.19..., -20.2...]]], "type": "Polygon"} >>> # hole within polygon >>> Polygon([ ... [(2.38, 57.322), (-120.43, 19.15), (23.194, -20.28), (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...], [-120.4..., 19.1...], [23.19..., -20.2...]], [[-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"} Precision can be set at the package level by setting `geojson.geometry.DEFAULT_PRECISION` .. code:: python >>> import geojson >>> geojson.geometry.DEFAULT_PRECISION = 5 >>> from geojson import Point >>> Point((-115.12341234, 37.12341234)) # rounded to 8 decimal places {"coordinates": [-115.12341, 37.12341], "type": "Point"} After setting the DEFAULT_PRECISION, coordinates will be rounded off to that precision with `geojson.load` or `geojson.loads`. Following one of those with `geojson.dump` is a quick and easy way to scale down the precision of excessively precise, arbitrarily-sized GeoJSON data. 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 -m pip install tox && tox`. 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: https://geojson.org/ .. _The GeoJSON Format Specification: https://tools.ietf.org/html/rfc7946 .. _\_\_geo\_interface\_\_ Specification: https://gist.github.com/sgillies/2217756 geojson-3.2.0/docs/000077500000000000000000000000001473161352600141205ustar00rootroot00000000000000geojson-3.2.0/docs/source/000077500000000000000000000000001473161352600154205ustar00rootroot00000000000000geojson-3.2.0/docs/source/conf.py000066400000000000000000000022151473161352600167170ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'geojson' copyright = '2024, Sean Gillies, Matthew Russell, Corey Farwell, Blake Grotewold, \ Zsolt Ero, Sergey Romaov, Ray Riga' author = 'Sean Gillies, Matthew Russell, Corey Farwell, Blake Grotewold, Zsolt Ero, \ Sergey Romaov, Ray Riga' release = '3.1.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = ['sphinxcontrib.jquery'] templates_path = ['_templates'] exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_rtd_theme' html_theme_path = ['_theme'] html_static_path = ['_static'] geojson-3.2.0/docs/source/index.rst000066400000000000000000000004551473161352600172650ustar00rootroot00000000000000.. geojson documentation master file, created by sphinx-quickstart on Tue Aug 6 21:00:20 2024. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. toctree:: :maxdepth: 2 :caption: Contents: .. include:: ../../README.rst geojson-3.2.0/docs/source/theme.toml000066400000000000000000000001421473161352600174140ustar00rootroot00000000000000[theme] name = "sphinx_rtd_theme" base_url = "https://readthedocs.org/projects/sphinx-rtd-theme/" geojson-3.2.0/geojson/000077500000000000000000000000001473161352600146345ustar00rootroot00000000000000geojson-3.2.0/geojson/__init__.py000066400000000000000000000013771473161352600167550ustar00rootroot00000000000000from 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__]) geojson-3.2.0/geojson/_version.py000066400000000000000000000001211473161352600170240ustar00rootroot00000000000000__version__ = "3.2.0" __version_info__ = tuple(map(int, __version__.split("."))) geojson-3.2.0/geojson/base.py000066400000000000000000000106061473161352600161230ustar00rootroot00000000000000import 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().__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(f"{type_} is not a GeoJSON type") geojson_factory = getattr(geojson.factory, type_) instance = geojson_factory(**d) except (AttributeError, KeyError) as invalid: if strict: raise ValueError( f"Cannot coerce {ob!r} into " f"a valid GeoJSON structure: {invalid}" ) 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__) geojson-3.2.0/geojson/codec.py000066400000000000000000000031711473161352600162650ustar00rootroot00000000000000try: 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(f"Number {obj!r} is not JSON compliant") 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, ensure_ascii=False, **kwargs): return json.dumps(to_mapping(obj), cls=cls, allow_nan=allow_nan, ensure_ascii=ensure_ascii, **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 geojson-3.2.0/geojson/examples.py000066400000000000000000000040131473161352600170220ustar00rootroot00000000000000""" SimpleWebFeature is a working example of a class that satisfies the Python geo interface. """ class SimpleWebFeature: """ 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) 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 geojson-3.2.0/geojson/factory.py000066400000000000000000000007151473161352600166600ustar00rootroot00000000000000from 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]) geojson-3.2.0/geojson/feature.py000066400000000000000000000033251473161352600166440ustar00rootroot00000000000000from 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().__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().__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) geojson-3.2.0/geojson/geometry.py000077500000000000000000000104031473161352600170420ustar00rootroot00000000000000from decimal import Decimal from numbers import Number, Real from geojson.base import GeoJSON DEFAULT_PRECISION = 6 class Geometry(GeoJSON): """ Represents an abstract base class for a WGS84 geometry. """ def __init__(self, coordinates=None, validate=False, precision=None, **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().__init__(**extra) if precision is None: precision = DEFAULT_PRECISION self["coordinates"] = self.clean_coordinates( coordinates or [], precision) if validate: errors = self.errors() if errors: raise ValueError(f'{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, (Real, Decimal)): new_coords.append(round(coord, precision)) else: raise ValueError(f"{coord!r} is not a JSON compliant number") return new_coords class GeometryCollection(GeoJSON): """ Represents an abstract base class for collections of WGS84 geometries. """ def __init__(self, geometries=None, **extra): super().__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' for number in coord: if not isinstance(number, Number): return 'a position cannot have inner positions' 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: """ GeoJSON default object. """ geojson-3.2.0/geojson/mapping.py000066400000000000000000000013441473161352600166430ustar00rootroot00000000000000from collections.abc 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)) geojson-3.2.0/geojson/utils.py000066400000000000000000000162171473161352600163550ustar00rootroot00000000000000"""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: # FeatureCollection for f in obj['features']: yield from coords(f) elif 'geometry' in obj: # Feature yield from coords(obj['geometry']) elif 'geometries' in obj: # GeometryCollection for g in obj['geometries']: yield from coords(g) else: if isinstance(obj, (tuple, list)): coordinates = obj 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(f"Invalid geometry object {obj!r}") 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(f"Invalid GeoJSON object {obj!r}") 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 lon_min, lat_min, lon_max, lat_max = boundingBox def random_lon(): return random.uniform(lon_min, lon_max) def random_lat(): return random.uniform(lat_min, lat_max) def create_point(): return Point((random_lon(), random_lat())) def create_line(): return LineString([create_point() for _ in range(numberVertices)]) def create_poly(): ave_radius = 60 ctr_x = 0.1 ctr_y = 0.2 irregularity = clip(0.1, 0, 1) * math.tau / numberVertices spikeyness = clip(0.5, 0, 1) * ave_radius lower = (math.tau / numberVertices) - irregularity upper = (math.tau / numberVertices) + irregularity angle_steps = [] for _ in range(numberVertices): angle_steps.append(random.uniform(lower, upper)) sum_angle = sum(angle_steps) k = sum_angle / math.tau angle_steps = [x / k for x in angle_steps] points = [] angle = random.uniform(0, math.tau) for angle_step in angle_steps: r_i = clip(random.gauss(ave_radius, spikeyness), 0, 2 * ave_radius) x = ctr_x + r_i * math.cos(angle) y = ctr_y + r_i * math.sin(angle) x = (x + 180.0) * (abs(lon_min - lon_max) / 360.0) + lon_min y = (y + 90.0) * (abs(lat_min - lat_max) / 180.0) + lat_min x = clip(x, lon_min, lon_max) y = clip(y, lat_min, lat_max) points.append((x, y)) angle += angle_step points.append(points[0]) # append first point to the end return Polygon([points]) def clip(x, min_val, max_val): if min_val > max_val: return x else: return min(max(min_val, x), max_val) if featureType == 'Point': return create_point() if featureType == 'LineString': return create_line() if featureType == 'Polygon': return create_poly() raise ValueError(f"featureType: {featureType} is not supported.") geojson-3.2.0/setup.py000066400000000000000000000036161473161352600147100ustar00rootroot00000000000000from setuptools import setup import sys import re with open("README.rst") as readme_file: readme_text = readme_file.read() VERSIONFILE = "geojson/_version.py" verstrline = open(VERSIONFILE).read() VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" mo = re.search(VSRE, verstrline, re.M) if mo: verstr = mo.group(1) else: raise RuntimeError(f"Unable to find version string in {VERSIONFILE}.") major_version, minor_version = sys.version_info[:2] if not (major_version == 3 and 7 <= minor_version <= 13): sys.stderr.write("Sorry, only Python 3.7 - 3.13 are " "supported at this time.\n") exit(1) 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.maintainer@gmail.com", url="https://github.com/jazzband/geojson", long_description=readme_text, packages=["geojson"], package_dir={"geojson": "geojson"}, package_data={"geojson": ["*.rst"]}, install_requires=[], python_requires=">=3.7", 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 :: 3", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering :: GIS", ] ) geojson-3.2.0/tests/000077500000000000000000000000001473161352600143325ustar00rootroot00000000000000geojson-3.2.0/tests/__init__.py000066400000000000000000000006101473161352600164400ustar00rootroot00000000000000import doctest import glob import os optionflags = (doctest.REPORT_ONLY_FIRST_FAILURE | doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS) _basedir = os.path.dirname(__file__) paths = glob.glob(f"{_basedir}/*.txt") test_suite = doctest.DocFileSuite(*paths, **dict(module_relative=False, optionflags=optionflags)) geojson-3.2.0/tests/data.geojson000066400000000000000000000001301473161352600166230ustar00rootroot00000000000000{ "properties": { "Ã": "Ã" }, "type": "Feature", "geometry": null } geojson-3.2.0/tests/test_base.py000066400000000000000000000043161473161352600166610ustar00rootroot00000000000000""" 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() geojson-3.2.0/tests/test_constructor.py000066400000000000000000000016401473161352600203310ustar00rootroot00000000000000""" 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]]) geojson-3.2.0/tests/test_coords.py000066400000000000000000000103651473161352600172410ustar00rootroot00000000000000import 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_geometrycollection(self): p1 = geojson.Point((-115.11, 37.11)) p2 = geojson.Point((-115.22, 37.22)) ln = geojson.LineString( [(-115.3, 37.3), (-115.4, 37.4), (-115.5, 37.5)]) 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(geojson.GeometryCollection([p1, p2, ln, g])) pairs = set(itr) self.assertEqual(pairs, { (-115.11, 37.11), (-115.22, 37.22), (-115.3, 37.3), (-115.4, 37.4), (-115.5, 37.5), (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) }) 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": ""}) geojson-3.2.0/tests/test_features.py000066400000000000000000000112251473161352600175620ustar00rootroot00000000000000from 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) 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: 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"}}')) geojson-3.2.0/tests/test_geo_interface.py000066400000000000000000000120321473161352600205330ustar00rootroot00000000000000""" 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: """ Basic Restaurant class """ def __init__(self, name, latlng): super().__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())) geojson-3.2.0/tests/test_null_geometries.py000066400000000000000000000015141473161352600211410ustar00rootroot00000000000000import 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) geojson-3.2.0/tests/test_strict_json.py000066400000000000000000000027711473161352600203130ustar00rootroot00000000000000""" 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) geojson-3.2.0/tests/test_utils.py000066400000000000000000000064421473161352600171110ustar00rootroot00000000000000""" Tests for geojson generation """ import random import unittest import geojson from geojson.utils import generate_random, map_geometries def generate_bbox(): min_lat = random.random() * 180.0 - 90.0 max_lat = random.random() * 180.0 - 90.0 if min_lat > max_lat: min_lat, max_lat = max_lat, min_lat min_lon = random.random() * 360.0 - 180.0 max_lon = random.random() * 360.0 - 180.0 if min_lon > max_lon: min_lon, max_lon = max_lon, min_lon return [min_lon, min_lat, max_lon, max_lat] def check_polygon_bbox(polygon, bbox): min_lon, min_lat, max_lon, max_lat = bbox eps = 1e-3 for linear_ring in polygon['coordinates']: for coordinate in linear_ring: if not (min_lon-eps <= coordinate[0] <= max_lon+eps and min_lat-eps <= coordinate[1] <= max_lat+eps): return False return True def check_point_bbox(point, bbox): min_lon, min_lat, max_lon, max_lat = bbox eps = 1e-3 if ( min_lon - eps <= point["coordinates"][0] <= max_lon + eps and min_lat - eps <= point["coordinates"][1] <= max_lat + eps ): return True return False class TestGenerateRandom(unittest.TestCase): def test_simple_polygon(self): for _ in range(5000): bbox = [-180.0, -90.0, 180.0, 90.0] result = generate_random('Polygon') self.assertIsInstance(result, geojson.geometry.Polygon) self.assertTrue(geojson.geometry.check_polygon(result)) self.assertTrue(check_polygon_bbox(result, bbox)) def test_bbox_polygon(self): for _ in range(5000): bbox = generate_bbox() result = generate_random('Polygon', boundingBox=bbox) self.assertIsInstance(result, geojson.geometry.Polygon) self.assertTrue(geojson.geometry.check_polygon(result)) self.assertTrue(check_polygon_bbox(result, bbox)) def test_bbox_point(self): for _ in range(5000): bbox = generate_bbox() result = generate_random("Point", boundingBox=bbox) self.assertIsInstance(result, geojson.geometry.Point) self.assertTrue(geojson.geometry.check_point(result)) self.assertTrue(check_point_bbox(result, bbox)) def test_raise_value_error(self): with self.assertRaises(ValueError): generate_random("MultiPolygon") class TestMapGeometries(unittest.TestCase): def test_with_simple_type(self): new_point = map_geometries( lambda g: geojson.MultiPoint([g["coordinates"]]), geojson.Point((-115.81, 37.24)), ) self.assertEqual(new_point, geojson.MultiPoint([(-115.81, 37.24)])) def test_with_feature_collection(self): new_point = map_geometries( lambda g: geojson.MultiPoint([g["coordinates"]]), geojson.FeatureCollection([geojson.Point((-115.81, 37.24))]), ) self.assertEqual( new_point, geojson.FeatureCollection( [geojson.MultiPoint([geojson.Point((-115.81, 37.24))])] ), ) def test_raise_value_error(self): invalid_object = geojson.Feature(type="InvalidType") with self.assertRaises(ValueError): map_geometries(lambda g: g, invalid_object) geojson-3.2.0/tests/test_validation.py000066400000000000000000000147011473161352600201000ustar00rootroot00000000000000""" 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) 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) geojson-3.2.0/tox.ini000066400000000000000000000003661473161352600145100ustar00rootroot00000000000000[tox] requires = tox>=4.2 env_list = py{py3, 313, 312, 311, 310, 39, 38, 37} [testenv] deps = pytest pytest-cov pass_env = FORCE_COLOR commands = {envpython} -m pytest --cov geojson --cov tests --cov-report xml {posargs}