├── .bumpversion.cfg ├── .github ├── codecov.yml └── workflows │ ├── ci.yml │ └── deploy_mkdocs.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── mkdocs.yml └── src │ ├── contributing.md │ ├── index.md │ └── release-notes.md ├── pyproject.toml ├── supermorecado ├── __init__.py ├── burntiles.py ├── densitytiles.py ├── edge_finder.py ├── py.typed ├── scripts │ ├── __init__.py │ └── cli.py ├── super_utils.py └── uniontiles.py └── tests ├── expected ├── burned.txt ├── edges.txt ├── heatmap.txt └── union.txt ├── fixtures ├── edges.txt ├── france.geojson ├── heatmap.txt ├── shape.geojson ├── title.geojson └── union.txt ├── test_cli.py └── test_mod.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.2 3 | commit = True 4 | tag = True 5 | tag_name = {new_version} 6 | 7 | [bumpversion:file:supermorecado/__init__.py] 8 | search = __version__ = "{current_version}" 9 | replace = __version__ = "{new_version}" 10 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 5 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | pull_request: 10 | env: 11 | LATEST_PY_VERSION: '3.10' 12 | 13 | jobs: 14 | tests: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: 19 | - '3.8' 20 | - '3.9' 21 | - '3.10' 22 | - '3.11' 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: Install module 32 | run: | 33 | python -m pip install --upgrade pip 34 | python -m pip install .["test"] 35 | 36 | - name: run pre-commit 37 | if: ${{ matrix.python-version == env.LATEST_PY_VERSION }} 38 | run: | 39 | python -m pip install pre-commit 40 | pre-commit run --all-files 41 | 42 | - name: Run tests 43 | run: python -m pytest --cov supermorecado --cov-report term-missing --cov-report xml 44 | 45 | - name: Upload Results 46 | if: ${{ matrix.python-version == env.LATEST_PY_VERSION }} 47 | uses: codecov/codecov-action@v1 48 | with: 49 | file: ./coverage.xml 50 | flags: unittests 51 | name: Python${{ matrix.python-version }} 52 | fail_ci_if_error: false 53 | 54 | publish: 55 | needs: [tests] 56 | runs-on: ubuntu-latest 57 | if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release' 58 | steps: 59 | - uses: actions/checkout@v3 60 | - name: Set up Python 61 | uses: actions/setup-python@v4 62 | with: 63 | python-version: ${{ env.LATEST_PY_VERSION }} 64 | 65 | - name: Install dependencies 66 | run: | 67 | python -m pip install --upgrade pip 68 | python -m pip install flit 69 | python -m pip install . 70 | 71 | - name: Set tag version 72 | id: tag 73 | run: | 74 | echo "version=${GITHUB_REF#refs/*/}" 75 | echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT 76 | 77 | - name: Set module version 78 | id: module 79 | run: | 80 | echo version=$(python -c'import supermorecado; print(supermorecado.__version__)') >> $GITHUB_OUTPUT 81 | 82 | - name: Build and publish 83 | if: ${{ steps.tag.outputs.version }} == ${{ steps.module.outputs.version}} 84 | env: 85 | FLIT_USERNAME: ${{ secrets.PYPI_USERNAME }} 86 | FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 87 | run: flit publish 88 | -------------------------------------------------------------------------------- /.github/workflows/deploy_mkdocs.yml: -------------------------------------------------------------------------------- 1 | name: Publish docs via GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | # Only rebuild website when docs have changed 9 | - 'README.md' 10 | - 'CHANGES.md' 11 | - 'CONTRIBUTING.md' 12 | - 'docs/**' 13 | - '.github/workflows/deploy_mkdocs.yml' 14 | 15 | jobs: 16 | build: 17 | name: Deploy docs 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout main 21 | uses: actions/checkout@v3 22 | 23 | - name: Set up Python 3.8 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: 3.8 27 | 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | python -m pip install mkdocs mkdocs-material pygments 32 | 33 | - name: Deploy docs 34 | run: mkdocs gh-deploy --force -f docs/mkdocs.yml 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # PyCharm: 132 | .idea 133 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/abravalheri/validate-pyproject 3 | rev: v0.12.1 4 | hooks: 5 | - id: validate-pyproject 6 | 7 | - repo: https://github.com/psf/black 8 | rev: 22.12.0 9 | hooks: 10 | - id: black 11 | language_version: python 12 | 13 | - repo: https://github.com/PyCQA/isort 14 | rev: 5.12.0 15 | hooks: 16 | - id: isort 17 | language_version: python 18 | 19 | - repo: https://github.com/charliermarsh/ruff-pre-commit 20 | rev: v0.0.238 21 | hooks: 22 | - id: ruff 23 | args: ["--fix"] 24 | 25 | - repo: https://github.com/pre-commit/mirrors-mypy 26 | rev: v0.991 27 | hooks: 28 | - id: mypy 29 | language_version: python 30 | # No reason to run if only tests have changed. They intentionally break typing. 31 | exclude: tests/.* 32 | additional_dependencies: 33 | - types-attrs 34 | - types-cachetools 35 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.1.2 (2023-09-11) 3 | 4 | * forward `truncate` option to `project_geom` in `burn` method (author @JackDunnNZ, https://github.com/developmentseed/supermorecado/pull/6) 5 | 6 | ## 0.1.1 (2023-06-03) 7 | 8 | * fix issue in `burnTiles.tile_extrema` for TMS that might return tile indices in `max -> min` order 9 | 10 | ## 0.1.0 (2023-06-02) 11 | 12 | * Initial release 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Issues and pull requests are more than welcome. 4 | 5 | **dev install** 6 | 7 | ```bash 8 | $ git clone https://github.com/developmentseed/supermorecado.git 9 | $ cd supermorecado 10 | $ python -m pip install -e .["dev"] 11 | ``` 12 | 13 | You can then run the tests with the following command: 14 | 15 | ```sh 16 | python -m pytest --cov supermorecado --cov-report term-missing -s -vv 17 | ``` 18 | 19 | ### pre-commit 20 | 21 | This repo is set to use `pre-commit` to run *isort*, *flake8*, *pydocstring*, *black* ("uncompromising Python code formatter") and mypy when committing new code. 22 | 23 | ```bash 24 | $ pre-commit install 25 | ``` 26 | 27 | ### Docs 28 | 29 | ```bash 30 | $ git clone https://github.com/developmentseed/supermorecado.git 31 | $ cd supermorecado 32 | $ python -m pip install -e .["docs"] 33 | ``` 34 | 35 | Hot-reloading docs: 36 | 37 | ```bash 38 | $ mkdocs serve -f docs/mkdocs.yml 39 | ``` 40 | 41 | To manually deploy docs (note you should never need to do this because Github 42 | Actions deploys automatically for new commits.): 43 | 44 | ```bash 45 | $ mkdocs gh-deploy -f docs/mkdocs.yml 46 | ``` 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Development Seed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Supermorecado 2 | 3 | 4 |

5 | 6 | Extend the functionality of morecantile with additional commands. 7 |

8 |

9 | 10 | Test 11 | 12 | 13 | Coverage 14 | 15 | 16 | Package version 17 | 18 | 19 | Downloads 20 | 21 | 22 | License 23 | 24 |

25 | 26 | --- 27 | 28 | **Documentation**: https://developmentseed.org/supermorecado/ 29 | 30 | **Source Code**: https://github.com/developmentseed/supermorecado 31 | 32 | --- 33 | 34 | supermorecado is like [supermercado](https://github.com/mapbox/supermercado), but with support for other TileMatrixSet grids. 35 | 36 | 37 | ## Install 38 | 39 | ```bash 40 | $ python -m pip install -U pip 41 | $ python -m pip install supermorecado 42 | 43 | # Or install from source: 44 | 45 | $ python -m pip install -U pip 46 | $ python -m pip install git+https://github.com/developmentseed/supermorecado.git 47 | ``` 48 | 49 | ## Usage 50 | 51 | ``` 52 | supermorecado --help 53 | Usage: supermorecado [OPTIONS] COMMAND [ARGS]... 54 | 55 | Command line interface for the Supermorecado Python package. 56 | 57 | Options: 58 | --help Show this message and exit. 59 | 60 | Commands: 61 | burn Burn a stream of GeoJSONs into a output stream of the tiles they intersect for a given zoom. 62 | edges For a stream of [, , ] tiles, return only those tiles that are on the edge. 63 | heatmap Creates a vector `heatmap` of tile densities. 64 | union Returns the unioned shape of a stream of [, , ] tiles in GeoJSON. 65 | ``` 66 | 67 | ### `supermorecado burn` 68 | 69 | ``` 70 | <{geojson} stream> | supermorecado burn --identifier {tms Identifier} | <[x, y, z] stream> 71 | ``` 72 | 73 | Takes an input stream of GeoJSON and returns a stream of intersecting `[x, y, z]`s for a given zoom. 74 | 75 | Using default TMS (`WebMercatorQuad`) 76 | ``` 77 | cat tests/fixtures/france.geojson | supermorecado burn 9 | morecantile shapes | fio collect | geojsonio 78 | ``` 79 | 80 | ![](https://user-images.githubusercontent.com/10407788/236114524-c0a3543f-dfa3-4fd3-af2f-27d60ab897e1.jpg) 81 | 82 | Using other TMS (e.g `WGS1984Quad`) 83 | ``` 84 | cat tests/fixtures/france.geojson | supermorecado burn 6 --identifier WGS1984Quad | morecantile shapes --identifier WGS1984Quad | fio collect | geojsonio 85 | ``` 86 | 87 | ![](https://user-images.githubusercontent.com/10407788/236115182-441b1e23-3335-4392-9a72-4c98838c76de.jpg) 88 | 89 | ### `supermorecado edges` 90 | 91 | ``` 92 | <[x, y, z] stream> | supermorecado edges | <[x, y, z] stream> 93 | ``` 94 | 95 | Outputs a stream of `[x, y, z]`s representing the edge tiles of an input stream of `[x, y, z]`s. Edge tile = any tile that is either directly adjacent to a tile that does not exist, or diagonal to an empty tile. 96 | 97 | ``` 98 | cat tests/fixtures/france.geojson | supermorecado burn 9 | supermorecado edges | morecantile shapes | fio collect | geojsonio 99 | ``` 100 | 101 | ![](https://user-images.githubusercontent.com/10407788/236123127-cc99c887-de9d-4823-a935-bd31a99809a2.jpg) 102 | 103 | ### `supermorecado union` 104 | 105 | ``` 106 | <[x, y, z] stream> | supermorecado union --identifier {tms Identifier} | <{geojson} stream> 107 | ``` 108 | 109 | Outputs a stream of unioned GeoJSON from an input stream of `[x, y, z]`s. Like `morecantile shapes` but as an overall footprint instead of individual shapes for each tile. 110 | 111 | Using default TMS (`WebMercatorQuad`) 112 | ``` 113 | cat tests/fixtures/france.geojson | supermorecado burn 9 | supermorecado union | fio collect | geojsonio 114 | ``` 115 | 116 | ![](https://user-images.githubusercontent.com/10407788/236115745-9c2f4fa3-e2ea-47d5-a5f5-c77f5308f4a3.jpg) 117 | 118 | Using other TMS (e.g `WGS1984Quad`) 119 | 120 | ``` 121 | cat tests/fixtures/france.geojson | supermorecado burn 6 --identifier WGS1984Quad | supermorecado union --identifier WGS1984Quad | fio collect | geojsonio 122 | ``` 123 | 124 | ![](https://user-images.githubusercontent.com/10407788/236115946-2dfabe05-e51d-473b-9957-26bbbe9e61bb.jpg) 125 | 126 | 127 | ### `supermorecado heatmap` 128 | 129 | ``` 130 | <[x, y, z] stream> | supermorecado heatmap --identifier {tms Identifier} | <{geojson} stream> 131 | ``` 132 | Outputs a stream of heatmap GeoJSON from an input stream of `[x, y, z]`s. 133 | 134 | Using default TMS (`WebMercatorQuad`) 135 | ``` 136 | cat tests/fixtures/heatmap.txt| supermorecado heatmap | fio collect | geojsonio 137 | ``` 138 | 139 | ![](https://github.com/developmentseed/supermorecado/assets/10407788/fec78304-12e8-47de-bca3-af58f04e8824) 140 | 141 | Using other TMS (e.g `WGS1984Quad`) 142 | 143 | ``` 144 | # create a list of tiles 145 | cat tests/fixtures/france.geojson | supermorecado burn 6 --identifier WGS1984Quad > france_wgs84_z6.txt 146 | # randomly append more tiles 147 | for run in {1..10}; do cat france_wgs84_z6.txt | sort -R | head -n 2 >> france_wgs84_z6.txt; done 148 | 149 | cat france_wgs84_z6.txt | supermorecado heatmap --identifier WGS1984Quad | fio collect | geojsonio 150 | ``` 151 | 152 | ![](https://github.com/developmentseed/supermorecado/assets/10407788/dec3dbdb-6030-4dd8-9efc-5c8d4b7c83d3) 153 | 154 | 155 | ## API migration 156 | 157 | `supermorecado` is really similar to `supermercado` (it reuse most of the code) but with the addition of multiple TMS support from morecantile. 158 | 159 | ```python 160 | features = [ 161 | { 162 | "geometry": { 163 | "coordinates": [ 164 | [-127.97, 49.15], 165 | [-101.95, -8.41], 166 | [-43.24, -32.84], 167 | [37.62, -25.17], 168 | [71.72, -7.01], 169 | [107.23, 48.69], 170 | ], 171 | "type": "LineString", 172 | }, 173 | "properties": {}, 174 | "type": "Feature", 175 | }, 176 | ] 177 | 178 | # supermercado 179 | from supermercado import burntiles, uniontiles 180 | 181 | tiles = burntiles.burn(features) 182 | u_tiles = uniontiles.union(features) 183 | 184 | # supermorecado 185 | import morecantile 186 | from supermorecado import burnTiles, unionTiles 187 | 188 | tms = morecantile.tms.get("WebMercatorQuad") 189 | 190 | burntiles = burnTiles(tms=tms) 191 | tiles = burntiles.burn(features) 192 | 193 | uniontiles = unionTiles(tms=tms) 194 | u_tiles = uniontiles.burn(features) 195 | ``` 196 | 197 | ## Changes 198 | 199 | See [CHANGES.md](https://github.com/developmentseed/supermorecado/blob/main/CHANGES.md). 200 | 201 | ## Contribution & Development 202 | 203 | See [CONTRIBUTING.md](https://github.com/developmentseed/supermorecado/blob/main/CONTRIBUTING.md) 204 | 205 | ## License 206 | 207 | See [LICENSE](https://github.com/developmentseed/supermorecado/blob/main/LICENSE) 208 | 209 | ## Authors 210 | 211 | Created by [Development Seed]() 212 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Project Information 2 | site_name: 'supermorecado' 3 | site_description: 'Extend the functionality of morecantile with additional commands.' 4 | 5 | docs_dir: 'src' 6 | site_dir: 'build' 7 | 8 | # Repository 9 | repo_name: 'developmentseed/supermorecado' 10 | repo_url: 'https://github.com/developmentseed/supermorecado' 11 | edit_uri: 'blob/main/src/' 12 | site_url: 'https://developmentseed.org/supermorecado/' 13 | 14 | # Social links 15 | extra: 16 | social: 17 | - icon: 'fontawesome/brands/github' 18 | link: 'https://github.com/developmentseed' 19 | - icon: 'fontawesome/brands/twitter' 20 | link: 'https://twitter.com/developmentseed' 21 | 22 | # Layout 23 | nav: 24 | - Home: 'index.md' 25 | - Development - Contributing: 'contributing.md' 26 | - Release: 'release-notes.md' 27 | 28 | # Theme 29 | theme: 30 | icon: 31 | logo: 'material/home' 32 | repo: 'fontawesome/brands/github' 33 | name: 'material' 34 | language: 'en' 35 | palette: 36 | primary: 'pink' 37 | accent: 'light pink' 38 | font: 39 | text: 'Nunito Sans' 40 | code: 'Fira Code' 41 | 42 | plugins: 43 | - search 44 | 45 | # These extensions are chosen to be a superset of Pandoc's Markdown. 46 | # This way, I can write in Pandoc's Markdown and have it be supported here. 47 | # https://pandoc.org/MANUAL.html 48 | markdown_extensions: 49 | - admonition 50 | - attr_list 51 | - codehilite: 52 | guess_lang: false 53 | - def_list 54 | - footnotes 55 | - pymdownx.arithmatex 56 | - pymdownx.betterem 57 | - pymdownx.caret: 58 | insert: false 59 | - pymdownx.details 60 | - pymdownx.emoji 61 | - pymdownx.escapeall: 62 | hardbreak: true 63 | nbsp: true 64 | - pymdownx.magiclink: 65 | hide_protocol: true 66 | repo_url_shortener: true 67 | - pymdownx.smartsymbols 68 | - pymdownx.superfences 69 | - pymdownx.tasklist: 70 | custom_checkbox: true 71 | - pymdownx.tilde 72 | - toc: 73 | permalink: true 74 | -------------------------------------------------------------------------------- /docs/src/contributing.md: -------------------------------------------------------------------------------- 1 | ../../CONTRIBUTING.md -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /docs/src/release-notes.md: -------------------------------------------------------------------------------- 1 | ../../CHANGES.md -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "supermorecado" 3 | description = "Extend the functionality of morecantile with additional commands." 4 | readme = "README.md" 5 | requires-python = ">=3.8" 6 | license = {file = "LICENSE"} 7 | authors = [ 8 | {name = "Vincent Sarago", email = "vincent@developmentseed.com"}, 9 | ] 10 | keywords = ["GIS", "TMS", "TileMatrixSet", "Map Tile"] 11 | classifiers = [ 12 | "Intended Audience :: Information Technology", 13 | "Intended Audience :: Science/Research", 14 | "License :: OSI Approved :: BSD License", 15 | "Programming Language :: Python :: 3.8", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Topic :: Scientific/Engineering :: GIS", 20 | ] 21 | dynamic = ["version"] 22 | dependencies = [ 23 | "morecantile", 24 | "rasterio", 25 | ] 26 | 27 | [project.optional-dependencies] 28 | test = [ 29 | "pytest", 30 | "pytest-cov", 31 | ] 32 | dev = [ 33 | "pre-commit", 34 | ] 35 | docs = [ 36 | "mkdocs", 37 | "mkdocs-material", 38 | "pygments", 39 | ] 40 | 41 | [project.urls] 42 | Source = "https://github.com/developmentseed/supermorecado" 43 | Documentation = "https://developmentseed.org/supermorecado/" 44 | 45 | [project.scripts] 46 | supermorecado = "supermorecado.scripts.cli:cli" 47 | 48 | [build-system] 49 | requires = ["flit>=3.2,<4"] 50 | build-backend = "flit_core.buildapi" 51 | 52 | [tool.flit.module] 53 | name = "supermorecado" 54 | 55 | [tool.flit.sdist] 56 | exclude = [ 57 | "tests/", 58 | "docs/", 59 | ".github/", 60 | "CHANGES.md", 61 | "CONTRIBUTING.md", 62 | ] 63 | 64 | [tool.coverage.run] 65 | branch = true 66 | parallel = true 67 | 68 | [tool.coverage.report] 69 | exclude_lines = [ 70 | "no cov", 71 | "if __name__ == .__main__.:", 72 | "if TYPE_CHECKING:", 73 | ] 74 | 75 | [tool.isort] 76 | profile = "black" 77 | known_first_party = ["supermorecado"] 78 | known_third_party = ["rasterio", "morecantile"] 79 | default_section = "THIRDPARTY" 80 | 81 | [tool.mypy] 82 | no_strict_optional = true 83 | 84 | [tool.ruff] 85 | select = [ 86 | "D1", # pydocstyle errors 87 | "E", # pycodestyle errors 88 | "W", # pycodestyle warnings 89 | "F", # flake8 90 | "C", # flake8-comprehensions 91 | "B", # flake8-bugbear 92 | ] 93 | ignore = [ 94 | "E501", # line too long, handled by black 95 | "B008", # do not perform function calls in argument defaults 96 | "B905", # ignore zip() without an explicit strict= parameter, only support with python >3.10 97 | ] 98 | 99 | [tool.ruff.per-file-ignores] 100 | "tests/test_cli.py" = ["D103"] 101 | "tests/test_mod.py" = ["D103"] 102 | -------------------------------------------------------------------------------- /supermorecado/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | supermorecado is an adaptation of mapbox/supermorecado to work with other TMS grid. 3 | 4 | """ 5 | 6 | __version__ = "0.1.2" 7 | 8 | from .burntiles import burnTiles # noqa 9 | from .densitytiles import densityTiles # noqa 10 | from .edge_finder import findedges # noqa 11 | from .uniontiles import unionTiles # noqa 12 | -------------------------------------------------------------------------------- /supermorecado/burntiles.py: -------------------------------------------------------------------------------- 1 | """Supermercado.burntiles but for other TMS. 2 | 3 | This submodule is adapted from mapbox/supermercado project: 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2015 Mapbox 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | """ 28 | 29 | from typing import Any, Dict, Sequence, Tuple 30 | 31 | import attr 32 | import morecantile 33 | import numpy 34 | from affine import Affine 35 | from rasterio import features 36 | 37 | WEB_MERCATOR_TMS = morecantile.tms.get("WebMercatorQuad") 38 | 39 | 40 | def _feature_extrema(geometry: Dict) -> Tuple[float, float, float, float]: 41 | """Get bounds extrema for a feature.""" 42 | if geometry["type"] == "Polygon": 43 | x, y = zip(*[c for part in geometry["coordinates"] for c in part]) 44 | 45 | elif geometry["type"] == "MultiPolygon": 46 | x, y = zip( 47 | *[c for poly in geometry["coordinates"] for part in poly for c in part] 48 | ) 49 | 50 | elif geometry["type"] == "LineString": 51 | x, y = zip(*list(geometry["coordinates"])) 52 | 53 | elif geometry["type"] == "Point": 54 | x, y = geometry["coordinates"] 55 | return x, y, x, y 56 | 57 | return min(x), min(y), max(x), max(y) 58 | 59 | 60 | @attr.s 61 | class burnTiles: 62 | """Burntiles.""" 63 | 64 | tms: morecantile.TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS) 65 | 66 | def find_extrema( 67 | self, 68 | features: Sequence[Dict[Any, Any]], 69 | truncate: bool = True, 70 | ) -> Tuple[float, float, float, float]: 71 | """Get bounds extrema for a set of features.""" 72 | epsilon = 1.0e-10 73 | min_x, min_y, max_x, max_y = zip( 74 | *[_feature_extrema(f["geometry"]) for f in features] 75 | ) 76 | 77 | tms_bbox = self.tms.bbox 78 | if truncate: 79 | return ( 80 | max(min(min_x) + epsilon, tms_bbox.left), 81 | max(min(min_y) + epsilon, tms_bbox.bottom), 82 | min(max(max_x) - epsilon, tms_bbox.right), 83 | min(max(max_y) - epsilon, tms_bbox.top), 84 | ) 85 | 86 | else: 87 | return ( 88 | min(min_x) + epsilon, 89 | min(min_y) + epsilon, 90 | max(max_x) - epsilon, 91 | max(max_y) - epsilon, 92 | ) 93 | 94 | def project_geom(self, geom: Dict, truncate: bool = False) -> Dict: 95 | """reproject geom in TMS CRS.""" 96 | if geom["type"] == "Polygon": 97 | return { 98 | "type": geom["type"], 99 | "coordinates": [ 100 | [self.tms.xy(*coords, truncate=truncate) for coords in part] 101 | for part in geom["coordinates"] 102 | ], 103 | } 104 | 105 | elif geom["type"] == "LineString": 106 | return { 107 | "type": geom["type"], 108 | "coordinates": [ 109 | self.tms.xy(*coords, truncate=truncate) 110 | for coords in geom["coordinates"] 111 | ], 112 | } 113 | 114 | elif geom["type"] == "Point": 115 | return { 116 | "type": geom["type"], 117 | "coordinates": self.tms.xy(*geom["coordinates"], truncate=truncate), 118 | } 119 | 120 | elif geom["type"] == "MultiPolygon": 121 | return { 122 | "type": geom["type"], 123 | "coordinates": [ 124 | [ 125 | [self.tms.xy(*coords, truncate=truncate) for coords in part] 126 | for part in poly 127 | ] 128 | for poly in geom["coordinates"] 129 | ], 130 | } 131 | 132 | else: 133 | raise Exception(f"Invalid geometry type {geom['type']}") 134 | 135 | def tile_extrema( 136 | self, bounds: Tuple[float, float, float, float], zoom: int 137 | ) -> Dict: 138 | """Tiles min/max at the given zoom for bounds.""" 139 | ulTile = self.tms.tile(bounds[0], bounds[3], zoom) 140 | lrTile = self.tms.tile(bounds[2], bounds[1], zoom) 141 | 142 | minx, maxx = (min(ulTile.x, lrTile.x), max(ulTile.x, lrTile.x)) 143 | miny, maxy = (min(ulTile.y, lrTile.y), max(ulTile.y, lrTile.y)) 144 | 145 | return { 146 | "x": {"min": minx, "max": maxx + 1}, 147 | "y": {"min": miny, "max": maxy + 1}, 148 | } 149 | 150 | def make_transform(self, tilerange: Dict, zoom: int) -> Affine: 151 | """Create Affine Transform from extrema.""" 152 | ulx, uly = self.tms.xy( 153 | *self.tms.ul(tilerange["x"]["min"], tilerange["y"]["min"], zoom) 154 | ) 155 | 156 | lrx, lry = self.tms.xy( 157 | *self.tms.ul(tilerange["x"]["max"], tilerange["y"]["max"], zoom) 158 | ) 159 | 160 | xcell = (lrx - ulx) / float(tilerange["x"]["max"] - tilerange["x"]["min"]) 161 | ycell = (uly - lry) / float(tilerange["y"]["max"] - tilerange["y"]["min"]) 162 | 163 | return Affine(xcell, 0, ulx, 0, -ycell, uly) 164 | 165 | def burn( 166 | self, 167 | polys: Sequence[Dict[Any, Any]], 168 | zoom: int, 169 | truncate: bool = True, 170 | ) -> numpy.ndarray: 171 | """Burn geometries to Tiles.""" 172 | bounds = self.find_extrema(polys, truncate=truncate) 173 | 174 | tilerange = self.tile_extrema(bounds, zoom) 175 | afftrans = self.make_transform(tilerange, zoom) 176 | 177 | burn = features.rasterize( 178 | ( 179 | (self.project_geom(geom["geometry"], truncate=truncate), 255) 180 | for geom in polys 181 | ), 182 | out_shape=( 183 | ( 184 | tilerange["y"]["max"] - tilerange["y"]["min"], 185 | tilerange["x"]["max"] - tilerange["x"]["min"], 186 | ) 187 | ), 188 | transform=afftrans, 189 | all_touched=True, 190 | ) 191 | 192 | xys = numpy.fliplr(numpy.dstack(numpy.where(burn))[0]) 193 | xys[:, 0] += tilerange["x"]["min"] 194 | xys[:, 1] += tilerange["y"]["min"] 195 | 196 | return numpy.append( 197 | xys, numpy.zeros((xys.shape[0], 1), dtype=numpy.uint8) + zoom, axis=1 198 | ) 199 | -------------------------------------------------------------------------------- /supermorecado/densitytiles.py: -------------------------------------------------------------------------------- 1 | """Tiles heatmap but for other TMS. 2 | 3 | adapted from https://github.com/mapbox/supermercado/pull/53 4 | """ 5 | 6 | from typing import Dict, List 7 | 8 | import attr 9 | import morecantile 10 | import numpy 11 | import numpy.typing as npt 12 | from affine import Affine 13 | from rasterio import features 14 | 15 | from supermorecado import super_utils as sutils 16 | 17 | WEB_MERCATOR_TMS = morecantile.tms.get("WebMercatorQuad") 18 | 19 | 20 | def create_density( 21 | tiles: npt.NDArray, xmin: int, xmax: int, ymin: int, ymax: int, pad: int = 1 22 | ) -> numpy.ndarray: 23 | """Given an ndarray or tiles and range, create a density raster of tile frequency.""" 24 | burn = numpy.zeros( 25 | (xmax - xmin + (pad * 2 + 1), ymax - ymin + (pad * 2 + 1)), dtype=numpy.uint32 26 | ) 27 | tiles, counts = numpy.unique(tiles, return_counts=True, axis=0) 28 | burn[(tiles[:, 0] - xmin + pad, tiles[:, 1] - ymin + pad)] = counts 29 | return burn 30 | 31 | 32 | @attr.s 33 | class densityTiles: 34 | """heatmap.""" 35 | 36 | tms: morecantile.TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS) 37 | 38 | def heatmap(self, tiles: npt.NDArray) -> List[Dict]: 39 | """Creates a vector "heatmap" of tile densities""" 40 | xmin, xmax, ymin, ymax = sutils.get_range(tiles) 41 | 42 | zoom = sutils.get_zoom(tiles) 43 | 44 | burn = create_density(tiles, xmin, xmax, ymin, ymax, 0) 45 | density = numpy.asarray( 46 | numpy.flipud(numpy.rot90(burn)).astype(numpy.uint8), 47 | order="C", 48 | ) 49 | 50 | nw = self.tms.xy(*self.tms.ul(xmin, ymin, zoom)) 51 | se = self.tms.xy(*self.tms.ul(xmax + 1, ymax + 1, zoom)) 52 | afftrans = Affine( 53 | ((se[0] - nw[0]) / float(xmax - xmin + 1)), 54 | 0.0, 55 | nw[0], 56 | 0.0, 57 | -((nw[1] - se[1]) / float(ymax - ymin + 1)), 58 | nw[1], 59 | ) 60 | 61 | unprojecter = sutils.Unprojecter(self.tms) 62 | 63 | return [ 64 | { 65 | "geometry": unprojecter.unproject(feature), 66 | "properties": {"n": count}, 67 | "type": "Feature", 68 | } 69 | for feature, count in features.shapes( 70 | density, 71 | mask=density > 0, 72 | transform=afftrans, 73 | ) 74 | ] 75 | -------------------------------------------------------------------------------- /supermorecado/edge_finder.py: -------------------------------------------------------------------------------- 1 | """Supermercado.edge_finder 2 | 3 | This submodule is adapted from mapbox/supermercado project: 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2015 Mapbox 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | """ 28 | 29 | import numpy 30 | import numpy.typing as npt 31 | 32 | from supermorecado import super_utils as sutils 33 | 34 | 35 | def findedges(tiles: npt.NDArray): 36 | """Find edges.""" 37 | xmin, xmax, ymin, ymax = sutils.get_range(tiles) 38 | 39 | zoom = sutils.get_zoom(tiles) 40 | 41 | # make an array of shape (xrange + 3, yrange + 3) 42 | burn = sutils.burnXYZs(tiles, xmin, xmax, ymin, ymax) 43 | 44 | # Create the indixes for rolling 45 | idxs = sutils.get_idx() 46 | 47 | # Using the indices to roll + stack the array, find the minimum along the rolled / stacked axis 48 | xys_edge = ( 49 | numpy.min( 50 | numpy.dstack( 51 | [numpy.roll(numpy.roll(burn, i[0], 0), i[1], 1) for i in idxs] 52 | ), 53 | axis=2, 54 | ) 55 | ^ burn 56 | ) 57 | 58 | # Set missed non-tiles to False 59 | xys_edge[burn is False] = False 60 | 61 | # Recreate the tile xyzs, and add the min vals 62 | xys_edge = numpy.dstack(numpy.where(xys_edge))[0] 63 | xys_edge[:, 0] += xmin - 1 64 | xys_edge[:, 1] += ymin - 1 65 | 66 | # Return the edge array 67 | return numpy.append( 68 | xys_edge, numpy.zeros((xys_edge.shape[0], 1), dtype=numpy.uint8) + zoom, axis=1 69 | ) 70 | -------------------------------------------------------------------------------- /supermorecado/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/supermorecado/7b2a68fce1c25921db81dd317ee4eb5ca574171e/supermorecado/py.typed -------------------------------------------------------------------------------- /supermorecado/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | """morecantile CLI.""" 2 | -------------------------------------------------------------------------------- /supermorecado/scripts/cli.py: -------------------------------------------------------------------------------- 1 | """Morecantile command line interface""" 2 | 3 | import json 4 | 5 | import click 6 | import cligj 7 | import morecantile 8 | 9 | from supermorecado import burnTiles, densityTiles, findedges 10 | from supermorecado import super_utils as sutils 11 | from supermorecado import unionTiles 12 | 13 | 14 | @click.group(help="Command line interface for the Supermorecado Python package.") 15 | def cli(): 16 | """Supermorecado CLI.""" 17 | pass 18 | 19 | 20 | @cli.command( 21 | short_help="For a stream of [, , ] tiles, return only those tiles that are on the edge." 22 | ) 23 | @click.argument("inputtiles", default="-", required=False) 24 | @click.option("--parsenames", is_flag=True) 25 | def edges(inputtiles, parsenames): 26 | """ 27 | For a stream of [, , ] tiles, return only those tiles that are on the edge. 28 | """ 29 | try: 30 | inputtiles = click.open_file(inputtiles).readlines() 31 | except IOError: 32 | inputtiles = [inputtiles] 33 | 34 | # parse the input stream into an array 35 | tiles = sutils.tile_parser(inputtiles, parsenames) 36 | for t in findedges(tiles): 37 | click.echo(t.tolist()) 38 | 39 | 40 | @cli.command( 41 | short_help="Returns the unioned shape of a stream of [, , ] tiles in GeoJSON." 42 | ) 43 | @click.argument("inputtiles", default="-", required=False) 44 | @click.option("--parsenames", is_flag=True) 45 | @click.option( 46 | "--identifier", 47 | type=click.Choice(morecantile.tms.list()), 48 | default="WebMercatorQuad", 49 | help="TileMatrixSet identifier.", 50 | ) 51 | @click.option( 52 | "--tms", 53 | help="Path to TileMatrixSet JSON file.", 54 | type=click.Path(), 55 | ) 56 | def union(inputtiles, parsenames, identifier, tms): 57 | """ 58 | Returns the unioned shape of a stream of [, , ] tiles in GeoJSON. 59 | """ 60 | try: 61 | inputtiles = click.open_file(inputtiles).readlines() 62 | except IOError: 63 | inputtiles = [inputtiles] 64 | 65 | tiles = sutils.tile_parser(inputtiles, parsenames) 66 | 67 | tilematrixset = morecantile.tms.get(identifier) 68 | if tms: 69 | with open(tms, "r") as f: 70 | tilematrixset = morecantile.TileMatrixSet(**json.load(f)) 71 | 72 | uniontiles = unionTiles(tms=tilematrixset) 73 | for u in uniontiles.union(tiles): 74 | click.echo(json.dumps(u)) 75 | 76 | 77 | @cli.command( 78 | short_help="Burn a stream of GeoJSONs into a output stream of the tiles they intersect for a given zoom." 79 | ) 80 | @cligj.features_in_arg 81 | @cligj.sequence_opt 82 | @click.argument("zoom", type=int) 83 | @click.option( 84 | "--identifier", 85 | type=click.Choice(morecantile.tms.list()), 86 | default="WebMercatorQuad", 87 | help="TileMatrixSet identifier.", 88 | ) 89 | @click.option( 90 | "--tms", 91 | help="Path to TileMatrixSet JSON file.", 92 | type=click.Path(), 93 | ) 94 | def burn(features, sequence, zoom, identifier, tms): 95 | """ 96 | Burn a stream of GeoJSONs into a output stream of the tiles they intersect for a given zoom. 97 | """ 98 | features = list(sutils.filter_features(features)) 99 | 100 | tilematrixset = morecantile.tms.get(identifier) 101 | if tms: 102 | with open(tms, "r") as f: 103 | tilematrixset = morecantile.TileMatrixSet(**json.load(f)) 104 | 105 | burntiles = burnTiles(tms=tilematrixset) 106 | for t in burntiles.burn(features, zoom): 107 | click.echo(t.tolist()) 108 | 109 | 110 | @cli.command(short_help="Creates a vector `heatmap` of tile densities.") 111 | @click.argument("inputtiles", default="-", required=False) 112 | @click.option("--parsenames", is_flag=True) 113 | @click.option( 114 | "--identifier", 115 | type=click.Choice(morecantile.tms.list()), 116 | default="WebMercatorQuad", 117 | help="TileMatrixSet identifier.", 118 | ) 119 | @click.option( 120 | "--tms", 121 | help="Path to TileMatrixSet JSON file.", 122 | type=click.Path(), 123 | ) 124 | def heatmap(inputtiles, parsenames, identifier, tms): 125 | """ 126 | Creates a vector `heatmap` of tile densities. 127 | """ 128 | try: 129 | inputtiles = click.open_file(inputtiles).readlines() 130 | except IOError: 131 | inputtiles = [inputtiles] 132 | 133 | tiles = sutils.tile_parser(inputtiles, parsenames) 134 | 135 | tilematrixset = morecantile.tms.get(identifier) 136 | if tms: 137 | with open(tms, "r") as f: 138 | tilematrixset = morecantile.TileMatrixSet(**json.load(f)) 139 | 140 | tiledensity = densityTiles(tms=tilematrixset) 141 | for u in tiledensity.heatmap(tiles): 142 | click.echo(json.dumps(u)) 143 | -------------------------------------------------------------------------------- /supermorecado/super_utils.py: -------------------------------------------------------------------------------- 1 | """Supermercado.super_utils but for other TMS. 2 | 3 | This submodule is adapted from mapbox/supermercado project: 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2015 Mapbox 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | """ 28 | import json 29 | import re 30 | from typing import Any, Dict, Generator, List, Sequence, Tuple 31 | 32 | import attr 33 | import morecantile 34 | import numpy 35 | import numpy.typing as npt 36 | 37 | 38 | def parseString(tilestring, matcher) -> List[int]: 39 | """Parse Tile.""" 40 | tile = [int(r) for r in matcher.match(tilestring).group().split("-")] 41 | tile.append(tile.pop(0)) 42 | return tile 43 | 44 | 45 | def get_range(xyz: npt.NDArray) -> Tuple[int, int, int, int]: 46 | """Get tiles extrema.""" 47 | return xyz[:, 0].min(), xyz[:, 0].max(), xyz[:, 1].min(), xyz[:, 1].max() 48 | 49 | 50 | def burnXYZs( 51 | tiles: npt.NDArray, xmin: float, xmax: float, ymin: float, ymax: float, pad: int = 1 52 | ): 53 | """Burn XYZ tiles.""" 54 | # make an array of shape (xrange + 3, yrange + 3) 55 | burn = numpy.zeros( 56 | (xmax - xmin + (pad * 2 + 1), ymax - ymin + (pad * 2 + 1)), dtype=bool 57 | ) 58 | 59 | # using the tile xys as indicides, burn in True where a tile exists 60 | burn[(tiles[:, 0] - xmin + pad, tiles[:, 1] - ymin + pad)] = True 61 | 62 | return burn 63 | 64 | 65 | def tile_parser(tiles: npt.ArrayLike, parsenames: bool = False) -> numpy.ndarray: 66 | """Parse Tile.""" 67 | if parsenames: 68 | tMatch = re.compile(r"[\d]+-[\d]+-[\d]+") 69 | tiles = numpy.array([parseString(t, tMatch) for t in tiles]) 70 | else: 71 | tiles = numpy.array([json.loads(t) for t in tiles]) 72 | 73 | return tiles 74 | 75 | 76 | def get_idx() -> numpy.ndarray: 77 | """Get numpy identity matrix.""" 78 | tt = numpy.zeros((3, 3), dtype=bool) 79 | tt[1, 1] = True 80 | return numpy.dstack(numpy.where(~tt))[0] - 1 81 | 82 | 83 | def get_zoom(tiles: npt.NDArray) -> int: 84 | """Get Zoom.""" 85 | t, d = tiles.shape 86 | if t < 1 or d != 3: 87 | raise ValueError("Tiles must be of shape n, 3") 88 | 89 | if tiles[:, 2].min() != tiles[:, 2].max(): 90 | raise ValueError("All tile zooms must be the same") 91 | 92 | return tiles[0, 2] 93 | 94 | 95 | def filter_features( # noqa: C901 96 | features: Sequence[Dict[Any, Any]] 97 | ) -> Generator[Dict, None, None]: 98 | """Filter feature.""" 99 | for f in features: 100 | if "geometry" in f and "type" in f["geometry"]: 101 | if f["geometry"]["type"] == "Polygon": 102 | yield f 103 | 104 | elif f["geometry"]["type"] == "Point": 105 | yield f 106 | 107 | elif f["geometry"]["type"] == "LineString": 108 | yield f 109 | 110 | elif f["geometry"]["type"] == "MultiPolygon": 111 | for part in f["geometry"]["coordinates"]: 112 | yield { 113 | "type": "Feature", 114 | "geometry": {"type": "Polygon", "coordinates": part}, 115 | } 116 | 117 | elif f["geometry"]["type"] == "MultiPoint": 118 | for part in f["geometry"]["coordinates"]: 119 | yield { 120 | "type": "Feature", 121 | "geometry": {"type": "Point", "coordinates": part}, 122 | } 123 | 124 | elif f["geometry"]["type"] == "MultiLineString": 125 | for part in f["geometry"]["coordinates"]: 126 | yield { 127 | "type": "Feature", 128 | "geometry": {"type": "LineString", "coordinates": part}, 129 | } 130 | 131 | 132 | @attr.s 133 | class Unprojecter: 134 | """Convert feature from TMS crs to the TMS's geographic CRS.""" 135 | 136 | tms: morecantile.TileMatrixSet = attr.ib() 137 | 138 | def xy_to_lng_lat( 139 | self, coordinates: Sequence[Tuple[float, float]] 140 | ) -> numpy.ndarray: 141 | """Convert coordinates.""" 142 | for c in coordinates: 143 | tc = numpy.array(c) 144 | yield numpy.dstack( 145 | [*self.tms._to_geographic.transform(tc[:, 0], tc[:, 1])] 146 | )[0].tolist() 147 | 148 | def unproject(self, feature: Dict[Any, Any]) -> Dict[Any, Any]: 149 | """Apply reprojection.""" 150 | feature["coordinates"] = list(self.xy_to_lng_lat(feature["coordinates"])) 151 | return feature 152 | -------------------------------------------------------------------------------- /supermorecado/uniontiles.py: -------------------------------------------------------------------------------- 1 | """Supermercado.uniontiles but for other TMS. 2 | 3 | This submodule is adapted from mapbox/supermercado project: 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2015 Mapbox 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | """ 28 | 29 | from typing import Dict, List 30 | 31 | import attr 32 | import morecantile 33 | import numpy 34 | import numpy.typing as npt 35 | from affine import Affine 36 | from rasterio import features 37 | 38 | from supermorecado import super_utils as sutils 39 | 40 | WEB_MERCATOR_TMS = morecantile.tms.get("WebMercatorQuad") 41 | 42 | 43 | @attr.s 44 | class unionTiles: 45 | """Uniontiles.""" 46 | 47 | tms: morecantile.TileMatrixSet = attr.ib(default=WEB_MERCATOR_TMS) 48 | 49 | def union(self, tiles: npt.NDArray) -> List[Dict]: 50 | """Union of tiles.""" 51 | xmin, xmax, ymin, ymax = sutils.get_range(tiles) 52 | 53 | zoom = sutils.get_zoom(tiles) 54 | 55 | burn = sutils.burnXYZs(tiles, xmin, xmax, ymin, ymax, 0) 56 | 57 | nw = self.tms.xy(*self.tms.ul(xmin, ymin, zoom)) 58 | se = self.tms.xy(*self.tms.ul(xmax + 1, ymax + 1, zoom)) 59 | afftrans = Affine( 60 | ((se[0] - nw[0]) / float(xmax - xmin + 1)), 61 | 0.0, 62 | nw[0], 63 | 0.0, 64 | -((nw[1] - se[1]) / float(ymax - ymin + 1)), 65 | nw[1], 66 | ) 67 | 68 | unprojecter = sutils.Unprojecter(self.tms) 69 | 70 | return [ 71 | { 72 | "geometry": unprojecter.unproject(feature), 73 | "properties": {}, 74 | "type": "Feature", 75 | } 76 | for feature, shapes in features.shapes( 77 | numpy.asarray( 78 | numpy.flipud(numpy.rot90(burn)).astype(numpy.uint8), 79 | order="C", 80 | ), 81 | transform=afftrans, 82 | ) 83 | if shapes == 1 84 | ] 85 | -------------------------------------------------------------------------------- /tests/expected/burned.txt: -------------------------------------------------------------------------------- 1 | [78, 178, 9] 2 | [79, 178, 9] 3 | [80, 178, 9] 4 | [81, 178, 9] 5 | [82, 178, 9] 6 | [83, 178, 9] 7 | [84, 178, 9] 8 | [85, 178, 9] 9 | [86, 178, 9] 10 | [87, 178, 9] 11 | [88, 178, 9] 12 | [98, 178, 9] 13 | [99, 178, 9] 14 | [77, 179, 9] 15 | [78, 179, 9] 16 | [79, 179, 9] 17 | [80, 179, 9] 18 | [81, 179, 9] 19 | [82, 179, 9] 20 | [83, 179, 9] 21 | [84, 179, 9] 22 | [85, 179, 9] 23 | [86, 179, 9] 24 | [96, 179, 9] 25 | [97, 179, 9] 26 | [98, 179, 9] 27 | [99, 179, 9] 28 | [100, 179, 9] 29 | [76, 180, 9] 30 | [77, 180, 9] 31 | [78, 180, 9] 32 | [79, 180, 9] 33 | [80, 180, 9] 34 | [81, 180, 9] 35 | [82, 180, 9] 36 | [83, 180, 9] 37 | [84, 180, 9] 38 | [95, 180, 9] 39 | [96, 180, 9] 40 | [97, 180, 9] 41 | [98, 180, 9] 42 | [99, 180, 9] 43 | [100, 180, 9] 44 | [103, 180, 9] 45 | [104, 180, 9] 46 | [105, 180, 9] 47 | [106, 180, 9] 48 | [107, 180, 9] 49 | [108, 180, 9] 50 | [76, 181, 9] 51 | [77, 181, 9] 52 | [78, 181, 9] 53 | [79, 181, 9] 54 | [80, 181, 9] 55 | [81, 181, 9] 56 | [82, 181, 9] 57 | [94, 181, 9] 58 | [95, 181, 9] 59 | [96, 181, 9] 60 | [97, 181, 9] 61 | [98, 181, 9] 62 | [99, 181, 9] 63 | [100, 181, 9] 64 | [101, 181, 9] 65 | [102, 181, 9] 66 | [103, 181, 9] 67 | [104, 181, 9] 68 | [105, 181, 9] 69 | [106, 181, 9] 70 | [107, 181, 9] 71 | [75, 182, 9] 72 | [76, 182, 9] 73 | [77, 182, 9] 74 | [78, 182, 9] 75 | [79, 182, 9] 76 | [80, 182, 9] 77 | [89, 182, 9] 78 | [90, 182, 9] 79 | [91, 182, 9] 80 | [95, 182, 9] 81 | [96, 182, 9] 82 | [97, 182, 9] 83 | [98, 182, 9] 84 | [99, 182, 9] 85 | [100, 182, 9] 86 | [101, 182, 9] 87 | [102, 182, 9] 88 | [103, 182, 9] 89 | [104, 182, 9] 90 | [105, 182, 9] 91 | [106, 182, 9] 92 | [109, 182, 9] 93 | [74, 183, 9] 94 | [75, 183, 9] 95 | [76, 183, 9] 96 | [77, 183, 9] 97 | [78, 183, 9] 98 | [79, 183, 9] 99 | [81, 183, 9] 100 | [87, 183, 9] 101 | [88, 183, 9] 102 | [89, 183, 9] 103 | [90, 183, 9] 104 | [91, 183, 9] 105 | [93, 183, 9] 106 | [94, 183, 9] 107 | [95, 183, 9] 108 | [96, 183, 9] 109 | [97, 183, 9] 110 | [98, 183, 9] 111 | [99, 183, 9] 112 | [100, 183, 9] 113 | [101, 183, 9] 114 | [103, 183, 9] 115 | [104, 183, 9] 116 | [105, 183, 9] 117 | [106, 183, 9] 118 | [108, 183, 9] 119 | [109, 183, 9] 120 | [75, 184, 9] 121 | [76, 184, 9] 122 | [77, 184, 9] 123 | [78, 184, 9] 124 | [79, 184, 9] 125 | [81, 184, 9] 126 | [82, 184, 9] 127 | [83, 184, 9] 128 | [85, 184, 9] 129 | [86, 184, 9] 130 | [87, 184, 9] 131 | [88, 184, 9] 132 | [89, 184, 9] 133 | [90, 184, 9] 134 | [91, 184, 9] 135 | [92, 184, 9] 136 | [93, 184, 9] 137 | [94, 184, 9] 138 | [95, 184, 9] 139 | [96, 184, 9] 140 | [97, 184, 9] 141 | [98, 184, 9] 142 | [99, 184, 9] 143 | [100, 184, 9] 144 | [101, 184, 9] 145 | [102, 184, 9] 146 | [103, 184, 9] 147 | [104, 184, 9] 148 | [105, 184, 9] 149 | [108, 184, 9] 150 | [109, 184, 9] 151 | [110, 184, 9] 152 | [75, 185, 9] 153 | [76, 185, 9] 154 | [77, 185, 9] 155 | [78, 185, 9] 156 | [79, 185, 9] 157 | [83, 185, 9] 158 | [84, 185, 9] 159 | [85, 185, 9] 160 | [86, 185, 9] 161 | [87, 185, 9] 162 | [88, 185, 9] 163 | [89, 185, 9] 164 | [90, 185, 9] 165 | [91, 185, 9] 166 | [92, 185, 9] 167 | [93, 185, 9] 168 | [94, 185, 9] 169 | [95, 185, 9] 170 | [96, 185, 9] 171 | [97, 185, 9] 172 | [98, 185, 9] 173 | [99, 185, 9] 174 | [100, 185, 9] 175 | [101, 185, 9] 176 | [102, 185, 9] 177 | [104, 185, 9] 178 | [107, 185, 9] 179 | [108, 185, 9] 180 | [109, 185, 9] 181 | [110, 185, 9] 182 | [75, 186, 9] 183 | [76, 186, 9] 184 | [77, 186, 9] 185 | [78, 186, 9] 186 | [79, 186, 9] 187 | [84, 186, 9] 188 | [85, 186, 9] 189 | [86, 186, 9] 190 | [87, 186, 9] 191 | [88, 186, 9] 192 | [89, 186, 9] 193 | [90, 186, 9] 194 | [91, 186, 9] 195 | [92, 186, 9] 196 | [93, 186, 9] 197 | [94, 186, 9] 198 | [95, 186, 9] 199 | [96, 186, 9] 200 | [97, 186, 9] 201 | [98, 186, 9] 202 | [99, 186, 9] 203 | [100, 186, 9] 204 | [101, 186, 9] 205 | [102, 186, 9] 206 | [103, 186, 9] 207 | [107, 186, 9] 208 | [108, 186, 9] 209 | [109, 186, 9] 210 | [110, 186, 9] 211 | [111, 186, 9] 212 | [76, 187, 9] 213 | [77, 187, 9] 214 | [78, 187, 9] 215 | [79, 187, 9] 216 | [80, 187, 9] 217 | [81, 187, 9] 218 | [86, 187, 9] 219 | [87, 187, 9] 220 | [88, 187, 9] 221 | [89, 187, 9] 222 | [90, 187, 9] 223 | [91, 187, 9] 224 | [92, 187, 9] 225 | [93, 187, 9] 226 | [94, 187, 9] 227 | [95, 187, 9] 228 | [96, 187, 9] 229 | [97, 187, 9] 230 | [98, 187, 9] 231 | [99, 187, 9] 232 | [100, 187, 9] 233 | [101, 187, 9] 234 | [102, 187, 9] 235 | [103, 187, 9] 236 | [104, 187, 9] 237 | [107, 187, 9] 238 | [108, 187, 9] 239 | [109, 187, 9] 240 | [110, 187, 9] 241 | [111, 187, 9] 242 | [76, 188, 9] 243 | [77, 188, 9] 244 | [78, 188, 9] 245 | [79, 188, 9] 246 | [80, 188, 9] 247 | [81, 188, 9] 248 | [82, 188, 9] 249 | [83, 188, 9] 250 | [87, 188, 9] 251 | [88, 188, 9] 252 | [89, 188, 9] 253 | [90, 188, 9] 254 | [91, 188, 9] 255 | [92, 188, 9] 256 | [93, 188, 9] 257 | [94, 188, 9] 258 | [95, 188, 9] 259 | [96, 188, 9] 260 | [97, 188, 9] 261 | [98, 188, 9] 262 | [99, 188, 9] 263 | [100, 188, 9] 264 | [101, 188, 9] 265 | [102, 188, 9] 266 | [103, 188, 9] 267 | [104, 188, 9] 268 | [107, 188, 9] 269 | [108, 188, 9] 270 | [109, 188, 9] 271 | [110, 188, 9] 272 | [111, 188, 9] 273 | [112, 188, 9] 274 | [78, 189, 9] 275 | [79, 189, 9] 276 | [80, 189, 9] 277 | [81, 189, 9] 278 | [82, 189, 9] 279 | [83, 189, 9] 280 | [84, 189, 9] 281 | [89, 189, 9] 282 | [90, 189, 9] 283 | [92, 189, 9] 284 | [93, 189, 9] 285 | [94, 189, 9] 286 | [95, 189, 9] 287 | [96, 189, 9] 288 | [97, 189, 9] 289 | [98, 189, 9] 290 | [100, 189, 9] 291 | [101, 189, 9] 292 | [102, 189, 9] 293 | [103, 189, 9] 294 | [104, 189, 9] 295 | [105, 189, 9] 296 | [108, 189, 9] 297 | [109, 189, 9] 298 | [110, 189, 9] 299 | [111, 189, 9] 300 | [112, 189, 9] 301 | [79, 190, 9] 302 | [80, 190, 9] 303 | [81, 190, 9] 304 | [82, 190, 9] 305 | [83, 190, 9] 306 | [84, 190, 9] 307 | [85, 190, 9] 308 | [86, 190, 9] 309 | [91, 190, 9] 310 | [92, 190, 9] 311 | [93, 190, 9] 312 | [94, 190, 9] 313 | [95, 190, 9] 314 | [96, 190, 9] 315 | [97, 190, 9] 316 | [98, 190, 9] 317 | [101, 190, 9] 318 | [102, 190, 9] 319 | [103, 190, 9] 320 | [104, 190, 9] 321 | [105, 190, 9] 322 | [107, 190, 9] 323 | [108, 190, 9] 324 | [109, 190, 9] 325 | [110, 190, 9] 326 | [111, 190, 9] 327 | [112, 190, 9] 328 | [81, 191, 9] 329 | [82, 191, 9] 330 | [83, 191, 9] 331 | [84, 191, 9] 332 | [85, 191, 9] 333 | [86, 191, 9] 334 | [87, 191, 9] 335 | [89, 191, 9] 336 | [90, 191, 9] 337 | [91, 191, 9] 338 | [92, 191, 9] 339 | [93, 191, 9] 340 | [94, 191, 9] 341 | [95, 191, 9] 342 | [96, 191, 9] 343 | [97, 191, 9] 344 | [98, 191, 9] 345 | [102, 191, 9] 346 | [103, 191, 9] 347 | [107, 191, 9] 348 | [108, 191, 9] 349 | [109, 191, 9] 350 | [110, 191, 9] 351 | [111, 191, 9] 352 | [82, 192, 9] 353 | [83, 192, 9] 354 | [84, 192, 9] 355 | [85, 192, 9] 356 | [86, 192, 9] 357 | [88, 192, 9] 358 | [89, 192, 9] 359 | [90, 192, 9] 360 | [91, 192, 9] 361 | [92, 192, 9] 362 | [93, 192, 9] 363 | [94, 192, 9] 364 | [95, 192, 9] 365 | [96, 192, 9] 366 | [97, 192, 9] 367 | [98, 192, 9] 368 | [107, 192, 9] 369 | [108, 192, 9] 370 | [109, 192, 9] 371 | [110, 192, 9] 372 | [111, 192, 9] 373 | [81, 193, 9] 374 | [82, 193, 9] 375 | [83, 193, 9] 376 | [84, 193, 9] 377 | [85, 193, 9] 378 | [86, 193, 9] 379 | [87, 193, 9] 380 | [88, 193, 9] 381 | [89, 193, 9] 382 | [90, 193, 9] 383 | [91, 193, 9] 384 | [92, 193, 9] 385 | [93, 193, 9] 386 | [94, 193, 9] 387 | [95, 193, 9] 388 | [96, 193, 9] 389 | [97, 193, 9] 390 | [106, 193, 9] 391 | [107, 193, 9] 392 | [108, 193, 9] 393 | [109, 193, 9] 394 | [110, 193, 9] 395 | [80, 194, 9] 396 | [81, 194, 9] 397 | [82, 194, 9] 398 | [83, 194, 9] 399 | [84, 194, 9] 400 | [85, 194, 9] 401 | [86, 194, 9] 402 | [87, 194, 9] 403 | [88, 194, 9] 404 | [89, 194, 9] 405 | [90, 194, 9] 406 | [91, 194, 9] 407 | [93, 194, 9] 408 | [94, 194, 9] 409 | [95, 194, 9] 410 | [96, 194, 9] 411 | [106, 194, 9] 412 | [107, 194, 9] 413 | [108, 194, 9] 414 | [109, 194, 9] 415 | [110, 194, 9] 416 | [79, 195, 9] 417 | [80, 195, 9] 418 | [81, 195, 9] 419 | [82, 195, 9] 420 | [83, 195, 9] 421 | [84, 195, 9] 422 | [85, 195, 9] 423 | [86, 195, 9] 424 | [87, 195, 9] 425 | [88, 195, 9] 426 | [89, 195, 9] 427 | [90, 195, 9] 428 | [92, 195, 9] 429 | [93, 195, 9] 430 | [94, 195, 9] 431 | [95, 195, 9] 432 | [96, 195, 9] 433 | [97, 195, 9] 434 | [98, 195, 9] 435 | [99, 195, 9] 436 | [100, 195, 9] 437 | [101, 195, 9] 438 | [102, 195, 9] 439 | [103, 195, 9] 440 | [104, 195, 9] 441 | [105, 195, 9] 442 | [106, 195, 9] 443 | [107, 195, 9] 444 | [108, 195, 9] 445 | [109, 195, 9] 446 | [110, 195, 9] 447 | [79, 196, 9] 448 | [80, 196, 9] 449 | [81, 196, 9] 450 | [82, 196, 9] 451 | [83, 196, 9] 452 | [84, 196, 9] 453 | [85, 196, 9] 454 | [86, 196, 9] 455 | [87, 196, 9] 456 | [88, 196, 9] 457 | [89, 196, 9] 458 | [92, 196, 9] 459 | [93, 196, 9] 460 | [94, 196, 9] 461 | [95, 196, 9] 462 | [96, 196, 9] 463 | [97, 196, 9] 464 | [98, 196, 9] 465 | [99, 196, 9] 466 | [100, 196, 9] 467 | [101, 196, 9] 468 | [102, 196, 9] 469 | [103, 196, 9] 470 | [104, 196, 9] 471 | [105, 196, 9] 472 | [106, 196, 9] 473 | [107, 196, 9] 474 | [108, 196, 9] 475 | [109, 196, 9] 476 | [81, 197, 9] 477 | [82, 197, 9] 478 | [83, 197, 9] 479 | [84, 197, 9] 480 | [85, 197, 9] 481 | [86, 197, 9] 482 | [87, 197, 9] 483 | [88, 197, 9] 484 | [92, 197, 9] 485 | [93, 197, 9] 486 | [94, 197, 9] 487 | [95, 197, 9] 488 | [96, 197, 9] 489 | [97, 197, 9] 490 | [98, 197, 9] 491 | [99, 197, 9] 492 | [100, 197, 9] 493 | [101, 197, 9] 494 | [102, 197, 9] 495 | [103, 197, 9] 496 | [104, 197, 9] 497 | [105, 197, 9] 498 | [106, 197, 9] 499 | [107, 197, 9] 500 | [108, 197, 9] 501 | [109, 197, 9] 502 | [85, 198, 9] 503 | [86, 198, 9] 504 | [93, 198, 9] 505 | [94, 198, 9] 506 | [95, 198, 9] 507 | [96, 198, 9] 508 | [97, 198, 9] 509 | [98, 198, 9] 510 | [99, 198, 9] 511 | [100, 198, 9] 512 | [101, 198, 9] 513 | [102, 198, 9] 514 | [103, 198, 9] 515 | [104, 198, 9] 516 | [105, 198, 9] 517 | [106, 198, 9] 518 | [107, 198, 9] 519 | [108, 198, 9] 520 | [94, 199, 9] 521 | [95, 199, 9] 522 | [96, 199, 9] 523 | [97, 199, 9] 524 | [98, 199, 9] 525 | [99, 199, 9] 526 | [100, 199, 9] 527 | [101, 199, 9] 528 | [102, 199, 9] 529 | [103, 199, 9] 530 | [104, 199, 9] 531 | [105, 199, 9] 532 | [106, 199, 9] 533 | [107, 199, 9] 534 | [108, 199, 9] 535 | [95, 200, 9] 536 | [96, 200, 9] 537 | [97, 200, 9] 538 | [98, 200, 9] 539 | [99, 200, 9] 540 | [100, 200, 9] 541 | [101, 200, 9] 542 | [102, 200, 9] 543 | [103, 200, 9] 544 | -------------------------------------------------------------------------------- /tests/expected/edges.txt: -------------------------------------------------------------------------------- 1 | [4188, 3104, 13] 2 | [4192, 2737, 13] 3 | [4192, 2745, 13] 4 | [4192, 2746, 13] 5 | [4192, 2748, 13] 6 | [4192, 2750, 13] 7 | [4192, 2756, 13] 8 | [4192, 2758, 13] 9 | [4192, 2760, 13] 10 | [4192, 2762, 13] 11 | [4192, 2765, 13] 12 | [4192, 2769, 13] 13 | [4192, 2774, 13] 14 | [4192, 2775, 13] 15 | [4192, 2777, 13] 16 | [4192, 2778, 13] 17 | [4192, 2782, 13] 18 | [4192, 2785, 13] 19 | [4192, 2787, 13] 20 | [4192, 2793, 13] 21 | [4192, 2794, 13] 22 | [4192, 2798, 13] 23 | [4192, 2800, 13] 24 | [4192, 2805, 13] 25 | [4192, 2806, 13] 26 | [4192, 2807, 13] 27 | [4192, 2808, 13] 28 | [4192, 2809, 13] 29 | [4192, 2811, 13] 30 | [4192, 2812, 13] 31 | [4192, 2821, 13] 32 | [4192, 2825, 13] 33 | [4192, 2827, 13] 34 | [4192, 2832, 13] 35 | [4192, 2839, 13] 36 | [4192, 2853, 13] 37 | [4192, 2856, 13] 38 | [4192, 2859, 13] 39 | [4192, 2863, 13] 40 | [4192, 2865, 13] 41 | [4192, 2867, 13] 42 | [4192, 2868, 13] 43 | [4192, 2871, 13] 44 | [4192, 2873, 13] 45 | [4192, 2878, 13] 46 | [4192, 2879, 13] 47 | [4192, 2881, 13] 48 | [4192, 2884, 13] 49 | [4192, 2887, 13] 50 | [4192, 2891, 13] 51 | [4192, 2892, 13] 52 | [4192, 2897, 13] 53 | [4192, 2898, 13] 54 | [4192, 2899, 13] 55 | [4192, 2913, 13] 56 | [4192, 2919, 13] 57 | [4192, 2921, 13] 58 | [4192, 2927, 13] 59 | [4192, 2929, 13] 60 | [4192, 2930, 13] 61 | [4192, 2931, 13] 62 | [4192, 2935, 13] 63 | [4192, 2943, 13] 64 | [4192, 2947, 13] 65 | [4192, 2959, 13] 66 | [4192, 2961, 13] 67 | [4192, 2964, 13] 68 | [4192, 2969, 13] 69 | [4192, 2971, 13] 70 | [4192, 2974, 13] 71 | [4192, 2975, 13] 72 | [4192, 2976, 13] 73 | [4192, 2977, 13] 74 | [4192, 2980, 13] 75 | [4192, 2981, 13] 76 | [4192, 2982, 13] 77 | [4192, 2983, 13] 78 | [4192, 2986, 13] 79 | [4192, 2987, 13] 80 | [4192, 2988, 13] 81 | [4192, 2994, 13] 82 | [4192, 2997, 13] 83 | [4192, 3098, 13] 84 | [4192, 3099, 13] 85 | [4192, 3101, 13] 86 | -------------------------------------------------------------------------------- /tests/expected/heatmap.txt: -------------------------------------------------------------------------------- 1 | {"geometry": {"type": "Polygon", "coordinates": [[[-5.625000000000113, 52.482780222078254], [-5.625000000000113, 48.9224992637583], [-1.2131008896728105e-13, 48.9224992637583], [-1.2131008896728105e-13, 52.482780222078254], [-5.625000000000113, 52.482780222078254]]]}, "properties": {"n": 6.0}, "type": "Feature"} 2 | {"geometry": {"type": "Polygon", "coordinates": [[[-1.2131008896728105e-13, 52.482780222078254], [-1.2131008896728105e-13, 48.9224992637583], [5.62499999999987, 48.9224992637583], [5.62499999999987, 52.482780222078254], [-1.2131008896728105e-13, 52.482780222078254]]]}, "properties": {"n": 4.0}, "type": "Feature"} 3 | {"geometry": {"type": "Polygon", "coordinates": [[[5.62499999999987, 52.482780222078254], [5.62499999999987, 48.9224992637583], [11.24999999999986, 48.9224992637583], [11.24999999999986, 52.482780222078254], [5.62499999999987, 52.482780222078254]]]}, "properties": {"n": 6.0}, "type": "Feature"} 4 | {"geometry": {"type": "Polygon", "coordinates": [[[-5.625000000000113, 48.9224992637583], [-5.625000000000113, 45.08903556483109], [-1.2131008896728105e-13, 45.08903556483109], [-1.2131008896728105e-13, 48.9224992637583], [-5.625000000000113, 48.9224992637583]]]}, "properties": {"n": 2.0}, "type": "Feature"} 5 | {"geometry": {"type": "Polygon", "coordinates": [[[-1.2131008896728105e-13, 48.9224992637583], [-1.2131008896728105e-13, 45.08903556483109], [11.24999999999986, 45.08903556483109], [11.24999999999986, 48.9224992637583], [-1.2131008896728105e-13, 48.9224992637583]]]}, "properties": {"n": 1.0}, "type": "Feature"} 6 | {"geometry": {"type": "Polygon", "coordinates": [[[-5.625000000000113, 45.08903556483109], [-5.625000000000113, 40.97989806962022], [-1.2131008896728105e-13, 40.97989806962022], [-1.2131008896728105e-13, 45.08903556483109], [-5.625000000000113, 45.08903556483109]]]}, "properties": {"n": 4.0}, "type": "Feature"} 7 | {"geometry": {"type": "Polygon", "coordinates": [[[-1.2131008896728105e-13, 45.08903556483109], [-1.2131008896728105e-13, 40.97989806962022], [5.62499999999987, 40.97989806962022], [5.62499999999987, 45.08903556483109], [-1.2131008896728105e-13, 45.08903556483109]]]}, "properties": {"n": 3.0}, "type": "Feature"} 8 | {"geometry": {"type": "Polygon", "coordinates": [[[5.62499999999987, 45.08903556483109], [5.62499999999987, 40.97989806962022], [11.24999999999986, 40.97989806962022], [11.24999999999986, 45.08903556483109], [5.62499999999987, 45.08903556483109]]]}, "properties": {"n": 2.0}, "type": "Feature"} 9 | -------------------------------------------------------------------------------- /tests/expected/union.txt: -------------------------------------------------------------------------------- 1 | {"geometry": {"type": "Polygon", "coordinates": [[[4.21875, 44.056011695785244], [4.21875, 44.02442151965933], [4.2626953125, 44.02442151965933], [4.2626953125, 44.056011695785244], [4.21875, 44.056011695785244]]]}, "type": "Feature", "properties": {}} 2 | {"geometry": {"type": "Polygon", "coordinates": [[[4.21875, 43.86621800655638], [4.21875, 43.83452678223682], [4.2626953125, 43.83452678223682], [4.2626953125, 43.86621800655638], [4.21875, 43.86621800655638]]]}, "type": "Feature", "properties": {}} 3 | {"geometry": {"type": "Polygon", "coordinates": [[[4.21875, 40.11168866559596], [4.21875, 40.07807142745009], [4.2626953125, 40.07807142745009], [4.2626953125, 40.11168866559596], [4.21875, 40.11168866559596]]]}, "type": "Feature", "properties": {}} 4 | {"geometry": {"type": "Polygon", "coordinates": [[[4.042968749999999, 39.90973623453719], [4.042968749999999, 39.87601941962115], [4.086914062499999, 39.87601941962115], [4.086914062499999, 39.90973623453719], [4.042968749999999, 39.90973623453719]]]}, "type": "Feature", "properties": {}} 5 | -------------------------------------------------------------------------------- /tests/fixtures/edges.txt: -------------------------------------------------------------------------------- 1 | [4188, 3104, 13] 2 | [4192, 2977, 13] 3 | [4192, 3098, 13] 4 | [4192, 2983, 13] 5 | [4192, 2935, 13] 6 | [4192, 2982, 13] 7 | [4192, 2980, 13] 8 | [4192, 3101, 13] 9 | [4192, 2987, 13] 10 | [4192, 2987, 13] 11 | [4192, 2986, 13] 12 | [4192, 2981, 13] 13 | [4192, 2997, 13] 14 | [4192, 2969, 13] 15 | [4192, 2947, 13] 16 | [4192, 2927, 13] 17 | [4192, 2961, 13] 18 | [4192, 2988, 13] 19 | [4192, 2976, 13] 20 | [4192, 2891, 13] 21 | [4192, 2994, 13] 22 | [4192, 2959, 13] 23 | [4192, 2892, 13] 24 | [4192, 2975, 13] 25 | [4192, 2931, 13] 26 | [4192, 2943, 13] 27 | [4192, 2971, 13] 28 | [4192, 2931, 13] 29 | [4192, 2919, 13] 30 | [4192, 2929, 13] 31 | [4192, 2930, 13] 32 | [4192, 2897, 13] 33 | [4192, 2878, 13] 34 | [4192, 2879, 13] 35 | [4192, 2980, 13] 36 | [4192, 2868, 13] 37 | [4192, 2887, 13] 38 | [4192, 2881, 13] 39 | [4192, 2913, 13] 40 | [4192, 2884, 13] 41 | [4192, 2899, 13] 42 | [4192, 2809, 13] 43 | [4192, 2859, 13] 44 | [4192, 2807, 13] 45 | [4192, 2921, 13] 46 | [4192, 2775, 13] 47 | [4192, 2811, 13] 48 | [4192, 2827, 13] 49 | [4192, 2867, 13] 50 | [4192, 2865, 13] 51 | [4192, 2856, 13] 52 | [4192, 2873, 13] 53 | [4192, 2863, 13] 54 | [4192, 2839, 13] 55 | [4192, 2774, 13] 56 | [4192, 2974, 13] 57 | [4192, 2808, 13] 58 | [4192, 2832, 13] 59 | [4192, 2793, 13] 60 | [4192, 3098, 13] 61 | [4192, 2787, 13] 62 | [4192, 2859, 13] 63 | [4192, 2853, 13] 64 | [4192, 2825, 13] 65 | [4192, 2825, 13] 66 | [4192, 2808, 13] 67 | [4192, 2787, 13] 68 | [4192, 2898, 13] 69 | [4192, 2812, 13] 70 | [4192, 2859, 13] 71 | [4192, 2765, 13] 72 | [4192, 2806, 13] 73 | [4192, 2769, 13] 74 | [4192, 2964, 13] 75 | [4192, 2821, 13] 76 | [4192, 2778, 13] 77 | [4192, 2785, 13] 78 | [4192, 2805, 13] 79 | [4192, 2737, 13] 80 | [4192, 2800, 13] 81 | [4192, 2762, 13] 82 | [4192, 2756, 13] 83 | [4192, 2986, 13] 84 | [4192, 2794, 13] 85 | [4192, 2760, 13] 86 | [4192, 2777, 13] 87 | [4192, 2782, 13] 88 | [4192, 2746, 13] 89 | [4192, 2748, 13] 90 | [4192, 2745, 13] 91 | [4192, 2871, 13] 92 | [4192, 2798, 13] 93 | [4192, 2758, 13] 94 | [4192, 2756, 13] 95 | [4192, 2750, 13] 96 | [4192, 2977, 13] 97 | [4192, 2765, 13] 98 | [4192, 2981, 13] 99 | [4192, 3099, 13] 100 | [4192, 2983, 13] 101 | -------------------------------------------------------------------------------- /tests/fixtures/heatmap.txt: -------------------------------------------------------------------------------- 1 | [31, 21, 6] 2 | [32, 21, 6] 3 | [33, 21, 6] 4 | [31, 22, 6] 5 | [32, 22, 6] 6 | [33, 22, 6] 7 | [31, 23, 6] 8 | [32, 23, 6] 9 | [33, 23, 6] 10 | [31, 22, 6] 11 | [31, 21, 6] 12 | [31, 21, 6] 13 | [31, 21, 6] 14 | [33, 21, 6] 15 | [31, 23, 6] 16 | [31, 21, 6] 17 | [31, 21, 6] 18 | [32, 21, 6] 19 | [32, 23, 6] 20 | [33, 21, 6] 21 | [33, 21, 6] 22 | [31, 23, 6] 23 | [31, 23, 6] 24 | [33, 21, 6] 25 | [33, 21, 6] 26 | [32, 21, 6] 27 | [32, 21, 6] 28 | [33, 23, 6] 29 | [32, 23, 6] 30 | -------------------------------------------------------------------------------- /tests/fixtures/shape.geojson: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-120.76171875,38.788345355085625],[-113.15917968749999,42.87596410238254],[-114.9609375,43.03677585761058],[-116.806640625,42.35854391749705],[-122.78320312499999,45.1510532655634],[-120.41015624999999,44.33956524809713],[-115.927734375,45.98169518512228],[-115.7080078125,44.9336963896947],[-110.61035156249999,45.460130637921004],[-113.64257812499999,46.37725420510028],[-109.9951171875,47.69497434186282],[-109.1162109375,46.28622391806708],[-103.9306640625,47.040182144806664],[-106.435546875,44.49650533109345],[-107.1826171875,45.42929873257377],[-105.9521484375,45.9511496866914],[-108.9404296875,45.706179285330855],[-105.8203125,41.80407814427237],[-108.10546875,41.343824581185686],[-110.0830078125,43.992814500489914],[-110.74218749999999,40.979898069620155],[-111.62109375,41.705728515237524],[-111.97265625,39.13006024213511],[-105.29296874999999,38.92522904714054],[-103.71093749999999,42.293564192170095],[-104.765625,43.389081939117496],[-103.22753906249999,45.644768217751924],[-100.986328125,42.261049162113856],[-103.798828125,36.914764288955936],[-112.412109375,36.06686213257888],[-115.00488281250001,38.06539235133249],[-113.37890625,41.44272637767212],[-119.091796875,37.54457732085582],[-123.96972656249999,38.61687046392973],[-121.728515625,41.07935114946899],[-126.21093749999999,43.13306116240612],[-127.3095703125,45.460130637921004],[-124.27734374999999,47.81315451752768],[-118.037109375,47.81315451752768],[-124.01367187499999,45.85941212790755],[-124.67285156250001,43.929549935614595],[-118.740234375,41.37680856570233],[-120.76171875,38.788345355085625]]]}}]} -------------------------------------------------------------------------------- /tests/fixtures/title.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "coordinates": [ 9 | [ 10 | 10.542436950016793, 11 | 12.481776462822026 12 | ], 13 | [ 14 | 10.46648898499086, 15 | 10.93898746679477 16 | ], 17 | [ 18 | 10.700661877151589, 19 | 10.932774453011547 20 | ], 21 | [ 22 | 10.751293853834596, 23 | 11.695972394899286 24 | ], 25 | [ 26 | 11.150020670215866, 27 | 10.920348033262272 28 | ], 29 | [ 30 | 11.414926538959008, 31 | 10.928278778460893 32 | ], 33 | [ 34 | 11.003362149764286, 35 | 11.743697340899018 36 | ], 37 | [ 38 | 11.285062711019066, 39 | 11.784271883768255 40 | ], 41 | [ 42 | 11.44032632057727, 43 | 11.895604409075375 44 | ], 45 | [ 46 | 11.536089492427067, 47 | 12.246898011045474 48 | ], 49 | [ 50 | 11.422573906819167, 51 | 12.438599060864746 52 | ], 53 | [ 54 | 11.279280693742919, 55 | 12.592436121496874 56 | ], 57 | [ 58 | 10.99179574308107, 59 | 12.667055964728448 60 | ], 61 | [ 62 | 10.68167488589583, 63 | 12.648534064245084 64 | ], 65 | [ 66 | 10.555094944186862, 67 | 12.60531105241347 68 | ], 69 | [ 70 | 10.542436950016793, 71 | 12.494132596225285 72 | ] 73 | ], 74 | "type": "LineString" 75 | } 76 | }, 77 | { 78 | "type": "Feature", 79 | "properties": {}, 80 | "geometry": { 81 | "coordinates": [ 82 | [ 83 | 6.584953155710878, 84 | 13.18122217239332 85 | ], 86 | [ 87 | 5.183886476761899, 88 | 13.19379469475767 89 | ], 90 | [ 91 | 5.164516891339105, 92 | 11.996599692622127 93 | ], 94 | [ 95 | 6.358974659106593, 96 | 12.002915136138071 97 | ], 98 | [ 99 | 6.371887716056079, 100 | 11.187027355656824 101 | ], 102 | [ 103 | 5.151603834391068, 104 | 11.237693707477476 105 | ], 106 | [ 107 | 5.235538704558337, 108 | 10.939902682828176 109 | ], 110 | [ 111 | 6.623692326557915, 112 | 10.933563422479622 113 | ], 114 | [ 115 | 6.6301488550326155, 116 | 12.091315772179172 117 | ], 118 | [ 119 | 5.390495387943446, 120 | 12.103942054777164 121 | ], 122 | [ 123 | 5.403408444892904, 124 | 12.992557119323308 125 | ], 126 | [ 127 | 6.57849662723757, 128 | 13.005139273823133 129 | ], 130 | [ 131 | 6.597866212660364, 132 | 13.17493566886769 133 | ] 134 | ], 135 | "type": "LineString" 136 | } 137 | }, 138 | { 139 | "type": "Feature", 140 | "properties": {}, 141 | "geometry": { 142 | "coordinates": [ 143 | [ 144 | 6.927149164856189, 145 | 12.595895901288344 146 | ], 147 | [ 148 | 6.92069263638146, 149 | 11.003287817715488 150 | ], 151 | [ 152 | 7.766497866530358, 153 | 10.984273705162607 154 | ], 155 | [ 156 | 7.798780508902638, 157 | 12.589594688644368 158 | ], 159 | [ 160 | 7.617997711618585, 161 | 12.595895901288344 162 | ], 163 | [ 164 | 7.611541183143885, 165 | 11.142687026544934 166 | ], 167 | [ 168 | 7.04336667739571, 169 | 11.180693437757938 170 | ], 171 | [ 172 | 7.082105848241241, 173 | 12.589594688644368 174 | ], 175 | [ 176 | 6.9336056933294685, 177 | 12.602196959086413 178 | ] 179 | ], 180 | "type": "LineString" 181 | } 182 | }, 183 | { 184 | "type": "Feature", 185 | "properties": {}, 186 | "geometry": { 187 | "coordinates": [ 188 | [ 189 | 8.211998331264255, 190 | 12.49505796682331 191 | ], 192 | [ 193 | 8.166802631943938, 194 | 10.965258367788024 195 | ], 196 | [ 197 | 8.347585429227934, 198 | 10.952580796744087 199 | ], 200 | [ 201 | 8.360498486176027, 202 | 11.946070825329429 203 | ], 204 | [ 205 | 8.592933511255069, 206 | 11.939754053122627 207 | ], 208 | [ 209 | 8.831825064808868, 210 | 11.983968361886312 211 | ], 212 | [ 213 | 8.967412162772575, 214 | 12.135505149522487 215 | ], 216 | [ 217 | 8.97386869124594, 218 | 12.362648518082267 219 | ], 220 | [ 221 | 8.967412162772575, 222 | 12.551784165088435 223 | ], 224 | [ 225 | 8.818912007860803, 226 | 12.646300021929108 227 | ], 228 | [ 229 | 8.521911698037258, 230 | 12.696694195427469 231 | ], 232 | [ 233 | 8.334672372278504, 234 | 12.721887540429236 235 | ], 236 | [ 237 | 8.244280973636478, 238 | 12.709291180222323 239 | ], 240 | [ 241 | 8.218454859738983, 242 | 12.501361493450617 243 | ] 244 | ], 245 | "type": "LineString" 246 | } 247 | }, 248 | { 249 | "type": "Feature", 250 | "properties": {}, 251 | "geometry": { 252 | "coordinates": [ 253 | [ 254 | 9.262831348055698, 255 | 12.401138718633263 256 | ], 257 | [ 258 | 9.19109703279699, 259 | 10.984655582885694 260 | ], 261 | [ 262 | 10.196378099246175, 263 | 10.937298459408382 264 | ], 265 | [ 266 | 10.209291156195661, 267 | 11.216042240533739 268 | ], 269 | [ 270 | 9.350572869097277, 271 | 11.247700707008306 272 | ], 273 | [ 274 | 9.369942454521492, 275 | 11.564091600235528 276 | ], 277 | [ 278 | 9.98331265959132, 279 | 11.545118172297649 280 | ], 281 | [ 282 | 10.0155953019636, 283 | 11.848536910405187 284 | ], 285 | [ 286 | 9.434507739264546, 287 | 11.88012371418722 288 | ], 289 | [ 290 | 9.460333853163462, 291 | 12.290413969626144 292 | ], 293 | [ 294 | 10.286769497888173, 295 | 12.246259354078191 296 | ], 297 | [ 298 | 10.273856440940136, 299 | 12.56778578322455 300 | ], 301 | [ 302 | 9.276813158066602, 303 | 12.611538165779578 304 | ], 305 | [ 306 | 9.262475097035235, 307 | 12.398501295475171 308 | ] 309 | ], 310 | "type": "LineString" 311 | } 312 | }, 313 | { 314 | "type": "Feature", 315 | "properties": {}, 316 | "geometry": { 317 | "coordinates": [ 318 | [ 319 | 10.79655469699182, 320 | 12.318239275854566 321 | ], 322 | [ 323 | 10.82819968241904, 324 | 12.033725000110834 325 | ], 326 | [ 327 | 11.20296659848259, 328 | 12.029923566814716 329 | ], 330 | [ 331 | 11.226067408529588, 332 | 12.180263023905809 333 | ], 334 | [ 335 | 11.163636527947233, 336 | 12.345790597359851 337 | ], 338 | [ 339 | 10.948450627042263, 340 | 12.349146189223475 341 | ], 342 | [ 343 | 10.809212691163282, 344 | 12.330602480996902 345 | ] 346 | ], 347 | "type": "LineString" 348 | } 349 | }, 350 | { 351 | "type": "Feature", 352 | "properties": {}, 353 | "geometry": { 354 | "coordinates": [ 355 | [ 356 | 8.391445941386905, 357 | 12.500845346570244 358 | ], 359 | [ 360 | 8.391445941386905, 361 | 12.117468941971623 362 | ], 363 | [ 364 | 8.720553789829353, 365 | 12.13603224340794 366 | ], 367 | [ 368 | 8.828146740281113, 369 | 12.247384829218618 370 | ], 371 | [ 372 | 8.828146740281113, 373 | 12.395781830050495 374 | ], 375 | [ 376 | 8.720553789829353, 377 | 12.544094403682806 378 | ], 379 | [ 380 | 8.549670868523094, 381 | 12.556449944220702 382 | ], 383 | [ 384 | 8.41043293264272, 385 | 12.556449944220702 386 | ], 387 | [ 388 | 8.39777493847265, 389 | 12.476128347905146 390 | ] 391 | ], 392 | "type": "LineString" 393 | } 394 | }, 395 | { 396 | "type": "Feature", 397 | "properties": {}, 398 | "geometry": { 399 | "coordinates": [ 400 | [ 401 | 11.545497529354407, 402 | 11.023266917045774 403 | ], 404 | [ 405 | 11.784592381902698, 406 | 11.779910371890054 407 | ], 408 | [ 409 | 12.0792019190595, 410 | 12.618064554618755 411 | ], 412 | [ 413 | 12.504196804706766, 414 | 12.021063100304644 415 | ], 416 | [ 417 | 12.860058997384442, 418 | 12.520990640474324 419 | ], 420 | [ 421 | 13.400591661136474, 422 | 10.939126233153232 423 | ], 424 | [ 425 | 13.072540691865356, 426 | 10.876074335483509 427 | ], 428 | [ 429 | 12.76423616339585, 430 | 12.034585851409176 431 | ], 432 | [ 433 | 12.517750501528724, 434 | 11.700862848956021 435 | ], 436 | [ 437 | 12.204117578261844, 438 | 12.028306974540541 439 | ], 440 | [ 441 | 11.885370002373406, 442 | 10.902801690322192 443 | ], 444 | [ 445 | 11.630756507945705, 446 | 11.002739444408121 447 | ] 448 | ], 449 | "type": "LineString" 450 | } 451 | }, 452 | { 453 | "type": "Feature", 454 | "properties": {}, 455 | "geometry": { 456 | "coordinates": [ 457 | [ 458 | 13.64965394834607, 459 | 12.542543572399069 460 | ], 461 | [ 462 | 13.63872669227996, 463 | 10.959179708545363 464 | ], 465 | [ 466 | 14.660425134523337, 467 | 10.969907980693307 468 | ], 469 | [ 470 | 14.671352390590641, 471 | 12.55854363508898 472 | ], 473 | [ 474 | 13.655117576379695, 475 | 12.553210391264173 476 | ] 477 | ], 478 | "type": "LineString" 479 | } 480 | }, 481 | { 482 | "type": "Feature", 483 | "properties": {}, 484 | "geometry": { 485 | "coordinates": [ 486 | [ 487 | 14.006704628190533, 488 | 12.174270437117215 489 | ], 490 | [ 491 | 13.995777372123257, 492 | 11.388003670647805 493 | ], 494 | [ 495 | 14.378231334461248, 496 | 11.355863947262264 497 | ], 498 | [ 499 | 14.42194035872808, 500 | 12.18495227808404 501 | ], 502 | [ 503 | 14.01216825622302, 504 | 12.18495227808404 505 | ] 506 | ], 507 | "type": "LineString" 508 | } 509 | }, 510 | { 511 | "type": "Feature", 512 | "properties": {}, 513 | "geometry": { 514 | "coordinates": [ 515 | [ 516 | 15.0157595984966, 517 | 12.539052671995677 518 | ], 519 | [ 520 | 14.985521867615859, 521 | 10.983299513819972 522 | ], 523 | [ 524 | 15.212205902701953, 525 | 10.974398107308176 526 | ], 527 | [ 528 | 15.203138541298017, 529 | 11.809929599113048 530 | ], 531 | [ 532 | 15.70184341848767, 533 | 11.0100021222231 534 | ], 535 | [ 536 | 15.954697340705877, 537 | 11.00396592582456 538 | ], 539 | [ 540 | 15.401548716489458, 541 | 11.844826961532988 542 | ], 543 | [ 544 | 15.671684844819765, 545 | 11.851934169880735 546 | ], 547 | [ 548 | 15.828786478136067, 549 | 12.005118974080418 550 | ], 551 | [ 552 | 15.95572953778347, 553 | 12.217891611803694 554 | ], 555 | [ 556 | 15.934480011093825, 557 | 12.566845159700833 558 | ], 559 | [ 560 | 15.689502938774297, 561 | 12.707845917917751 562 | ], 563 | [ 564 | 15.41587443314532, 565 | 12.762672722689985 566 | ], 567 | [ 568 | 15.03077951741949, 569 | 12.763795510202073 570 | ], 571 | [ 572 | 15.030858674633208, 573 | 12.651768581698946 574 | ], 575 | [ 576 | 15.021791313229357, 577 | 12.54558016951728 578 | ] 579 | ], 580 | "type": "LineString" 581 | } 582 | }, 583 | { 584 | "type": "Feature", 585 | "properties": {}, 586 | "geometry": { 587 | "coordinates": [ 588 | [ 589 | 15.206670497204243, 590 | 12.439298929609919 591 | ], 592 | [ 593 | 15.197603135800364, 594 | 12.138113380730204 595 | ], 596 | [ 597 | 15.623769121761939, 598 | 12.173564875649845 599 | ], 600 | [ 601 | 15.696308012989988, 602 | 12.43044548602738 603 | ], 604 | [ 605 | 15.415219809483602, 606 | 12.483561603797497 607 | ], 608 | [ 609 | 15.206670497204243, 610 | 12.457004908632456 611 | ] 612 | ], 613 | "type": "LineString" 614 | } 615 | }, 616 | { 617 | "type": "Feature", 618 | "properties": {}, 619 | "geometry": { 620 | "coordinates": [ 621 | [ 622 | 17.626022558607673, 623 | 12.699134205783025 624 | ], 625 | [ 626 | 17.598820474397996, 627 | 11.039566276032218 628 | ], 629 | [ 630 | 18.65063439719657, 631 | 11.057367746307179 632 | ], 633 | [ 634 | 18.65063439719657, 635 | 11.226427814055455 636 | ], 637 | [ 638 | 17.81643714808021, 639 | 11.20863663264096 640 | ], 641 | [ 642 | 17.771100341062805, 643 | 12.530982298046084 644 | ], 645 | [ 646 | 18.487421891934645, 647 | 12.548687671172004 648 | ], 649 | [ 650 | 18.496489253338552, 651 | 12.761057075532094 652 | ], 653 | [ 654 | 17.626022558607673, 655 | 12.716827990588172 656 | ] 657 | ], 658 | "type": "LineString" 659 | } 660 | }, 661 | { 662 | "type": "Feature", 663 | "properties": {}, 664 | "geometry": { 665 | "coordinates": [ 666 | [ 667 | 18.772187482496804, 668 | 11.066678998598604 669 | ], 670 | [ 671 | 19.307161805299984, 672 | 12.779183072761953 673 | ], 674 | [ 675 | 19.692182465989063, 676 | 11.880303651885377 677 | ], 678 | [ 679 | 20.0506854403815, 680 | 11.111177234010555 681 | ], 682 | [ 683 | 19.805866682488585, 684 | 11.075579184739766 685 | ], 686 | [ 687 | 19.425037503544473, 688 | 11.99081839401444 689 | ], 690 | [ 691 | 19.279959721089313, 692 | 11.999689470080256 693 | ], 694 | [ 695 | 19.01700624038972, 696 | 11.066678998598604 697 | ], 698 | [ 699 | 18.78125484389966, 700 | 11.075579184739766 701 | ] 702 | ], 703 | "type": "LineString" 704 | } 705 | }, 706 | { 707 | "type": "Feature", 708 | "properties": {}, 709 | "geometry": { 710 | "coordinates": [ 711 | [ 712 | 19.32257938231018, 713 | 12.466729916657158 714 | ], 715 | [ 716 | 19.267455920669505, 717 | 12.12303636159254 718 | ], 719 | [ 720 | 19.43282630559318, 721 | 12.11002302199509 722 | ], 723 | [ 724 | 19.326381000354587, 725 | 12.464873318179741 726 | ] 727 | ], 728 | "type": "LineString" 729 | } 730 | }, 731 | { 732 | "type": "Feature", 733 | "properties": {}, 734 | "geometry": { 735 | "coordinates": [ 736 | [ 737 | 20.16648659742509, 738 | 12.71150966791366 739 | ], 740 | [ 741 | 20.158979800587588, 742 | 11.102980962577433 743 | ], 744 | [ 745 | 20.60188081402731, 746 | 11.073514802849928 747 | ], 748 | [ 749 | 20.894645890708432, 750 | 11.11771292731649 751 | ], 752 | [ 753 | 21.11234299900954, 754 | 11.272353419067286 755 | ], 756 | [ 757 | 21.20242456106547, 758 | 11.4710545316712 759 | ], 760 | [ 761 | 21.2925061231214, 762 | 11.853342283894676 763 | ], 764 | [ 765 | 21.315026513634763, 766 | 12.249767122805977 767 | ], 768 | [ 769 | 21.127356592685373, 770 | 12.594319999756223 771 | ], 772 | [ 773 | 20.917166197849184, 774 | 12.792046210852632 775 | ], 776 | [ 777 | 20.46675838757116, 778 | 12.843283377447563 779 | ], 780 | [ 781 | 20.219034091917564, 782 | 12.82864525143448 783 | ], 784 | [ 785 | 20.166486514051655, 786 | 12.777405106779497 787 | ], 788 | [ 789 | 20.166486514051655, 790 | 12.711509647220026 791 | ] 792 | ], 793 | "type": "LineString" 794 | } 795 | }, 796 | { 797 | "type": "Feature", 798 | "properties": {}, 799 | "geometry": { 800 | "coordinates": [ 801 | [ 802 | 20.404207275132137, 803 | 12.484159293749642 804 | ], 805 | [ 806 | 20.39670047829466, 807 | 11.441322763105902 808 | ], 809 | [ 810 | 20.696972351813315, 811 | 11.448680541823393 812 | ], 813 | [ 814 | 20.862121882248516, 815 | 11.684027881322777 816 | ], 817 | [ 818 | 20.989737428494465, 819 | 11.919175644756194 820 | ], 821 | [ 822 | 20.952203444304445, 823 | 12.242170993681881 824 | ], 825 | [ 826 | 20.726999539165035, 827 | 12.498818032505568 828 | ], 829 | [ 830 | 20.404207275132137, 831 | 12.535461243342695 832 | ] 833 | ], 834 | "type": "LineString" 835 | } 836 | }, 837 | { 838 | "type": "Feature", 839 | "properties": {}, 840 | "geometry": { 841 | "coordinates": [ 842 | [ 843 | 21.5036232387327, 844 | 12.867216366888243 845 | ], 846 | [ 847 | 21.550994835024767, 848 | 11.140842419001501 849 | ], 850 | [ 851 | 22.85067922930031, 852 | 11.139638072488452 853 | ], 854 | [ 855 | 22.84148718280747, 856 | 12.93081422925242 857 | ], 858 | [ 859 | 21.54837112517245, 860 | 12.907279282856095 861 | ] 862 | ], 863 | "type": "LineString" 864 | } 865 | }, 866 | { 867 | "type": "Feature", 868 | "properties": {}, 869 | "geometry": { 870 | "coordinates": [ 871 | [ 872 | 21.861342165026315, 873 | 12.60857213786187 874 | ], 875 | [ 876 | 21.907756304694345, 877 | 11.509935634418028 878 | ], 879 | [ 880 | 22.55139634388661, 881 | 11.489376252852693 882 | ], 883 | [ 884 | 22.52098383261702, 885 | 12.577314071452719 886 | ], 887 | [ 888 | 21.933954473662908, 889 | 12.59300726521181 890 | ] 891 | ], 892 | "type": "LineString" 893 | } 894 | }, 895 | { 896 | "type": "Feature", 897 | "properties": {}, 898 | "geometry": { 899 | "coordinates": [ 900 | [ 901 | 16.220664187288946, 902 | 12.624522550871177 903 | ], 904 | [ 905 | 16.197097174704, 906 | 11.076170888733131 907 | ], 908 | [ 909 | 17.186911703294612, 910 | 11.04513239469216 911 | ], 912 | [ 913 | 17.171200361571067, 914 | 11.27010090064526 915 | ], 916 | [ 917 | 16.385633275387676, 918 | 11.262345720958933 919 | ], 920 | [ 921 | 16.40134461711125, 922 | 11.711861532853831 923 | ], 924 | [ 925 | 17.014086944334537, 926 | 11.735096344062441 927 | ], 928 | [ 929 | 17.021942615195883, 930 | 11.99831143015004 931 | ], 932 | [ 933 | 16.44847864228194, 934 | 11.967355797447993 935 | ], 936 | [ 937 | 16.472045654867685, 938 | 12.555004932522039 939 | ], 940 | [ 941 | 17.124066336399494, 942 | 12.562729883530153 943 | ], 944 | [ 945 | 17.124066336399494, 946 | 12.732629867848487 947 | ], 948 | [ 949 | 16.220664187288946, 950 | 12.72490918273543 951 | ], 952 | [ 953 | 16.220664187288946, 954 | 12.647691611011183 955 | ] 956 | ], 957 | "type": "LineString" 958 | } 959 | } 960 | ] 961 | } 962 | -------------------------------------------------------------------------------- /tests/fixtures/union.txt: -------------------------------------------------------------------------------- 1 | [4188, 3104, 13] 2 | [4192, 2977, 13] 3 | [4192, 3098, 13] 4 | [4192, 2983, 13] 5 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | """tests from mapbox/supermercado project: 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2015 Mapbox 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | """ 26 | 27 | import json 28 | import os 29 | 30 | import morecantile 31 | from click.testing import CliRunner 32 | 33 | from supermorecado.scripts.cli import cli 34 | 35 | mercantile = morecantile.tms.get("WebMercatorQuad") 36 | 37 | 38 | def test_union_cli(): 39 | filename = os.path.join(os.path.dirname(__file__), "fixtures/union.txt") 40 | expectedFilename = os.path.join(os.path.dirname(__file__), "expected/union.txt") 41 | runner = CliRunner() 42 | result = runner.invoke(cli, ["union", filename]) 43 | assert result.exit_code == 0 44 | with open(expectedFilename) as ofile: 45 | expected = ofile.readlines() 46 | # TODO fuzzy test of featurecollection equality 47 | assert len(result.output.strip().split("\n")) == len(expected) 48 | 49 | 50 | def test_edge_cli(): 51 | filename = os.path.join(os.path.dirname(__file__), "fixtures/edges.txt") 52 | expectedFilename = os.path.join(os.path.dirname(__file__), "expected/edges.txt") 53 | runner = CliRunner() 54 | result = runner.invoke(cli, ["edges", filename]) 55 | assert result.exit_code == 0 56 | with open(expectedFilename) as ofile: 57 | expected = ofile.read() 58 | assert result.output == expected 59 | 60 | 61 | def test_burn_cli(): 62 | filename = os.path.join(os.path.dirname(__file__), "fixtures/shape.geojson") 63 | expectedFilename = os.path.join(os.path.dirname(__file__), "expected/burned.txt") 64 | 65 | with open(filename) as ofile: 66 | geojson = ofile.read() 67 | 68 | runner = CliRunner() 69 | result = runner.invoke(cli, ["burn", "9"], input=geojson) 70 | assert result.exit_code == 0 71 | 72 | with open(expectedFilename) as ofile: 73 | expected = ofile.read() 74 | assert result.output == expected 75 | 76 | 77 | def test_heatmap_cli(): 78 | filename = os.path.join(os.path.dirname(__file__), "fixtures/heatmap.txt") 79 | expectedFilename = os.path.join(os.path.dirname(__file__), "expected/heatmap.txt") 80 | 81 | runner = CliRunner() 82 | result = runner.invoke(cli, ["heatmap", filename]) 83 | assert result.exit_code == 0 84 | 85 | with open(expectedFilename) as ofile: 86 | expected = ofile.readlines() 87 | expected_counts = [json.loads(f)["properties"]["n"] for f in expected] 88 | 89 | count = [json.loads(f)["properties"]["n"] for f in result.output.splitlines()] 90 | assert count == expected_counts 91 | 92 | 93 | def test_burn_tile_center_point_roundtrip(): 94 | tile = [83885, 202615, 19] 95 | w, s, e, n = mercantile.bounds(*tile) 96 | 97 | x = (e - w) / 2 + w 98 | y = (n - s) / 2 + s 99 | 100 | point_feature = { 101 | "type": "Feature", 102 | "properties": {}, 103 | "geometry": {"type": "Point", "coordinates": [x, y]}, 104 | } 105 | 106 | runner = CliRunner() 107 | result = runner.invoke(cli, ["burn", "19"], input=json.dumps(point_feature)) 108 | 109 | assert json.loads(result.output) == tile 110 | 111 | 112 | def test_burn_tile_center_lines_roundtrip(): 113 | tiles = list(mercantile.children([0, 0, 0])) 114 | bounds = (mercantile.bounds(*t) for t in tiles) 115 | coords = (((e - w) / 2 + w, (n - s) / 2 + s) for w, s, e, n in bounds) 116 | 117 | features = { 118 | "type": "Feature", 119 | "properties": {}, 120 | "geometry": {"type": "LineString", "coordinates": list(coords)}, 121 | } 122 | 123 | runner = CliRunner() 124 | result = runner.invoke(cli, ["burn", "1"], input=json.dumps(features)) 125 | 126 | output_tiles = [json.loads(t) for t in result.output.split("\n") if t] 127 | assert sorted(output_tiles) == sorted([list(t) for t in tiles]) 128 | 129 | 130 | def test_burn_cli_tile_shape(): 131 | tilegeom = '{"bbox": [-122.4755859375, 37.75334401310657, -122.431640625, 37.78808138412046], "geometry": {"coordinates": [[[-122.4755859375, 37.75334401310657], [-122.4755859375, 37.78808138412046], [-122.431640625, 37.78808138412046], [-122.431640625, 37.75334401310657], [-122.4755859375, 37.75334401310657]]], "type": "Polygon"}, "id": "(1309, 3166, 13)", "properties": {"title": "XYZ tile (1309, 3166, 13)"}, "type": "Feature"}' 132 | runner = CliRunner() 133 | result = runner.invoke(cli, ["burn", "13"], input=tilegeom) 134 | 135 | assert result.output == "[1309, 3166, 13]\n" 136 | -------------------------------------------------------------------------------- /tests/test_mod.py: -------------------------------------------------------------------------------- 1 | """tests from mapbox/supermercado project: 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2015 Mapbox 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | """ 26 | 27 | import morecantile 28 | import numpy 29 | import pytest 30 | 31 | from supermorecado import super_utils as sutils 32 | from supermorecado.burntiles import burnTiles 33 | 34 | 35 | def test_get_range(): 36 | xyzs = numpy.zeros((10, 3), dtype=int) 37 | 38 | xMinR, xMaxR, yMinR, yMaxR = numpy.random.randint(0, 100, 4) 39 | 40 | xMaxR += xMinR 41 | yMaxR += yMinR 42 | 43 | xMaxIdx, yMaxIdx = numpy.random.randint(0, 9, 2) 44 | 45 | xyzs[:, 0] = xMinR 46 | xyzs[:, 1] = yMinR 47 | 48 | xyzs[xMaxIdx, 0] = xMaxR 49 | xyzs[yMaxIdx, 1] = yMaxR 50 | 51 | xmin, xmax, ymin, ymax = sutils.get_range(xyzs) 52 | 53 | assert xmin == xMinR 54 | assert xmax == xMaxR 55 | assert ymin == yMinR 56 | assert ymax == yMaxR 57 | 58 | 59 | def test_get_zoom(): 60 | xyzs = numpy.zeros((10, 3), dtype=int) 61 | 62 | zRand = numpy.random.randint(1, 100, 1)[0] 63 | xyzs[:, 2] = zRand 64 | 65 | assert sutils.get_zoom(xyzs) == zRand 66 | 67 | 68 | def test_get_zoom_fails_multiple_zooms(): 69 | xyzs = numpy.zeros((10, 3), dtype=int) 70 | 71 | zRand = numpy.random.randint(1, 100, 1)[0] 72 | xyzs[:, 2] = zRand 73 | 74 | xyzs[2, 2] = zRand + 1 75 | 76 | with pytest.raises(ValueError): 77 | sutils.get_zoom(xyzs) 78 | 79 | 80 | def test_get_zoom_fails_bad_dims_small(): 81 | xyzs = numpy.zeros((10, 2)) 82 | 83 | with pytest.raises(ValueError): 84 | sutils.get_zoom(xyzs) 85 | 86 | 87 | def test_get_zoom_fails_bad_dims_big(): 88 | xyzs = numpy.zeros((10, 4)) 89 | 90 | with pytest.raises(ValueError): 91 | sutils.get_zoom(xyzs) 92 | 93 | 94 | def test_filter_features_polygon(): 95 | """Polygon should go through unfiltered""" 96 | features = [ 97 | { 98 | "type": "Feature", 99 | "geometry": { 100 | "type": "Polygon", 101 | "coordinates": [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]]], 102 | }, 103 | } 104 | ] 105 | 106 | assert list(sutils.filter_features(features)) == features 107 | 108 | 109 | def test_filter_features_linestring(): 110 | """LineString should go through unfiltered""" 111 | features = [ 112 | { 113 | "type": "Feature", 114 | "geometry": { 115 | "type": "LineString", 116 | "coordinates": [[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]], 117 | }, 118 | } 119 | ] 120 | 121 | assert list(sutils.filter_features(features)) == features 122 | 123 | 124 | def test_filter_features_point(): 125 | """Points should go through unfiltered""" 126 | features = [ 127 | {"type": "Feature", "geometry": {"type": "Point", "coordinates": [[0, 0]]}} 128 | ] 129 | 130 | assert list(sutils.filter_features(features)) == features 131 | 132 | 133 | def test_filter_features_multi_polygon(): 134 | """MultiPolygons should be turned into multiple Polygons""" 135 | features = [ 136 | { 137 | "type": "Feature", 138 | "geometry": { 139 | "type": "MultiPolygon", 140 | "coordinates": [ 141 | [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]]], 142 | [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]]], 143 | ], 144 | }, 145 | } 146 | ] 147 | expected = [ 148 | { 149 | "type": "Feature", 150 | "geometry": { 151 | "type": "Polygon", 152 | "coordinates": [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]]], 153 | }, 154 | }, 155 | { 156 | "type": "Feature", 157 | "geometry": { 158 | "type": "Polygon", 159 | "coordinates": [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]]], 160 | }, 161 | }, 162 | ] 163 | assert list(sutils.filter_features(features)) == expected 164 | 165 | 166 | def test_filter_features_multi_point(): 167 | """MultiPoints should be turned into multiple Points""" 168 | features = [ 169 | { 170 | "type": "Feature", 171 | "geometry": {"type": "MultiPoint", "coordinates": [[0, 0], [1, 0]]}, 172 | } 173 | ] 174 | expected = [ 175 | {"type": "Feature", "geometry": {"type": "Point", "coordinates": [0, 0]}}, 176 | {"type": "Feature", "geometry": {"type": "Point", "coordinates": [1, 0]}}, 177 | ] 178 | assert list(sutils.filter_features(features)) == expected 179 | 180 | 181 | def test_filter_features_multi_linstrings(): 182 | """MultiLineStrings should be turned into multiple LineStrings""" 183 | features = [ 184 | { 185 | "type": "Feature", 186 | "geometry": { 187 | "type": "MultiLineString", 188 | "coordinates": [ 189 | [[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]], 190 | [[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]], 191 | ], 192 | }, 193 | } 194 | ] 195 | expected = [ 196 | { 197 | "type": "Feature", 198 | "geometry": { 199 | "type": "LineString", 200 | "coordinates": [[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]], 201 | }, 202 | }, 203 | { 204 | "type": "Feature", 205 | "geometry": { 206 | "type": "LineString", 207 | "coordinates": [[0, 0], [1, 0], [1, 1], [0, 1], [0, 1]], 208 | }, 209 | }, 210 | ] 211 | assert list(sutils.filter_features(features)) == expected 212 | 213 | 214 | def test_find_extrema(): 215 | """Extrema should be calculated correctly""" 216 | features = [ 217 | { 218 | "geometry": { 219 | "coordinates": [ 220 | [-127.97, 49.15], 221 | [-101.95, -8.41], 222 | [-43.24, -32.84], 223 | [37.62, -25.17], 224 | [71.72, -7.01], 225 | [107.23, 48.69], 226 | ], 227 | "type": "LineString", 228 | }, 229 | "properties": {}, 230 | "type": "Feature", 231 | }, 232 | { 233 | "geometry": { 234 | "coordinates": [[-98.09, 61.44], [-46.76, 61.1]], 235 | "type": "LineString", 236 | }, 237 | "properties": {}, 238 | "type": "Feature", 239 | }, 240 | { 241 | "geometry": { 242 | "coordinates": [[-6.33, 59.89], [59.06, 59.89]], 243 | "type": "LineString", 244 | }, 245 | "properties": {}, 246 | "type": "Feature", 247 | }, 248 | ] 249 | tms = morecantile.tms.get("WebMercatorQuad") 250 | burntiles = burnTiles(tms=tms) 251 | 252 | bounds = burntiles.find_extrema(features) 253 | assert bounds == ( 254 | -127.9699999999, 255 | -32.8399999999, 256 | 107.2299999999, 257 | 61.439999999899996, 258 | ) 259 | assert len(burntiles.burn(features, 5)) == 51 260 | 261 | tms = morecantile.tms.get("WGS1984Quad") 262 | burntiles = burnTiles(tms=tms) 263 | bounds = burntiles.find_extrema(features) 264 | # bounds should be the same in other TMS 265 | assert bounds == ( 266 | -127.9699999999, 267 | -32.8399999999, 268 | 107.2299999999, 269 | 61.439999999899996, 270 | ) 271 | assert len(burntiles.burn(features, 5)) == 94 272 | 273 | 274 | # def test_find_extrema_cross_antimeridian(): 275 | # """Extrema should be calculated correctly""" 276 | # features = [ 277 | # { 278 | # "geometry": { 279 | # "coordinates": [ 280 | # [-190.01, 49.15], 281 | # [-101.95, -8.41], 282 | # [-43.24, -32.84], 283 | # [37.62, -25.17], 284 | # [71.72, -7.01], 285 | # [190.01, 48.69], 286 | # ], 287 | # "type": "LineString", 288 | # }, 289 | # "properties": {}, 290 | # "type": "Feature", 291 | # }, 292 | # { 293 | # "geometry": { 294 | # "coordinates": [[-98.09, 61.44], [-46.76, 61.1]], 295 | # "type": "LineString", 296 | # }, 297 | # "properties": {}, 298 | # "type": "Feature", 299 | # }, 300 | # { 301 | # "geometry": { 302 | # "coordinates": [[-6.33, 59.89], [59.06, 59.89]], 303 | # "type": "LineString", 304 | # }, 305 | # "properties": {}, 306 | # "type": "Feature", 307 | # }, 308 | # ] 309 | # bounds = find_extrema(features) 310 | 311 | # assert bounds == ( 312 | # -190.0099999999, 313 | # -32.8399999999, 314 | # 190.0099999999, 315 | # 61.439999999899996, 316 | # ) 317 | 318 | 319 | # def test_find_extrema_clipped_northsouth(): 320 | # """Extrema should be calculated correctly""" 321 | # features = [ 322 | # { 323 | # "geometry": { 324 | # "coordinates": [ 325 | # [-190.01, 90], 326 | # [-101.95, -8.41], 327 | # [-43.24, -32.84], 328 | # [37.62, -25.17], 329 | # [71.72, -7.01], 330 | # [190.01, -90], 331 | # ], 332 | # "type": "LineString", 333 | # }, 334 | # "properties": {}, 335 | # "type": "Feature", 336 | # }, 337 | # { 338 | # "geometry": { 339 | # "coordinates": [[-98.09, 61.44], [-46.76, 61.1]], 340 | # "type": "LineString", 341 | # }, 342 | # "properties": {}, 343 | # "type": "Feature", 344 | # }, 345 | # { 346 | # "geometry": { 347 | # "coordinates": [[-6.33, 59.89], [59.06, 59.89]], 348 | # "type": "LineString", 349 | # }, 350 | # "properties": {}, 351 | # "type": "Feature", 352 | # }, 353 | # ] 354 | # bounds = find_extrema(features) 355 | 356 | # assert bounds == ( 357 | # -190.0099999999, 358 | # -85.0511287798066, 359 | # 190.0099999999, 360 | # 85.0511287798066, 361 | # ) 362 | 363 | 364 | def test_truncate(): 365 | """Make sure the features are correctly truncated""" 366 | features = [ 367 | { 368 | "type": "Feature", 369 | "geometry": { 370 | "type": "Polygon", 371 | "coordinates": [ 372 | [ 373 | (-180.0, -50.00000149011612), 374 | (-180.0, 50.0), 375 | (180.00000536441803, 50.0), 376 | (180.00000536441803, -50.00000149011612), 377 | (-180.0, -50.00000149011612), 378 | ] 379 | ], 380 | }, 381 | } 382 | ] 383 | tms = morecantile.tms.get("WebMercatorQuad") 384 | 385 | burntiles = burnTiles(tms=tms) 386 | assert len(burntiles.burn(features, 1, truncate=True)) > 0 387 | 388 | burntiles = burnTiles(tms=tms) 389 | assert len(burntiles.burn(features, 1, truncate=False)) == 0 390 | --------------------------------------------------------------------------------