├── setup.cfg ├── vtzero ├── version.py ├── __init__.py ├── cvtzero.pxd └── tile.pyx ├── .gitignore ├── .flake8 ├── MANIFEST.in ├── .github ├── codecov.yml └── workflows │ └── ci.yml ├── Makefile ├── .gitmodules ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── pyproject.toml ├── example └── __init__.py ├── README.md ├── setup.py └── tests ├── test_fixtures.py └── test_tile.py /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | version = attr: vtzero.version.__version__ 3 | -------------------------------------------------------------------------------- /vtzero/version.py: -------------------------------------------------------------------------------- 1 | """vtzero version.""" 2 | 3 | __version__ = "0.0.1b5" 4 | -------------------------------------------------------------------------------- /vtzero/__init__.py: -------------------------------------------------------------------------------- 1 | """vtzero: a python wrapper of https://github.com/mapbox/vtzero.""" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.egg-info 3 | build/ 4 | *.cpp 5 | *.so 6 | .cache 7 | 8 | .tox/ 9 | dist/ 10 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501,W503,E203 3 | exclude = .git,__pycache__,docs/source/conf.py,old,build,dist 4 | max-line-length = 90 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include vtzero/*.pyx vtzero/*.pxd 2 | include vendor/protozero/include/protozero/*.hpp 3 | include vendor/vtzero/include/vtzero/*.hpp 4 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 5 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | git submodule update --init 3 | 4 | compile: 5 | python setup.py build_ext --inplace 6 | 7 | test: 8 | pip install . 9 | py.test -v 10 | 11 | 12 | release: init compile test 13 | rm -rf dist/ build/ *.egg-info 14 | python setup.py sdist upload 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/vtzero"] 2 | path = vendor/vtzero 3 | url = https://github.com/mapbox/vtzero 4 | [submodule "vendor/protozero"] 5 | path = vendor/protozero 6 | url = https://github.com/mapbox/protozero 7 | [submodule "vendor/mvt-fixtures"] 8 | path = vendor/mvt-fixtures 9 | url = https://github.com/mapbox/mvt-fixtures 10 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/PyCQA/isort 3 | rev: 5.13.2 4 | hooks: 5 | - id: isort 6 | language_version: python 7 | 8 | - repo: https://github.com/astral-sh/ruff-pre-commit 9 | rev: v0.3.5 10 | hooks: 11 | - id: ruff 12 | args: ["--fix"] 13 | - id: ruff-format 14 | 15 | - repo: https://github.com/pre-commit/mirrors-mypy 16 | rev: v1.9.0 17 | hooks: 18 | - id: mypy 19 | language_version: python 20 | # No reason to run if only tests have changed. They intentionally break typing. 21 | exclude: tests/.* 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 2 | 3 | ### 0.0.1b5 - 2025-01-20 4 | 5 | - add python 3.13 support 6 | 7 | ### 0.0.1b4 - 2022-10-25 8 | 9 | - add python 3.12 support 10 | - remove support for python <3.8 11 | 12 | ### 0.0.1b3 - 2022-10-25 13 | 14 | - add python 3.9, 3.10, 3.11 support 15 | - add python wheels 16 | - update vendor submodules 17 | 18 | ### 0.0.1b2 - 2021-06-16 19 | 20 | - add pyproject.toml to follow PEP 518 compliance and install Cython before building (Author @Plantain, https://github.com/tilery/python-vtzero/pull/14) 21 | - update minimal Cython version to `>=0.29.23` (https://github.com/tilery/python-vtzero/pull/16) 22 | - remove `pkg_resources` to increase performance when loading the library (https://github.com/tilery/python-vtzero/pull/18) 23 | 24 | 25 | ### 0.0.1b1 - 2019-04-17 26 | 27 | - Initial release. 28 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=40.8.0", "wheel", "Cython>=0.29.23"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.isort] 6 | profile = "black" 7 | known_first_party = ["vtzero"] 8 | default_section = "THIRDPARTY" 9 | 10 | [tool.mypy] 11 | no_strict_optional = "True" 12 | 13 | [tool.ruff] 14 | line-length = 90 15 | 16 | [tool.ruff.lint] 17 | select = [ 18 | "D1", # pydocstyle errors 19 | "E", # pycodestyle errors 20 | "W", # pycodestyle warnings 21 | "F", # flake8 22 | "C", # flake8-comprehensions 23 | "B", # flake8-bugbear 24 | ] 25 | ignore = [ 26 | "E501", # line too long, handled by black 27 | "B008", # do not perform function calls in argument defaults 28 | "B905", # ignore zip() without an explicit strict= parameter, only support with python >3.10 29 | "B028", 30 | ] 31 | -------------------------------------------------------------------------------- /example/__init__.py: -------------------------------------------------------------------------------- 1 | """Example of python-vtzero usage.""" 2 | 3 | from vtzero.tile import Layer, Linestring, Point, Polygon, Tile, VectorTile 4 | 5 | # Create MVT 6 | tile = Tile() 7 | 8 | # Add a layer 9 | layer = Layer(tile, b"my_layer") 10 | 11 | # Add a point 12 | feature = Point(layer) 13 | feature.add_points(1) 14 | feature.set_point(10, 10) 15 | feature.add_property(b"foo", b"bar") 16 | feature.add_property(b"x", b"y") 17 | feature.commit() 18 | 19 | # Add a polygon 20 | feature = Polygon(layer) 21 | feature.add_ring(5) 22 | feature.set_point(0, 0) 23 | feature.set_point(1, 0) 24 | feature.set_point(1, 1) 25 | feature.set_point(0, 1) 26 | feature.close_ring() 27 | feature.commit() 28 | 29 | # Add a line 30 | feature = Linestring(layer) 31 | feature.add_linestring(3) 32 | feature.set_point(0, 0) 33 | feature.set_point(1, 1) 34 | feature.set_point(2, 2) 35 | feature.commit() 36 | 37 | # Encode mvt 38 | data = tile.serialize() 39 | 40 | # Decode MVT and print info 41 | tile = VectorTile(data) 42 | layer = next(tile) 43 | print(f"Layer Name: {layer.name.decode()}") 44 | print(f"MVT version: {layer.version}") 45 | print(f"MVT extent: {layer.extent}") 46 | features = [] 47 | while True: 48 | f = next(layer) 49 | if f.geometry_type == 0: 50 | break 51 | features.append(f) 52 | print(f"Nb Features: {len(features)}") 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-vtzero 2 | 3 | Experimental Python wrapper of [vtzero](https://github.com/mapbox/vtzero) a minimalist vector tile decoder and encoder in C++ 4 | 5 | [![Status](https://github.com/tilery/python-vtzero/workflows/CI/badge.svg)](https://github.com/tilery/python-vtzero/actions?query=workflow%3ACI) 6 | [![Packaging status](https://badge.fury.io/py/vtzero.svg)](https://badge.fury.io/py/vtzero) 7 | 8 | 9 | ## Requirements 10 | 11 | - Python >= 3.8 12 | - gcc/clang++ >= 4.5 (C++11) 13 | 14 | ## Install 15 | 16 | You can install python-vtzero using pip 17 | 18 | ```bash 19 | $ python -m pip install vtzero 20 | ``` 21 | 22 | or install from source 23 | 24 | ```bash 25 | $ git clone https://github.com/tilery/python-vtzero 26 | $ cd python-vtzero 27 | 28 | # Download vendor submodules (protozero, mvt-fixtures, vtzero) 29 | $ git submodule update --init 30 | 31 | # Compile Cython module 32 | $ python setup.py build_ext --inplace 33 | $ python -m pip install -e . 34 | ``` 35 | 36 | ## Example 37 | 38 | A complete example can be found [here](example/__init__.py) 39 | 40 | ```python 41 | from vtzero.tile import VectorTile, Tile, Layer, Point 42 | 43 | # Create MVT 44 | tile = Tile() 45 | 46 | # Add a layer 47 | layer = Layer(tile, b'my_layer') 48 | 49 | # Add a point 50 | feature = Point(layer) 51 | feature.add_points(1) 52 | feature.set_point(10, 10) 53 | feature.add_property(b'foo', b'bar') 54 | feature.add_property(b'x', b'y') 55 | feature.commit() 56 | 57 | # Encode mvt 58 | data = tile.serialize() 59 | ``` 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """python-vtzero setup.""" 2 | 3 | from Cython.Build import cythonize 4 | from setuptools import setup 5 | from setuptools.extension import Extension 6 | 7 | with open("README.md") as f: 8 | long_description = f.read() 9 | 10 | ext_options = { 11 | "include_dirs": ["./vendor/vtzero/include", "./vendor/protozero/include"], 12 | "extra_compile_args": ["-O2", "-std=c++11"], 13 | } 14 | ext_modules = cythonize( 15 | [Extension("vtzero.tile", ["vtzero/tile.pyx"], language="c++", **ext_options)] 16 | ) 17 | 18 | extra_reqs = { 19 | "test": ["pytest", "pytest-cov"], 20 | } 21 | 22 | 23 | setup( 24 | name="vtzero", 25 | description="Python wrapper for vtzero C++ library.", 26 | long_description=long_description, 27 | long_description_content_type="text/markdown", 28 | classifiers=[ 29 | "License :: OSI Approved :: MIT License", 30 | "Intended Audience :: Developers", 31 | "Programming Language :: Python :: 3", 32 | "Operating System :: POSIX", 33 | "Environment :: Web Environment", 34 | "Development Status :: 2 - Pre-Alpha", 35 | "Topic :: Scientific/Engineering :: GIS", 36 | ], 37 | keywords="mvt mapbox vector tile gis", 38 | platforms=["POSIX"], 39 | author="Yohan Boniface", 40 | author_email="yohan.boniface@data.gouv.fr", 41 | license="MIT", 42 | packages=["vtzero"], 43 | ext_modules=ext_modules, 44 | provides=["vtzero"], 45 | include_package_data=True, 46 | extras_require=extra_reqs, 47 | python_requires=">=3.8", 48 | ) 49 | -------------------------------------------------------------------------------- /tests/test_fixtures.py: -------------------------------------------------------------------------------- 1 | """fixtures.""" 2 | 3 | import json 4 | from pathlib import Path 5 | 6 | import pytest 7 | 8 | from vtzero.tile import VectorTile 9 | 10 | ROOT = Path(__file__).parent.parent / "vendor/mvt-fixtures/fixtures" 11 | 12 | # Magic: Generate tests from yml. 13 | # def pytest_generate_tests(metafunc): 14 | # if {'info', 'data', 'mvt'} <= set(metafunc.fixturenames): 15 | # root = Path(__file__).parent.parent / 'vendor/mvt-fixtures/fixtures' 16 | # # print([x for x in fixtures.iterdir() if x.is_dir()]) 17 | # fixtures = [] 18 | # paths = list(root.iterdir()) 19 | # paths.sort() 20 | # for path in paths: 21 | # fixtures.append([ 22 | # json.loads((path / 'info.json').open('r').read()), 23 | # json.loads((path / 'tile.json').open('r').read()), 24 | # (path / 'tile.mvt').open('rb').read(), 25 | # ]) 26 | # metafunc.parametrize("info,data,mvt", fixtures) 27 | 28 | 29 | @pytest.fixture 30 | def fixture(): 31 | """data fixtures.""" 32 | 33 | def _(id): 34 | path = ROOT / id 35 | with (path / "info.json").open("r") as f: 36 | info = json.loads(f.read()) 37 | with (path / "tile.json").open("r") as f: 38 | data = json.loads(f.read()) 39 | with (path / "tile.mvt").open("rb") as f: 40 | mvt = f.read() 41 | return info, data, mvt 42 | 43 | return _ 44 | 45 | 46 | def first_feature(tile): 47 | """check and return feature.""" 48 | assert not tile.empty() 49 | assert len(tile) == 1 50 | layer = next(tile) 51 | assert layer.name == b"hello" 52 | assert layer.version == 2 53 | assert layer.extent == 4096 54 | assert len(layer) == 1 55 | return next(layer) 56 | 57 | 58 | def test_empty_tile(fixture): 59 | """empty tile fixture.""" 60 | info, data, mvt = fixture("001") 61 | tile = VectorTile(mvt) 62 | assert tile.empty() 63 | assert len(tile) == 0 64 | 65 | 66 | def test_single_point_without_id(fixture): 67 | """Point.""" 68 | info, data, mvt = fixture("002") 69 | tile = VectorTile(mvt) 70 | feature = first_feature(tile) 71 | assert not feature.has_id() 72 | assert feature.id == 0 73 | assert feature.geometry_type == VectorTile.POINT 74 | # assert feature.geometry == [25, 17] 75 | -------------------------------------------------------------------------------- /vtzero/cvtzero.pxd: -------------------------------------------------------------------------------- 1 | # cython: language_level=3 2 | 3 | from libc.stdint cimport int32_t, uint32_t, uint64_t 4 | from libcpp cimport bool 5 | from libcpp.string cimport string 6 | 7 | 8 | cdef extern from 'protozero/pbf_reader.hpp' namespace 'protozero': 9 | cdef cppclass data_view: 10 | data_view() 11 | const char* data() 12 | 13 | 14 | cdef extern from 'vtzero/vector_tile.hpp' namespace 'vtzero': 15 | cdef cppclass vector_tile: 16 | vector_tile() 17 | # vector_tile(const data_view data) 18 | vector_tile(const string& data) 19 | bool empty() 20 | size_t count_layers() 21 | layer next_layer() 22 | layer get_layer(size_t index) 23 | layer get_layer_by_name(const data_view name) 24 | layer get_layer_by_name(const string& name) 25 | layer get_layer_by_name(const char* name) 26 | 27 | 28 | cdef extern from 'vtzero/geometry.hpp' namespace 'vtzero': 29 | ctypedef struct point: 30 | int32_t x 31 | int32_t y 32 | cdef decode_point_geometry(const geometry geometry, bool strict, geom_handler) 33 | 34 | 35 | cdef extern from 'vtzero/layer.hpp' namespace 'vtzero': 36 | cdef cppclass layer: 37 | layer() 38 | data_view data() 39 | data_view name() 40 | uint32_t version() 41 | uint32_t extent() 42 | size_t num_features() 43 | feature next_feature() 44 | 45 | 46 | cdef extern from 'vtzero/feature.hpp' namespace 'vtzero': 47 | 48 | cdef cppclass feature: 49 | feature() 50 | feature(const layer* layer, const data_view data) 51 | bool has_id() 52 | uint64_t id() 53 | GeomType geometry_type() 54 | geometry geometry() 55 | 56 | 57 | cdef extern from 'vtzero/types.hpp' namespace 'vtzero': 58 | cdef cppclass index_value: 59 | index_value() 60 | 61 | cdef cppclass geometry: 62 | geometry() 63 | 64 | 65 | cdef extern from 'vtzero/types.hpp' namespace 'vtzero::GeomType': 66 | cdef enum GeomType "vtzero::GeomType": 67 | UNKNOWN 68 | POINT 69 | LINESTRING 70 | POLYGON 71 | 72 | 73 | cdef extern from 'vtzero/builder.hpp' namespace 'vtzero': 74 | cdef cppclass layer_builder_impl: 75 | layer_builder_impl() 76 | 77 | cdef cppclass point_feature_builder: 78 | point_feature_builder() 79 | point_feature_builder(layer_builder layer) 80 | void add_point(const int32_t x, const int32_t y) 81 | void add_points(uint32_t count) 82 | void set_point(const point p) 83 | void set_point(const int32_t x, const int32_t y) 84 | void add_property(char* key, char* value) 85 | void set_id(const uint64_t id) 86 | void commit() 87 | void rollback() 88 | 89 | cdef cppclass polygon_feature_builder: 90 | polygon_feature_builder() 91 | polygon_feature_builder(layer_builder layer) 92 | void add_ring(uint32_t count) 93 | void close_ring() 94 | void set_point(const point p) 95 | void set_point(const int32_t x, const int32_t y) 96 | void add_property(char* key, char* value) 97 | void set_id(const uint64_t id) 98 | void commit() 99 | void rollback() 100 | 101 | cdef cppclass linestring_feature_builder: 102 | linestring_feature_builder() 103 | linestring_feature_builder(layer_builder layer) 104 | void add_linestring(uint32_t count) 105 | void set_point(const point p) 106 | void set_point(const int32_t x, const int32_t y) 107 | void add_property(char* key, char* value) 108 | void set_id(const uint64_t id) 109 | void commit() 110 | void rollback() 111 | 112 | cdef cppclass tile_builder: 113 | tile_builder() 114 | layer_builder_impl* add_layer(layer& layer) 115 | layer_builder_impl* add_layer(const char*, uint32_t version, uint32_t extent) 116 | string serialize() 117 | 118 | cdef cppclass layer_builder: 119 | layer_builder() 120 | layer_builder(tile_builder& tile, char* name) 121 | void add_feature(const feature& feature) 122 | -------------------------------------------------------------------------------- /tests/test_tile.py: -------------------------------------------------------------------------------- 1 | """Test tile encoding.""" 2 | 3 | from vtzero.tile import Layer, Linestring, Point, Polygon, Tile 4 | 5 | 6 | def test_point_encoding(): 7 | """Test creation of point feature.""" 8 | tile = Tile() 9 | points = Layer(tile, b"points") 10 | feature = Point(points) 11 | feature.add_points(1) 12 | feature.set_point(10, 10) 13 | feature.add_property(b"foo", b"bar") 14 | feature.add_property(b"x", b"y") 15 | feature.commit() 16 | assert ( 17 | tile.serialize() 18 | == b'\x1a0x\x02\n\x06points(\x80 \x12\r\x18\x01"\x03\t\x14\x14\x12\x04\x00\x00\x01\x01\x1a\x03foo\x1a\x01x"\x05\n\x03bar"\x03\n\x01y' 19 | ) # noqa 20 | 21 | 22 | def test_polygon_encoding(): 23 | """Test creation of polygon feature.""" 24 | tile = Tile() 25 | poly = Layer(tile, b"polygon") 26 | feature = Polygon(poly) 27 | feature.add_ring(5) 28 | feature.set_point(0, 0) 29 | feature.set_point(10, 0) 30 | feature.set_point(10, 10) 31 | feature.set_point(0, 10) 32 | feature.set_point(0, 0) 33 | feature.add_property(b"foo", b"bar") 34 | feature.commit() 35 | assert ( 36 | tile.serialize() 37 | == b'\x1a/x\x02\n\x07polygon(\x80 \x12\x13\x18\x03"\x0b\t\x00\x00\x1a\x14\x00\x00\x14\x13\x00\x0f\x12\x02\x00\x00\x1a\x03foo"\x05\n\x03bar' 38 | ) # noqa 39 | 40 | 41 | def test_polygon_encoding_close_ring(): 42 | """Test creation of polygon feature with 'close_ring' method.""" 43 | tile = Tile() 44 | poly = Layer(tile, b"polygon") 45 | feature = Polygon(poly) 46 | feature.add_ring(5) 47 | feature.set_point(0, 0) 48 | feature.set_point(10, 0) 49 | feature.set_point(10, 10) 50 | feature.set_point(0, 10) 51 | feature.close_ring() 52 | feature.add_property(b"foo", b"bar") 53 | feature.commit() 54 | assert ( 55 | tile.serialize() 56 | == b'\x1a/x\x02\n\x07polygon(\x80 \x12\x13\x18\x03"\x0b\t\x00\x00\x1a\x14\x00\x00\x14\x13\x00\x0f\x12\x02\x00\x00\x1a\x03foo"\x05\n\x03bar' 57 | ) # noqa 58 | 59 | 60 | def test_linestring_encoding(): 61 | """Test creation of linestring feature.""" 62 | tile = Tile() 63 | line = Layer(tile, b"linestring") 64 | feature = Linestring(line) 65 | feature.add_linestring(3) 66 | feature.set_point(0, 0) 67 | feature.set_point(10, 10) 68 | feature.set_point(20, 20) 69 | feature.add_property(b"foo", b"bar") 70 | feature.commit() 71 | assert ( 72 | tile.serialize() 73 | == b'\x1a/x\x02\n\nlinestring(\x80 \x12\x10\x18\x02"\x08\t\x00\x00\x12\x14\x14\x14\x14\x12\x02\x00\x00\x1a\x03foo"\x05\n\x03bar' 74 | ) # noqa 75 | 76 | 77 | def test_set_id_valid(): 78 | """Test set_id method.""" 79 | tile = Tile() 80 | points = Layer(tile, b"points") 81 | feature = Point(points) 82 | feature.set_id(1) 83 | feature.add_points(1) 84 | feature.set_point(10, 10) 85 | feature.add_property(b"foo", b"bar") 86 | feature.add_property(b"x", b"y") 87 | feature.commit() 88 | assert ( 89 | tile.serialize() 90 | == b'\x1a2x\x02\n\x06points(\x80 \x12\x0f\x18\x01\x08\x01"\x03\t\x14\x14\x12\x04\x00\x00\x01\x01\x1a\x03foo\x1a\x01x"\x05\n\x03bar"\x03\n\x01y' 91 | ) # noqa 92 | 93 | tile = Tile() 94 | poly = Layer(tile, b"polygon") 95 | feature = Polygon(poly) 96 | feature.set_id(1) 97 | feature.add_ring(5) 98 | feature.set_point(0, 0) 99 | feature.set_point(10, 0) 100 | feature.set_point(10, 10) 101 | feature.set_point(0, 10) 102 | feature.set_point(0, 0) 103 | feature.commit() 104 | assert ( 105 | tile.serialize() 106 | == b'\x1a!x\x02\n\x07polygon(\x80 \x12\x11\x18\x03\x08\x01"\x0b\t\x00\x00\x1a\x14\x00\x00\x14\x13\x00\x0f' 107 | ) # noqa 108 | 109 | tile = Tile() 110 | line = Layer(tile, b"linestring") 111 | feature = Linestring(line) 112 | feature.set_id(1) 113 | feature.add_linestring(3) 114 | feature.set_point(0, 0) 115 | feature.set_point(10, 10) 116 | feature.set_point(20, 20) 117 | feature.commit() 118 | assert ( 119 | tile.serialize() 120 | == b'\x1a!x\x02\n\nlinestring(\x80 \x12\x0e\x18\x02\x08\x01"\x08\t\x00\x00\x12\x14\x14\x14\x14' 121 | ) # noqa 122 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # On every pull request, but only on push to master 4 | on: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - '*' 10 | pull_request: 11 | 12 | jobs: 13 | tests: 14 | name: ${{ matrix.name }} 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | include: 20 | - {name: Windows, python: '3.10', os: windows-latest} 21 | - {name: Mac, python: '3.10', os: macos-latest} 22 | - {name: 'Ubuntu', python: '3.10', os: ubuntu-latest} 23 | - {name: '3.13', python: '3.13', os: ubuntu-latest} 24 | - {name: '3.12', python: '3.12', os: ubuntu-latest} 25 | - {name: '3.11', python: '3.11', os: ubuntu-latest} 26 | - {name: '3.9', python: '3.9', os: ubuntu-latest} 27 | - {name: '3.8', python: '3.8', os: ubuntu-latest} 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Set up Python ${{ matrix.python }} 32 | uses: actions/setup-python@v5 33 | with: 34 | python-version: ${{ matrix.python }} 35 | 36 | - name: Install dependencies 37 | run: | 38 | python -m pip install --upgrade pip 39 | git submodule update --init 40 | python -m pip install -e .["test"] 41 | 42 | - name: Run pre-commit 43 | if: ${{ matrix.name == 'Ubuntu' }} 44 | run: | 45 | python -m pip install pre-commit 46 | pre-commit run --all-files 47 | 48 | - name: Run Tests 49 | run: | 50 | python -m pytest --cov vtzero --cov-report xml --cov-report term-missing 51 | 52 | - name: Upload Results 53 | if: ${{ matrix.name == 'Ubuntu' }} 54 | uses: codecov/codecov-action@v1 55 | with: 56 | file: ./coverage.xml 57 | flags: unittests 58 | name: ${{ matrix.python }} 59 | fail_ci_if_error: false 60 | 61 | ####################################################################################### 62 | # Deploy 63 | ####################################################################################### 64 | build_wheels: 65 | needs: [tests] 66 | if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release' 67 | name: ${{ matrix.os }} 68 | runs-on: ${{ matrix.os }} 69 | strategy: 70 | fail-fast: false 71 | matrix: 72 | os: [ubuntu-latest, windows-latest, macos-latest] 73 | 74 | steps: 75 | - uses: actions/checkout@v3 76 | 77 | # Used to host cibuildwheel 78 | - uses: actions/setup-python@v4 79 | with: 80 | python-version: '3.10' 81 | 82 | - name: Install cibuildwheel 83 | run: python -m pip install cibuildwheel==2.22.0 84 | 85 | - name: Install dependencies 86 | run: | 87 | git submodule update --init 88 | 89 | - name: Build wheels 90 | run: python -m cibuildwheel --output-dir wheelhouse 91 | env: 92 | CIBW_SKIP: 'pp*' 93 | CIBW_ARCHS_MACOS: auto universal2 94 | 95 | - uses: actions/upload-artifact@v4 96 | with: 97 | name: vtzero-wheels-${{ matrix.os }}-${{ strategy.job-index }} 98 | path: ./wheelhouse/*.whl 99 | 100 | build_sdist: 101 | needs: [tests] 102 | if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release' 103 | name: Build source distribution 104 | runs-on: ubuntu-latest 105 | steps: 106 | - uses: actions/checkout@v3 107 | 108 | - uses: actions/setup-python@v4 109 | name: Install Python 110 | with: 111 | python-version: '3.10' 112 | 113 | - name: Install dependencies 114 | run: | 115 | python -m pip install numpy Cython 116 | git submodule update --init 117 | 118 | - name: Build sdist 119 | run: python setup.py sdist 120 | 121 | - uses: actions/upload-artifact@v4 122 | with: 123 | name: vtzero-sdist 124 | path: dist/*.tar.gz 125 | 126 | upload_pypi: 127 | needs: [build_wheels, build_sdist] 128 | runs-on: ubuntu-latest 129 | steps: 130 | - uses: actions/download-artifact@v4 131 | with: 132 | pattern: vtzero-* 133 | path: dist 134 | merge-multiple: true 135 | 136 | - uses: pypa/gh-action-pypi-publish@release/v1 137 | with: 138 | user: ${{ secrets.PYPI_USERNAME }} 139 | password: ${{ secrets.PYPI_PASSWORD }} 140 | -------------------------------------------------------------------------------- /vtzero/tile.pyx: -------------------------------------------------------------------------------- 1 | # cython: language_level=3 2 | 3 | """vtzero.tile module.""" 4 | 5 | from libc.stdint cimport uint32_t 6 | from libcpp.string cimport string 7 | 8 | from vtzero cimport cvtzero 9 | 10 | 11 | cdef class VectorTile: 12 | 13 | UNKNOWN = cvtzero.GeomType.UNKNOWN 14 | POINT = cvtzero.POINT 15 | LINESTRING = cvtzero.LINESTRING 16 | POLYGON = cvtzero.POLYGON 17 | 18 | cdef cvtzero.vector_tile* tile 19 | cdef string data 20 | 21 | def __cinit__(self, string data): 22 | # C++ will only create a pointer to the python bytes object 23 | # so let's keep a reference here to be sure it's not garbage 24 | # collected during the tile lifetime. 25 | self.data = data 26 | self.tile = new cvtzero.vector_tile(self.data) 27 | 28 | def empty(self): 29 | return self.tile.empty() 30 | 31 | def __len__(self): 32 | return self.tile.count_layers() 33 | 34 | def __next__(self): 35 | cdef: 36 | cvtzero.layer layer = self.tile.next_layer() 37 | VectorLayer pylayer = VectorLayer() 38 | pylayer.set_layer(layer) 39 | return pylayer 40 | 41 | def __getitem__(self, item): 42 | cdef: 43 | cvtzero.layer layer 44 | VectorLayer pylayer = VectorLayer() 45 | if isinstance(item, int): 46 | layer = self.tile.get_layer(item) 47 | else: 48 | self.tile.get_layer_by_name(item) 49 | pylayer.set_layer(layer) 50 | return pylayer 51 | 52 | def __dealloc__(self): 53 | del self.tile 54 | 55 | 56 | cdef class VectorLayer: 57 | 58 | cdef cvtzero.layer layer 59 | 60 | cdef set_layer(self, cvtzero.layer layer): 61 | self.layer = layer 62 | 63 | @property 64 | def name(self): 65 | return self.layer.name() 66 | 67 | @property 68 | def version(self): 69 | return self.layer.version() 70 | 71 | @property 72 | def extent(self): 73 | return self.layer.extent() 74 | 75 | def __len__(self): 76 | return self.layer.num_features() 77 | 78 | def __next__(self): 79 | cdef: 80 | cvtzero.feature feature = self.layer.next_feature() 81 | VectorFeature pyfeature = VectorFeature() 82 | pyfeature.set_feature(feature) 83 | return pyfeature 84 | 85 | 86 | cdef class VectorFeature: 87 | 88 | cdef cvtzero.feature feature 89 | 90 | cdef set_feature(self, cvtzero.feature feature): 91 | self.feature = feature 92 | 93 | def has_id(self): 94 | return self.feature.has_id() 95 | 96 | @property 97 | def id(self): 98 | return self.feature.id() 99 | 100 | @property 101 | def geometry_type(self): 102 | return self.feature.geometry_type() 103 | 104 | 105 | cdef class Tile: 106 | 107 | cdef cvtzero.tile_builder builder 108 | 109 | def __cinit__(self): 110 | self.builder = cvtzero.tile_builder() 111 | 112 | def serialize(self): 113 | return self.builder.serialize() 114 | 115 | 116 | cdef class Layer: 117 | 118 | cdef cvtzero.layer_builder* builder 119 | 120 | def __cinit__(self, Tile tile, char* name): 121 | self.builder = new cvtzero.layer_builder(tile.builder, name) 122 | 123 | 124 | cdef class Point: 125 | 126 | cdef cvtzero.point_feature_builder* builder 127 | 128 | def __cinit__(self, Layer layer): 129 | self.builder = new cvtzero.point_feature_builder(layer.builder[0]) 130 | 131 | def add_point(self, x, y): 132 | self.builder.add_point(x, y) 133 | 134 | def add_points(self, count): 135 | self.builder.add_points(count) 136 | 137 | def set_point(self, x, y): 138 | self.builder.set_point(x, y) 139 | 140 | def add_property(self, char* key, char* value): 141 | self.builder.add_property(key, value) 142 | 143 | def set_id(self, int id): 144 | self.builder.set_id(id) 145 | 146 | def commit(self): 147 | self.builder.commit() 148 | 149 | def rollback(self): 150 | self.builder.rollback() 151 | 152 | 153 | cdef class Polygon: 154 | 155 | cdef cvtzero.polygon_feature_builder* builder 156 | 157 | def __cinit__(self, Layer layer): 158 | self.builder = new cvtzero.polygon_feature_builder(layer.builder[0]) 159 | 160 | def add_ring(self, count): 161 | self.builder.add_ring(count) 162 | 163 | def close_ring(self, ): 164 | self.builder.close_ring() 165 | 166 | def set_point(self, x, y): 167 | self.builder.set_point(x, y) 168 | 169 | def add_property(self, char* key, char* value): 170 | self.builder.add_property(key, value) 171 | 172 | def set_id(self, int id): 173 | self.builder.set_id(id) 174 | 175 | def commit(self): 176 | self.builder.commit() 177 | 178 | def rollback(self): 179 | self.builder.rollback() 180 | 181 | 182 | cdef class Linestring: 183 | 184 | cdef cvtzero.linestring_feature_builder* builder 185 | 186 | def __cinit__(self, Layer layer): 187 | self.builder = new cvtzero.linestring_feature_builder(layer.builder[0]) 188 | 189 | def add_linestring(self, count): 190 | self.builder.add_linestring(count) 191 | 192 | def set_point(self, x, y): 193 | self.builder.set_point(x, y) 194 | 195 | def add_property(self, char* key, char* value): 196 | self.builder.add_property(key, value) 197 | 198 | def set_id(self, int id): 199 | self.builder.set_id(id) 200 | 201 | def commit(self): 202 | self.builder.commit() 203 | 204 | def rollback(self): 205 | self.builder.rollback() 206 | --------------------------------------------------------------------------------