├── .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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------