├── .gitignore ├── .gitmodules ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── UPGRADING.md ├── bench └── run.cpp ├── demo ├── Makefile ├── README.md └── decode.cpp ├── include └── mapbox │ ├── vector_tile.hpp │ └── vector_tile │ ├── vector_tile_config.hpp │ └── version.hpp ├── include_dirs.js ├── package.json └── test ├── duplicate-keys-values.mvt ├── include └── catch.hpp ├── test046.mvt ├── test2048.mvt └── unit ├── catch.cpp ├── tags.test.cpp └── vector_tile.test.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .mason 3 | mason_packages 4 | demo/decode 5 | demo/data 6 | demo/include -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/mvt-fixtures"] 2 | path = test/mvt-fixtures 3 | url = https://github.com/mapbox/mvt-fixtures.git 4 | [submodule "bench/mvt-bench-fixtures"] 5 | path = bench/mvt-bench-fixtures 6 | url = https://github.com/mapbox/mvt-bench-fixtures.git 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | Makefile 3 | mason_packages 4 | build 5 | .travis.yml 6 | .npmignore 7 | .mason 8 | .gitmodules 9 | demo -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | matrix: 4 | include: 5 | - os: linux 6 | sudo: false 7 | env: CXX=g++-4.9 BUILDTYPE=Release 8 | addons: 9 | apt: 10 | sources: [ 'ubuntu-toolchain-r-test' ] 11 | packages: [ 'g++-4.9', 'libstdc++-4.9-dev' ] 12 | - os: linux 13 | sudo: false 14 | env: CXX=g++-5 BUILDTYPE=Release 15 | addons: 16 | apt: 17 | sources: [ 'ubuntu-toolchain-r-test' ] 18 | packages: [ 'g++-5', 'libstdc++-5-dev' ] 19 | - os: linux 20 | sudo: false 21 | env: CXX=clang++-3.9 BUILDTYPE=Release 22 | addons: 23 | apt: 24 | sources: [ 'ubuntu-toolchain-r-test' ] 25 | packages: [ 'libstdc++-5-dev' ] 26 | # linux libc++ (vs libstdc++) 27 | - os: linux 28 | sudo: false 29 | env: CXX=clang++-3.9 BUILDTYPE=Release CXXFLAGS="-stdlib=libc++ -lc++ -lc++abi -lpthread -lrt -ldl" 30 | addons: 31 | apt: 32 | sources: [ 'ubuntu-toolchain-r-test' ] 33 | packages: [ 'libstdc++-5-dev' ] 34 | - os: linux 35 | sudo: false 36 | env: CXX=clang++-3.9 BUILDTYPE=Debug CXXFLAGS="-stdlib=libc++ -lc++ -lc++abi -lpthread -lrt -ldl" 37 | addons: 38 | apt: 39 | sources: [ 'ubuntu-toolchain-r-test' ] 40 | packages: [ 'libstdc++-5-dev' ] 41 | - os: linux 42 | sudo: false 43 | env: CXX=clang++-3.9 BUILDTYPE=Debug CXXFLAGS="-fsanitize=address" 44 | addons: 45 | apt: 46 | sources: [ 'ubuntu-toolchain-r-test' ] 47 | packages: [ 'libstdc++-5-dev' ] 48 | - os: osx 49 | osx_image: xcode7.3 50 | 51 | cache: apt 52 | 53 | install: 54 | - | 55 | if [[ ${CXX} =~ "clang" ]] && [[ $(uname -s) == "Linux" ]]; then 56 | mkdir ./mason 57 | curl -sSfL https://github.com/mapbox/mason/archive/v0.8.0.tar.gz | tar --gunzip --extract --strip-components=1 --exclude="*md" --exclude="test*" --directory=./mason 58 | ./mason/mason install clang++ 3.9.1 59 | export PATH=$(./mason/mason prefix clang++ 3.9.1)/bin:${PATH} 60 | which clang++ 61 | ./mason/mason install binutils 2.27 62 | export PATH=$(./mason/mason prefix binutils 2.27)/bin:${PATH} 63 | which ld 64 | fi 65 | 66 | script: 67 | - make test 68 | - make bench 69 | - make run-demo -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # 1.0.4 4 | 5 | - Prevent rare situation where a feature with a command count of 0 would trigger an underflow while decoding a vector tile's geometry. 6 | 7 | # 1.0.3 8 | 9 | - Support for geometry v1.0.0 10 | 11 | ## 1.0.2 12 | 13 | - Add support for QNX 7 compiler. 14 | 15 | ## 1.0.1 16 | 17 | - Limit possibility rare situation where it might be possible to have an overflow or underflow of integers while decoding a vector tile's geometry 18 | 19 | ## 1.0.0 20 | 21 | - Allow nulls from property parsing 22 | - Several bug fixes around geometry parsing to prevent over allocation of memory. 23 | 24 | ## 1.0.0-alpha.1 25 | 26 | - Added demo application showing usage of decoder 27 | 28 | ## 1.0.0-rc4 29 | 30 | - Add decoder implementation 31 | 32 | ## 1.0.0-rc3 33 | 34 | - expose a node.js-style `require()` so that other projects can dynamically determine the path to the headers, exactly where npm has decided to install them (sensitive to deduping). 35 | 36 | ## 1.0.0-rc2 37 | 38 | - `#include` paths are now "quoted" 39 | - `package.json` for publishing `.hpp` headers to npm 40 | 41 | ## 1.0.0-rc1 42 | 43 | - `vector-tile` initial release, which includes enum classes referencing the Mapbox Vector Tile Specification protobuf tags. [#2](https://github.com/mapbox/vector-tile/issues/2). 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to vector-tile 2 | 3 | ## Releasing 4 | 5 | To release a new vector-tile version: 6 | 7 | - Make sure all tests are passing on travis 8 | - Update version number in 9 | - include/mapbox/vector-tile/version.hpp 10 | - update unit tests of version constants in test/unit/vector_tile.test.cpp 11 | - Update CHANGELOG.md 12 | - Update UPGRADING.md 13 | - Create a new tag and push to github `git push --tags` 14 | - Publish to npm: 15 | - First run `make testpack` to see what files will be published 16 | - If you see any unwanted files, then add them to `.npmignore` 17 | - Then run: `npm publish` 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Mapbox 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC := $(CC) 2 | CXX := $(CXX) 3 | CXXFLAGS := $(CXXFLAGS) -isystem mason_packages/.link/include/ -Iinclude -std=c++14 -DPROTOZERO_STRICT_API=1 4 | RELEASE_FLAGS := -O3 -DNDEBUG -flto -fvisibility-inlines-hidden -fvisibility=hidden 5 | WARNING_FLAGS := -Wall -Wextra -pedantic -Werror -Wsign-compare -Wfloat-equal -Wconversion -Wshadow 6 | CLANG_WARNING_FLAGS := -Wno-unsequenced -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-exit-time-destructors 7 | ifneq (,$(findstring clang,$(CXX))) 8 | WARNING_FLAGS += $(CLANG_WARNING_FLAGS) 9 | endif 10 | DEBUG_FLAGS := -O0 -DDEBUG -fno-inline-functions -fno-omit-frame-pointer 11 | DEMO_DIR:=./demo 12 | 13 | export BUILDTYPE ?= Release 14 | 15 | export GEOMETRY_RELEASE ?= 1.0.0 16 | export PROTOZERO_RELEASE ?= 1.5.1 17 | export VARIANT_RELEASE ?= 1.1.4 18 | 19 | ifeq ($(BUILDTYPE),Release) 20 | FINAL_FLAGS := $(WARNING_FLAGS) $(RELEASE_FLAGS) 21 | else 22 | FINAL_FLAGS := -g $(WARNING_FLAGS) $(DEBUG_FLAGS) 23 | endif 24 | 25 | default: test 26 | 27 | HEADERS = $(wildcard include/mapbox/vector_tile/*.hpp) include/mapbox/vector_tile.hpp 28 | 29 | ./.mason/mason: 30 | mkdir ./.mason 31 | curl -sSfL https://github.com/mapbox/mason/archive/v0.19.0.tar.gz | tar --gunzip --extract --strip-components=1 --exclude="*md" --exclude="test*" --directory=./.mason 32 | 33 | mason_packages/headers/protozero: .mason/mason 34 | .mason/mason install protozero $(PROTOZERO_RELEASE) && .mason/mason link protozero $(PROTOZERO_RELEASE) 35 | 36 | mason_packages/headers/geometry: .mason/mason 37 | .mason/mason install geometry $(GEOMETRY_RELEASE) && .mason/mason link geometry $(GEOMETRY_RELEASE) 38 | 39 | mason_packages/headers/variant: .mason/mason 40 | .mason/mason install variant $(VARIANT_RELEASE) && .mason/mason link variant $(VARIANT_RELEASE) 41 | 42 | deps: mason_packages/headers/geometry mason_packages/headers/variant mason_packages/headers/protozero 43 | 44 | build/$(BUILDTYPE)/test: test/unit/* $(HEADERS) Makefile 45 | mkdir -p build/$(BUILDTYPE)/ 46 | $(CXX) $(FINAL_FLAGS) test/unit/*.cpp -isystem test/include $(CXXFLAGS) -o build/$(BUILDTYPE)/test 47 | 48 | test/mvt-fixtures: 49 | git submodule update --init 50 | 51 | test: deps build/$(BUILDTYPE)/test test/mvt-fixtures 52 | ./build/$(BUILDTYPE)/test 53 | 54 | # added with: git submodule add https://github.com/mapbox/mvt-bench-fixtures.git bench/mvt-bench-fixtures 55 | bench/mvt-bench-fixtures: 56 | git submodule update --init 57 | 58 | build/$(BUILDTYPE)/bench: bench/* $(HEADERS) Makefile bench/mvt-bench-fixtures 59 | mkdir -p build/$(BUILDTYPE)/ 60 | $(CXX) $(FINAL_FLAGS) bench/*.cpp $(CXXFLAGS) -o build/$(BUILDTYPE)/bench 61 | 62 | bench: deps build/$(BUILDTYPE)/bench 63 | ./build/$(BUILDTYPE)/bench 64 | 65 | debug: 66 | BUILDTYPE=Debug make test 67 | 68 | COMMON_DOC_FLAGS = --report --output docs $(HEADERS) 69 | 70 | clean: 71 | rm -rf build/ 72 | rm -rf demo/data/ 73 | rm -rf demo/include/ 74 | 75 | distclean: clean 76 | rm -rf .mason 77 | rm -rf mason_packages 78 | 79 | cldoc: 80 | pip install cldoc --user 81 | 82 | testpack: 83 | rm -f ./*tgz 84 | npm pack 85 | tar -ztvf *tgz 86 | rm -f ./*tgz 87 | 88 | demo: 89 | rm -rf $(DEMO_DIR)/include 90 | rm -rf $(DEMO_DIR)/data 91 | rm -rf $(DEMO_DIR)/decode 92 | mkdir -p $(DEMO_DIR)/include/ 93 | mkdir -p $(DEMO_DIR)/data/ 94 | cp -r include/* $(DEMO_DIR)/include/ 95 | cp -r $(shell .mason/mason prefix geometry $(GEOMETRY_RELEASE))/include/* $(DEMO_DIR)/include/ 96 | cp -r $(shell .mason/mason prefix variant $(VARIANT_RELEASE))/include/* $(DEMO_DIR)/include/ 97 | cp -r $(shell .mason/mason prefix protozero $(PROTOZERO_RELEASE))/include/* $(DEMO_DIR)/include/ 98 | cp test/mvt-fixtures/fixtures/valid/* $(DEMO_DIR)/data/ 99 | 100 | run-demo: demo 101 | make -C $(DEMO_DIR) 102 | ./demo/decode ./demo/data/Feature-single-point.mvt 103 | 104 | docs: cldoc 105 | cldoc generate $(CXXFLAGS) -- $(COMMON_DOC_FLAGS) 106 | 107 | .PHONY: demo run-demo -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Vector Tile Library 2 | 3 | C++14 library for decoding [Mapbox Vector Tiles](https://www.mapbox.com/vector-tiles/). 4 | 5 | [![Build Status](https://travis-ci.org/mapbox/vector-tile.svg?branch=master)](https://travis-ci.org/mapbox/vector-tile) 6 | 7 | ## Depends 8 | 9 | - C++14 compiler 10 | - [protozero](https://github.com/mapbox/protozero) 11 | - [variant](https://github.com/mapbox/variant) 12 | - [geometry](https://github.com/mapbox/geometry.hpp) 13 | 14 | 15 | ## Building 16 | 17 | Call 18 | ```sh 19 | git submodule init 20 | git submodule update 21 | ``` 22 | 23 | to install test fixtures from an external git repository. 24 | 25 | To install all dependencies and build the tests in this repo do: 26 | 27 | ```sh 28 | make test 29 | ``` 30 | 31 | ## To bundle the `demo` program do: 32 | 33 | ```sh 34 | make demo 35 | ``` 36 | 37 | This copies all the includes into the `demo/include` folder such that the demo can be build like: 38 | 39 | ```sh 40 | make -C demo/ 41 | ``` 42 | 43 | Or also like: 44 | 45 | ```sh 46 | cd demo 47 | make 48 | ``` 49 | 50 | # Who is using vector-tile? 51 | 52 | These are the applications targeted to upgrade to this library: 53 | 54 | * [Mapbox GL Native](https://github.com/mapbox/mapbox-gl-native) 55 | * [Mapnik Vector Tile](https://github.com/mapbox/mapnik-vector-tile) 56 | * [Tippecanoe](https://github.com/mapbox/tippecanoe) 57 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | TODO -------------------------------------------------------------------------------- /bench/run.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | std::size_t feature_count = 0; 7 | 8 | static void decode_entire_tile(std::string const& buffer) { 9 | mapbox::vector_tile::buffer tile(buffer); 10 | for (auto const& name : tile.layerNames()) { 11 | const mapbox::vector_tile::layer layer = tile.getLayer(name); 12 | std::size_t num_features = layer.featureCount(); 13 | if (num_features == 0) { 14 | std::cout << "Layer '" << name << "' (empty)\n"; 15 | continue; 16 | } 17 | for (std::size_t i=0;i()) { 21 | throw std::runtime_error("Hit unexpected error decoding feature"); 22 | } 23 | auto props = feature.getProperties(); 24 | mapbox::vector_tile::points_arrays_type geom = feature.getGeometries(1.0); 25 | ++feature_count; 26 | } 27 | } 28 | } 29 | 30 | static void run_bench(std::vector const& tiles, std::size_t iterations) { 31 | 32 | for (std::size_t i=0;i 40 | using milliseconds = std::chrono::duration; 41 | 42 | int main(/*int argc, char* const argv[]*/) { 43 | try { 44 | std::vector tiles; 45 | for (std::size_t x=4680;x<=4693;++x) { 46 | for (std::size_t y=6260;y<=6274;++y) { 47 | std::string path = "bench/mvt-bench-fixtures/fixtures/14-" + std::to_string(x) + "-" + std::to_string(y) + ".mvt"; 48 | std::ifstream stream(path.c_str(),std::ios_base::in|std::ios_base::binary); 49 | if (!stream.is_open()) 50 | { 51 | throw std::runtime_error("could not open: '" + path + "'"); 52 | } 53 | std::string message(std::istreambuf_iterator(stream.rdbuf()),(std::istreambuf_iterator())); 54 | stream.close(); 55 | tiles.emplace_back(message); 56 | } 57 | } 58 | std::clog << "decoding " << tiles.size() << " tiles\n"; 59 | std::clog << "warming up...\n"; 60 | // warmup 61 | run_bench(tiles,1); 62 | std::clog << "running bench...\n"; 63 | // now actually decode all tiles for N iterations 64 | auto t1 = std::chrono::high_resolution_clock::now(); 65 | run_bench(tiles,100); 66 | auto t2 = std::chrono::high_resolution_clock::now(); 67 | auto elapsed = milliseconds(t2 - t1).count(); 68 | if (feature_count != 8157770) { 69 | std::clog << "Warning expected feature_count of 8157770, was: " << feature_count << "\n"; 70 | } 71 | std::clog << "elapsed: " << std::fixed << elapsed << " ms\n"; 72 | } catch (std::exception const& ex) { 73 | std::cerr << ex.what() << "\n"; 74 | return -1; 75 | } 76 | return 0; 77 | } -------------------------------------------------------------------------------- /demo/Makefile: -------------------------------------------------------------------------------- 1 | CC := $(CC) 2 | CXX := $(CXX) 3 | CXXFLAGS := $(CXXFLAGS) -Iinclude -std=c++14 -Wall 4 | RELEASE_FLAGS := -O3 -DNDEBUG -fvisibility-inlines-hidden -fvisibility=hidden 5 | DEBUG_FLAGS := -g -O0 -DDEBUG -fno-inline-functions -fno-omit-frame-pointer 6 | 7 | export BUILDTYPE ?= Release 8 | 9 | ifeq ($(BUILDTYPE),Release) 10 | FINAL_FLAGS := $(RELEASE_FLAGS) 11 | else 12 | FINAL_FLAGS := $(DEBUG_FLAGS) 13 | endif 14 | 15 | 16 | default: decode 17 | 18 | decode: decode.cpp 19 | $(CXX) decode.cpp $(FINAL_FLAGS) $(CXXFLAGS) -o decode 20 | 21 | 22 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | ## Vector Tile Library Demonstration 2 | 3 | This is a command line program that shows how to decode a Mapbox Vector Tile in C++. 4 | 5 | It uses the Mapbox C++ Vector Tile library inside the `include/` directory. 6 | 7 | ## Depends 8 | 9 | - C++14 compiler (>= clang++ 3.6, g++ 5) 10 | - make (https://www.gnu.org/software/make) 11 | 12 | ## Building 13 | 14 | To build the demo program, called `decode`: 15 | 16 | 1) Open a terminal and navigate into the directory containing this README.md 17 | 18 | 2) Then type: 19 | 20 | ```bash 21 | make 22 | ``` 23 | 24 | 3) Then run the decode program against the same data: 25 | 26 | ```bash 27 | ./decode data/Feature-single-point.mvt 28 | ``` 29 | 30 | ## Next steps 31 | 32 | - Look inside `decode.cpp` for examples of how to decode tiles. 33 | - For more usage details see the tests at https://github.com/mapbox/vector-tile/blob/master/test/unit/vector_tile.test.cpp 34 | - For more details about available Mapbox Vector tiles see: https://www.mapbox.com/vector-tiles/ -------------------------------------------------------------------------------- /demo/decode.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | std::string open_tile(std::string const& path) { 7 | std::ifstream stream(path.c_str(),std::ios_base::in|std::ios_base::binary); 8 | if (!stream.is_open()) 9 | { 10 | throw std::runtime_error("could not open: '" + path + "'"); 11 | } 12 | std::string message(std::istreambuf_iterator(stream.rdbuf()),(std::istreambuf_iterator())); 13 | stream.close(); 14 | return message; 15 | } 16 | 17 | class print_value { 18 | 19 | public: 20 | std::string operator()(std::vector val) { 21 | return "vector"; 22 | } 23 | 24 | std::string operator()(std::unordered_map val) { 25 | return "unordered_map"; 26 | } 27 | 28 | std::string operator()(mapbox::feature::null_value_t val) { 29 | return "null"; 30 | } 31 | 32 | std::string operator()(std::nullptr_t val) { 33 | return "nullptr"; 34 | } 35 | 36 | std::string operator()(uint64_t val) { 37 | return std::to_string(val); 38 | } 39 | std::string operator()(int64_t val) { 40 | return std::to_string(val); 41 | } 42 | std::string operator()(double val) { 43 | return std::to_string(val); 44 | } 45 | std::string operator()(std::string const& val) { 46 | return val; 47 | } 48 | 49 | std::string operator()(bool val) { 50 | return val ? "true" : "false"; 51 | } 52 | }; 53 | 54 | int main(int argc, char** argv) { 55 | try { 56 | if (argc < 2) { 57 | std::clog << "please provide path to uncompressed Mapbox Vector tile (.mvt)\n"; 58 | return -1; 59 | } 60 | std::string tile_path(argv[1]); 61 | std::string buffer = open_tile(tile_path); 62 | mapbox::vector_tile::buffer tile(buffer); 63 | std::cout << "Decoding tile: " << tile_path << "\n"; 64 | for (auto const& name : tile.layerNames()) { 65 | const mapbox::vector_tile::layer layer = tile.getLayer(name); 66 | std::size_t feature_count = layer.featureCount(); 67 | if (feature_count == 0) { 68 | std::cout << "Layer '" << name << "' (empty)\n"; 69 | continue; 70 | } 71 | std::cout << "Layer '" << name << "'\n"; 72 | std::cout << " Features:\n"; 73 | for (std::size_t i=0;i()) { 77 | std::cout << " id: " << feature_id.get() << "\n"; 78 | } else { 79 | std::cout << " id: (no id set)\n"; 80 | } 81 | std::cout << " type: " << int(feature.getType()) << "\n"; 82 | auto props = feature.getProperties(); 83 | std::cout << " Properties:\n"; 84 | for (auto const& prop : props) { 85 | print_value printvisitor; 86 | std::string value = mapbox::util::apply_visitor(printvisitor, prop.second); 87 | std::cout << " " << prop.first << ": " << value << "\n"; 88 | } 89 | std::cout << " Vertices:\n"; 90 | mapbox::vector_tile::points_arrays_type geom = feature.getGeometries(1.0); 91 | for (auto const& point_array : geom) { 92 | for (auto const& point : point_array) { 93 | std::clog << point.x << "," << point.y; 94 | } 95 | } 96 | std::clog << "\n"; 97 | } 98 | } 99 | } catch (std::exception const& ex) { 100 | std::clog << "ERROR: " << ex.what() << "\n"; 101 | return -1; 102 | } 103 | } -------------------------------------------------------------------------------- /include/mapbox/vector_tile.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vector_tile/vector_tile_config.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include // reference_wrapper 12 | #include 13 | #include 14 | 15 | namespace mapbox { namespace vector_tile { 16 | 17 | using point_type = mapbox::geometry::point; 18 | 19 | class points_array_type : public std::vector { 20 | public: 21 | using coordinate_type = point_type::coordinate_type; 22 | template 23 | points_array_type(Args&&... args) : std::vector(std::forward(args)...) {} 24 | }; 25 | 26 | class points_arrays_type : public std::vector { 27 | public: 28 | using coordinate_type = points_array_type::coordinate_type; 29 | template 30 | points_arrays_type(Args&&... args) : std::vector(std::forward(args)...) {} 31 | }; 32 | 33 | class layer; 34 | 35 | class feature { 36 | public: 37 | using properties_type = mapbox::feature::property_map; 38 | using packed_iterator_type = protozero::iterator_range; 39 | 40 | feature(protozero::data_view const&, layer const&); 41 | 42 | GeomType getType() const { return type; } 43 | /** 44 | * Retrieve the value associated with a given key from the feature. 45 | * 46 | * @param key The key used to look up the corresponding value. 47 | * @param warning A pointer to a string that may be used to record any warnings that 48 | * occur during the lookup process. 49 | * The caller is responsible for managing the memory of this string. 50 | * @return The value associated with the specified key, or a null value if the key is not found. 51 | * 52 | * Note: If the lookup process encounters a duplicate key in the feature, the function will 53 | * return the value in the `values` set to which the associated tag index points to, and 54 | * will append a message to the `warning` string (if provided) to alert the caller to the 55 | * presence of the duplicate key. 56 | * The caller should ensure that the `warning` string is properly initialized 57 | * and cleaned up after use. 58 | */ 59 | mapbox::feature::value getValue(std::string const&, std::string* warning = nullptr) const; 60 | properties_type getProperties() const; 61 | mapbox::feature::identifier const& getID() const; 62 | std::uint32_t getExtent() const; 63 | std::uint32_t getVersion() const; 64 | template 65 | GeometryCollectionType getGeometries(float scale) const; 66 | 67 | private: 68 | const layer& layer_; 69 | mapbox::feature::identifier id; 70 | GeomType type = GeomType::UNKNOWN; 71 | packed_iterator_type tags_iter; 72 | packed_iterator_type geometry_iter; 73 | }; 74 | 75 | class layer { 76 | public: 77 | layer(protozero::data_view const& layer_view); 78 | 79 | std::size_t featureCount() const { return features.size(); } 80 | protozero::data_view const& getFeature(std::size_t) const; 81 | std::string const& getName() const; 82 | std::uint32_t getExtent() const { return extent; } 83 | std::uint32_t getVersion() const { return version; } 84 | 85 | private: 86 | friend class feature; 87 | 88 | std::string name; 89 | std::uint32_t version; 90 | std::uint32_t extent; 91 | std::multimap keysMap; 92 | std::vector> keys; 93 | std::vector values; 94 | std::vector features; 95 | }; 96 | 97 | class buffer { 98 | public: 99 | buffer(std::string const& data); 100 | std::vector layerNames() const; 101 | std::map getLayers() const { return layers; }; 102 | layer getLayer(const std::string&) const; 103 | 104 | private: 105 | std::map layers; 106 | }; 107 | 108 | static mapbox::feature::value parseValue(protozero::data_view const& value_view) { 109 | mapbox::feature::value value; 110 | protozero::pbf_reader value_reader(value_view); 111 | while (value_reader.next()) 112 | { 113 | switch (value_reader.tag()) { 114 | case ValueType::STRING: 115 | value = value_reader.get_string(); 116 | break; 117 | case ValueType::FLOAT: 118 | value = static_cast(value_reader.get_float()); 119 | break; 120 | case ValueType::DOUBLE: 121 | value = value_reader.get_double(); 122 | break; 123 | case ValueType::INT: 124 | value = value_reader.get_int64(); 125 | break; 126 | case ValueType::UINT: 127 | value = value_reader.get_uint64(); 128 | break; 129 | case ValueType::SINT: 130 | value = value_reader.get_sint64(); 131 | break; 132 | case ValueType::BOOL: 133 | value = value_reader.get_bool(); 134 | break; 135 | default: 136 | value_reader.skip(); 137 | break; 138 | } 139 | } 140 | return value; 141 | } 142 | 143 | inline feature::feature(protozero::data_view const& feature_view, layer const& l) 144 | : layer_(l), 145 | id(), 146 | type(GeomType::UNKNOWN), 147 | tags_iter(), 148 | geometry_iter() 149 | { 150 | protozero::pbf_reader feature_pbf(feature_view); 151 | while (feature_pbf.next()) { 152 | switch (feature_pbf.tag()) { 153 | case FeatureType::ID: 154 | id = feature_pbf.get_uint64(); 155 | break; 156 | case FeatureType::TAGS: 157 | tags_iter = feature_pbf.get_packed_uint32(); 158 | break; 159 | case FeatureType::TYPE: 160 | type = static_cast(feature_pbf.get_enum()); 161 | break; 162 | case FeatureType::GEOMETRY: 163 | geometry_iter = feature_pbf.get_packed_uint32(); 164 | break; 165 | default: 166 | feature_pbf.skip(); 167 | break; 168 | } 169 | } 170 | } 171 | 172 | inline mapbox::feature::value feature::getValue(const std::string& key, std::string* warning ) const { 173 | const auto key_range = layer_.keysMap.equal_range(key); 174 | const auto key_count = std::distance(key_range.first, key_range.second) ; 175 | if (key_count < 1) { 176 | return mapbox::feature::null_value; 177 | } 178 | 179 | const auto values_count = layer_.values.size(); 180 | auto start_itr = tags_iter.begin(); 181 | const auto end_itr = tags_iter.end(); 182 | while (start_itr != end_itr) { 183 | std::uint32_t tag_key = static_cast(*start_itr++); 184 | 185 | if (start_itr == end_itr) { 186 | throw std::runtime_error("uneven number of feature tag ids"); 187 | } 188 | 189 | std::uint32_t tag_val = static_cast(*start_itr++);; 190 | if (values_count <= tag_val) { 191 | throw std::runtime_error("feature referenced out of range value"); 192 | } 193 | 194 | bool key_found = false; 195 | for (auto i = key_range.first; i != key_range.second; ++i) { 196 | if (i->second == tag_key) { 197 | key_found = true; 198 | break; 199 | } 200 | } 201 | 202 | if (key_found) { 203 | // Continue process with case when same keys having multiple tag ids. 204 | if (key_count > 1 && warning) { 205 | *warning = std::string("duplicate keys with different tag ids are found"); 206 | } 207 | return parseValue(layer_.values[tag_val]); 208 | } 209 | } 210 | 211 | return mapbox::feature::null_value; 212 | } 213 | 214 | inline feature::properties_type feature::getProperties() const { 215 | auto start_itr = tags_iter.begin(); 216 | const auto end_itr = tags_iter.end(); 217 | properties_type properties; 218 | auto iter_len = std::distance(start_itr,end_itr); 219 | if (iter_len > 0) { 220 | properties.reserve(static_cast(iter_len/2)); 221 | while (start_itr != end_itr) { 222 | std::uint32_t tag_key = static_cast(*start_itr++); 223 | if (start_itr == end_itr) { 224 | throw std::runtime_error("uneven number of feature tag ids"); 225 | } 226 | std::uint32_t tag_val = static_cast(*start_itr++); 227 | properties.emplace(layer_.keys.at(tag_key),parseValue(layer_.values.at(tag_val))); 228 | } 229 | } 230 | return properties; 231 | } 232 | 233 | inline mapbox::feature::identifier const& feature::getID() const { 234 | return id; 235 | } 236 | 237 | inline std::uint32_t feature::getExtent() const { 238 | return layer_.getExtent(); 239 | } 240 | 241 | inline std::uint32_t feature::getVersion() const { 242 | return layer_.getVersion(); 243 | } 244 | 245 | template 246 | GeometryCollectionType feature::getGeometries(float scale) const { 247 | std::uint8_t cmd = 1; 248 | std::uint32_t length = 0; 249 | std::int64_t x = 0; 250 | std::int64_t y = 0; 251 | 252 | GeometryCollectionType paths; 253 | 254 | paths.emplace_back(); 255 | 256 | auto start_itr = geometry_iter.begin(); 257 | const auto end_itr = geometry_iter.end(); 258 | bool first = true; 259 | std::uint32_t len_reserve = 0; 260 | std::size_t extra_coords = 0; 261 | if (type == GeomType::LINESTRING) { 262 | extra_coords = 1; 263 | } else if (type == GeomType::POLYGON) { 264 | extra_coords = 2; 265 | } 266 | bool is_point = type == GeomType::POINT; 267 | 268 | while (start_itr != end_itr) { 269 | if (length == 0) { 270 | std::uint32_t cmd_length = static_cast(*start_itr++); 271 | cmd = cmd_length & 0x7; 272 | length = len_reserve = cmd_length >> 3; 273 | // Prevents the creation of vector tiles that would cause 274 | // a denial of service from massive over allocation. Protection 275 | // limit is based on the assumption of an int64_t point which is 276 | // 16 bytes in size and wanting to have a maximum of 1 MB of memory 277 | // used. 278 | constexpr std::uint32_t MAX_LENGTH = (1024 * 1024) / 16; 279 | if (len_reserve > MAX_LENGTH) { 280 | len_reserve = MAX_LENGTH; 281 | } 282 | } 283 | 284 | if (cmd == CommandType::MOVE_TO || cmd == CommandType::LINE_TO) { 285 | if (length == 0) { 286 | // If length is still equal to zero after the preceding step, this 287 | // represents a command with an invalid command count, so we continue here to 288 | // exit appropriately rather than underflow when we decrement length 289 | continue; 290 | } 291 | 292 | --length; 293 | 294 | if (is_point) { 295 | if (first && cmd == CommandType::MOVE_TO) { 296 | // note: this invalidates pointers. So we always 297 | // dynamically get the path with paths.back() 298 | paths.reserve(len_reserve); 299 | first = false; 300 | } 301 | } else { 302 | if (first && cmd == CommandType::LINE_TO) { 303 | paths.back().reserve(len_reserve + extra_coords); 304 | first = false; 305 | } 306 | } 307 | 308 | if (cmd == CommandType::MOVE_TO && !paths.back().empty()) { 309 | if (paths.back().size() < paths.back().capacity()) { 310 | // Assuming we had an invalid length before 311 | // lets shrink to fit, just to make sure 312 | // we don't have a large capacity vector 313 | // just wasting memory 314 | paths.back().shrink_to_fit(); 315 | } 316 | paths.emplace_back(); 317 | if (!is_point) { 318 | first = true; 319 | } 320 | } 321 | 322 | x += protozero::decode_zigzag32(static_cast(*start_itr++)); 323 | y += protozero::decode_zigzag32(static_cast(*start_itr++)); 324 | float px = ::roundf(static_cast(x) * scale); 325 | float py = ::roundf(static_cast(y) * scale); 326 | static const float max_coord = static_cast(std::numeric_limits::max()); 327 | static const float min_coord = static_cast(std::numeric_limits::min()); 328 | 329 | if (px > max_coord || 330 | px < min_coord || 331 | py > max_coord || 332 | py < min_coord 333 | ) { 334 | throw std::runtime_error("paths outside valid range of coordinate_type"); 335 | } else { 336 | paths.back().emplace_back( 337 | static_cast(px), 338 | static_cast(py)); 339 | } 340 | } else if (cmd == CommandType::CLOSE) { 341 | if (!paths.back().empty()) { 342 | paths.back().push_back(paths.back()[0]); 343 | } 344 | length = 0; 345 | } else { 346 | throw std::runtime_error("unknown command"); 347 | } 348 | } 349 | if (paths.size() < paths.capacity()) { 350 | // Assuming we had an invalid length before 351 | // lets shrink to fit, just to make sure 352 | // we don't have a large capacity vector 353 | // just wasting memory 354 | paths.shrink_to_fit(); 355 | } 356 | #if defined(DEBUG) 357 | for (auto const& p : paths) { 358 | assert(p.size() == p.capacity()); 359 | } 360 | #endif 361 | return paths; 362 | } 363 | 364 | inline buffer::buffer(std::string const& data) 365 | : layers() { 366 | protozero::pbf_reader data_reader(data); 367 | while (data_reader.next(TileType::LAYERS)) { 368 | const protozero::data_view layer_view = data_reader.get_view(); 369 | protozero::pbf_reader layer_reader(layer_view); 370 | std::string name; 371 | bool has_name = false; 372 | while (layer_reader.next(LayerType::NAME)) { 373 | name = layer_reader.get_string(); 374 | has_name = true; 375 | } 376 | if (!has_name) { 377 | throw std::runtime_error("Layer missing name"); 378 | } 379 | layers.emplace(name, layer_view); 380 | } 381 | } 382 | 383 | inline std::vector buffer::layerNames() const { 384 | std::vector names; 385 | names.reserve(layers.size()); 386 | for (auto const& layer : layers) { 387 | names.emplace_back(layer.first); 388 | } 389 | return names; 390 | } 391 | 392 | inline layer buffer::getLayer(const std::string& name) const { 393 | auto layer_it = layers.find(name); 394 | if (layer_it == layers.end()) { 395 | throw std::runtime_error(std::string("no layer by the name of '")+name+"'"); 396 | } 397 | return layer(layer_it->second); 398 | } 399 | 400 | inline layer::layer(protozero::data_view const& layer_view) : 401 | name(), 402 | version(1), 403 | extent(4096), 404 | keysMap(), 405 | keys(), 406 | values(), 407 | features() 408 | { 409 | bool has_name = false; 410 | bool has_extent = false; 411 | bool has_version = false; 412 | protozero::pbf_reader layer_pbf(layer_view); 413 | while (layer_pbf.next()) { 414 | switch (layer_pbf.tag()) { 415 | case LayerType::NAME: 416 | { 417 | name = layer_pbf.get_string(); 418 | has_name = true; 419 | } 420 | break; 421 | case LayerType::FEATURES: 422 | { 423 | features.push_back(layer_pbf.get_view()); 424 | } 425 | break; 426 | case LayerType::KEYS: 427 | { 428 | // We want to keep the keys in the order of the vector tile 429 | // https://github.com/mapbox/mapbox-gl-native/pull/5183 430 | auto iter = keysMap.emplace(layer_pbf.get_string(), keys.size()); 431 | keys.emplace_back(std::reference_wrapper(iter->first)); 432 | } 433 | break; 434 | case LayerType::VALUES: 435 | { 436 | values.emplace_back(layer_pbf.get_view()); 437 | } 438 | break; 439 | case LayerType::EXTENT: 440 | { 441 | extent = layer_pbf.get_uint32(); 442 | has_extent = true; 443 | } 444 | break; 445 | case LayerType::VERSION: 446 | { 447 | version = layer_pbf.get_uint32(); 448 | has_version = true; 449 | } 450 | break; 451 | default: 452 | { 453 | layer_pbf.skip(); 454 | } 455 | break; 456 | } 457 | } 458 | if (!has_version || !has_name || !has_extent) { 459 | std::string msg("missing required field:"); 460 | if (!has_version) { 461 | msg += " version "; 462 | } 463 | if (!has_extent) { 464 | msg += " extent "; 465 | } 466 | if (!has_name) { 467 | msg += " name"; 468 | } 469 | throw std::runtime_error(msg.c_str()); 470 | } 471 | } 472 | 473 | inline protozero::data_view const& layer::getFeature(std::size_t i) const { 474 | return features.at(i); 475 | } 476 | 477 | inline std::string const& layer::getName() const { 478 | return name; 479 | } 480 | 481 | }} // namespace mapbox/vector_tile 482 | -------------------------------------------------------------------------------- /include/mapbox/vector_tile/vector_tile_config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace mapbox { namespace vector_tile { 6 | 7 | 8 | enum TileType : std::uint32_t 9 | { 10 | LAYERS = 3 11 | }; 12 | 13 | enum LayerType : std::uint32_t 14 | { 15 | VERSION = 15, 16 | NAME = 1, 17 | FEATURES = 2, 18 | KEYS = 3, 19 | VALUES = 4, 20 | EXTENT = 5 21 | }; 22 | 23 | enum FeatureType : std::uint32_t 24 | { 25 | ID = 1, 26 | TAGS = 2, 27 | TYPE = 3, 28 | GEOMETRY = 4, 29 | RASTER = 5 30 | }; 31 | 32 | enum ValueType : std::uint32_t 33 | { 34 | STRING = 1, 35 | FLOAT = 2, 36 | DOUBLE = 3, 37 | INT = 4, 38 | UINT = 5, 39 | SINT = 6, 40 | BOOL = 7 41 | }; 42 | 43 | enum GeomType : std::uint8_t 44 | { 45 | UNKNOWN = 0, 46 | POINT = 1, 47 | LINESTRING = 2, 48 | POLYGON = 3 49 | }; 50 | 51 | enum CommandType : std::uint8_t 52 | { 53 | END = 0, 54 | MOVE_TO = 1, 55 | LINE_TO = 2, 56 | CLOSE = 7 57 | }; 58 | 59 | 60 | } } 61 | -------------------------------------------------------------------------------- /include/mapbox/vector_tile/version.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// The major version number 4 | #define VECTOR_TILE_VERSION_MAJOR 1 5 | 6 | /// The minor version number 7 | #define VECTOR_TILE_VERSION_MINOR 0 8 | 9 | /// The patch number 10 | #define VECTOR_TILE_VERSION_PATCH 4 11 | 12 | /// The complete version number 13 | #define VECTOR_TILE_VERSION_CODE (VECTOR_TILE_VERSION_MAJOR * 10000 + VECTOR_TILE_VERSION_MINOR * 100 + VECTOR_TILE_VERSION_PATCH) 14 | 15 | /// Version number as string 16 | #define VECTOR_TILE_VERSION_STRING "1.0.4" 17 | -------------------------------------------------------------------------------- /include_dirs.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | console.log(path.join(path.relative('.', __dirname),'include')); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/vector-tile", 3 | "version": "1.0.0", 4 | "description": "A C++ header only library for decoding and encoding Mapbox Vector Tiles", 5 | "main": "./include_dirs.js", 6 | "repository" : { 7 | "type" : "git", 8 | "url" : "git://github.com/mapbox/vector-tile.git" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/duplicate-keys-values.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vector-tile/2a17b719f908e5ce1f81f4846ee9086aa06b4781/test/duplicate-keys-values.mvt -------------------------------------------------------------------------------- /test/test046.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vector-tile/2a17b719f908e5ce1f81f4846ee9086aa06b4781/test/test046.mvt -------------------------------------------------------------------------------- /test/test2048.mvt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/vector-tile/2a17b719f908e5ce1f81f4846ee9086aa06b4781/test/test2048.mvt -------------------------------------------------------------------------------- /test/unit/catch.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /test/unit/tags.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | TEST_CASE( "Version constant" ) { 7 | CHECK(std::string(VECTOR_TILE_VERSION_STRING) == std::string("1.0.4")); 8 | CHECK(VECTOR_TILE_VERSION_CODE == 10004); 9 | } 10 | 11 | TEST_CASE( "Protobuf Tag Constants" ) { 12 | CHECK(mapbox::vector_tile::TileType::LAYERS == 3); 13 | 14 | CHECK(mapbox::vector_tile::LayerType::VERSION == 15); 15 | CHECK(mapbox::vector_tile::LayerType::NAME == 1); 16 | CHECK(mapbox::vector_tile::LayerType::FEATURES == 2); 17 | CHECK(mapbox::vector_tile::LayerType::KEYS == 3); 18 | CHECK(mapbox::vector_tile::LayerType::VALUES == 4); 19 | CHECK(mapbox::vector_tile::LayerType::EXTENT == 5); 20 | 21 | CHECK(mapbox::vector_tile::FeatureType::ID == 1); 22 | CHECK(mapbox::vector_tile::FeatureType::TAGS == 2); 23 | CHECK(mapbox::vector_tile::FeatureType::TYPE == 3); 24 | CHECK(mapbox::vector_tile::FeatureType::GEOMETRY == 4); 25 | CHECK(mapbox::vector_tile::FeatureType::RASTER == 5); 26 | 27 | CHECK(mapbox::vector_tile::ValueType::STRING == 1); 28 | CHECK(mapbox::vector_tile::ValueType::FLOAT == 2); 29 | CHECK(mapbox::vector_tile::ValueType::DOUBLE == 3); 30 | CHECK(mapbox::vector_tile::ValueType::INT == 4); 31 | CHECK(mapbox::vector_tile::ValueType::UINT == 5); 32 | CHECK(mapbox::vector_tile::ValueType::SINT == 6); 33 | CHECK(mapbox::vector_tile::ValueType::BOOL == 7); 34 | 35 | CHECK(mapbox::vector_tile::GeomType::UNKNOWN == 0); 36 | CHECK(mapbox::vector_tile::GeomType::POINT == 1); 37 | CHECK(mapbox::vector_tile::GeomType::LINESTRING == 2); 38 | CHECK(mapbox::vector_tile::GeomType::POLYGON == 3); 39 | } 40 | -------------------------------------------------------------------------------- /test/unit/vector_tile.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | static std::string open_tile(std::string const& path) { 10 | std::ifstream stream(path.c_str(),std::ios_base::in|std::ios_base::binary); 11 | if (!stream.is_open()) 12 | { 13 | throw std::runtime_error("could not open: '" + path + "'"); 14 | } 15 | std::string message(std::istreambuf_iterator(stream.rdbuf()),(std::istreambuf_iterator())); 16 | stream.close(); 17 | return message; 18 | } 19 | 20 | #define ASSERT_KNOWN_FEATURE() \ 21 | auto const layer_names = tile.layerNames(); \ 22 | REQUIRE(layer_names.size() == 1); \ 23 | REQUIRE(layer_names[0] == "layer_name"); \ 24 | auto const layer = tile.getLayer("layer_name"); \ 25 | REQUIRE(layer.featureCount() == 1); \ 26 | REQUIRE(layer.getName() == "layer_name"); \ 27 | auto const feature = mapbox::vector_tile::feature(layer.getFeature(0),layer); \ 28 | auto const& feature_id = feature.getID(); \ 29 | REQUIRE(feature_id.is()); \ 30 | REQUIRE(feature_id.get() == 123ull); \ 31 | auto props = feature.getProperties(); \ 32 | auto itr = props.find("hello"); \ 33 | REQUIRE(itr != props.end()); \ 34 | auto const& val = itr->second; \ 35 | REQUIRE(val.is()); \ 36 | REQUIRE(val.get() == "world"); \ 37 | auto opt_val = feature.getValue("hello"); \ 38 | REQUIRE(opt_val.is()); \ 39 | REQUIRE(opt_val.get() == "world"); \ 40 | mapbox::vector_tile::points_arrays_type geom = feature.getGeometries(1.0); 41 | 42 | std::string stringify_geom(mapbox::vector_tile::points_arrays_type const& geom) { 43 | std::stringstream s; 44 | for (auto const& point_array : geom) { 45 | s << "["; 46 | for (auto const& point : point_array) { 47 | s << point.x << ", " << point.y; 48 | } 49 | s << "]"; 50 | } 51 | return s.str(); 52 | } 53 | 54 | TEST_CASE( "Read Feature-single-point.mvt" ) { 55 | std::string buffer = open_tile("test/mvt-fixtures/fixtures/valid/Feature-single-point.mvt"); 56 | mapbox::vector_tile::buffer tile(buffer); 57 | ASSERT_KNOWN_FEATURE() 58 | REQUIRE(feature.getType() == mapbox::vector_tile::GeomType::POINT); \ 59 | REQUIRE(stringify_geom(geom) == "[25, 17]"); 60 | } 61 | 62 | TEST_CASE( "Read Feature-single-multipoint.mvt" ) { 63 | std::string buffer = open_tile("test/mvt-fixtures/fixtures/valid/Feature-single-multipoint.mvt"); 64 | mapbox::vector_tile::buffer tile(buffer); 65 | ASSERT_KNOWN_FEATURE() 66 | REQUIRE(feature.getType() == mapbox::vector_tile::GeomType::POINT); \ 67 | REQUIRE(stringify_geom(geom) == "[5, 7][3, 2]"); 68 | } 69 | 70 | TEST_CASE( "Read Feature-single-linestring.mvt" ) { 71 | std::string buffer = open_tile("test/mvt-fixtures/fixtures/valid/Feature-single-linestring.mvt"); 72 | mapbox::vector_tile::buffer tile(buffer); 73 | ASSERT_KNOWN_FEATURE() 74 | REQUIRE(feature.getType() == mapbox::vector_tile::GeomType::LINESTRING); \ 75 | REQUIRE(stringify_geom(geom) == "[2, 22, 1010, 10]"); 76 | } 77 | 78 | TEST_CASE( "Read Feature-single-multilinestring.mvt" ) { 79 | std::string buffer = open_tile("test/mvt-fixtures/fixtures/valid/Feature-single-multilinestring.mvt"); 80 | mapbox::vector_tile::buffer tile(buffer); 81 | ASSERT_KNOWN_FEATURE() 82 | REQUIRE(feature.getType() == mapbox::vector_tile::GeomType::LINESTRING); \ 83 | REQUIRE(stringify_geom(geom) == "[2, 22, 1010, 10][1, 13, 5]"); 84 | } 85 | 86 | TEST_CASE( "Read Feature-single-polygon.mvt" ) { 87 | std::string buffer = open_tile("test/mvt-fixtures/fixtures/valid/Feature-single-polygon.mvt"); 88 | mapbox::vector_tile::buffer tile(buffer); 89 | ASSERT_KNOWN_FEATURE() 90 | REQUIRE(feature.getType() == mapbox::vector_tile::GeomType::POLYGON); \ 91 | REQUIRE(stringify_geom(geom) == "[3, 68, 1220, 343, 6]"); 92 | } 93 | 94 | /*TEST_CASE( "Read Feature-single-multipolygon.mvt" ) { 95 | std::string buffer = open_tile("test/mvt-fixtures/fixtures/valid/Feature-single-multipolygon.mvt"); 96 | mapbox::vector_tile::buffer tile(buffer); 97 | ASSERT_KNOWN_FEATURE() 98 | REQUIRE(feature.getType() == mapbox::vector_tile::GeomType::POINT); \ 99 | REQUIRE(stringify_geom(geom) == "25, 17"); 100 | }*/ 101 | 102 | TEST_CASE( "Prevent massive over allocation" ) { 103 | std::string buffer = open_tile("test/test046.mvt"); 104 | mapbox::vector_tile::buffer tile(buffer); 105 | auto const layer_names = tile.layerNames(); 106 | REQUIRE(layer_names.size() == 1); 107 | REQUIRE(layer_names[0] == "0000000000"); 108 | auto const layer = tile.getLayer("0000000000"); 109 | REQUIRE(layer.featureCount() == 1); 110 | REQUIRE(layer.getName() == "0000000000"); 111 | auto const feature = mapbox::vector_tile::feature(layer.getFeature(0),layer); 112 | mapbox::vector_tile::points_arrays_type geom = feature.getGeometries(1.0); 113 | REQUIRE(geom.capacity() <= 655360); 114 | } 115 | 116 | TEST_CASE( "Prevent underflow in case of LineTo with 0 command count" ) { 117 | std::string buffer = open_tile("test/test2048.mvt"); 118 | mapbox::vector_tile::buffer tile(buffer); 119 | auto const layer_names = tile.layerNames(); 120 | REQUIRE(layer_names.size() == 1); 121 | REQUIRE(layer_names[0] == "roads"); 122 | auto const layer = tile.getLayer("roads"); 123 | REQUIRE(layer.featureCount() == 243); 124 | for (std::size_t i = 0; i < layer.featureCount(); ++i) { 125 | auto const feature = mapbox::vector_tile::feature(layer.getFeature(i), layer); 126 | REQUIRE(feature.getType() == mapbox::vector_tile::GeomType::LINESTRING); 127 | auto geom = feature.getGeometries(1.0); 128 | REQUIRE(!geom.empty()); 129 | } 130 | } 131 | 132 | TEST_CASE( "Allow multiple keys mapping to different tag ids" ) { 133 | // duplicate key 'hello' and duplicate value 'world' 134 | std::string buffer = open_tile("test/duplicate-keys-values.mvt"); 135 | mapbox::vector_tile::buffer tile(buffer); 136 | auto const layer_names = tile.layerNames(); 137 | REQUIRE(layer_names.size() == 1); 138 | REQUIRE(layer_names[0] == "duplicates"); 139 | auto const layer = tile.getLayer("duplicates"); 140 | REQUIRE(layer.featureCount() == 1); 141 | auto const feature = mapbox::vector_tile::feature(layer.getFeature(0), layer); 142 | REQUIRE(feature.getType() == mapbox::vector_tile::GeomType::POLYGON); 143 | 144 | std::string error; 145 | auto const val = feature.getValue("hello", &error); 146 | REQUIRE(!error.empty()); 147 | REQUIRE(error == "duplicate keys with different tag ids are found"); 148 | REQUIRE(val.is()); 149 | REQUIRE(val.get() == "world"); 150 | error.clear(); 151 | REQUIRE(error.empty()); 152 | auto const val1 = feature.getValue("unique", &error); 153 | REQUIRE(error.empty()); 154 | REQUIRE(val1.is()); 155 | REQUIRE(val1.get() == "single_value"); 156 | } --------------------------------------------------------------------------------