pax_global_header00006660000000000000000000000064136722415310014516gustar00rootroot0000000000000052 comment=b0dfbd0d5f5095ff7483a4167820e8e48417ffe5 mercantile-1.1.5/000077500000000000000000000000001367224153100136455ustar00rootroot00000000000000mercantile-1.1.5/.gitignore000066400000000000000000000015041367224153100156350ustar00rootroot00000000000000 # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover rasterio/_*.html # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # IDE's etc. .idea/ venv/ venv2/ # vim .*.swp .DS_Store .pytest_cache package-lock.json node_modules .hypothesis mercantile-1.1.5/.travis.yml000066400000000000000000000007551367224153100157650ustar00rootroot00000000000000language: python dist: xenial python: - "3.5" - "3.6" - "3.7" - "3.8" install: - "pip install -r requirements.txt" - "pip install pytest-cov~=2.8 pytest~=5.3.0" - "pip install -e .[test]" script: - "python -m pytest --cov mercantile --cov-report term-missing" - "python -m pydocstyle mercantile tests" after_success: - coveralls deploy: on: repo: mapbox/mercantile python: 3.7 tags: true provider: pypi distributions: "sdist bdist_wheel" user: "@token" mercantile-1.1.5/AUTHORS.txt000066400000000000000000000011371367224153100155350ustar00rootroot00000000000000Sean Gillies Matthew Perry Felix Jung Patrick M Young Amit Kapadia Brendan Ward Sam Murphy dnomadb Andrew Harvey Daniel J. H Jacob Wasserman James Gill Jeremiah Cooper Rohit Singh Stefano Costa drnextgis jqtrde mercantile-1.1.5/CHANGES.txt000066400000000000000000000144511367224153100154630ustar00rootroot00000000000000Changes ======= 1.1.5 (2020-06-16) ------------------ - A bug in ``simplify()`` has been fixed and the algorithm has been improved (#111). - Implementation of ``tile()`` has been simplified and corrected (#114). 1.1.4 (2020-04-28) ------------------ - Change a list comprehension to a generator expression in simplify(). - Change DeprecationWarning introduced in 1.1.3 to a UserWarning to increase visibility. - Ensure symmetric InvalidLatitudeErrors at both poles (#106). 1.1.3 (2020-04-13) ------------------ - Warn about deprecation of support when mercantile is imported with Python versions < 3. Mercantile 2.0 will not be compatible with Python 2.7. - The bounding tile of the bounds of a tile is now that same tile (#100). 1.1.2 (2019-08-05) ------------------ - fid of 0 is now allowed by the ``feature()`` function (#85). - Passing the bounds of a tile to ``tiles()`` now yields exactly that tile only (given the correct zoom), resolving #84 and #87. - QuadKeyError derives from ValueError again, resolving issue #98, but only until version 2.0. A deprecation warning explaining this is raised from quadkey_to_tile just before QuadKeyError is raised. - Format source using black. 1.1.1 (2019-07-01) ------------------ - Update tests to work with pytest 5. 1.1.0 (2019-06-21) ------------------ - A zoom keyword argument has been added to both ``children()`` and ``parent()``, allowing the user to specify the zoom level for each (#94). - A new ``simplify()`` function merges child to parent tiles, upwards, producing the shortest sequence of tiles that cover the same space (#94). - The mercantile module now raises only exceptions deriving from MercantileError. Such errors indicate improper usage of the mercantile module. The occurance of a builtin exception indicates a bug in mercantile. 1.0.4 (2018-06-04) ------------------ - Added missing docstrings (#80). 1.0.3 (2018-05-17) ------------------ - Support a single zoom value passed to ``tiles()`` as advertised (#78). 1.0.2 (2018-05-08) ------------------ - The ``xy`` function returns ``float(inf)`` and ``float(-inf)`` for y at the North and South pole, respectively, instead of raising an exception (#76). 1.0.1 (2018-02-15) ------------------ - Corrected an error in the ``bbox`` parameter description in the ``bounding_tile`` docstring (#73). - Corrected an error in the geojson.io example in the CLI docs: the proper usage is ``mercantile shapes --collect`` (#71, #72). - Add missing ``--version`` option to mercantile command. - Python 3.6 has been added to the Travis build matrix. 1.0.0 (2017-12-01) ------------------ Thanks to all contributors (see AUTHORS.txt), users, and testers, Mercantile 1.0.0 is ready. Share and enjoy! 1.0b2 (2017-11-27) ------------------ - Add ``tiles`` to ``__all__`` and sort that list. This sorts the classes and functions in the API docs. 1.0b1 (2017-11-21) ------------------ - Documentation: overhauled API reference docs based on output of sphinx-apidoc. 1.0a1 (2017-11-16) ------------------ - New feature: the ``feature`` function returns a GeoJSON feature for a tile (#46). 0.11.0 (2017-10-17) ------------------- - New feature: the ``lnglat`` function is the inverse of ``xy`` (#62). - New feature: the --bounding-tile option of mercantile-tiles has been made into a new mercantile-bounding-tile command (#43). - API change: the --bounding-tile and --with-bounds options of mercantile-tiles have been removed. 0.10.0 (2017-05-26) ------------------- - API change: ``InvalidLatitudeError`` is raised by ``tile`` when Y cannot be computed. - New feature: ``xy_bounds`` to get Spherical Mercator bounds for tile (#60). - New feature: ``Bbox`` class with ``left``, ``bottom``, ``top``, ``right`` properties (#60). - Bug fix: prevent ``tiles`` from returning tiles with invalid indexes (#47). 0.9.0 (2016-05-20) ------------------ - Refactoring: new ``normalize_input`` and ``iter_lines`` functions for use in the CLI (#58). - Refactoring: the coarse ``try/except`` blocks have been removed from within CLI subcommands, ``sys.exit`` calls are removed, and ``sys.stdout.write`` calls have been replaced by ``click.echo`` (#58). - Refactoring: many PEP 8 changes and new tests to bring the project to 100% coverage (#58). - New feature: functions and subcommand for converting between tiles and quadkeys (#50, #51, #56, #57). - Bug fix: correct output when a point is given to mercantile-tiles (#48, #49). - Bug fix: more consistent interface for tile arguments (#53). 0.8.3 (2015-08-24) ------------------ - Fix error in lng/lat truncation. If lat was > 90, the *lng* was set to a wrong value. 0.8.2 (2014-10-29) ------------------ - Add tiles() function (#38). - Split antimeridian crossing bounds in tiles() (#40). 0.8.1 (2014-10-22) ------------------ - Emulate JS >>> operator so we get same results as tilebelt (#36). 0.8 (2014-10-22) ---------------- - Streamlining of sequence related options (#35). - Add customization of output shape ids (#33). 0.7.1 (2014-10-21) ------------------ - Make lng/lat truncation optional and off by default (#29). 0.7 (2014-10-21) ---------------- - Add customization of output shape properties (#30). 0.6.1 (2014-10-13) ------------------ - Guard against lng/lat values off the globe (#27). 0.6 (2014-09-27) ---------------- - Add bounding_tile function and tests (#25). - Add --bounding-tile option to tiles command. 0.5.1 (2014-09-25) ------------------ - Let mercantile tiles accept point input as well as bbox or GeojSON. 0.5 (2014-09-24) ---------------- - Add mercantile parent and children commands (#17). - Fix numerical precision bug in roundtripping shapes/tiles (#19). - Compute bbox if input GeoJSON doesn't provide one (#23). 0.4 (2014-08-21) ---------------- - Add option for RS-deliminted JSON sequences. - Transparent handling of file, stream, and text input (#11). - Add buffering option (#13). - Add --extents option to mercantile shapes (#14, #16). - Round coordinates to proper precision. 0.3 (2014-08-19) ---------------- - Add mercator output option for shapes (#9). 0.2.1 (2014-08-19) ------------------ - Feature collections as option for shapes command. 0.2 (2014-08-19) ---------------- - Added tile() function (#2). - Add mercantile script (#3). - Added shapes command (#6). 0.1 (2014-03-26) ---------------- - Added mercantile.tool script for use with geojsonio-cli. mercantile-1.1.5/CODE_OF_CONDUCT.md000066400000000000000000000036751367224153100164570ustar00rootroot00000000000000# Contributor Code of Conduct As contributors and maintainers of this project, 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 this project a harassment-free experience for everyone, regardless of 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. Project maintainers 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. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 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 opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) mercantile-1.1.5/LICENSE.txt000066400000000000000000000027621367224153100154770ustar00rootroot00000000000000Copyright (c) 2014-2017, Sean C. Gillies 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 Sean C. Gillies 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 COPYRIGHT OWNER OR CONTRIBUTORS 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. mercantile-1.1.5/README.rst000066400000000000000000000074401367224153100153410ustar00rootroot00000000000000========== Mercantile ========== .. image:: https://travis-ci.org/mapbox/mercantile.svg :target: https://travis-ci.org/mapbox/mercantile :alt: Build Status .. image:: https://coveralls.io/repos/github/mapbox/mercantile/badge.svg?branch=master :target: https://coveralls.io/github/mapbox/mercantile?branch=master :alt: Coverage Status .. image:: https://readthedocs.org/projects/mercantile/badge/?version=latest :target: http://mercantile.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status Spherical mercator coordinate and tile utilities Documentation: http://mercantile.readthedocs.io/en/latest/ The mercantile module provides ``ul(xtile, ytile, zoom)`` and ``bounds(xtile, ytile, zoom)`` functions that respectively return the upper left corner and bounding longitudes and latitudes for XYZ tiles, a ``xy(lng, lat)`` function that returns spherical mercator x and y coordinates, a ``tile(lng, lat, zoom)`` function that returns the tile containing a given point, and quadkey conversion functions ``quadkey(xtile, ytile, zoom)`` and ``quadkey_to_tile(quadkey)`` for translating between quadkey and tile coordinates. .. code-block:: pycon >>> import mercantile >>> mercantile.ul(486, 332, 10) LngLat(lng=-9.140625, lat=53.33087298301705) >>> mercantile.bounds(486, 332, 10) LngLatBbox(west=-9.140625, south=53.12040528310657, east=-8.7890625, north=53.33087298301705) >>> mercantile.xy(*mercantile.ul(486, 332, 10)) (-1017529.7205322663, 7044436.526761846) >> mercantile.xy_bounds(486, 332, 10) Bbox(left=-1017529.7205322663, bottom=7005300.768279833, right=-978393.962050256, top=7044436.526761846) >>> mercantile.tile(*mercantile.ul(486, 332, 10) + (10,)) Tile(x=486, y=332, z=10) >>> mercantile.quadkey(486, 332, 10) '0313102310' >>> mercantile.quadkey_to_tile('0313102310') Tile(x=486, y=332, z=10) Also in mercantile are functions to traverse the tile stack. .. code-block:: pycon >>> mercantile.parent(486, 332, 10) Tile(x=243, y=166, z=9) >>> mercantile.children(mercantile.parent(486, 332, 10)) [Tile(x=486, y=332, z=10), Tile(x=487, y=332, z=10), Tile(x=487, y=333, z=10), Tile(x=486, y=333, z=10)] Named tuples are used to represent tiles, coordinates, and bounding boxes. Mercantile CLI ============== Mercantile's command line interface, named "mercantile", has commands for getting the shapes of Web Mercator tiles as GeoJSON and getting the tiles that intersect with a GeoJSON bounding box. .. code-block:: console $ mercantile --help Usage: mercantile [OPTIONS] COMMAND [ARGS]... Command line interface for the Mercantile Python package. Options: -v, --verbose Increase verbosity. -q, --quiet Decrease verbosity. --help Show this message and exit. Commands: bounding-tile Print the bounding tile of a lng/lat point, bounding box, or GeoJSON objects. children Print the children of the tile. parent print the parent tile. quadkey Convert to/from quadkeys. shapes Print the shapes of tiles as GeoJSON. tiles Print tiles that overlap or contain a lng/lat point, bounding box, or GeoJSON objects. See `docs/cli.rst `__ for more about the mercantile program. See Also ======== `supermercado `__ is another python lib with added tile logic functionality (union tile shapes, find edge tiles, and find tile intersections for complex geometries). `node-sphericalmercator `__ provides many of the same features for Node. `tilebelt `__ has some of the GeoJSON features as mercantile and a few more (tile parents, quadkey). mercantile-1.1.5/docs/000077500000000000000000000000001367224153100145755ustar00rootroot00000000000000mercantile-1.1.5/docs/Makefile000066400000000000000000000167061367224153100162470ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Mercantile.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Mercantile.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Mercantile" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Mercantile" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." mercantile-1.1.5/docs/api/000077500000000000000000000000001367224153100153465ustar00rootroot00000000000000mercantile-1.1.5/docs/api/mercantile.rst000066400000000000000000000003701367224153100202230ustar00rootroot00000000000000mercantile package ================== Subpackages ----------- .. toctree:: mercantile.scripts Module contents --------------- .. automodule:: mercantile :members: :undoc-members: :show-inheritance: :member-order: groupwise mercantile-1.1.5/docs/api/mercantile.scripts.rst000066400000000000000000000002641367224153100217130ustar00rootroot00000000000000mercantile.scripts package ========================== Module contents --------------- .. automodule:: mercantile.scripts :members: :undoc-members: :show-inheritance: mercantile-1.1.5/docs/api/modules.rst000066400000000000000000000001111367224153100175410ustar00rootroot00000000000000API reference ============= .. toctree:: :maxdepth: 4 mercantile mercantile-1.1.5/docs/authors.rst000066400000000000000000000000341367224153100170110ustar00rootroot00000000000000.. include:: ../AUTHORS.txt mercantile-1.1.5/docs/changelog.rst000066400000000000000000000000341367224153100172530ustar00rootroot00000000000000.. include:: ../CHANGES.txt mercantile-1.1.5/docs/cli.rst000066400000000000000000000141041367224153100160760ustar00rootroot00000000000000Command line interface ====================== .. code-block:: console $ mercantile --help Usage: mercantile [OPTIONS] COMMAND [ARGS]... Command line interface for the Mercantile Python package. Options: -v, --verbose Increase verbosity. -q, --quiet Decrease verbosity. --help Show this message and exit. Commands: bounding-tile Print the bounding tile of a lng/lat point, bounding box, or GeoJSON objects. children Print the children of the tile. parent Print the parent tile. quadkey Convert to/from quadkeys. shapes Print the shapes of tiles as GeoJSON. tiles Print tiles that overlap or contain a lng/lat point, bounding box, or GeoJSON objects. Examples -------- ``mercantile shapes`` generates GeoJSON from tiles and ``mercantile tiles`` does the reverse operation. .. code-block:: console $ mercantile shapes "[2331, 1185, 12]" | mercantile tiles 12 [2331, 1185, 12] ``mercantile parent`` and ``mercantile children`` traverse the hierarchy of Web Mercator tiles. .. code-block:: console $ mercantile parent "[2331,1185,12]" | mercantile children [2330, 1184, 12] [2331, 1184, 12] [2331, 1185, 12] [2330, 1185, 12] ``mercantile quadkey`` will convert to/from quadkey representations of tiles. .. code-block:: console $ mercantile quadkey "[486, 332, 10]" 0313102310 $ mercantile quadkey 0313102310 [486, 332, 10] shapes ------ The shapes command writes Mercator tile shapes to several forms of GeoJSON. .. code-block:: console $ echo "[106, 193, 9]" | mercantile shapes --indent 2 --precision 6 { "features": [ { "geometry": { "coordinates": [ [ [ -105.46875, 39.909736 ], [ -105.46875, 40.446947 ], [ -104.765625, 40.446947 ], [ -104.765625, 39.909736 ], [ -105.46875, 39.909736 ] ] ], "type": "Polygon" }, "id": "(106, 193, 9)", "properties": { "title": "XYZ tile (106, 193, 9)" }, "type": "Feature" } ], "type": "FeatureCollection" } bounding-tile ------------- With the bounding-tile command you can write the input's bounding tile, the smallest mercator tile of any resolution that completely contains the input. .. code-block:: console $ echo "[-105, 39.99, -104.99, 40]" | mercantile bounding-tile [1706, 3101, 13] Note well that when the input crosses lng 0 or lat 0, or any such tile boundary, the bounding tile will be at a shallow zoom level. .. code-block:: console $ echo "[-1, 1, 1, 2]" | mercantile bounding-tile [0, 0, 0] $ echo "[-91, 1, -89, 2]" | mercantile bounding-tile [0, 0, 1] Compare these bounding tiles to the one for a similarly size input box shifted away from the zoom=1 tile intersection. .. code-block:: console $ echo "[-92, 1, -91, 2]" | mercantile tiles bounding-tile [31, 63, 7] tiles ----- With the tiles command you can write descriptions of tiles intersecting with a geographic point, bounding box, or GeoJSON object. .. code-block:: console $ echo "[-105, 39.99, -104.99, 40]" | mercantile tiles 14 [3413, 6202, 14] [3413, 6203, 14] The commands can be piped together to do this: .. code-block:: console $ echo "[-105, 39.99, -104.99, 40]" \ > | mercantile tiles 14 \ > | mercantile shapes --indent 2 --precision 6 { "features": [ { "geometry": { "coordinates": [ [ [ -105.007324, 39.993956 ], [ -105.007324, 40.010787 ], [ -104.985352, 40.010787 ], [ -104.985352, 39.993956 ], [ -105.007324, 39.993956 ] ] ], "type": "Polygon" }, "id": "(3413, 6202, 14)", "properties": { "title": "XYZ tile (3413, 6202, 14)" }, "type": "Feature" }, { "geometry": { "coordinates": [ [ [ -105.007324, 39.97712 ], [ -105.007324, 39.993956 ], [ -104.985352, 39.993956 ], [ -104.985352, 39.97712 ], [ -105.007324, 39.97712 ] ] ], "type": "Polygon" }, "id": "(3413, 6203, 14)", "properties": { "title": "XYZ tile (3413, 6203, 14)" }, "type": "Feature" } ], "type": "FeatureCollection" } or do a round trip like this .. code-block:: console $ echo "[106, 193, 9]" | mercantile shapes | mercantile tiles 9 [106, 193, 9] If you have `geojsonio-cli `_ installed, you can shoot this GeoJSON straight to `geojson.io `__ for lightning-fast visualization and editing. .. code-block:: console $ echo "[-105, 39.99, -104.99, 40]" \ | mercantile tiles 14 \ | mercantile shapes --collect \ | geojsonio When supplying GeoJSON as input, you may need to first compact with the help of ``jq`` .. code-block:: console $ cat input.geojson | jq -c . | mercantile tiles 14 mercantile-1.1.5/docs/conf.py000066400000000000000000000232531367224153100161010ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Mercantile documentation build configuration file, created by # sphinx-quickstart on Tue Nov 14 23:21:41 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath(".")) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ["sphinx.ext.autodoc", "sphinx.ext.autosummary", "numpydoc"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "Mercantile" copyright = "2017, Sean C. Gillies" author = "Sean C. Gillies" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = "1.0.0" # The full version, including alpha/beta/rc tags. release = "1.0.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = 'Mercantile v1.0.0' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} html_sidebars = { "**": [ "about.html", "navigation.html", "relations.html", "searchbox.html", "donate.html", ] } # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = "Mercantiledoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, "Mercantile.tex", "Mercantile Documentation", "Sean C. Gillies", "manual", ) ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "mercantile", "Mercantile Documentation", [author], 1)] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "Mercantile", "Mercantile Documentation", author, "Mercantile", "One line description of project.", "Miscellaneous", ) ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False mercantile-1.1.5/docs/index.rst000066400000000000000000000007731367224153100164450ustar00rootroot00000000000000Mercantile ========== Mercantile is a module of utilities for working with XYZ style spherical mercator tiles (as in Google Maps, OSM, Mapbox, etc.) and includes a set of command line programs built on these utilities. Project repository: https://github.com/mapbox/mercantile Contents -------- .. toctree:: :maxdepth: 2 quickstart install api/modules.rst cli changelog license authors Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` mercantile-1.1.5/docs/install.rst000066400000000000000000000002361367224153100167760ustar00rootroot00000000000000Installation ============ Mercantile works with Python 3 and Python 2 and is easy to install with pip. .. code-block:: console pip install mercantile mercantile-1.1.5/docs/license.rst000066400000000000000000000000741367224153100167520ustar00rootroot00000000000000License ======= .. raw:: text .. include:: ../LICENSE.txt mercantile-1.1.5/docs/quickstart.rst000066400000000000000000000030701367224153100175210ustar00rootroot00000000000000Quick start =========== In the XYZ tiling system, the region of the world from 85.0511 (more precisely: ``arctan(sinh(π))``) degrees south of the Equator to 85.0511 degrees north is covered at zoom level 0 by a single tile. The number of tiles at each zoom level is ``2**(2*Z)``. At zoom level 1, we have 4 tiles. .. code-block:: none +-------------+-------------+ 85.0511 deg N | | | | x: 0 | x: 1 | | y: 0 | y: 0 | | z: 1 | z: 1 | | | | +-------------+-------------+ 0.0 deg N | | | | x: 0 | x: 1 | | y: 1 | y: 1 | | z: 1 | z: 1 | | | | +-------------+-------------+ 85.0511 deg S 180.0 deg W 180.0 deg E You can get the tile containing a longitude and latitude pair from the ``mercantile.tile`` function. .. code-block:: pycon >>> import mercantile >>> mercantile.tile(-105.0, 40.0, 1) Tile(x=0, y=0, z=1) You can get the geographic (longitude and latitude) bounds of a tile from the ``mercantile.bounds`` function. .. code-block:: pycon >>> mercantile.bounds(mercantile.Tile(x=0, y=0, z=1)) LngLatBbox(west=-180.0, south=0.0, east=0.0, north=85.0511287798066) mercantile-1.1.5/mercantile/000077500000000000000000000000001367224153100157705ustar00rootroot00000000000000mercantile-1.1.5/mercantile/__init__.py000066400000000000000000000441141367224153100201050ustar00rootroot00000000000000"""Web mercator XYZ tile utilities""" from collections import namedtuple import math import sys import warnings import operator if sys.version_info < (3,): warnings.warn( "Python versions < 3 will not be supported by mercantile 2.0.", UserWarning, ) from collections import Sequence else: from collections.abc import Sequence __version__ = "1.1.5" __all__ = [ "Bbox", "LngLat", "LngLatBbox", "Tile", "bounding_tile", "bounds", "children", "feature", "lnglat", "parent", "quadkey", "quadkey_to_tile", "simplify", "tile", "tiles", "ul", "xy_bounds", ] R2D = 180 / math.pi RE = 6378137.0 CE = 2 * math.pi * RE EPSILON = 1e-14 LL_EPSILON = 1e-11 Tile = namedtuple("Tile", ["x", "y", "z"]) """An XYZ web mercator tile Attributes ---------- x, y, z : int x and y indexes of the tile and zoom level z. """ LngLat = namedtuple("LngLat", ["lng", "lat"]) """A longitude and latitude pair Attributes ---------- lng, lat : float Longitude and latitude in decimal degrees east or north. """ LngLatBbox = namedtuple("LngLatBbox", ["west", "south", "east", "north"]) """A geographic bounding box Attributes ---------- west, south, east, north : float Bounding values in decimal degrees. """ Bbox = namedtuple("Bbox", ["left", "bottom", "right", "top"]) """A web mercator bounding box Attributes ---------- left, bottom, right, top : float Bounding values in meters. """ class MercantileError(Exception): """Base exception""" class InvalidLatitudeError(MercantileError): """Raised when math errors occur beyond ~85 degrees N or S""" class InvalidZoomError(MercantileError): """Raised when a zoom level is invalid""" class ParentTileError(MercantileError): """Raised when a parent tile cannot be determined""" class QuadKeyError(MercantileError): """Raised when errors occur in computing or parsing quad keys""" class TileArgParsingError(MercantileError): """Raised when errors occur in parsing a function's tile arg(s)""" class TileError(MercantileError): """Raised when a tile can't be determined""" def _parse_tile_arg(*args): """parse the *tile arg of module functions Parameters ---------- tile : Tile or sequence of int May be be either an instance of Tile or 3 ints, X, Y, Z. Returns ------- Tile Raises ------ TileArgParsingError """ if len(args) == 1: args = args[0] if len(args) == 3: return Tile(*args) else: raise TileArgParsingError( "the tile argument may have 1 or 3 values. Note that zoom is a keyword-only argument" ) def ul(*tile): """Returns the upper left longitude and latitude of a tile Parameters ---------- tile : Tile or sequence of int May be be either an instance of Tile or 3 ints, X, Y, Z. Returns ------- LngLat Examples -------- >>> ul(Tile(x=0, y=0, z=1)) LngLat(lng=-180.0, lat=85.0511287798066) >>> mercantile.ul(1, 1, 1) LngLat(lng=0.0, lat=0.0) """ tile = _parse_tile_arg(*tile) xtile, ytile, zoom = tile Z2 = math.pow(2, zoom) lon_deg = xtile / Z2 * 360.0 - 180.0 lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / Z2))) lat_deg = math.degrees(lat_rad) return LngLat(lon_deg, lat_deg) def bounds(*tile): """Returns the bounding box of a tile Parameters ---------- tile : Tile or sequence of int May be be either an instance of Tile or 3 ints, X, Y, Z. Returns ------- LngLatBBox Notes ----- Epsilon is subtracted from the right limit and added to the bottom limit. """ tile = _parse_tile_arg(*tile) xtile, ytile, zoom = tile a = ul(xtile, ytile, zoom) b = ul(xtile + 1, ytile + 1, zoom) return LngLatBbox(a[0], b[1], b[0], a[1]) def truncate_lnglat(lng, lat): if lng > 180.0: lng = 180.0 elif lng < -180.0: lng = -180.0 if lat > 90.0: lat = 90.0 elif lat < -90.0: lat = -90.0 return lng, lat def xy(lng, lat, truncate=False): """Convert longitude and latitude to web mercator x, y Parameters ---------- lng, lat : float Longitude and latitude in decimal degrees. truncate : bool, optional Whether to truncate or clip inputs to web mercator limits. Returns ------- x, y : float y will be inf at the North Pole (lat >= 90) and -inf at the South Pole (lat <= -90). """ if truncate: lng, lat = truncate_lnglat(lng, lat) x = RE * math.radians(lng) if lat <= -90: y = float("-inf") elif lat >= 90: y = float("inf") else: y = RE * math.log(math.tan((math.pi * 0.25) + (0.5 * math.radians(lat)))) return x, y def lnglat(x, y, truncate=False): """Convert web mercator x, y to longitude and latitude Parameters ---------- x, y : float web mercator coordinates in meters. truncate : bool, optional Whether to truncate or clip inputs to web mercator limits. Returns ------- LngLat """ lng, lat = ( x * R2D / RE, ((math.pi * 0.5) - 2.0 * math.atan(math.exp(-y / RE))) * R2D, ) if truncate: lng, lat = truncate_lnglat(lng, lat) return LngLat(lng, lat) def xy_bounds(*tile): """Get the web mercator bounding box of a tile Parameters ---------- tile : Tile or sequence of int May be be either an instance of Tile or 3 ints, X, Y, Z. Returns ------- Bbox Notes ----- Epsilon is subtracted from the right limit and added to the bottom limit. """ tile = _parse_tile_arg(*tile) xtile, ytile, zoom = tile tile_size = CE / math.pow(2, zoom) left = xtile * tile_size - CE / 2 right = left + tile_size top = CE / 2 - ytile * tile_size bottom = top - tile_size return Bbox(left, bottom, right, top) def _xy(lng, lat, truncate=False): if truncate: lng, lat = truncate_lnglat(lng, lat) x = lng / 360.0 + 0.5 sinlat = math.sin(math.radians(lat)) try: y = 0.5 - 0.25 * math.log((1.0 + sinlat) / (1.0 - sinlat)) / math.pi except (ValueError, ZeroDivisionError): raise InvalidLatitudeError("Y can not be computed: lat={!r}".format(lat)) else: return x, y def tile(lng, lat, zoom, truncate=False): """Get the tile containing a longitude and latitude Parameters ---------- lng, lat : float A longitude and latitude pair in decimal degrees. zoom : int The web mercator zoom level. truncate : bool, optional Whether or not to truncate inputs to limits of web mercator. Returns ------- Tile """ x, y = _xy(lng, lat, truncate=truncate) Z2 = math.pow(2, zoom) if x <= 0: xtile = 0 elif x >= 1: xtile = int(Z2 - 1) else: # To address loss of precision in round-tripping between tile # and lng/lat, points within EPSILON of the right side of a tile # are counted in the next tile over. xtile = math.floor((x + EPSILON) * Z2) if y <= 0: ytile = 0 elif y >= 1: ytile = int(Z2 - 1) else: ytile = math.floor((y + EPSILON) * Z2) return Tile(xtile, ytile, zoom) def quadkey(*tile): """Get the quadkey of a tile Parameters ---------- tile : Tile or sequence of int May be be either an instance of Tile or 3 ints, X, Y, Z. Returns ------- str """ tile = _parse_tile_arg(*tile) xtile, ytile, zoom = tile qk = [] for z in range(zoom, 0, -1): digit = 0 mask = 1 << (z - 1) if xtile & mask: digit += 1 if ytile & mask: digit += 2 qk.append(str(digit)) return "".join(qk) def quadkey_to_tile(qk): """Get the tile corresponding to a quadkey Parameters ---------- qk : str A quadkey string. Returns ------- Tile """ if len(qk) == 0: return Tile(0, 0, 0) xtile, ytile = 0, 0 for i, digit in enumerate(reversed(qk)): mask = 1 << i if digit == "1": xtile = xtile | mask elif digit == "2": ytile = ytile | mask elif digit == "3": xtile = xtile | mask ytile = ytile | mask elif digit != "0": warnings.warn( "QuadKeyError will not derive from ValueError in mercantile 2.0.", DeprecationWarning, ) raise QuadKeyError("Unexpected quadkey digit: %r", digit) return Tile(xtile, ytile, i + 1) def tiles(west, south, east, north, zooms, truncate=False): """Get the tiles overlapped by a geographic bounding box Parameters ---------- west, south, east, north : sequence of float Bounding values in decimal degrees. zooms : int or sequence of int One or more zoom levels. truncate : bool, optional Whether or not to truncate inputs to web mercator limits. Yields ------ Tile Notes ----- A small epsilon is used on the south and east parameters so that this function yields exactly one tile when given the bounds of that same tile. """ if truncate: west, south = truncate_lnglat(west, south) east, north = truncate_lnglat(east, north) if west > east: bbox_west = (-180.0, south, east, north) bbox_east = (west, south, 180.0, north) bboxes = [bbox_west, bbox_east] else: bboxes = [(west, south, east, north)] for w, s, e, n in bboxes: # Clamp bounding values. w = max(-180.0, w) s = max(-85.051129, s) e = min(180.0, e) n = min(85.051129, n) if not isinstance(zooms, Sequence): zooms = [zooms] for z in zooms: ul_tile = tile(w, n, z) lr_tile = tile(e - LL_EPSILON, s + LL_EPSILON, z) for i in range(ul_tile.x, lr_tile.x + 1): for j in range(ul_tile.y, lr_tile.y + 1): yield Tile(i, j, z) def parent(*tile, **kwargs): """Get the parent of a tile The parent is the tile of one zoom level lower that contains the given "child" tile. Parameters ---------- tile : Tile or sequence of int May be be either an instance of Tile or 3 ints, X, Y, Z. zoom : int, optional Determines the *zoom* level of the returned parent tile. This defaults to one lower than the tile (the immediate parent). Returns ------- Tile Examples -------- >>> parent(Tile(0, 0, 2)) Tile(x=0, y=0, z=1) >>> parent(Tile(0, 0, 2), zoom=0) Tile(x=0, y=0, z=0) """ tile = _parse_tile_arg(*tile) # zoom is a keyword-only argument. zoom = kwargs.get("zoom", None) if zoom is not None and (tile[2] < zoom or zoom != int(zoom)): raise InvalidZoomError( "zoom must be an integer and less than that of the input tile" ) x, y, z = tile if x != int(x) or y != int(y) or z != int(z): raise ParentTileError("the parent of a non-integer tile is undefined") target_zoom = z - 1 if zoom is None else zoom # Algorithm heavily inspired by https://github.com/mapbox/tilebelt. return_tile = tile while return_tile[2] > target_zoom: xtile, ytile, ztile = return_tile if xtile % 2 == 0 and ytile % 2 == 0: return_tile = Tile(xtile // 2, ytile // 2, ztile - 1) elif xtile % 2 == 0: return_tile = Tile(xtile // 2, (ytile - 1) // 2, ztile - 1) elif not xtile % 2 == 0 and ytile % 2 == 0: return_tile = Tile((xtile - 1) // 2, ytile // 2, ztile - 1) else: return_tile = Tile((xtile - 1) // 2, (ytile - 1) // 2, ztile - 1) return return_tile def children(*tile, **kwargs): """Get the children of a tile The children are ordered: top-left, top-right, bottom-right, bottom-left. Parameters ---------- tile : Tile or sequence of int May be be either an instance of Tile or 3 ints, X, Y, Z. zoom : int, optional Returns all children at zoom *zoom*, in depth-first clockwise winding order. If unspecified, returns the immediate (i.e. zoom + 1) children of the tile. Returns ------- list Examples -------- >>> children(Tile(0, 0, 0)) [Tile(x=0, y=0, z=1), Tile(x=0, y=1, z=1), Tile(x=1, y=0, z=1), Tile(x=1, y=1, z=1)] >>> children(Tile(0, 0, 0), zoom=2) [Tile(x=0, y=0, z=2), Tile(x=0, y=1, z=2), Tile(x=0, y=2, z=2), Tile(x=0, y=3, z=2), ...] """ tile = _parse_tile_arg(*tile) # zoom is a keyword-only argument. zoom = kwargs.get("zoom", None) xtile, ytile, ztile = tile if zoom is not None and (ztile > zoom or zoom != int(zoom)): raise InvalidZoomError( "zoom must be an integer and greater than that of the input tile" ) target_zoom = zoom if zoom is not None else ztile + 1 tiles = [tile] while tiles[0][2] < target_zoom: xtile, ytile, ztile = tiles.pop(0) tiles += [ Tile(xtile * 2, ytile * 2, ztile + 1), Tile(xtile * 2 + 1, ytile * 2, ztile + 1), Tile(xtile * 2 + 1, ytile * 2 + 1, ztile + 1), Tile(xtile * 2, ytile * 2 + 1, ztile + 1), ] return tiles def simplify(tiles): """Reduces the size of the tileset as much as possible by merging leaves into parents. Parameters ---------- tiles : Sequence of tiles to merge. Returns ------- list """ def merge(merge_set): """Checks to see if there are 4 tiles in merge_set which can be merged. If there are, this merges them. This returns a list of tiles, as well as a boolean indicating if any were merged. By repeatedly applying merge, a tileset can be simplified. """ upwards_merge = {} for tile in merge_set: tile_parent = parent(tile) if tile_parent not in upwards_merge: upwards_merge[tile_parent] = set() upwards_merge[tile_parent] |= {tile} current_tileset = [] changed = False for supertile, children in upwards_merge.items(): if len(children) == 4: current_tileset += [supertile] changed = True else: current_tileset += list(children) return current_tileset, changed # Check to see if a tile and its parent both already exist. # Ensure that tiles are sorted by zoom so parents are encountered first. # If so, discard the child (it's covered in the parent) root_set = set() for tile in sorted(tiles, key=operator.itemgetter(2)): x, y, z = tile is_new_tile = True for supertile in (parent(tile, zoom=i) for i in range(z + 1)): if supertile in root_set: is_new_tile = False continue if is_new_tile: root_set |= {tile} # Repeatedly run merge until no further simplification is possible. is_merging = True while is_merging: root_set, is_merging = merge(root_set) return root_set def rshift(val, n): return (val % 0x100000000) >> n def bounding_tile(*bbox, **kwds): """Get the smallest tile containing a geographic bounding box NB: when the bbox spans lines of lng 0 or lat 0, the bounding tile will be Tile(x=0, y=0, z=0). Parameters ---------- bbox : sequence of float west, south, east, north bounding values in decimal degrees. Returns ------- Tile """ if len(bbox) == 2: bbox += bbox w, s, e, n = bbox truncate = bool(kwds.get("truncate")) if truncate: w, s = truncate_lnglat(w, s) e, n = truncate_lnglat(e, n) e = e - LL_EPSILON s = s + LL_EPSILON try: tmin = tile(w, n, 32) tmax = tile(e, s, 32) except InvalidLatitudeError: return Tile(0, 0, 0) cell = tmin[:2] + tmax[:2] z = _getBboxZoom(*cell) if z == 0: return Tile(0, 0, 0) x = rshift(cell[0], (32 - z)) y = rshift(cell[1], (32 - z)) return Tile(x, y, z) def _getBboxZoom(*bbox): MAX_ZOOM = 28 for z in range(0, MAX_ZOOM): mask = 1 << (32 - (z + 1)) if (bbox[0] & mask) != (bbox[2] & mask) or (bbox[1] & mask) != (bbox[3] & mask): return z return MAX_ZOOM def feature( tile, fid=None, props=None, projected="geographic", buffer=None, precision=None ): """Get the GeoJSON feature corresponding to a tile Parameters ---------- tile : Tile or sequence of int May be be either an instance of Tile or 3 ints, X, Y, Z. fid : str, optional A feature id. props : dict, optional Optional extra feature properties. projected : str, optional Non-standard web mercator GeoJSON can be created by passing 'mercator'. buffer : float, optional Optional buffer distance for the GeoJSON polygon. precision : int, optional GeoJSON coordinates will be truncated to this number of decimal places. Returns ------- dict """ west, south, east, north = bounds(tile) if projected == "mercator": west, south = xy(west, south, truncate=False) east, north = xy(east, north, truncate=False) if buffer: west -= buffer south -= buffer east += buffer north += buffer if precision and precision >= 0: west, south, east, north = ( round(v, precision) for v in (west, south, east, north) ) bbox = [min(west, east), min(south, north), max(west, east), max(south, north)] geom = { "type": "Polygon", "coordinates": [ [[west, south], [west, north], [east, north], [east, south], [west, south]] ], } xyz = str(tile) feat = { "type": "Feature", "bbox": bbox, "id": xyz, "geometry": geom, "properties": {"title": "XYZ tile %s" % xyz}, } if props: feat["properties"].update(props) if fid is not None: feat["id"] = fid return feat mercantile-1.1.5/mercantile/scripts/000077500000000000000000000000001367224153100174575ustar00rootroot00000000000000mercantile-1.1.5/mercantile/scripts/__init__.py000066400000000000000000000366471367224153100216100ustar00rootroot00000000000000"""Mercantile command line interface """ import json import logging import sys import click import mercantile def configure_logging(verbosity): """Configure logging level Parameters ---------- verbosity : int The number of `-v` options from the command line. Returns ------- None """ log_level = max(10, 30 - 10 * verbosity) logging.basicConfig(stream=sys.stderr, level=log_level) logger = logging.getLogger(__name__) def coords(obj): """Yield all coordinate coordinate tuples from a geometry or feature. From python-geojson package.""" 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 else: for f in coords(e): yield f def normalize_input(input): """Normalize file or string input.""" try: src = click.open_file(input).readlines() except IOError: src = [input] return src def iter_lines(lines): """Iterate over lines of input, stripping and skipping.""" for line in lines: line = line.strip() if line: yield line # The CLI command group. @click.group(help="Command line interface for the Mercantile Python package.") @click.option("--verbose", "-v", count=True, help="Increase verbosity.") @click.option("--quiet", "-q", count=True, help="Decrease verbosity.") @click.version_option(version=mercantile.__version__, message="%(version)s") @click.pass_context def cli(ctx, verbose, quiet): """Execute the main mercantile command""" verbosity = verbose - quiet configure_logging(verbosity) ctx.obj = {} ctx.obj["verbosity"] = verbosity # Commands are below. # The shapes command. @cli.command(short_help="Print the shapes of tiles as GeoJSON.") # This input is either a filename, stdin, or a string. @click.argument("input", default="-", required=False) # Coordinate precision option. @click.option( "--precision", type=int, default=None, help="Decimal precision of coordinates." ) # JSON formatting options. @click.option( "--indent", default=None, type=int, help="Indentation level for JSON output" ) @click.option( "--compact/--no-compact", default=False, help="Use compact separators (',', ':')." ) # Geographic (default) or Mercator switch. @click.option( "--geographic", "projected", flag_value="geographic", default=True, help="Output in geographic coordinates (the default).", ) @click.option( "--mercator", "projected", flag_value="mercator", help="Output in Web Mercator coordinates.", ) @click.option( "--seq", is_flag=True, default=False, help="Write a RS-delimited JSON sequence (default is LF).", ) # GeoJSON feature (default) or collection switch. Meaningful only # when --x-json-seq is used. @click.option( "--feature", "output_mode", flag_value="feature", default=True, help="Output as sequence of GeoJSON features (the default).", ) @click.option( "--bbox", "output_mode", flag_value="bbox", help="Output as sequence of GeoJSON bbox arrays.", ) @click.option( "--collect", is_flag=True, default=False, help="Output as a GeoJSON feature collections.", ) # Optionally write out bboxen in a form that goes # straight into GDAL utilities like gdalwarp. @click.option( "--extents/--no-extents", default=False, help="Write shape extents as ws-separated strings (default is " "False).", ) # Optionally buffer the shapes by shifting the x and y values of each # vertex by a constant number of decimal degrees or meters (depending # on whether --geographic or --mercator is in effect). @click.option( "--buffer", type=float, default=None, help="Shift shape x and y values by a constant number", ) @click.pass_context def shapes( ctx, input, precision, indent, compact, projected, seq, output_mode, collect, extents, buffer, ): """Reads one or more Web Mercator tile descriptions from stdin and writes either a GeoJSON feature collection (the default) or a JSON sequence of GeoJSON features/collections to stdout. Input may be a compact newline-delimited sequences of JSON or a pretty-printed ASCII RS-delimited sequence of JSON (like https://tools.ietf.org/html/rfc8142 and https://tools.ietf.org/html/rfc7159). Tile descriptions may be either an [x, y, z] array or a JSON object of the form {"tile": [x, y, z], "properties": {"name": "foo", ...}} In the latter case, the properties object will be used to update the properties object of the output feature. """ dump_kwds = {"sort_keys": True} if indent: dump_kwds["indent"] = indent if compact: dump_kwds["separators"] = (",", ":") src = normalize_input(input) features = [] col_xs = [] col_ys = [] for i, line in enumerate(iter_lines(src)): obj = json.loads(line) if isinstance(obj, dict): x, y, z = obj["tile"][:3] props = obj.get("properties") fid = obj.get("id") elif isinstance(obj, list): x, y, z = obj[:3] props = {} fid = None else: raise click.BadParameter("{0}".format(obj), param=input, param_hint="input") feature = mercantile.feature( (x, y, z), fid=fid, props=props, projected=projected, buffer=buffer, precision=precision, ) bbox = feature["bbox"] w, s, e, n = bbox col_xs.extend([w, e]) col_ys.extend([s, n]) if collect: features.append(feature) elif extents: click.echo(" ".join(map(str, bbox))) else: if seq: click.echo(u"\x1e") if output_mode == "bbox": click.echo(json.dumps(bbox, **dump_kwds)) elif output_mode == "feature": click.echo(json.dumps(feature, **dump_kwds)) if collect and features: bbox = [min(col_xs), min(col_ys), max(col_xs), max(col_ys)] click.echo( json.dumps( {"type": "FeatureCollection", "bbox": bbox, "features": features}, **dump_kwds ) ) # The tiles command. @cli.command( short_help="Print tiles that overlap or contain a lng/lat point, " "bounding box, or GeoJSON objects." ) # Mandatory Mercator zoom level argument. @click.argument("zoom", type=int, default=-1) # This input is either a filename, stdin, or a string. # Has to follow the zoom arg. @click.argument("input", default="-", required=False) @click.option( "--seq/--lf", default=False, help="Write a RS-delimited JSON sequence (default is LF).", ) @click.pass_context def tiles(ctx, zoom, input, seq): """Lists Web Mercator tiles at ZOOM level intersecting GeoJSON [west, south, east, north] bounding boxen, features, or collections read from stdin. Output is a JSON [x, y, z] array. Input may be a compact newline-delimited sequences of JSON or a pretty-printed ASCII RS-delimited sequence of JSON (like https://tools.ietf.org/html/rfc8142 and https://tools.ietf.org/html/rfc7159). Example: $ echo "[-105.05, 39.95, -105, 40]" | mercantile tiles 12 Output: [852, 1550, 12] [852, 1551, 12] [853, 1550, 12] [853, 1551, 12] """ src = iter(normalize_input(input)) first_line = next(src) # If input is RS-delimited JSON sequence. if first_line.startswith(u"\x1e"): def feature_gen(): buffer = first_line.strip(u"\x1e") for line in src: if line.startswith(u"\x1e"): if buffer: yield json.loads(buffer) buffer = line.strip(u"\x1e") else: buffer += line else: yield json.loads(buffer) else: def feature_gen(): yield json.loads(first_line) for line in src: yield json.loads(line) source = feature_gen() # Detect the input format for obj in source: if isinstance(obj, list): bbox = obj if len(bbox) == 2: bbox += bbox if len(bbox) != 4: raise click.BadParameter( "{0}".format(bbox), param=input, param_hint="input" ) elif isinstance(obj, dict): if "bbox" in obj: bbox = obj["bbox"] else: box_xs = [] box_ys = [] for feat in obj.get("features", [obj]): lngs, lats = zip(*list(coords(feat))) box_xs.extend([min(lngs), max(lngs)]) box_ys.extend([min(lats), max(lats)]) bbox = min(box_xs), min(box_ys), max(box_xs), max(box_ys) west, south, east, north = bbox epsilon = 1.0e-10 if east != west and north != south: # 2D bbox # shrink the bounds a small amount so that # shapes/tiles round trip. west += epsilon south += epsilon east -= epsilon north -= epsilon for tile in mercantile.tiles(west, south, east, north, [zoom], truncate=False): vals = (tile.x, tile.y, zoom) output = json.dumps(vals) if seq: click.echo(u"\x1e") click.echo(output) # The bounding-tile command. @cli.command( "bounding-tile", short_help="Print the bounding tile of a lng/lat point, " "bounding box, or GeoJSON objects.", ) # This input is either a filename, stdin, or a string. @click.argument("input", default="-", required=False) @click.option( "--seq/--lf", default=False, help="Write a RS-delimited JSON sequence (default is LF).", ) @click.pass_context def bounding_tile(ctx, input, seq): """Print the Web Mercator tile at ZOOM level bounding GeoJSON [west, south, east, north] bounding boxes, features, or collections read from stdin. Input may be a compact newline-delimited sequences of JSON or a pretty-printed ASCII RS-delimited sequence of JSON (like https://tools.ietf.org/html/rfc8142 and https://tools.ietf.org/html/rfc7159). Example: $ echo "[-105.05, 39.95, -105, 40]" | mercantile bounding-tile Output: [426, 775, 11] """ src = iter(normalize_input(input)) first_line = next(src) # If input is RS-delimited JSON sequence. if first_line.startswith(u"\x1e"): def feature_gen(): buffer = first_line.strip(u"\x1e") for line in src: if line.startswith(u"\x1e"): if buffer: yield json.loads(buffer) buffer = line.strip(u"\x1e") else: buffer += line else: yield json.loads(buffer) else: def feature_gen(): yield json.loads(first_line) for line in src: yield json.loads(line) source = feature_gen() # Detect the input format for obj in source: if isinstance(obj, list): bbox = obj if len(bbox) == 2: bbox += bbox if len(bbox) != 4: raise click.BadParameter( "{0}".format(bbox), param=input, param_hint="input" ) elif isinstance(obj, dict): if "bbox" in obj: bbox = obj["bbox"] else: box_xs = [] box_ys = [] for feat in obj.get("features", [obj]): lngs, lats = zip(*list(coords(feat))) box_xs.extend([min(lngs), max(lngs)]) box_ys.extend([min(lats), max(lats)]) bbox = min(box_xs), min(box_ys), max(box_xs), max(box_ys) west, south, east, north = bbox vals = mercantile.bounding_tile(west, south, east, north, truncate=False) output = json.dumps(vals) if seq: click.echo(u"\x1e") click.echo(output) # The children command. @cli.command(short_help="Print the children of the tile.") @click.argument("input", default="-", required=False) @click.option( "--depth", type=int, default=1, help="Number of zoom levels to traverse (default is 1).", ) @click.pass_context def children(ctx, input, depth): """Takes [x, y, z] tiles as input and writes children to stdout in the same form. Input may be a compact newline-delimited sequences of JSON or a pretty-printed ASCII RS-delimited sequence of JSON (like https://tools.ietf.org/html/rfc8142 and https://tools.ietf.org/html/rfc7159). $ echo "[486, 332, 10]" | mercantile parent Output: [243, 166, 9] """ src = normalize_input(input) for line in iter_lines(src): line = line.strip() tiles = [json.loads(line)[:3]] for i in range(depth): tiles = sum([mercantile.children(t) for t in tiles], []) for t in tiles: output = json.dumps(t) click.echo(output) # The parent command. @cli.command(short_help="Print the parent tile.") @click.argument("input", default="-", required=False) @click.option( "--depth", type=int, default=1, help="Number of zoom levels to traverse (default is 1).", ) @click.pass_context def parent(ctx, input, depth): """Takes [x, y, z] tiles as input and writes parents to stdout in the same form. Input may be a compact newline-delimited sequences of JSON or a pretty-printed ASCII RS-delimited sequence of JSON (like https://tools.ietf.org/html/rfc8142 and https://tools.ietf.org/html/rfc7159). $ echo "[486, 332, 10]" | mercantile parent Output: [243, 166, 9] """ src = normalize_input(input) for line in iter_lines(src): tile = json.loads(line)[:3] if tile[2] - depth < 0: raise click.UsageError("Invalid parent level: {0}".format(tile[2] - depth)) for i in range(depth): tile = mercantile.parent(tile) output = json.dumps(tile) click.echo(output) @cli.command(short_help="Convert to/from quadkeys.") @click.argument("input", default="-", required=False) @click.pass_context def quadkey(ctx, input): """Takes [x, y, z] tiles or quadkeys as input and writes quadkeys or a [x, y, z] tiles to stdout, respectively. Input may be a compact newline-delimited sequences of JSON or a pretty-printed ASCII RS-delimited sequence of JSON (like https://tools.ietf.org/html/rfc8142 and https://tools.ietf.org/html/rfc7159). $ echo "[486, 332, 10]" | mercantile quadkey Output: 0313102310 $ echo "0313102310" | mercantile quadkey Output: [486, 332, 10] """ src = normalize_input(input) try: for line in iter_lines(src): if line[0] == "[": tile = json.loads(line)[:3] output = mercantile.quadkey(tile) else: tile = mercantile.quadkey_to_tile(line) output = json.dumps(tile) click.echo(output) except mercantile.QuadKeyError: raise click.BadParameter("{0}".format(input), param=input, param_hint="input") mercantile-1.1.5/requirements.txt000066400000000000000000000000351367224153100171270ustar00rootroot00000000000000click==6.7 pydocstyle==2.1.1 mercantile-1.1.5/rtd-requirements.txt000066400000000000000000000000111367224153100177100ustar00rootroot00000000000000numpydoc mercantile-1.1.5/setup.cfg000066400000000000000000000000311367224153100154600ustar00rootroot00000000000000[pydocstyle] select = D1 mercantile-1.1.5/setup.py000066400000000000000000000023431367224153100153610ustar00rootroot00000000000000"""Mercantile package build script""" import sys from setuptools import setup, find_packages open_kwds = {} if sys.version_info > (3,): open_kwds["encoding"] = "utf-8" with open("mercantile/__init__.py") as f: for line in f: if "__version__" in line: version = line.split("=")[1].strip().strip('"').strip("'") continue with open("README.rst", **open_kwds) as f: readme = f.read() setup( name="mercantile", version=version, description="Web mercator XYZ tile utilities", long_description=readme, classifiers=[ "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", ], keywords="mapping, web mercator, tiles", author="Sean Gillies", author_email="sean@mapbox.com", url="https://github.com/mapbox/mercantile", license="BSD", packages=find_packages(exclude=["ez_setup", "examples", "tests"]), include_package_data=True, zip_safe=False, install_requires=["click>=3.0"], extras_require={ "dev": ["check-manifest"], "test": ["coveralls", "hypothesis", "pytest-cov", "pydocstyle"], }, entry_points=""" [console_scripts] mercantile=mercantile.scripts:cli """, ) mercantile-1.1.5/tests/000077500000000000000000000000001367224153100150075ustar00rootroot00000000000000mercantile-1.1.5/tests/__init__.py000066400000000000000000000000371367224153100171200ustar00rootroot00000000000000"""Mercantile tests package""" mercantile-1.1.5/tests/test_cli.py000066400000000000000000000266541367224153100172040ustar00rootroot00000000000000"""Tests of the mercantile CLI""" from click.testing import CliRunner import pytest from mercantile.scripts import cli def test_cli_shapes_failure(): runner = CliRunner() result = runner.invoke(cli, ["shapes"], "0") assert result.exit_code == 2 def test_cli_shapes(): runner = CliRunner() result = runner.invoke(cli, ["shapes", "--precision", "6"], "[106, 193, 9]") assert result.exit_code == 0 assert ( result.output == '{"bbox": [-105.46875, 39.909736, -104.765625, 40.446947], "geometry": {"coordinates": [[[-105.46875, 39.909736], [-105.46875, 40.446947], [-104.765625, 40.446947], [-104.765625, 39.909736], [-105.46875, 39.909736]]], "type": "Polygon"}, "id": "(106, 193, 9)", "properties": {"title": "XYZ tile (106, 193, 9)"}, "type": "Feature"}\n' ) def test_cli_shapes_arg(): runner = CliRunner() result = runner.invoke(cli, ["shapes", "[106, 193, 9]", "--precision", "6"]) assert result.exit_code == 0 assert ( result.output == '{"bbox": [-105.46875, 39.909736, -104.765625, 40.446947], "geometry": {"coordinates": [[[-105.46875, 39.909736], [-105.46875, 40.446947], [-104.765625, 40.446947], [-104.765625, 39.909736], [-105.46875, 39.909736]]], "type": "Polygon"}, "id": "(106, 193, 9)", "properties": {"title": "XYZ tile (106, 193, 9)"}, "type": "Feature"}\n' ) def test_cli_shapes_buffer(): runner = CliRunner() result = runner.invoke( cli, ["shapes", "[106, 193, 9]", "--buffer", "1.0", "--precision", "6"] ) assert result.exit_code == 0 assert ( result.output == '{"bbox": [-106.46875, 38.909736, -103.765625, 41.446947], "geometry": {"coordinates": [[[-106.46875, 38.909736], [-106.46875, 41.446947], [-103.765625, 41.446947], [-103.765625, 38.909736], [-106.46875, 38.909736]]], "type": "Polygon"}, "id": "(106, 193, 9)", "properties": {"title": "XYZ tile (106, 193, 9)"}, "type": "Feature"}\n' ) def test_cli_shapes_compact(): """Output is compact.""" runner = CliRunner() result = runner.invoke(cli, ["shapes", "--compact"], "[106, 193, 9]") assert result.exit_code == 0 assert '"type":"Feature"' in result.output.strip() def test_cli_shapes_indentation(): """Output is indented.""" runner = CliRunner() result = runner.invoke(cli, ["shapes", "--indent", "8"], "[106, 193, 9]") assert result.exit_code == 0 assert ' "type": "Feature"' in result.output.strip() def test_cli_shapes_collect(): """Shapes are collected into a feature collection.""" runner = CliRunner() result = runner.invoke(cli, ["shapes", "--collect", "--feature"], "[106, 193, 9]") assert result.exit_code == 0 assert "FeatureCollection" in result.output def test_cli_shapes_extents(): runner = CliRunner() result = runner.invoke( cli, ["shapes", "[106, 193, 9]", "--extents", "--mercator", "--precision", "3"] ) assert result.exit_code == 0 assert result.output == "-11740727.545 4852834.052 -11662456.028 4931105.569\n" def test_cli_shapes_bbox(): """JSON text sequences of bboxes are output.""" runner = CliRunner() result = runner.invoke( cli, [ "shapes", "[106, 193, 9]", "--seq", "--bbox", "--mercator", "--precision", "3", ], ) assert result.exit_code == 0 assert ( result.output == "\x1e\n[-11740727.545, 4852834.052, -11662456.028, 4931105.569]\n" ) def test_cli_shapes_props_fid(): runner = CliRunner() result = runner.invoke( cli, [ "shapes", '{"tile": [106, 193, 9], "properties": {"title": "foo"}, "id": "42"}', ], ) assert result.exit_code == 0 assert '"title": "foo"' in result.output assert '"id": "42"' in result.output def test_cli_tiles_bad_bounds(): """Bounds of len 3 are bad.""" runner = CliRunner() result = runner.invoke(cli, ["tiles", "14"], "[-105, 39.99, -104.99]") assert result.exit_code == 2 def test_cli_bounding_tile_bad_bounds(): """Bounds of len 3 are bad.""" runner = CliRunner() result = runner.invoke(cli, ["bounding-tile"], "[-105, 39.99, -104.99]") assert result.exit_code == 2 def test_cli_tiles_no_bounds(): runner = CliRunner() result = runner.invoke(cli, ["tiles", "14"], "[-105, 39.99, -104.99, 40]") assert result.exit_code == 0 assert result.output == "[3413, 6202, 14]\n[3413, 6203, 14]\n" def test_cli_tiles_multi_bounds(): """A LF-delimited sequence can be used as input.""" runner = CliRunner() result = runner.invoke( cli, ["tiles", "14"], "[-105, 39.99, -104.99, 40]\n[-105, 39.99, -104.99, 40]" ) assert result.exit_code == 0 assert len(result.output.strip().split("\n")) == 4 def test_cli_tiles_multi_bounds_seq(): """A JSON text sequence can be used as input.""" runner = CliRunner() result = runner.invoke( cli, ["tiles", "14"], "\x1e\n[-105, 39.99, -104.99, 40]\n\x1e\n[-105, 39.99, -104.99, 40]", ) assert result.exit_code == 0 assert len(result.output.strip().split("\n")) == 4 def test_cli_bounding_tile(): runner = CliRunner() result = runner.invoke(cli, ["bounding-tile"], "[-105, 39.99, -104.99, 40]") assert result.exit_code == 0 assert result.output == "[1706, 3101, 13]\n" def test_cli_bounding_tile_bbox(): runner = CliRunner() result = runner.invoke( cli, ["bounding-tile"], '{"bbox": [-105, 39.99, -104.99, 40]}' ) assert result.exit_code == 0 assert result.output == "[1706, 3101, 13]\n" def test_cli_bounding_tile2(): runner = CliRunner() result = runner.invoke(cli, ["bounding-tile"], "[-105, 39.99]") assert result.exit_code == 0 def test_cli_multi_bounding_tile(): """A JSON text sequence can be used as input.""" runner = CliRunner() result = runner.invoke( cli, ["bounding-tile"], "[-105, 39.99, -104.99, 40]\n[-105, 39.99, -104.99, 40]" ) assert result.exit_code == 0 assert len(result.output.strip().split("\n")) == 2 def test_cli_multi_bounding_tile_seq(): """A JSON text sequence can be used as input.""" runner = CliRunner() result = runner.invoke( cli, ["bounding-tile"], "\x1e\n[-105, 39.99, -104.99, 40]\n\x1e\n[-105, 39.99, -104.99, 40]", ) assert result.exit_code == 0 assert len(result.output.strip().split("\n")) == 2 def test_cli_tiles_bounding_tiles_z0(): runner = CliRunner() result = runner.invoke(cli, ["bounding-tile"], "[-1, -1, 1, 1]") assert result.exit_code == 0 assert result.output == "[0, 0, 0]\n" def test_cli_tiles_bounding_tiles_seq(): runner = CliRunner() result = runner.invoke(cli, ["bounding-tile", "--seq"], "[-1, -1, 1, 1]") assert result.exit_code == 0 assert result.output == "\x1e\n[0, 0, 0]\n" def test_cli_tiles_implicit_stdin(): runner = CliRunner() result = runner.invoke(cli, ["tiles", "14"], "[-105, 39.99, -104.99, 40]") assert result.exit_code == 0 assert result.output == "[3413, 6202, 14]\n[3413, 6203, 14]\n" def test_cli_tiles_arg(): runner = CliRunner() result = runner.invoke(cli, ["tiles", "14", "[-105, 39.99, -104.99, 40]"]) assert result.exit_code == 0 assert result.output == "[3413, 6202, 14]\n[3413, 6203, 14]\n" def test_cli_tiles_geosjon(): collection = '{"features": [{"geometry": {"coordinates": [[[-105.46875, 39.909736], [-105.46875, 40.446947], [-104.765625, 40.446947], [-104.765625, 39.909736], [-105.46875, 39.909736]]], "type": "Polygon"}, "id": "(106, 193, 9)", "properties": {"title": "XYZ tile (106, 193, 9)"}, "type": "Feature"}], "type": "FeatureCollection"}' runner = CliRunner() result = runner.invoke(cli, ["tiles", "9"], collection) assert result.exit_code == 0 assert result.output == "[106, 193, 9]\n[106, 194, 9]\n" def test_cli_bounding_tile_geosjon(): collection = '{"features": [{"geometry": {"coordinates": [[[-105.46875, 39.909736], [-105.46875, 40.446947], [-104.765625, 40.446947], [-104.765625, 39.909736], [-105.46875, 39.909736]]], "type": "Polygon"}, "id": "(106, 193, 9)", "properties": {"title": "XYZ tile (106, 193, 9)"}, "type": "Feature"}], "type": "FeatureCollection"}' runner = CliRunner() result = runner.invoke(cli, ["bounding-tile"], collection) assert result.exit_code == 0 assert result.output == "[26, 48, 7]\n" def test_cli_parent_failure(): """[0, 0, 0] has no parent""" runner = CliRunner() result = runner.invoke(cli, ["parent"], "[0, 0, 0]") assert result.exit_code == 2 def test_cli_parent(): runner = CliRunner() result = runner.invoke(cli, ["parent"], "[486, 332, 10]\n[486, 332, 10]") assert result.exit_code == 0 assert result.output == "[243, 166, 9]\n[243, 166, 9]\n" def test_cli_parent_depth(): runner = CliRunner() result = runner.invoke(cli, ["parent", "--depth", "2"], "[486, 332, 10]") assert result.exit_code == 0 assert result.output == "[121, 83, 8]\n" def test_cli_parent_multidepth(): runner = CliRunner() result = runner.invoke( cli, ["parent", "--depth", "2"], "[486, 332, 10]\n[121, 83, 8]" ) assert result.exit_code == 0 assert result.output == "[121, 83, 8]\n[30, 20, 6]\n" def test_cli_children(): runner = CliRunner() result = runner.invoke(cli, ["children"], "[243, 166, 9]") assert result.exit_code == 0 assert ( result.output == "[486, 332, 10]\n[487, 332, 10]\n[487, 333, 10]\n[486, 333, 10]\n" ) def test_cli_strict_overlap_contain(): runner = CliRunner() result1 = runner.invoke(cli, ["shapes"], "[2331,1185,12]") assert result1.exit_code == 0 result2 = runner.invoke(cli, ["tiles", "12"], result1.output) assert result2.exit_code == 0 assert result2.output == "[2331, 1185, 12]\n" def test_cli_tiles_seq(): runner = CliRunner() result = runner.invoke(cli, ["tiles", "14", "--seq"], "[14.0859, 5.798]") assert result.exit_code == 0 assert result.output == "\x1e\n[8833, 7927, 14]\n" def test_cli_tiles_points(): runner = CliRunner() result = runner.invoke(cli, ["tiles", "14"], "[14.0859, 5.798]") assert result.exit_code == 0 assert result.output == "[8833, 7927, 14]\n" def test_cli_tiles_point_geojson(): runner = CliRunner() result = runner.invoke( cli, ["tiles", "14"], '{"type":"geometry","coordinates":[14.0859, 5.798]}' ) assert result.exit_code == 0 assert result.output == "[8833, 7927, 14]\n" def test_cli_quadkey_failure(): """Abort when an invalid quadkey is passed""" runner = CliRunner() with pytest.warns(DeprecationWarning): result = runner.invoke(cli, ["quadkey", "lolwut"]) assert result.exit_code == 2 assert "lolwut" in result.output def test_cli_quadkey_from_tiles(): runner = CliRunner() result = runner.invoke(cli, ["quadkey"], "[486, 332, 10]\n[6826, 12415, 15]") assert result.exit_code == 0 assert result.output == "0313102310\n023101012323232\n" def test_cli_quadkey_from_quadkeys(): runner = CliRunner() result = runner.invoke(cli, ["quadkey"], "0313102310\n023101012323232\n") assert result.exit_code == 0 assert result.output == "[486, 332, 10]\n[6826, 12415, 15]\n" def test_cli_quadkey_from_mixed(): runner = CliRunner() result = runner.invoke(cli, ["quadkey"], "0313102310\n[6826, 12415, 15]\n") assert result.exit_code == 0 assert result.output == "[486, 332, 10]\n023101012323232\n" mercantile-1.1.5/tests/test_funcs.py000066400000000000000000000322101367224153100175340ustar00rootroot00000000000000import warnings from hypothesis import example, given from hypothesis.strategies import composite, integers import pytest import mercantile @pytest.mark.parametrize( "args", [(486, 332, 10), [(486, 332, 10)], [mercantile.Tile(486, 332, 10)]] ) def test_ul(args): expected = (-9.140625, 53.33087298301705) lnglat = mercantile.ul(*args) for a, b in zip(expected, lnglat): assert round(a - b, 7) == 0 assert lnglat[0] == lnglat.lng assert lnglat[1] == lnglat.lat @pytest.mark.parametrize( "args", [(486, 332, 10), [(486, 332, 10)], [mercantile.Tile(486, 332, 10)]] ) def test_bbox(args): expected = (-9.140625, 53.12040528310657, -8.7890625, 53.33087298301705) bbox = mercantile.bounds(*args) for a, b in zip(expected, bbox): assert round(a - b, 7) == 0 assert bbox.west == bbox[0] assert bbox.south == bbox[1] assert bbox.east == bbox[2] assert bbox.north == bbox[3] def test_xy_tile(): """x, y for the 486-332-10 tile is correctly calculated""" ul = mercantile.ul(486, 332, 10) xy = mercantile.xy(*ul) expected = (-1017529.7205322663, 7044436.526761846) for a, b in zip(expected, xy): assert round(a - b, 7) == 0 def test_xy_null_island(): """x, y for (0, 0) is correctly calculated""" xy = mercantile.xy(0.0, 0.0) expected = (0.0, 0.0) for a, b in zip(expected, xy): assert round(a - b, 7) == 0 def test_xy_south_pole(): """Return -inf for y at South Pole""" assert mercantile.xy(0.0, -90) == (0.0, float("-inf")) def test_xy_north_pole(): """Return inf for y at North Pole""" assert mercantile.xy(0.0, 90) == (0.0, float("inf")) def test_xy_truncate(): """Input is truncated""" assert mercantile.xy(-181.0, 0.0, truncate=True) == mercantile.xy(-180.0, 0.0) def test_lnglat(): xy = (-8366731.739810849, -1655181.9927159143) assert mercantile.lnglat(*xy) == (-75.15963, -14.704620000000013) def test_lnglat_truncate(): xy = (-28366731.739810849, -1655181.9927159143) assert mercantile.lnglat(*xy, truncate=True) == (-180, -14.704620000000013) def test_lnglat_xy_roundtrip(): lnglat = (-105.0844, 40.5853) roundtrip = mercantile.lnglat(*mercantile.xy(*lnglat)) for a, b in zip(roundtrip, lnglat): assert round(a - b, 7) == 0 @pytest.mark.parametrize( "args", [(486, 332, 10), [(486, 332, 10)], [mercantile.Tile(486, 332, 10)]] ) def test_xy_bounds(args): expected = ( -1017529.7205322663, 7005300.768279833, -978393.962050256, 7044436.526761846, ) bounds = mercantile.xy_bounds(*args) for a, b in zip(expected, bounds): assert round(a - b, 7) == 0 def test_tile_not_truncated(): tile = mercantile.tile(20.6852, 40.1222, 9) expected = (285, 193) assert tile[0] == expected[0] assert tile[1] == expected[1] def test_tile_truncate(): """Input is truncated""" assert mercantile.tile(-181.0, 0.0, 9, truncate=True) == mercantile.tile( -180.0, 0.0, 9 ) def test_tiles(): bounds = (-105, 39.99, -104.99, 40) tiles = list(mercantile.tiles(*bounds, zooms=[14])) expect = [ mercantile.Tile(x=3413, y=6202, z=14), mercantile.Tile(x=3413, y=6203, z=14), ] assert sorted(tiles) == sorted(expect) def test_tiles_single_zoom(): bounds = (-105, 39.99, -104.99, 40) tiles = list(mercantile.tiles(*bounds, zooms=14)) expect = [ mercantile.Tile(x=3413, y=6202, z=14), mercantile.Tile(x=3413, y=6203, z=14), ] assert sorted(tiles) == sorted(expect) def test_tiles_truncate(): """Input is truncated""" assert list( mercantile.tiles(-181.0, 0.0, -170.0, 10.0, zooms=[2], truncate=True) ) == list(mercantile.tiles(-180.0, 0.0, -170.0, 10.0, zooms=[2])) def test_tiles_antimerdian_crossing_bbox(): """Antimeridian-crossing bounding boxes are handled""" bounds = (175.0, 5.0, -175.0, 10.0) assert len(list(mercantile.tiles(*bounds, zooms=[2]))) == 2 def test_global_tiles_clamped(): """Y is clamped to (0, 2 ** zoom - 1)""" tiles = list(mercantile.tiles(-180, -90, 180, 90, [1])) assert len(tiles) == 4 assert min(t.y for t in tiles) == 0 assert max(t.y for t in tiles) == 1 @pytest.mark.parametrize( "t", [ mercantile.Tile(x=3413, y=6202, z=14), mercantile.Tile(486, 332, 10), mercantile.Tile(10, 10, 10), ], ) def test_tiles_roundtrip(t): """tiles(bounds(tile)) gives the tile""" res = list(mercantile.tiles(*mercantile.bounds(t), zooms=[t.z])) assert len(res) == 1 val = res.pop() assert val.x == t.x assert val.y == t.y assert val.z == t.z def test_tiles_roundtrip_children(): """tiles(bounds(tile)) gives the tile's children""" t = mercantile.Tile(x=3413, y=6202, z=14) res = list(mercantile.tiles(*mercantile.bounds(t), zooms=[15])) assert len(res) == 4 def test_quadkey(): tile = mercantile.Tile(486, 332, 10) expected = "0313102310" assert mercantile.quadkey(tile) == expected def test_quadkey_to_tile(): qk = "0313102310" expected = mercantile.Tile(486, 332, 10) assert mercantile.quadkey_to_tile(qk) == expected def test_empty_quadkey_to_tile(): qk = "" expected = mercantile.Tile(0, 0, 0) assert mercantile.quadkey_to_tile(qk) == expected def test_quadkey_failure(recwarn): """expect a deprecation warning""" warnings.simplefilter("always") with pytest.raises(mercantile.QuadKeyError): mercantile.quadkey_to_tile("lolwut") assert len(recwarn) == 1 assert recwarn.pop(DeprecationWarning) @pytest.mark.parametrize("args", [(486, 332, 10, 9), ((486, 332, 10), 9)]) def test_parent_invalid_args(args): """tile arg must have length 1 or 3""" with pytest.raises(mercantile.TileArgParsingError): mercantile.parent(*args) def test_parent(): parent = mercantile.parent(486, 332, 10) assert parent == (243, 166, 9) assert parent.z == 9 def test_parent_multi(): parent = mercantile.parent(486, 332, 10, zoom=8) assert parent == (121, 83, 8) assert parent.z == 8 def test_children(): x, y, z = 243, 166, 9 children = mercantile.children(x, y, z) assert len(children) == 4 # Four sub-tiles (children) when zooming in # # ------- # | a | b | # ---|--- # | d | c | # ------- # # with: # # a=(2x, 2y, z + 1) b=(2x + 1, 2y, z + 1) # # d=(2x, 2y + 1, z + 1) c=(2x + 1, 2y + 1, z + 1) # # See: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Subtiles a, b, c, d = children assert a == mercantile.Tile(2 * x, 2 * y, z + 1) assert b == mercantile.Tile(2 * x + 1, 2 * y, z + 1) assert c == mercantile.Tile(2 * x + 1, 2 * y + 1, z + 1) assert d == mercantile.Tile(2 * x, 2 * y + 1, z + 1) def test_children_multi(): children = mercantile.children(243, 166, 9, zoom=11) assert len(children) == 16 targets = [ (972, 664, 11), (973, 664, 11), (973, 665, 11), (972, 665, 11), (974, 664, 11), (975, 664, 11), (975, 665, 11), (974, 665, 11), (974, 666, 11), (975, 666, 11), (975, 667, 11), (974, 667, 11), (972, 666, 11), (973, 666, 11), (973, 667, 11), (972, 667, 11), ] for target in targets: assert target in children def test_child_fractional_zoom(): with pytest.raises(mercantile.InvalidZoomError) as e: mercantile.children((243, 166, 9), zoom=10.2) assert "zoom must be an integer and greater than" in str(e.value) def test_child_bad_tile_zoom(): with pytest.raises(mercantile.InvalidZoomError) as e: mercantile.children((243, 166, 9), zoom=8) assert "zoom must be an integer and greater than" in str(e.value) def test_parent_fractional_tile(): with pytest.raises(mercantile.ParentTileError) as e: mercantile.parent((243.3, 166.2, 9), zoom=1) assert "the parent of a non-integer tile is undefined" in str(e.value) def test_parent_fractional_zoom(): with pytest.raises(mercantile.InvalidZoomError) as e: mercantile.parent((243, 166, 9), zoom=1.2) assert "zoom must be an integer and less than" in str(e.value) def test_parent_bad_tile_zoom(): with pytest.raises(mercantile.InvalidZoomError) as e: mercantile.parent((243.3, 166.2, 9), zoom=10) assert "zoom must be an integer and less than" in str(e.value) def test_simplify(): children = mercantile.children(243, 166, 9, zoom=12) assert len(children) == 64 children = children[:-3] children.append(children[0]) simplified = mercantile.simplify(children) targets = [ (487, 332, 10), (486, 332, 10), (487, 333, 10), (973, 667, 11), (973, 666, 11), (972, 666, 11), (1944, 1334, 12), ] for target in targets: assert target in simplified def test_simplify_removal(): ''' Verify that tiles are being removed by simplify() ''' tiles = [ (1298, 3129, 13), (649, 1564, 12), (650, 1564, 12), ] simplified = mercantile.simplify(tiles) assert (1298, 3129, 13) not in simplified, 'Tile covered by a parent' assert (650, 1564, 12) in simplified, 'Highest-level tile' assert (649, 1564, 12) in simplified, 'Also highest-level tile' @pytest.mark.parametrize( "bounds,tile", [ ((-92.5, 0.5, -90.5, 1.5), (31, 63, 7)), ((-90.5, 0.5, -89.5, 0.5), (0, 0, 1)), ((-92, 0, -88, 2), (0, 0, 1)), ((-92, -2, -88, 2), (0, 0, 0)), ((-92, -2, -88, 0), (0, 1, 1)), ], ) def test_bounding_tile(bounds, tile): assert mercantile.bounding_tile(*bounds) == mercantile.Tile(*tile) def test_overflow_bounding_tile(): assert mercantile.bounding_tile( -179.99999999999997, -90.00000000000003, 180.00000000000014, -63.27066048950458 ) == (0, 0, 0) def test_bounding_tile_pt(): """A point is a valid input""" assert mercantile.bounding_tile(-91.5, 1.0).z == 28 def test_bounding_tile_truncate(): """Input is truncated""" assert mercantile.bounding_tile( -181.0, 1.0, truncate=True ) == mercantile.bounding_tile(-180.0, 1.0) def test_truncate_lng_under(): assert mercantile.truncate_lnglat(-181, 0) == (-180, 0) def test_truncate_lng_over(): assert mercantile.truncate_lnglat(181, 0) == (180, 0) def test_truncate_lat_under(): assert mercantile.truncate_lnglat(0, -91) == (0, -90) def test_truncate_lat_over(): assert mercantile.truncate_lnglat(0, 91) == (0, 90) @pytest.mark.parametrize( "args, tile", [ ((0, 0, 0), (0, 0, 0)), (mercantile.Tile(0, 0, 0), (0, 0, 0)), (((0, 0, 0)), (0, 0, 0)), ], ) def test_arg_parse(args, tile): """Helper function parse tile args properly""" assert mercantile._parse_tile_arg(*args) == mercantile.Tile(*tile) @pytest.mark.parametrize("args", [(0, 0), (0, 0, 0, 0)]) def test_arg_parse_error(args): """Helper function raises exception as expected""" with pytest.raises(mercantile.TileArgParsingError): mercantile._parse_tile_arg(*args) @composite def tiles(draw, zooms=integers(min_value=0, max_value=28)): z = draw(zooms) x = draw(integers(min_value=0, max_value=2 ** z - 1)) y = draw(integers(min_value=0, max_value=2 ** z - 1)) return mercantile.Tile(x, y, z) @given(tiles()) @example(mercantile.Tile(10, 10, 10)) def test_bounding_tile_roundtrip(t): """bounding_tile(bounds(tile)) gives the tile""" val = mercantile.bounding_tile(*mercantile.bounds(t)) assert val.x == t.x assert val.y == t.y assert val.z == t.z @given(tiles()) @example(mercantile.Tile(10, 10, 10)) def test_ul_tile_roundtrip(t): """ul and tile roundtrip""" lnglat = mercantile.ul(t) tile = mercantile.tile(lnglat.lng, lnglat.lat, t.z) assert tile.z == t.z assert tile.x == t.x assert tile.y == t.y @given(tiles()) @example(mercantile.Tile(10, 10, 10)) def test_ul_xy_bounds(t): """xy(*ul(t)) will be within 1e-7 of xy_bounds(t)""" assert mercantile.xy(*mercantile.ul(t))[1] == pytest.approx( mercantile.xy_bounds(t).top, abs=1e-7 ) assert mercantile.xy(*mercantile.ul(t))[0] == pytest.approx( mercantile.xy_bounds(t).left, abs=1e-7 ) def test_lower_left_tile(): assert mercantile.tile(180.0, -85, 1) == mercantile.Tile(1, 1, 1) @pytest.mark.parametrize("lat", [-90.0, 90.0]) def test_tile_poles(lat): with pytest.raises(mercantile.InvalidLatitudeError): mercantile.tile(0.0, lat, zoom=17) @pytest.mark.parametrize("lat", [-90.0, 90.0]) def test__xy_poles(lat): with pytest.raises(mercantile.InvalidLatitudeError): mercantile._xy(0.0, lat) @pytest.mark.parametrize("lat,fy", [(85.0511287798066, 0.0), (-85.0511287798066, 1.0)]) def test__xy_limits(lat, fy): x, y = mercantile._xy(0.0, lat) assert x == 0.5 assert y == pytest.approx(fy) @pytest.mark.parametrize("lat", [86.0]) def test__xy_north_of_limit(lat): x, y = mercantile._xy(0.0, lat) assert x == 0.5 assert y < 0 @pytest.mark.parametrize("lat", [-86.0]) def test__xy_south_of_limit(lat): x, y = mercantile._xy(0.0, lat) assert x == 0.5 assert y > 1 mercantile-1.1.5/tox.ini000066400000000000000000000002511367224153100151560ustar00rootroot00000000000000[tox] envlist = py27,py34,py35,py36,py37 [testenv] deps = pytest-cov responses commands = python -m pytest --cov mercantile --cov-report term-missing