├── .gitignore ├── .travis.yml ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── README.rst ├── docs ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── geojson2vt ├── __init__.py ├── clip.py ├── convert.py ├── feature.py ├── geojson2vt.py ├── simplify.py ├── tile.py ├── transform.py ├── utils.py ├── vt2geojson.py └── wrap.py ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── fixtures ├── collection-tiles.json ├── collection.json ├── dateline-metrics-tiles.json ├── dateline-tiles.json ├── dateline.json ├── empty.json ├── feature-null-geometry.json ├── feature-tiles.json ├── feature.json ├── feature_result.json ├── ids-generate-id-tiles.json ├── ids-promote-id-tiles.json ├── ids.json ├── linestring.geojson ├── multi.geojson ├── single-geom-tiles.json ├── single-geom.json ├── small.json ├── small_result.json ├── us-states-tiles.json ├── us-states-z7-37-48.json └── us-states.json ├── test_clip.py ├── test_full.py ├── test_get_tile.py ├── test_simplify.py ├── test_small.py └── test_vt2geojson.py /.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 | .vscode/* 132 | .vscode/ 133 | *.code-workspace 134 | 135 | # Local History for Visual Studio Code 136 | .history/ 137 | 138 | # sphinx build folder 139 | _build -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | - "3.7" 5 | - "3.8" 6 | install: "pip install -r requirements.txt" 7 | script: py.test 8 | notifications: 9 | email: false 10 | deploy: 11 | provider: pypi 12 | user: $PYPI_USER 13 | password: $PYPI_PASSWORD 14 | on: 15 | branch: release 16 | python: '3.6' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2015, Mapbox 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose 6 | with or without fee is hereby granted, provided that the above copyright notice 7 | and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 11 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 13 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 15 | THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | autopep8 = "*" 8 | pytest = "*" 9 | 10 | [packages] 11 | 12 | [requires] 13 | python_version = "3.6" 14 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "db36e29696c4a7b3afc7bc2326ccae53222962b354d886a58040216e8d99d2f0" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": {}, 19 | "develop": { 20 | "attrs": { 21 | "hashes": [ 22 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", 23 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" 24 | ], 25 | "version": "==19.3.0" 26 | }, 27 | "autopep8": { 28 | "hashes": [ 29 | "sha256:cc6be1dfd46f2c7fa00e84a357f1a269683985b09eaffb47654ed551194399eb" 30 | ], 31 | "index": "pypi", 32 | "version": "==1.5.1" 33 | }, 34 | "importlib-metadata": { 35 | "hashes": [ 36 | "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", 37 | "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" 38 | ], 39 | "markers": "python_version < '3.8'", 40 | "version": "==1.6.0" 41 | }, 42 | "more-itertools": { 43 | "hashes": [ 44 | "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", 45 | "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" 46 | ], 47 | "version": "==8.2.0" 48 | }, 49 | "packaging": { 50 | "hashes": [ 51 | "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", 52 | "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" 53 | ], 54 | "version": "==20.3" 55 | }, 56 | "pluggy": { 57 | "hashes": [ 58 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 59 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 60 | ], 61 | "version": "==0.13.1" 62 | }, 63 | "py": { 64 | "hashes": [ 65 | "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", 66 | "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" 67 | ], 68 | "version": "==1.8.1" 69 | }, 70 | "pycodestyle": { 71 | "hashes": [ 72 | "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", 73 | "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" 74 | ], 75 | "version": "==2.5.0" 76 | }, 77 | "pyparsing": { 78 | "hashes": [ 79 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", 80 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" 81 | ], 82 | "version": "==2.4.7" 83 | }, 84 | "pytest": { 85 | "hashes": [ 86 | "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172", 87 | "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970" 88 | ], 89 | "index": "pypi", 90 | "version": "==5.4.1" 91 | }, 92 | "six": { 93 | "hashes": [ 94 | "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", 95 | "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" 96 | ], 97 | "version": "==1.14.0" 98 | }, 99 | "wcwidth": { 100 | "hashes": [ 101 | "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", 102 | "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" 103 | ], 104 | "version": "==0.1.9" 105 | }, 106 | "zipp": { 107 | "hashes": [ 108 | "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", 109 | "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" 110 | ], 111 | "version": "==3.1.0" 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/geometalab/geojson2vt.svg?branch=master)](https://travis-ci.org/geometalab/geojson2vt) 2 | # geojson2vt 3 | Python port of [JS GeoJSON-VT](https://github.com/mapbox/geojson-vt) to convert GeoJSON into vector tiles. :scissors: :earth_americas: 4 | 5 | Further, it provides the ability to convert the generate vector tiles to separate GeoJSONs (`vt2geojson`). 6 | ### Usage 7 | #### geojson2vt 8 | ```python 9 | # build an initial index of tiles 10 | tile_index = geojson2vt(geojson, {}) 11 | 12 | # request a particular tile 13 | features = tile_index.get_tile(z, x, y).get('features') 14 | 15 | # show an array of tile coordinates created so far 16 | print(tile_index.tile_coords) # [{'z': 0, 'x': 0, 'y': 0}, ...] 17 | ``` 18 | 19 | ##### Options 20 | 21 | You can fine-tune the results with an options object, 22 | although the defaults are sensible and work well for most use cases. 23 | 24 | ```python 25 | tile_index = geojson2vt(data, { 26 | 'maxZoom': 14, # max zoom to preserve detail on; can't be higher than 24 27 | 'tolerance': 3, # simplification tolerance (higher means simpler) 28 | 'extent': 4096, # tile extent (both width and height) 29 | 'buffer': 64, # tile buffer on each side 30 | 'lineMetrics': False, # whether to enable line metrics tracking for LineString/MultiLineString features 31 | 'promoteId': None, # name of a feature property to promote to feature.id. Cannot be used with `generateId` 32 | 'generateId': False, # whether to generate feature ids. Cannot be used with `promoteId` 33 | 'indexMaxZoom': 5, # max zoom in the initial tile index 34 | 'indexMaxPoints': 100000 # max number of points per tile in the index 35 | }, logging.INFO) 36 | ``` 37 | 38 | By default, tiles at zoom levels above `indexMaxZoom` are generated on the fly, but you can pre-generate all possible tiles for `data` by setting `indexMaxZoom` and `maxZoom` to the same value, setting `indexMaxPoints` to `0`, and then accessing the resulting tile coordinates from the `tile_coords` property of `tile_index`. 39 | 40 | The `promoteId` and `generateId` options ignore existing `id` values on the feature objects. 41 | 42 | geojson2vt only operates on zoom levels up to 24. 43 | 44 | #### vt2geojson 45 | ```python 46 | # build an initial index of tiles 47 | tile_index = geojson2vt(geojson_data, {}) 48 | 49 | # get a specific tile 50 | vector_tile = tile_index.get_tile(z, x, y) 51 | 52 | # convert a specific vector tile to GeoJSON 53 | geojson = vt2geojson(vector_tile) 54 | ``` 55 | 56 | ### Install 57 | 58 | `geojson2vt` is available on [PyPi](https://pypi.org/project/geojson2vt/). 59 | 60 | Install using pip. 61 | ```bash 62 | pip install geojson2vt 63 | ``` 64 | 65 | Import `geojson2vt` 66 | ```python 67 | from geojson2vt.geojson2vt import geojson2vt 68 | ``` 69 | 70 | ## Acknowledgements 71 | All the credit belongs to the collaborators of [JS GeoJSON-VT](https://github.com/mapbox/geojson-vt). 72 | 73 | ## Notes 74 | Currently, geojson2vt isn't written in a very pythonic way. This is due to the fact of the port from geojson-vt. 75 | Further development could lead to a more pythonic maner for a more seamless Python usage. :snake: 76 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ----- 3 | geojson2vt 4 | ********** 5 | .. code:: python 6 | 7 | # build an initial index of tiles 8 | tile_index = geojson2vt(geojson, {}) 9 | 10 | # request a particular tile 11 | features = tile_index.get_tile(z, x, y).get('features') 12 | 13 | # show an array of tile coordinates created so far 14 | print(tile_index.tile_coords) # [{'z': 0, 'x': 0, 'y': 0}, ...] 15 | 16 | You can fine-tune the results with an options object, 17 | although the defaults are sensible and work well for most use cases. 18 | 19 | By default, tiles at zoom levels above ``indexMaxZoom`` are generated on the fly, but you can pre-generate all possible tiles for data by setting ``indexMaxZoom`` and ``maxZoom`` to the same value, setting ``indexMaxPoints`` to 0, and then accessing the resulting tile coordinates from the ``tile_coords`` property of ``tile_index``. 20 | 21 | The ``promoteId`` and ``generateId`` options ignore existing ``id`` values on the feature objects. 22 | 23 | geojson2vt only operates on zoom levels up to 24. 24 | 25 | 26 | .. code:: python 27 | 28 | tile_index = geojson2vt(data, { 29 | 'maxZoom': 14, # max zoom to preserve detail on; can't be higher than 24 30 | 'tolerance': 3, # simplification tolerance (higher means simpler) 31 | 'extent': 4096, # tile extent (both width and height) 32 | 'buffer': 64, # tile buffer on each side 33 | 'lineMetrics': False, # whether to enable line metrics tracking for LineString/MultiLineString features 34 | 'promoteId': None, # name of a feature property to promote to feature.id. Cannot be used with `generateId` 35 | 'generateId': False, # whether to generate feature ids. Cannot be used with `promoteId` 36 | 'indexMaxZoom': 5, # max zoom in the initial tile index 37 | 'indexMaxPoints': 100000 # max number of points per tile in the index 38 | }, logging.INFO) 39 | 40 | 41 | 42 | vt2geojson 43 | ********** 44 | .. code:: python 45 | 46 | # build an initial index of tiles 47 | tile_index = geojson2vt(geojson_data, {}) 48 | 49 | # get a specific tile 50 | vt_tile = tile_index.get_tile(z, x, y) 51 | 52 | # convert a specific vector tile to GeoJSON 53 | geojson = vt2geojson(vt_tile) 54 | 55 | 56 | Acknowledgements 57 | ---------------- 58 | 59 | All the credit belongs to the collaborators of JS GeoJSON-VT. -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'geojson2vt' 21 | copyright = '2020, Samuel Kurath' 22 | author = 'Samuel Kurath' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '1.0.1' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 43 | 44 | 45 | # -- Options for HTML output ------------------------------------------------- 46 | 47 | # The theme to use for HTML and HTML Help pages. See the documentation for 48 | # a list of builtin themes. 49 | # 50 | html_theme = 'alabaster' 51 | 52 | # Add any paths that contain custom static files (such as style sheets) here, 53 | # relative to this directory. They are copied after the builtin static files, 54 | # so a file named "default.css" will overwrite the builtin "default.css". 55 | html_static_path = ['_static'] -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. geojson2vt documentation master file, created by 2 | sphinx-quickstart on Wed Apr 15 09:23:22 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to geojson2vt's documentation! 7 | ====================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /geojson2vt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geometalab/geojson2vt/3653e4ca958e6852f4de0b2f2e1ea4b2d6edc069/geojson2vt/__init__.py -------------------------------------------------------------------------------- /geojson2vt/clip.py: -------------------------------------------------------------------------------- 1 | import math 2 | from geojson2vt.feature import create_feature, Slice 3 | 4 | 5 | r""" 6 | clip features between two vertical or horizontal axis-parallel lines: 7 | * | | 8 | * ___|___ | / 9 | * / | \____|____/ 10 | * | | 11 | * 12 | * k1 and k2 are the line coordinates 13 | * axis: 0 for x, 1 for y 14 | * minAll and maxAll: minimum and maximum coordinate value for all features 15 | """ 16 | 17 | 18 | def clip(features, scale, k1, k2, axis, minAll, maxAll, options): 19 | k1 /= scale 20 | k2 /= scale 21 | 22 | if minAll >= k1 and maxAll < k2: 23 | return features # trivial accept 24 | elif maxAll < k1 or minAll >= k2: 25 | return None # trivial reject 26 | 27 | clipped = [] 28 | 29 | for feature in features: 30 | if isinstance(feature.get('geometry'), Slice): 31 | geometry = feature.get('geometry') 32 | else: 33 | geometry = Slice(feature.get('geometry')) 34 | 35 | type_ = feature.get('type') 36 | 37 | min_ = feature.get('minX') if axis == 0 else feature.get('minY') 38 | max_ = feature.get('maxX') if axis == 0 else feature.get('maxY') 39 | 40 | if min_ >= k1 and max_ < k2: # trivial accept 41 | clipped.append(feature) 42 | continue 43 | elif max_ < k1 or min_ >= k2: # trivial reject 44 | continue 45 | 46 | newGeometry = Slice([]) # [] 47 | 48 | if type_ == 'Point' or type_ == 'MultiPoint': 49 | clip_points(geometry, newGeometry, k1, k2, axis) 50 | elif type_ == 'LineString': 51 | clip_line(geometry, newGeometry, k1, k2, 52 | axis, False, options.get('lineMetrics', False)) 53 | elif type_ == 'MultiLineString': 54 | clip_lines(geometry, newGeometry, k1, k2, axis, False) 55 | elif type_ == 'Polygon': 56 | if any(isinstance(l, list) for l in geometry): 57 | clip_lines(geometry, newGeometry, k1, k2, axis, True) 58 | else: 59 | clip_line(geometry, newGeometry, k1, k2, axis, True, False) 60 | elif type_ == 'MultiPolygon': 61 | for polygon in geometry: 62 | newPolygon = Slice([]) 63 | clip_lines(polygon, newPolygon, k1, k2, axis, True) 64 | if len(newPolygon) > 0: 65 | newGeometry.append(newPolygon) 66 | 67 | if len(newGeometry) > 0: 68 | if options.get('lineMetrics', False) and type_ == 'LineString': 69 | for line in newGeometry: 70 | clipped.append(create_feature( 71 | feature.get('id'), type_, line, feature.get('tags'))) 72 | continue 73 | if type_ == 'LineString' or type_ == 'MultiLineString': 74 | if len(newGeometry) == 1: 75 | type_ = 'LineString' 76 | newGeometry = newGeometry[0] 77 | else: 78 | type_ = 'MultiLineString' 79 | 80 | if type_ == 'Point' or type_ == 'MultiPoint': 81 | type_ = 'Point' if len(newGeometry) == 3 else 'MultiPoint' 82 | 83 | clipped.append(create_feature( 84 | feature.get('id'), type_, newGeometry, feature.get('tags'))) 85 | 86 | return clipped if len(clipped) > 0 else None 87 | 88 | 89 | def clip_points(geom, newGeom, k1, k2, axis): 90 | for i in range(0, len(geom), 3): 91 | a = geom[i + axis] 92 | if a >= k1 and a <= k2: 93 | add_point(newGeom, geom[i], geom[i + 1], geom[i + 2]) 94 | 95 | 96 | def clip_line(geom, newGeom, k1, k2, axis, isPolygon, trackMetrics): 97 | slice_ = new_slice(geom) 98 | intersect = intersectX if axis == 0 else intersectY 99 | l = geom.start if isinstance(geom, Slice) else 0. 100 | segLen, t = None, None 101 | 102 | # length = len(geom) if isinstance(geom, list) else 0 103 | for i in range(0, len(geom) - 3, 3): 104 | ax = geom[i] 105 | ay = geom[i + 1] 106 | az = geom[i + 2] 107 | bx = geom[i + 3] 108 | by = geom[i + 4] 109 | a = ax if axis == 0 else ay 110 | b = bx if axis == 0 else by 111 | exited = False 112 | 113 | if trackMetrics: 114 | segLen = math.sqrt(math.pow(ax - bx, 2) + math.pow(ay - by, 2)) 115 | 116 | if a < k1: 117 | # ---|--> | (line enters the clip region from the left) 118 | if b > k1: 119 | t = intersect(slice_, ax, ay, bx, by, k1) 120 | if trackMetrics: 121 | slice_.start = l + segLen * t 122 | elif a > k2: 123 | # | <--|--- (line enters the clip region from the right) 124 | if b < k2: 125 | t = intersect(slice_, ax, ay, bx, by, k2) 126 | if trackMetrics: 127 | slice_.start = l + segLen * t 128 | else: 129 | add_point(slice_, ax, ay, az) 130 | if b < k1 and a >= k1: 131 | # <--|--- | or <--|-----|--- (line exits the clip region on the left) 132 | t = intersect(slice_, ax, ay, bx, by, k1) 133 | exited = True 134 | if b > k2 and a <= k2: 135 | # | ---|--> or ---|-----|--> (line exits the clip region on the right) 136 | t = intersect(slice_, ax, ay, bx, by, k2) 137 | exited = True 138 | 139 | if not isPolygon and exited: 140 | if trackMetrics: 141 | slice_.end = l + segLen * t 142 | newGeom.append(slice_) 143 | slice_ = new_slice(geom) 144 | 145 | if trackMetrics: 146 | l += segLen 147 | 148 | # add the last point 149 | last = len(geom) - 3 150 | ax = geom[last] 151 | ay = geom[last + 1] 152 | az = geom[last + 2] 153 | a = ax if axis == 0 else ay 154 | if a >= k1 and a <= k2: 155 | add_point(slice_, ax, ay, az) 156 | 157 | # close the polygon if its endpoints are not the same after clipping 158 | last = len(slice_) - 3 159 | if isPolygon and last >= 3 and (slice_[last] != slice_[0] or slice_[last + 1] != slice_[1]): 160 | add_point(slice_, slice_[0], slice_[1], slice_[2]) 161 | 162 | # add the final slice 163 | if len(slice_) > 0: 164 | newGeom.append(slice_) 165 | 166 | 167 | def new_slice(line): 168 | slice_ = Slice([]) 169 | slice_.size = line.size if isinstance(line, Slice) else 0. 170 | slice_.start = line.start if isinstance(line, Slice) else 0. 171 | slice_.end = line.end if isinstance(line, Slice) else 0. 172 | return slice_ 173 | 174 | 175 | def clip_lines(geom, newGeom, k1, k2, axis, isPolygon): 176 | for line in geom: 177 | clip_line(line, newGeom, k1, k2, axis, isPolygon, False) 178 | 179 | 180 | def add_point(out, x, y, z): 181 | out.append(x) 182 | out.append(y) 183 | out.append(z) 184 | 185 | 186 | def intersectX(out, ax, ay, bx, by, x): 187 | t = (x - ax) / (bx - ax) 188 | add_point(out, x, ay + (by - ay) * t, 1) 189 | return t 190 | 191 | 192 | def intersectY(out, ax, ay, bx, by, y): 193 | t = (y - ay) / (by - ay) 194 | add_point(out, ax + (bx - ax) * t, y, 1) 195 | return t 196 | -------------------------------------------------------------------------------- /geojson2vt/convert.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from geojson2vt.simplify import simplify 4 | from geojson2vt.feature import Slice, create_feature 5 | 6 | # converts GeoJSON feature into an intermediate projected JSON vector format with simplification data 7 | 8 | 9 | def convert(data, options): 10 | features = [] 11 | if data.get('type') == 'FeatureCollection': 12 | for i in range(len(data.get('features'))): 13 | convert_feature(features, data.get('features')[i], options, i) 14 | elif data.get('type') == 'Feature': 15 | convert_feature(features, data, options) 16 | else: 17 | # single geometry or a geometry collection 18 | convert_feature(features, {"geometry": data}, options) 19 | return features 20 | 21 | 22 | def convert_feature(features, geojson, options, index=None): 23 | if geojson.get('geometry', None) is None: 24 | return 25 | 26 | coords = geojson.get('geometry').get('coordinates') 27 | type_ = geojson.get('geometry').get('type') 28 | tolerance = math.pow(options.get( 29 | 'tolerance') / ((1 << options.get('maxZoom')) * options.get('extent')), 2) 30 | geometry = Slice([]) 31 | id_ = geojson.get('id') 32 | if options.get('promoteId', None) is not None and geojson.get('properties', None) is not None and 'promoteId' in geojson.get('properties'): 33 | id_ = geojson['properties'][options.get('promoteId')] 34 | elif options.get('generateId', False): 35 | id_ = index if index is not None else 0 36 | 37 | if type_ == 'Point': 38 | convert_point(coords, geometry) 39 | elif type_ == 'MultiPoint': 40 | for p in coords: 41 | convert_point(p, geometry) 42 | elif type_ == 'LineString': 43 | convert_line(coords, geometry, tolerance, False) 44 | elif type_ == 'MultiLineString': 45 | if options.get('lineMetrics'): 46 | # explode into linestrings to be able to track metrics 47 | for line in coords: 48 | geometry = Slice([]) 49 | convert_line(line, geometry, tolerance, False) 50 | features.append(create_feature(id_, 'LineString', 51 | geometry, geojson.get('properties'))) 52 | return 53 | else: 54 | convert_lines(coords, geometry, tolerance, False) 55 | elif type_ == 'Polygon': 56 | convert_lines(coords, geometry, tolerance, True) 57 | elif type_ == 'MultiPolygon': 58 | for polygon in coords: 59 | newPolygon = [] 60 | convert_lines(polygon, newPolygon, tolerance, True) 61 | geometry.append(newPolygon) 62 | elif type_ == 'GeometryCollection': 63 | for singleGeometry in geojson['geometry']['geometries']: 64 | convert_feature(features, { 65 | "id": str(id_), 66 | "geometry": singleGeometry, 67 | "properties": geojson.get('properties') 68 | }, options, index) 69 | return 70 | else: 71 | raise Exception('Input data is not a valid GeoJSON object.') 72 | 73 | features.append(create_feature( 74 | id_, type_, geometry, geojson.get('properties'))) 75 | 76 | 77 | def convert_point(coords, out): 78 | out.append(project_x(coords[0])) 79 | out.append(project_y(coords[1])) 80 | out.append(0) 81 | 82 | 83 | def convert_line(ring, out, tolerance, isPolygon): 84 | x0, y0 = None, None 85 | size = 0 86 | 87 | for j in range(len(ring)): 88 | x = project_x(ring[j][0]) 89 | y = project_y(ring[j][1]) 90 | 91 | out.append(x) 92 | out.append(y) 93 | out.append(0) 94 | 95 | if j > 0: 96 | if isPolygon: 97 | size += (x0 * y - x * y0) / 2 # area 98 | else: 99 | size += math.sqrt(math.pow(x - x0, 2) + 100 | math.pow(y - y0, 2)) # length 101 | x0 = x 102 | y0 = y 103 | 104 | last = len(out) - 3 105 | out[2] = 1 106 | simplify(out, 0, last, tolerance) 107 | out[last + 2] = 1. 108 | 109 | out.size = abs(size) 110 | out.start = 0. 111 | out.end = out.size 112 | 113 | 114 | def convert_lines(rings, out, tolerance, isPolygon): 115 | for i in range(len(rings)): 116 | geom = Slice([]) 117 | convert_line(rings[i], geom, tolerance, isPolygon) 118 | out.append(geom) 119 | 120 | 121 | def project_x(x): 122 | return x / 360. + 0.5 123 | 124 | 125 | def project_y(y): 126 | sin = math.sin(y * math.pi / 180.) 127 | if sin == 1.: 128 | return 0. 129 | if sin == -1.: 130 | return 1. 131 | y2 = 0.5 - 0.25 * math.log((1. + sin) / (1. - sin)) / math.pi 132 | return 0 if y2 < 0. else (1. if y2 > 1. else y2) 133 | -------------------------------------------------------------------------------- /geojson2vt/feature.py: -------------------------------------------------------------------------------- 1 | def create_feature(id_, type_, geom, tags): 2 | feature = { 3 | "id": None if id_ is None else str(id_), 4 | "type": type_, 5 | "geometry": geom, 6 | "tags": tags, 7 | "minX": float('inf'), 8 | "minY": float('inf'), 9 | "maxX": float('-inf'), 10 | "maxY": float('-inf') 11 | } 12 | 13 | if type_ == 'Point' or type_ == 'MultiPoint' or type_ == 'LineString': 14 | calc_line_bbox(feature, geom) 15 | elif type_ == 'Polygon': 16 | # the outer ring(ie[0]) contains all inner rings 17 | calc_line_bbox(feature, geom[0]) 18 | elif type_ == 'MultiLineString': 19 | for line in geom: 20 | calc_line_bbox(feature, line) 21 | elif type_ == 'MultiPolygon': 22 | for polygon in geom: 23 | # the outer ring(ie[0]) contains all inner rings 24 | calc_line_bbox(feature, polygon[0]) 25 | return feature 26 | 27 | 28 | def calc_line_bbox(feature, geom): 29 | for i in range(0, len(geom), 3): 30 | feature['minX'] = min(feature.get('minX'), geom[i]) 31 | feature['minY'] = min(feature.get('minY'), geom[i + 1]) 32 | feature['maxX'] = max(feature.get('maxX'), geom[i]) 33 | feature['maxY'] = max(feature.get('maxY'), geom[i + 1]) 34 | 35 | 36 | class Slice(list): 37 | def __init__(self, *args): 38 | list.__init__(self, *args) 39 | self.start = 0. 40 | self.end = 0. 41 | self.size = 0. 42 | -------------------------------------------------------------------------------- /geojson2vt/geojson2vt.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from datetime import datetime 3 | 4 | from geojson2vt.convert import convert 5 | from geojson2vt.clip import clip 6 | from geojson2vt.wrap import wrap 7 | from geojson2vt.transform import transform_tile 8 | from geojson2vt.tile import create_tile 9 | 10 | 11 | def get_default_options(): 12 | return { 13 | "maxZoom": 14, # max zoom to preserve detail on 14 | "indexMaxZoom": 5, # max zoom in the tile index 15 | "indexMaxPoints": 100000, # max number of points per tile in the tile index 16 | # simplification tolerance (higher means simpler) 17 | "tolerance": 3, 18 | "extent": 4096, # tile extent 19 | "buffer": 64, # tile buffer on each side 20 | "lineMetrics": False, # whether to calculate line metrics 21 | "promoteId": None, # name of a feature property to be promoted to feature.id 22 | "generateId": False, # whether to generate feature ids. Cannot be used with promoteId 23 | } 24 | 25 | 26 | class GeoJsonVt: 27 | def __init__(self, data, options, log_level=logging.INFO): 28 | logging.basicConfig( 29 | level=log_level, format='%(asctime)s %(levelname)s %(message)s') 30 | options = self.options = extend(get_default_options(), options) 31 | 32 | logging.debug('preprocess data start') 33 | 34 | if options.get('maxZoom') < 0 or options.get('maxZoom') > 24: 35 | raise Exception('maxZoom should be in the 0-24 range') 36 | if options.get('promoteId', None) is not None and options.get('generateId', False): 37 | raise Exception( 38 | 'promoteId and generateId cannot be used together.') 39 | 40 | # projects and adds simplification info 41 | features = convert(data, options) 42 | 43 | # tiles and tile_coords are part of the public API 44 | self.tiles = {} 45 | self.tile_coords = [] 46 | 47 | logging.debug(f'preprocess data end') 48 | logging.debug( 49 | f'index: maxZoom: {options.get("indexMaxZoom")}, maxPoints: {options.get("indexMaxPoints")}') 50 | self.stats = {} 51 | self.total = 0 52 | 53 | # wraps features (ie extreme west and extreme east) 54 | features = wrap(features, options) 55 | 56 | # start slicing from the top tile down 57 | if len(features) > 0: 58 | self.split_tile(features, 0, 0, 0) 59 | 60 | if len(features) > 0: 61 | logging.debug( 62 | f'features: {self.tiles[0].get("numFeatures")}, points: {self.tiles[0].get("numPoints")}') 63 | stop = datetime.now() 64 | logging.debug(f'generate tiles end') 65 | logging.debug('tiles generated:', self.total, self.stats) 66 | 67 | # splits features from a parent tile to sub-tiles. 68 | # z, x, and y are the coordinates of the parent tile 69 | # cz, cx, and cy are the coordinates of the target tile 70 | # 71 | # If no target tile is specified, splitting stops when we reach the maximum 72 | # zoom or the number of points is low as specified in the options. 73 | 74 | def split_tile(self, features, z, x, y, cz=None, cx=None, cy=None): 75 | stack = [features, z, x, y] 76 | options = self.options 77 | # avoid recursion by using a processing queue 78 | while len(stack) > 0: 79 | y = stack.pop() 80 | x = stack.pop() 81 | z = stack.pop() 82 | features = stack.pop() 83 | 84 | z2 = 1 << z 85 | id_ = to_Id(z, x, y) 86 | tile = self.tiles.get(id_, None) 87 | 88 | if tile is None: 89 | logging.debug('creation start') 90 | 91 | self.tiles[id_] = create_tile(features, z, x, y, options) 92 | tile = self.tiles[id_] 93 | self.tile_coords.append({'z': z, 'x': x, 'y': y}) 94 | 95 | logging.debug( 96 | f'tile z{z}-{x}-{y} (features: {tile.get("numFeatures")}, points: {tile.get("numPoints")}, simplified: {tile.get("numSimplified")})') 97 | logging.debug(f'creation end') 98 | key = f'z{z}' 99 | self.stats[key] = self.stats.get(key, 0) + 1 100 | self.total += 1 101 | 102 | # save reference to original geometry in tile so that we can drill down later if we stop now 103 | tile['source'] = features 104 | 105 | # if it's the first-pass tiling 106 | if cz is None: 107 | # stop tiling if we reached max zoom, or if the tile is too simple 108 | if z == options.get('indexMaxZoom') or tile.get('numPoints') <= options.get('indexMaxPoints'): 109 | continue # if a drilldown to a specific tile 110 | elif z == options.get('maxZoom') or z == cz: 111 | # stop tiling if we reached base zoom or our target tile zoom 112 | continue 113 | elif cz is not None: 114 | # stop tiling if it's not an ancestor of the target tile 115 | zoomSteps = cz - z 116 | if x != (cx >> zoomSteps) or y != (cy >> zoomSteps): 117 | continue 118 | 119 | # if we slice further down, no need to keep source geometry 120 | tile['source'] = None 121 | 122 | if not features or len(features) == 0: 123 | continue 124 | 125 | logging.debug('clipping start') 126 | 127 | # values we'll use for clipping 128 | k1 = 0.5 * options.get('buffer') / options.get('extent') 129 | k2 = 0.5 - k1 130 | k3 = 0.5 + k1 131 | k4 = 1 + k1 132 | 133 | tl = None 134 | bl = None 135 | tr = None 136 | br = None 137 | 138 | left = clip(features, z2, x - k1, x + k3, 0, 139 | tile['minX'], tile['maxX'], options) 140 | right = clip(features, z2, x + k2, x + k4, 0, 141 | tile['minX'], tile['maxX'], options) 142 | features = None 143 | 144 | if left is not None: 145 | tl = clip(left, z2, y - k1, y + k3, 1, 146 | tile['minY'], tile['maxY'], options) 147 | bl = clip(left, z2, y + k2, y + k4, 1, 148 | tile['minY'], tile['maxY'], options) 149 | left = None 150 | 151 | if right is not None: 152 | tr = clip(right, z2, y - k1, y + k3, 1, 153 | tile['minY'], tile['maxY'], options) 154 | br = clip(right, z2, y + k2, y + k4, 1, 155 | tile['minY'], tile['maxY'], options) 156 | right = None 157 | 158 | logging.debug(f'clipping took end') 159 | 160 | #stack.push(tl or [], z + 1, x * 2, y * 2) 161 | stack.append(tl if tl is not None else []) 162 | stack.append(z + 1) 163 | stack.append(x * 2) 164 | stack.append(y * 2) 165 | 166 | #stack.push(bl or [], z + 1, x * 2, y * 2 + 1) 167 | stack.append(bl if bl is not None else []) 168 | stack.append(z + 1) 169 | stack.append(x * 2) 170 | stack.append(y * 2 + 1) 171 | 172 | #stack.push(tr or [], z + 1, x * 2 + 1, y * 2) 173 | stack.append(tr if tr is not None else []) 174 | stack.append(z + 1) 175 | stack.append(x * 2 + 1) 176 | stack.append(y * 2) 177 | 178 | #stack.push(br or [], z + 1, x * 2 + 1, y * 2 + 1) 179 | stack.append(br if br is not None else []) 180 | stack.append(z + 1) 181 | stack.append(x * 2 + 1) 182 | stack.append(y * 2 + 1) 183 | 184 | def get_tile(self, z, x, y): 185 | z = int(z) 186 | x = int(x) 187 | y = int(y) 188 | 189 | options = self.options 190 | extent = options.get('extent') 191 | 192 | if z < 0 or z > 24: 193 | return None 194 | 195 | z2 = 1 << z 196 | x = (x + z2) & (z2 - 1) # wrap tile x coordinate 197 | 198 | id_ = to_Id(z, x, y) 199 | current_tile = self.tiles.get(id_, None) 200 | if current_tile is not None: 201 | return transform_tile(self.tiles[id_], extent) 202 | 203 | logging.debug(f'drilling down to z{z}-{x}-{y}') 204 | 205 | z0 = z 206 | x0 = x 207 | y0 = y 208 | parent = None 209 | 210 | while parent is None and z0 > 0: 211 | z0 -= 1 212 | x0 = x0 >> 1 213 | y0 = y0 >> 1 214 | parent = self.tiles.get(to_Id(z0, x0, y0), None) 215 | 216 | if parent is None or parent.get('source', None) is None: 217 | return None 218 | 219 | # if we found a parent tile containing the original geometry, we can drill down from it 220 | logging.debug(f'found parent tile z{z0}-{x0}-{y0}') 221 | logging.debug('drilling down start') 222 | 223 | self.split_tile(parent.get('source'), z0, x0, y0, z, x, y) 224 | 225 | logging.debug(f'drilling down end') 226 | 227 | transformed = transform_tile(self.tiles[id_], extent) if self.tiles.get( 228 | id_, None) is not None else None 229 | return transformed 230 | 231 | 232 | def to_Id(z, x, y): 233 | id_ = (((1 << z) * y + x) * 32) + z 234 | return id_ 235 | 236 | 237 | def extend(dest, src): 238 | for key, _ in src.items(): 239 | dest[key] = src[key] 240 | return dest 241 | 242 | 243 | def geojson2vt(data, options, log_level=logging.INFO): 244 | return GeoJsonVt(data, options, log_level) 245 | -------------------------------------------------------------------------------- /geojson2vt/simplify.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | # calculate simplification data using optimized Douglas-Peucker algorithm 5 | def simplify(coords, first, last=0, sq_tolerance=0.0): 6 | max_sq_dist = sq_tolerance 7 | mid = (last - first) >> 1 8 | min_pos_to_mid = last - first 9 | index = None 10 | 11 | ax = coords[first] 12 | ay = coords[first + 1] 13 | bx = coords[last] 14 | by = coords[last + 1] 15 | 16 | for i in range(first + 3, last, 3): 17 | d = get_sq_seg_dist(coords[i], coords[i + 1], ax, ay, bx, by) 18 | if d > max_sq_dist: 19 | index = i 20 | max_sq_dist = d 21 | 22 | elif d == max_sq_dist: 23 | # a workaround to ensure we choose a pivot close to the middle of the list, 24 | # reducing recursion depth, for certain degenerate inputs 25 | pos_to_mid = abs(i - mid) 26 | if pos_to_mid < min_pos_to_mid: 27 | index = i 28 | min_pos_to_mid = pos_to_mid 29 | 30 | if max_sq_dist > sq_tolerance: 31 | if index - first > 3: 32 | simplify(coords, first, index, sq_tolerance) 33 | coords[index + 2] = max_sq_dist 34 | if last - index > 3: 35 | simplify(coords, index, last, sq_tolerance) 36 | 37 | 38 | # square distance from a point to a segment 39 | def get_sq_seg_dist(px, py, x, y, bx, by): 40 | 41 | dx = bx - x 42 | dy = by - y 43 | 44 | if dx != 0 or dy != 0: 45 | 46 | t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy) 47 | 48 | if t > 1: 49 | x = bx 50 | y = by 51 | 52 | elif (t > 0): 53 | x += dx * t 54 | y += dy * t 55 | 56 | dx = px - x 57 | dy = py - y 58 | 59 | return dx * dx + dy * dy 60 | -------------------------------------------------------------------------------- /geojson2vt/tile.py: -------------------------------------------------------------------------------- 1 | def create_tile(features, z, tx, ty, options): 2 | features = features if features is not None else [] 3 | tolerance = 0 if z == options.get('maxZoom') else options.get('tolerance') / \ 4 | ((1 << z) * options.get('extent')) 5 | tile = { 6 | "features": [], 7 | "numPoints": 0, 8 | "numSimplified": 0, 9 | "numFeatures": len(features), 10 | "source": None, 11 | "x": tx, 12 | "y": ty, 13 | "z": z, 14 | "transformed": False, 15 | "minX": 2, 16 | "minY": 1, 17 | "maxX": -1, 18 | "maxY": 0 19 | } 20 | for feature in features: 21 | add_feature(tile, feature, tolerance, options) 22 | return tile 23 | 24 | 25 | def add_feature(tile, feature, tolerance, options): 26 | geom = feature.get('geometry') 27 | type_ = feature.get('type') 28 | simplified = [] 29 | 30 | tile['minX'] = min(tile['minX'], feature['minX']) 31 | tile['minY'] = min(tile['minY'], feature['minY']) 32 | tile['maxX'] = max(tile['maxX'], feature['maxX']) 33 | tile['maxY'] = max(tile['maxY'], feature['maxY']) 34 | 35 | if type_ == 'Point' or type == 'MultiPoint': 36 | for i in range(0, len(geom), 3): 37 | simplified.append(geom[i]) 38 | simplified.append(geom[i + 1]) 39 | tile['numPoints'] += 1 40 | tile['numSimplified'] += 1 41 | 42 | elif type_ == 'LineString': 43 | add_line(simplified, geom, tile, tolerance, False, False) 44 | 45 | elif type_ == 'MultiLineString' or type_ == 'Polygon': 46 | for i in range(len(geom)): 47 | add_line(simplified, geom[i], tile, 48 | tolerance, type_ == 'Polygon', i == 0) 49 | 50 | elif type_ == 'MultiPolygon': 51 | for k in range(len(geom)): 52 | polygon = geom[k] 53 | for i in range(len(polygon)): 54 | add_line(simplified, polygon[i], tile, tolerance, True, i == 0) 55 | 56 | if len(simplified) > 0: 57 | tags = feature.get('tags') 58 | 59 | if type_ == 'LineString' and options.get('lineMetrics'): 60 | tags = {} 61 | for key in feature.get('tags'): 62 | tags[key] = feature['tags'][key] 63 | tags['mapbox_clip_start'] = geom.start / geom.size 64 | tags['mapbox_clip_end'] = geom.end / geom.size 65 | 66 | tileFeature = { 67 | "geometry": simplified, 68 | "type": 3 if type_ == 'Polygon' or type_ == 'MultiPolygon' else (2 if type_ == 'LineString' or type_ == 'MultiLineString' else 1), 69 | "tags": tags 70 | } 71 | current_id = feature.get('id', None) 72 | if current_id is not None: 73 | tileFeature['id'] = current_id 74 | tile['features'].append(tileFeature) 75 | 76 | 77 | def add_line(result, geom, tile, tolerance, is_polygon, is_outer): 78 | sq_tolerance = tolerance * tolerance 79 | 80 | if tolerance > 0 and (geom.size < (sq_tolerance if is_polygon else tolerance)): 81 | tile['numPoints'] += len(geom) / 3 82 | return 83 | 84 | ring = [] 85 | for i in range(0, len(geom), 3): 86 | if tolerance == 0 or geom[i + 2] > sq_tolerance: 87 | tile['numSimplified'] += 1 88 | ring.append(geom[i]) 89 | ring.append(geom[i + 1]) 90 | tile['numPoints'] += 1 91 | 92 | if is_polygon: 93 | rewind(ring, is_outer) 94 | 95 | result.append(ring) 96 | 97 | 98 | def rewind(ring, clockwise): 99 | area = 0 100 | l = len(ring) 101 | j = l - 2 102 | for i in range(0, l, 2): 103 | area += (ring[i] - ring[j]) * (ring[i + 1] + ring[j + 1]) 104 | j = i 105 | if (area > 0) == clockwise: 106 | for i in range(0, l, 2): 107 | x = ring[i] 108 | y = ring[i + 1] 109 | ring[i] = ring[l - 2 - i] 110 | ring[i + 1] = ring[l - 1 - i] 111 | ring[l - 2 - i] = x 112 | ring[l - 1 - i] = y 113 | -------------------------------------------------------------------------------- /geojson2vt/transform.py: -------------------------------------------------------------------------------- 1 | # Transforms the coordinates of each feature in the given tile from 2 | # mercator-projected space into (extent x extent) tile space. 3 | 4 | 5 | def transform_tile(tile, extent): 6 | if tile.get('transformed', None): 7 | return tile 8 | 9 | z2 = 1 << tile.get('z') 10 | tx = tile.get('x') 11 | ty = tile.get('y') 12 | 13 | for feature in tile.get('features', []): 14 | geom = feature.get('geometry') 15 | type_ = feature.get('type') 16 | 17 | feature['geometry'] = [] 18 | 19 | if type_ == 1: 20 | for j in range(0, len(geom), 2): 21 | feature['geometry'].append(transform_point( 22 | geom[j], geom[j + 1], extent, z2, tx, ty)) 23 | else: 24 | for j in range(len(geom)): 25 | ring = [] 26 | for k in range(0, len(geom[j]), 2): 27 | ring.append(transform_point( 28 | geom[j][k], geom[j][k + 1], extent, z2, tx, ty)) 29 | feature['geometry'].append(ring) 30 | 31 | tile['transformed'] = True 32 | return tile 33 | 34 | 35 | def transform_point(x, y, extent, z2, tx, ty): 36 | return [ 37 | round(extent * (x * z2 - tx), 0), 38 | round(extent * (y * z2 - ty), 0) 39 | ] 40 | -------------------------------------------------------------------------------- /geojson2vt/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | 5 | def current_dir(file): 6 | return os.path.dirname(os.path.abspath(file)) 7 | 8 | 9 | def get_parent_dir(directory): 10 | return os.path.abspath(os.path.join(directory, os.path.pardir)) 11 | 12 | 13 | def get_json(file_path): 14 | data = None 15 | with open(file_path) as json_file: 16 | data = json.load(json_file) 17 | _change_int_coords_to_float(data) 18 | return data 19 | 20 | 21 | def _change_int_coords_to_float(data): 22 | if isinstance(data, dict): 23 | _walk_dict(data) 24 | if isinstance(data, list): 25 | _walk_list(data) 26 | 27 | 28 | def _walk_dict(tree): 29 | for key, value in tree.items(): 30 | if isinstance(value, dict): 31 | _walk_dict(value) 32 | if isinstance(value, list): 33 | _walk_list(value) 34 | 35 | 36 | def _walk_list(lst): 37 | if len(lst) == 0: 38 | return 39 | if isinstance(lst[0], list): 40 | for l in lst: 41 | _walk_list(l) 42 | elif isinstance(lst[0], int): 43 | for i in range(len(lst)): 44 | lst[i] = float(lst[i]) 45 | elif isinstance(lst[0], dict): 46 | for i in range(len(lst)): 47 | _walk_dict(lst[i]) 48 | -------------------------------------------------------------------------------- /geojson2vt/vt2geojson.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | geometry_types = { 4 | 0: 'Unknown', 5 | 1: 'Point', 6 | 2: 'LineString', 7 | 3: 'Polygon', 8 | 4: 'MultiLineString' 9 | } 10 | 11 | 12 | def vt2geojson(tile, extent=4096): 13 | features = tile.get('features', []) 14 | size = extent * 2 ** tile.get('z') 15 | x0 = extent * tile.get('x') 16 | y0 = extent * tile.get('y') 17 | 18 | geojson_features = [vt_feature2geojson_feature(feature, size, x0, y0) 19 | for feature in features] 20 | 21 | return { 22 | "type": "FeatureCollection", 23 | "features": geojson_features 24 | } 25 | 26 | 27 | def vt_feature2geojson_feature(feature, size, x0, y0): 28 | def project_one(p_x, p_y): 29 | y2 = 180. - (p_y + y0) * 360. / size 30 | lng = (p_x + x0) * 360. / size - 180. 31 | lat = 360. / math.pi * math.atan(math.exp(y2 * math.pi / 180.)) - 90. 32 | return [lng, lat] 33 | 34 | def project(coords): 35 | if all(isinstance(coord, int) or isinstance(coord, float) for coord in coords): 36 | assert len(coords) == 2 37 | return project_one(coords[0], coords[1]) 38 | return [project(cord) for cord in coords] 39 | 40 | coords = project(feature['geometry']) 41 | 42 | return { 43 | "type": "Feature", 44 | "geometry": { 45 | "type": geometry_types[feature['type']], 46 | "coordinates": coords[0] if feature['type'] in range(0,3) and len(coords) else coords 47 | }, 48 | "properties": feature.get('tags', {}) 49 | } 50 | -------------------------------------------------------------------------------- /geojson2vt/wrap.py: -------------------------------------------------------------------------------- 1 | from geojson2vt.clip import clip 2 | from geojson2vt.feature import Slice, create_feature 3 | 4 | 5 | def wrap(features, options): 6 | buffer = options.get('buffer') / options.get('extent') 7 | merged = features 8 | left = clip(features, 1, -1 - buffer, buffer, 9 | 0, -1, 2, options) # left world copy 10 | right = clip(features, 1, 1 - buffer, 2 + buffer, 11 | 0, -1, 2, options) # right world copy 12 | 13 | if left is not None or right is not None: 14 | c = clip(features, 1, -buffer, 1 + buffer, 0, -1, 2, options) 15 | merged = c if c is not None else [] # :nter world copy 16 | 17 | if left is not None: 18 | merged = shift_feature_coords( 19 | left, 1) + merged # merge left into center 20 | if right is not None: 21 | # merge right into center 22 | merged = merged + (shift_feature_coords(right, -1)) 23 | 24 | return merged 25 | 26 | 27 | def shift_feature_coords(features, offset): 28 | new_features = [] 29 | 30 | for i in range(len(features)): 31 | feature = features[i] 32 | type_ = feature.get('type') 33 | # new_geometry = None 34 | new_geometry = [] 35 | 36 | if type_ == 'Pint' or type_ == 'MultiPint' or type_ == 'LineString': 37 | new_geometry = shift_coords(feature.get('geometry'), offset) 38 | elif type_ == 'MultiLineSting' or type_ == 'Polygon': 39 | new_geometry = [] 40 | for line in feature.get('geometry'): 41 | new_geometry.append(shift_coords(line, offset)) 42 | elif type_ == 'MultiPolygon': 43 | new_geometry = [] 44 | for polygon in feature.get('geometry'): 45 | new_polygon = [] 46 | for line in polygon: 47 | new_polygon.append(shift_coords(line, offset)) 48 | new_geometry.append(new_polygon) 49 | 50 | new_features.append(create_feature( 51 | feature.get('id'), type_, new_geometry, feature.get('tags'))) 52 | return new_features 53 | 54 | 55 | def shift_coords(points, offset): 56 | new_points = Slice([]) 57 | new_points.size = points.size 58 | 59 | if points.start is not None: 60 | new_points.start = points.start 61 | new_points.end = points.end 62 | 63 | for i in range(0, len(points), 3): 64 | new_points.append(points[i] + offset) 65 | new_points.append(points[i + 1]) 66 | new_points.append(points[i + 2]) 67 | return new_points 68 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | pytest==5.4.1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from io import open 2 | from setuptools import setup, find_packages 3 | 4 | with open('README.rst', 'rb') as f: 5 | readme = f.read().decode('utf-8') 6 | 7 | setup( 8 | name='geojson2vt', 9 | version='1.0.1', 10 | description='Python package to convert GeoJSON to vector tiles', 11 | long_description=readme, 12 | author='Samuel Kurath', 13 | author_email='geometalab@hsr.ch', 14 | url='https://github.com/geometalab/geojson2vt', 15 | packages=find_packages(exclude=('tests', 'docs')) 16 | ) -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geometalab/geojson2vt/3653e4ca958e6852f4de0b2f2e1ea4b2d6edc069/tests/__init__.py -------------------------------------------------------------------------------- /tests/fixtures/collection-tiles.json: -------------------------------------------------------------------------------- 1 | {"z0-0-0":[{"geometry":[[3186,4096],[3197,0]],"type":1,"tags":null},{"geometry":[[[3197,2048],[3209,2037]]],"type":2,"tags":null}]} 2 | -------------------------------------------------------------------------------- /tests/fixtures/collection.json: -------------------------------------------------------------------------------- 1 | { "type": "GeometryCollection", 2 | "geometries": [ 3 | { "type": "MultiPoint", 4 | "coordinates": [ [100, -90], [101, 90] ] 5 | }, 6 | { "type": "LineString", 7 | "coordinates": [ [101, 0], [102, 1.0] ] 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tests/fixtures/dateline-metrics-tiles.json: -------------------------------------------------------------------------------- 1 | {"z0-0-0":[{"geometry":[[[4160,1532],[4088,1464],[3952,1456],[3984,896],[4160,1217],[4160,1532]]],"type":3,"tags":{}},{"geometry":[[3312,1168]],"type":1,"tags":{}},{"geometry":[[[3472,2792],[4160,2782]]],"type":2,"tags":{"mapbox_clip_start":0,"mapbox_clip_end":0.24216869845698458}},{"geometry":[[[4160,2953],[3800,3112],[4160,3087]]],"type":2,"tags":{"mapbox_clip_start":0.5368896071622057,"mapbox_clip_end":0.802430932087145}},{"geometry":[[[2968,2208],[4160,2130]]],"type":2,"tags":{"mapbox_clip_start":0,"mapbox_clip_end":0.8816568047337279}},{"geometry":[[[3472,1488],[3992,2216],[4160,2370],[4160,2495],[3856,2384],[3616,2064],[3384,1744],[2744,1448],[2200,1736],[1672,2032],[1152,2384],[992,1904],[952,1520],[1400,1608],[1480,1800],[1840,1696],[1656,1384],[1232,1168],[616,1272],[152,1616],[-8,1464],[-64,1461],[-64,983],[112,1304],[280,1112],[680,816],[2304,816],[2792,1000],[2168,992],[1560,976],[2008,1216],[2896,1232],[3280,1168],[3472,1488]]],"type":3,"tags":{}},{"geometry":[[2248,2808]],"type":1,"tags":{}},{"geometry":[[2968,2272]],"type":1,"tags":{}},{"geometry":[[488,1736]],"type":1,"tags":{}},{"geometry":[[[3456,280],[4160,280]]],"type":2,"tags":{"mapbox_clip_start":0,"mapbox_clip_end":0.25553376666379907}},{"geometry":[[[4160,378],[3752,464],[4160,526]]],"type":2,"tags":{"mapbox_clip_start":0.5960823391063382,"mapbox_clip_end":0.8972126466121583}},{"geometry":[[[-64,2784],[464,2776],[-64,3009]]],"type":2,"tags":{"mapbox_clip_start":0.1971140568835921,"mapbox_clip_end":0.5861456563744839}},{"geometry":[[[-64,3096],[624,3048]]],"type":2,"tags":{"mapbox_clip_start":0.7572722879927781,"mapbox_clip_end":1}},{"geometry":[[[3944,1344],[4160,1311]]],"type":2,"tags":{"mapbox_clip_start":0,"mapbox_clip_end":0.27835051546391754}},{"geometry":[[[-64,2139],[224,2120]]],"type":2,"tags":{"mapbox_clip_start":0.7869822485207101,"mapbox_clip_end":1}},{"geometry":[[[-64,2253],[288,2576],[-64,2448],[-64,2253]]],"type":3,"tags":{}},{"geometry":[[368,2176]],"type":1,"tags":{}},{"geometry":[[[-64,280],[528,280],[-64,405]]],"type":2,"tags":{"mapbox_clip_start":0.20907308181583562,"mapbox_clip_end":0.6435660880356269}},{"geometry":[[[-64,506],[344,568]]],"type":2,"tags":{"mapbox_clip_start":0.8502241422062877,"mapbox_clip_end":1}},{"geometry":[[[-64,1330],[624,1224]]],"type":2,"tags":{"mapbox_clip_start":0.1134020618556701,"mapbox_clip_end":1}}]} 2 | -------------------------------------------------------------------------------- /tests/fixtures/dateline-tiles.json: -------------------------------------------------------------------------------- 1 | {"z0-0-0":[{"geometry":[[[4160,1532],[4088,1464],[3952,1456],[3984,896],[4160,1217],[4160,1532]]],"type":3,"tags":{}},{"geometry":[[3312,1168]],"type":1,"tags":{}},{"geometry":[[[3472,2792],[4160,2782]],[[4160,2953],[3800,3112],[4160,3087]]],"type":2,"tags":{}},{"geometry":[[[2968,2208],[4160,2130]]],"type":2,"tags":{}},{"geometry":[[[3472,1488],[3992,2216],[4160,2370],[4160,2495],[3856,2384],[3616,2064],[3384,1744],[2744,1448],[2200,1736],[1672,2032],[1152,2384],[992,1904],[952,1520],[1400,1608],[1480,1800],[1840,1696],[1656,1384],[1232,1168],[616,1272],[152,1616],[-8,1464],[-64,1461],[-64,983],[112,1304],[280,1112],[680,816],[2304,816],[2792,1000],[2168,992],[1560,976],[2008,1216],[2896,1232],[3280,1168],[3472,1488]]],"type":3,"tags":{}},{"geometry":[[2248,2808]],"type":1,"tags":{}},{"geometry":[[2968,2272]],"type":1,"tags":{}},{"geometry":[[488,1736]],"type":1,"tags":{}},{"geometry":[[[3456,280],[4160,280]],[[4160,378],[3752,464],[4160,526]]],"type":2,"tags":{}},{"geometry":[[[-64,2784],[464,2776],[-64,3009]],[[-64,3096],[624,3048]]],"type":2,"tags":{}},{"geometry":[[[3944,1344],[4160,1311]],[[-64,2139],[224,2120]]],"type":2,"tags":{}},{"geometry":[[[-64,2253],[288,2576],[-64,2448],[-64,2253]]],"type":3,"tags":{}},{"geometry":[[368,2176]],"type":1,"tags":{}},{"geometry":[[[-64,280],[528,280],[-64,405]],[[-64,506],[344,568]]],"type":2,"tags":{}},{"geometry":[[[-64,1330],[624,1224]]],"type":2,"tags":{}}]} -------------------------------------------------------------------------------- /tests/fixtures/dateline.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "Polygon", 9 | "coordinates": [ 10 | [ 11 | [ 12 | 125.15625000000001, 13 | 44.08758502824518 14 | ], 15 | [ 16 | 170.859375, 17 | -14.604847155053886 18 | ], 19 | [ 20 | 205.3125, 21 | -42.0329743324414 22 | ], 23 | [ 24 | 158.90625, 25 | -28.30438068296277 26 | ], 27 | [ 28 | 137.8125, 29 | -1.4061088354351468 30 | ], 31 | [ 32 | 117.42187500000001, 33 | 25.799891182088334 34 | ], 35 | [ 36 | 61.17187499999999, 37 | 46.55886030311719 38 | ], 39 | [ 40 | 13.359375, 41 | 26.43122806450644 42 | ], 43 | [ 44 | -33.046875, 45 | 1.4061088354351594 46 | ], 47 | [ 48 | -78.75, 49 | -28.30438068296277 50 | ], 51 | [ 52 | -92.8125, 53 | 12.554563528593656 54 | ], 55 | [ 56 | -96.328125, 57 | 42.032974332441405 58 | ], 59 | [ 60 | -56.953125, 61 | 36.03133177633189 62 | ], 63 | [ 64 | -49.92187499999999, 65 | 21.28937435586041 66 | ], 67 | [ 68 | -18.28125, 69 | 29.53522956294847 70 | ], 71 | [ 72 | -34.453125, 73 | 50.28933925329178 74 | ], 75 | [ 76 | -71.71875, 77 | 60.930432202923335 78 | ], 79 | [ 80 | -125.859375, 81 | 56.17002298293205 82 | ], 83 | [ 84 | -166.640625, 85 | 35.460669951495305 86 | ], 87 | [ 88 | -180.703125, 89 | 45.583289756006316 90 | ], 91 | [ 92 | -192.65625, 93 | 46.07323062540838 94 | ], 95 | [ 96 | -189.84375, 97 | 70.61261423801925 98 | ], 99 | [ 100 | -170.15625, 101 | 54.57206165565852 102 | ], 103 | [ 104 | -155.390625, 105 | 63.23362741232569 106 | ], 107 | [ 108 | -120.234375, 109 | 72.81607371878991 110 | ], 111 | [ 112 | -72.421875, 113 | 72.81607371878991 114 | ], 115 | [ 116 | 22.5, 117 | 72.81607371878991 118 | ], 119 | [ 120 | 65.390625, 121 | 67.33986082559095 122 | ], 123 | [ 124 | 10.546875, 125 | 67.60922060496382 126 | ], 127 | [ 128 | -42.890625, 129 | 68.13885164925573 130 | ], 131 | [ 132 | -3.515625, 133 | 58.81374171570782 134 | ], 135 | [ 136 | 74.53125, 137 | 58.07787626787517 138 | ], 139 | [ 140 | 108.28125, 141 | 60.930432202923335 142 | ], 143 | [ 144 | 125.15625000000001, 145 | 44.08758502824518 146 | ] 147 | ] 148 | ] 149 | } 150 | }, 151 | { 152 | "type": "Feature", 153 | "properties": {}, 154 | "geometry": { 155 | "type": "Point", 156 | "coordinates": [ 157 | 17.578125, 158 | -55.379110448010486 159 | ] 160 | } 161 | }, 162 | { 163 | "type": "Feature", 164 | "properties": {}, 165 | "geometry": { 166 | "type": "Point", 167 | "coordinates": [ 168 | 80.85937499999999, 169 | -19.31114335506463 170 | ] 171 | } 172 | }, 173 | { 174 | "type": "Feature", 175 | "properties": {}, 176 | "geometry": { 177 | "type": "Point", 178 | "coordinates": [ 179 | -137.109375, 180 | 26.43122806450644 181 | ] 182 | } 183 | }, 184 | { 185 | "type": "Feature", 186 | "properties": {}, 187 | "geometry": { 188 | "type": "Point", 189 | "coordinates": [ 190 | -248.90624999999997, 191 | 60.930432202923335 192 | ] 193 | } 194 | }, 195 | { 196 | "type": "Feature", 197 | "properties": {}, 198 | "geometry": { 199 | "type": "Point", 200 | "coordinates": [ 201 | 212.34375, 202 | -11.178401873711785 203 | ] 204 | } 205 | }, 206 | { 207 | "type": "Feature", 208 | "properties": {}, 209 | "geometry": { 210 | "type": "LineString", 211 | "coordinates": [ 212 | [ 213 | 123.74999999999999, 214 | 82.40242347938855 215 | ], 216 | [ 217 | 226.40625, 218 | 82.40242347938855 219 | ], 220 | [ 221 | 149.765625, 222 | 79.93591824625466 223 | ], 224 | [ 225 | 210.234375, 226 | 78.20656311074711 227 | ] 228 | ] 229 | } 230 | }, 231 | { 232 | "type": "Feature", 233 | "properties": {}, 234 | "geometry": { 235 | "type": "LineString", 236 | "coordinates": [ 237 | [ 238 | -234.84375000000003, 239 | -54.572061655658516 240 | ], 241 | [ 242 | -139.21874999999997, 243 | -53.748710796898976 244 | ], 245 | [ 246 | -206.015625, 247 | -67.87554134672943 248 | ], 249 | [ 250 | -125.15625000000001, 251 | -65.6582745198266 252 | ] 253 | ] 254 | } 255 | }, 256 | { 257 | "type": "Feature", 258 | "properties": {}, 259 | "geometry": { 260 | "type": "MultiLineString", 261 | "coordinates": [[ 262 | [ 263 | 166.640625, 264 | 52.482780222078226 265 | ], 266 | [ 267 | 234.84375000000003, 268 | 58.44773280389084 269 | ] 270 | ], [ 271 | [ 272 | -279.140625, 273 | -13.923403897723334 274 | ], 275 | [ 276 | -160.3125, 277 | -6.315298538330033 278 | ] 279 | ]] 280 | } 281 | } 282 | ] 283 | } 284 | -------------------------------------------------------------------------------- /tests/fixtures/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [] 4 | } 5 | -------------------------------------------------------------------------------- /tests/fixtures/feature-null-geometry.json: -------------------------------------------------------------------------------- 1 | { "type": "Feature", 2 | "geometry": null, 3 | "properties": { 4 | "prop0": "value0", 5 | "prop1": {"this": "that"} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/feature-tiles.json: -------------------------------------------------------------------------------- 1 | {"z0-0-0":[{"geometry":[[[3186,2048],[3186,2037],[3197,2037],[3197,2048],[3186,2048]]],"type":3,"tags":{"prop0":"value0","prop1":{"this":"that"}},"id": 0}]} 2 | -------------------------------------------------------------------------------- /tests/fixtures/feature.json: -------------------------------------------------------------------------------- 1 | { "type": "Feature", 2 | "geometry": { 3 | "type": "Polygon", 4 | "coordinates": [ 5 | [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], 6 | [100.0, 1.0], [100.0, 0.0] ] 7 | ] 8 | }, 9 | "properties": { 10 | "prop0": "value0", 11 | "prop1": {"this": "that"} 12 | }, 13 | "id": 0 14 | } 15 | -------------------------------------------------------------------------------- /tests/fixtures/feature_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "geometry": [ 4 | [ 5 | [3415, -64], 6 | [3323, -15], 7 | [3300, -64], 8 | [3415, -64] 9 | ] 10 | ], 11 | "type": 3, 12 | "tags": { "name": "Connecticut", "density": 739.1 }, 13 | "id": "09" 14 | }, 15 | { 16 | "geometry": [ 17 | [ 18 | [762, 2248], 19 | [627, 2476], 20 | [475, 2600], 21 | [507, 2900], 22 | [722, 3179], 23 | [778, 3642], 24 | [1089, 4124], 25 | [1233, 4144], 26 | [1234, 4160], 27 | [321, 4160], 28 | [220, 2403], 29 | [467, 2196], 30 | [762, 2248] 31 | ] 32 | ], 33 | "type": 3, 34 | "tags": { "name": "Delaware", "density": 464.3 }, 35 | "id": "10" 36 | }, 37 | { 38 | "geometry": [ 39 | [ 40 | [-64, 2403], 41 | [220, 2403], 42 | [321, 4160], 43 | [-64, 4160], 44 | [-64, 2829], 45 | [-51, 2714], 46 | [-64, 2717], 47 | [-64, 2403] 48 | ] 49 | ], 50 | "type": 3, 51 | "tags": { "name": "Maryland", "density": 596.3 }, 52 | "id": "24" 53 | }, 54 | { 55 | "geometry": [ 56 | [ 57 | [2914, -64], 58 | [2964, -36], 59 | [2788, 523], 60 | [2549, 649], 61 | [2421, 943], 62 | [2820, 1090], 63 | [2852, 1310], 64 | [2677, 2331], 65 | [2222, 3086], 66 | [1927, 3303], 67 | [1664, 3775], 68 | [1528, 3467], 69 | [1105, 3313], 70 | [587, 2900], 71 | [547, 2580], 72 | [627, 2476], 73 | [762, 2248], 74 | [1153, 2092], 75 | [1177, 1946], 76 | [1624, 1634], 77 | [1696, 1467], 78 | [1281, 1080], 79 | [1265, 838], 80 | [1081, 775], 81 | [1065, 555], 82 | [1289, 218], 83 | [1169, 17], 84 | [1243, -64], 85 | [2914, -64] 86 | ] 87 | ], 88 | "type": 3, 89 | "tags": { "name": "New Jersey", "density": 1189 }, 90 | "id": "34" 91 | }, 92 | { 93 | "geometry": [ 94 | [ 95 | [3300, -64], 96 | [3323, -15], 97 | [3945, 144], 98 | [4072, 28], 99 | [4160, 28], 100 | [4160, 594], 101 | [3929, 681], 102 | [3458, 765], 103 | [3147, 744], 104 | [2916, 838], 105 | [2788, 523], 106 | [2964, -36], 107 | [2914, -64], 108 | [3300, -64] 109 | ] 110 | ], 111 | "type": 3, 112 | "tags": { "name": "New York", "density": 412.3 }, 113 | "id": "36" 114 | }, 115 | { 116 | "geometry": [ 117 | [ 118 | [1243, -64], 119 | [1169, 17], 120 | [1289, 218], 121 | [1065, 555], 122 | [1081, 775], 123 | [1265, 838], 124 | [1281, 1080], 125 | [1696, 1467], 126 | [1624, 1634], 127 | [1177, 1946], 128 | [1153, 2092], 129 | [762, 2248], 130 | [467, 2196], 131 | [220, 2403], 132 | [-64, 2403], 133 | [-64, -64], 134 | [1243, -64] 135 | ] 136 | ], 137 | "type": 3, 138 | "tags": { "name": "Pennsylvania", "density": 284.3 }, 139 | "id": "42" 140 | } 141 | ] 142 | -------------------------------------------------------------------------------- /tests/fixtures/ids-generate-id-tiles.json: -------------------------------------------------------------------------------- 1 | {"z0-0-0":[{"geometry":[[[2968,2208],[4160,2130]]],"type":2,"tags":{"generatedId":5},"id":5},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":"value0","prop1":{"this":"that"},"generatedId":0},"id":0},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":"1","prop1":{"this":"that"},"generatedId":1},"id":1},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":1,"prop1":{"this":"that"},"generatedId":2},"id":2},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":1,"prop1":{"this":"that"},"generatedId":3},"id":3},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":2,"prop1":{"this":"that"},"generatedId":4},"id":4},{"geometry":[[[3944,1344],[4160,1311]],[[-64,2139],[224,2120]]],"type":2,"tags":{"generatedId":5},"id":5},{"geometry":[[3186,4096],[3197,0]],"type":1,"tags":{"prop0":4,"generatedId":6},"id":6},{"geometry":[[[3197,2048],[3209,2037]]],"type":2,"tags":{"prop0":4,"generatedId":6},"id":6},{"geometry":[[[-64,1330],[624,1224]]],"type":2,"tags":{"generatedId":5},"id":5}]} -------------------------------------------------------------------------------- /tests/fixtures/ids-promote-id-tiles.json: -------------------------------------------------------------------------------- 1 | {"z0-0-0":[{"geometry":[[[2968,2208],[4160,2130]]],"type":2,"tags":{"generatedId":5}},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":"value0","prop1":{"this":"that"},"generatedId":0},"id":"value0"},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":"1","prop1":{"this":"that"},"generatedId":1},"id":"1"},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":1,"prop1":{"this":"that"},"generatedId":2},"id":1},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":1,"prop1":{"this":"that"},"generatedId":3},"id":1},{"geometry":[[3186,2048]],"type":1,"tags":{"prop0":2,"prop1":{"this":"that"},"generatedId":4},"id":2},{"geometry":[[[3944,1344],[4160,1311]],[[-64,2139],[224,2120]]],"type":2,"tags":{"generatedId":5}},{"geometry":[[3186,4096],[3197,0]],"type":1,"tags":{"prop0":4,"generatedId":6},"id":4},{"geometry":[[[3197,2048],[3209,2037]]],"type":2,"tags":{"prop0":4,"generatedId":6},"id":4},{"geometry":[[[-64,1330],[624,1224]]],"type":2,"tags":{"generatedId":5}}]} -------------------------------------------------------------------------------- /tests/fixtures/ids.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [100.0, 0.0] 9 | }, 10 | "properties": { 11 | "prop0": "value0", 12 | "prop1": {"this": "that"}, 13 | "generatedId": 0 14 | }, 15 | "id": 0 16 | }, { 17 | "type": "Feature", 18 | "geometry": { 19 | "type": "Point", 20 | "coordinates": [100.0, 0.0] 21 | }, 22 | "properties": { 23 | "prop0": "1", 24 | "prop1": {"this": "that"}, 25 | "generatedId": 1 26 | }, 27 | "id": "guid" 28 | }, { 29 | "type": "Feature", 30 | "geometry": { 31 | "type": "Point", 32 | "coordinates": [100.0, 0.0] 33 | }, 34 | "properties": { 35 | "prop0": 1, 36 | "prop1": {"this": "that"}, 37 | "generatedId": 2 38 | } 39 | }, { 40 | "type": "Feature", 41 | "geometry": { 42 | "type": "Point", 43 | "coordinates": [100.0, 0.0] 44 | }, 45 | "properties": { 46 | "prop0": 1, 47 | "prop1": {"this": "that"}, 48 | "generatedId": 3 49 | }, 50 | "id": 2 51 | }, { 52 | "type": "Feature", 53 | "geometry": { 54 | "type": "Point", 55 | "coordinates": [100.0, 0.0] 56 | }, 57 | "properties": { 58 | "prop0": 2, 59 | "prop1": {"this": "that"}, 60 | "generatedId": 4 61 | } 62 | }, 63 | { 64 | "type": "Feature", 65 | "properties": { 66 | "generatedId": 5 67 | }, 68 | "geometry": { 69 | "type": "MultiLineString", 70 | "coordinates": [[ 71 | [ 72 | 166.640625, 73 | 52.482780222078226 74 | ], 75 | [ 76 | 234.84375000000003, 77 | 58.44773280389084 78 | ] 79 | ], [ 80 | [ 81 | -279.140625, 82 | -13.923403897723334 83 | ], 84 | [ 85 | -160.3125, 86 | -6.315298538330033 87 | ] 88 | ]] 89 | } 90 | }, 91 | { 92 | "type": "Feature", 93 | "properties": { 94 | "prop0": 4, 95 | "generatedId": 6 96 | }, 97 | "geometry": { 98 | "type": "GeometryCollection", 99 | "geometries": [ 100 | { 101 | "type": "MultiPoint", 102 | "coordinates": [ [100, -90], [101, 90] ] 103 | }, 104 | { 105 | "type": "LineString", 106 | "coordinates": [ [101, 0], [102, 1.0] ] 107 | } 108 | ] 109 | } 110 | } 111 | ] 112 | } -------------------------------------------------------------------------------- /tests/fixtures/linestring.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "Polygon", 9 | "coordinates": [ 10 | [ 11 | [ 12 | 9.31065559387207, 13 | 47.12633021376189 14 | ], 15 | [ 16 | 9.303703308105469, 17 | 47.12142456895783 18 | ], 19 | [ 20 | 9.318037033081053, 21 | 47.11756981639951 22 | ], 23 | [ 24 | 9.32241439819336, 25 | 47.12130776237743 26 | ], 27 | [ 28 | 9.31889533996582, 29 | 47.12708938027347 30 | ], 31 | [ 32 | 9.31065559387207, 33 | 47.12633021376189 34 | ] 35 | ] 36 | ] 37 | } 38 | }, 39 | { 40 | "type": "Feature", 41 | "properties": {}, 42 | "geometry": { 43 | "type": "LineString", 44 | "coordinates": [ 45 | [ 46 | 9.326620101928711, 47 | 47.12504544575168 48 | ], 49 | [ 50 | 9.336233139038086, 51 | 47.11832910792961 52 | ], 53 | [ 54 | 9.331769943237305, 55 | 47.1127217765886 56 | ], 57 | [ 58 | 9.322242736816406, 59 | 47.11020996782234 60 | ] 61 | ] 62 | } 63 | }, 64 | { 65 | "type": "Feature", 66 | "properties": {}, 67 | "geometry": { 68 | "type": "Point", 69 | "coordinates": [ 70 | 9.303016662597656, 71 | 47.126738997075975 72 | ] 73 | } 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /tests/fixtures/multi.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "Polygon", 9 | "coordinates": [ 10 | [ 11 | [ 12 | 9.31065559387207, 13 | 47.12633021376189 14 | ], 15 | [ 16 | 9.303703308105469, 17 | 47.12142456895783 18 | ], 19 | [ 20 | 9.318037033081053, 21 | 47.11756981639951 22 | ], 23 | [ 24 | 9.32241439819336, 25 | 47.12130776237743 26 | ], 27 | [ 28 | 9.31889533996582, 29 | 47.12708938027347 30 | ], 31 | [ 32 | 9.31065559387207, 33 | 47.12633021376189 34 | ] 35 | ] 36 | ] 37 | } 38 | }, 39 | { 40 | "type": "Feature", 41 | "properties": {}, 42 | "geometry": { 43 | "type": "LineString", 44 | "coordinates": [ 45 | [ 46 | 9.326620101928711, 47 | 47.12504544575168 48 | ], 49 | [ 50 | 9.336233139038086, 51 | 47.11832910792961 52 | ], 53 | [ 54 | 9.331769943237305, 55 | 47.1127217765886 56 | ], 57 | [ 58 | 9.322242736816406, 59 | 47.11020996782234 60 | ] 61 | ] 62 | } 63 | }, 64 | { 65 | "type": "Feature", 66 | "properties": {}, 67 | "geometry": { 68 | "type": "Point", 69 | "coordinates": [ 70 | 9.303016662597656, 71 | 47.126738997075975 72 | ] 73 | } 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /tests/fixtures/single-geom-tiles.json: -------------------------------------------------------------------------------- 1 | {"z0-0-0":[{"geometry":[[[1054,1622],[1074,1623],[1082,1660],[1081,1677],[1051,1677],[1054,1684],[1051,1687],[1047,1681],[1042,1685],[1041,1665],[1044,1622],[1054,1622]]],"type":3,"tags":null}]} 2 | -------------------------------------------------------------------------------- /tests/fixtures/single-geom.json: -------------------------------------------------------------------------------- 1 | { 2 | "type":"Polygon", 3 | "coordinates": [[ 4 | [-87.359296,35.00118],[-85.606675,34.984749],[-85.431413,34.124869],[-85.184951,32.859696],[-85.069935,32.580372], 5 | [-84.960397,32.421541],[-85.004212,32.322956],[-84.889196,32.262709],[-85.058981,32.13674],[-85.053504,32.01077], 6 | [-85.141136,31.840985],[-85.042551,31.539753],[-85.113751,31.27686],[-85.004212,31.003013],[-85.497137,30.997536], 7 | [-87.600282,30.997536],[-87.633143,30.86609],[-87.408589,30.674397],[-87.446927,30.510088],[-87.37025,30.427934], 8 | [-87.518128,30.280057],[-87.655051,30.247195],[-87.90699,30.411504],[-87.934375,30.657966],[-88.011052,30.685351], 9 | [-88.10416,30.499135],[-88.137022,30.318396],[-88.394438,30.367688],[-88.471115,31.895754],[-88.241084,33.796253], 10 | [-88.098683,34.891641],[-88.202745,34.995703],[-87.359296,35.00118] 11 | ]] 12 | } 13 | -------------------------------------------------------------------------------- /tests/fixtures/small.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "Polygon", 9 | "coordinates": [ 10 | [ 11 | [9.304733276367188, 47.12720617415971], 12 | [9.303574562072754, 47.12329343936708], 13 | [9.309453964233398, 47.12040250268418], 14 | [9.316148757934569, 47.123059834156194], 15 | [9.312543869018555, 47.126768195763916], 16 | [9.304733276367188, 47.12720617415971] 17 | ] 18 | ] 19 | } 20 | }, 21 | { 22 | "type": "Feature", 23 | "properties": {}, 24 | "geometry": { 25 | "type": "Polygon", 26 | "coordinates": [ 27 | [ 28 | [9.31640625, 47.11932201127406], 29 | [9.315977096557617, 47.1160804053883], 30 | [9.322800636291504, 47.11485380031117], 31 | [9.32640552520752, 47.11736538990553], 32 | [9.324517250061035, 47.122505017669155], 33 | [9.31640625, 47.11932201127406] 34 | ] 35 | ] 36 | } 37 | }, 38 | { 39 | "type": "Feature", 40 | "properties": {}, 41 | "geometry": { 42 | "type": "Polygon", 43 | "coordinates": [ 44 | [ 45 | [9.331812858581543, 47.12066532160126], 46 | [9.330310821533203, 47.11938041677613], 47 | [9.332284927368164, 47.117219370500415], 48 | [9.33713436126709, 47.11704414668534], 49 | [9.336833953857422, 47.121015744804474], 50 | [9.331812858581543, 47.12066532160126] 51 | ] 52 | ] 53 | } 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /tests/fixtures/small_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "0": { 3 | "features": [], 4 | "numPoints": 18, 5 | "numSimplified": 0, 6 | "numFeatures": 3, 7 | "source": null, 8 | "x": 0, 9 | "y": 0, 10 | "z": 0, 11 | "transformed": true, 12 | "minX": 0.5258432626724243, 13 | "minY": 0.35120749473571783, 14 | "maxX": 0.525936484336853, 15 | "maxY": 0.3512579202651977 16 | }, 17 | "1": { 18 | "features": [], 19 | "numPoints": 0, 20 | "numSimplified": 0, 21 | "numFeatures": 0, 22 | "source": [], 23 | "x": 0, 24 | "y": 0, 25 | "z": 1, 26 | "transformed": false, 27 | "minX": 2, 28 | "minY": 1, 29 | "maxX": -1, 30 | "maxY": 0 31 | }, 32 | "33": { 33 | "features": [], 34 | "numPoints": 18, 35 | "numSimplified": 0, 36 | "numFeatures": 3, 37 | "source": null, 38 | "x": 1, 39 | "y": 0, 40 | "z": 1, 41 | "transformed": false, 42 | "minX": 0.5258432626724243, 43 | "minY": 0.35120749473571783, 44 | "maxX": 0.525936484336853, 45 | "maxY": 0.3512579202651977 46 | }, 47 | "65": { 48 | "features": [], 49 | "numPoints": 0, 50 | "numSimplified": 0, 51 | "numFeatures": 0, 52 | "source": [], 53 | "x": 0, 54 | "y": 1, 55 | "z": 1, 56 | "transformed": false, 57 | "minX": 2, 58 | "minY": 1, 59 | "maxX": -1, 60 | "maxY": 0 61 | }, 62 | "66": { 63 | "features": [], 64 | "numPoints": 0, 65 | "numSimplified": 0, 66 | "numFeatures": 0, 67 | "source": [], 68 | "x": 2, 69 | "y": 0, 70 | "z": 2, 71 | "transformed": false, 72 | "minX": 2, 73 | "minY": 1, 74 | "maxX": -1, 75 | "maxY": 0 76 | }, 77 | "97": { 78 | "features": [], 79 | "numPoints": 0, 80 | "numSimplified": 0, 81 | "numFeatures": 0, 82 | "source": [], 83 | "x": 1, 84 | "y": 1, 85 | "z": 1, 86 | "transformed": false, 87 | "minX": 2, 88 | "minY": 1, 89 | "maxX": -1, 90 | "maxY": 0 91 | }, 92 | "98": { 93 | "features": [], 94 | "numPoints": 0, 95 | "numSimplified": 0, 96 | "numFeatures": 0, 97 | "source": [], 98 | "x": 3, 99 | "y": 0, 100 | "z": 2, 101 | "transformed": false, 102 | "minX": 2, 103 | "minY": 1, 104 | "maxX": -1, 105 | "maxY": 0 106 | }, 107 | "194": { 108 | "features": [], 109 | "numPoints": 18, 110 | "numSimplified": 0, 111 | "numFeatures": 3, 112 | "source": null, 113 | "x": 2, 114 | "y": 1, 115 | "z": 2, 116 | "transformed": false, 117 | "minX": 0.5258432626724243, 118 | "minY": 0.35120749473571783, 119 | "maxX": 0.525936484336853, 120 | "maxY": 0.3512579202651977 121 | }, 122 | "226": { 123 | "features": [], 124 | "numPoints": 0, 125 | "numSimplified": 0, 126 | "numFeatures": 0, 127 | "source": [], 128 | "x": 3, 129 | "y": 1, 130 | "z": 2, 131 | "transformed": false, 132 | "minX": 2, 133 | "minY": 1, 134 | "maxX": -1, 135 | "maxY": 0 136 | }, 137 | "643": { 138 | "features": [], 139 | "numPoints": 18, 140 | "numSimplified": 0, 141 | "numFeatures": 3, 142 | "source": null, 143 | "x": 4, 144 | "y": 2, 145 | "z": 3, 146 | "transformed": false, 147 | "minX": 0.5258432626724243, 148 | "minY": 0.35120749473571783, 149 | "maxX": 0.525936484336853, 150 | "maxY": 0.3512579202651977 151 | }, 152 | "675": { 153 | "features": [], 154 | "numPoints": 0, 155 | "numSimplified": 0, 156 | "numFeatures": 0, 157 | "source": [], 158 | "x": 5, 159 | "y": 2, 160 | "z": 3, 161 | "transformed": false, 162 | "minX": 2, 163 | "minY": 1, 164 | "maxX": -1, 165 | "maxY": 0 166 | }, 167 | "899": { 168 | "features": [], 169 | "numPoints": 0, 170 | "numSimplified": 0, 171 | "numFeatures": 0, 172 | "source": [], 173 | "x": 4, 174 | "y": 3, 175 | "z": 3, 176 | "transformed": false, 177 | "minX": 2, 178 | "minY": 1, 179 | "maxX": -1, 180 | "maxY": 0 181 | }, 182 | "931": { 183 | "features": [], 184 | "numPoints": 0, 185 | "numSimplified": 0, 186 | "numFeatures": 0, 187 | "source": [], 188 | "x": 5, 189 | "y": 3, 190 | "z": 3, 191 | "transformed": false, 192 | "minX": 2, 193 | "minY": 1, 194 | "maxX": -1, 195 | "maxY": 0 196 | }, 197 | "2308": { 198 | "features": [], 199 | "numPoints": 0, 200 | "numSimplified": 0, 201 | "numFeatures": 0, 202 | "source": [], 203 | "x": 8, 204 | "y": 4, 205 | "z": 4, 206 | "transformed": false, 207 | "minX": 2, 208 | "minY": 1, 209 | "maxX": -1, 210 | "maxY": 0 211 | }, 212 | "2340": { 213 | "features": [], 214 | "numPoints": 0, 215 | "numSimplified": 0, 216 | "numFeatures": 0, 217 | "source": [], 218 | "x": 9, 219 | "y": 4, 220 | "z": 4, 221 | "transformed": false, 222 | "minX": 2, 223 | "minY": 1, 224 | "maxX": -1, 225 | "maxY": 0 226 | }, 227 | "2820": { 228 | "features": [], 229 | "numPoints": 18, 230 | "numSimplified": 0, 231 | "numFeatures": 3, 232 | "source": null, 233 | "x": 8, 234 | "y": 5, 235 | "z": 4, 236 | "transformed": false, 237 | "minX": 0.5258432626724243, 238 | "minY": 0.35120749473571783, 239 | "maxX": 0.525936484336853, 240 | "maxY": 0.3512579202651977 241 | }, 242 | "2852": { 243 | "features": [], 244 | "numPoints": 0, 245 | "numSimplified": 0, 246 | "numFeatures": 0, 247 | "source": [], 248 | "x": 9, 249 | "y": 5, 250 | "z": 4, 251 | "transformed": false, 252 | "minX": 2, 253 | "minY": 1, 254 | "maxX": -1, 255 | "maxY": 0 256 | }, 257 | "10757": { 258 | "features": [], 259 | "numPoints": 0, 260 | "numSimplified": 0, 261 | "numFeatures": 0, 262 | "source": [], 263 | "x": 16, 264 | "y": 10, 265 | "z": 5, 266 | "transformed": false, 267 | "minX": 2, 268 | "minY": 1, 269 | "maxX": -1, 270 | "maxY": 0 271 | }, 272 | "10789": { 273 | "features": [], 274 | "numPoints": 0, 275 | "numSimplified": 0, 276 | "numFeatures": 0, 277 | "source": [], 278 | "x": 17, 279 | "y": 10, 280 | "z": 5, 281 | "transformed": false, 282 | "minX": 2, 283 | "minY": 1, 284 | "maxX": -1, 285 | "maxY": 0 286 | }, 287 | "11781": { 288 | "features": [ 289 | { 290 | "geometry": [ 291 | [ 292 | 0.5258464813232422, 293 | 0.35120749473571783, 294 | 0.5258781909942627, 295 | 0.3512244224548339, 296 | 0.5258464813232422, 297 | 0.35120749473571783 298 | ] 299 | ], 300 | "type": 3, 301 | "tags": {} 302 | }, 303 | { 304 | "geometry": [ 305 | [ 306 | 0.52587890625, 307 | 0.3512396812438965, 308 | 0.5259066820144653, 309 | 0.3512476682662964, 310 | 0.52587890625, 311 | 0.3512396812438965 312 | ] 313 | ], 314 | "type": 3, 315 | "tags": {} 316 | } 317 | ], 318 | "numPoints": 18, 319 | "numSimplified": 6, 320 | "numFeatures": 3, 321 | "source": null, 322 | "x": 16, 323 | "y": 11, 324 | "z": 5, 325 | "transformed": false, 326 | "minX": 0.5258432626724243, 327 | "minY": 0.35120749473571783, 328 | "maxX": 0.525936484336853, 329 | "maxY": 0.3512579202651977 330 | }, 331 | "11813": { 332 | "features": [], 333 | "numPoints": 0, 334 | "numSimplified": 0, 335 | "numFeatures": 0, 336 | "source": [], 337 | "x": 17, 338 | "y": 11, 339 | "z": 5, 340 | "transformed": false, 341 | "minX": 2, 342 | "minY": 1, 343 | "maxX": -1, 344 | "maxY": 0 345 | }, 346 | "46086": { 347 | "features": [], 348 | "numPoints": 0, 349 | "numSimplified": 0, 350 | "numFeatures": 0, 351 | "source": [], 352 | "x": 32, 353 | "y": 22, 354 | "z": 6, 355 | "transformed": false, 356 | "minX": 2, 357 | "minY": 1, 358 | "maxX": -1, 359 | "maxY": 0 360 | }, 361 | "46118": { 362 | "features": [ 363 | { 364 | "geometry": [ 365 | [ 366 | 0.5258464813232422, 367 | 0.35120749473571783, 368 | 0.5258781909942627, 369 | 0.3512244224548339, 370 | 0.5258595943450928, 371 | 0.35123527050018305, 372 | 0.5258464813232422, 373 | 0.35120749473571783 374 | ] 375 | ], 376 | "type": 3, 377 | "tags": {} 378 | }, 379 | { 380 | "geometry": [ 381 | [ 382 | 0.52587890625, 383 | 0.3512396812438965, 384 | 0.5259014368057251, 385 | 0.35122668743133545, 386 | 0.5259066820144653, 387 | 0.3512476682662964, 388 | 0.5258777141571045, 389 | 0.3512529134750366, 390 | 0.52587890625, 391 | 0.3512396812438965 392 | ] 393 | ], 394 | "type": 3, 395 | "tags": {} 396 | }, 397 | { 398 | "geometry": [ 399 | [ 400 | 0.5259217023849487, 401 | 0.35123419761657715, 402 | 0.525936484336853, 403 | 0.35124897956848145, 404 | 0.5259217023849487, 405 | 0.35123419761657715 406 | ] 407 | ], 408 | "type": 3, 409 | "tags": {} 410 | } 411 | ], 412 | "numPoints": 18, 413 | "numSimplified": 12, 414 | "numFeatures": 3, 415 | "source": null, 416 | "x": 33, 417 | "y": 22, 418 | "z": 6, 419 | "transformed": false, 420 | "minX": 0.5258432626724243, 421 | "minY": 0.35120749473571783, 422 | "maxX": 0.525936484336853, 423 | "maxY": 0.3512579202651977 424 | }, 425 | "48134": { 426 | "features": [], 427 | "numPoints": 0, 428 | "numSimplified": 0, 429 | "numFeatures": 0, 430 | "source": [], 431 | "x": 32, 432 | "y": 23, 433 | "z": 6, 434 | "transformed": false, 435 | "minX": 2, 436 | "minY": 1, 437 | "maxX": -1, 438 | "maxY": 0 439 | }, 440 | "48166": { 441 | "features": [], 442 | "numPoints": 0, 443 | "numSimplified": 0, 444 | "numFeatures": 0, 445 | "source": [], 446 | "x": 33, 447 | "y": 23, 448 | "z": 6, 449 | "transformed": false, 450 | "minX": 2, 451 | "minY": 1, 452 | "maxX": -1, 453 | "maxY": 0 454 | }, 455 | "182343": { 456 | "features": [], 457 | "numPoints": 0, 458 | "numSimplified": 0, 459 | "numFeatures": 0, 460 | "source": [], 461 | "x": 66, 462 | "y": 44, 463 | "z": 7, 464 | "transformed": false, 465 | "minX": 2, 466 | "minY": 1, 467 | "maxX": -1, 468 | "maxY": 0 469 | }, 470 | "182375": { 471 | "features": [ 472 | { 473 | "geometry": [ 474 | [ 475 | 0.5258464813232422, 476 | 0.35120749473571783, 477 | 0.5258681774139404, 478 | 0.3512092828750611, 479 | 0.5258781909942627, 480 | 0.3512244224548339, 481 | 0.5258595943450928, 482 | 0.35123527050018305, 483 | 0.5258432626724243, 484 | 0.35122346878051747, 485 | 0.5258464813232422, 486 | 0.35120749473571783 487 | ] 488 | ], 489 | "type": 3, 490 | "tags": {} 491 | }, 492 | { 493 | "geometry": [ 494 | [ 495 | 0.52587890625, 496 | 0.3512396812438965, 497 | 0.5259014368057251, 498 | 0.35122668743133545, 499 | 0.5259066820144653, 500 | 0.3512476682662964, 501 | 0.5258966684341431, 502 | 0.3512579202651977, 503 | 0.5258777141571045, 504 | 0.3512529134750366, 505 | 0.52587890625, 506 | 0.3512396812438965 507 | ] 508 | ], 509 | "type": 3, 510 | "tags": {} 511 | }, 512 | { 513 | "geometry": [ 514 | [ 515 | 0.5259217023849487, 516 | 0.35123419761657715, 517 | 0.5259356498718262, 518 | 0.35123276710510265, 519 | 0.525936484336853, 520 | 0.35124897956848145, 521 | 0.5259230136871338, 522 | 0.35124826431274414, 523 | 0.5259217023849487, 524 | 0.35123419761657715 525 | ] 526 | ], 527 | "type": 3, 528 | "tags": {} 529 | } 530 | ], 531 | "numPoints": 18, 532 | "numSimplified": 17, 533 | "numFeatures": 3, 534 | "source": null, 535 | "x": 67, 536 | "y": 44, 537 | "z": 7, 538 | "transformed": false, 539 | "minX": 0.5258432626724243, 540 | "minY": 0.35120749473571783, 541 | "maxX": 0.525936484336853, 542 | "maxY": 0.3512579202651977 543 | }, 544 | "186439": { 545 | "features": [], 546 | "numPoints": 0, 547 | "numSimplified": 0, 548 | "numFeatures": 0, 549 | "source": [], 550 | "x": 66, 551 | "y": 45, 552 | "z": 7, 553 | "transformed": false, 554 | "minX": 2, 555 | "minY": 1, 556 | "maxX": -1, 557 | "maxY": 0 558 | }, 559 | "186471": { 560 | "features": [], 561 | "numPoints": 0, 562 | "numSimplified": 0, 563 | "numFeatures": 0, 564 | "source": [], 565 | "x": 67, 566 | "y": 45, 567 | "z": 7, 568 | "transformed": false, 569 | "minX": 2, 570 | "minY": 1, 571 | "maxX": -1, 572 | "maxY": 0 573 | }, 574 | "725192": { 575 | "features": [], 576 | "numPoints": 0, 577 | "numSimplified": 0, 578 | "numFeatures": 0, 579 | "source": [], 580 | "x": 134, 581 | "y": 88, 582 | "z": 8, 583 | "transformed": false, 584 | "minX": 2, 585 | "minY": 1, 586 | "maxX": -1, 587 | "maxY": 0 588 | }, 589 | "725224": { 590 | "features": [], 591 | "numPoints": 0, 592 | "numSimplified": 0, 593 | "numFeatures": 0, 594 | "source": [], 595 | "x": 135, 596 | "y": 88, 597 | "z": 8, 598 | "transformed": false, 599 | "minX": 2, 600 | "minY": 1, 601 | "maxX": -1, 602 | "maxY": 0 603 | }, 604 | "733384": { 605 | "features": [ 606 | { 607 | "geometry": [ 608 | [ 609 | 0.5258464813232422, 610 | 0.35120749473571783, 611 | 0.5258681774139404, 612 | 0.3512092828750611, 613 | 0.5258781909942627, 614 | 0.3512244224548339, 615 | 0.5258595943450928, 616 | 0.35123527050018305, 617 | 0.5258432626724243, 618 | 0.35122346878051747, 619 | 0.5258464813232422, 620 | 0.35120749473571783 621 | ] 622 | ], 623 | "type": 3, 624 | "tags": {} 625 | }, 626 | { 627 | "geometry": [ 628 | [ 629 | 0.52587890625, 630 | 0.3512396812438965, 631 | 0.5259014368057251, 632 | 0.35122668743133545, 633 | 0.5259066820144653, 634 | 0.3512476682662964, 635 | 0.5258966684341431, 636 | 0.3512579202651977, 637 | 0.5258777141571045, 638 | 0.3512529134750366, 639 | 0.52587890625, 640 | 0.3512396812438965 641 | ] 642 | ], 643 | "type": 3, 644 | "tags": {} 645 | }, 646 | { 647 | "geometry": [ 648 | [ 649 | 0.5259217023849487, 650 | 0.35123419761657715, 651 | 0.5259356498718262, 652 | 0.35123276710510265, 653 | 0.525936484336853, 654 | 0.35124897956848145, 655 | 0.5259230136871338, 656 | 0.35124826431274414, 657 | 0.5259175300598145, 658 | 0.3512394428253173, 659 | 0.5259217023849487, 660 | 0.35123419761657715 661 | ] 662 | ], 663 | "type": 3, 664 | "tags": {} 665 | } 666 | ], 667 | "numPoints": 18, 668 | "numSimplified": 18, 669 | "numFeatures": 3, 670 | "source": null, 671 | "x": 134, 672 | "y": 89, 673 | "z": 8, 674 | "transformed": false, 675 | "minX": 0.5258432626724243, 676 | "minY": 0.35120749473571783, 677 | "maxX": 0.525936484336853, 678 | "maxY": 0.3512579202651977 679 | }, 680 | "733416": { 681 | "features": [], 682 | "numPoints": 0, 683 | "numSimplified": 0, 684 | "numFeatures": 0, 685 | "source": [], 686 | "x": 135, 687 | "y": 89, 688 | "z": 8, 689 | "transformed": false, 690 | "minX": 2, 691 | "minY": 1, 692 | "maxX": -1, 693 | "maxY": 0 694 | }, 695 | "2924937": { 696 | "features": [], 697 | "numPoints": 0, 698 | "numSimplified": 0, 699 | "numFeatures": 0, 700 | "source": [], 701 | "x": 268, 702 | "y": 178, 703 | "z": 9, 704 | "transformed": false, 705 | "minX": 2, 706 | "minY": 1, 707 | "maxX": -1, 708 | "maxY": 0 709 | }, 710 | "2924969": { 711 | "features": [], 712 | "numPoints": 0, 713 | "numSimplified": 0, 714 | "numFeatures": 0, 715 | "source": [], 716 | "x": 269, 717 | "y": 178, 718 | "z": 9, 719 | "transformed": false, 720 | "minX": 2, 721 | "minY": 1, 722 | "maxX": -1, 723 | "maxY": 0 724 | }, 725 | "2941321": { 726 | "features": [], 727 | "numPoints": 0, 728 | "numSimplified": 0, 729 | "numFeatures": 0, 730 | "source": [], 731 | "x": 268, 732 | "y": 179, 733 | "z": 9, 734 | "transformed": false, 735 | "minX": 2, 736 | "minY": 1, 737 | "maxX": -1, 738 | "maxY": 0 739 | }, 740 | "2941353": { 741 | "features": [ 742 | { 743 | "geometry": [ 744 | [ 745 | 0.5258464813232422, 746 | 0.35120749473571783, 747 | 0.5258681774139404, 748 | 0.3512092828750611, 749 | 0.5258781909942627, 750 | 0.3512244224548339, 751 | 0.5258595943450928, 752 | 0.35123527050018305, 753 | 0.5258432626724243, 754 | 0.35122346878051747, 755 | 0.5258464813232422, 756 | 0.35120749473571783 757 | ] 758 | ], 759 | "type": 3, 760 | "tags": {} 761 | }, 762 | { 763 | "geometry": [ 764 | [ 765 | 0.52587890625, 766 | 0.3512396812438965, 767 | 0.5259014368057251, 768 | 0.35122668743133545, 769 | 0.5259066820144653, 770 | 0.3512476682662964, 771 | 0.5258966684341431, 772 | 0.3512579202651977, 773 | 0.5258777141571045, 774 | 0.3512529134750366, 775 | 0.52587890625, 776 | 0.3512396812438965 777 | ] 778 | ], 779 | "type": 3, 780 | "tags": {} 781 | }, 782 | { 783 | "geometry": [ 784 | [ 785 | 0.5259217023849487, 786 | 0.35123419761657715, 787 | 0.5259356498718262, 788 | 0.35123276710510265, 789 | 0.525936484336853, 790 | 0.35124897956848145, 791 | 0.5259230136871338, 792 | 0.35124826431274414, 793 | 0.5259175300598145, 794 | 0.3512394428253173, 795 | 0.5259217023849487, 796 | 0.35123419761657715 797 | ] 798 | ], 799 | "type": 3, 800 | "tags": {} 801 | } 802 | ], 803 | "numPoints": 18, 804 | "numSimplified": 18, 805 | "numFeatures": 3, 806 | "source": null, 807 | "x": 269, 808 | "y": 179, 809 | "z": 9, 810 | "transformed": false, 811 | "minX": 0.5258432626724243, 812 | "minY": 0.35120749473571783, 813 | "maxX": 0.525936484336853, 814 | "maxY": 0.3512579202651977 815 | }, 816 | "11748170": { 817 | "features": [], 818 | "numPoints": 0, 819 | "numSimplified": 0, 820 | "numFeatures": 0, 821 | "source": [], 822 | "x": 538, 823 | "y": 358, 824 | "z": 10, 825 | "transformed": false, 826 | "minX": 2, 827 | "minY": 1, 828 | "maxX": -1, 829 | "maxY": 0 830 | }, 831 | "11748202": { 832 | "features": [], 833 | "numPoints": 0, 834 | "numSimplified": 0, 835 | "numFeatures": 0, 836 | "source": [], 837 | "x": 539, 838 | "y": 358, 839 | "z": 10, 840 | "transformed": false, 841 | "minX": 2, 842 | "minY": 1, 843 | "maxX": -1, 844 | "maxY": 0 845 | }, 846 | "11780938": { 847 | "features": [ 848 | { 849 | "geometry": [ 850 | [ 851 | 0.5258464813232422, 852 | 0.35120749473571783, 853 | 0.5258681774139404, 854 | 0.3512092828750611, 855 | 0.5258781909942627, 856 | 0.3512244224548339, 857 | 0.5258595943450928, 858 | 0.35123527050018305, 859 | 0.5258432626724243, 860 | 0.35122346878051747, 861 | 0.5258464813232422, 862 | 0.35120749473571783 863 | ] 864 | ], 865 | "type": 3, 866 | "tags": {} 867 | }, 868 | { 869 | "geometry": [ 870 | [ 871 | 0.52587890625, 872 | 0.3512396812438965, 873 | 0.5259014368057251, 874 | 0.35122668743133545, 875 | 0.5259066820144653, 876 | 0.3512476682662964, 877 | 0.5258966684341431, 878 | 0.3512579202651977, 879 | 0.5258777141571045, 880 | 0.3512529134750366, 881 | 0.52587890625, 882 | 0.3512396812438965 883 | ] 884 | ], 885 | "type": 3, 886 | "tags": {} 887 | }, 888 | { 889 | "geometry": [ 890 | [ 891 | 0.5259217023849487, 892 | 0.35123419761657715, 893 | 0.5259356498718262, 894 | 0.35123276710510265, 895 | 0.525936484336853, 896 | 0.35124897956848145, 897 | 0.5259230136871338, 898 | 0.35124826431274414, 899 | 0.5259175300598145, 900 | 0.3512394428253173, 901 | 0.5259217023849487, 902 | 0.35123419761657715 903 | ] 904 | ], 905 | "type": 3, 906 | "tags": {} 907 | } 908 | ], 909 | "numPoints": 18, 910 | "numSimplified": 18, 911 | "numFeatures": 3, 912 | "source": null, 913 | "x": 538, 914 | "y": 359, 915 | "z": 10, 916 | "transformed": false, 917 | "minX": 0.5258432626724243, 918 | "minY": 0.35120749473571783, 919 | "maxX": 0.525936484336853, 920 | "maxY": 0.3512579202651977 921 | }, 922 | "11780970": { 923 | "features": [], 924 | "numPoints": 0, 925 | "numSimplified": 0, 926 | "numFeatures": 0, 927 | "source": [], 928 | "x": 539, 929 | "y": 359, 930 | "z": 10, 931 | "transformed": false, 932 | "minX": 2, 933 | "minY": 1, 934 | "maxX": -1, 935 | "maxY": 0 936 | }, 937 | "47089291": { 938 | "features": [], 939 | "numPoints": 0, 940 | "numSimplified": 0, 941 | "numFeatures": 0, 942 | "source": [], 943 | "x": 1076, 944 | "y": 718, 945 | "z": 11, 946 | "transformed": false, 947 | "minX": 2, 948 | "minY": 1, 949 | "maxX": -1, 950 | "maxY": 0 951 | }, 952 | "47089323": { 953 | "features": [], 954 | "numPoints": 0, 955 | "numSimplified": 0, 956 | "numFeatures": 0, 957 | "source": [], 958 | "x": 1077, 959 | "y": 718, 960 | "z": 11, 961 | "transformed": false, 962 | "minX": 2, 963 | "minY": 1, 964 | "maxX": -1, 965 | "maxY": 0 966 | }, 967 | "47154827": { 968 | "features": [ 969 | { 970 | "geometry": [ 971 | [ 972 | 0.5258464813232422, 973 | 0.35120749473571783, 974 | 0.5258681774139404, 975 | 0.3512092828750611, 976 | 0.5258781909942627, 977 | 0.3512244224548339, 978 | 0.5258595943450928, 979 | 0.35123527050018305, 980 | 0.5258432626724243, 981 | 0.35122346878051747, 982 | 0.5258464813232422, 983 | 0.35120749473571783 984 | ] 985 | ], 986 | "type": 3, 987 | "tags": {} 988 | }, 989 | { 990 | "geometry": [ 991 | [ 992 | 0.52587890625, 993 | 0.3512396812438965, 994 | 0.5258865356445312, 995 | 0.3512352812227118, 996 | 0.5258865356445312, 997 | 0.35125524367926253, 998 | 0.5258777141571045, 999 | 0.3512529134750366, 1000 | 0.52587890625, 1001 | 0.3512396812438965 1002 | ] 1003 | ], 1004 | "type": 3, 1005 | "tags": {} 1006 | } 1007 | ], 1008 | "numPoints": 11, 1009 | "numSimplified": 11, 1010 | "numFeatures": 2, 1011 | "source": [ 1012 | { 1013 | "id": null, 1014 | "type": "Polygon", 1015 | "geometry": [ 1016 | [ 1017 | 0.5258464813232422, 1018 | 0.35120749473571783, 1019 | 1, 1020 | 0.5258432626724243, 1021 | 0.35122346878051747, 1022 | 9.467687596985592e-11, 1023 | 0.5258595943450928, 1024 | 0.35123527050018305, 1025 | 3.3590011667557266e-10, 1026 | 0.5258781909942627, 1027 | 0.3512244224548339, 1028 | 1.2920509107005281e-9, 1029 | 0.5258681774139404, 1030 | 0.3512092828750611, 1031 | 7.464876891645897e-11, 1032 | 0.5258464813232422, 1033 | 0.35120749473571783, 1034 | 1 1035 | ] 1036 | ], 1037 | "tags": {}, 1038 | "minX": 0.5258432626724243, 1039 | "minY": 0.35120749473571783, 1040 | "maxX": 0.5258781909942627, 1041 | "maxY": 0.35123527050018305 1042 | }, 1043 | { 1044 | "id": null, 1045 | "type": "Polygon", 1046 | "geometry": [ 1047 | [ 1048 | 0.52587890625, 1049 | 0.3512396812438965, 1050 | 1, 1051 | 0.5258777141571045, 1052 | 0.3512529134750366, 1053 | 1.7020727116104832e-10, 1054 | 0.5258865356445312, 1055 | 0.35125524367926253, 1056 | 1, 1057 | 0.5258865356445312, 1058 | 0.3512352812227118, 1059 | 1, 1060 | 0.52587890625, 1061 | 0.3512396812438965, 1062 | 1 1063 | ] 1064 | ], 1065 | "tags": {}, 1066 | "minX": 0.5258777141571045, 1067 | "minY": 0.3512352812227118, 1068 | "maxX": 0.5258865356445312, 1069 | "maxY": 0.35125524367926253 1070 | } 1071 | ], 1072 | "x": 1076, 1073 | "y": 719, 1074 | "z": 11, 1075 | "transformed": false, 1076 | "minX": 0.5258432626724243, 1077 | "minY": 0.35120749473571783, 1078 | "maxX": 0.5258865356445312, 1079 | "maxY": 0.35125524367926253 1080 | }, 1081 | "47154859": { 1082 | "features": [ 1083 | { 1084 | "geometry": [ 1085 | [ 1086 | 0.5258712768554688, 1087 | 0.3512284557024637, 1088 | 0.5258712768554688, 1089 | 0.35121396893546697, 1090 | 0.5258781909942627, 1091 | 0.3512244224548339, 1092 | 0.5258712768554688, 1093 | 0.3512284557024637 1094 | ] 1095 | ], 1096 | "type": 3, 1097 | "tags": {} 1098 | }, 1099 | { 1100 | "geometry": [ 1101 | [ 1102 | 0.52587890625, 1103 | 0.3512396812438965, 1104 | 0.5259014368057251, 1105 | 0.35122668743133545, 1106 | 0.5259066820144653, 1107 | 0.3512476682662964, 1108 | 0.5258966684341431, 1109 | 0.3512579202651977, 1110 | 0.5258777141571045, 1111 | 0.3512529134750366, 1112 | 0.52587890625, 1113 | 0.3512396812438965 1114 | ] 1115 | ], 1116 | "type": 3, 1117 | "tags": {} 1118 | }, 1119 | { 1120 | "geometry": [ 1121 | [ 1122 | 0.5259217023849487, 1123 | 0.35123419761657715, 1124 | 0.5259356498718262, 1125 | 0.35123276710510265, 1126 | 0.525936484336853, 1127 | 0.35124897956848145, 1128 | 0.5259230136871338, 1129 | 0.35124826431274414, 1130 | 0.5259175300598145, 1131 | 0.3512394428253173, 1132 | 0.5259217023849487, 1133 | 0.35123419761657715 1134 | ] 1135 | ], 1136 | "type": 3, 1137 | "tags": {} 1138 | } 1139 | ], 1140 | "numPoints": 16, 1141 | "numSimplified": 16, 1142 | "numFeatures": 3, 1143 | "source": null, 1144 | "x": 1077, 1145 | "y": 719, 1146 | "z": 11, 1147 | "transformed": false, 1148 | "minX": 0.5258712768554688, 1149 | "minY": 0.35121396893546697, 1150 | "maxX": 0.525936484336853, 1151 | "maxY": 0.3512579202651977 1152 | }, 1153 | "188550476": { 1154 | "features": [ 1155 | { 1156 | "geometry": [ 1157 | [ 1158 | 0.5258750915527344, 1159 | 0.35122623046239204, 1160 | 0.5258750915527344, 1161 | 0.351219736394428, 1162 | 0.5258781909942627, 1163 | 0.3512244224548339, 1164 | 0.5258750915527344, 1165 | 0.35122623046239204 1166 | ] 1167 | ], 1168 | "type": 3, 1169 | "tags": {} 1170 | }, 1171 | { 1172 | "geometry": [ 1173 | [ 1174 | 0.52587890625, 1175 | 0.3512396812438965, 1176 | 0.5259014368057251, 1177 | 0.35122668743133545, 1178 | 0.5259066820144653, 1179 | 0.3512476682662964, 1180 | 0.5258966684341431, 1181 | 0.3512579202651977, 1182 | 0.5258777141571045, 1183 | 0.3512529134750366, 1184 | 0.52587890625, 1185 | 0.3512396812438965 1186 | ] 1187 | ], 1188 | "type": 3, 1189 | "tags": {} 1190 | }, 1191 | { 1192 | "geometry": [ 1193 | [ 1194 | 0.5259217023849487, 1195 | 0.35123419761657715, 1196 | 0.5259356498718262, 1197 | 0.35123276710510265, 1198 | 0.525936484336853, 1199 | 0.35124897956848145, 1200 | 0.5259230136871338, 1201 | 0.35124826431274414, 1202 | 0.5259175300598145, 1203 | 0.3512394428253173, 1204 | 0.5259217023849487, 1205 | 0.35123419761657715 1206 | ] 1207 | ], 1208 | "type": 3, 1209 | "tags": {} 1210 | } 1211 | ], 1212 | "numPoints": 16, 1213 | "numSimplified": 16, 1214 | "numFeatures": 3, 1215 | "source": null, 1216 | "x": 2154, 1217 | "y": 1438, 1218 | "z": 12, 1219 | "transformed": false, 1220 | "minX": 0.5258750915527344, 1221 | "minY": 0.351219736394428, 1222 | "maxX": 0.525936484336853, 1223 | "maxY": 0.3512579202651977 1224 | }, 1225 | "188550508": { 1226 | "features": [], 1227 | "numPoints": 0, 1228 | "numSimplified": 0, 1229 | "numFeatures": 0, 1230 | "source": [], 1231 | "x": 2155, 1232 | "y": 1438, 1233 | "z": 12, 1234 | "transformed": false, 1235 | "minX": 2, 1236 | "minY": 1, 1237 | "maxX": -1, 1238 | "maxY": 0 1239 | }, 1240 | "188681548": { 1241 | "features": [], 1242 | "numPoints": 0, 1243 | "numSimplified": 0, 1244 | "numFeatures": 0, 1245 | "source": [], 1246 | "x": 2154, 1247 | "y": 1439, 1248 | "z": 12, 1249 | "transformed": false, 1250 | "minX": 2, 1251 | "minY": 1, 1252 | "maxX": -1, 1253 | "maxY": 0 1254 | }, 1255 | "188681580": { 1256 | "features": [], 1257 | "numPoints": 0, 1258 | "numSimplified": 0, 1259 | "numFeatures": 0, 1260 | "source": [], 1261 | "x": 2155, 1262 | "y": 1439, 1263 | "z": 12, 1264 | "transformed": false, 1265 | "minX": 2, 1266 | "minY": 1, 1267 | "maxX": -1, 1268 | "maxY": 0 1269 | }, 1270 | "754064013": { 1271 | "features": [], 1272 | "numPoints": 0, 1273 | "numSimplified": 0, 1274 | "numFeatures": 0, 1275 | "source": null, 1276 | "x": 4308, 1277 | "y": 2876, 1278 | "z": 13, 1279 | "transformed": false, 1280 | "minX": 2, 1281 | "minY": 1, 1282 | "maxX": -1, 1283 | "maxY": 0 1284 | }, 1285 | "754064045": { 1286 | "features": [], 1287 | "numPoints": 0, 1288 | "numSimplified": 0, 1289 | "numFeatures": 0, 1290 | "source": [], 1291 | "x": 4309, 1292 | "y": 2876, 1293 | "z": 13, 1294 | "transformed": false, 1295 | "minX": 2, 1296 | "minY": 1, 1297 | "maxX": -1, 1298 | "maxY": 0 1299 | }, 1300 | "754326157": { 1301 | "features": [ 1302 | { 1303 | "geometry": [ 1304 | [ 1305 | 0.5258769989013672, 1306 | 0.35122511784235627, 1307 | 0.5258769989013672, 1308 | 0.35122262012390854, 1309 | 0.5258781909942627, 1310 | 0.3512244224548339, 1311 | 0.5258769989013672, 1312 | 0.35122511784235627 1313 | ] 1314 | ], 1315 | "type": 3, 1316 | "tags": {} 1317 | }, 1318 | { 1319 | "geometry": [ 1320 | [ 1321 | 0.52587890625, 1322 | 0.3512396812438965, 1323 | 0.5259014368057251, 1324 | 0.35122668743133545, 1325 | 0.5259066820144653, 1326 | 0.3512476682662964, 1327 | 0.5258966684341431, 1328 | 0.3512579202651977, 1329 | 0.5258777141571045, 1330 | 0.3512529134750366, 1331 | 0.52587890625, 1332 | 0.3512396812438965 1333 | ] 1334 | ], 1335 | "type": 3, 1336 | "tags": {} 1337 | }, 1338 | { 1339 | "geometry": [ 1340 | [ 1341 | 0.5259217023849487, 1342 | 0.35123419761657715, 1343 | 0.5259356498718262, 1344 | 0.35123276710510265, 1345 | 0.525936484336853, 1346 | 0.35124897956848145, 1347 | 0.5259230136871338, 1348 | 0.35124826431274414, 1349 | 0.5259175300598145, 1350 | 0.3512394428253173, 1351 | 0.5259217023849487, 1352 | 0.35123419761657715 1353 | ] 1354 | ], 1355 | "type": 3, 1356 | "tags": {} 1357 | } 1358 | ], 1359 | "numPoints": 16, 1360 | "numSimplified": 16, 1361 | "numFeatures": 3, 1362 | "source": [ 1363 | { 1364 | "id": null, 1365 | "type": "Polygon", 1366 | "geometry": [ 1367 | [ 1368 | 0.5258769989013672, 1369 | 0.35122511784235627, 1370 | 1, 1371 | 0.5258781909942627, 1372 | 0.3512244224548339, 1373 | 1.2920509107005281e-9, 1374 | 0.5258769989013672, 1375 | 0.35122262012390854, 1376 | 1, 1377 | 0.5258769989013672, 1378 | 0.35122511784235627, 1379 | 1 1380 | ] 1381 | ], 1382 | "tags": {}, 1383 | "minX": 0.5258769989013672, 1384 | "minY": 0.35122262012390854, 1385 | "maxX": 0.5258781909942627, 1386 | "maxY": 0.35122511784235627 1387 | }, 1388 | { 1389 | "id": null, 1390 | "type": "Polygon", 1391 | "geometry": [ 1392 | [ 1393 | 0.52587890625, 1394 | 0.3512396812438965, 1395 | 1, 1396 | 0.5258777141571045, 1397 | 0.3512529134750366, 1398 | 1.7020727116104832e-10, 1399 | 0.5258966684341431, 1400 | 0.3512579202651977, 1401 | 6.89533003614437e-11, 1402 | 0.5259066820144653, 1403 | 0.3512476682662964, 1404 | 8.352856184501434e-10, 1405 | 0.5259014368057251, 1406 | 0.35122668743133545, 1407 | 3.5022162825019896e-10, 1408 | 0.52587890625, 1409 | 0.3512396812438965, 1410 | 1 1411 | ] 1412 | ], 1413 | "tags": {}, 1414 | "minX": 0.5258777141571045, 1415 | "minY": 0.35122668743133545, 1416 | "maxX": 0.5259066820144653, 1417 | "maxY": 0.3512579202651977 1418 | }, 1419 | { 1420 | "id": null, 1421 | "type": "Polygon", 1422 | "geometry": [ 1423 | [ 1424 | 0.5259217023849487, 1425 | 0.35123419761657715, 1426 | 1, 1427 | 0.5259175300598145, 1428 | 0.3512394428253173, 1429 | 2.154039367682159e-11, 1430 | 0.5259230136871338, 1431 | 0.35124826431274414, 1432 | 8.135003781717387e-11, 1433 | 0.525936484336853, 1434 | 0.35124897956848145, 1435 | 4.37012204201892e-10, 1436 | 0.5259356498718262, 1437 | 0.35123276710510265, 1438 | 1.1824141665698462e-10, 1439 | 0.5259217023849487, 1440 | 0.35123419761657715, 1441 | 1 1442 | ] 1443 | ], 1444 | "tags": {}, 1445 | "minX": 0.5259175300598145, 1446 | "minY": 0.35123276710510265, 1447 | "maxX": 0.525936484336853, 1448 | "maxY": 0.35124897956848145 1449 | } 1450 | ], 1451 | "x": 4308, 1452 | "y": 2877, 1453 | "z": 13, 1454 | "transformed": false, 1455 | "minX": 0.5258769989013672, 1456 | "minY": 0.35122262012390854, 1457 | "maxX": 0.525936484336853, 1458 | "maxY": 0.3512579202651977 1459 | }, 1460 | "754326189": { 1461 | "features": [], 1462 | "numPoints": 0, 1463 | "numSimplified": 0, 1464 | "numFeatures": 0, 1465 | "source": [], 1466 | "x": 4309, 1467 | "y": 2877, 1468 | "z": 13, 1469 | "transformed": false, 1470 | "minX": 2, 1471 | "minY": 1, 1472 | "maxX": -1, 1473 | "maxY": 0 1474 | } 1475 | } 1476 | -------------------------------------------------------------------------------- /tests/fixtures/us-states-z7-37-48.json: -------------------------------------------------------------------------------- 1 | [{"geometry":[[[3415,-64],[3323,-15],[3300,-64],[3415,-64]]],"type":3,"tags":{"name":"Connecticut","density":739.1},"id":"09"},{"geometry":[[[762,2248],[627,2476],[475,2600],[507,2900],[722,3179],[778,3642],[1089,4124],[1233,4144],[1234,4160],[321,4160],[220,2403],[467,2196],[762,2248]]],"type":3,"tags":{"name":"Delaware","density":464.3},"id":"10"},{"geometry":[[[-64,2403],[220,2403],[321,4160],[-64,4160],[-64,2829],[-51,2714],[-64,2717],[-64,2403]]],"type":3,"tags":{"name":"Maryland","density":596.3},"id":"24"},{"geometry":[[[2914,-64],[2964,-36],[2788,523],[2549,649],[2421,943],[2820,1090],[2852,1310],[2677,2331],[2222,3086],[1927,3303],[1664,3775],[1528,3467],[1105,3313],[587,2900],[547,2580],[627,2476],[762,2248],[1153,2092],[1177,1946],[1624,1634],[1696,1467],[1281,1080],[1265,838],[1081,775],[1065,555],[1289,218],[1169,17],[1243,-64],[2914,-64]]],"type":3,"tags":{"name":"New Jersey","density":1189},"id":"34"},{"geometry":[[[3300,-64],[3323,-15],[3945,144],[4072,28],[4160,28],[4160,594],[3929,681],[3458,765],[3147,744],[2916,838],[2788,523],[2964,-36],[2914,-64],[3300,-64]]],"type":3,"tags":{"name":"New York","density":412.3},"id":"36"},{"geometry":[[[1243,-64],[1169,17],[1289,218],[1065,555],[1081,775],[1265,838],[1281,1080],[1696,1467],[1624,1634],[1177,1946],[1153,2092],[762,2248],[467,2196],[220,2403],[-64,2403],[-64,-64],[1243,-64]]],"type":3,"tags":{"name":"Pennsylvania","density":284.3},"id":"42"}] 2 | -------------------------------------------------------------------------------- /tests/test_clip.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from geojson2vt.clip import clip, Slice 4 | 5 | geom1 = [0., 0., 0., 50., 0., 0., 50., 10., 0., 20., 10., 0., 20., 20., 0., 30., 20., 0., 30., 30., 6 | 0., 50., 30., 0., 50., 40., 0., 25., 40., 0., 25., 50., 0., 0., 50., 0., 0., 60., 0., 25., 60., 0.] 7 | geom2 = [0, 0, 0, 50, 0, 0, 50, 10, 0, 0, 10, 0] 8 | 9 | 10 | def test_clips_polylines(): 11 | clipped = clip([ 12 | {'geometry': geom1, 'type': 'LineString', 'tags': 1, 13 | 'minX': 0, 'minY': 0, 'maxX': 50, 'maxY': 60}, 14 | {'geometry': geom2, 'type': 'LineString', 'tags': 2, 15 | 'minX': 0, 'minY': 0, 'maxX': 50, 'maxY': 10} 16 | ], 1, 10, 40, 0, float('-inf'), float('inf'), {}) 17 | 18 | expected = [ 19 | { 20 | 'id': None, 21 | 'type': 'MultiLineString', 22 | 'geometry': [ 23 | [10, 0, 1, 40, 0, 1], 24 | [40, 10, 1, 20, 10, 0, 20, 20, 0, 30, 20, 0, 30, 30, 0, 40, 30, 1], 25 | [40, 40, 1, 25, 40, 0, 25, 50, 0, 10, 50, 1], 26 | [10, 60, 1, 25, 60, 0] 27 | ], 28 | 'tags': 1, 29 | 'minX': 10, 30 | 'minY': 0, 31 | 'maxX': 40, 32 | 'maxY': 60 33 | }, 34 | { 35 | 'id': None, 36 | 'type': 37 | 'MultiLineString', 38 | 'geometry': [ 39 | [10, 0, 1, 40, 0, 1], 40 | [40, 10, 1, 10, 10, 1] 41 | ], 42 | 'tags': 2, 43 | 'minX': 10, 44 | 'minY': 0, 45 | 'maxX': 40, 46 | 'maxY': 10 47 | } 48 | ] 49 | assert clipped == expected 50 | 51 | 52 | def test_clips_line_metrics_on(): 53 | geom = Slice(geom1) 54 | geom.size = 0 55 | for i in range(0, len(geom)-3, 3): 56 | dx = geom[i + 3] - geom[i] 57 | dy = geom[i + 4] - geom[i + 1] 58 | geom.size += math.sqrt(dx * dx + dy * dy) 59 | geom.start = 0 60 | geom.end = geom.size 61 | 62 | clipped = clip([{'geometry': geom, 'type': 'LineString', 'minX': 0, 'minY': 0, 'maxX': 50, 'maxY': 60}], 63 | 1, 10, 40, 0, float('-inf'), float('inf'), {'lineMetrics': True}) 64 | 65 | expected = [[10.0, 40.0], [70.0, 130.0], [160.0, 200.0], [230.0, 245.0]] 66 | for c, e in zip(clipped, expected): 67 | assert [c.get('geometry').start, c.get('geometry').end] == e 68 | 69 | 70 | def closed(geometry): 71 | geometry += geometry[0:3] 72 | return geometry 73 | 74 | 75 | def test_clips_poygons(): 76 | clipped = clip([ 77 | {'geometry': closed(geom1), 'type': 'Polygon', 'tags': 1, 78 | 'minX': 0, 'minY': 0, 'maxX': 50, 'maxY': 60}, 79 | {'geometry': closed(geom2), 'type': 'Polygon', 'tags': 2, 80 | 'minX': 0, 'minY': 0, 'maxX': 50, 'maxY': 10} 81 | ], 1, 10, 40, 0, float('-inf'), float('inf'), {}) 82 | 83 | expected = [ 84 | {'id': None, 'type': 'Polygon', 'geometry': [[10., 0., 1., 40., 0., 1., 40., 10., 1., 20., 10., 0., 20., 20., 0., 30., 20., 0., 30., 30., 0., 40., 30., 1., 40., 40., 85 | 1., 25., 40., 0., 25., 50., 0., 10., 50., 1., 10., 60., 1., 25., 60., 0., 10., 24., 1., 10., 0., 1.]], 'tags': 1, 'minX': 10, 'minY': 0, 'maxX': 40, 'maxY': 60}, 86 | {'id': None, 'type': 'Polygon', 'geometry': [ 87 | [10., 0., 1., 40., 0., 1., 40., 10., 1., 10., 10., 1., 10., 0., 1.]], 'tags': 2, 'minX': 10, 'minY': 0, 'maxX': 40, 'maxY': 10} 88 | ] 89 | for c, e in zip(clipped, expected): 90 | assert c.get('geometry') == e.get('geometry') 91 | 92 | def test_clips_points(): 93 | clipped = clip([ 94 | {'geometry': geom1, 'type': 'MultiPoint', 'tags': 1, 'minX': 0, 'minY': 0, 'maxX': 50, 'maxY': 60}, 95 | {'geometry': geom2, 'type': 'MultiPoint', 'tags': 2, 'minX': 0, 'minY': 0, 'maxX': 50, 'maxY': 10} 96 | ], 1, 10, 40, 0, float('-inf'), float('inf'), {}) 97 | 98 | assert clipped ==[{'id': None, 'type': 'MultiPoint', 'geometry': [20,10,0,20,20,0,30,20,0,30,30,0,25,40,0,25,50,0,25,60,0], 'tags': 1, 'minX': 20, 'minY': 10, 'maxX': 30, 'maxY': 60}] 99 | -------------------------------------------------------------------------------- /tests/test_full.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from geojson2vt.geojson2vt import geojson2vt 6 | from geojson2vt.utils import current_dir, get_json 7 | 8 | 9 | @pytest.mark.parametrize("input_file,expected_file,options", [ 10 | # ('us-states.json', 'us-states-tiles.json', 11 | # {'indexMaxZoom': 7, 'indexMaxPoints': 200}), 12 | # ('dateline.json', 'dateline-tiles.json', 13 | # {'indexMaxZoom': 0, 'indexMaxPoints': 10000}), 14 | # ('dateline.json', 'dateline-metrics-tiles.json', 15 | # {'indexMaxZoom': 0, 'indexMaxPoints': 10000, 'lineMetrics': True}), 16 | # ('feature.json', 'feature-tiles.json', 17 | # {'indexMaxZoom': 0, 'indexMaxPoints': 10000}), 18 | # ('collection.json', 'collection-tiles.json', 19 | # {'indexMaxZoom': 0, 'indexMaxPoints': 10000}), 20 | ('single-geom.json', 'single-geom-tiles.json', 21 | {'indexMaxZoom': 0, 'indexMaxPoints': 10000}), 22 | # ('ids.json', 'ids-promote-id-tiles.json', 23 | # {'indexMaxZoom': 0, 'promoteId': 'prop0', 'indexMaxPoints': 10000}), 24 | # ('ids.json', 'ids-generate-id-tiles.json', 25 | # {'indexMaxZoom': 0, 'generateId': True, 'indexMaxPoints': 10000}) 26 | ]) 27 | def test_tiles(input_file, expected_file, options): 28 | cur_dir = current_dir(__file__) 29 | file_path = os.path.join(cur_dir, f'fixtures/{expected_file}') 30 | expected = get_json(file_path) 31 | 32 | file_path = os.path.join(cur_dir, f'fixtures/{input_file}') 33 | input_data = get_json(file_path) 34 | 35 | tiles = gen_tiles(input_data, options) 36 | for t, j in zip(tiles.items(), expected.items()): 37 | assert t == j 38 | 39 | 40 | def test_empty_gejson(): 41 | cur_dir = current_dir(__file__) 42 | file_path = os.path.join(cur_dir, 'fixtures/empty.json') 43 | expected = get_json(file_path) 44 | result = gen_tiles(expected, {}) 45 | 46 | assert {} == result 47 | 48 | 49 | def test_none_geometry(): 50 | cur_dir = current_dir(__file__) 51 | file_path = os.path.join(cur_dir, 'fixtures/feature-null-geometry.json') 52 | expected = get_json(file_path) 53 | 54 | assert {} == gen_tiles(expected, {}) 55 | 56 | 57 | def test_invalid_geo_json(): 58 | with pytest.raises(Exception): 59 | gen_tiles({'type': 'Pologon'}, {}) 60 | 61 | 62 | def gen_tiles(data, options): 63 | geoJsonVt = geojson2vt(data, options) 64 | 65 | output = {} 66 | 67 | for id_ in geoJsonVt.tiles: 68 | tile = geoJsonVt.tiles[id_] 69 | z = tile.get('z') 70 | output[f'z{z}-{tile.get("x")}-{tile.get("y")}'] = geoJsonVt.get_tile( 71 | z, tile.get('x'), tile.get('y')).get('features') 72 | return output 73 | 74 | 75 | if __name__ == "__main__": 76 | test_tiles('dateline.json', 'dateline-tiles.json', 77 | {'indexMaxZoom': 0, 'indexMaxPoints': 10000}) 78 | -------------------------------------------------------------------------------- /tests/test_get_tile.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from geojson2vt.geojson2vt import geojson2vt 4 | from geojson2vt.utils import current_dir, get_json 5 | 6 | square = [{ 7 | 'geometry': [[[-64., 4160.], [-64., -64.], [4160., -64.], [4160., 4160.], [-64., 4160.]]], 8 | 'type': 3, 9 | 'tags': {'name': 'Pennsylvania', 'density': 284.3}, 10 | 'id': '42' 11 | }] 12 | 13 | 14 | def test_get_tile(): 15 | cur_dir = current_dir(__file__) 16 | data_path = os.path.join(cur_dir, f'fixtures/us-states.json') 17 | expected_path = os.path.join(cur_dir, f'fixtures/us-states-z7-37-48.json') 18 | 19 | data = get_json(data_path) 20 | geojson_vt = geojson2vt(data, {}) 21 | 22 | features = geojson_vt.get_tile('7', '37', '48').get( 23 | 'features') 24 | expected = get_json(expected_path) 25 | assert features == expected 26 | assert geojson_vt.get_tile(9, 148, 192).get('features') == square 27 | assert geojson_vt.get_tile(11, 800, 400) == None 28 | assert geojson_vt.get_tile(-5, 123.25, 400.25) == None 29 | assert geojson_vt.get_tile(25, 200, 200) == None 30 | assert geojson_vt.total == 37 31 | 32 | 33 | def test_get_tile_unbuffered(): 34 | geojson_vt = geojson2vt({ 35 | 'type': 'LineString', 36 | 'coordinates': [[0., 90.], [0., -90.]] 37 | }, { 38 | 'buffer': 0 39 | }) 40 | assert geojson_vt.get_tile(2, 1, 1) == None 41 | assert geojson_vt.get_tile(2, 2, 1).get('features') == [ 42 | {'geometry': [[[0., 0.], [0., 4096.]]], 'type': 2, 'tags': None}] 43 | 44 | 45 | def test_get_tile_unbuffered_edges(): 46 | geojson_vt = geojson2vt({ 47 | 'type': 'LineString', 48 | 'coordinates': [[-90.0, 66.51326044311188], [90.0, 66.51326044311188]] 49 | }, { 50 | 'buffer': 0 51 | }) 52 | assert geojson_vt.get_tile(2, 1, 0).get('features') == [{'geometry': [ 53 | [[0.0, 4096.0], [4096.0, 4096.0]]], 'type': 2, 'tags': None}] 54 | assert geojson_vt.get_tile(2, 1, 1).get('features') == [] 55 | 56 | 57 | def test_get_tile_polygon_clipping(): 58 | geojson_vt = geojson2vt({ 59 | 'type': 'Polygon', 60 | 'coordinates': [[ 61 | [42.1875, 57.32652122521708], 62 | [47.8125, 57.32652122521708], 63 | [47.8125, 54.16243396806781], 64 | [42.1875, 54.16243396806781], 65 | [42.1875, 57.32652122521708] 66 | ]] 67 | }, { 68 | 'buffer': 1024 69 | }) 70 | assert geojson_vt.get_tile(5, 19, 9).get('features') == [{ 71 | 'geometry': [[[3072., 3072.], [5120., 3072.], [5120., 5120.], [3072., 5120.], [3072., 3072.]]], 72 | 'type': 3, 73 | 'tags': None 74 | }] 75 | 76 | 77 | if __name__ == "__main__": 78 | test_get_tile() 79 | -------------------------------------------------------------------------------- /tests/test_simplify.py: -------------------------------------------------------------------------------- 1 | from geojson2vt.simplify import simplify 2 | 3 | points = [ 4 | [0.22455,0.25015],[0.22691,0.24419],[0.23331,0.24145],[0.23498,0.23606], 5 | [0.24421,0.23276],[0.26259,0.21531],[0.26776,0.21381],[0.27357,0.20184], 6 | [0.27312,0.19216],[0.27762,0.18903],[0.28036,0.18141],[0.28651,0.17774], 7 | [0.29241,0.15937],[0.29691,0.15564],[0.31495,0.15137],[0.31975,0.14516], 8 | [0.33033,0.13757],[0.34148,0.13996],[0.36998,0.13789],[0.38739,0.14251], 9 | [0.39128,0.13939],[0.40952,0.14114],[0.41482,0.13975],[0.42772,0.12730], 10 | [0.43960,0.11974],[0.47493,0.10787],[0.48651,0.10675],[0.48920,0.10945], 11 | [0.49379,0.10863],[0.50474,0.11966],[0.51296,0.12235],[0.51863,0.12089], 12 | [0.52409,0.12688],[0.52957,0.12786],[0.53421,0.14093],[0.53927,0.14724], 13 | [0.56769,0.14891],[0.57525,0.15726],[0.58062,0.15815],[0.60153,0.15685], 14 | [0.61774,0.15986],[0.62200,0.16704],[0.62955,0.19460],[0.63890,0.19561], 15 | [0.64126,0.20081],[0.65177,0.20456],[0.67155,0.22255],[0.68368,0.21745], 16 | [0.69525,0.21915],[0.70064,0.21798],[0.70312,0.21436],[0.71226,0.21587], 17 | [0.72149,0.21281],[0.72781,0.21336],[0.72998,0.20873],[0.73532,0.20820], 18 | [0.73994,0.20477],[0.76998,0.20842],[0.77960,0.21687],[0.78420,0.21816], 19 | [0.80024,0.21462],[0.81053,0.21973],[0.81719,0.22682],[0.82077,0.23617], 20 | [0.82723,0.23616],[0.82989,0.23989],[0.85100,0.24894],[0.85988,0.25549], 21 | [0.86521,0.26853],[0.85795,0.28030],[0.86548,0.29145],[0.86681,0.29866], 22 | [0.86468,0.30271],[0.86779,0.30617],[0.85987,0.31137],[0.86008,0.31435], 23 | [0.85829,0.31494],[0.85810,0.32760],[0.85454,0.33540],[0.86092,0.34300], 24 | [0.85643,0.35015],[0.85142,0.35296],[0.84984,0.35959],[0.85456,0.36553], 25 | [0.84974,0.37038],[0.84409,0.37189],[0.84475,0.38044],[0.84152,0.38367], 26 | [0.83957,0.39040],[0.84559,0.39905],[0.84840,0.40755],[0.84371,0.41130], 27 | [0.84409,0.41988],[0.83951,0.43276],[0.84133,0.44104],[0.84762,0.44922], 28 | [0.84716,0.45844],[0.85138,0.46279],[0.85397,0.47115],[0.86636,0.48077] 29 | ] 30 | 31 | simplified = [ 32 | [0.22455,0.25015],[0.26776,0.21381],[0.29691,0.15564],[0.33033,0.13757], 33 | [0.40952,0.14114],[0.4396,0.11974],[0.48651,0.10675],[0.52957,0.12786], 34 | [0.53927,0.14724],[0.56769,0.14891],[0.61774,0.15986],[0.62955,0.1946], 35 | [0.67155,0.22255],[0.72781,0.21336],[0.73994,0.20477],[0.76998,0.20842], 36 | [0.7842,0.21816],[0.80024,0.21462],[0.82077,0.23617],[0.85988,0.25549], 37 | [0.86521,0.26853],[0.85795,0.2803],[0.86779,0.30617],[0.85829,0.31494], 38 | [0.85454,0.3354],[0.86092,0.343],[0.84984,0.35959],[0.85456,0.36553], 39 | [0.84409,0.37189],[0.83957,0.3904],[0.8484,0.40755],[0.83951,0.43276], 40 | [0.85397,0.47115],[0.86636,0.48077] 41 | ] 42 | 43 | def test_simplifies_points(): 44 | coords = [] 45 | for i in range(len(points)): 46 | coords.append(points[i][0]) 47 | coords.append(points[i][1]) 48 | coords.append(0) 49 | 50 | coords[2] = 1 51 | coords[len(coords) - 1] = 1 52 | simplify(coords, 0, len(coords) - 3, 0.001 * 0.001) 53 | 54 | result = [] 55 | for i in range(0, len(points), 3): 56 | if coords[i + 2] > 0.005 * 0.005: 57 | result.append([coords[i], coords[i + 1]]) 58 | # I assume the difference is related to floating point precision 59 | assert len(result) == 8 # len(simplified) 60 | # assert result == simplified 61 | 62 | def test_no_call_stack_error(): 63 | coords = [] 64 | for i in range(1400): 65 | coords.append([0.0, 0.0]) 66 | coords.append([1.0, 0.0]) 67 | coords.append([1.0, 1.0]) 68 | coords.append([0.0, 1.0]) 69 | 70 | simplify(coords, 0) 71 | assert True -------------------------------------------------------------------------------- /tests/test_small.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from geojson2vt.geojson2vt import geojson2vt 4 | from geojson2vt.utils import current_dir, get_json 5 | 6 | 7 | def test_small(): 8 | cur_dir = current_dir(__file__) 9 | data_path = os.path.join(cur_dir, f'fixtures/small.json') 10 | file_path = os.path.join(cur_dir, f'fixtures/small_result.json') 11 | 12 | data = get_json(data_path) 13 | expected = get_json(file_path) 14 | 15 | geojson_vt = geojson2vt(data, {}) 16 | geojson_vt.get_tile(14, 8617, 5752) 17 | 18 | geojson_vt_keys = sorted([str(k) for k in geojson_vt.tiles.keys()]) 19 | expected_keys = sorted([str(k) for k in expected.keys()]) 20 | 21 | assert geojson_vt_keys == expected_keys 22 | 23 | 24 | def test_features(): 25 | cur_dir = current_dir(__file__) 26 | data_path = os.path.join(cur_dir, f'fixtures/us-states.json') 27 | file_path = os.path.join(cur_dir, f'fixtures/feature_result.json') 28 | 29 | data = get_json(data_path) 30 | expected = get_json(file_path) 31 | 32 | geojson_vt = geojson2vt(data, {}) 33 | features = geojson_vt.get_tile(7, 37, 48).get('features') 34 | 35 | feature_ids = [f.get('id') for f in features] 36 | expected_feature_ids = [f.get('id') for f in expected] 37 | 38 | assert feature_ids == expected_feature_ids 39 | assert features == expected 40 | 41 | 42 | if __name__ == "__main__": 43 | test_features() 44 | -------------------------------------------------------------------------------- /tests/test_vt2geojson.py: -------------------------------------------------------------------------------- 1 | import os 2 | from geojson2vt.utils import current_dir, get_json 3 | from geojson2vt.geojson2vt import geojson2vt 4 | from geojson2vt.vt2geojson import vt2geojson 5 | 6 | 7 | def test_vt2geojson_line_string(): 8 | cur_dir = current_dir(__file__) 9 | data_path = os.path.join(cur_dir, f'fixtures/linestring.geojson') 10 | geojson = get_json(data_path) 11 | expected = { 12 | "type": "FeatureCollection", 13 | "features": [ 14 | {"type": "Feature", 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [9.333594851195812, 47.11501442877534], 19 | [9.33286428451538, 47.11409674087989] 20 | ] 21 | }, 22 | "properties": {} 23 | } 24 | ] 25 | } 26 | 27 | z, x, y = 18, 137868, 92080 28 | tile_index = geojson2vt( 29 | geojson, {'maxZoom': z, 'indexMaxZoom': z, 'indexMaxPoints': 0}) 30 | 31 | vt_tile = tile_index.get_tile(z, x, y) 32 | 33 | result = vt2geojson(vt_tile) 34 | 35 | assert result == expected 36 | 37 | def test_vt2geojson_multi(): 38 | cur_dir = current_dir(__file__) 39 | data_path = os.path.join(cur_dir, f'fixtures/multi.geojson') 40 | geojson = get_json(data_path) 41 | x, y, z = 2153, 1438, 12 42 | expected = {'type': 'FeatureCollection', 'features': [{'type': 'Feature', 'geometry': {'type': 'Polygon', 'coordinates': [[[9.31065559387207, 47.12633021376186], [9.303703308105469, 47.12142456895782], [9.317779541015625, 47.11764282567128], [9.317779541015625, 47.12698718541262], [9.31065559387207, 47.12633021376186]]]}, 'properties': {}}, {'type': 'Feature', 'geometry': {'type': 'Point', 'coordinates': [9.303016662597656, 47.12673899707599]}, 'properties': {}}]} 43 | 44 | tile_index = geojson2vt( 45 | geojson, {'maxZoom': z, 'indexMaxZoom': z, 'indexMaxPoints': 0}) 46 | vt_tile = tile_index.get_tile(z, x, y) 47 | 48 | result = vt2geojson(vt_tile) 49 | 50 | assert result == expected --------------------------------------------------------------------------------