├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── README.md ├── examples ├── decode.py └── encode.py ├── requirements.txt ├── setup.py ├── tests ├── conftest.py ├── create_test_data.py ├── data │ ├── invalid │ │ └── single_layer_v3_polygon_3d.mvt │ └── valid │ │ ├── all_attribute_types_v3.mvt │ │ ├── single_layer_v2_linestring.mvt │ │ ├── single_layer_v2_points.mvt │ │ ├── single_layer_v2_polygon.mvt │ │ ├── single_layer_v3_linestring_3d.mvt │ │ ├── single_layer_v3_points_3d.mvt │ │ ├── single_layer_v3_spline.mvt │ │ └── single_layer_v3_spline_3d.mvt ├── test_decode.py ├── test_encode.py ├── test_scaling.py └── test_zig_zag.py ├── vector_tile.proto └── vector_tile_base ├── __init__.py ├── engine.py └── vector_tile_pb2.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.mvt binary diff=hex 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Mapbox 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of Sean C. Gillies nor the names of 13 | its contributors may be used to endorse or promote products derived from 14 | this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vector-tile-base 2 | ================ 3 | 4 | This library encodes and decodes [Mapbox Vector Tiles](https://github.com/mapbox/vector-tile-spec). It is intended for use by developers with clear understanding of the Vector Tile format. The code is written as a pure python implementation with support of the google protobuf python format. 5 | 6 | ## Depends 7 | 8 | - Google protobuf python bindings 9 | 10 | ## Development 11 | 12 | Install the python locally with pip: 13 | 14 | ``` 15 | pip install -e . 16 | ``` 17 | 18 | To run tests use [pytest](https://docs.pytest.org/en/latest/): 19 | 20 | ``` 21 | pytest 22 | ``` 23 | 24 | ## Example 25 | 26 | Some very simple code examples 27 | 28 | ### Encode 29 | 30 | There is an example encoding provided in `examples` and can be used to ecode a `.mvt` file. 31 | 32 | ``` 33 | python examples/encode.py my.mvt 34 | ``` 35 | 36 | ``` 37 | import vector_tile_base 38 | import sys 39 | 40 | vt = vector_tile_base.VectorTile() 41 | layer = vt.add_layer('my_locations') 42 | feature = layer.add_point_feature() 43 | feature.add_points([[10,10],[20,20]]) 44 | feature.id = 1 45 | feature.attributes = { 'type': 1, 'name': 'my_points' } 46 | 47 | encoded_tile = vt.serialize() 48 | 49 | f = open(sys.argv[1], "wb") 50 | f.write(encoded_tile) 51 | f.close() 52 | ``` 53 | 54 | ### Decode 55 | 56 | There is an example decoding provided in `examples` and can be used to decode a `.mvt` file. 57 | 58 | ``` 59 | python examples/decode.py my.mvt 60 | ``` 61 | 62 | ``` 63 | import vector_tile_base 64 | import sys 65 | 66 | f = open(sys.argv[1], "rb") 67 | raw_tile = f.read() 68 | f.close() 69 | 70 | vt = vector_tile_base.VectorTile(raw_tile) 71 | for l in vt.layers: 72 | print(l.name) 73 | for f in l.features: 74 | print(f.type) 75 | print(f.attributes) 76 | print(f.get_geometry()) 77 | ``` 78 | 79 | -------------------------------------------------------------------------------- /examples/decode.py: -------------------------------------------------------------------------------- 1 | import vector_tile_base 2 | import sys 3 | 4 | f = open(sys.argv[1], "rb") 5 | raw_tile = f.read() 6 | f.close() 7 | 8 | vt = vector_tile_base.VectorTile(raw_tile) 9 | for l in vt.layers: 10 | print(l.name) 11 | for f in l.features: 12 | print(f.type) 13 | print(f.attributes) 14 | print(f.get_geometry()) 15 | -------------------------------------------------------------------------------- /examples/encode.py: -------------------------------------------------------------------------------- 1 | import vector_tile_base 2 | import sys 3 | 4 | vt = vector_tile_base.VectorTile() 5 | layer = vt.add_layer('my_locations') 6 | feature = layer.add_point_feature() 7 | feature.add_points([[10,10],[20,20]]) 8 | feature.id = 1 9 | feature.attributes = { 'type': 1, 'name': 'my_points' } 10 | 11 | encoded_tile = vt.serialize() 12 | 13 | f = open(sys.argv[1], "wb") 14 | f.write(encoded_tile) 15 | f.close() 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | protobuf==2.6.1 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import sys, os 3 | 4 | # Parse the version from the vector tile base module. 5 | with open('vector_tile_base/__init__.py') as f: 6 | for line in f: 7 | if line.find("__version__") >= 0: 8 | version = line.split("=")[1].strip() 9 | version = version.strip('"') 10 | version = version.strip("'") 11 | continue 12 | 13 | setup(name='vector_tile_base', 14 | version=version, 15 | description="Python implementation of Mapbox vector tiles", 16 | long_description="""\ 17 | """, 18 | classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 19 | keywords='', 20 | author='Sean Gillies', 21 | author_email='sean@mapbox.com', 22 | url='https://github.com/mapbox/vector-tile-base', 23 | license='BSD', 24 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), 25 | include_package_data=True, 26 | zip_safe=False, 27 | install_requires=[ 28 | 'protobuf' 29 | ], 30 | extras_require={ 31 | 'test': ['pytest'], 32 | }, 33 | entry_points=""" 34 | # -*- Entry points: -*- 35 | """, 36 | ) 37 | 38 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | from vector_tile_base import VectorTile 4 | 5 | def load_vector_tile(name): 6 | divided = name.split('_') 7 | if divided[1] == 'invalid': 8 | name = '_'.join(divided[2:]) 9 | filename = os.path.join('tests', 'data', 'invalid', name + '.mvt') 10 | elif divided[1] == 'valid': 11 | name = '_'.join(divided[2:]) 12 | filename = os.path.join('tests', 'data', 'valid', name + '.mvt') 13 | else: 14 | name = '_'.join(divided[1:]) 15 | filename = os.path.join('tests', 'data', name + '.mvt') 16 | f = open(filename, 'rb') 17 | test_data = f.read() 18 | f.close() 19 | return VectorTile(test_data) 20 | 21 | @pytest.fixture() 22 | def vt(request): 23 | if request.node.originalname is None: 24 | return load_vector_tile(request.node.name) 25 | else: 26 | return load_vector_tile(request.node.originalname) 27 | -------------------------------------------------------------------------------- /tests/create_test_data.py: -------------------------------------------------------------------------------- 1 | from vector_tile_base import * 2 | import os 3 | import sys 4 | 5 | # To create a new test fixture create a method 6 | # that starts with "create_" and returns a string 7 | # of a vector tile buffer. This will be saved to a 8 | # file in the test/data/ folder using the name of 9 | # the method provided. The next name after "create_" 10 | # can be be "valid" or "invalid" changing the 11 | # destination folder in which the data is saved. If 12 | # it is not set no folder is assigned. 13 | 14 | # Example 1: 15 | # def create_valid_point_a() will create a file 16 | # named test/data/valid/point_a.mvt 17 | # Example 2: 18 | # def create_invalid_stuff() will create a file 19 | # named test/data/invalid/stuff.mvt 20 | # Example 3: 21 | # def create_wild() will create a file 22 | # named test/data/wild.mvt 23 | 24 | def create_valid_single_layer_v2_points(): 25 | vt = VectorTile() 26 | # layer 0 27 | layer = vt.add_layer('points', version=2) 28 | feature = layer.add_point_feature(has_elevation=False) 29 | feature.id = 2 30 | feature.add_points([20,20]) 31 | feature.attributes = { 'some': 'attr' } 32 | feature = layer.add_point_feature(has_elevation=False) 33 | feature.id = 3 34 | feature.add_points([20,20]) 35 | feature.attributes = { 'some': 'attr' } 36 | feature = layer.add_point_feature(has_elevation=False) 37 | feature.id = 4 38 | feature.add_points([20,20]) 39 | feature.attributes = { 'some': 'otherattr' } 40 | feature = layer.add_point_feature(has_elevation=False) 41 | feature.id = 5 42 | feature.add_points([20,20]) 43 | feature.attributes = { 'otherkey': 'attr' } 44 | return vt.serialize() 45 | 46 | def create_valid_single_layer_v2_linestring(): 47 | vt = VectorTile() 48 | layer = vt.add_layer('lines', version=2) 49 | feature = layer.add_line_string_feature(has_elevation=False) 50 | feature.id = 6 51 | feature.add_line_string([[10,10],[10,20],[20,20]]) 52 | feature.add_line_string([[11,11],[12,13]]) 53 | feature.attributes = { 'highway': 'primary', 'maxspeed': 50 } 54 | return vt.serialize() 55 | 56 | def create_valid_single_layer_v2_polygon(): 57 | vt = VectorTile() 58 | layer = vt.add_layer('polygons', version=2) 59 | feature = layer.add_polygon_feature(has_elevation=False) 60 | feature.id = 7 61 | feature.add_ring([[0,0],[10,0],[10,10],[0,10],[0,0]]) 62 | feature.add_ring([[3,3],[3,5],[5,5],[3,3]]) 63 | feature.attributes = { 'natural': 'wood' } 64 | return vt.serialize() 65 | 66 | def create_valid_single_layer_v3_spline(): 67 | vt = VectorTile() 68 | layer = vt.add_layer('splines', version=3) 69 | feature = layer.add_spline_feature(has_elevation=False, degree=3) 70 | feature.id = 8 71 | scaling = layer.add_attribute_scaling(precision=10.0**-8, min_value=0.0, max_value=25.0) 72 | knots = FloatList(scaling, [0.0, 2.0, 3.0, 4.0, 5.875, 6.0, 7.0, 8.0]) 73 | feature.add_spline([[8,10],[9,11],[11,9],[12,10]], knots) 74 | feature.attributes = { 'natural': 'spline' } 75 | return vt.serialize() 76 | 77 | def create_valid_single_layer_v3_points_3d(): 78 | vt = VectorTile() 79 | layer = vt.add_layer('points_3d', version=3) 80 | layer.set_tile_location(zoom=4, x=3, y=2) 81 | feature = layer.add_point_feature(has_elevation=True) 82 | feature.id = 10 83 | feature.add_points([20,20,10]) 84 | feature.attributes = { 'some': 'attr' } 85 | feature = layer.add_point_feature(has_elevation=True) 86 | feature.id = 11 87 | feature.add_points([20,20,20]) 88 | feature.attributes = { 'some': 'attr' } 89 | feature = layer.add_point_feature(has_elevation=True) 90 | feature.id = 12 91 | feature.add_points([20,20,30]) 92 | feature.attributes = { 'some': 'otherattr' } 93 | feature = layer.add_point_feature(has_elevation=True) 94 | feature.id = 13 95 | feature.add_points([20,20,40]) 96 | feature.attributes = { 'otherkey': 'attr' } 97 | return vt.serialize() 98 | 99 | def create_valid_single_layer_v3_linestring_3d(): 100 | vt = VectorTile() 101 | layer = vt.add_layer('lines_3d', version=3) 102 | feature = layer.add_line_string_feature(has_elevation=True) 103 | feature.id = 14 104 | feature.add_line_string([[10,10,10],[10,20,20],[20,20,30]]) 105 | feature.add_line_string([[11,11,10],[12,13,20]]) 106 | feature.attributes = { 'highway': 'primary', 'maxspeed': 50 } 107 | return vt.serialize() 108 | 109 | def create_invalid_single_layer_v3_polygon_3d(): 110 | vt = VectorTile() 111 | layer = vt.add_layer('polygons_3d', version=3) 112 | feature = layer.add_polygon_feature(has_elevation=True) 113 | feature.id = 15 114 | feature.add_ring([[0,0,10],[10,0,20],[10,10,30],[0,10,20],[0,0,10]]) 115 | feature.add_ring([[3,3,20],[3,5,40],[5,5,30],[3,3,20]]) 116 | feature.attributes = { 'natural': 'wood' } 117 | return vt.serialize() 118 | 119 | def create_valid_single_layer_v3_spline_3d(): 120 | vt = VectorTile() 121 | layer = vt.add_layer('splines_3d', version=3) 122 | feature = layer.add_spline_feature(has_elevation=True, degree=3) 123 | feature.id = 16 124 | scaling = layer.add_attribute_scaling(precision=10.0**-8, min_value=0.0, max_value=25.0) 125 | knots = FloatList(scaling, [0.0, 2.0, 3.0, 4.0, 5.875, 6.0, 7.0, 8.0]) 126 | feature.add_spline([[8,10,10],[9,11,11],[11,9,12],[12,10,13]], knots) 127 | feature.attributes = { 'natural': 'spline' } 128 | return vt.serialize() 129 | 130 | def create_valid_all_attribute_types_v3(): 131 | vt = VectorTile() 132 | layer = vt.add_layer('example', version=3) 133 | scaling = layer.add_attribute_scaling(precision=10.0**-8, min_value=0.0, max_value=25.0) 134 | feature = layer.add_point_feature() 135 | feature.id = 1 136 | feature.add_points([20,20]) 137 | feature.attributes = { 138 | 'bool_true': True, 139 | 'bool_false': False, 140 | 'null': None, 141 | 'string': 'a_string', 142 | 'float': Float(1.0), 143 | 'double': 2.0, 144 | 'inline_uint': UInt(1), 145 | 'inline_sint': -1, 146 | 'uint': UInt(2**60), 147 | 'int': -2**60, 148 | 'dlist': FloatList(scaling, [1.0, 2.0, 3.0, 3.5, 4.5, 6.9]), 149 | 'map': {'key1': 1, 'nested_map': { 'key': 1 }, 'nested_list': [1, 2, 3]}, 150 | 'list': [True, False, None, 'a_string', Float(1.0), 2.0, UInt(1), -1, UInt(2**60), -2**60, {'key1': 1}, [1,2,3],FloatList(scaling, [1.0, 2.0, 3.0, 3.5, 4.5, 6.9])] 151 | } 152 | return vt.serialize() 153 | 154 | def write_test_file(data, name, folder): 155 | if folder is None: 156 | filename = os.path.join('tests', 'data', name + '.mvt') 157 | else: 158 | filename = os.path.join('tests', 'data', folder, name + '.mvt') 159 | f = open(filename, 'wb') 160 | f.write(data) 161 | f.close() 162 | 163 | def run_all_creates(): 164 | a = sys.modules[__name__] 165 | for i in dir(a): 166 | item = getattr(a,i) 167 | if callable(item) and i.startswith('create'): 168 | data = item() 169 | divided = i.split('_') 170 | if divided[1] == 'invalid': 171 | folder = 'invalid' 172 | name = '_'.join(divided[2:]) 173 | elif divided[1] == 'valid': 174 | folder = 'valid' 175 | name = '_'.join(divided[2:]) 176 | else: 177 | folder = None 178 | name = '_'.join(divided[1:]) 179 | write_test_file(data, name, folder) 180 | 181 | if __name__ == '__main__': 182 | run_all_creates() 183 | -------------------------------------------------------------------------------- /tests/data/invalid/single_layer_v3_polygon_3d.mvt: -------------------------------------------------------------------------------- 1 | G 2 | polygons_3d'"   *:(natural2woodx -------------------------------------------------------------------------------- /tests/data/valid/all_attribute_types_v3.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vector-tile-base/39b7a503e8cda48e454312f0abe3ff628bd6bdd0/tests/data/valid/all_attribute_types_v3.mvt -------------------------------------------------------------------------------- /tests/data/valid/single_layer_v2_linestring.mvt: -------------------------------------------------------------------------------- 1 | G 2 | lines"   3 | highwaymaxspeed" 4 | primary" 2x -------------------------------------------------------------------------------- /tests/data/valid/single_layer_v2_points.mvt: -------------------------------------------------------------------------------- 1 | k 2 | points " (( " (( " (( " ((someotherkey" 3 | attr" 4 | otherattrx -------------------------------------------------------------------------------- /tests/data/valid/single_layer_v2_polygon.mvt: -------------------------------------------------------------------------------- 1 | = 2 | polygons"   natural" 3 | woodx -------------------------------------------------------------------------------- /tests/data/valid/single_layer_v3_linestring_3d.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vector-tile-base/39b7a503e8cda48e454312f0abe3ff628bd6bdd0/tests/data/valid/single_layer_v3_linestring_3d.mvt -------------------------------------------------------------------------------- /tests/data/valid/single_layer_v3_points_3d.mvt: -------------------------------------------------------------------------------- 1 | | 2 | points_3d 3 | " ((*: " ((*:( " ((*:< " ((*:Psomeotherkey2attr2 otherattr`hpx -------------------------------------------------------------------------------- /tests/data/valid/single_layer_v3_spline.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vector-tile-base/39b7a503e8cda48e454312f0abe3ff628bd6bdd0/tests/data/valid/single_layer_v3_spline.mvt -------------------------------------------------------------------------------- /tests/data/valid/single_layer_v3_spline_3d.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vector-tile-base/39b7a503e8cda48e454312f0abe3ff628bd6bdd0/tests/data/valid/single_layer_v3_spline_3d.mvt -------------------------------------------------------------------------------- /tests/test_decode.py: -------------------------------------------------------------------------------- 1 | from vector_tile_base import VectorTile, SplineFeature, PointFeature, PolygonFeature, LineStringFeature, Layer, FeatureAttributes, FloatList 2 | 3 | def test_valid_single_layer_v2_points(vt): 4 | assert len(vt.layers) == 1 5 | layer = vt.layers[0] 6 | assert isinstance(layer, Layer) 7 | assert layer.name == 'points' 8 | assert layer.extent == 4096 9 | assert layer.version == 2 10 | assert len(layer.features) == 4 11 | expected_id = 2 12 | # Test layer features 13 | for feature in layer.features: 14 | assert isinstance(feature, PointFeature) 15 | assert feature.type == 'point' 16 | assert feature.id == expected_id 17 | assert not feature.has_elevation 18 | geometry = feature.get_points() 19 | assert geometry == feature.get_geometry() 20 | assert isinstance(geometry, list) 21 | assert len(geometry) == 1 22 | point = geometry[0] 23 | assert isinstance(point, list) 24 | assert len(point) == 2 25 | assert point[0] == 20 26 | assert point[1] == 20 27 | props = feature.attributes 28 | assert isinstance(props, FeatureAttributes) 29 | assert len(props) == 1 30 | if expected_id == 2: 31 | assert props['some'] 32 | assert props['some'] == 'attr' 33 | elif expected_id == 3: 34 | assert props['some'] 35 | assert props['some'] == 'attr' 36 | elif expected_id == 4: 37 | assert props['some'] 38 | assert props['some'] == 'otherattr' 39 | elif expected_id == 5: 40 | assert props['otherkey'] 41 | assert props['otherkey'] == 'attr' 42 | expected_id = expected_id + 1 43 | 44 | def test_valid_single_layer_v2_linestring(vt): 45 | assert len(vt.layers) == 1 46 | layer = vt.layers[0] 47 | assert isinstance(layer, Layer) 48 | assert layer.name == 'lines' 49 | assert layer.extent == 4096 50 | assert layer.version == 2 51 | assert layer.zoom == None 52 | assert layer.x == None 53 | assert layer.y == None 54 | assert len(layer.features) == 1 55 | # Test layer features 56 | feature = layer.features[0] 57 | assert isinstance(feature, LineStringFeature) 58 | assert feature.type == 'line_string' 59 | assert feature.id == 6 60 | assert not feature.has_elevation 61 | geometry = feature.get_line_strings() 62 | assert geometry == feature.get_geometry() 63 | assert isinstance(geometry, list) 64 | assert len(geometry) == 2 65 | geometry == [[[10,10],[10,20],[20,20]],[[11,11],[12,13]]] 66 | props = feature.attributes 67 | assert isinstance(props, FeatureAttributes) 68 | assert len(props) == 2 69 | assert props['highway'] 70 | assert props['highway'] == 'primary' 71 | assert props['maxspeed'] 72 | assert props['maxspeed'] == 50 73 | 74 | def test_valid_single_layer_v2_polygon(vt): 75 | assert len(vt.layers) == 1 76 | layer = vt.layers[0] 77 | assert isinstance(layer, Layer) 78 | assert layer.name == 'polygons' 79 | assert layer.extent == 4096 80 | assert layer.version == 2 81 | assert len(layer.features) == 1 82 | # Test layer features 83 | feature = layer.features[0] 84 | assert isinstance(feature, PolygonFeature) 85 | assert feature.type == 'polygon' 86 | assert feature.id == 7 87 | assert not feature.has_elevation 88 | geometry = feature.get_rings() 89 | multi_polygons = feature.get_polygons() 90 | assert multi_polygons == feature.get_geometry() 91 | assert isinstance(geometry, list) 92 | assert isinstance(multi_polygons, list) 93 | assert len(multi_polygons) == 1 94 | assert geometry == multi_polygons[0] 95 | assert len(geometry) == 2 96 | assert geometry == [[[0,0],[10,0],[10,10],[0,10],[0,0]],[[3,3],[3,5],[5,5],[3,3]]] 97 | props = feature.attributes 98 | assert isinstance(props, FeatureAttributes) 99 | assert len(props) == 1 100 | assert props['natural'] 101 | assert props['natural'] == 'wood' 102 | 103 | def test_valid_single_layer_v3_spline(vt): 104 | assert len(vt.layers) == 1 105 | layer = vt.layers[0] 106 | assert isinstance(layer, Layer) 107 | assert layer.name == 'splines' 108 | assert layer.extent == 4096 109 | assert layer.version == 3 110 | assert len(layer.features) == 1 111 | # Test layer features 112 | feature = layer.features[0] 113 | assert isinstance(feature, SplineFeature) 114 | assert feature.type == 'spline' 115 | assert feature.id == 8 116 | assert not feature.has_elevation 117 | assert feature.degree == 3 118 | splines = feature.get_splines() 119 | assert isinstance(splines, list) 120 | assert len(splines) == 1 121 | assert len(splines[0]) == 2 122 | control_points = splines[0][0] 123 | assert isinstance(control_points, list) 124 | assert len(control_points) == 4 125 | assert control_points == [[8,10],[9,11],[11,9],[12,10]] 126 | knots = splines[0][1] 127 | assert [[control_points, knots]] == feature.get_geometry() 128 | assert len(knots) == 8 129 | assert knots == [0.0, 2.0, 3.0, 4.0, 5.875, 6.0, 7.0, 8.0] 130 | props = feature.attributes 131 | assert isinstance(props, FeatureAttributes) 132 | assert len(props) == 1 133 | assert props['natural'] 134 | assert props['natural'] == 'spline' 135 | 136 | def test_valid_single_layer_v3_points_3d(vt): 137 | expected_id = 10 138 | assert len(vt.layers) == 1 139 | layer = vt.layers[0] 140 | assert isinstance(layer, Layer) 141 | assert layer.name == 'points_3d' 142 | assert layer.extent == 4096 143 | assert layer.version == 3 144 | assert layer.zoom == 4 145 | assert layer.x == 3 146 | assert layer.y == 2 147 | assert len(layer.features) == 4 148 | # Test layer features 149 | point_z = 10 150 | for feature in layer.features: 151 | assert isinstance(feature, PointFeature) 152 | assert feature.type == 'point' 153 | assert feature.id == expected_id 154 | assert feature.has_elevation 155 | geometry = feature.get_points() 156 | assert isinstance(geometry, list) 157 | assert len(geometry) == 1 158 | point = geometry[0] 159 | assert isinstance(point, list) 160 | assert len(point) == 3 161 | assert point[0] == 20 162 | assert point[1] == 20 163 | assert point[2] == point_z 164 | point_z += 10 165 | props = feature.attributes 166 | assert isinstance(props, FeatureAttributes) 167 | assert len(props) == 1 168 | if expected_id == 2: 169 | assert props['some'] 170 | assert props['some'] == 'attr' 171 | elif expected_id == 3: 172 | assert props['some'] 173 | assert props['some'] == 'attr' 174 | elif expected_id == 4: 175 | assert props['some'] 176 | assert props['some'] == 'otherattr' 177 | elif expected_id == 5: 178 | assert props['otherkey'] 179 | assert props['otherkey'] == 'attr' 180 | expected_id += 1 181 | 182 | def test_valid_single_layer_v3_linestring_3d(vt): 183 | assert len(vt.layers) == 1 184 | layer = vt.layers[0] 185 | assert isinstance(layer, Layer) 186 | assert layer.name == 'lines_3d' 187 | assert layer.extent == 4096 188 | assert layer.version == 3 189 | assert len(layer.features) == 1 190 | # Test layer features 191 | feature = layer.features[0] 192 | assert isinstance(feature, LineStringFeature) 193 | assert feature.type == 'line_string' 194 | assert feature.id == 14 195 | assert feature.has_elevation 196 | geometry = feature.get_line_strings() 197 | assert isinstance(geometry, list) 198 | assert len(geometry) == 2 199 | geometry == [[[10,10,10],[10,20,20],[20,20,30]],[[11,11,10],[12,13,20]]] 200 | props = feature.attributes 201 | assert isinstance(props, FeatureAttributes) 202 | assert len(props) == 2 203 | assert props['highway'] 204 | assert props['highway'] == 'primary' 205 | assert props['maxspeed'] 206 | assert props['maxspeed'] == 50 207 | 208 | def test_invalid_single_layer_v3_polygon_3d(vt): 209 | # This test is officially invalid currently, 210 | # because polygons in 3d are undefined, but 211 | # the decoder will handle it just fine. 212 | assert len(vt.layers) == 1 213 | layer = vt.layers[0] 214 | assert isinstance(layer, Layer) 215 | assert layer.name == 'polygons_3d' 216 | assert layer.extent == 4096 217 | assert layer.version == 3 218 | assert len(layer.features) == 1 219 | # Test layer features 220 | feature = layer.features[0] 221 | assert isinstance(feature, PolygonFeature) 222 | assert feature.type == 'polygon' 223 | assert feature.id == 15 224 | assert feature.has_elevation 225 | geometry = feature.get_rings() 226 | multi_polygons = feature.get_polygons() 227 | assert isinstance(geometry, list) 228 | assert isinstance(multi_polygons, list) 229 | assert len(multi_polygons) == 1 230 | assert geometry == multi_polygons[0] 231 | assert len(geometry) == 2 232 | assert geometry == [[[0,0,10],[10,0,20],[10,10,30],[0,10,20],[0,0,10]],[[3,3,20],[3,5,40],[5,5,30],[3,3,20]]] 233 | props = feature.attributes 234 | assert isinstance(props, FeatureAttributes) 235 | assert len(props) == 1 236 | assert props['natural'] 237 | assert props['natural'] == 'wood' 238 | 239 | def test_valid_single_layer_v3_spline_3d(vt): 240 | assert len(vt.layers) == 1 241 | layer = vt.layers[0] 242 | assert isinstance(layer, Layer) 243 | assert layer.name == 'splines_3d' 244 | assert layer.extent == 4096 245 | assert layer.version == 3 246 | assert len(layer.features) == 1 247 | # Test layer features 248 | feature = layer.features[0] 249 | assert isinstance(feature, SplineFeature) 250 | assert feature.type == 'spline' 251 | assert feature.id == 16 252 | assert feature.has_elevation 253 | assert feature.degree == 3 254 | splines = feature.get_splines() 255 | assert isinstance(splines, list) 256 | assert len(splines) == 1 257 | assert len(splines[0]) == 2 258 | control_points = splines[0][0] 259 | assert isinstance(control_points, list) 260 | assert len(control_points) == 4 261 | assert control_points == [[8,10,10],[9,11,11],[11,9,12],[12,10,13]] 262 | knots = splines[0][1] 263 | assert [[control_points, knots]] == feature.get_geometry() 264 | assert len(knots) == 8 265 | assert knots == [0.0, 2.0, 3.0, 4.0, 5.875, 6.0, 7.0, 8.0] 266 | props = feature.attributes 267 | assert isinstance(props, FeatureAttributes) 268 | assert len(props) == 1 269 | assert props['natural'] 270 | assert props['natural'] == 'spline' 271 | 272 | def test_valid_all_attribute_types_v3(vt): 273 | assert len(vt.layers) == 1 274 | layer = vt.layers[0] 275 | assert isinstance(layer, Layer) 276 | assert layer.name == 'example' 277 | assert layer.extent == 4096 278 | assert layer.version == 3 279 | assert len(layer.features) == 1 280 | feature = layer.features[0] 281 | assert isinstance(feature, PointFeature) 282 | assert feature.type == 'point' 283 | assert feature.id == 1 284 | expected_attributes = { 285 | 'bool_true': True, 286 | 'bool_false': False, 287 | 'null': None, 288 | 'string': 'a_string', 289 | 'float': 1.0, 290 | 'double': 2.0, 291 | 'inline_uint': 1, 292 | 'inline_sint': -1, 293 | 'uint': 2**60, 294 | 'int': -2**60, 295 | 'dlist': [1.0, 2.0, 3.0, 3.5, 4.5, 6.899999998509884], 296 | 'map': {'key1': 1, 'nested_map': { 'key': 1 }, 'nested_list': [1, 2, 3]}, 297 | 'list': [True, False, None, 'a_string', 1.0, 2.0, 1, -1, 2**60, -2**60, {'key1': 1}, [1,2,3], [1.0, 2.0, 3.0, 3.5, 4.5, 6.899999998509884]] 298 | } 299 | assert feature.attributes == expected_attributes 300 | 301 | -------------------------------------------------------------------------------- /tests/test_encode.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from vector_tile_base import vector_tile_pb2 3 | from vector_tile_base import VectorTile, SplineFeature, PointFeature, PolygonFeature, LineStringFeature, Layer, FeatureAttributes, Float, FloatList 4 | 5 | def test_no_layers(): 6 | vt = VectorTile() 7 | assert len(vt.serialize()) == 0 8 | 9 | def test_create_layer(): 10 | vt = VectorTile() 11 | layer = vt.add_layer('point') 12 | assert layer.name == 'point' 13 | assert layer.version == 2 14 | assert isinstance(layer, Layer) 15 | layer = vt.add_layer('point_3', 3) 16 | assert layer.name == 'point_3' 17 | assert layer.version == 3 18 | assert isinstance(layer, Layer) 19 | layer = vt.add_layer('point_4', 4) 20 | assert layer.name == 'point_4' 21 | assert layer.version == 4 22 | assert isinstance(layer, Layer) 23 | 24 | def test_layer_extent(): 25 | vt = VectorTile() 26 | layer = vt.add_layer('test') 27 | assert layer.extent == 4096 28 | layer.extent = 8000 29 | assert layer.extent == 8000 30 | 31 | def test_layer_zoom_x_y(): 32 | vt = VectorTile() 33 | layer = vt.add_layer('test0', version=2) 34 | assert layer.x == None 35 | assert layer.y == None 36 | assert layer.zoom == None 37 | with pytest.raises(Exception): 38 | layer.set_tile_location(zoom=3, x=4, y=2) 39 | layer = vt.add_layer('test1', version=3) 40 | assert layer.x == None 41 | assert layer.y == None 42 | assert layer.zoom == None 43 | layer.set_tile_location(zoom=3, x=4, y=2) 44 | assert layer.x == 4 45 | assert layer.y == 2 46 | assert layer.zoom == 3 47 | layer = vt.add_layer('test2', version=3, x=0, y=0, zoom=0) 48 | assert layer.x == 0 49 | assert layer.y == 0 50 | assert layer.zoom == 0 51 | layer.set_tile_location(zoom=3, x=4, y=2) 52 | assert layer.x == 4 53 | assert layer.y == 2 54 | assert layer.zoom == 3 55 | with pytest.raises(Exception): 56 | layer.x = 5 57 | with pytest.raises(Exception): 58 | layer.y = 5 59 | with pytest.raises(Exception): 60 | layer.zoom = 4 61 | with pytest.raises(Exception): 62 | layer = vt.add_layer('test2', version=2, x=0, y=0, zoom=0) 63 | with pytest.raises(Exception): 64 | vt.add_layer('test3', version=3, x=-1, y=0, zoom=0) 65 | with pytest.raises(Exception): 66 | vt.add_layer('test4', version=3, x=1, y=0, zoom=0) 67 | with pytest.raises(Exception): 68 | vt.add_layer('test5', version=3, x=2, y=0, zoom=1) 69 | with pytest.raises(Exception): 70 | vt.add_layer('test3', version=3, x=0, y=-1, zoom=0) 71 | with pytest.raises(Exception): 72 | vt.add_layer('test4', version=3, x=0, y=1, zoom=0) 73 | with pytest.raises(Exception): 74 | vt.add_layer('test5', version=3, x=0, y=2, zoom=1) 75 | with pytest.raises(Exception): 76 | vt.add_layer('test3', version=3, x=0, y=0, zoom=-1) 77 | with pytest.raises(Exception): 78 | vt.add_layer('test3', version=3, x=0, y=0, zoom=51) 79 | 80 | def test_layer_name(): 81 | vt = VectorTile() 82 | layer = vt.add_layer('test') 83 | assert layer.name == 'test' 84 | layer.name = 'foo' 85 | assert layer.name == 'foo' 86 | 87 | def test_layer_features(): 88 | vt = VectorTile() 89 | layer = vt.add_layer('test') 90 | assert len(layer.features) == 0 91 | assert isinstance(layer.features, list) 92 | with pytest.raises(AttributeError): 93 | layer.features = [1,2] 94 | assert len(layer.features) == 0 95 | 96 | def test_feature_id(): 97 | vt = VectorTile() 98 | layer = vt.add_layer('test', version=2) 99 | feature = layer.add_point_feature() 100 | assert feature.id == None 101 | feature.id = 12 102 | assert feature.id == 12 103 | # Fails for a version 2 layer 104 | with pytest.raises(Exception): 105 | feature.id = "FeatureName" 106 | 107 | layer = vt.add_layer('test2', version=3) 108 | feature = layer.add_point_feature() 109 | assert feature.id == None 110 | feature.id = 12 111 | assert feature.id == 12 112 | feature.id = "FeatureName" 113 | assert feature.id == "FeatureName" 114 | 115 | data = vt.serialize() 116 | vt = VectorTile(data) 117 | feature = vt.layers[0].features[0] 118 | assert feature.id == 12 119 | feature = vt.layers[1].features[0] 120 | assert feature.id == "FeatureName" 121 | 122 | def test_feature_attributes_version_2(): 123 | vt = VectorTile() 124 | layer = vt.add_layer('test', version=2) 125 | assert layer.version == 2 126 | feature = layer.add_point_feature() 127 | assert isinstance(feature, PointFeature) 128 | assert len(layer.features) == 1 129 | assert feature == layer.features[0] 130 | prop = feature.attributes 131 | assert isinstance(prop, FeatureAttributes) 132 | assert feature.attributes == {} 133 | assert prop == {} 134 | prop['fun'] = 'stuff' 135 | assert 'fun' in prop 136 | assert prop['fun'] == 'stuff' 137 | assert feature.attributes['fun'] == 'stuff' 138 | assert feature.attributes == {'fun':'stuff'} 139 | # Can set by external dictionary 140 | prop_dict = { 'number': 1, 'bool': True, 'string': 'foo', 'float': 4.1 } 141 | feature.attributes = prop_dict 142 | assert feature.attributes == prop_dict 143 | # Key error on not existant property 144 | with pytest.raises(KeyError): 145 | foo = feature.attributes['doesnotexist'] 146 | # Type errors on invalid key types 147 | with pytest.raises(TypeError): 148 | feature.attributes[1.234] = True 149 | with pytest.raises(TypeError): 150 | feature.attributes[1] = True 151 | with pytest.raises(TypeError): 152 | foo = feature.attributes[1.234] 153 | with pytest.raises(TypeError): 154 | foo = feature.attributes[1] 155 | # During setting invalid attributes with bad keys or value types will just be dropped 156 | prop_dict = {'foo': [1,2,3], 'fee': [{'a':'b'}, {'a':['c','d']}], 1.2341: 'stuff', 1: 'fish', 'go': False, 'double': 2.32432, 'float': Float(23432.3222) } 157 | prop_dict2 = {'go': False, 'double': 2.32432, 'float': Float(23432.3222) } 158 | feature.attributes = prop_dict 159 | assert feature.attributes != prop_dict 160 | assert feature.attributes == prop_dict2 161 | 162 | # Show that geometric attributes don't work with version 2 163 | with pytest.raises(Exception): 164 | feature.geometric_attributes = {'hmm': [1,2,3,4,5]} 165 | 166 | # Now serialize the tile 167 | data = vt.serialize() 168 | # Reload as new tile to check that cursor moves to proper position for another add point 169 | vt = VectorTile(data) 170 | feature = vt.layers[0].features[0] 171 | assert feature.attributes['go'] == prop_dict2['go'] 172 | assert feature.attributes['double'] == prop_dict2['double'] 173 | # note change is expected due to float encoding! 174 | assert feature.attributes['float'] == 23432.322265625 175 | # Show that geometric attributes don't work with version 2 176 | assert feature.geometric_attributes == {} 177 | 178 | def test_feature_attributes_version_3_legacy(): 179 | vt = VectorTile() 180 | layer = vt.add_layer('test', version=3, legacy_attributes=True) 181 | assert layer.version == 3 182 | feature = layer.add_point_feature() 183 | assert isinstance(feature, PointFeature) 184 | assert len(layer.features) == 1 185 | assert feature == layer.features[0] 186 | prop = feature.attributes 187 | assert isinstance(prop, FeatureAttributes) 188 | assert feature.attributes == {} 189 | assert prop == {} 190 | prop['fun'] = 'stuff' 191 | assert 'fun' in prop 192 | assert prop['fun'] == 'stuff' 193 | assert feature.attributes['fun'] == 'stuff' 194 | assert feature.attributes == {'fun':'stuff'} 195 | # Can set by external dictionary 196 | prop_dict = { 'number': 1, 'bool': True, 'string': 'foo', 'float': 4.1 } 197 | feature.attributes = prop_dict 198 | assert feature.attributes == prop_dict 199 | # Key error on not existant property 200 | with pytest.raises(KeyError): 201 | foo = feature.attributes['doesnotexist'] 202 | # Type errors on invalid key types 203 | with pytest.raises(TypeError): 204 | feature.attributes[1.234] = True 205 | with pytest.raises(TypeError): 206 | feature.attributes[1] = True 207 | with pytest.raises(TypeError): 208 | foo = feature.attributes[1.234] 209 | with pytest.raises(TypeError): 210 | foo = feature.attributes[1] 211 | assert len(layer._values) == 5 212 | # During setting invalid attributes with bad keys or value types will just be dropped 213 | prop_dict = { 214 | 'foo': [1,2,3], 215 | 'fee': [{'a':'b'}, {'a':['c','d']}], 216 | 1.2341: 'stuff', 217 | 1: 'fish', 218 | 'go': False, 219 | 'double': 2.32432, 220 | 'float': Float(23432.3222), 221 | 'double2': 23432.3222, 222 | 'double3': 23432.3222 223 | } 224 | prop_dict2 = { 225 | 'go': False, 226 | 'double': 2.32432, 227 | 'float': Float(23432.3222), 228 | 'double2': 23432.3222, 229 | 'double3': 23432.3222 230 | } 231 | feature.attributes = prop_dict 232 | assert feature.attributes != prop_dict 233 | assert feature.attributes == prop_dict2 234 | assert len(layer._values) == 9 235 | 236 | # Show that geometric attributes don't work with version 3 legacy attributes 237 | with pytest.raises(Exception): 238 | feature.geometric_attributes = {'hmm': [1,2,3,4,5]} 239 | 240 | # Now serialize the tile 241 | data = vt.serialize() 242 | # Reload as new tile to check that cursor moves to proper position for another add point 243 | vt = VectorTile(data) 244 | feature = vt.layers[0].features[0] 245 | assert feature.attributes['go'] == prop_dict2['go'] 246 | assert feature.attributes['double'] == prop_dict2['double'] 247 | # note change is expected due to float encoding! 248 | assert feature.attributes['float'] == 23432.322265625 249 | # Show that geometric attributes don't work with version 3 with legacy attributes 250 | assert feature.geometric_attributes == {} 251 | 252 | def test_feature_attributes_version_3(): 253 | vt = VectorTile() 254 | layer = vt.add_layer('test', version=3) 255 | assert layer.version == 3 256 | feature = layer.add_point_feature() 257 | assert isinstance(feature, PointFeature) 258 | assert len(layer.features) == 1 259 | assert feature == layer.features[0] 260 | prop = feature.attributes 261 | assert isinstance(prop, FeatureAttributes) 262 | assert feature.attributes == {} 263 | assert prop == {} 264 | prop['fun'] = 'stuff' 265 | assert 'fun' in prop 266 | assert prop['fun'] == 'stuff' 267 | assert feature.attributes['fun'] == 'stuff' 268 | assert feature.attributes == {'fun':'stuff'} 269 | # Can set by external dictionary 270 | prop_dict = { 'number': 1, 'bool': True, 'string': 'foo', 'float': 4.1 } 271 | feature.attributes = prop_dict 272 | assert feature.attributes == prop_dict 273 | # Key error on not existant property 274 | with pytest.raises(KeyError): 275 | foo = feature.attributes['doesnotexist'] 276 | # Type errors on invalid key types 277 | with pytest.raises(TypeError): 278 | feature.attributes[1.234] = True 279 | with pytest.raises(TypeError): 280 | feature.attributes[1] = True 281 | with pytest.raises(TypeError): 282 | foo = feature.attributes[1.234] 283 | with pytest.raises(TypeError): 284 | foo = feature.attributes[1] 285 | 286 | dvalues = [1.0, 2.0, None, 2.3, 4.3, None, None, 15.0, 22.5] 287 | scaling1 = layer.add_attribute_scaling(precision=10.0**-6, min_value=1.0, max_value=25.0) 288 | assert scaling1.index == 0 289 | assert scaling1.offset == 0 290 | assert scaling1.base == 1.0 291 | assert scaling1.multiplier == 9.5367431640625e-07 292 | 293 | # roughly 10**-8 precision 294 | scaling2 = layer.add_attribute_scaling(offset=10, base=8.0, multiplier=7.450580596923828e-09) 295 | assert scaling2.index == 1 296 | assert scaling2.offset == 10 297 | assert scaling2.base == 8.0 298 | assert scaling2.multiplier == 7.450580596923828e-09 299 | 300 | flist1 = FloatList(scaling1, dvalues) 301 | flist2 = FloatList(scaling2, dvalues) 302 | 303 | # During setting invalid attributes with bad keys or value types will just be dropped 304 | prop_dict = { 305 | 'foo': [1,2,3], 306 | 'fee': [{'a':'b'}, {'a':['c','d']}], 307 | 1.2341: 'stuff', 308 | 1: 'fish', 309 | 'go': False, 310 | 'double': 2.32432, 311 | 'float': Float(23432.3222), 312 | 'doubleList': flist1, 313 | 'otherDoubleList': flist2 314 | } 315 | prop_dict2 = { 316 | 'foo': [1,2,3], 317 | 'fee': [{'a':'b'}, {'a':['c','d']}], 318 | 'go': False, 319 | 'double': 2.32432, 320 | 'float': Float(23432.3222), 321 | 'doubleList': flist1, 322 | 'otherDoubleList': flist2 323 | } 324 | geometric_dict = { 325 | 'now': [1,2,4,5,234], 326 | 'stuff': [{'x':12}, None, None, None, {'y':13}], 327 | 'dlist': flist2, 328 | 'not': None, 329 | 'this': 8, 330 | 'or': True, 331 | 'that': { 'lost':'values'} 332 | } 333 | geometric_dict2 = { 334 | 'now': [1,2,4,5,234], 335 | 'stuff': [{'x':12}, None, None, None, {'y':13}], 336 | 'dlist': flist2 337 | } 338 | feature.attributes = prop_dict 339 | assert feature.attributes != prop_dict 340 | assert feature.attributes == prop_dict2 341 | 342 | feature.geometric_attributes = geometric_dict 343 | assert feature.geometric_attributes == geometric_dict2 344 | 345 | # Now serialize the tile 346 | data = vt.serialize() 347 | # Reload as new tile to check that cursor moves to proper position for another add point 348 | vt = VectorTile(data) 349 | feature = vt.layers[0].features[0] 350 | assert feature.attributes['foo'] == prop_dict2['foo'] 351 | assert feature.attributes['fee'] == prop_dict2['fee'] 352 | assert feature.attributes['go'] == prop_dict2['go'] 353 | assert feature.attributes['double'] == prop_dict2['double'] 354 | # note change is expected due to float encoding! 355 | assert feature.attributes['float'] == 23432.322265625 356 | # specialized double encoding with scaling should result in approximate equality 357 | assert isinstance(feature.attributes['doubleList'], list) 358 | dlist = feature.attributes['doubleList'] 359 | for i in range(len(dlist)): 360 | if dvalues[i] is None: 361 | assert dlist[i] is None 362 | else: 363 | assert abs(dvalues[i] - dlist[i]) < 10.0**-6 364 | assert isinstance(feature.attributes['otherDoubleList'], list) 365 | dlist = feature.attributes['otherDoubleList'] 366 | for i in range(len(dlist)): 367 | if dvalues[i] is None: 368 | assert dlist[i] is None 369 | else: 370 | assert abs(dvalues[i] - dlist[i]) < 10.0**-8 371 | 372 | assert feature.geometric_attributes['now'] == geometric_dict2['now'] 373 | assert feature.geometric_attributes['stuff'] == geometric_dict2['stuff'] 374 | dlist = feature.geometric_attributes['dlist'] 375 | for i in range(len(dlist)): 376 | if dvalues[i] is None: 377 | assert dlist[i] is None 378 | else: 379 | assert abs(dvalues[i] - dlist[i]) < 10.0**-8 380 | 381 | def test_create_point_feature(): 382 | vt = VectorTile() 383 | layer = vt.add_layer('test') 384 | feature = layer.add_point_feature() 385 | assert isinstance(feature, PointFeature) 386 | assert len(layer.features) == 1 387 | assert feature == layer.features[0] 388 | assert not feature.has_elevation 389 | feature.add_points([10,11]) 390 | geometry = feature.get_points() 391 | assert geometry[0] == [10,11] 392 | # add points simply adds to end 393 | feature.add_points([10,12]) 394 | feature.add_points([10,13]) 395 | geometry = feature.get_points() 396 | assert geometry[1] == [10,12] 397 | assert geometry[2] == [10,13] 398 | # clear current geometry 399 | feature.clear_geometry() 400 | assert feature.get_points() == [] 401 | # This is proper way to add multiple points! 402 | feature.add_points([[10,11],[10,12],[10,13]]) 403 | assert feature.get_points() == [[10,11],[10,12],[10,13]] 404 | 405 | # Now serialize the tile 406 | data = vt.serialize() 407 | # Reload as new tile to check that cursor moves to proper position for another add point 408 | vt = VectorTile(data) 409 | feature = vt.layers[0].features[0] 410 | feature.add_points([10,14]) 411 | assert feature.get_points() == [[10,11],[10,12],[10,13],[10,14]] 412 | 413 | def test_create_point_feature_3d(): 414 | vt = VectorTile() 415 | layer = vt.add_layer('test') 416 | ## Should fail first because layer is a version 2 layer 417 | with pytest.raises(Exception): 418 | feature = layer.add_point_feature(has_elevation=True) 419 | vt = VectorTile() 420 | layer = vt.add_layer('test', version=3) 421 | feature = layer.add_point_feature(has_elevation=True) 422 | assert isinstance(feature, PointFeature) 423 | assert len(layer.features) == 1 424 | assert feature == layer.features[0] 425 | assert feature.has_elevation 426 | ## Should fail to add 2 point list 427 | with pytest.raises(IndexError): 428 | feature.add_points([10,11]) 429 | feature.add_points([10,11,12]) 430 | geometry = feature.get_points() 431 | assert geometry[0] == [10,11,12] 432 | # add points simply adds to end 433 | feature.add_points([10,12,13]) 434 | feature.add_points([10,13,14]) 435 | geometry = feature.get_points() 436 | assert geometry[1] == [10,12,13] 437 | assert geometry[2] == [10,13,14] 438 | # clear current geometry 439 | feature.clear_geometry() 440 | assert feature.get_points() == [] 441 | # This is proper way to add multiple points! 442 | feature.add_points([[10,11,12],[10,12,13],[10,13,14]]) 443 | assert feature.get_points() == [[10,11,12],[10,12,13],[10,13,14]] 444 | 445 | # Now serialize the tile 446 | data = vt.serialize() 447 | # Reload as new tile to check that cursor moves to proper position for another add point 448 | vt = VectorTile(data) 449 | feature = vt.layers[0].features[0] 450 | feature.add_points([10,14,15]) 451 | assert feature.get_points() == [[10,11,12],[10,12,13],[10,13,14],[10,14,15]] 452 | 453 | def test_create_line_feature(): 454 | vt = VectorTile() 455 | layer = vt.add_layer('test') 456 | feature = layer.add_line_string_feature() 457 | assert isinstance(feature, LineStringFeature) 458 | assert len(layer.features) == 1 459 | assert feature == layer.features[0] 460 | assert not feature.has_elevation 461 | line_string = [[10,11],[10,12],[10,13],[10,14]] 462 | feature.add_line_string(line_string) 463 | # note that we pull back possible multi line string here 464 | assert feature.get_line_strings() == [line_string] 465 | 466 | bad_line_string = [[1,1]] 467 | with pytest.raises(Exception): 468 | feature.add_line_string(bad_line_string) 469 | assert feature.get_line_strings() == [line_string] 470 | bad_line_string2 = [[1,1], [0]] 471 | with pytest.raises(IndexError): 472 | feature.add_line_string(bad_line_string2) 473 | assert feature.get_line_strings() == [line_string] 474 | 475 | line_string2 = [[9,9],[30,5]] 476 | feature.add_line_string(line_string2) 477 | assert feature.get_line_strings() == [line_string, line_string2] 478 | 479 | # clear current geometry 480 | feature.clear_geometry() 481 | assert feature.get_line_strings() == [] 482 | feature.add_line_string(line_string) 483 | assert feature.get_line_strings() == [line_string] 484 | 485 | # Now serialize the tile 486 | data = vt.serialize() 487 | # Reload as new tile to check that cursor moves to proper position for another add point 488 | vt = VectorTile(data) 489 | feature = vt.layers[0].features[0] 490 | feature.add_line_string(line_string2) 491 | assert feature.get_line_strings() == [line_string, line_string2] 492 | 493 | def test_create_line_feature_3d(): 494 | vt = VectorTile() 495 | layer = vt.add_layer('test') 496 | # Should raise because is version 2 tile 497 | with pytest.raises(Exception): 498 | feature = layer.add_line_string_feature(has_elevation=True) 499 | vt = VectorTile() 500 | layer = vt.add_layer('test', version=3) 501 | feature = layer.add_line_string_feature(has_elevation=True) 502 | assert isinstance(feature, LineStringFeature) 503 | assert len(layer.features) == 1 504 | assert feature == layer.features[0] 505 | assert feature.has_elevation 506 | line_string = [[10,11,12],[10,12,13],[10,13,14],[10,14,15]] 507 | feature.add_line_string(line_string) 508 | # note that we pull back possible multi line string here 509 | assert feature.get_line_strings() == [line_string] 510 | 511 | bad_line_string = [[1,1,1]] 512 | with pytest.raises(Exception): 513 | feature.add_line_string(bad_line_string) 514 | assert feature.get_line_strings() == [line_string] 515 | bad_line_string2 = [[1,1],[2,2]] 516 | with pytest.raises(IndexError): 517 | feature.add_line_string(bad_line_string2) 518 | assert feature.get_line_strings() == [line_string] 519 | 520 | line_string2 = [[9,9,9],[30,5,9]] 521 | feature.add_line_string(line_string2) 522 | assert feature.get_line_strings() == [line_string, line_string2] 523 | 524 | # clear current geometry 525 | feature.clear_geometry() 526 | assert feature.get_line_strings() == [] 527 | feature.add_line_string(line_string) 528 | assert feature.get_line_strings() == [line_string] 529 | 530 | # Now serialize the tile 531 | data = vt.serialize() 532 | # Reload as new tile to check that cursor moves to proper position for another add point 533 | vt = VectorTile(data) 534 | feature = vt.layers[0].features[0] 535 | feature.add_line_string(line_string2) 536 | assert feature.get_line_strings() == [line_string, line_string2] 537 | 538 | def test_create_polygon_feature(): 539 | vt = VectorTile() 540 | layer = vt.add_layer('test') 541 | feature = layer.add_polygon_feature() 542 | assert isinstance(feature, PolygonFeature) 543 | assert len(layer.features) == 1 544 | assert feature == layer.features[0] 545 | assert not feature.has_elevation 546 | polygon = [[[0,0],[10,0],[10,10],[0,10],[0,0]],[[3,3],[3,5],[5,5],[3,3]]] 547 | feature.add_ring(polygon[0]) 548 | feature.add_ring(polygon[1]) 549 | assert feature.get_rings() == polygon 550 | assert feature.get_polygons() == [polygon] 551 | 552 | bad_ring1 = [[0,0],[1,0]] 553 | with pytest.raises(Exception): 554 | feature.add_ring(bad_ring) 555 | assert feature.get_polygons() == [polygon] 556 | 557 | bad_ring2 = [[0,0],[1,0],[0,0]] 558 | with pytest.raises(Exception): 559 | feature.add_ring(bad_ring2) 560 | assert feature.get_polygons() == [polygon] 561 | 562 | bad_ring3 = [[0,0],[1,0],[1,1],[1],[0,0]] 563 | with pytest.raises(IndexError): 564 | feature.add_ring(bad_ring3) 565 | assert feature.get_polygons() == [polygon] 566 | 567 | feature.add_ring(polygon[0]) 568 | feature.add_ring(polygon[1]) 569 | assert feature.get_polygons() == [polygon, polygon] 570 | 571 | # clear current geometry 572 | feature.clear_geometry() 573 | assert feature.get_rings() == [] 574 | assert feature.get_polygons() == [] 575 | 576 | # Add in opposite order 577 | feature.add_ring(polygon[1]) 578 | feature.add_ring(polygon[0]) 579 | assert feature.get_rings() == [polygon[1], polygon[0]] 580 | # First ring in wrong winding order so dropped from polygon output 581 | assert feature.get_polygons() == [[polygon[0]]] 582 | 583 | # clear current geometry 584 | feature.clear_geometry() 585 | assert feature.get_rings() == [] 586 | assert feature.get_polygons() == [] 587 | 588 | feature.add_ring(polygon[0]) 589 | feature.add_ring(polygon[1]) 590 | assert feature.get_rings() == polygon 591 | assert feature.get_polygons() == [polygon] 592 | 593 | # Now serialize the tile 594 | data = vt.serialize() 595 | # Reload as new tile to check that cursor moves to proper position for another add point 596 | vt = VectorTile(data) 597 | feature = vt.layers[0].features[0] 598 | assert feature.get_rings() == polygon 599 | assert feature.get_polygons() == [polygon] 600 | feature.add_ring(polygon[0]) 601 | feature.add_ring(polygon[1]) 602 | assert feature.get_polygons() == [polygon, polygon] 603 | 604 | def test_create_polygon_feature_3d(): 605 | vt = VectorTile() 606 | layer = vt.add_layer('test') 607 | # Should not be allowed with version 2 layer 608 | with pytest.raises(Exception): 609 | feature = layer.add_polygon_feature(has_elevation=True) 610 | vt = VectorTile() 611 | layer = vt.add_layer('test', version=3) 612 | feature = layer.add_polygon_feature(has_elevation=True) 613 | assert isinstance(feature, PolygonFeature) 614 | assert len(layer.features) == 1 615 | assert feature == layer.features[0] 616 | assert feature.has_elevation 617 | polygon = [[[0,0,1],[10,0,1],[10,10,1],[0,10,1],[0,0,1]],[[3,3,1],[3,5,1],[5,5,1],[3,3,1]]] 618 | feature.add_ring(polygon[0]) 619 | feature.add_ring(polygon[1]) 620 | assert feature.get_rings() == polygon 621 | assert feature.get_polygons() == [polygon] 622 | 623 | bad_ring1 = [[0,0,1],[1,0,1]] 624 | with pytest.raises(Exception): 625 | feature.add_ring(bad_ring) 626 | assert feature.get_polygons() == [polygon] 627 | 628 | bad_ring2 = [[0,0,1],[1,0,1],[0,0,1]] 629 | with pytest.raises(Exception): 630 | feature.add_ring(bad_ring2) 631 | assert feature.get_polygons() == [polygon] 632 | 633 | bad_ring3 = [[0,0,1],[1,0,1],[1,1,1],[1,1],[0,0,1]] 634 | with pytest.raises(IndexError): 635 | feature.add_ring(bad_ring3) 636 | assert feature.get_polygons() == [polygon] 637 | 638 | feature.add_ring(polygon[0]) 639 | feature.add_ring(polygon[1]) 640 | assert feature.get_polygons() == [polygon, polygon] 641 | 642 | # clear current geometry 643 | feature.clear_geometry() 644 | assert feature.get_rings() == [] 645 | assert feature.get_polygons() == [] 646 | 647 | feature.add_ring(polygon[0]) 648 | feature.add_ring(polygon[1]) 649 | assert feature.get_rings() == polygon 650 | assert feature.get_polygons() == [polygon] 651 | 652 | # Now serialize the tile 653 | data = vt.serialize() 654 | # Reload as new tile to check that cursor moves to proper position for another add point 655 | vt = VectorTile(data) 656 | feature = vt.layers[0].features[0] 657 | assert feature.get_rings() == polygon 658 | assert feature.get_polygons() == [polygon] 659 | feature.add_ring(polygon[0]) 660 | feature.add_ring(polygon[1]) 661 | assert feature.get_polygons() == [polygon, polygon] 662 | 663 | def test_create_spline_feature_fail_v2(): 664 | vt = VectorTile() 665 | layer = vt.add_layer('test') 666 | with pytest.raises(Exception): 667 | feature = layer.add_spline_feature() 668 | 669 | def test_create_spline_feature(): 670 | vt = VectorTile() 671 | layer = vt.add_layer('test', version=3) 672 | feature = layer.add_spline_feature() 673 | assert isinstance(feature, SplineFeature) 674 | assert len(layer.features) == 1 675 | assert feature == layer.features[0] 676 | assert not feature.has_elevation 677 | assert feature.degree == 2 678 | 679 | scaling = layer.add_attribute_scaling(precision=10.0**-8, min_value=0.0, max_value=25.0) 680 | bad_control_points1 = [[8,10]] 681 | knot_values = [0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0] 682 | knots = FloatList(scaling, knot_values) 683 | with pytest.raises(Exception): 684 | feature.add_spline(bad_control_points1, knots) 685 | bad_control_points2 = [[8,10],[9,11],[9],[12,10]] 686 | with pytest.raises(IndexError): 687 | feature.add_spline(bad_control_points2, knots) 688 | 689 | control_points = [[8,10],[9,11],[11,9],[12,10]] 690 | feature.add_spline(control_points, knots) 691 | 692 | assert feature.get_splines() == [[control_points, knot_values]] 693 | 694 | def test_create_spline_feature_3d(): 695 | vt = VectorTile() 696 | layer = vt.add_layer('test', version=3) 697 | feature = layer.add_spline_feature(has_elevation=True) 698 | assert isinstance(feature, SplineFeature) 699 | assert len(layer.features) == 1 700 | assert feature == layer.features[0] 701 | assert feature.has_elevation 702 | assert feature.degree == 2 703 | 704 | scaling = layer.add_attribute_scaling(precision=10.0**-8, min_value=0.0, max_value=25.0) 705 | bad_control_points1 = [[8,10,1]] 706 | knot_values = [0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0] 707 | knots = FloatList(scaling, knot_values) 708 | with pytest.raises(Exception): 709 | feature.add_spline(bad_control_points1, knots) 710 | bad_control_points2 = [[8,10,1],[9,11,1],[9,1],[12,10,1]] 711 | with pytest.raises(IndexError): 712 | feature.add_spline(bad_control_points2, knots) 713 | 714 | control_points = [[8,10,1],[9,11,1],[11,9,1],[12,10,1]] 715 | feature.add_spline(control_points, knots) 716 | 717 | assert feature.get_splines() == [[control_points, knot_values]] 718 | 719 | def test_create_spline_feature_3d_with_elevation_scaling(): 720 | vt = VectorTile() 721 | layer = vt.add_layer('test', version=3) 722 | 723 | scaling = layer.add_attribute_scaling(precision=10.0**-8, min_value=0.0, max_value=25.0) 724 | knot_values = [0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0] 725 | knots = FloatList(scaling, knot_values) 726 | control_points = [[8,10,-11000.0],[9,11,9000.0],[11,9,85.1],[12,10,1500.74]] 727 | 728 | layer.add_elevation_scaling(precision=10.0**-8, min_value=-11000.0, max_value=9000.0) 729 | with pytest.raises(Exception): 730 | feature = layer.add_spline_feature(has_elevation=True) 731 | feature.add_spline(control_points, knots) 732 | 733 | feature = layer.add_spline_feature(has_elevation=True) 734 | layer.add_elevation_scaling(precision=10.0**-5, min_value=-11000.0, max_value=9000.0) 735 | feature.add_spline(control_points, knots) 736 | 737 | out = feature.get_splines() 738 | assert len(out) == 1 739 | assert len(out[0]) == 2 740 | out_control_points = out[0][0] 741 | out_knot_values = out[0][1] 742 | assert out_knot_values == knot_values 743 | assert len(control_points) == len(out_control_points) 744 | for i in range(len(control_points)): 745 | assert len(control_points[i]) == len(out_control_points[i]) 746 | assert out_control_points[i][0] == control_points[i][0] 747 | assert out_control_points[i][1] == control_points[i][1] 748 | assert out_control_points[i][2] == pytest.approx(control_points[i][2]) 749 | 750 | 751 | def test_float(): 752 | x1 = Float(293.045998633665) 753 | x2 = Float(293.045998634) 754 | x3 = Float(293.045998633748) 755 | vm = vector_tile_pb2.Tile.Value() 756 | vm.float_value = 293.045998633665 757 | x1_encoded = vm.float_value 758 | vm.float_value = 293.045998634 759 | x2_encoded = vm.float_value 760 | vm.float_value = 293.045998633748 761 | x3_encoded = vm.float_value 762 | assert x1 == x2 763 | assert x1 == x3 764 | assert x1_encoded == x2_encoded 765 | assert x1_encoded == x3_encoded 766 | assert x1 == x1_encoded 767 | assert x2 == x2_encoded 768 | assert x3 == x3_encoded 769 | -------------------------------------------------------------------------------- /tests/test_scaling.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from vector_tile_base.engine import scaling_calculation, zig_zag_encode_64 3 | from random import random, uniform 4 | 5 | def encode_to_int(dlist, sF, base, offset): 6 | return [int(round(sF*(d-base) - offset)) for d in dlist] 7 | 8 | def decode_to_float(ilist, sR, base, offset): 9 | return [sR*(i + offset) + base for i in ilist] 10 | 11 | def delta_encode(ilist, offset): 12 | prev = offset 13 | out = [] 14 | for i in ilist: 15 | out.append(i - prev) 16 | prev = i 17 | return out 18 | 19 | def find_diff(dlist, new_dlist): 20 | return [abs(dlist[i] - new_dlist[i]) for i in range(len(dlist))] 21 | 22 | def calculate_size(some_data): 23 | size = 0 24 | for v in some_data: 25 | if isinstance(v, float): 26 | size = size + 8 27 | else: 28 | n = zig_zag_encode_64(v) >> 7 29 | size = size + 1 30 | while n != 0: 31 | n = n >> 7 32 | size = size + 1 33 | return size 34 | 35 | def test_scaling_range(): 36 | minf = 0.0 37 | maxf = 10.0**8 38 | dlist = [uniform(-maxf, maxf) for x in range(1000)] 39 | precision = 10**-6 40 | out = scaling_calculation(precision, minf, maxf) 41 | ilist = encode_to_int(dlist, out['sF'], out['base'], 0) 42 | ilist_delta = delta_encode(ilist, 0) 43 | new_dlist = decode_to_float(ilist, out['sR'], out['base'], 0) 44 | ilist_sorted = ilist 45 | ilist_sorted.sort() 46 | ilist_delta_sorted = delta_encode(ilist_sorted, 0) 47 | 48 | dlist_size = calculate_size(dlist) 49 | ilist_size = calculate_size(ilist) 50 | ilist_delta_size = calculate_size(ilist_delta) 51 | ilist_delta_sorted_size = calculate_size(ilist_delta_sorted) 52 | assert dlist_size > ilist_size 53 | 54 | assert ilist_size > ilist_delta_sorted_size 55 | assert ilist_delta_size > ilist_delta_sorted_size 56 | 57 | max_diff = max(find_diff(dlist, new_dlist)) 58 | assert max_diff < precision 59 | 60 | def test_bad_scaling_values(): 61 | with pytest.raises(Exception): 62 | scaling_calculation(-1.0, 0.0, 10.0) 63 | with pytest.raises(Exception): 64 | scaling_calculation(100.0, 0.0, 10.0) 65 | -------------------------------------------------------------------------------- /tests/test_zig_zag.py: -------------------------------------------------------------------------------- 1 | from vector_tile_base.engine import zig_zag_encode_64, zig_zag_encode, zig_zag_decode 2 | 3 | def test_zig_zag_encode(): 4 | assert zig_zag_encode(0) == 0 5 | assert zig_zag_encode(-1) == 1 6 | assert zig_zag_encode(1) == 2 7 | assert zig_zag_encode(-2) == 3 8 | assert zig_zag_encode(2) == 4 9 | assert zig_zag_encode(-((2**31)-1)) == 4294967293 10 | assert zig_zag_encode((2**31)-1) == 4294967294 11 | assert zig_zag_encode(-(2**31)) == 4294967295 12 | ## Fails after this value 13 | assert zig_zag_encode(2**31) != 4294967296 14 | 15 | def test_zig_zag_encode_64(): 16 | assert zig_zag_encode_64(0) == 0 17 | assert zig_zag_encode_64(-1) == 1 18 | assert zig_zag_encode_64(1) == 2 19 | assert zig_zag_encode_64(-2) == 3 20 | assert zig_zag_encode_64(2) == 4 21 | assert zig_zag_encode_64(-(2**31-1)) == 4294967293 22 | assert zig_zag_encode_64(2**31-1) == 4294967294 23 | assert zig_zag_encode_64(-(2**31)) == 4294967295 24 | assert zig_zag_encode_64(2**31) == 4294967296 25 | assert zig_zag_encode_64(-(2**63-1)) == 18446744073709551613 26 | assert zig_zag_encode_64(2**63-1) == 18446744073709551614 27 | assert zig_zag_encode_64(-(2**63)) == 18446744073709551615 28 | # Fails after this value 29 | assert zig_zag_encode_64(2**63) != 18446744073709551616 30 | 31 | def test_zig_zag_decode(): 32 | assert zig_zag_decode(0) == 0 33 | assert zig_zag_decode(1) == -1 34 | assert zig_zag_decode(2) == 1 35 | assert zig_zag_decode(3) == -2 36 | assert zig_zag_decode(4) == 2 37 | assert zig_zag_decode(4294967293) == -2147483647 38 | assert zig_zag_decode(4294967294) == 2147483647 39 | assert zig_zag_decode(4294967295) == -2147483648 40 | assert zig_zag_decode(4294967296) == 2147483648 41 | assert zig_zag_decode(18446744073709551613) == -9223372036854775807 42 | assert zig_zag_decode(18446744073709551614) == 9223372036854775807 43 | assert zig_zag_decode(18446744073709551615) == -9223372036854775808 44 | # Upper limit not the same due to implementation 45 | assert zig_zag_decode(18446744073709551616) == 9223372036854775808 46 | # show round trip fails before after the point 47 | assert zig_zag_encode_64(zig_zag_decode(18446744073709551615)) == 18446744073709551615 48 | assert zig_zag_encode_64(zig_zag_decode(18446744073709551616)) != 18446744073709551616 49 | -------------------------------------------------------------------------------- /vector_tile.proto: -------------------------------------------------------------------------------- 1 | package vector_tile; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message Tile { 6 | 7 | // GeomType is described in section 4.3.4 of the specification 8 | enum GeomType { 9 | UNKNOWN = 0; 10 | POINT = 1; 11 | LINESTRING = 2; 12 | POLYGON = 3; 13 | SPLINE = 4; // like a LineString, but a spline 14 | } 15 | 16 | // Variant type encoding 17 | // The use of values is described in section 4.4.1 of the specification 18 | // Deprecated in version 3. 19 | message Value { 20 | // Exactly one of these values must be present in a valid message 21 | optional string string_value = 1; 22 | optional float float_value = 2; 23 | optional double double_value = 3; 24 | optional int64 int_value = 4; 25 | optional uint64 uint_value = 5; 26 | optional sint64 sint_value = 6; 27 | optional bool bool_value = 7; 28 | 29 | extensions 8 to max; 30 | } 31 | 32 | message Scaling { 33 | // Formula for values in dimension: 34 | // value = base + multiplier * (delta_encoded_value + offset) 35 | 36 | // Offset for deltas in this specific tile. 37 | // If not set, offset = 0 38 | optional sint64 offset = 1; 39 | 40 | // If not set, multiplier = 1.0 41 | optional double multiplier = 2; 42 | 43 | // If not set, base = 0.0 44 | optional double base = 3; 45 | } 46 | 47 | // Features are described in section 4.2 of the specification 48 | message Feature { 49 | optional uint64 id = 1 [ default = 0 ]; 50 | 51 | // Tags of this feature are encoded as repeated pairs of 52 | // integers. 53 | // A detailed description of tags is located in sections 54 | // 4.2 and 4.4 of the specification 55 | // Deprecated in version 3. Use attributes instead. 56 | repeated uint32 tags = 2 [ packed = true ]; 57 | 58 | // The type of geometry stored in this feature. 59 | optional GeomType type = 3 [ default = UNKNOWN ]; 60 | 61 | // Contains a stream of commands and parameters (vertices). 62 | // A detailed description on geometry encoding is located in 63 | // section 4.3 of the specification. 64 | repeated uint32 geometry = 4 [ packed = true ]; 65 | 66 | // Attributes of this feature in a special inline encoding. See 67 | // the spec section 4.4.2 for details. 68 | repeated uint64 attributes = 5 [ packed = true ]; 69 | 70 | // Attributes of this feature that are tied to its geometry. 71 | // The value for each key-value pair must be a list type 72 | // that contains one element per moveto, lineto, or closepath 73 | // in the geometry. 74 | repeated uint64 geometric_attributes = 6 [ packed = true ]; 75 | 76 | // Delta-encoded elevation for each moveto, lineto, or closepath 77 | // in the geometry. 78 | // These values are scaled and offset using the layer's 79 | // elevation_scaling, if present. 80 | repeated sint32 elevation = 7 [ packed = true]; 81 | 82 | repeated uint64 spline_knots = 8 [ packed = true ]; 83 | 84 | optional uint32 spline_degree = 9 [ default = 2]; 85 | 86 | // A string as a unique identifier for the Feature. Use either 87 | // id or string_id, but not both. See spec section 4.2. 88 | optional string string_id = 10; 89 | 90 | } 91 | 92 | // Layers are described in section 4.1 of the specification 93 | message Layer { 94 | // Any compliant implementation must first read the version 95 | // number encoded in this message and choose the correct 96 | // implementation for this version number before proceeding to 97 | // decode other parts of this message. 98 | required uint32 version = 15 [ default = 1 ]; 99 | 100 | required string name = 1; 101 | 102 | // The actual features in this tile. 103 | repeated Feature features = 2; 104 | 105 | // Dictionary encoding for keys 106 | repeated string keys = 3; 107 | 108 | // Dictionary encoding for values 109 | // Deprecated in version 3. 110 | repeated Value values = 4; 111 | 112 | // Although this is an "optional" field it is required by the specification. 113 | // See https://github.com/mapbox/vector-tile-spec/issues/47 114 | optional uint32 extent = 5 [ default = 4096 ]; 115 | 116 | // Table of values of type string. Used for Inline Attributes 117 | // in version 3. See spec section 4.4.2 for details. 118 | repeated string string_values = 6; 119 | 120 | // Table of values of type float. Used for Inline Attributes 121 | // in version 3. See spec section 4.4.2 for details. 122 | repeated float float_values = 7 [ packed = true ]; 123 | 124 | // Table of values of type double. Used for Inline Attributes 125 | // in version 3. See spec section 4.4.2 for details. 126 | repeated double double_values = 8 [ packed = true ]; 127 | 128 | // Table of values of type integer. Used for Inline Attributes 129 | // in version 3. See spec section 4.4.2 for details. 130 | repeated fixed64 int_values = 9 [ packed = true ]; 131 | 132 | optional Scaling elevation_scaling = 10; 133 | 134 | // Individual feature attributes refer to these Scalings 135 | // by their index within this sequence in the Layer. 136 | repeated Scaling attribute_scalings = 11; 137 | 138 | optional uint32 tile_x = 12; 139 | optional uint32 tile_y = 13; 140 | optional uint32 tile_zoom = 14; 141 | 142 | extensions 16 to max; 143 | } 144 | 145 | repeated Layer layers = 3; 146 | 147 | extensions 16 to 8191; 148 | } 149 | -------------------------------------------------------------------------------- /vector_tile_base/__init__.py: -------------------------------------------------------------------------------- 1 | from . import engine 2 | 3 | VectorTile = engine.VectorTile 4 | Layer = engine.Layer 5 | PointFeature = engine.PointFeature 6 | LineStringFeature = engine.LineStringFeature 7 | PolygonFeature = engine.PolygonFeature 8 | SplineFeature = engine.SplineFeature 9 | FeatureAttributes = engine.FeatureAttributes 10 | Float = engine.Float 11 | FloatList = engine.FloatList 12 | UInt = engine.UInt 13 | scaling_calculation = engine.scaling_calculation 14 | 15 | __version__ = "1.0" 16 | 17 | -------------------------------------------------------------------------------- /vector_tile_base/engine.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import math 3 | from . import vector_tile_pb2 4 | 5 | # Constants 6 | 7 | ## Complex Value Type 8 | CV_TYPE_STRING = 0 9 | CV_TYPE_FLOAT = 1 10 | CV_TYPE_DOUBLE = 2 11 | CV_TYPE_UINT = 3 12 | CV_TYPE_SINT = 4 13 | CV_TYPE_INLINE_UINT = 5 14 | CV_TYPE_INLINE_SINT = 6 15 | CV_TYPE_BOOL_NULL = 7 16 | CV_TYPE_LIST = 8 17 | CV_TYPE_MAP = 9 18 | CV_TYPE_LIST_DOUBLE = 10 19 | 20 | ## Complex Value Bool/Null Meaning 21 | CV_NULL = 0 22 | CV_BOOL_FALSE = 1 23 | CV_BOOL_TRUE = 2 24 | 25 | DEFAULT_SPLINE_DEGREE = 2 26 | 27 | # Python3 Compatability 28 | try: 29 | unicode 30 | other_str = unicode 31 | except NameError: 32 | other_str = bytes 33 | long = int 34 | 35 | def zig_zag_encode(val): 36 | return (int(val) << 1) ^ (int(val) >> 31) 37 | 38 | def zig_zag_encode_64(val): 39 | return (int(val) << 1) ^ (int(val) >> 63) 40 | 41 | def zig_zag_decode(val): 42 | return ((val >> 1) ^ (-(val & 1))) 43 | 44 | def command_integer(cmd_id, count): 45 | return (cmd_id & 0x7) | (count << 3); 46 | 47 | def command_move_to(count): 48 | return command_integer(1, count) 49 | 50 | def command_line_to(count): 51 | return command_integer(2, count) 52 | 53 | def command_close_path(): 54 | return command_integer(7,1) 55 | 56 | def get_command_id(command_integer): 57 | return command_integer & 0x7; 58 | 59 | def get_command_count(command_integer): 60 | return command_integer >> 3 61 | 62 | def next_command_move_to(command_integer): 63 | return get_command_id(command_integer) == 1 64 | 65 | def next_command_line_to(command_integer): 66 | return get_command_id(command_integer) == 2 67 | 68 | def next_command_close_path(command_integer): 69 | return get_command_id(command_integer) == 7 70 | 71 | def get_inline_value_id(complex_value): 72 | return complex_value & 0x0F; 73 | 74 | def get_inline_value_parameter(complex_value): 75 | return complex_value >> 4; 76 | 77 | def complex_value_integer(cmd_id, param): 78 | return (cmd_id & 0x0F) | (param << 4); 79 | 80 | class Float(float): 81 | 82 | def __new__(self, *args, **kwargs): 83 | x = float(*args, **kwargs) 84 | vm = vector_tile_pb2.Tile.Value() 85 | vm.float_value = x 86 | return float.__new__(self, vm.float_value) 87 | def __init__(self, *args, **kwargs): 88 | float.__init__(*args, **kwargs) 89 | 90 | class UInt(long): 91 | 92 | def __new__(self, *args, **kwargs): 93 | return long.__new__(self, *args, **kwargs) 94 | def __init__(self, *args, **kwargs): 95 | long.__init__(*args, **kwargs) 96 | 97 | def scaling_calculation(precision, min_float, max_float): 98 | if min_float >= max_float: 99 | raise Exception("Invalid Float Range") 100 | if precision > (max_float - min_float): 101 | raise Exception("Precision value too large for range") 102 | if precision < 0: 103 | raise Exception("Precision can not be a negative value") 104 | lbits = math.ceil(math.log((max_float - min_float) / precision, 2) + 1.0) 105 | #lbytes = int(math.ceil(lbits / 8.0)) 106 | bPow = int(math.ceil(math.log(max_float - min_float, 2))) 107 | #dPow = 8*lbytes - 1 108 | dPow = lbits - 1 109 | sF = pow(2.0, (dPow - bPow)) 110 | sR = pow(2.0, (bPow - dPow)) 111 | return {'sF': sF, 'sR': sR, 'base': min_float } 112 | 113 | class FloatList(list): 114 | 115 | def __init__(self, *args, **kwargs): 116 | if len(args) < 0: 117 | raise Exception("FloatList initialization requires first argument to be Scaling object") 118 | if isinstance(args[0], FloatList): 119 | self._scaling = args[0]._scaling 120 | elif isinstance(args[0], Scaling): 121 | self._scaling = args[0] 122 | args = tuple(args[1:]) 123 | else: 124 | raise Exception("Unknown object passed to FloatList, first argument must be a Scaling object") 125 | if isinstance(args[0], list): 126 | new_list = [] 127 | for v in args[0]: 128 | if v is None: 129 | new_list.append(v) 130 | elif isinstance(v, float): 131 | new_list.append(self._scaling.encode_value(v)) 132 | elif isinstance(v, int) or isinstance(v, long): 133 | new_list.append(self._scaling.encode_value(float(v))) 134 | new_args = [new_list] 135 | new_args.extend(args[1:]) 136 | args = tuple(new_args) 137 | super(FloatList, self).__init__(*args, **kwargs) 138 | 139 | def append_value(self, value): 140 | if value is None: 141 | self.append(None) 142 | else: 143 | self.append(self._scaling.encode_value(value)) 144 | 145 | def get_value_at(self, index): 146 | if self[index] is None: 147 | return self[index] 148 | return self._scaling.decode_value(self[index]) 149 | 150 | def set_value_at(self, index, value): 151 | if value is None: 152 | self[index] = None 153 | else: 154 | self[index] = self._scaling.encode_value(value) 155 | 156 | def get_all_values(self): 157 | vals = [] 158 | for v in self: 159 | if v is None: 160 | vals.append(None) 161 | else: 162 | vals.append(self._scaling.decode_value(v)) 163 | return vals 164 | 165 | @property 166 | def index(self): 167 | return self._scaling.index 168 | 169 | class FeatureAttributes(object): 170 | 171 | def __init__(self, feature, layer, is_geometric=False): 172 | self._feature = feature 173 | self._layer = layer 174 | self._attr = {} 175 | self._attr_current = False 176 | self._is_geometric = is_geometric 177 | 178 | def _encode_attr(self): 179 | if self._layer._inline_attributes: 180 | if self._is_geometric: 181 | self._feature.geometric_attributes[:] = self._layer.add_attributes(self._attr, True) 182 | else: 183 | self._feature.attributes[:] = self._layer.add_attributes(self._attr, False) 184 | else: 185 | self._feature.tags[:] = self._layer.add_attributes(self._attr) 186 | self._attr_current = True 187 | 188 | def _decode_attr(self): 189 | if not self._attr_current: 190 | if self._layer._inline_attributes: 191 | if self._is_geometric: 192 | if len(self._feature.geometric_attributes) == 0: 193 | self._attr = {} 194 | else: 195 | self._attr = self._layer.get_attributes(self._feature.geometric_attributes, True) 196 | else: 197 | if len(self._feature.attributes) == 0: 198 | self._attr = {} 199 | else: 200 | self._attr = self._layer.get_attributes(self._feature.attributes) 201 | else: 202 | if len(self._feature.tags) == 0: 203 | self._attr = {} 204 | else: 205 | self._attr = self._layer.get_attributes(self._feature.tags) 206 | self._attr_current = True 207 | 208 | def __len__(self): 209 | self._decode_attr() 210 | return len(self._attr) 211 | 212 | def __getitem__(self, key): 213 | self._decode_attr() 214 | if not isinstance(key, str) and not isinstance(key, other_str): 215 | raise TypeError("Keys must be of type str") 216 | return self._attr[key] 217 | 218 | def __delitem__(self, key): 219 | self._decode_attr() 220 | del self._attr[key] 221 | self._encode_attr() 222 | 223 | def __setitem__(self, key, value): 224 | if not isinstance(key, str) and not isinstance(key, other_str): 225 | raise TypeError("Keys must be of type str or other_str") 226 | self._decode_attr() 227 | self._attr[key] = value 228 | self._encode_attr() 229 | 230 | def __iter__(self): 231 | self._decode_attr() 232 | return self._attr.__iter__() 233 | 234 | def __eq__(self, other): 235 | self._decode_attr() 236 | if isinstance(other, dict): 237 | return self._attr == other 238 | elif isinstance(other, FeatureAttributes): 239 | other._decode_attr() 240 | return self._attr == other._attr 241 | return False 242 | 243 | def __str__(self): 244 | self._decode_attr() 245 | return self._attr.__str__() 246 | 247 | def __contains__(self, key): 248 | self._decode_attr() 249 | return self._attr.__contains__(key) 250 | 251 | def set(self, attr): 252 | self._attr = dict(attr) 253 | self._encode_attr() 254 | 255 | class Feature(object): 256 | 257 | def __init__(self, feature, layer, has_elevation=None): 258 | self._feature = feature 259 | self._layer = layer 260 | if has_elevation is None: 261 | if len(self._feature.elevation) != 0: 262 | self._has_elevation = True 263 | else: 264 | self._has_elevation = False 265 | else: 266 | if has_elevation and self._layer.version < 3: 267 | raise Exception("Layers of version 1 or 2 can not have elevation data in features") 268 | self._has_elevation = has_elevation 269 | 270 | self._reset_cursor() 271 | self._attributes = FeatureAttributes(feature, layer, is_geometric=False) 272 | if self._layer._inline_attributes: 273 | self._geometric_attributes = FeatureAttributes(feature, layer, is_geometric=True) 274 | else: 275 | self._geometric_attributes = {} 276 | 277 | def _reset_cursor(self): 278 | self.cursor = [] 279 | if self._has_elevation: 280 | self.cursor[:3] = itertools.repeat(0, 3) 281 | else: 282 | self.cursor[:2] = itertools.repeat(0, 2) 283 | self._cursor_at_end = False 284 | 285 | def _encode_point(self, pt, cmd_list, elevation_list): 286 | cmd_list.append(zig_zag_encode(int(pt[0]) - self.cursor[0])) 287 | cmd_list.append(zig_zag_encode(int(pt[1]) - self.cursor[1])) 288 | self.cursor[0] = int(pt[0]) 289 | self.cursor[1] = int(pt[1]) 290 | if self._has_elevation: 291 | if self._layer._elevation_scaling is None: 292 | elevation_list.append(int(pt[2]) - self.cursor[2]) 293 | self.cursor[2] = int(pt[2]) 294 | else: 295 | new_pt = self._layer._elevation_scaling.encode_value(pt[2]) 296 | elevation_list.append(new_pt - self.cursor[2]) 297 | self.cursor[2] = new_pt 298 | 299 | 300 | def _decode_point(self, integers): 301 | self.cursor[0] = self.cursor[0] + zig_zag_decode(integers[0]) 302 | self.cursor[1] = self.cursor[1] + zig_zag_decode(integers[1]) 303 | out = [self.cursor[0], self.cursor[1]] 304 | if len(integers) > 2: 305 | self.cursor[2] = self.cursor[2] + integers[2] 306 | if self._layer._elevation_scaling is None: 307 | out.append(self.cursor[2]) 308 | else: 309 | out.append(self._layer._elevation_scaling.decode_value(self.cursor[2])) 310 | return out 311 | 312 | def _points_equal(self, pt1, pt2): 313 | if pt1[0] is not pt2[0] or pt1[1] is not pt2[1] or (self._has_elevation and pt1[2] is not pt2[2]): 314 | return False 315 | return True 316 | 317 | @property 318 | def has_elevation(self): 319 | return self._has_elevation 320 | 321 | @property 322 | def attributes(self): 323 | return self._attributes 324 | 325 | @attributes.setter 326 | def attributes(self, attrs): 327 | self._attributes.set(attrs) 328 | 329 | @property 330 | def geometric_attributes(self): 331 | return self._geometric_attributes 332 | 333 | @geometric_attributes.setter 334 | def geometric_attributes(self, attrs): 335 | if not self._layer._inline_attributes: 336 | raise Exception("Can not set geometric attributes for none inline attributes configured layer.") 337 | self._geometric_attributes.set(attrs) 338 | 339 | @property 340 | def id(self): 341 | if self._feature.HasField('id'): 342 | return self._feature.id; 343 | elif self._feature.HasField('string_id'): 344 | return self._feature.string_id; 345 | return None 346 | 347 | @id.setter 348 | def id(self, id_val): 349 | if isinstance(id_val, int): 350 | self._feature.id = id_val 351 | if self._feature.HasField('string_id'): 352 | self._feature.ClearField('string_id') 353 | elif self._layer.version >= 3: 354 | self._feature.string_id = id_val 355 | if self._feature.HasField('id'): 356 | self._feature.ClearField('id') 357 | else: 358 | raise Exception("Can not set string id for features using version 2 or below of the VT specification") 359 | 360 | def clear_geometry(self): 361 | self.has_geometry = False 362 | self._reset_cursor() 363 | self._feature.ClearField('geometry') 364 | self._feature.ClearField('elevation') 365 | 366 | class PointFeature(Feature): 367 | 368 | def __init__(self, feature, layer, has_elevation=None): 369 | super(PointFeature, self).__init__(feature, layer, has_elevation) 370 | if feature.type is not vector_tile_pb2.Tile.POINT: 371 | feature.type = vector_tile_pb2.Tile.POINT 372 | self.type = 'point' 373 | self._num_points = 0 374 | 375 | def add_points(self, points): 376 | if not isinstance(points, list): 377 | raise Exception("Invalid point geometry") 378 | if not self._cursor_at_end: 379 | # Use geometry retrieval process to move cursor to proper position 380 | pts = self.get_points() 381 | self._num_points = len(pts) 382 | if len(points) < 1: 383 | return 384 | multi_point = isinstance(points[0], list) 385 | if multi_point: 386 | num_commands = len(points) 387 | else: 388 | num_commands = 1 389 | 390 | cmd_list = [] 391 | if self._has_elevation: 392 | elevation_list = [] 393 | else: 394 | elevation_list = None 395 | if self._num_points == 0: 396 | cmd_list.append(command_move_to(num_commands)) 397 | try: 398 | if multi_point: 399 | for i in range(num_commands): 400 | self._encode_point(points[i], cmd_list, elevation_list) 401 | else: 402 | self._encode_point(points, cmd_list, elevation_list) 403 | except Exception as e: 404 | self._reset_cursor() 405 | raise e 406 | if self._num_points != 0: 407 | self._num_points = self._num_points + num_commands 408 | self._feature.geometry[0] = command_move_to(self._num_points) 409 | self._feature.geometry.extend(cmd_list) 410 | if elevation_list: 411 | try: 412 | self._feature.elevation.extend(elevation_list) 413 | except ValueError: 414 | raise Exception("Elevation scaling results in value outside of value range of sint32, reduce elevation scaling precision.") 415 | 416 | def get_points(self, no_elevation=False): 417 | points = [] 418 | self._reset_cursor() 419 | geom = iter(self._feature.geometry) 420 | if self.has_elevation and not no_elevation: 421 | elevation = iter(self._feature.elevation) 422 | try: 423 | current_command = next(geom) 424 | while next_command_move_to(current_command): 425 | for i in range(get_command_count(current_command)): 426 | if self.has_elevation and not no_elevation: 427 | points.append(self._decode_point([next(geom), next(geom), next(elevation)])) 428 | else: 429 | points.append(self._decode_point([next(geom), next(geom)])) 430 | current_command = next(geom) 431 | except StopIteration: 432 | pass 433 | self._cursor_at_end = True 434 | return points 435 | 436 | def get_geometry(self, no_elevation = False): 437 | return self.get_points(no_elevation) 438 | 439 | class LineStringFeature(Feature): 440 | 441 | def __init__(self, feature, layer, has_elevation=None): 442 | super(LineStringFeature, self).__init__(feature, layer, has_elevation) 443 | if feature.type is not vector_tile_pb2.Tile.LINESTRING: 444 | feature.type = vector_tile_pb2.Tile.LINESTRING 445 | self.type = 'line_string' 446 | 447 | def add_line_string(self, linestring): 448 | num_commands = len(linestring) 449 | if num_commands < 2: 450 | raise Exception("Error adding linestring, less then 2 points provided") 451 | if not self._cursor_at_end: 452 | # Use geometry retrieval process to move cursor to proper position 453 | self.get_line_strings() 454 | if self._has_elevation: 455 | elevation_list = [] 456 | else: 457 | elevation_list = None 458 | try: 459 | cmd_list = [] 460 | cmd_list.append(command_move_to(1)) 461 | self._encode_point(linestring[0], cmd_list, elevation_list) 462 | cmd_list.append(command_line_to(num_commands - 1)) 463 | for i in range(1, num_commands): 464 | self._encode_point(linestring[i], cmd_list, elevation_list) 465 | except Exception as e: 466 | self._reset_cursor() 467 | raise e 468 | self._feature.geometry.extend(cmd_list) 469 | if elevation_list: 470 | try: 471 | self._feature.elevation.extend(elevation_list) 472 | except ValueError: 473 | raise Exception("Elevation scaling results in value outside of value range of sint32, reduce elevation scaling precision.") 474 | 475 | def get_line_strings(self, no_elevation=False): 476 | line_strings = [] 477 | line_string = [] 478 | self._reset_cursor() 479 | geom = iter(self._feature.geometry) 480 | if self.has_elevation and not no_elevation: 481 | elevation = iter(self._feature.elevation) 482 | try: 483 | current_command = next(geom) 484 | while next_command_move_to(current_command): 485 | line_string = [] 486 | if get_command_count(current_command) != 1: 487 | raise Exception("Command move_to has command count not equal to 1 in a line string") 488 | if self.has_elevation and not no_elevation: 489 | line_string.append(self._decode_point([next(geom), next(geom), next(elevation)])) 490 | else: 491 | line_string.append(self._decode_point([next(geom), next(geom)])) 492 | current_command = next(geom) 493 | if not next_command_line_to(current_command): 494 | raise Exception("Command move_to not followed by a line_to command in a line string") 495 | while next_command_line_to(current_command): 496 | for i in range(get_command_count(current_command)): 497 | if self.has_elevation and not no_elevation: 498 | line_string.append(self._decode_point([next(geom), next(geom), next(elevation)])) 499 | else: 500 | line_string.append(self._decode_point([next(geom), next(geom)])) 501 | current_command = next(geom) 502 | if len(line_string) > 1: 503 | line_strings.append(line_string) 504 | except StopIteration: 505 | if len(line_string) > 1: 506 | line_strings.append(line_string) 507 | pass 508 | self._cursor_at_end = True 509 | return line_strings 510 | 511 | def get_geometry(self, no_elevation=False): 512 | return self.get_line_strings(no_elevation) 513 | 514 | class PolygonFeature(Feature): 515 | 516 | def __init__(self, feature, layer, has_elevation=None): 517 | super(PolygonFeature, self).__init__(feature, layer, has_elevation) 518 | if feature.type is not vector_tile_pb2.Tile.POLYGON: 519 | feature.type = vector_tile_pb2.Tile.POLYGON 520 | self.type = 'polygon' 521 | 522 | def add_ring(self, ring): 523 | if not self._cursor_at_end: 524 | # Use geometry retrieval process to move cursor to proper position 525 | self.get_rings() 526 | num_commands = len(ring) 527 | if num_commands < 3: 528 | raise Exception("Error adding ring to polygon, too few points") 529 | if self._points_equal(ring[0], ring[-1]): 530 | num_commands = num_commands - 1 531 | if num_commands < 3: 532 | raise Exception("Error adding ring to polygon, too few points with last point closing") 533 | cmd_list = [] 534 | if self._has_elevation: 535 | elevation_list = [] 536 | else: 537 | elevation_list = None 538 | try: 539 | cmd_list.append(command_move_to(1)) 540 | self._encode_point(ring[0], cmd_list, elevation_list) 541 | cmd_list.append(command_line_to(num_commands - 1)) 542 | for i in range(1, num_commands): 543 | self._encode_point(ring[i], cmd_list, elevation_list) 544 | cmd_list.append(command_close_path()) 545 | except Exception as e: 546 | self._reset_cursor() 547 | raise e 548 | self._feature.geometry.extend(cmd_list) 549 | if elevation_list: 550 | try: 551 | self._feature.elevation.extend(elevation_list) 552 | except ValueError: 553 | raise Exception("Elevation scaling results in value outside of value range of sint32, reduce elevation scaling precision.") 554 | 555 | def get_rings(self, no_elevation=False): 556 | rings = [] 557 | ring = [] 558 | self._reset_cursor() 559 | geom = iter(self._feature.geometry) 560 | if self.has_elevation and not no_elevation: 561 | elevation = iter(self._feature.elevation) 562 | try: 563 | current_command = next(geom) 564 | while next_command_move_to(current_command): 565 | ring = [] 566 | if get_command_count(current_command) != 1: 567 | raise Exception("Command move_to has command count not equal to 1 in a line string") 568 | if self.has_elevation and not no_elevation: 569 | ring.append(self._decode_point([next(geom), next(geom), next(elevation)])) 570 | else: 571 | ring.append(self._decode_point([next(geom), next(geom)])) 572 | current_command = next(geom) 573 | while next_command_line_to(current_command): 574 | for i in range(get_command_count(current_command)): 575 | if self.has_elevation and not no_elevation: 576 | ring.append(self._decode_point([next(geom), next(geom), next(elevation)])) 577 | else: 578 | ring.append(self._decode_point([next(geom), next(geom)])) 579 | current_command = next(geom) 580 | if not next_command_close_path(current_command): 581 | raise Exception("Polygon not closed with close_path command") 582 | ring.append(ring[0]) 583 | if len(ring) > 3: 584 | rings.append(ring) 585 | current_command = next(geom) 586 | except StopIteration: 587 | pass 588 | self._cursor_at_end = True 589 | return rings 590 | 591 | def _is_ring_clockwise(self, ring): 592 | area = 0.0 593 | for i in range(len(ring) - 1): 594 | area += (float(ring[i][0]) * float(ring[i+1][1])) - (float(ring[i][1]) * float(ring[i+1][0])) 595 | return area < 0.0 596 | 597 | def get_polygons(self, no_elevation=False): 598 | rings = self.get_rings(no_elevation) 599 | polygons = [] 600 | polygon = [] 601 | for ring in rings: 602 | if not self._is_ring_clockwise(ring): 603 | if len(polygon) != 0: 604 | polygons.append(polygon) 605 | polygon = [] 606 | polygon.append(ring) 607 | elif len(polygon) != 0: 608 | polygon.append(ring) 609 | if len(polygon) != 0: 610 | polygons.append(polygon) 611 | return polygons 612 | 613 | def get_geometry(self, no_elevation=False): 614 | return self.get_polygons(no_elevation) 615 | 616 | class SplineFeature(Feature): 617 | 618 | def __init__(self, feature, layer, has_elevation=None, degree=None): 619 | super(SplineFeature, self).__init__(feature, layer, has_elevation) 620 | if feature.type is not vector_tile_pb2.Tile.SPLINE: 621 | feature.type = vector_tile_pb2.Tile.SPLINE 622 | self.type = 'spline' 623 | if self._feature.HasField('spline_degree'): 624 | self._degree = self._feature.spline_degree 625 | elif degree is None or degree == DEFAULT_SPLINE_DEGREE: 626 | self._degree = DEFAULT_SPLINE_DEGREE 627 | else: 628 | self._degree = degree 629 | self._feature.spline_degree = degree 630 | 631 | def add_spline(self, control_points, knots): 632 | num_commands = len(control_points) 633 | if num_commands < 2: 634 | raise Exception("Error adding control points, less then 2 points provided") 635 | if not isinstance(knots, FloatList): 636 | raise Exception("Knot values must be provided in the form of a FloatList") 637 | num_knots = len(knots) 638 | if num_knots != (num_commands + self._degree + 1): 639 | raise Exception("The length of knots must be equal to the length of control points + degree + 1") 640 | cmd_list = [] 641 | if self._has_elevation: 642 | elevation_list = [] 643 | else: 644 | elevation_list = None 645 | try: 646 | cmd_list.append(command_move_to(1)) 647 | self._encode_point(control_points[0], cmd_list, elevation_list) 648 | cmd_list.append(command_line_to(num_commands - 1)) 649 | for i in range(1, num_commands): 650 | self._encode_point(control_points[i], cmd_list, elevation_list) 651 | except Exception as e: 652 | self._reset_cursor() 653 | raise e 654 | self._feature.geometry.extend(cmd_list) 655 | if elevation_list: 656 | try: 657 | self._feature.elevation.extend(elevation_list) 658 | except ValueError: 659 | raise Exception("Elevation scaling results in value outside of value range of sint32, reduce elevation scaling precision.") 660 | values, length = self._layer._add_inline_float_list(knots) 661 | values.insert(0, complex_value_integer(CV_TYPE_LIST_DOUBLE, length)) 662 | self._feature.spline_knots.extend(values) 663 | 664 | @property 665 | def degree(self): 666 | return self._degree 667 | 668 | def get_splines(self, no_elevation=False): 669 | splines = [] 670 | self._reset_cursor() 671 | geom = iter(self._feature.geometry) 672 | knots_itr = iter(self._feature.spline_knots) 673 | if self._has_elevation and not no_elevation: 674 | elevation = iter(self._feature.elevation) 675 | try: 676 | current_command = next(geom) 677 | while next_command_move_to(current_command): 678 | control_points = [] 679 | if get_command_count(current_command) != 1: 680 | raise Exception("Command move_to has command count not equal to 1 in a line string") 681 | if self._has_elevation and not no_elevation: 682 | control_points.append(self._decode_point([next(geom), next(geom), next(elevation)])) 683 | else: 684 | control_points.append(self._decode_point([next(geom), next(geom)])) 685 | current_command = next(geom) 686 | while next_command_line_to(current_command): 687 | for i in range(get_command_count(current_command)): 688 | if self._has_elevation and not no_elevation: 689 | control_points.append(self._decode_point([next(geom), next(geom), next(elevation)])) 690 | else: 691 | control_points.append(self._decode_point([next(geom), next(geom)])) 692 | current_command = next(geom) 693 | if len(control_points) > 1: 694 | splines.append([control_points]) 695 | except StopIteration: 696 | if len(control_points) > 1: 697 | splines.append([control_points]) 698 | pass 699 | 700 | try: 701 | for i in range(len(splines)): 702 | complex_value = next(knots_itr) 703 | val_id = get_inline_value_id(complex_value) 704 | param = get_inline_value_parameter(complex_value) 705 | if val_id == CV_TYPE_LIST_DOUBLE: 706 | knots = self._layer._get_inline_float_list(knots_itr, param) 707 | num_cp = len(splines[i][0]) 708 | num_knots = len(knots) 709 | if num_knots == (num_cp + self._degree + 1): 710 | splines[i].append(knots) 711 | except StopIteration: 712 | pass 713 | self._cursor_at_end = True 714 | return splines 715 | 716 | def get_geometry(self, no_elevation=False): 717 | return self.get_splines(no_elevation) 718 | 719 | class Scaling(object): 720 | 721 | def __init__(self, scaling_object, index = None, offset = None, multiplier = None, base = None): 722 | self._scaling_object = scaling_object 723 | self._index = index 724 | if offset is not None or multiplier is not None or base is not None: 725 | self._init_from_values(offset, multiplier, base) 726 | else: 727 | self._init_from_object() 728 | 729 | def _init_from_object(self): 730 | if self._scaling_object.HasField('offset'): 731 | self._offset = self._scaling_object.offset 732 | else: 733 | self._offset = 0 734 | if self._scaling_object.HasField('multiplier'): 735 | self._multiplier = self._scaling_object.multiplier 736 | else: 737 | self._multiplier = 1.0 738 | if self._scaling_object.HasField('base'): 739 | self._base = self._scaling_object.base 740 | else: 741 | self._base = 0.0 742 | 743 | def _init_from_values(self, offset, multiplier, base): 744 | if offset is not None and offset != 0: 745 | self._scaling_object.offset = int(offset) 746 | self._offset = int(offset) 747 | else: 748 | self._offset = 0 749 | if multiplier is not None and multiplier != 1.0: 750 | self._scaling_object.multiplier = float(multiplier) 751 | self._multiplier = float(multiplier) 752 | else: 753 | self._multiplier = 1.0 754 | if base is not None and base != 0.0: 755 | self._scaling_object.base = float(base) 756 | self._base = float(base) 757 | else: 758 | self._base = 0.0 759 | 760 | @property 761 | def type(self): 762 | return self._type 763 | 764 | @property 765 | def offset(self): 766 | return self._offset 767 | 768 | @property 769 | def multiplier(self): 770 | return self._multiplier 771 | 772 | @property 773 | def base(self): 774 | return self._base 775 | 776 | @property 777 | def index(self): 778 | return self._index 779 | 780 | def encode_value(self, value): 781 | return int(round((value - self._base) / self._multiplier)) - self._offset 782 | 783 | def decode_value(self, value): 784 | return self._multiplier * (value + self._offset) + self._base 785 | 786 | class Layer(object): 787 | 788 | def __init__(self, layer, name = None, version = None, x = None, y = None, zoom = None, legacy_attributes=False): 789 | self._layer = layer 790 | self._features = [] 791 | if name: 792 | self._layer.name = name 793 | if version: 794 | self._layer.version = version 795 | elif not self._layer.HasField('version'): 796 | self._layer.version = 2 797 | 798 | self._keys = [] 799 | self._decode_keys() 800 | if self.version > 2 and len(self._layer.values) == 0 and not legacy_attributes: 801 | self._inline_attributes = True 802 | self._string_values = [] 803 | self._float_values = [] 804 | self._double_values = [] 805 | self._int_values = [] 806 | self._decode_inline_values() 807 | else: 808 | self._inline_attributes = False 809 | self._values = [] 810 | self._decode_values() 811 | 812 | self._decode_attribute_scalings() 813 | 814 | if x is not None and y is not None and zoom is not None: 815 | self.set_tile_location(zoom, x, y) 816 | 817 | if self._layer.HasField('elevation_scaling'): 818 | self._elevation_scaling = Scaling(self._layer.elevation_scaling) 819 | else: 820 | self._elevation_scaling = None 821 | 822 | self._build_features() 823 | 824 | def _decode_attribute_scalings(self): 825 | self._attribute_scalings = [] 826 | for i in range(len(self._layer.attribute_scalings)): 827 | self._attribute_scalings.append(Scaling(self._layer.attribute_scalings[i], index=i)) 828 | 829 | def _decode_values(self): 830 | for val in self._layer.values: 831 | if val.HasField('bool_value'): 832 | self._values.append(val.bool_value) 833 | elif val.HasField('string_value'): 834 | self._values.append(val.string_value) 835 | elif val.HasField('float_value'): 836 | self._values.append(val.float_value) 837 | elif val.HasField('double_value'): 838 | self._values.append(val.double_value) 839 | elif val.HasField('int_value'): 840 | self._values.append(val.int_value) 841 | elif val.HasField('uint_value'): 842 | self._values.append(val.uint_value) 843 | elif val.HasField('sint_value'): 844 | self._values.append(val.sint_value) 845 | 846 | def _decode_inline_values(self): 847 | for val in self._layer.string_values: 848 | self._string_values.append(val) 849 | for val in self._layer.float_values: 850 | self._float_values.append(Float(val)) 851 | for val in self._layer.double_values: 852 | self._double_values.append(val) 853 | for val in self._layer.int_values: 854 | self._int_values.append(val) 855 | 856 | def _decode_keys(self): 857 | for key in self._layer.keys: 858 | self._keys.append(key) 859 | 860 | def _build_features(self): 861 | for feature in self._layer.features: 862 | if feature.type == vector_tile_pb2.Tile.POINT: 863 | self._features.append(PointFeature(feature, self)) 864 | elif feature.type == vector_tile_pb2.Tile.LINESTRING: 865 | self._features.append(LineStringFeature(feature, self)) 866 | elif feature.type == vector_tile_pb2.Tile.POLYGON: 867 | self._features.append(PolygonFeature(feature, self)) 868 | elif feature.type == vector_tile_pb2.Tile.SPLINE: 869 | self._features.append(SplineFeature(feature, self)) 870 | 871 | def add_elevation_scaling(self, offset=0, multiplier=1.0, base=0.0, min_value=None, max_value=None, precision=None): 872 | if self.version < 3: 873 | raise Exception("Can not add elevation scaling to Version 2 or below Vector Tiles.") 874 | if min_value is not None and max_value is not None and precision is not None: 875 | out = scaling_calculation(precision, float(min_value), float(max_value)) 876 | offset = 0 877 | base = out['base'] 878 | multiplier = out['sR'] 879 | self._elevation_scaling = Scaling(self._layer.elevation_scaling, offset=offset, multiplier=multiplier, base=base) 880 | return self._elevation_scaling 881 | 882 | def add_attribute_scaling(self, offset=0, multiplier=1.0, base=0.0, min_value=None, max_value=None, precision=None): 883 | if self.version < 3: 884 | raise Exception("Can not add attribute scaling to Version 2 or below Vector Tiles.") 885 | if not self._inline_attributes: 886 | raise Exception("Can not add attribute scaling to Version 3 or greater layers that do not support inline attributes") 887 | if min_value is not None and max_value is not None and precision is not None: 888 | out = scaling_calculation(precision, float(min_value), float(max_value)) 889 | offset = 0 890 | base = out['base'] 891 | multiplier = out['sR'] 892 | index = len(self._attribute_scalings) 893 | self._attribute_scalings.append(Scaling(self._layer.attribute_scalings.add(), index=index, offset=offset, multiplier=multiplier, base=base)) 894 | return self._attribute_scalings[index] 895 | 896 | def add_point_feature(self, has_elevation=False): 897 | self._features.append(PointFeature(self._layer.features.add(), self, has_elevation=has_elevation)) 898 | return self._features[-1] 899 | 900 | def add_line_string_feature(self, has_elevation=False): 901 | self._features.append(LineStringFeature(self._layer.features.add(), self, has_elevation=has_elevation)) 902 | return self._features[-1] 903 | 904 | def add_polygon_feature(self, has_elevation=False): 905 | self._features.append(PolygonFeature(self._layer.features.add(), self, has_elevation=has_elevation)) 906 | return self._features[-1] 907 | 908 | def add_spline_feature(self, has_elevation=False, degree=None): 909 | if self.version < 3: 910 | raise Exception("Can not add splines to Version 2 or below Vector Tiles.") 911 | self._features.append(SplineFeature(self._layer.features.add(), self, has_elevation=has_elevation, degree=degree)) 912 | return self._features[-1] 913 | 914 | @property 915 | def features(self): 916 | return self._features 917 | 918 | @property 919 | def name(self): 920 | return self._layer.name 921 | 922 | @name.setter 923 | def name(self, name): 924 | self._layer.name = name 925 | 926 | @property 927 | def extent(self): 928 | if self._layer.HasField('extent'): 929 | return self._layer.extent 930 | return 4096 931 | 932 | @extent.setter 933 | def extent(self, extent): 934 | self._layer.extent = extent 935 | 936 | @property 937 | def version(self): 938 | if self._layer.HasField('version'): 939 | return self._layer.version 940 | return 2 941 | 942 | @property 943 | def elevation_scaling(self): 944 | return self._elevation_scaling 945 | 946 | @property 947 | def attribute_scalings(self): 948 | return self._attribute_scalings 949 | 950 | @property 951 | def x(self): 952 | if self._layer.HasField('tile_x'): 953 | return self._layer.tile_x 954 | else: 955 | return None 956 | 957 | @property 958 | def y(self): 959 | if self._layer.HasField('tile_y'): 960 | return self._layer.tile_y 961 | else: 962 | return None 963 | 964 | @property 965 | def zoom(self): 966 | if self._layer.HasField('tile_zoom'): 967 | return self._layer.tile_zoom 968 | else: 969 | return None 970 | 971 | def set_tile_location(self, zoom, x, y): 972 | if self.version < 3: 973 | raise Exception("Can not add tile location to Version 2 or below Vector Tiles.") 974 | if zoom < 0 or zoom > 50: 975 | raise Exception("Please use a zoom level between 0 and 50") 976 | if x < 0 or x > (2**zoom - 1): 977 | raise Exception("Tile x value outside of possible values given zoom level") 978 | if y < 0 or y > (2**zoom - 1): 979 | raise Exception("Tile y value outside of possible values given zoom level") 980 | self._layer.tile_x = x 981 | self._layer.tile_y = y 982 | self._layer.tile_zoom = zoom 983 | 984 | def get_attributes(self, int_list, list_only=False): 985 | if not self._inline_attributes: 986 | attributes = {} 987 | for i in range(0,len(int_list),2): 988 | attributes[self._keys[int_list[i]]] = self._values[int_list[i+1]] 989 | return attributes 990 | else: 991 | return self._get_inline_map_attributes(iter(int_list), limit=None, list_only=list_only) 992 | 993 | def _get_inline_value(self, complex_value, value_itr): 994 | val_id = get_inline_value_id(complex_value) 995 | param = get_inline_value_parameter(complex_value) 996 | if val_id == CV_TYPE_STRING: 997 | return self._string_values[param] 998 | elif val_id == CV_TYPE_FLOAT: 999 | return self._float_values[param] 1000 | elif val_id == CV_TYPE_DOUBLE: 1001 | return self._double_values[param] 1002 | elif val_id == CV_TYPE_SINT: 1003 | return zig_zag_decode(self._int_values[param]) 1004 | elif val_id == CV_TYPE_UINT: 1005 | return self._int_values[param] 1006 | elif val_id == CV_TYPE_INLINE_UINT: 1007 | return param 1008 | elif val_id == CV_TYPE_INLINE_SINT: 1009 | return zig_zag_decode(param) 1010 | elif val_id == CV_TYPE_BOOL_NULL: 1011 | if param == CV_BOOL_FALSE: 1012 | return False 1013 | elif param == CV_BOOL_TRUE: 1014 | return True 1015 | else: 1016 | return None 1017 | elif val_id == CV_TYPE_LIST: 1018 | return self._get_inline_list_attributes(value_itr, param) 1019 | elif val_id == CV_TYPE_MAP: 1020 | return self._get_inline_map_attributes(value_itr, param) 1021 | elif val_id == CV_TYPE_LIST_DOUBLE: 1022 | return self._get_inline_float_list(value_itr, param) 1023 | else: 1024 | raise Exception("Unknown value type in inline value") 1025 | 1026 | def _get_inline_map_attributes(self, value_itr, limit = None, list_only=False): 1027 | attr_map = {} 1028 | if limit == 0: 1029 | return attr_map 1030 | count = 0 1031 | for key in value_itr: 1032 | try: 1033 | val = next(value_itr) 1034 | except StopIteration: 1035 | break 1036 | if list_only: 1037 | val_id = get_inline_value_id(val) 1038 | if val_id != CV_TYPE_LIST and val_id != CV_TYPE_LIST_DOUBLE: 1039 | raise Exception("Invalid value type top level in geometric_attributes of feature, must be a list type") 1040 | attr_map[self._keys[key]] = self._get_inline_value(val, value_itr) 1041 | count = count + 1 1042 | if limit is not None and count >= limit: 1043 | break 1044 | return attr_map 1045 | 1046 | def _get_inline_list_attributes(self, value_itr, limit = None): 1047 | attr_list = [] 1048 | if limit == 0: 1049 | return attr_list 1050 | count = 0 1051 | for val in value_itr: 1052 | attr_list.append(self._get_inline_value(val, value_itr)) 1053 | count = count + 1 1054 | if limit is not None and count >= limit: 1055 | break 1056 | return attr_list 1057 | 1058 | def _get_inline_float_list(self, value_itr, limit = None): 1059 | index = next(value_itr) 1060 | if index < 0 and index >= len(self._attribute_scalings): 1061 | raise Exception("Invalid attribute scaling index") 1062 | scaling = self._attribute_scalings[index] 1063 | attr_list = [] 1064 | if limit == 0: 1065 | return attr_list 1066 | count = 0 1067 | cursor = 0 1068 | for val in value_itr: 1069 | if val == 0: 1070 | attr_list.append(None) 1071 | else: 1072 | cursor = cursor + zig_zag_decode(val - 1) 1073 | attr_list.append(scaling.decode_value(cursor)) 1074 | count = count + 1 1075 | if limit is not None and count >= limit: 1076 | break 1077 | return attr_list 1078 | 1079 | def _is_in_values(self, value): 1080 | for e_v in self._values: 1081 | if value == e_v and type(value) == type(e_v): 1082 | return True 1083 | return False 1084 | 1085 | def _add_legacy_attributes(self, attrs): 1086 | tags = [] 1087 | remove = [] 1088 | for k,v in attrs.items(): 1089 | if not isinstance(k, str) and not isinstance(k, other_str): 1090 | remove.append(k) 1091 | continue 1092 | if not self._is_in_values(v): 1093 | if isinstance(v,bool): 1094 | val = self._layer.values.add() 1095 | val.bool_value = v 1096 | elif (isinstance(v,str)) or (isinstance(v,other_str)): 1097 | val = self._layer.values.add() 1098 | val.string_value = v 1099 | elif isinstance(v,UInt) and v >= 0: 1100 | val = self._layer.values.add() 1101 | val.uint_value = v 1102 | elif isinstance(v,int) or isinstance(v,long): 1103 | val = self._layer.values.add() 1104 | if v >= 0: 1105 | val.int_value = v 1106 | else: 1107 | val.sint_value = v 1108 | elif isinstance(v,Float): 1109 | val = self._layer.values.add() 1110 | val.float_value = v 1111 | elif isinstance(v,float): 1112 | val = self._layer.values.add() 1113 | val.double_value = v 1114 | else: 1115 | remove.append(k) 1116 | continue 1117 | self._values.append(v) 1118 | value_index = len(self._values) - 1 1119 | else: 1120 | value_index = self._values.index(v) 1121 | 1122 | if k not in self._keys: 1123 | self._layer.keys.append(k) 1124 | self._keys.append(k) 1125 | tags.append(self._keys.index(k)) 1126 | tags.append(value_index) 1127 | for k in remove: 1128 | del attrs[k] 1129 | return tags 1130 | 1131 | def _add_inline_value(self, v): 1132 | if v is None: 1133 | return complex_value_integer(CV_TYPE_BOOL_NULL, CV_NULL) 1134 | elif isinstance(v, bool): 1135 | if v == True: 1136 | return complex_value_integer(CV_TYPE_BOOL_NULL, CV_BOOL_TRUE) 1137 | else: 1138 | return complex_value_integer(CV_TYPE_BOOL_NULL, CV_BOOL_FALSE) 1139 | elif isinstance(v,str) or isinstance(v,other_str): 1140 | try: 1141 | index = self._string_values.index(v) 1142 | return complex_value_integer(CV_TYPE_STRING, index) 1143 | except ValueError: 1144 | self._string_values.append(v) 1145 | self._layer.string_values.append(v) 1146 | return complex_value_integer(CV_TYPE_STRING, len(self._string_values) - 1) 1147 | elif isinstance(v,UInt) and v >= 0: 1148 | if v >= 2**56: 1149 | try: 1150 | index = self._int_values.index(v) 1151 | return complex_value_integer(CV_TYPE_UINT, index) 1152 | except ValueError: 1153 | self._int_values.append(v) 1154 | self._layer.int_values.append(v) 1155 | return complex_value_integer(CV_TYPE_UINT, len(self._int_values) - 1) 1156 | else: 1157 | return complex_value_integer(CV_TYPE_INLINE_UINT, v) 1158 | elif isinstance(v,int) or isinstance(v, long): 1159 | if v >= 2**55 or v <= -2**55: 1160 | zz_v = zig_zag_encode_64(v) 1161 | try: 1162 | index = self._int_values.index(zz_v) 1163 | return complex_value_integer(CV_TYPE_SINT, index) 1164 | except ValueError: 1165 | self._int_values.append(zz_v) 1166 | self._layer.int_values.append(zz_v) 1167 | return complex_value_integer(CV_TYPE_SINT, len(self._int_values) - 1) 1168 | else: 1169 | return complex_value_integer(CV_TYPE_INLINE_SINT, zig_zag_encode_64(v)) 1170 | elif isinstance(v, Float): 1171 | try: 1172 | index = self._float_values.index(v) 1173 | return complex_value_integer(CV_TYPE_FLOAT, index) 1174 | except ValueError: 1175 | self._float_values.append(v) 1176 | self._layer.float_values.append(v) 1177 | return complex_value_integer(CV_TYPE_FLOAT, len(self._float_values) - 1) 1178 | elif isinstance(v, float): 1179 | try: 1180 | index = self._double_values.index(v) 1181 | return complex_value_integer(CV_TYPE_DOUBLE, index) 1182 | except ValueError: 1183 | self._double_values.append(v) 1184 | self._layer.double_values.append(v) 1185 | return complex_value_integer(CV_TYPE_DOUBLE, len(self._double_values) - 1) 1186 | elif isinstance(v,FloatList): 1187 | values, length = self._add_inline_float_list(v) 1188 | values.insert(0, complex_value_integer(CV_TYPE_LIST_DOUBLE, length)) 1189 | return values 1190 | elif isinstance(v,list): 1191 | values, length = self._add_inline_list_attributes(v) 1192 | if not values: 1193 | return None 1194 | values.insert(0, complex_value_integer(CV_TYPE_LIST, length)) 1195 | return values 1196 | elif isinstance(v, dict): 1197 | values, length = self._add_inline_map_attributes(v) 1198 | if not values: 1199 | return None 1200 | values.insert(0, complex_value_integer(CV_TYPE_MAP, length)) 1201 | return values 1202 | return None 1203 | 1204 | def _add_inline_float_list(self, attrs): 1205 | delta_values = [attrs.index] 1206 | length = len(attrs) 1207 | cursor = 0 1208 | for v in attrs: 1209 | if v is None: 1210 | delta_values.append(0) 1211 | else: 1212 | delta_values.append(zig_zag_encode_64(v - cursor) + 1) 1213 | cursor = v 1214 | return delta_values, length 1215 | 1216 | def _add_inline_list_attributes(self, attrs): 1217 | complex_values = [] 1218 | length = len(attrs) 1219 | remove = [] 1220 | for v in attrs: 1221 | val = self._add_inline_value(v) 1222 | if val is None: 1223 | remove.append(v) 1224 | continue 1225 | if isinstance(val, list): 1226 | complex_values.extend(val) 1227 | else: 1228 | complex_values.append(val) 1229 | if remove: 1230 | length = length - len(remove) 1231 | data[:] = [x for x in data if x not in remove] 1232 | return complex_values, length 1233 | 1234 | def _add_inline_map_attributes(self, attrs, list_only=False): 1235 | complex_values = [] 1236 | length = len(attrs) 1237 | remove = [] 1238 | for k,v in attrs.items(): 1239 | if not isinstance(k, str) and not isinstance(k, other_str): 1240 | remove.append(k) 1241 | continue 1242 | if list_only and not isinstance(v, list) and not isinstance(v, FloatList): 1243 | remove.append(k) 1244 | continue 1245 | val = self._add_inline_value(v) 1246 | if val is None: 1247 | remove.append(k) 1248 | continue 1249 | try: 1250 | key_val = self._keys.index(k) 1251 | except ValueError: 1252 | self._layer.keys.append(k) 1253 | self._keys.append(k) 1254 | key_val = len(self._keys) - 1 1255 | complex_values.append(key_val) 1256 | if isinstance(val, list): 1257 | complex_values.extend(val) 1258 | else: 1259 | complex_values.append(val) 1260 | length = length - len(remove) 1261 | for k in remove: 1262 | del attrs[k] 1263 | return complex_values, length 1264 | 1265 | def add_attributes(self, attrs, list_only=False): 1266 | if self._inline_attributes: 1267 | values, length = self._add_inline_map_attributes(attrs, list_only) 1268 | return values 1269 | elif list_only: 1270 | return [] 1271 | else: 1272 | return self._add_legacy_attributes(attrs) 1273 | 1274 | class VectorTile(object): 1275 | 1276 | def __init__(self, tile = None): 1277 | self._layers = [] 1278 | if tile: 1279 | if (isinstance(tile,str)) or (isinstance(tile,other_str)): 1280 | self._tile = vector_tile_pb2.Tile() 1281 | self._tile.ParseFromString(tile) 1282 | else: 1283 | self._tile = tile 1284 | self._build_layers() 1285 | else: 1286 | self._tile = vector_tile_pb2.Tile() 1287 | 1288 | def __str__(self): 1289 | return self._tile.__str__() 1290 | 1291 | def _build_layers(self): 1292 | for layer in self._tile.layers: 1293 | self._layers.append(Layer(layer)) 1294 | 1295 | def serialize(self): 1296 | return self._tile.SerializeToString() 1297 | 1298 | def add_layer(self, name, version = None, x = None, y = None, zoom = None, legacy_attributes=False): 1299 | self._layers.append(Layer(self._tile.layers.add(), name, version=version, x=x, y=y, zoom=zoom, legacy_attributes=legacy_attributes)) 1300 | return self._layers[-1] 1301 | 1302 | @property 1303 | def layers(self): 1304 | return self._layers 1305 | -------------------------------------------------------------------------------- /vector_tile_base/vector_tile_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: vector_tile.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import message as _message 8 | from google.protobuf import reflection as _reflection 9 | from google.protobuf import symbol_database as _symbol_database 10 | from google.protobuf import descriptor_pb2 11 | # @@protoc_insertion_point(imports) 12 | 13 | _sym_db = _symbol_database.Default() 14 | 15 | 16 | 17 | 18 | DESCRIPTOR = _descriptor.FileDescriptor( 19 | name='vector_tile.proto', 20 | package='vector_tile', 21 | serialized_pb=_b('\n\x11vector_tile.proto\x12\x0bvector_tile\"\xa6\x08\n\x04Tile\x12\'\n\x06layers\x18\x03 \x03(\x0b\x32\x17.vector_tile.Tile.Layer\x1a\xa1\x01\n\x05Value\x12\x14\n\x0cstring_value\x18\x01 \x01(\t\x12\x13\n\x0b\x66loat_value\x18\x02 \x01(\x02\x12\x14\n\x0c\x64ouble_value\x18\x03 \x01(\x01\x12\x11\n\tint_value\x18\x04 \x01(\x03\x12\x12\n\nuint_value\x18\x05 \x01(\x04\x12\x12\n\nsint_value\x18\x06 \x01(\x12\x12\x12\n\nbool_value\x18\x07 \x01(\x08*\x08\x08\x08\x10\x80\x80\x80\x80\x02\x1a;\n\x07Scaling\x12\x0e\n\x06offset\x18\x01 \x01(\x12\x12\x12\n\nmultiplier\x18\x02 \x01(\x01\x12\x0c\n\x04\x62\x61se\x18\x03 \x01(\x01\x1a\x8b\x02\n\x07\x46\x65\x61ture\x12\r\n\x02id\x18\x01 \x01(\x04:\x01\x30\x12\x10\n\x04tags\x18\x02 \x03(\rB\x02\x10\x01\x12\x31\n\x04type\x18\x03 \x01(\x0e\x32\x1a.vector_tile.Tile.GeomType:\x07UNKNOWN\x12\x14\n\x08geometry\x18\x04 \x03(\rB\x02\x10\x01\x12\x16\n\nattributes\x18\x05 \x03(\x04\x42\x02\x10\x01\x12 \n\x14geometric_attributes\x18\x06 \x03(\x04\x42\x02\x10\x01\x12\x15\n\televation\x18\x07 \x03(\x11\x42\x02\x10\x01\x12\x18\n\x0cspline_knots\x18\x08 \x03(\x04\x42\x02\x10\x01\x12\x18\n\rspline_degree\x18\t \x01(\r:\x01\x32\x12\x11\n\tstring_id\x18\n \x01(\t\x1a\xb1\x03\n\x05Layer\x12\x12\n\x07version\x18\x0f \x02(\r:\x01\x31\x12\x0c\n\x04name\x18\x01 \x02(\t\x12+\n\x08\x66\x65\x61tures\x18\x02 \x03(\x0b\x32\x19.vector_tile.Tile.Feature\x12\x0c\n\x04keys\x18\x03 \x03(\t\x12\'\n\x06values\x18\x04 \x03(\x0b\x32\x17.vector_tile.Tile.Value\x12\x14\n\x06\x65xtent\x18\x05 \x01(\r:\x04\x34\x30\x39\x36\x12\x15\n\rstring_values\x18\x06 \x03(\t\x12\x18\n\x0c\x66loat_values\x18\x07 \x03(\x02\x42\x02\x10\x01\x12\x19\n\rdouble_values\x18\x08 \x03(\x01\x42\x02\x10\x01\x12\x16\n\nint_values\x18\t \x03(\x06\x42\x02\x10\x01\x12\x34\n\x11\x65levation_scaling\x18\n \x01(\x0b\x32\x19.vector_tile.Tile.Scaling\x12\x35\n\x12\x61ttribute_scalings\x18\x0b \x03(\x0b\x32\x19.vector_tile.Tile.Scaling\x12\x0e\n\x06tile_x\x18\x0c \x01(\r\x12\x0e\n\x06tile_y\x18\r \x01(\r\x12\x11\n\ttile_zoom\x18\x0e \x01(\r*\x08\x08\x10\x10\x80\x80\x80\x80\x02\"K\n\x08GeomType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\t\n\x05POINT\x10\x01\x12\x0e\n\nLINESTRING\x10\x02\x12\x0b\n\x07POLYGON\x10\x03\x12\n\n\x06SPLINE\x10\x04*\x05\x08\x10\x10\x80@B\x02H\x03') 22 | ) 23 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 24 | 25 | 26 | 27 | _TILE_GEOMTYPE = _descriptor.EnumDescriptor( 28 | name='GeomType', 29 | full_name='vector_tile.Tile.GeomType', 30 | filename=None, 31 | file=DESCRIPTOR, 32 | values=[ 33 | _descriptor.EnumValueDescriptor( 34 | name='UNKNOWN', index=0, number=0, 35 | options=None, 36 | type=None), 37 | _descriptor.EnumValueDescriptor( 38 | name='POINT', index=1, number=1, 39 | options=None, 40 | type=None), 41 | _descriptor.EnumValueDescriptor( 42 | name='LINESTRING', index=2, number=2, 43 | options=None, 44 | type=None), 45 | _descriptor.EnumValueDescriptor( 46 | name='POLYGON', index=3, number=3, 47 | options=None, 48 | type=None), 49 | _descriptor.EnumValueDescriptor( 50 | name='SPLINE', index=4, number=4, 51 | options=None, 52 | type=None), 53 | ], 54 | containing_type=None, 55 | options=None, 56 | serialized_start=1015, 57 | serialized_end=1090, 58 | ) 59 | _sym_db.RegisterEnumDescriptor(_TILE_GEOMTYPE) 60 | 61 | 62 | _TILE_VALUE = _descriptor.Descriptor( 63 | name='Value', 64 | full_name='vector_tile.Tile.Value', 65 | filename=None, 66 | file=DESCRIPTOR, 67 | containing_type=None, 68 | fields=[ 69 | _descriptor.FieldDescriptor( 70 | name='string_value', full_name='vector_tile.Tile.Value.string_value', index=0, 71 | number=1, type=9, cpp_type=9, label=1, 72 | has_default_value=False, default_value=_b("").decode('utf-8'), 73 | message_type=None, enum_type=None, containing_type=None, 74 | is_extension=False, extension_scope=None, 75 | options=None), 76 | _descriptor.FieldDescriptor( 77 | name='float_value', full_name='vector_tile.Tile.Value.float_value', index=1, 78 | number=2, type=2, cpp_type=6, label=1, 79 | has_default_value=False, default_value=0, 80 | message_type=None, enum_type=None, containing_type=None, 81 | is_extension=False, extension_scope=None, 82 | options=None), 83 | _descriptor.FieldDescriptor( 84 | name='double_value', full_name='vector_tile.Tile.Value.double_value', index=2, 85 | number=3, type=1, cpp_type=5, label=1, 86 | has_default_value=False, default_value=0, 87 | message_type=None, enum_type=None, containing_type=None, 88 | is_extension=False, extension_scope=None, 89 | options=None), 90 | _descriptor.FieldDescriptor( 91 | name='int_value', full_name='vector_tile.Tile.Value.int_value', index=3, 92 | number=4, type=3, cpp_type=2, label=1, 93 | has_default_value=False, default_value=0, 94 | message_type=None, enum_type=None, containing_type=None, 95 | is_extension=False, extension_scope=None, 96 | options=None), 97 | _descriptor.FieldDescriptor( 98 | name='uint_value', full_name='vector_tile.Tile.Value.uint_value', index=4, 99 | number=5, type=4, cpp_type=4, label=1, 100 | has_default_value=False, default_value=0, 101 | message_type=None, enum_type=None, containing_type=None, 102 | is_extension=False, extension_scope=None, 103 | options=None), 104 | _descriptor.FieldDescriptor( 105 | name='sint_value', full_name='vector_tile.Tile.Value.sint_value', index=5, 106 | number=6, type=18, cpp_type=2, label=1, 107 | has_default_value=False, default_value=0, 108 | message_type=None, enum_type=None, containing_type=None, 109 | is_extension=False, extension_scope=None, 110 | options=None), 111 | _descriptor.FieldDescriptor( 112 | name='bool_value', full_name='vector_tile.Tile.Value.bool_value', index=6, 113 | number=7, type=8, cpp_type=7, label=1, 114 | has_default_value=False, default_value=False, 115 | message_type=None, enum_type=None, containing_type=None, 116 | is_extension=False, extension_scope=None, 117 | options=None), 118 | ], 119 | extensions=[ 120 | ], 121 | nested_types=[], 122 | enum_types=[ 123 | ], 124 | options=None, 125 | is_extendable=True, 126 | extension_ranges=[(8, 536870912), ], 127 | oneofs=[ 128 | ], 129 | serialized_start=85, 130 | serialized_end=246, 131 | ) 132 | 133 | _TILE_SCALING = _descriptor.Descriptor( 134 | name='Scaling', 135 | full_name='vector_tile.Tile.Scaling', 136 | filename=None, 137 | file=DESCRIPTOR, 138 | containing_type=None, 139 | fields=[ 140 | _descriptor.FieldDescriptor( 141 | name='offset', full_name='vector_tile.Tile.Scaling.offset', index=0, 142 | number=1, type=18, cpp_type=2, label=1, 143 | has_default_value=False, default_value=0, 144 | message_type=None, enum_type=None, containing_type=None, 145 | is_extension=False, extension_scope=None, 146 | options=None), 147 | _descriptor.FieldDescriptor( 148 | name='multiplier', full_name='vector_tile.Tile.Scaling.multiplier', index=1, 149 | number=2, type=1, cpp_type=5, label=1, 150 | has_default_value=False, default_value=0, 151 | message_type=None, enum_type=None, containing_type=None, 152 | is_extension=False, extension_scope=None, 153 | options=None), 154 | _descriptor.FieldDescriptor( 155 | name='base', full_name='vector_tile.Tile.Scaling.base', index=2, 156 | number=3, type=1, cpp_type=5, label=1, 157 | has_default_value=False, default_value=0, 158 | message_type=None, enum_type=None, containing_type=None, 159 | is_extension=False, extension_scope=None, 160 | options=None), 161 | ], 162 | extensions=[ 163 | ], 164 | nested_types=[], 165 | enum_types=[ 166 | ], 167 | options=None, 168 | is_extendable=False, 169 | extension_ranges=[], 170 | oneofs=[ 171 | ], 172 | serialized_start=248, 173 | serialized_end=307, 174 | ) 175 | 176 | _TILE_FEATURE = _descriptor.Descriptor( 177 | name='Feature', 178 | full_name='vector_tile.Tile.Feature', 179 | filename=None, 180 | file=DESCRIPTOR, 181 | containing_type=None, 182 | fields=[ 183 | _descriptor.FieldDescriptor( 184 | name='id', full_name='vector_tile.Tile.Feature.id', index=0, 185 | number=1, type=4, cpp_type=4, label=1, 186 | has_default_value=True, default_value=0, 187 | message_type=None, enum_type=None, containing_type=None, 188 | is_extension=False, extension_scope=None, 189 | options=None), 190 | _descriptor.FieldDescriptor( 191 | name='tags', full_name='vector_tile.Tile.Feature.tags', index=1, 192 | number=2, type=13, cpp_type=3, label=3, 193 | has_default_value=False, default_value=[], 194 | message_type=None, enum_type=None, containing_type=None, 195 | is_extension=False, extension_scope=None, 196 | options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), 197 | _descriptor.FieldDescriptor( 198 | name='type', full_name='vector_tile.Tile.Feature.type', index=2, 199 | number=3, type=14, cpp_type=8, label=1, 200 | has_default_value=True, default_value=0, 201 | message_type=None, enum_type=None, containing_type=None, 202 | is_extension=False, extension_scope=None, 203 | options=None), 204 | _descriptor.FieldDescriptor( 205 | name='geometry', full_name='vector_tile.Tile.Feature.geometry', index=3, 206 | number=4, type=13, cpp_type=3, label=3, 207 | has_default_value=False, default_value=[], 208 | message_type=None, enum_type=None, containing_type=None, 209 | is_extension=False, extension_scope=None, 210 | options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), 211 | _descriptor.FieldDescriptor( 212 | name='attributes', full_name='vector_tile.Tile.Feature.attributes', index=4, 213 | number=5, type=4, cpp_type=4, label=3, 214 | has_default_value=False, default_value=[], 215 | message_type=None, enum_type=None, containing_type=None, 216 | is_extension=False, extension_scope=None, 217 | options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), 218 | _descriptor.FieldDescriptor( 219 | name='geometric_attributes', full_name='vector_tile.Tile.Feature.geometric_attributes', index=5, 220 | number=6, type=4, cpp_type=4, label=3, 221 | has_default_value=False, default_value=[], 222 | message_type=None, enum_type=None, containing_type=None, 223 | is_extension=False, extension_scope=None, 224 | options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), 225 | _descriptor.FieldDescriptor( 226 | name='elevation', full_name='vector_tile.Tile.Feature.elevation', index=6, 227 | number=7, type=17, cpp_type=1, label=3, 228 | has_default_value=False, default_value=[], 229 | message_type=None, enum_type=None, containing_type=None, 230 | is_extension=False, extension_scope=None, 231 | options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), 232 | _descriptor.FieldDescriptor( 233 | name='spline_knots', full_name='vector_tile.Tile.Feature.spline_knots', index=7, 234 | number=8, type=4, cpp_type=4, label=3, 235 | has_default_value=False, default_value=[], 236 | message_type=None, enum_type=None, containing_type=None, 237 | is_extension=False, extension_scope=None, 238 | options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), 239 | _descriptor.FieldDescriptor( 240 | name='spline_degree', full_name='vector_tile.Tile.Feature.spline_degree', index=8, 241 | number=9, type=13, cpp_type=3, label=1, 242 | has_default_value=True, default_value=2, 243 | message_type=None, enum_type=None, containing_type=None, 244 | is_extension=False, extension_scope=None, 245 | options=None), 246 | _descriptor.FieldDescriptor( 247 | name='string_id', full_name='vector_tile.Tile.Feature.string_id', index=9, 248 | number=10, type=9, cpp_type=9, label=1, 249 | has_default_value=False, default_value=_b("").decode('utf-8'), 250 | message_type=None, enum_type=None, containing_type=None, 251 | is_extension=False, extension_scope=None, 252 | options=None), 253 | ], 254 | extensions=[ 255 | ], 256 | nested_types=[], 257 | enum_types=[ 258 | ], 259 | options=None, 260 | is_extendable=False, 261 | extension_ranges=[], 262 | oneofs=[ 263 | ], 264 | serialized_start=310, 265 | serialized_end=577, 266 | ) 267 | 268 | _TILE_LAYER = _descriptor.Descriptor( 269 | name='Layer', 270 | full_name='vector_tile.Tile.Layer', 271 | filename=None, 272 | file=DESCRIPTOR, 273 | containing_type=None, 274 | fields=[ 275 | _descriptor.FieldDescriptor( 276 | name='version', full_name='vector_tile.Tile.Layer.version', index=0, 277 | number=15, type=13, cpp_type=3, label=2, 278 | has_default_value=True, default_value=1, 279 | message_type=None, enum_type=None, containing_type=None, 280 | is_extension=False, extension_scope=None, 281 | options=None), 282 | _descriptor.FieldDescriptor( 283 | name='name', full_name='vector_tile.Tile.Layer.name', index=1, 284 | number=1, type=9, cpp_type=9, label=2, 285 | has_default_value=False, default_value=_b("").decode('utf-8'), 286 | message_type=None, enum_type=None, containing_type=None, 287 | is_extension=False, extension_scope=None, 288 | options=None), 289 | _descriptor.FieldDescriptor( 290 | name='features', full_name='vector_tile.Tile.Layer.features', index=2, 291 | number=2, type=11, cpp_type=10, label=3, 292 | has_default_value=False, default_value=[], 293 | message_type=None, enum_type=None, containing_type=None, 294 | is_extension=False, extension_scope=None, 295 | options=None), 296 | _descriptor.FieldDescriptor( 297 | name='keys', full_name='vector_tile.Tile.Layer.keys', index=3, 298 | number=3, type=9, cpp_type=9, label=3, 299 | has_default_value=False, default_value=[], 300 | message_type=None, enum_type=None, containing_type=None, 301 | is_extension=False, extension_scope=None, 302 | options=None), 303 | _descriptor.FieldDescriptor( 304 | name='values', full_name='vector_tile.Tile.Layer.values', index=4, 305 | number=4, type=11, cpp_type=10, label=3, 306 | has_default_value=False, default_value=[], 307 | message_type=None, enum_type=None, containing_type=None, 308 | is_extension=False, extension_scope=None, 309 | options=None), 310 | _descriptor.FieldDescriptor( 311 | name='extent', full_name='vector_tile.Tile.Layer.extent', index=5, 312 | number=5, type=13, cpp_type=3, label=1, 313 | has_default_value=True, default_value=4096, 314 | message_type=None, enum_type=None, containing_type=None, 315 | is_extension=False, extension_scope=None, 316 | options=None), 317 | _descriptor.FieldDescriptor( 318 | name='string_values', full_name='vector_tile.Tile.Layer.string_values', index=6, 319 | number=6, type=9, cpp_type=9, label=3, 320 | has_default_value=False, default_value=[], 321 | message_type=None, enum_type=None, containing_type=None, 322 | is_extension=False, extension_scope=None, 323 | options=None), 324 | _descriptor.FieldDescriptor( 325 | name='float_values', full_name='vector_tile.Tile.Layer.float_values', index=7, 326 | number=7, type=2, cpp_type=6, label=3, 327 | has_default_value=False, default_value=[], 328 | message_type=None, enum_type=None, containing_type=None, 329 | is_extension=False, extension_scope=None, 330 | options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), 331 | _descriptor.FieldDescriptor( 332 | name='double_values', full_name='vector_tile.Tile.Layer.double_values', index=8, 333 | number=8, type=1, cpp_type=5, label=3, 334 | has_default_value=False, default_value=[], 335 | message_type=None, enum_type=None, containing_type=None, 336 | is_extension=False, extension_scope=None, 337 | options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), 338 | _descriptor.FieldDescriptor( 339 | name='int_values', full_name='vector_tile.Tile.Layer.int_values', index=9, 340 | number=9, type=6, cpp_type=4, label=3, 341 | has_default_value=False, default_value=[], 342 | message_type=None, enum_type=None, containing_type=None, 343 | is_extension=False, extension_scope=None, 344 | options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), 345 | _descriptor.FieldDescriptor( 346 | name='elevation_scaling', full_name='vector_tile.Tile.Layer.elevation_scaling', index=10, 347 | number=10, type=11, cpp_type=10, label=1, 348 | has_default_value=False, default_value=None, 349 | message_type=None, enum_type=None, containing_type=None, 350 | is_extension=False, extension_scope=None, 351 | options=None), 352 | _descriptor.FieldDescriptor( 353 | name='attribute_scalings', full_name='vector_tile.Tile.Layer.attribute_scalings', index=11, 354 | number=11, type=11, cpp_type=10, label=3, 355 | has_default_value=False, default_value=[], 356 | message_type=None, enum_type=None, containing_type=None, 357 | is_extension=False, extension_scope=None, 358 | options=None), 359 | _descriptor.FieldDescriptor( 360 | name='tile_x', full_name='vector_tile.Tile.Layer.tile_x', index=12, 361 | number=12, type=13, cpp_type=3, label=1, 362 | has_default_value=False, default_value=0, 363 | message_type=None, enum_type=None, containing_type=None, 364 | is_extension=False, extension_scope=None, 365 | options=None), 366 | _descriptor.FieldDescriptor( 367 | name='tile_y', full_name='vector_tile.Tile.Layer.tile_y', index=13, 368 | number=13, type=13, cpp_type=3, label=1, 369 | has_default_value=False, default_value=0, 370 | message_type=None, enum_type=None, containing_type=None, 371 | is_extension=False, extension_scope=None, 372 | options=None), 373 | _descriptor.FieldDescriptor( 374 | name='tile_zoom', full_name='vector_tile.Tile.Layer.tile_zoom', index=14, 375 | number=14, type=13, cpp_type=3, label=1, 376 | has_default_value=False, default_value=0, 377 | message_type=None, enum_type=None, containing_type=None, 378 | is_extension=False, extension_scope=None, 379 | options=None), 380 | ], 381 | extensions=[ 382 | ], 383 | nested_types=[], 384 | enum_types=[ 385 | ], 386 | options=None, 387 | is_extendable=True, 388 | extension_ranges=[(16, 536870912), ], 389 | oneofs=[ 390 | ], 391 | serialized_start=580, 392 | serialized_end=1013, 393 | ) 394 | 395 | _TILE = _descriptor.Descriptor( 396 | name='Tile', 397 | full_name='vector_tile.Tile', 398 | filename=None, 399 | file=DESCRIPTOR, 400 | containing_type=None, 401 | fields=[ 402 | _descriptor.FieldDescriptor( 403 | name='layers', full_name='vector_tile.Tile.layers', index=0, 404 | number=3, type=11, cpp_type=10, label=3, 405 | has_default_value=False, default_value=[], 406 | message_type=None, enum_type=None, containing_type=None, 407 | is_extension=False, extension_scope=None, 408 | options=None), 409 | ], 410 | extensions=[ 411 | ], 412 | nested_types=[_TILE_VALUE, _TILE_SCALING, _TILE_FEATURE, _TILE_LAYER, ], 413 | enum_types=[ 414 | _TILE_GEOMTYPE, 415 | ], 416 | options=None, 417 | is_extendable=True, 418 | extension_ranges=[(16, 8192), ], 419 | oneofs=[ 420 | ], 421 | serialized_start=35, 422 | serialized_end=1097, 423 | ) 424 | 425 | _TILE_VALUE.containing_type = _TILE 426 | _TILE_SCALING.containing_type = _TILE 427 | _TILE_FEATURE.fields_by_name['type'].enum_type = _TILE_GEOMTYPE 428 | _TILE_FEATURE.containing_type = _TILE 429 | _TILE_LAYER.fields_by_name['features'].message_type = _TILE_FEATURE 430 | _TILE_LAYER.fields_by_name['values'].message_type = _TILE_VALUE 431 | _TILE_LAYER.fields_by_name['elevation_scaling'].message_type = _TILE_SCALING 432 | _TILE_LAYER.fields_by_name['attribute_scalings'].message_type = _TILE_SCALING 433 | _TILE_LAYER.containing_type = _TILE 434 | _TILE.fields_by_name['layers'].message_type = _TILE_LAYER 435 | _TILE_GEOMTYPE.containing_type = _TILE 436 | DESCRIPTOR.message_types_by_name['Tile'] = _TILE 437 | 438 | Tile = _reflection.GeneratedProtocolMessageType('Tile', (_message.Message,), dict( 439 | 440 | Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), dict( 441 | DESCRIPTOR = _TILE_VALUE, 442 | __module__ = 'vector_tile_pb2' 443 | # @@protoc_insertion_point(class_scope:vector_tile.Tile.Value) 444 | )) 445 | , 446 | 447 | Scaling = _reflection.GeneratedProtocolMessageType('Scaling', (_message.Message,), dict( 448 | DESCRIPTOR = _TILE_SCALING, 449 | __module__ = 'vector_tile_pb2' 450 | # @@protoc_insertion_point(class_scope:vector_tile.Tile.Scaling) 451 | )) 452 | , 453 | 454 | Feature = _reflection.GeneratedProtocolMessageType('Feature', (_message.Message,), dict( 455 | DESCRIPTOR = _TILE_FEATURE, 456 | __module__ = 'vector_tile_pb2' 457 | # @@protoc_insertion_point(class_scope:vector_tile.Tile.Feature) 458 | )) 459 | , 460 | 461 | Layer = _reflection.GeneratedProtocolMessageType('Layer', (_message.Message,), dict( 462 | DESCRIPTOR = _TILE_LAYER, 463 | __module__ = 'vector_tile_pb2' 464 | # @@protoc_insertion_point(class_scope:vector_tile.Tile.Layer) 465 | )) 466 | , 467 | DESCRIPTOR = _TILE, 468 | __module__ = 'vector_tile_pb2' 469 | # @@protoc_insertion_point(class_scope:vector_tile.Tile) 470 | )) 471 | _sym_db.RegisterMessage(Tile) 472 | _sym_db.RegisterMessage(Tile.Value) 473 | _sym_db.RegisterMessage(Tile.Scaling) 474 | _sym_db.RegisterMessage(Tile.Feature) 475 | _sym_db.RegisterMessage(Tile.Layer) 476 | 477 | 478 | DESCRIPTOR.has_options = True 479 | DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('H\003')) 480 | _TILE_FEATURE.fields_by_name['tags'].has_options = True 481 | _TILE_FEATURE.fields_by_name['tags']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) 482 | _TILE_FEATURE.fields_by_name['geometry'].has_options = True 483 | _TILE_FEATURE.fields_by_name['geometry']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) 484 | _TILE_FEATURE.fields_by_name['attributes'].has_options = True 485 | _TILE_FEATURE.fields_by_name['attributes']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) 486 | _TILE_FEATURE.fields_by_name['geometric_attributes'].has_options = True 487 | _TILE_FEATURE.fields_by_name['geometric_attributes']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) 488 | _TILE_FEATURE.fields_by_name['elevation'].has_options = True 489 | _TILE_FEATURE.fields_by_name['elevation']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) 490 | _TILE_FEATURE.fields_by_name['spline_knots'].has_options = True 491 | _TILE_FEATURE.fields_by_name['spline_knots']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) 492 | _TILE_LAYER.fields_by_name['float_values'].has_options = True 493 | _TILE_LAYER.fields_by_name['float_values']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) 494 | _TILE_LAYER.fields_by_name['double_values'].has_options = True 495 | _TILE_LAYER.fields_by_name['double_values']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) 496 | _TILE_LAYER.fields_by_name['int_values'].has_options = True 497 | _TILE_LAYER.fields_by_name['int_values']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) 498 | # @@protoc_insertion_point(module_scope) 499 | --------------------------------------------------------------------------------