Squashed 'third_party/vtzero/' content from commit 2b43c8f5c

git-subtree-dir: third_party/vtzero
git-subtree-split: 2b43c8f5c7cc461a874a657673dfc15f8fb21d1d
This commit is contained in:
Michael Krasnyk 2018-04-19 22:03:52 +03:00
commit 6bee8866de
75 changed files with 28148 additions and 0 deletions

48
.clang-tidy Normal file
View File

@ -0,0 +1,48 @@
---
Checks: '*,-cert-dcl21-cpp,-cert-err60-cpp,-clang-analyzer-core.CallAndMessage,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-fuchsia-*,-google-runtime-references,-hicpp-no-array-decay,-readability-avoid-const-params-in-decls,-readability-implicit-bool-cast,-readability-implicit-bool-conversion'
#
# Disabled checks:
#
# cert-dcl21-cpp
# It is unclear whether this is still a good recommendation in modern C++.
#
# cert-err60-cpp
# Reports std::runtime_error as broken which we can't do anything about.
#
# clang-analyzer-core.CallAndMessage
# Produces false positives
#
# cppcoreguidelines-owning-memory
# Don't want to add dependency on gsl library.
#
# cppcoreguidelines-pro-bounds-array-to-pointer-decay
# Limited use and many false positives including for all asserts.
#
# cppcoreguidelines-pro-bounds-pointer-arithmetic
# This is a low-level library, it needs to do pointer arithmetic.
#
# fuchsia-*
# Much too strict.
#
# google-runtime-references
# This is just a matter of preference, and we can't change the interfaces
# now anyways.
#
# hicpp-no-array-decay
# Limited use and many false positives including for all asserts.
#
# readability-avoid-const-params-in-decls
# Inconsistently complaines about some cases but not others. It is nice
# to have const in parameters if we don't change them just like with any
# other variables.
#
# readability-implicit-bool-cast
# Old name for readability-implicit-bool-conversion.
#
# readability-implicit-bool-conversion
# I don't think this makes the code more readable.
#
WarningsAsErrors: '*'
HeaderFilterRegex: '\/include\/'
AnalyzeTemporaryDtors: false
...

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "test/mvt-fixtures"]
path = test/mvt-fixtures
url = https://github.com/mapbox/mvt-fixtures.git

178
.travis.yml Normal file
View File

@ -0,0 +1,178 @@
#-----------------------------------------------------------------------------
#
# Configuration for continuous integration service at travis-ci.org
#
#-----------------------------------------------------------------------------
language: generic
sudo: false
dist: trusty
#-----------------------------------------------------------------------------
# Save common build configurations as shortcuts, so we can reference them later.
addons_shortcuts:
addons_clang35: &clang35
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-3.5' ]
packages: [ 'libboost1.55-dev', 'clang-3.5' ]
addons_clang38: &clang38
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-3.8' ]
packages: [ 'libboost1.55-dev', 'clang-3.8' ]
addons_clang39: &clang39
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-3.9' ]
packages: [ 'libboost1.55-dev', 'clang-3.9' ]
addons_clang40: &clang40
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0' ]
packages: [ 'libboost1.55-dev', 'clang-4.0' ]
addons_clang50: &clang50
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0' ]
packages: [ 'libboost1.55-dev', 'clang-5.0', 'clang-tidy-5.0' ]
addons_clang60: &clang60
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0' ]
packages: [ 'libboost1.55-dev', 'clang-6.0', 'clang-tidy-6.0' ]
addons_gcc48: &gcc48
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'libboost1.55-dev', 'g++-4.8', 'gcc-4.8' ]
addons_gcc49: &gcc49
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'libboost1.55-dev', 'g++-4.9', 'gcc-4.9' ]
addons_gcc5: &gcc5
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'libboost1.55-dev', 'g++-5', 'gcc-5' ]
addons_gcc6: &gcc6
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'libboost1.55-dev', 'g++-6', 'gcc-6' ]
#-----------------------------------------------------------------------------
matrix:
include:
- os: linux
compiler: "clang-3.5"
env: BUILD='Debug' CC=clang-3.5 CXX=clang++-3.5
addons: *clang35
- os: linux
compiler: "clang-3.8"
env: BUILD='Debug' CC=clang-3.8 CXX=clang++-3.8
addons: *clang38
- os: linux
compiler: "clang-3.9"
env: BUILD='Debug' CC=clang-3.9 CXX=clang++-3.9
addons: *clang39
- os: linux
compiler: "clang-4.0"
env: BUILD='Debug' CC=clang-4.0 CXX=clang++-4.0
addons: *clang40
- os: linux
compiler: "clang-5.0"
env: BUILD='Debug' CC=clang-5.0 CXX=clang++-5.0
CLANG_TIDY=clang-tidy-5.0
addons: *clang50
- os: linux
compiler: "clang-5.0"
env: BUILD='Release' CC=clang-5.0 CXX=clang++-5.0
addons: *clang50
- os: linux
compiler: "clang-5.0"
env: BUILD='Debug' CC=clang-5.0 CXX=clang++-5.0
CXXFLAGS="-fsanitize=address,undefined,integer -fno-sanitize-recover=all -fno-omit-frame-pointer"
LDFLAGS="-fsanitize=address,undefined,integer"
# LSAN doesn't work on container-based system
sudo: required
addons: *clang50
- os: linux
compiler: "clang-6.0"
env: BUILD='Debug' CC=clang-6.0 CXX=clang++-6.0
addons: *clang60
- os: linux
compiler: "gcc-4.8"
env: BUILD='Debug' CC=gcc-4.8 CXX=g++-4.8
addons: *gcc48
- os: linux
compiler: "gcc-4.9"
env: BUILD='Debug' CC=gcc-4.9 CXX=g++-4.9
COVERAGE=gcov-4.9
CXXFLAGS="--coverage" LDFLAGS="--coverage"
addons: *gcc49
- os: linux
compiler: "gcc-5"
env: BUILD='Debug' CC=gcc-5 CXX=g++-5
CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0"
addons: *gcc5
- os: linux
compiler: "gcc-5"
env: BUILD='Debug' CC=gcc-5 CXX=g++-5
CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=1"
addons: *gcc5
- os: linux
compiler: "gcc-6"
env: BUILD='Debug' CC=gcc-6 CXX=g++-6
addons: *gcc6
- os: linux
compiler: "gcc-6"
env: BUILD='Debug' CC=gcc-6 CXX=g++-6
PROTOZERO_DATA_VIEW=std::experimental::string_view
addons: *gcc6
- os: linux
compiler: "gcc-6"
env: BUILD='Release' CC=gcc-6 CXX=g++-6
addons: *gcc6
- os: osx
osx_image: xcode6.4
compiler: clang
env: BUILD='Debug'
- os: osx
osx_image: xcode7.3
compiler: clang
env: BUILD='Debug'
- os: osx
osx_image: xcode8.3
compiler: clang
env: BUILD='Debug'
- os: osx
osx_image: xcode9.1
compiler: clang
env: BUILD='Debug'
- os: osx
osx_image: xcode9.1
compiler: clang
env: BUILD='Release'
#-----------------------------------------------------------------------------
install:
- git submodule update --init
- (cd ..; git clone --depth=1 https://github.com/mapbox/protozero)
script:
- mkdir build
- cd build
- cmake .. -LA -DCMAKE_BUILD_TYPE=${BUILD} -DPROTOZERO_DATA_VIEW=$PROTOZERO_DATA_VIEW -DCLANG_TIDY=$(which ${CLANG_TIDY})
- make VERBOSE=1
- ctest --output-on-failure
- if [ -n "${CLANG_TIDY}" ]; then make clang-tidy; fi
- |
if [ -n "${COVERAGE}" ]; then
which ${COVERAGE}
curl -S -f https://codecov.io/bash -o codecov
chmod +x codecov
${COVERAGE} -p $(find test -name 'test_*.o')
./codecov -Z -c -X gcov -F unit_tests
${COVERAGE} -p $(find test -name 'fixture_tests.cpp.o')
./codecov -Z -c -X gcov -F fixture_tests
fi
#-----------------------------------------------------------------------------

50
.ycm_extra_conf.py Normal file
View File

@ -0,0 +1,50 @@
#-----------------------------------------------------------------------------
#
# Configuration for YouCompleteMe Vim plugin
#
# http://valloric.github.io/YouCompleteMe/
#
#-----------------------------------------------------------------------------
from os.path import realpath, dirname
basedir = dirname(realpath(__file__))
# some default flags
# for more information install clang-3.2-doc package and
# check UsersManual.html
flags = [
'-Werror',
'-Wall',
'-Wextra',
'-pedantic',
'-Wno-return-type',
'-Wno-unused-parameter',
'-Wno-unused-variable',
'-std=c++11',
# '-x' and 'c++' also required
# use 'c' for C projects
'-x',
'c++',
# workaround for https://github.com/Valloric/YouCompleteMe/issues/303
# also see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=800618
'-isystem',
'/usr/lib/ycmd/clang_includes/',
'-I%s/../protozero/include' % basedir,
'-I%s/include' % basedir,
'-I%s/test/include' % basedir,
'-I%s/test/catch' % basedir,
]
# youcompleteme is calling this function to get flags
# You can also set database for flags. Check: JSONCompilationDatabase.html in
# clang-3.2-doc package
def FlagsForFile( filename ):
return {
'flags': flags,
'do_cache': True
}

44
CHANGELOG.md Normal file
View File

@ -0,0 +1,44 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/)
This project adheres to [Semantic Versioning](https://semver.org/).
## [unreleased] -
### Added
### Changed
### Fixed
## [1.0.1] - 2018-04-12
### Added
* Some documentation and tests.
### Changed
* Catch exceptions in vtzero-streets example and output error message.
* Adds a template parameter to the `create_property_map` function allowing
mapping between value types.
### Fixed
* The indexes returned by `feature::next_property_indexes()` are now
checked against the size of the key/value tables in the layer. If
an index is too large a `vtzero::out_of_range_exception` is returned.
This way the user code doesn't have to check this. The function
`feature::for_each_property()` now also uses these checks.
## [1.0.0] - 2018-03-09
First release
[unreleased]: https://github.com/osmcode/libosmium/compare/v1.1.0...HEAD

157
CMakeLists.txt Normal file
View File

@ -0,0 +1,157 @@
#-----------------------------------------------------------------------------
#
# CMake config
#
# vtzero
#
#-----------------------------------------------------------------------------
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
#-----------------------------------------------------------------------------
project(vtzero)
set(VTZERO_VERSION_MAJOR 1)
set(VTZERO_VERSION_MINOR 0)
set(VTZERO_VERSION_PATCH 1)
set(VTZERO_VERSION
"${VTZERO_VERSION_MAJOR}.${VTZERO_VERSION_MINOR}.${VTZERO_VERSION_PATCH}")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
#-----------------------------------------------------------------------------
# This variable must be set to the directory where the mvt-fixtures from the
# https://github.com/mapbox/mvt-fixtures repository are to be found. Usually
# this is the directory where the submodule is checked out as described in
# the README, but you can also set this to a different path, for instance
# to change the setting while doing development.
set(MVT_FIXTURES "${CMAKE_SOURCE_DIR}/test/mvt-fixtures" CACHE PATH "mvt-fixtures directory for tests")
#-----------------------------------------------------------------------------
option(WERROR "Add -Werror flag to build (turns warnings into errors)" ON)
if(MSVC)
add_definitions(-std=c++11 /W3)
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS)
else()
add_definitions(-std=c++11 -Wall -Wextra -pedantic -Wsign-compare -Wconversion)
# add_definitions(-Weverything -Wno-c++98-compat -Wno-documentation -Wno-switch-enum -Wno-weak-vtables -Wno-padded -Wno-documentation-unknown-command -Wno-exit-time-destructors)
if(WERROR)
add_definitions(-Werror)
endif()
endif()
include_directories("${CMAKE_SOURCE_DIR}/include")
set(PROTOZERO_DATA_VIEW "" CACHE STRING "Type used for vtzero::data_view")
if(NOT PROTOZERO_DATA_VIEW STREQUAL "")
add_definitions(-DPROTOZERO_DATA_VIEW=${PROTOZERO_DATA_VIEW})
endif()
#-----------------------------------------------------------------------------
#
# Find dependencies
#
#-----------------------------------------------------------------------------
find_package(Protozero 1.6.0 REQUIRED)
include_directories(SYSTEM ${PROTOZERO_INCLUDE_DIR})
find_package(Boost)
#-----------------------------------------------------------------------------
#
# Optional "clang-tidy" target
#
#-----------------------------------------------------------------------------
message(STATUS "Looking for clang-tidy")
find_program(CLANG_TIDY NAMES clang-tidy clang-tidy-6.0 clang-tidy-5.0)
if(CLANG_TIDY)
message(STATUS "Looking for clang-tidy - found ${CLANG_TIDY}")
add_custom_target(clang-tidy
${CLANG_TIDY}
-p ${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}/examples/*.cpp
${CMAKE_SOURCE_DIR}/test/*.cpp
${CMAKE_SOURCE_DIR}/test/t/*.cpp
)
else()
message(STATUS "Looking for clang-tidy - not found")
message(STATUS " Build target 'clang-tidy' will not be available.")
endif()
#-----------------------------------------------------------------------------
#
# Optional "cppcheck" target
#
#-----------------------------------------------------------------------------
message(STATUS "Looking for cppcheck")
find_program(CPPCHECK NAMES cppcheck)
if(CPPCHECK)
message(STATUS "Looking for cppcheck - found")
add_custom_target(cppcheck
${CPPCHECK}
-Uassert --std=c++11 --enable=all
${CMAKE_SOURCE_DIR}/examples/*.cpp
${CMAKE_SOURCE_DIR}/test/*.cpp
${CMAKE_SOURCE_DIR}/test/t/*.cpp
)
else()
message(STATUS "Looking for cppcheck - not found")
message(STATUS " Build target 'cppcheck' will not be available.")
endif()
#-----------------------------------------------------------------------------
#
# Include what you use
#
#-----------------------------------------------------------------------------
message(STATUS "Looking for iwyu")
find_program(IWYU_TOOL NAMES iwyu_tool)
if(IWYU_TOOL)
message(STATUS "Looking for iwyu - found")
add_custom_target(iwyu
${IWYU_TOOL} -p ${CMAKE_BINARY_DIR}
)
else()
message(STATUS "Looking for iwyu - not found")
message(STATUS " Build target 'iwyu' will not be available.")
endif()
#-----------------------------------------------------------------------------
#
# Installation
#
#-----------------------------------------------------------------------------
install(DIRECTORY include/vtzero DESTINATION include)
#-----------------------------------------------------------------------------
enable_testing()
add_subdirectory(doc)
add_subdirectory(examples)
add_subdirectory(test)
#-----------------------------------------------------------------------------

25
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,25 @@
# Contributing to vtzero
## Releasing
To release a new vtzero version:
- Make sure all tests are passing locally and on travis/appveyor
- Update version number in
- `CMakeLists.txt` (one place)
- `include/vtzero/version.hpp` (two places)
- Update CHANGELOG.md
- Update UPGRADING.md if necessary
- `git commit -m "Release X.Y.Z" include/vtzero/version.hpp CMakeLists.txt CHANGELOG.md UPGRADING.md`
- `git tag vX.Y.Z`
- `git push`
- `git push --tags`
- Go to https://github.com/mapbox/vtzero/releases
and edit the new release. Put "Version x.y.z" in title and
cut-and-paste entry from CHANGELOG.md.
## Updating submodules
Call `git submodule update --recursive --remote` to update to the newest
version of the mvt fixtures used for testing.

28
EXTERNAL_LICENSES.txt Normal file
View File

@ -0,0 +1,28 @@
==== clara.hpp
Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

25
LICENSE Normal file
View File

@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2017, Mapbox
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

93
README.md Normal file
View File

@ -0,0 +1,93 @@
# vtzero
Tiny and fast vector tile decoder and encoder in C++.
Implements the [Mapbox Vector Tile Specification 2.x](https://www.mapbox.com/vector-tiles/specification).
[![Build Status](https://travis-ci.org/mapbox/vtzero.svg?branch=master)](https://travis-ci.org/mapbox/vtzero)
[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/mapbox/vtzero?svg=true)](https://ci.appveyor.com/project/Mapbox/vtzero)
[![Coverage Status](https://codecov.io/gh/mapbox/vtzero/branch/master/graph/badge.svg)](https://codecov.io/gh/mapbox/vtzero)
## Depends
* C++11 compiler (GCC 4.8 or higher, clang 3.5 or higher, ...)
* CMake
* [Protozero](https://github.com/mapbox/protozero) version >= 1.6.0
## Build
First clone `protozero`:
```
git clone git@github.com:mapbox/protozero.git
```
Then clone `vtzero` beside `protozero`. The `vtzero` build system will, among
several places, look for `protozero` at `../protozero`. (If you would like to
use `protozero` from a different path you can set `PROTOZERO_INCLUDE_DIR` in
the CMake configuration step.)
Then, inside the `vtzero` directory do:
```
git submodule update --init
```
Finally, to build the examples and tests do:
```
mkdir build
cd build
cmake ..
make
```
Call `ctest` to run the tests.
## Examples
Several examples are provided to show usage of the library.
Call
examples/vtzero-create
to create test tile named `test.mvt`.
Call
examples/vtzero-show TILE-FILE
to show the contents of `TILE-FILE`.
You can use
examples/vtzero-check TILE-FILE
to check vector tile for validity.
## Docs
Extensive documentation is available:
* [Tutorial](doc/tutorial.md) (start here)
* [Reading vector tiles](doc/reading.md)
* [Writing vector tiles](doc/writing.md)
* [Advanced vtzero topics](doc/advanced.md)
Make sure to read all of it before using vtzero. Vtzero isn't the simplest
library to use, because it always chooses performance over ease of use.
If [Doxygen](http://www.stack.nl/~dimitri/doxygen/) is installed on your
system, the build process will automatically create the API docs for you.
The results will be in your build directory under `doc/html`.
## Authors
Jochen Topf (jochen@topf.org),
Dane Springmeyer (dane@mapbox.com)

61
appveyor.yml Normal file
View File

@ -0,0 +1,61 @@
#-----------------------------------------------------------------------------
#
# Configuration for continuous integration service at appveyor.com
#
#-----------------------------------------------------------------------------
platform: x64
image: Visual Studio 2017
clone_depth: 1
#-----------------------------------------------------------------------------
environment:
matrix:
- config: MSYS2
autocrlf: true
- config: Debug
autocrlf: true
- config: RelWithDebInfo
autocrlf: true
- config: Debug
autocrlf: false
- config: RelWithDebInfo
autocrlf: false
#-----------------------------------------------------------------------------
init:
- git config --global core.autocrlf %autocrlf%
- git config --get core.autocrlf
# The option --ask=20 is a workaround for a problem with the MSYS2 update
# process. Without it the following error is printed and the appveyor script
# halts: "msys2-runtime and catgets are in conflict. Remove catgets?"
# See also: https://github.com/Alexpux/MSYS2-packages/issues/1141
install:
- cd c:\projects
- git clone --depth=1 https://github.com/mapbox/protozero
- if [%config%]==[MSYS2] (
C:\msys64\usr\bin\pacman --noconfirm --sync --refresh --refresh --sysupgrade --sysupgrade --ask=20
&& C:\msys64\usr\bin\pacman -Rc --noconfirm mingw-w64-x86_64-gcc-libs
)
build_script:
- cd c:\projects\vtzero
- git submodule update --init
- if [%config%]==[MSYS2] (
build-msys2.bat
) else (
build-appveyor.bat
)
# remove garbage VS messages
# https://help.appveyor.com/discussions/problems/4569-the-target-_convertpdbfiles-listed-in-a-beforetargets-attribute-at-c-does-not-exist-in-the-project-and-will-be-ignored
before_build:
- del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets"
#-----------------------------------------------------------------------------

58
build-appveyor.bat Normal file
View File

@ -0,0 +1,58 @@
@ECHO OFF
SETLOCAL
SET EL=0
ECHO ~~~~~~ %~f0 ~~~~~~
::show all available env vars
SET
ECHO cmake on AppVeyor
cmake -version
ECHO activating VS cmd prompt && CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
IF %ERRORLEVEL% NEQ 0 GOTO ERROR
IF EXIST build ECHO deleting build dir... && RD /Q /S build
IF %ERRORLEVEL% NEQ 0 GOTO ERROR
MKDIR build
IF %ERRORLEVEL% NEQ 0 GOTO ERROR
CD build
ECHO config^: %config%
::This will produce lots of LNK4099 warnings which can be ignored.
::Unfortunately they can't be disabled, see
::https://stackoverflow.com/questions/661606/visual-c-how-to-disable-specific-linker-warnings
SET CMAKE_CMD=cmake .. ^
-LA -G "Visual Studio 14 Win64"
ECHO calling^: %CMAKE_CMD%
%CMAKE_CMD%
IF %ERRORLEVEL% NEQ 0 GOTO ERROR
SET avlogger=
IF /I "%APPVEYOR%"=="True" SET avlogger=/logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
msbuild vtzero.sln ^
/p:Configuration=%config% ^
/toolsversion:14.0 ^
/p:Platform=x64 ^
/p:PlatformToolset=v140 %avlogger%
IF %ERRORLEVEL% NEQ 0 GOTO ERROR
ctest --output-on-failure ^
-C %config% ^
IF %ERRORLEVEL% NEQ 0 GOTO ERROR
GOTO DONE
:ERROR
ECHO ~~~~~~ ERROR %~f0 ~~~~~~
SET EL=%ERRORLEVEL%
:DONE
IF %EL% NEQ 0 ECHO. && ECHO !!! ERRORLEVEL^: %EL% !!! && ECHO.
ECHO ~~~~~~ DONE %~f0 ~~~~~~
EXIT /b %EL%

18
build-msys2.bat Normal file
View File

@ -0,0 +1,18 @@
echo "Adding MSYS2 to path..."
SET "PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%"
echo %PATH%
echo "Installing MSYS2 packages..."
bash -lc "pacman -S --needed --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-boost"
echo "Generating makefiles"
mkdir build
cd build
cmake .. -LA -G "MSYS Makefiles"
echo "Building"
make VERBOSE=1
echo "Testing"
ctest --output-on-failure

63
cmake/FindProtozero.cmake Normal file
View File

@ -0,0 +1,63 @@
#----------------------------------------------------------------------
#
# FindProtozero.cmake
#
# Find the protozero headers.
#
#----------------------------------------------------------------------
#
# Usage:
#
# Copy this file somewhere into your project directory, where cmake can
# find it. Usually this will be a directory called "cmake" which you can
# add to the CMake module search path with the following line in your
# CMakeLists.txt:
#
# list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
#
# Then add the following in your CMakeLists.txt:
#
# find_package(Protozero [version] [REQUIRED])
# include_directories(SYSTEM ${PROTOZERO_INCLUDE_DIR})
#
# The version number is optional. If it is not set, any version of
# protozero will do.
#
# if(NOT PROTOZERO_FOUND)
# message(WARNING "Protozero not found!\n")
# endif()
#
#----------------------------------------------------------------------
#
# Variables:
#
# PROTOZERO_FOUND - True if Protozero was found.
# PROTOZERO_INCLUDE_DIR - Where to find include files.
#
#----------------------------------------------------------------------
# find include path
find_path(PROTOZERO_INCLUDE_DIR protozero/version.hpp
PATH_SUFFIXES include
PATHS ../protozero
)
# Check version number
if(Protozero_FIND_VERSION)
file(STRINGS "${PROTOZERO_INCLUDE_DIR}/protozero/version.hpp" _version_define REGEX "#define PROTOZERO_VERSION_STRING")
if("${_version_define}" MATCHES "#define PROTOZERO_VERSION_STRING \"([0-9.]+)\"")
set(_version "${CMAKE_MATCH_1}")
else()
set(_version "unknown")
endif()
endif()
#set(PROTOZERO_INCLUDE_DIRS "${PROTOZERO_INCLUDE_DIR}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Protozero
REQUIRED_VARS PROTOZERO_INCLUDE_DIR
VERSION_VAR _version)
#----------------------------------------------------------------------

3
codecov.yml Normal file
View File

@ -0,0 +1,3 @@
ignore:
- "bench"
- "test"

36
doc/CMakeLists.txt Normal file
View File

@ -0,0 +1,36 @@
#-----------------------------------------------------------------------------
#
# CMake Config
#
# vtzero documentation
#
#-----------------------------------------------------------------------------
message(STATUS "Configuring documentation")
message(STATUS "Looking for doxygen")
find_package(Doxygen)
if(DOXYGEN_FOUND)
message(STATUS "Looking for doxygen - found")
configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
file(GLOB HEADER_FILES "${CMAKE_SOURCE_DIR}/include/vtzero/*.hpp")
add_custom_command(OUTPUT html/index.html
COMMAND ${DOXYGEN_EXECUTABLE}
ARGS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
DEPENDS Doxyfile.in advanced.md doc.md
${HEADER_FILES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Generating API documentation with Doxygen" VERBATIM)
add_custom_target(doc ALL
DEPENDS html/index.html)
else()
message(STATUS "Looking for doxygen - not found")
message(STATUS " Disabled making of documentation.")
endif()
#-----------------------------------------------------------------------------
message(STATUS "Configuring documentation - done")
#-----------------------------------------------------------------------------

2353
doc/Doxyfile.in Normal file

File diff suppressed because it is too large Load Diff

114
doc/advanced.md Normal file
View File

@ -0,0 +1,114 @@
# Advanced vtzero topics
## Differences between the protocol buffer specification and the vtzero implementation
The [protobuf specification
says](https://developers.google.com/protocol-buffers/docs/encoding#optional)
that a decoder library must handle repeated *non-packed* fields if repeated
*packed* fields are expected and it must handle multiple repeated packed fields
as if the items are concatenated. Encoders should never encode fields in this
way, though, so it is very unlikely that this would ever happen. For
performance reasons vtzero doesn't handle this case.
## Differences between the vector tile specification and the vtzero implementation
The [vector tile specification](https://github.com/mapbox/vector-tile-spec/blob/master/2.1/README.md#41-layers)
clearly says that you can not have two layers with the same
name in a vector tile. For performance reasons this is neither checked on
reading nor on writing.
## The `create_vtzero_point` customization point
The vtzero builder classes have several functions which take a `vtzero::point`
as argument. But chances are that you are using a different point type in your
code. That's why these functions have overloads taking any type `TPoint` that
can be converted to a `vtzero::point`. This conversion is done by calling the
function `create_vtzero_point()`. Vtzero supplies a version of this function
which will work with any type with members `x` and `y`:
```cpp
template <typename TPoint>
vtzero::point create_vtzero_point(TPoint p) noexcept {
return {p.x, p.y};
}
```
You can define your own overload of that function taking your own point type
as parameter and returning a `vtzero::point`. Vtzero will find your function
using [ADL](http://en.cppreference.com/w/cpp/language/adl) which magically
makes the vtzero builders work with your point type.
## Using the `property_mapper` class when copying layers
Sometimes you want to copy some features of a layer into a new layer. Because
you only copy some features (and/or only some properties of the features), the
key and value tables in the layer have to be rebuilt. This is where the
`property_mapper` class helps you. It keeps the mapping between the index
values of the old and the new table adding property keys and values as needed
to the new table.
Here is some code that shows you how to use it:
```cpp
#include <vtzero/property_mapper.hpp> // you have to include this
vtzero::layer layer = ...; // layer you got from an existing tile
vtzero::layer_builder layer_builder{...}; // create new layer
// instantiate the property mapper with the old and new layers
vtzero::property_mapper mapper{layer, layer_builder};
// you'll probably want to iterate over all features in the old layer...
while (auto feature = layer.next_feature()) {
// ... and decide somehow which ones you want to keep
if (keep_feature(feature)) {
// instantiate a feature builder as usual and copy id and geometry
vtzero::geometry_feature_builder feature_builder{layer_builder};
if (feature.has_id()) {
feature_builder.set_id(feature.id());
}
feature_builder.set_geometry(feature.geometry());
// now iterate over all properties...
while (auto idxs = feature.next_property_indexes()) {
// ... decide which ones to keep,
if (keep_property(idxs)) {
// ... and add them to the new layer using the mapper
feature_builder.add_property(mapper(idxs));
}
}
}
}
```
## Protection against huge memory use
When decoding a vector tile we got from an unknown source, we don't know what
surprises it might contain. Building data structures based on the vector tile
sometimes means we have to allocate memory and in the worst case this might be
quite a lot of memory. Vtzero usually doesn't allocate any memory when decoding
a tile, except when reading properties, when there is space for lookup tables
allocated. The memory use for these lookup tables is `sizeof(data_view)` times
the number of entries in the key/value table. In the worst case, when a vector
tile basically only contains such a table, memory use is proportional to the
size of the vector tile. But memory use can be an order of magnitude larger
than the tile size! If you are concerned about memory use, limit the size
of the vector tiles you give to vtzero.
When reading geometries from vector tiles, vtzero doesn't need much memory
itself, but the users of vtzero might. In a typical case you might reserve
enough memory to store, say, a linestring, and then fill that memory. To allow
you to do this, vtzero tells you about the number of points in the linestring.
This number comes from the tile and it might be rather large. Vtzero does a
consistency check comparing the number of points the geometry says it has with
the number of bytes used for the geometry and it will throw an exception if the
numbers can't fit. So you are protected against tiny tiles pretending to
contain a huge geometry. But there still could be a medium-sized tile which
gets "blown up" into a huge memory hog. Your representation of a linestring
can be an order of magnitude larger than the minimum 2 bytes per point
needed in the encoded tile.
So again: If you are concerned about memory use, limit the size of the vector
tiles you give to vtzero.

13
doc/doc.md Normal file
View File

@ -0,0 +1,13 @@
Tiny and fast vector tile decoder and encoder in C++.
This is the API documentation that was automatically created from the
source code. For more information about vtzero go to
https://github.com/mapbox/vtzero .
Vtzero is a header-only library. You do not need to compile and link it,
just include the headers you need.
Everything in namespaces called "detail" is for internal vtzero use only,
do not depend on it in your code.

459
doc/reading.md Normal file
View File

@ -0,0 +1,459 @@
# Reading vector tiles
To access the contents of vector tiles with vtzero create a `vector_tile`
object first with the data of the tile as first argument:
```cpp
#include <vtzero/vector_tile.hpp> // always needed when reading vector tiles
std::string vt_data = ...;
vtzero::vector_tile tile{vt_data};
```
Instead of a string, you can also initialize the `vector_tile` using a
`vtzero::data_view`. This class contains only a pointer and size referencing
some data similar to the C++17 `std::string_view` class. It is typedef'd from
the `protozero::data_view`. See [the protozero
doc](https://github.com/mapbox/protozero/blob/master/doc/advanced.md#protozero_use_view)
for more details.
```cpp
vtzero::data_view vt_data = ...;
vtzero::vector_tile tile{vt_data};
```
In both cases the `vector_tile` object contains references to the original
tile data. You have to make sure this data stays available through the whole
lifetime of the `vector_tile` object and all the other objects we'll create
in this tutorial for accessing parts of the vector tile. The data is **not**
copied by vtzero when accessing vector tiles.
You can think of the `vector_tile` class as a "proxy" class giving you access
to the decoded data, similarly the classes `layer`, `feature`, and
`property` described in the next chapters are "proxy" classes, too.
## Accessing layers
Vector tiles consist of a list of layers. The list of layers can be empty
in which case `tile.empty()` will return true.
The simplest and fasted way to access the layers is through the `next_layer()`
function:
```cpp
while (auto layer = tile.next_layer()) {
...
}
```
Note that this creates new layer objects on the fly referencing the layer you
are currently looking at. Once you have iterated over all the layers,
`next_layer()` will return the "invalid" (default constructed) layer object
which converts to false in an boolean context.
You can reset the layer iterator to the beginning again if you need to go
over the layers again:
```cpp
tile.reset_layer();
```
Instead of using this external iterator, you can use a different function with
an internal iterator that calls a function defined by you for each layer. Your
function must take a `layer&&` as parameter and return `true` if the iteration
should continue and `false` otherwise:
```cpp
tile.for_each_layer([&](layer&& l) {
// do something with layer
return true;
});
```
Both the external and internal iteration do basically the same and have the
same performance characteristics.
You can also access layers through their index or name:
```cpp
tile.get_layer(3);
```
will give you the 4th layer in the tile. With
```cpp
tile.get_layer_by_name("foobar");
```
you'll get the layer with the specified name. Both will return the invalid
layer if that layer doesn't exist.
Note that accessing layers by index or name is less efficient than iterating
over them using `next_layer()` if you are accessing several layers. So usually
you should only use those function if you want to access one specific layer
only.
If you need the number of layers, you can call `tile.count_layers()`. This
function still has to iterate over the layers internally decoding some of the
data, so it is not cheap.
## The layer
Once you have a layer as described in the previous chapter you can access the
metadata of this layer easily:
* The version is available with `layer.version()`. Only version 1 and 2 are
currently supported by this library.
* The extent of the tile is available through `layer.extent()`. This is usually
4096.
* The function `layer.name()` returns the name of the layer as `data_view`.
This does **not** include a final 0-byte!
* The number of features is returned by the `layer.num_features()` function.
If it doesn't contain any features `layer.empty()` will return true.
(Different then the `vector_tile::count_layers()`, the `layer::num_features()`
function is `O(1)`).
To access the features call the `next_feature()` function until it returns
the invalid (default constructed) feature:
```cpp
while (auto feature = layer.next_feature()) {
...
}
```
Use `reset_feature()` to restart the feature iterator from the beginning.
Instead of using this external iterator, you can use a different function with
an internal iterator that calls a function defined by you for each feature.
Your function must take a `feature&&` as parameter and return `true` if the
iteration should continue and `false` otherwise:
```cpp
layer.for_each_feature([&](feature&& f) {
// do something with the feature
return true;
});
```
Both the external and internal iteration do basically the same and have the
same performance characteristics.
If you know the ID of a feature you can get the feature using
`get_feature_by_id()`, but note that this will do a linear search through
all the features in the layer, decoding each one until it finds the right ID.
This is almost always **not** what you want.
Note that the feature returned by `next_feature()` or `get_feature_by_id()`
will internally contain a pointer to the layer it came from. The layer has to
stay valid as long as the feature is used.
## The feature
You get features from the layer as described in the previous chapter. The
`feature` class gives you access to the ID, the geometry and the properties
of the feature. Access the ID using the `id()` method which will return 0
if no ID is set. You can ask for the existence of the ID using `has_id()`:
```cpp
auto feature = layer...;
if (feature.has_id()) {
cout << feature.id() << '\n';
}
```
The `geometry()` method returns an object of the `geometry` class. It contains
the geometry type and a reference to the (un-decoded) geometry data. See a
later chapter on the details of decoding this geometry. You can also directly
add this geometry to a new feature you are writing.
The number of properties in the feature is returned by the
`feature::num_properties()` function. If the feature doesn't contain any
properties `feature.empty()` will return true. (Different then the
`vector_tile::count_layers()`, the `feature::num_properties()` function is
`O(1)`).
To access the properties call the `next_property()` function until it returns
the invalid (default constructed) property:
```cpp
while (auto property = feature.next_property()) {
...
}
```
Use `reset_property()` to restart the property iterator from the beginning.
Instead of using this external iterator, you can use a different function with
an internal iterator that calls a function defined by you for each property.
Your function must take a `property&&` as parameter and return `true` if the
iteration should continue and `false` otherwise:
```cpp
feature.for_each_property([&](property&& p) {
...
return true;
});
```
Both the external and internal iteration do basically the same and have the
same performance characteristics.
## The property
Each property you get from the feature is an object of the `property` class. It
contains a view of the property key and value. The key is always a string
encoded in a `vtzero::data_view`, the value can be of different types but is
always encapsulated in a `property_value` type, a variant type that can be
converted into whatever type the value really has.
```cpp
auto property = ...;
std::string pkey = property.key(); // returns a vtzero::data_view which can
// be converted to std::string
property_value pvalue = property.value();
```
To get the type of the property value, call `type()`:
```cpp
const auto type = pvalue.type();
```
If the property value is an int, for instance, you can get it like this:
```cpp
if (pvalue.type() == property_value_type::int_value)
int64_t v = pvalue.int_value();
}
```
Instead of accessing the values this way, you'll often use the visitor
interface. Here is an example where the `print_visitor` struct is used to print
out the values. In this case one overload is used for all primitive types
(`double`, `float`, `int`, `uint`, `bool`), one overload is used for the `string_value`
type which is encoded in a `data_view`. You must make sure your visitor handles
all those types.
```cpp
struct print_visitor {
template <typename T>
void operator()(T value) {
std::cout << value;
}
void operator()(vtzero::data_view value) {
std::cout << std::string(value);
}
};
vtzero::apply_visitor(print_visitor{}, pvalue));
```
All call operators of your visitor class have to return the same type. In the
case above this was `void`, but it can be something else. That return type
will be the return type of the `apply_visitor` function. This can be used,
for instance, to convert the values into one type:
```cpp
struct to_string_visitor {
template <typename T>
std::string operator()(T value) {
reutrn std::to_string(value);
}
std::string operator()(vtzero::data_view value) {
return std::string(value);
}
};
std::string v = vtzero::apply_visitor(to_string_visitor{}, pvalue);
```
Sometimes you want to convert the `property_value` type into your own variant
type. You can use the `vtzero::convert_property_value()` free function for
this.
Lets say you are using `boost` and this is your variant:
```cpp
using variant_type = boost::variant<std::string, float, double, int64_t, uint64_t, bool>;
```
You can then use the following line to convert the data:
```cpp
variant_type v = vtzero::convert_property_value<variant_type>(pvalue);
```
Your variant type must be constructible from all the types `std::string`,
`float`, `double`, `int64_t`, `uint64_t`, and `bool`. If it is not, you can
define a mapping between those types and the types you use in your variant
class.
```cpp
using variant_type = boost::variant<mystring, double, int64_t, uint64_t, bool>;
struct mapping : vtzero::property_value_mapping {
using string_type = mystring; // use your own string type which must be
// convertible from data_view
using float_type = double; // no float in variant, so convert to double
};
variant_type v = vtzero::convert_property_value<variant_type, mapping>(pvalue);
```
## Creating a properties map
This linear access to the properties with lazy decoding of each property only
when it is accessed saves memory allocations, especially if you are only
interested in very few properties. But sometimes it is easier to create a
mapping (based on `std::unordered_map` for instance) between keys and values. This is where
the `vtzero::create_properties_map()` templated free function comes in. It
needs the map type as template parameter:
```cpp
using key_type = std::string; // must be something that can be converted from data_view
using value_type = boost::variant<std::string, float, double, int64_t, uint64_t, bool>;
using map_type = std::map<key_type, value_type>;
auto feature = ...;
auto map = create_properties_map<map_type>(feature);
```
Both `std::map` and `std::unordered_map` are supported as map type, but this
should also work with any other map type that has an `emplace()` method.
## Geometries
Features must contain a geometry of type UNKNOWN, POINT, LINESTRING, or
POLYGON. The UNKNOWN type is not further specified by the vector tile spec,
this library doesn't allow you to do anything with this type. Note that
multipoint, multilinestring, and multipolygon geometries are also possible,
they don't have special types.
You can get the geometry type with `feature.geometry_type()`, but usually
you'll get the geometry with `feature.geometry()`. This will return an object
of type `vtzero::geometry` which contains the geometry type and a view of
the raw geometry data. To decode the data you have to call one of the decoder
free functions `decode_geometry()`, `decode_point_geometry()`,
`decode_linestring_geometry()`, or `decode_polygon_geometry()`. The first of
these functions can decode any point, linestring, or polygon geometry. The
others must be called with a geometry of the specified type and will only
decode that type.
For all the decoder functions the first parameter is the geometry (as returned
by `feature.geometry()`), the second parameter is a *handler* object that you
must implement. The decoder function will call certain callbacks on this object
that give you part of the geometry data which allows you to use this data in
any way you like.
The handler for `decode_point_geometry()` must implement the following
functions:
* `void points_begin(uint32_t count)`: This is called once at the beginning
with the number of points. For a point geometry, this will be 1, for
multipoint geometries this will be larger.
* `void points_point(vtzero::point point)`: This is called once for each
point.
* `void points_end()`: This is called once at the end.
The handler for `decode_linestring_geometry()` must implement the following
functions:
* `void linestring_begin(uint32_t count)`: This is called at the beginning
of each linestring with the number of points in this linestring. For a simple
linestring this function will only be called once, for a multilinestring
it will be called several times.
* `void linestring_point(vtzero::point point)`: This is called once for each
point.
* `void linestring_end()`: This is called at the end of each linestring.
The handler for `decode_polygon_geometry` must implement the following
functions:
* `void ring_begin(uint32_t count)`: This is called at the beginning
of each ring with the number of points in this ring. For a simple polygon
with only one outer ring, this function will only be called once, if there
are inner rings or if this is a multipolygon, it will be called several
times.
* `void ring_point(vtzero::point point)`: This is called once for each
point.
* `void ring_end(vtzero::ring_type)`: This is called at the end of each ring.
The parameter tells you whether the ring is an outer or inner ring or whether
the ring was invalid (if the area is 0).
The handler for `decode_geometry()` must implement all of the functions
mentioned above for the different types. It is guaranteed that only one
set of functions will be called depending on the geometry type.
If your handler implements the `result()` method, the decode functions will
have the return type of the `result()` method and will return whatever
result returns. If the `result()` method is not available, the decode functions
return void.
Here is a typical implementation of a linestring handler:
```cpp
struct linestring_handler {
using linestring = std::vector<my_point_type>;
linestring points;
void linestring_begin(uint32_t count) {
points.reserve(count);
}
void linestring_point(vtzero::point point) noexcept {
points.push_back(convert_to_my_point(point));
}
void linestring_end() const noexcept {
}
linestring result() {
return std::move(points);
}
};
```
Note that the `count` given to the `linestring_begin()` method is used here to
reserve memory. This is potentially problematic if the count is large. Please
keep this in mind.
## Accessing the key/value lookup tables in a layer
Vector tile layers contain two tables with all the property keys and all
property values used in the features in that layer. Vtzero usually handles
those table lookups internally without you noticing. But sometimes it might
be necessary to access this data directly.
From the layer object you can get references to the tables:
```cpp
vtzero::layer layer = ...;
const auto& kt = layer.key_table();
const auto& vt = layer.value_table();
```
Instead you can also lookup keys and values using methods on the layer object:
```cpp
vtzero::layer layer = ...;
const vtzero::data_view k = layer.key(17);
const vtzero::property_value_view v = layer.value(42);
```
As usual in vtzero you only get views back, so you need to keep the layer
object around as long as you are accessing the results of those methods.
Note that the lookup tables are created on first access from the layer data. As
long as you are not accessing those tables directly or by looking up any
properties in a feature, the tables are not created and no extra memory is
used.

94
doc/tutorial.md Normal file
View File

@ -0,0 +1,94 @@
# Vtzero Tutorial
The vtzero header-only library is used to read and write vector tile data
as specified in the [Mapbox Vector Tile
Specification](https://github.com/mapbox/vector-tile-spec). This document
assumes that you are familiar with that specification.
## Overview
The library has basically two parts: The part concerned with decoding existing
vector tiles and the part concerned with creating new vector tiles. You can
use either one without knowing much about the other side, but it is, of course
also possible to read parts of a vector tile and stick it into a new one.
Vtzero is trying to do as little work as possible while still giving you a
reasonably easy to use interface. This means that it will, as much as feasible,
decode the different parts of a vector tile only when you ask for them. Most
importantly it will try to avoid memory allocation and it will not copy data
around unnecessarily but work with references instead.
On the writing side it means that you have to call the API in a specific order
when adding more data to a vector tile. This allows vtzero to avoid multiple
copies of the data.
## Basic types
Vtzero contains several basic small (value) types such as `GeomType`,
`property_value_type`, `index_value`, `index_value_pair`, and `point` which
hold basic values in a type-safe way. Most of them are defined in `types.hpp`
(`point` is in `geometry.hpp`).
Sometimes it is useful to be able to print the values of those types, for
instance when debugging. For this overloads of `operator<<` on `basic_ostream`
are available in `vtzero/output.hpp`. Include this file and you can use the
usual `std::cout << some_value;` to print those values.
## Use of asserts and exceptions
The vtzero library uses a lot of asserts to help you use it correctly. It is
recommended you use debug builds while developing your code, because you'll
get asserts from vtzero telling you when you use it in a wrong way. This is
especially important when writing tiles using the builder classes, because
their methods have to be called in a certain order that might not always be
obvious but is checked by the asserts.
Exceptions, on the other hand, are used when errors occur during the normal run
of vtzero. This is especially important on the reading side when vtzero makes
every effort to handle any kind of input, even if the input data is corrupt
in some way. (Vtzero can't detect all problems though, your code still has to
do its own checking, see the [advanced topics](advanced.md) for some more
information.)
Many vtzero functions can throw exceptions. Most of them fall into these
categories:
* If the underlying protocol buffers data has some kind of problem, you'll
get an exception from the [protozero
library](https://github.com/mapbox/protozero/blob/master/doc/tutorial.md#asserts-and-exceptions-in-the-protozero-library).
They are all derived from `protozero::exception`.
* If the protocol buffers data is okay, but the vector tile data is invalid
in some way, you'll get an exception from the vtzero library.
* If any memory allocation failed, you'll get a `std::bad_alloc` exception.
All the exceptions thrown directly by the vtzero library are derived from
`vtzero::exception`. These exceptions are:
* A `format_exception` is thrown when vector tile encoding isn't valid
according to the vector tile specification.
* A `geometry_exception` is thrown when a geometry encoding isn't valid
according to the vector tile specification.
* A `type_exception` is thrown when a property value is accessed using the
wrong type.
* A `version_exception` is thrown when an unknown version number is found in
the layer. Currently vtzero only supports version 1 and 2.
* An `out_of_range_exception` is thrown when an index into the key or value
table in a layer is out of range. This can only happen if the tile data is
invalid.
## Include files
Usually you only directly include the following files:
* When reading: `<vtzero/vector_tile.hpp>`
* When writing: `<vtzero/builder.hpp>`
* If you need any of the special indexes: `<vtzero/index.hpp>`
* If you want overloads of `operator<<` for basic types: `<vtzero/output.hpp>`
* If you need the version: `<vtzero/version.hpp>`
## Reading and writing vector tiles
* [Reading vector tiles](reading.md)
* [Writing vector tiles](writing.md)

539
doc/writing.md Normal file
View File

@ -0,0 +1,539 @@
# Writing vector tiles
Writing vector tiles start with creating a `tile_builder`. This builder will
then be used to add layers and features in those layers. Once all this is done,
you call `serialize()` to actually build the vector tile from the data you
provided to the builders:
```cpp
#include <vtzero/builder.hpp> // always needed when writing vector tiles
vtzero::tile_builder tbuilder;
// add lots of data to builder...
std::string buffer = tbuilder.serialize();
```
You can also serialize the data into an existing buffer instead:
```cpp
std::string buffer; // got buffer from somewhere
tbuilder.serialize(buffer);
```
## Adding layers to tiles
Once you have a tile builder, you'll first need some layers:
```cpp
vtzero::tile_builder tbuilder;
vtzero::layer_builder layer_pois{tbuilder, "pois", 2, 4096};
vtzero::layer_builder layer_roads{tbuilder, "roads"};
vtzero::layer_builder layer_forests{tbuilder, "forests"};
```
Here three layers called "pois", "roads", and "forests" are added. The first
one explicitly specifies the vector tile version used and the extent. The
values specified here are the default, so all layers in this example will have
a version of 2 and an extent of 4096.
If you have read a layer from an existing vector tile and want to copy over
some of the data, you can use this layer to initialize the new layer in the
new vector tile with the name, version and extent from the existing layer like
this:
```cpp
vtzero::layer some_layer = ...;
vtzero::layer_builder layer_pois{tbuilder, some_layer};
// same as...
vtzero::layer_builder layer_pois{tbuilder, some_layer.name(),
some_layer.version(),
some_layer.extent()};
```
If you want to copy over an existing layer completely, you can use the
`add_existing_layer()` function instead:
```cpp
vtzero::layer some_layer = ...;
vtzero::tile_builder tbuilder;
tbuilder.add_existing_layer(some_layer);
```
Or, if you have the encoded layer data available in a `data_view` this also
works:
```cpp
vtzero::data_view layer_data = ...;
vtzero::tile_builder tbuilder;
tbuilder.add_existing_layer(layer_data);
```
Note that this call will only store a reference to the data to be added in the
tile builder. The data will only be copied when the final `serialize()` is
called, so the input data must still be available then!
You can mix any of the ways of adding a layer to the tile mentioned above. The
layers will be added to the tile in the order you add them to the
`tile_builder`.
The tile builder is smart enough to not add empty layers, so you can start
out with all the layers you might need and if some of them stay empty, they
will not be added to the tile when `serialize()` is called.
## Adding features to layers
Once we have one or more `layer_builder`s instantiated, we can add features
to them. This is done through the following feature builder classes:
* `point_feature_builder` to add a feature with a (multi)point geometry,
* `linestring_feature_builder` to add a feature with a (multi)linestring
geometry,
* `polygon_feature_builder` to add a feature with a (multi)polygon geometry, or
* `geometry_feature_builder` to add a feature with an existing geometry you
got from reading a vector tile.
In all cases you need to instantiate the feature builder class, optionally
add the feature ID using the `set_id()` method, add the geometry and then
add all the properties of this feature. You have to keep to this order!
```cpp
...
vtzero::layer_builder lbuilder{...};
{
vtzero::point_feature_builder fbuilder{lbuilder};
// optionally set the ID
fbuilder.set_id(23);
// add the geometry (exact calls are different for different feature builders)
fbuilder.add_point(99, 33);
// add the properties
fbuilder.add_property("amenity", "restaurant");
// call commit() when you are done
fbuilder.commit()
}
```
You have to call `commit()` on the feature builder object after you set all the
data to actually add it to the layer. If you don't do this, the feature will
not be added to the layer! This can be useful, for instance, if you detect that
you have an invalid geometry while you are adding the geometry to the feature
builder. In that case you can call `rollback()` explicitly or just let the
feature builder go out of scope and it will do the rollback automatically.
Only the first call to `commit()` or `rollback()` will take effect, any further
calls to these functions on the same feature builder object are ignored.
## Adding a geometry to the feature
There are different ways of adding the geometry to the feature, depending on
the geometry type.
### Adding a point geometry
Simply call `add_point()` to set the point geometry. There are three different
overloads for this function. One takes a `vtzero::point`, one takes two
`uint32_t`s with the x and y coordinates and one takes any type `T` that can
be converted to a `vtzero::point` using the `create_vtzero_point` function.
This templated function works on any type that has `x` and `y` members and
you can create your own overload of this function. See the
[advanced.md](advanced topics documentation).
### Adding a multipoint geometry
Call `add_points()` with the number of points in the geometry as only argument.
After that call `set_point()` for each of those points. `set_point()` has
multiple overloads just like the `add_point()` method described above.
There is also the `add_points_from_container()` function which copies the
point from any container type supporting the `size()` function and which
iterator yields a `vtzero::point` or something convertible to it.
### Adding a linestring geometry
Call `add_linestring()` with the number of points in the linestring as only
argument. After that call `set_point()` for each of those points. `set_point()`
has multiple overloads just like the `add_point()` method described above.
```cpp
...
vtzero::layer_builder lbuilder{...};
try {
vtzero::linestring_feature_builder fbuilder{lbuilder};
// optionally set the ID
fbuilder.set_id(23);
// add the geometry
fbuilder.add_linestring(2);
fbuilder.set_point(1, 2);
fbuilder.set_point(3, 4);
// add the properties
fbuilder.add_property("highway", "primary");
fbuilder.add_property("maxspeed", 80);
// call commit() when you are done
fbuilder.commit()
} catch (const vtzero::geometry_exception& e) {
// if we are here, something was wrong with the geometry.
}
```
Note that we have wrapped the feature builder in a try-catch-block here. This
will ignore all geometry errors (which can happen if two consective points
are the same creating a zero-length segment).
There are two other versions of the `add_linestring()` function. They take two
iterators defining a range to get the points from. Dereferencing those
iterators must yield a `vtzero::point` or something convertible to it. One of
these functions takes a third argument, the number of points the iterator will
yield. If this is not available `std::distance(begin, end)` is called which
internally by the `add_linestring()` function which might be slow depending on
your iterator type.
### Adding a multilinestring geometry
Adding a multilinestring works just like adding a linestring, just do the
calls to `add_linestring()` etc. repeatedly for each of the linestrings.
### Adding a polygon geometry
A polygon consists of one outer ring and zero or more inner rings. You have
to first add the outer ring and then the inner rings, if any.
Call `add_ring()` with the number of points in the ring as only argument. After
that call `set_point()` for each of those points. `set_point()` has multiple
overloads just like the `add_point()` method described above. The minimum
number of points is 4 and the last point must be the same as the first point
(or call `close_ring()` instead of the last `set_point()`).
```cpp
...
vtzero::layer_builder lbuilder{...};
try {
vtzero::polygon_feature_builder fbuilder{lbuilder};
// optionally set the ID
fbuilder.set_id(23);
// add the geometry
fbuilder.add_ring(5);
fbuilder.set_point(1, 1);
fbuilder.set_point(1, 2);
fbuilder.set_point(2, 2);
fbuilder.set_point(2, 1);
fbuilder.set_point(1, 1); // or call fbuilder.close_ring() instead
// add the properties
fbuilder.add_property("landuse", "forest");
// call commit() when you are done
fbuilder.commit()
} catch (const vtzero::geometry_exception& e) {
// if we are here, something was wrong with the geometry.
}
```
Note that we have wrapped the feature builder in a try-catch-block here. This
will ignore all geometry errors (which can happen if two consective points
are the same creating a zero-length segment or if the last point is not the
same as the first point).
There are two other versions of the `add_ring()` function. They take two
iterators defining a range to get the points from. Dereferencing those
iterators must yield a `vtzero::point` or something convertible to it. One of
these functions takes a third argument, the number of points the iterator will
yield. If this is not available `std::distance(begin, end)` is called which
internally by the `add_ring()` function which might be slow depending on your
iterator type.
### Adding a multipolygon geometry
Adding a multipolygon works just like adding a polygon, just do the calls to
`add_ring()` etc. repeatedly for each of the rings. Make sure to always first
add an outer ring, then the inner rings in this outer ring, then the next
outer ring and so on.
### Adding an existing geometry
The `geometry_feature_builder` class is used to add geometries you got from
reading a vector tile. This is useful when you want to copy over a geometry
from a feature without decoding it.
```cpp
auto geom = ... // get geometry from a feature you are reading
...
vtzero::tile_builder tb;
vtzero::layer_builder lb{tb};
vtzero::geometry_feature_builder fb{lb};
fb.set_id(123); // optionally set ID
fb.add_geometry(geom) // add geometry
fb.add_property("foo", "bar"); // add properties
fb.commit();
...
```
## Adding properties to the feature
A feature can have any number of properties. They are added with the
`add_property()` method called on the feature builder. There are two different
ways of doing this. The *simple approach* which does all the work for you and
the *advanced approach* which can be more efficient, but you have to to some
more work. It is recommended that you start out with the simple approach and
only switch to the advanced approach once you have a working program and want
to get the last bit of performance out of it.
The difference stems from the way properties are encoded in vector tiles. While
properties "belong" to features, they are really stored in two tables (for the
keys and values) in the layer. The individual feature only references the
entries in those tables by index. This make the encoded tile smaller, but it
means somebody has to manage those tables. In the simple approach this is done
behind the scenes by the `layer_builder` object, in the advanced approach you
handle that yourself.
Do not mix the simple and the advanced approach unless you know what you are
doing.
### The simple approach to adding properties
For the simple approach call `add_property()` with two arguments. The first is
the key, it must have some kind of string type (`std::string`, `const char*`,
`vtzero::data_view`, anything really that converts to a `data_view`). The
second argument is the value, for which most basic C++ types are allowed
(string types, integer types, double, ...). See the API documentation for the
constructors of the `encoded_property_value` class for a list.
```cpp
vtzero::layer_builder lb{...};
vtzero::linestring_feature_builder fb{lb};
...
fb.add_property("waterway", "stream"); // string value
fb.add_property("name", "Little Creek");
fb.add_property("width", 1.5); // double value
...
```
Sometimes you need to specify exactly which type should be used in the
encoding. The `encoded_property_value` constructor can take special types for
that like in the following example, where you force the `sint` encoding:
```cpp
fb.add_property("layer", vtzero::sint_value_type(2));
```
You can also call `add_property()` with a single `vtzero::property` argument
(which is handy if you are copying this property over from a tile you are
reading):
```cpp
while (auto property = feature.next_property()) {
if (property.key() == "name") {
feature_builder.add_property(property);
}
}
```
### The advanced approach to adding properties
In the advanced approach you have to do the indexing yourself. Here is a very
basic example:
```cpp
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
const vtzero::index_value highway = lbuilder.add_key("highway");
const vtzero::index_value primary = lbuilder.add_value("primary");
...
vtzero::point_feature_builder fbuilder{lbuilder};
...
fbuilder.add_property(highway, primary);
...
```
The methods `add_key()` and `add_value()` on the layer builder are used to add
keys and values to the tables in the layer. They both return the index (of type
`vtzero::index_value`) of those keys or values in the tables. You store
those index values somewhere (in this case in the `highway` and `primary`
variables) and use them when calling `add_property()` on the feature builder.
In some cases you only have a few property keys and know them beforehand,
then storing the key indexes in individual variables might work. But for
values this usually doesn't make much sense, and if all your keys and values
are only known at runtime, it doesn't work either. For this you need some kind
of index data structure mapping from keys/values to index values. You can
implement this yourself, but it is easier to use some classes provided by
vtzero. Then the code looks like this:
```cpp
#include <vtzero/index.hpp> // use this include to get the index classes
...
vtzero::layer_builder lb{...};
vtzero::key_index<std::map> key_index{lb};
vtzero::value_index_internal<std::unordered_map> value_index{lb};
...
vtzero::point_feature_builder fb{lb};
...
fb.add_property(key_index("highway"), value_index("primary"));
...
```
In this example the `key_index` template class is used for keys, it uses
`std::map` internally as can be seen by its template argument. The
`value_index_internal` template class is used for values, it uses
`std::unordered_map` internally in this example. Whether you specify `std::map`
or `std::unordered_map` or something else (that needs to be compatible to those
classes) is up to you. Benchmark your use case and decide then.
Keys are always strings, so they are easy to handle. For keys there is only the
single `key_index` in vtzero.
For values this is more difficult. Basically there are two choices:
1. Encode the value according to the vector tile encoding rules which results
in a string and store this in the index. This is what the
`value_index_internal` class does.
2. Store the un-encoded value in the index. The index lookup will be faster,
but you need a different index type for each value type. This is what the
`value_index` classes do.
The `value_index` template classes need three template arguments: The type
used internally to encode the value, the type used externally, and the map
type.
In this example the user program has the values as `int`, the index will store
them in a `std::map<int>`. The integer value is then encoded in an `sint`
int the vector tile:
```cpp
vtzero::value_index<vtzero::sint_value:type, int, std::map> index;
```
Sometimes these generic indexes based on `std::map` or `std::unordered_map`
are inefficient, that's why there are specialized indexes for special cases:
* The `value_index_bool` class can only index boolean values.
* The `value_index_small_uint` class can only index small unsigned integer
values (up to `uint16_t`). It uses a vector internally, so if all your
numbers are small and densely packed, this is very efficient. This is
especially useful for `enum` types.
## The `add_property()` function.
The last chapters already talked about the `add_property()` function of the
`feature_builder` class. But because it is a bit difficult to see all the
different ways `add_property()` can be called, here is some more information.
The `add_property()` function is called with either two parameters for the
key and value or with one parameter that combines the key and value.
If it is called with an `index_value` for the key or value, that index value is
stored directly into the feature. If it is called with an `index_value_pair`,
the index values in the `index_value_pair` are stored directly in the feature.
If it is called with something that is not an `index_value` or
`index_value_pair`, the function will interpret the data as keys or values.
It will add those keys and values to the layer (if they are not already there),
find the corresponding index values and store them in the feature.
You can mix index-use with non-index use. For instance
```cpp
index_value key_maxspeed = lbuilder.add_key("maxspeed");
...
fbuilder.add_property(key_maxspeed, 30);
```
In this case the key ("maxspeed") was added to the layer once and its index
value (`key_maxspeed`) can later be reused. The value (30), on the other hand,
is only added to the layer in the `add_property()` call.
So for keys, you can have as argument:
* An `index_value`.
* A `data_view` or something that converts to it like a `const char*` or `std::string`.
For values, you can have as argument:
* An `index_value`.
* A `property_value`.
* An `encoded_property_value` or anything that converts to it.
For combined keys and values, you can have as argument:
* An `index_value_pair`.
* A `property`.
## Deriving from `layer_builder` and `feature_builder`
The `vtzero::layer_builder` and `vtzero::feature_builder` classes have been
designed in a way that they can be derived from easily. This allows you to
encapsulate part of your vector tile writing code if some aspects of your
layers/features are always the same, such as the layer name and the names
and types of properties.
Say you want to write a layer named "restaurants" with point geometries.
Each feature should have a name and a 5-star-rating. First you create a
class derived from the `layer_builder` with all the indexes you want to use.
For the keys you don't need indexes in this case, because there are only
two keys for which we can easily store the index values in the layer.
```cpp
class restaurant_layer_builder : public vtzero::layer_builder {
public:
// The index we'll use for the "name" property values
vtzero::value_index<vtzero::string_value_type, std::string, std::unordered_map> string_index;
// The index we'll use for the "stars" property values
vtzero::value_index_small_uint stars_index;
// The index value of the "name" key
vtzero::index_value key_name;
// The index value of the "stars" key
vtzero::index_value key_stars;
restaurant_layer_builder(vtzero::tile_builder& tile) :
layer_builder(tile, "restaurants"), // the name of the layer
string_index(*this),
stars_index(*this),
key_name(add_key_without_dup_check("name")),
key_stars(add_key_without_dup_check("stars")) {
}
};
```
The we'll add a class derived from `feature_builder` to help with adding
features:
```cpp
class restaurant_feature_builder : public vtzero::feature_builder {
restaurant_layer_builder& m_layer;
public:
restaurant_feature_builder(restaurant_layer_builder& layer, uint64_t id) :
vtzero::point_feature_builder(layer), // always a point geometry
m_layer(layer) {
set_id(id); // we always have an ID in this case
}
void add_location(mylocation& loc) { // restaurant location is stored in your own type
add_point(loc.lon(), loc.lat());
}
void set_name(const std::string& name) {
add_property(m_layer.key_name,
m_layer.string_index(vtzero::encoded_property_value{name}));
}
void set_stars(stars s) { // your own "stars" type
vtzero::encoded_property_value svalue{ s.num_stars() }; // convert stars type to small integer
add_property(m_layer.key_stars,
m_layer.stars_index(svalue));
}
};
```
This example only shows a general pattern you can follow to derive from the
`layer_builder` and `feature_builder` classes. In some cases this makes more
sense then in others. The derived classes make it easy for you to mix your
own functions (for instance when you need to convert from your own types to
vtzero types like with the `mylocation` and `stars` types above) or just use
the functions in the base classes.

88
examples/CMakeLists.txt Normal file
View File

@ -0,0 +1,88 @@
#-----------------------------------------------------------------------------
#
# CMake config
#
# vtzero examples
#
#-----------------------------------------------------------------------------
include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/include-external")
set(TEST_FILE "${CMAKE_SOURCE_DIR}/test/data/mapbox-streets-v6-14-8714-8017.mvt")
add_executable(vtzero-check vtzero-check.cpp utils.cpp)
add_executable(vtzero-create vtzero-create.cpp utils.cpp)
add_executable(vtzero-encode-geom vtzero-encode-geom.cpp utils.cpp)
add_executable(vtzero-stats vtzero-stats.cpp utils.cpp)
add_executable(vtzero-streets vtzero-streets.cpp utils.cpp)
#-------------------------------------------------------------
add_executable(vtzero-filter vtzero-filter.cpp utils.cpp)
add_test(NAME vtzero-filter-empty
COMMAND vtzero-filter)
set_tests_properties(vtzero-filter-empty PROPERTIES
PASS_REGULAR_EXPRESSION "^Error in command line: Missing file name of vector tile to read")
add_test(NAME vtzero-filter-help
COMMAND vtzero-filter -h)
set_tests_properties(vtzero-filter-help PROPERTIES
PASS_REGULAR_EXPRESSION "^usage:\n vtzero-filter")
add_test(NAME vtzero-filter-layer
COMMAND vtzero-filter -o ${CMAKE_CURRENT_BINARY_DIR}/bridges.mvt ${TEST_FILE} bridge)
add_test(NAME vtzero-filter-feature
COMMAND vtzero-filter -o ${CMAKE_CURRENT_BINARY_DIR}/bridges.mvt ${TEST_FILE} waterway_label 221925711)
add_test(NAME vtzero-filter-invalid-id
COMMAND vtzero-filter -o ${CMAKE_CURRENT_BINARY_DIR}/bridges.mvt ${TEST_FILE} waterway_label abc)
set_tests_properties(vtzero-filter-invalid-id PROPERTIES
WILL_FAIL true)
#-------------------------------------------------------------
add_executable(vtzero-show vtzero-show.cpp utils.cpp)
add_test(NAME vtzero-show-empty
COMMAND vtzero-show)
set_tests_properties(vtzero-show-empty PROPERTIES
PASS_REGULAR_EXPRESSION "^Error in command line: Missing file name of vector tile to read")
add_test(NAME vtzero-show-help
COMMAND vtzero-show -h)
set_tests_properties(vtzero-show-help PROPERTIES
PASS_REGULAR_EXPRESSION "^usage:\n vtzero-show")
add_test(NAME vtzero-show-layers
COMMAND vtzero-show -l ${TEST_FILE})
set_tests_properties(vtzero-show-layers PROPERTIES
PASS_REGULAR_EXPRESSION "^landuse 78\nwaterway 327\n.*\nwaterway_label 4\n$")
add_test(NAME vtzero-show-layer-num
COMMAND vtzero-show ${TEST_FILE} 2)
set_tests_properties(vtzero-show-layer-num PROPERTIES
PASS_REGULAR_EXPRESSION "layer: [0-9]+\n name: water\n")
add_test(NAME vtzero-show-layer-name
COMMAND vtzero-show ${CMAKE_SOURCE_DIR}/test/data/mapbox-streets-v6-14-8714-8017.mvt water)
set_tests_properties(vtzero-show-layer-name PROPERTIES
PASS_REGULAR_EXPRESSION "layer: [0-9]+\n name: water\n")
#-------------------------------------------------------------
file(GLOB ext_tests RELATIVE ${CMAKE_SOURCE_DIR}/test/data/ ${CMAKE_SOURCE_DIR}/test/data/*.mvt)
foreach(_test IN LISTS ext_tests)
message(STATUS "Adding ext test: ${_test}")
add_test(NAME ext-tests-${_test}
COMMAND vtzero-show ${CMAKE_SOURCE_DIR}/test/data/${_test})
endforeach()
#-----------------------------------------------------------------------------

100
examples/utils.cpp Normal file
View File

@ -0,0 +1,100 @@
/*****************************************************************************
Utility functions for vtzero example programs.
*****************************************************************************/
#include "utils.hpp"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>
/**
* Read complete contents of a file into a string.
*
* The file is read in binary mode.
*
* @param filename The file name. Can be empty or "-" to read from STDIN.
* @returns a string with the contents of the file.
* @throws various exceptions if there is an error
*/
std::string read_file(const std::string& filename) {
if (filename.empty() || (filename.size() == 1 && filename[0] == '-')) {
return std::string{std::istreambuf_iterator<char>(std::cin.rdbuf()),
std::istreambuf_iterator<char>()};
}
std::ifstream stream{filename, std::ios_base::in | std::ios_base::binary};
if (!stream) {
throw std::runtime_error{std::string{"Can not open file '"} + filename + "'"};
}
stream.exceptions(std::ifstream::failbit);
std::string buffer{std::istreambuf_iterator<char>(stream.rdbuf()),
std::istreambuf_iterator<char>()};
stream.close();
return buffer;
}
/**
* Write contents of a buffer into a file.
*
* The file is written in binary mode.
*
* @param buffer The data to be written.
* @param filename The file name.
* @throws various exceptions if there is an error
*/
void write_data_to_file(const std::string& buffer, const std::string& filename) {
std::ofstream stream{filename, std::ios_base::out | std::ios_base::binary};
if (!stream) {
throw std::runtime_error{std::string{"Can not open file '"} + filename + "'"};
}
stream.exceptions(std::ifstream::failbit);
stream.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
stream.close();
}
/**
* Get a specific layer from a vector tile. The layer can be specified as a
* number n in which case the nth layer in this tile is returned. Or it can
* be specified as text, in which case the layer with that name is returned.
*
* Calls exit(1) if there is an error.
*
* @param tile The vector tile.
* @param layer_name_or_num specifies the layer.
*/
vtzero::layer get_layer(const vtzero::vector_tile& tile, const std::string& layer_name_or_num) {
vtzero::layer layer;
char* str_end = nullptr;
const long num = std::strtol(layer_name_or_num.c_str(), &str_end, 10); // NOLINT(google-runtime-int)
if (str_end == layer_name_or_num.data() + layer_name_or_num.size()) {
if (num >= 0 && num < std::numeric_limits<long>::max()) { // NOLINT(google-runtime-int)
layer = tile.get_layer(static_cast<std::size_t>(num));
if (!layer) {
std::cerr << "No such layer: " << num << '\n';
std::exit(1);
}
return layer;
}
}
layer = tile.get_layer_by_name(layer_name_or_num);
if (!layer) {
std::cerr << "No layer named '" << layer_name_or_num << "'.\n";
std::exit(1);
}
return layer;
}

11
examples/utils.hpp Normal file
View File

@ -0,0 +1,11 @@
#include <vtzero/vector_tile.hpp>
#include <string>
std::string read_file(const std::string& filename);
void write_data_to_file(const std::string& buffer, const std::string& filename);
vtzero::layer get_layer(const vtzero::vector_tile& tile, const std::string& layer_name_or_num);

225
examples/vtzero-check.cpp Normal file
View File

@ -0,0 +1,225 @@
/*****************************************************************************
Example program for vtzero library.
vtzero-check - Check vector tiles for validity
*****************************************************************************/
#include "utils.hpp"
#include <vtzero/vector_tile.hpp>
#include <iostream>
#include <set>
#include <stdexcept>
#include <string>
class result {
int m_return_code = 0;
public:
void has_warning() noexcept {
if (m_return_code < 1) {
m_return_code = 1;
}
}
void has_error() noexcept {
if (m_return_code < 2) {
m_return_code = 2;
}
}
void has_fatal_error() noexcept {
if (m_return_code < 3) {
m_return_code = 3;
}
}
int return_code() const noexcept {
return m_return_code;
}
} result;
class CheckGeomHandler {
vtzero::point m_prev_point{};
int m_layer_num;
int m_feature_num;
int64_t m_extent;
bool m_is_first_point = false;
int m_count = 0;
void print_context() const {
std::cerr << " in layer " << m_layer_num
<< " in feature " << m_feature_num
<< " in geometry " << m_count
<< ": ";
}
void print_error(const char* message) const {
result.has_error();
std::cerr << "Error";
print_context();
std::cerr << message << '\n';
}
void print_warning(const char* message) const {
result.has_warning();
std::cerr << "Warning";
print_context();
std::cerr << message << '\n';
}
void check_point_location(const vtzero::point point) const {
if (point.x < -m_extent ||
point.y < -m_extent ||
point.x > 2 * m_extent ||
point.y > 2 * m_extent) {
print_warning("point waaaay beyond the extent");
}
}
public:
CheckGeomHandler(uint32_t extent, int layer_num, int feature_num) :
m_layer_num(layer_num),
m_feature_num(feature_num),
m_extent(static_cast<int64_t>(extent)) {
}
// ----------------------------------------------------------------------
void points_begin(const uint32_t /*count*/) const {
}
void points_point(const vtzero::point point) const {
check_point_location(point);
}
void points_end() const {
}
// ----------------------------------------------------------------------
void linestring_begin(const uint32_t count) {
if (count < 2) {
print_error("Not enough points in linestring");
}
m_is_first_point = true;
}
void linestring_point(const vtzero::point point) {
if (m_is_first_point) {
m_is_first_point = false;
} else {
if (point == m_prev_point) {
print_error("Duplicate point in linestring");
}
}
m_prev_point = point;
check_point_location(point);
}
void linestring_end() {
++m_count;
}
// ----------------------------------------------------------------------
void ring_begin(const uint32_t count) {
if (count < 4) {
print_error("Not enough points in ring");
}
m_is_first_point = true;
}
void ring_point(const vtzero::point point) {
if (m_is_first_point) {
m_is_first_point = false;
} else {
if (point == m_prev_point) {
print_error("Duplicate point in ring");
}
}
m_prev_point = point;
check_point_location(point);
}
void ring_end(const vtzero::ring_type rt) {
if (rt == vtzero::ring_type::invalid) {
print_error("Invalid ring with area 0");
}
if (m_count == 0 && rt != vtzero::ring_type::outer) {
print_error("First ring isn't an outer ring");
}
++m_count;
}
}; // class CheckGeomHandler
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " TILE\n";
return 1;
}
std::string input_file{argv[1]};
const auto data = read_file(input_file);
std::set<std::string> layer_names;
vtzero::vector_tile tile{data};
int layer_num = 0;
int feature_num = -1;
try {
while (auto layer = tile.next_layer()) {
if (layer.name().empty()) {
std::cerr << "Error in layer " << layer_num << ": name is empty (spec 4.1)\n";
result.has_error();
}
std::string name(layer.name());
if (layer_names.count(name) > 0) {
std::cerr << "Error in layer " << layer_num << ": name is duplicate of previous layer ('" << name << "') (spec 4.1)\n";
result.has_error();
}
layer_names.insert(name);
feature_num = 0;
while (auto feature = layer.next_feature()) {
CheckGeomHandler handler{layer.extent(), layer_num, feature_num};
vtzero::decode_geometry(feature.geometry(), handler);
++feature_num;
}
if (feature_num == 0) {
std::cerr << "Warning: No features in layer " << layer_num << " (spec 4.1)\n";
result.has_warning();
}
feature_num = -1;
++layer_num;
}
if (layer_num == 0) {
std::cerr << "Warning: No layers in vector tile (spec 4.1)\n";
result.has_warning();
}
} catch (const std::exception& e) {
std::cerr << "Fatal error in layer " << layer_num;
if (feature_num >= 0) {
std::cerr << " in feature " << feature_num;
}
std::cerr << ": " << e.what() << '\n';
result.has_fatal_error();
}
return result.return_code();
}

101
examples/vtzero-create.cpp Normal file
View File

@ -0,0 +1,101 @@
/*****************************************************************************
Example program for vtzero library.
vtzero-create - Create a vector tile
*****************************************************************************/
#include "utils.hpp"
#include <vtzero/builder.hpp>
#include <vtzero/index.hpp>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
int main() {
vtzero::tile_builder tile;
vtzero::layer_builder layer_points{tile, "points"};
vtzero::layer_builder layer_lines{tile, "lines"};
vtzero::layer_builder layer_polygons{tile, "polygons"};
vtzero::key_index<std::unordered_map> idx{layer_points};
{
vtzero::point_feature_builder feature{layer_points};
feature.set_id(1);
feature.add_points(1);
feature.set_point(10, 10);
feature.add_property("foo", "bar");
feature.add_property("x", "y");
feature.rollback();
}
const auto some = idx("some");
{
vtzero::point_feature_builder feature{layer_points};
feature.set_id(2);
feature.add_point(20, 20);
feature.add_property(some, "attr");
}
{
vtzero::point_feature_builder feature{layer_points};
feature.set_id(3);
feature.add_point(20, 20);
feature.add_property(idx("some"), "attr");
}
{
vtzero::point_feature_builder feature{layer_points};
feature.set_id(4);
feature.add_point(20, 20);
feature.add_property(idx("some"), "otherattr");
}
vtzero::point_feature_builder feature1{layer_points};
feature1.set_id(5);
feature1.add_point(vtzero::point{20, 20});
feature1.add_property("otherkey", "attr");
feature1.commit();
vtzero::value_index<vtzero::sint_value_type, int32_t, std::unordered_map> maxspeed_index{layer_lines};
{
vtzero::linestring_feature_builder feature{layer_lines};
feature.set_id(6);
feature.add_linestring(3);
feature.set_point(10, 10);
feature.set_point(10, 20);
feature.set_point(vtzero::point{20, 20});
std::vector<vtzero::point> points = {{11, 11}, {12, 13}};
feature.add_linestring_from_container(points);
feature.add_property("highway", "primary");
feature.add_property(std::string{"maxspeed"}, maxspeed_index(50));
}
{
vtzero::polygon_feature_builder feature{layer_polygons};
feature.set_id(7);
feature.add_ring(5);
feature.set_point(0, 0);
feature.set_point(10, 0);
feature.set_point(10, 10);
feature.set_point(0, 10);
feature.set_point(0, 0);
feature.add_ring(4);
feature.set_point(3, 3);
feature.set_point(3, 5);
feature.set_point(5, 5);
feature.close_ring();
feature.add_property("natural", "wood");
feature.add_property("number_of_trees", vtzero::sint_value_type{23402752});
}
const auto data = tile.serialize();
write_data_to_file(data, "test.mvt");
}

View File

@ -0,0 +1,142 @@
/*****************************************************************************
Example program for vtzero library.
vtzero-encode-geom - Encode geometry on command line
This can be used for debugging. Uses internals of vtzero!
*****************************************************************************/
#include "utils.hpp"
#include <vtzero/geometry.hpp>
#include <cctype>
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
int64_t get_int(const char* arg) {
char* endptr = nullptr;
const int64_t value = std::strtoll(arg, &endptr, 10);
if (*endptr == '\0') {
return value;
}
throw std::runtime_error{"not a valid number"};
}
uint32_t move_to(const char* arg) {
if (!std::isdigit(arg[0])) {
throw std::runtime_error{"need count after M command"};
}
const auto scount = get_int(arg);
if (scount <= 0) {
throw std::runtime_error{"count after M command must be 1 or larger"};
}
const auto count = static_cast<uint32_t>(scount);
std::cout << "MOVE_TO(" << count << ")\t" << vtzero::detail::command_move_to(count) << '\n';
return vtzero::detail::command_move_to(count);
}
uint32_t line_to(const char* arg) {
if (!std::isdigit(arg[0])) {
throw std::runtime_error{"need count after L command"};
}
const auto scount = get_int(arg);
if (scount <= 0) {
throw std::runtime_error{"count after L command must be 1 or larger"};
}
const auto count = static_cast<uint32_t>(scount);
std::cout << "LINE_TO(" << count << ")\t" << vtzero::detail::command_line_to(count) << '\n';
return vtzero::detail::command_line_to(count);
}
uint32_t close_path(const char* arg) {
if (arg[0] != '\0') {
throw std::runtime_error{"extra data after C command"};
}
std::cout << "CLOSE_PATH\t" << vtzero::detail::command_close_path() << '\n';
return vtzero::detail::command_close_path();
}
uint32_t number(const char* arg) {
const auto num = static_cast<int32_t>(get_int(arg));
std::cout << "number(" << num << ")\t" << protozero::encode_zigzag32(num) << '\n';
return protozero::encode_zigzag32(num);
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " GEOMETRY ELEMENTS...\n"
<< "GEOMETRY ELEMENTS are:\n"
<< " M[count] -- MOVE_TO count\n"
<< " L[count] -- LINE_TO count\n"
<< " C -- CLOSE_PATH\n"
<< " [number] -- number that will be zigzag encoded\n";
return 1;
}
std::vector<uint32_t> values;
std::cout << "raw data\tencoded\n-----------------------------------\n";
for (int i = 1; i < argc; ++i) {
try {
switch (argv[i][0]) {
case '\0':
break;
case 'M':
values.push_back(move_to(argv[i] + 1));
break;
case 'L':
values.push_back(line_to(argv[i] + 1));
break;
case 'C':
values.push_back(close_path(argv[i] + 1));
break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
values.push_back(number(argv[i]));
break;
default:
throw std::runtime_error{std::string{"unknown data: "} + argv[i]};
return 1;
}
} catch (const std::runtime_error& e) {
std::cerr << "error(" << i << "): " << e.what() << '\n';
return 1;
}
}
std::string out{"["};
for (auto value : values) {
out += ' ';
out += std::to_string(value);
out += ',';
}
out.back() = ' ';
std::cout << '\n' << out << "]\n";
}

101
examples/vtzero-filter.cpp Normal file
View File

@ -0,0 +1,101 @@
/*****************************************************************************
Example program for vtzero library.
vtzero-filter - Copy parts of a vector tile into a new tile.
*****************************************************************************/
#include "utils.hpp"
#include <vtzero/builder.hpp>
#include <vtzero/vector_tile.hpp>
#include <clara.hpp>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <limits>
#include <string>
int main(int argc, char* argv[]) {
std::string filename;
std::string layer_num_or_name;
std::string idstr;
std::string output_file{"filtered.mvt"};
bool help = false;
const auto cli
= clara::Opt(output_file, "FILE")
["-o"]["--output"]
("write output to FILE")
| clara::Help(help)
| clara::Arg(filename, "FILENAME").required()
("vector tile")
| clara::Arg(layer_num_or_name, "LAYER-NUM|LAYER-NAME").required()
("layer")
| clara::Arg(idstr, "ID")
("feature_id");
const auto result = cli.parse(clara::Args(argc, argv));
if (!result) {
std::cerr << "Error in command line: " << result.errorMessage() << '\n';
return 1;
}
if (help) {
std::cout << cli
<< "\nFilter contents of vector tile.\n";
return 0;
}
if (filename.empty()) {
std::cerr << "Error in command line: Missing file name of vector tile to read\n";
return 1;
}
if (layer_num_or_name.empty()) {
std::cerr << "Error in command line: Missing layer number or name\n";
return 1;
}
const auto data = read_file(filename);
vtzero::vector_tile tile{data};
auto layer = get_layer(tile, layer_num_or_name);
std::cerr << "Found layer: " << std::string(layer.name()) << "\n";
vtzero::tile_builder tb;
if (idstr.empty()) {
tb.add_existing_layer(layer);
} else {
char* str_end = nullptr;
const int64_t id = std::strtoll(idstr.c_str(), &str_end, 10);
if (str_end != idstr.c_str() + idstr.size()) {
std::cerr << "Feature ID must be numeric.\n";
return 1;
}
if (id < 0) {
std::cerr << "Feature ID must be >= 0.\n";
return 1;
}
const auto feature = layer.get_feature_by_id(static_cast<uint64_t>(id));
if (!feature.valid()) {
std::cerr << "No feature with that id: " << id << '\n';
return 1;
}
vtzero::layer_builder layer_builder{tb, layer};
layer_builder.add_feature(feature);
}
std::string output = tb.serialize();
write_data_to_file(output, output_file);
}

242
examples/vtzero-show.cpp Normal file
View File

@ -0,0 +1,242 @@
/*****************************************************************************
Example program for vtzero library.
vtzero-show - Show content of vector tile
*****************************************************************************/
#include "utils.hpp"
#include <vtzero/vector_tile.hpp>
#include <clara.hpp>
#include <cstdint>
#include <exception>
#include <iostream>
#include <string>
class geom_handler {
std::string output{};
public:
void points_begin(const uint32_t /*count*/) const noexcept {
}
void points_point(const vtzero::point point) const {
std::cout << " POINT(" << point.x << ',' << point.y << ")\n";
}
void points_end() const noexcept {
}
void linestring_begin(const uint32_t count) {
output = " LINESTRING[count=";
output += std::to_string(count);
output += "](";
}
void linestring_point(const vtzero::point point) {
output += std::to_string(point.x);
output += ' ';
output += std::to_string(point.y);
output += ',';
}
void linestring_end() {
if (output.empty()) {
return;
}
if (output.back() == ',') {
output.resize(output.size() - 1);
}
output += ")\n";
std::cout << output;
}
void ring_begin(const uint32_t count) {
output = " RING[count=";
output += std::to_string(count);
output += "](";
}
void ring_point(const vtzero::point point) {
output += std::to_string(point.x);
output += ' ';
output += std::to_string(point.y);
output += ',';
}
void ring_end(const vtzero::ring_type rt) {
if (output.empty()) {
return;
}
if (output.back() == ',') {
output.back() = ')';
}
switch (rt) {
case vtzero::ring_type::outer:
output += "[OUTER]\n";
break;
case vtzero::ring_type::inner:
output += "[INNER]\n";
break;
default:
output += "[INVALID]\n";
}
std::cout << output;
}
}; // class geom_handler
template <typename TChar, typename TTraits>
std::basic_ostream<TChar, TTraits>& operator<<(std::basic_ostream<TChar, TTraits>& out, vtzero::data_view value) {
out.write(value.data(), static_cast<std::streamsize>(value.size()));
return out;
}
struct print_value {
template <typename T>
void operator()(T value) const {
std::cout << value;
}
void operator()(const vtzero::data_view value) const {
std::cout << '"' << value << '"';
}
}; // struct print_value
static void print_layer(vtzero::layer& layer, bool print_tables, bool print_value_types, int& layer_num, int& feature_num) {
std::cout << "=============================================================\n"
<< "layer: " << layer_num << '\n'
<< " name: " << std::string(layer.name()) << '\n'
<< " version: " << layer.version() << '\n'
<< " extent: " << layer.extent() << '\n';
if (print_tables) {
std::cout << " keys:\n";
int n = 0;
for (const auto& key : layer.key_table()) {
std::cout << " " << n++ << ": " << key << '\n';
}
std::cout << " values:\n";
n = 0;
for (const vtzero::property_value& value : layer.value_table()) {
std::cout << " " << n++ << ": ";
vtzero::apply_visitor(print_value{}, value);
if (print_value_types) {
std::cout << " [" << vtzero::property_value_type_name(value.type()) << "]\n";
} else {
std::cout << '\n';
}
}
}
feature_num = 0;
while (auto feature = layer.next_feature()) {
std::cout << " feature: " << feature_num << '\n'
<< " id: ";
if (feature.has_id()) {
std::cout << feature.id() << '\n';
} else {
std::cout << "(none)\n";
}
std::cout << " geomtype: " << vtzero::geom_type_name(feature.geometry_type()) << '\n'
<< " geometry:\n";
vtzero::decode_geometry(feature.geometry(), geom_handler{});
std::cout << " properties:\n";
while (auto property = feature.next_property()) {
std::cout << " " << property.key() << '=';
vtzero::apply_visitor(print_value{}, property.value());
if (print_value_types) {
std::cout << " [" << vtzero::property_value_type_name(property.value().type()) << "]\n";
} else {
std::cout << '\n';
}
}
++feature_num;
}
}
static void print_layer_overview(const vtzero::layer& layer) {
std::cout << layer.name() << ' ' << layer.num_features() << '\n';
}
int main(int argc, char* argv[]) {
std::string filename;
std::string layer_num_or_name;
bool layer_overview = false;
bool print_tables = false;
bool print_value_types = false;
bool help = false;
const auto cli
= clara::Opt(layer_overview)
["-l"]["--layers"]
("show layer overview with feature count")
| clara::Opt(print_tables)
["-t"]["--tables"]
("also print key/value tables")
| clara::Opt(print_value_types)
["-T"]["--value-types"]
("also show value types")
| clara::Help(help)
| clara::Arg(filename, "FILENAME").required()
("vector tile")
| clara::Arg(layer_num_or_name, "LAYER-NUM|LAYER-NAME")
("layer");
const auto result = cli.parse(clara::Args(argc, argv));
if (!result) {
std::cerr << "Error in command line: " << result.errorMessage() << '\n';
return 1;
}
if (help) {
std::cout << cli
<< "\nShow contents of vector tile FILENAME.\n";
return 0;
}
if (filename.empty()) {
std::cerr << "Error in command line: Missing file name of vector tile to read\n";
return 1;
}
int layer_num = 0;
int feature_num = 0;
try {
const auto data = read_file(filename);
vtzero::vector_tile tile{data};
if (layer_num_or_name.empty()) {
while (auto layer = tile.next_layer()) {
if (layer_overview) {
print_layer_overview(layer);
} else {
print_layer(layer, print_tables, print_value_types, layer_num, feature_num);
}
++layer_num;
}
} else {
auto layer = get_layer(tile, layer_num_or_name);
if (layer_overview) {
print_layer_overview(layer);
} else {
print_layer(layer, print_tables, print_value_types, layer_num, feature_num);
}
}
} catch (const std::exception& e) {
std::cerr << "Error in layer " << layer_num << " (feature " << feature_num << "): " << e.what() << '\n';
return 1;
}
return 0;
}

36
examples/vtzero-stats.cpp Normal file
View File

@ -0,0 +1,36 @@
/*****************************************************************************
Example program for vtzero library.
vtzero-stats - Output some stats on layers
*****************************************************************************/
#include "utils.hpp"
#include <vtzero/vector_tile.hpp>
#include <iostream>
#include <stdexcept>
#include <string>
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " TILE\n";
return 1;
}
std::string input_file{argv[1]};
const auto data = read_file(input_file);
vtzero::vector_tile tile{data};
while (const auto layer = tile.next_layer()) {
std::cout.write(layer.name().data(), static_cast<std::streamsize>(layer.name().size()));
std::cout << ' '
<< layer.num_features() << ' '
<< layer.key_table().size() << ' '
<< layer.value_table().size() << '\n';
}
}

View File

@ -0,0 +1,79 @@
/*****************************************************************************
Example program for vtzero library.
vtzero-streets - Copy features from road_label layer if they have property
class="street". Output is always in file "streets.mvt".
*****************************************************************************/
#include "utils.hpp"
#include <vtzero/builder.hpp>
#include <vtzero/property_mapper.hpp>
#include <vtzero/vector_tile.hpp>
#include <iostream>
#include <string>
static bool keep_feature(const vtzero::feature& feature) {
static const std::string key{"class"};
static const std::string val{"street"};
bool found = false;
feature.for_each_property([&](const vtzero::property& prop) {
found = key == prop.key() && val == prop.value().string_value();
return !found;
});
return found;
}
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " TILE\n";
return 1;
}
std::string input_file{argv[1]};
std::string output_file{"streets.mvt"};
const auto data = read_file(input_file);
try {
vtzero::vector_tile tile{data};
auto layer = get_layer(tile, "road_label");
if (!layer) {
std::cerr << "No 'road_label' layer found\n";
return 1;
}
vtzero::tile_builder tb;
vtzero::layer_builder layer_builder{tb, layer};
vtzero::property_mapper mapper{layer, layer_builder};
while (auto feature = layer.next_feature()) {
if (keep_feature(feature)) {
vtzero::geometry_feature_builder feature_builder{layer_builder};
if (feature.has_id()) {
feature_builder.set_id(feature.id());
}
feature_builder.set_geometry(feature.geometry());
while (auto idxs = feature.next_property_indexes()) {
feature_builder.add_property(mapper(idxs));
}
}
}
std::string output = tb.serialize();
write_data_to_file(output, output_file);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << '\n';
return 1;
}
}

1239
include-external/clara.hpp Normal file

File diff suppressed because it is too large Load Diff

1256
include/vtzero/builder.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,274 @@
#ifndef VTZERO_BUILDER_IMPL_HPP
#define VTZERO_BUILDER_IMPL_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file builder_impl.hpp
*
* @brief Contains classes internal to the builder.
*/
#include "encoded_property_value.hpp"
#include "property_value.hpp"
#include "types.hpp"
#include <protozero/pbf_builder.hpp>
#include <protozero/pbf_message.hpp>
#include <cstdlib>
#include <string>
#include <unordered_map>
#include <utility>
namespace vtzero {
namespace detail {
class layer_builder_base {
public:
layer_builder_base() noexcept = default;
virtual ~layer_builder_base() noexcept = default;
layer_builder_base(const layer_builder_base&) noexcept = default;
layer_builder_base& operator=(const layer_builder_base&) noexcept = default;
layer_builder_base(layer_builder_base&&) noexcept = default;
layer_builder_base& operator=(layer_builder_base&&) noexcept = default;
virtual std::size_t estimated_size() const = 0;
virtual void build(protozero::pbf_builder<detail::pbf_tile>& pbf_tile_builder) const = 0;
}; // class layer_builder_base
class layer_builder_impl : public layer_builder_base {
// Buffer containing the encoded layer metadata and features
std::string m_data;
// Buffer containing the encoded keys table
std::string m_keys_data;
// Buffer containing the encoded values table
std::string m_values_data;
protozero::pbf_builder<detail::pbf_layer> m_pbf_message_layer;
protozero::pbf_builder<detail::pbf_layer> m_pbf_message_keys;
protozero::pbf_builder<detail::pbf_layer> m_pbf_message_values;
// The number of features in the layer
std::size_t m_num_features = 0;
// The number of keys in the keys table
uint32_t m_num_keys = 0;
// The number of values in the values table
uint32_t m_num_values = 0;
// Below this value, no index will be used to find entries in the
// key/value tables. This number is based on some initial
// benchmarking but probably needs some tuning.
// See also https://github.com/mapbox/vtzero/issues/30
static constexpr const uint32_t max_entries_flat = 20;
using map_type = std::unordered_map<std::string, index_value>;
map_type m_keys_index;
map_type m_values_index;
static index_value find_in_table(const data_view text, const std::string& data) {
uint32_t index = 0;
protozero::pbf_message<detail::pbf_layer> pbf_table{data};
while (pbf_table.next()) {
const auto v = pbf_table.get_view();
if (v == text) {
return index_value{index};
}
++index;
}
return index_value{};
}
// Read the key or value table and populate an index from its
// entries. This is done once the table becomes too large to do
// linear search in it.
static void populate_index(const std::string& data, map_type& map) {
uint32_t index = 0;
protozero::pbf_message<detail::pbf_layer> pbf_table{data};
while (pbf_table.next()) {
map[pbf_table.get_string()] = index++;
}
}
index_value add_value_without_dup_check(const data_view text) {
m_pbf_message_values.add_string(detail::pbf_layer::values, text);
return m_num_values++;
}
index_value add_value(const data_view text) {
const auto index = find_in_values_table(text);
if (index.valid()) {
return index;
}
return add_value_without_dup_check(text);
}
index_value find_in_keys_table(const data_view text) {
if (m_num_keys < max_entries_flat) {
return find_in_table(text, m_keys_data);
}
if (m_keys_index.empty()) {
populate_index(m_keys_data, m_keys_index);
}
auto& v = m_keys_index[std::string(text)];
if (!v.valid()) {
v = add_key_without_dup_check(text);
}
return v;
}
index_value find_in_values_table(const data_view text) {
if (m_num_values < max_entries_flat) {
return find_in_table(text, m_values_data);
}
if (m_values_index.empty()) {
populate_index(m_values_data, m_values_index);
}
auto& v = m_values_index[std::string(text)];
if (!v.valid()) {
v = add_value_without_dup_check(text);
}
return v;
}
public:
template <typename TString>
layer_builder_impl(TString&& name, uint32_t version, uint32_t extent) :
m_pbf_message_layer(m_data),
m_pbf_message_keys(m_keys_data),
m_pbf_message_values(m_values_data) {
m_pbf_message_layer.add_uint32(detail::pbf_layer::version, version);
m_pbf_message_layer.add_string(detail::pbf_layer::name, std::forward<TString>(name));
m_pbf_message_layer.add_uint32(detail::pbf_layer::extent, extent);
}
~layer_builder_impl() noexcept override = default;
layer_builder_impl(const layer_builder_impl&) = delete;
layer_builder_impl& operator=(const layer_builder_impl&) = delete;
layer_builder_impl(layer_builder_impl&&) = default;
layer_builder_impl& operator=(layer_builder_impl&&) = default;
index_value add_key_without_dup_check(const data_view text) {
m_pbf_message_keys.add_string(detail::pbf_layer::keys, text);
return m_num_keys++;
}
index_value add_key(const data_view text) {
const auto index = find_in_keys_table(text);
if (index.valid()) {
return index;
}
return add_key_without_dup_check(text);
}
index_value add_value_without_dup_check(const property_value value) {
return add_value_without_dup_check(value.data());
}
index_value add_value_without_dup_check(const encoded_property_value& value) {
return add_value_without_dup_check(value.data());
}
index_value add_value(const property_value value) {
return add_value(value.data());
}
index_value add_value(const encoded_property_value& value) {
return add_value(value.data());
}
const std::string& data() const noexcept {
return m_data;
}
const std::string& keys_data() const noexcept {
return m_keys_data;
}
const std::string& values_data() const noexcept {
return m_values_data;
}
protozero::pbf_builder<detail::pbf_layer>& message() noexcept {
return m_pbf_message_layer;
}
void increment_feature_count() noexcept {
++m_num_features;
}
std::size_t estimated_size() const override {
constexpr const std::size_t estimated_overhead_for_pbf_encoding = 8;
return data().size() +
keys_data().size() +
values_data().size() +
estimated_overhead_for_pbf_encoding;
}
void build(protozero::pbf_builder<detail::pbf_tile>& pbf_tile_builder) const override {
if (m_num_features > 0) {
pbf_tile_builder.add_bytes_vectored(detail::pbf_tile::layers,
data(),
keys_data(),
values_data());
}
}
}; // class layer_builder_impl
class layer_builder_existing : public layer_builder_base {
data_view m_data;
public:
explicit layer_builder_existing(const data_view data) :
m_data(data) {
}
std::size_t estimated_size() const override {
constexpr const std::size_t estimated_overhead_for_pbf_encoding = 8;
return m_data.size() + estimated_overhead_for_pbf_encoding;
}
void build(protozero::pbf_builder<detail::pbf_tile>& pbf_tile_builder) const override {
pbf_tile_builder.add_bytes(detail::pbf_tile::layers, m_data);
}
}; // class layer_builder_existing
} // namespace detail
} // namespace vtzero
#endif // VTZERO_BUILDER_IMPL_HPP

View File

@ -0,0 +1,244 @@
#ifndef VTZERO_ENCODED_PROPERTY_VALUE_HPP
#define VTZERO_ENCODED_PROPERTY_VALUE_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file encoded_property_value.hpp
*
* @brief Contains the encoded_property_value class.
*/
#include "types.hpp"
#include <protozero/pbf_builder.hpp>
#include <functional>
#include <string>
namespace vtzero {
/**
* A property value encoded in the vector_tile internal format. Can be
* created from values of many different types and then later added to
* a layer/feature.
*/
class encoded_property_value {
std::string m_data;
public:
/// Construct from vtzero::string_value_type.
explicit encoded_property_value(string_value_type value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_string(detail::pbf_value::string_value, value.value);
}
/// Construct from const char*.
explicit encoded_property_value(const char* value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_string(detail::pbf_value::string_value, value);
}
/// Construct from const char* and size_t.
explicit encoded_property_value(const char* value, std::size_t size) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_string(detail::pbf_value::string_value, value, size);
}
/// Construct from std::string.
explicit encoded_property_value(const std::string& value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_string(detail::pbf_value::string_value, value);
}
/// Construct from vtzero::data_view.
explicit encoded_property_value(const data_view& value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_string(detail::pbf_value::string_value, value);
}
// ------------------
/// Construct from vtzero::float_value_type.
explicit encoded_property_value(float_value_type value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_float(detail::pbf_value::float_value, value.value);
}
/// Construct from float.
explicit encoded_property_value(float value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_float(detail::pbf_value::float_value, value);
}
// ------------------
/// Construct from vtzero::double_value_type.
explicit encoded_property_value(double_value_type value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_double(detail::pbf_value::double_value, value.value);
}
/// Construct from double.
explicit encoded_property_value(double value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_double(detail::pbf_value::double_value, value);
}
// ------------------
/// Construct from vtzero::int_value_type.
explicit encoded_property_value(int_value_type value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_int64(detail::pbf_value::int_value, value.value);
}
/// Construct from int64_t.
explicit encoded_property_value(int64_t value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_int64(detail::pbf_value::int_value, value);
}
/// Construct from int32_t.
explicit encoded_property_value(int32_t value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_int64(detail::pbf_value::int_value, static_cast<int64_t>(value));
}
/// Construct from int16_t.
explicit encoded_property_value(int16_t value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_int64(detail::pbf_value::int_value, static_cast<int64_t>(value));
}
// ------------------
/// Construct from vtzero::uint_value_type.
explicit encoded_property_value(uint_value_type value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_uint64(detail::pbf_value::uint_value, value.value);
}
/// Construct from uint64_t.
explicit encoded_property_value(uint64_t value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_uint64(detail::pbf_value::uint_value, value);
}
/// Construct from uint32_t.
explicit encoded_property_value(uint32_t value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_uint64(detail::pbf_value::uint_value, static_cast<uint64_t>(value));
}
/// Construct from uint16_t.
explicit encoded_property_value(uint16_t value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_uint64(detail::pbf_value::uint_value, static_cast<uint64_t>(value));
}
// ------------------
/// Construct from vtzero::sint_value_type.
explicit encoded_property_value(sint_value_type value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_sint64(detail::pbf_value::sint_value, value.value);
}
// ------------------
/// Construct from vtzero::bool_value_type.
explicit encoded_property_value(bool_value_type value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_bool(detail::pbf_value::bool_value, value.value);
}
/// Construct from bool.
explicit encoded_property_value(bool value) {
protozero::pbf_builder<detail::pbf_value> pbf_message_value{m_data};
pbf_message_value.add_bool(detail::pbf_value::bool_value, value);
}
// ------------------
/**
* Get view of the raw data stored inside.
*/
data_view data() const noexcept {
return {m_data.data(), m_data.size()};
}
/**
* Hash function compatible with std::hash.
*/
std::size_t hash() const noexcept {
return std::hash<std::string>{}(m_data);
}
}; // class encoded_property_value
/// Encoded property values are equal if they contain the same data.
inline bool operator==(const encoded_property_value& lhs, const encoded_property_value& rhs) noexcept {
return lhs.data() == rhs.data();
}
/// Encoded property values are unequal if they are not equal.
inline bool operator!=(const encoded_property_value& lhs, const encoded_property_value& rhs) noexcept {
return !(lhs == rhs);
}
/// Arbitrary ordering based on internal data.
inline bool operator<(const encoded_property_value& lhs, const encoded_property_value& rhs) noexcept {
return lhs.data() < rhs.data();
}
/// Arbitrary ordering based on internal data.
inline bool operator<=(const encoded_property_value& lhs, const encoded_property_value& rhs) noexcept {
return lhs.data() <= rhs.data();
}
/// Arbitrary ordering based on internal data.
inline bool operator>(const encoded_property_value& lhs, const encoded_property_value& rhs) noexcept {
return lhs.data() > rhs.data();
}
/// Arbitrary ordering based on internal data.
inline bool operator>=(const encoded_property_value& lhs, const encoded_property_value& rhs) noexcept {
return lhs.data() >= rhs.data();
}
} // namespace vtzero
namespace std {
/**
* Specialization of std::hash for encoded_property_value.
*/
template <>
struct hash<vtzero::encoded_property_value> {
/// key vtzero::encoded_property_value
using argument_type = vtzero::encoded_property_value;
/// hash result: size_t
using result_type = std::size_t;
/// calculate the hash of the argument
std::size_t operator()(const vtzero::encoded_property_value& value) const noexcept {
return value.hash();
}
}; // struct hash
} // namespace std
#endif // VTZERO_ENCODED_PROPERTY_VALUE_HPP

View File

@ -0,0 +1,134 @@
#ifndef VTZERO_EXCEPTION_HPP
#define VTZERO_EXCEPTION_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file exception.hpp
*
* @brief Contains the exceptions used in the vtzero library.
*/
#include <cstdint>
#include <stdexcept>
#include <string>
namespace vtzero {
/**
* Base class for all exceptions directly thrown by the vtzero library.
*/
class exception : public std::runtime_error {
public:
/// Constructor
explicit exception(const char* message) :
std::runtime_error(message) {
}
/// Constructor
explicit exception(const std::string& message) :
std::runtime_error(message) {
}
}; // class exception
/**
* This exception is thrown when vector tile encoding isn't valid according
* to the vector tile specification.
*/
class format_exception : public exception {
public:
/// Constructor
explicit format_exception(const char* message) :
exception(message) {
}
/// Constructor
explicit format_exception(const std::string& message) :
exception(message) {
}
}; // class format_exception
/**
* This exception is thrown when a geometry encoding isn't valid according
* to the vector tile specification.
*/
class geometry_exception : public format_exception {
public:
/// Constructor
explicit geometry_exception(const char* message) :
format_exception(message) {
}
/// Constructor
explicit geometry_exception(const std::string& message) :
format_exception(message) {
}
}; // class geometry_exception
/**
* This exception is thrown when a property value is accessed using the
* wrong type.
*/
class type_exception : public exception {
public:
/// Constructor
explicit type_exception() :
exception("wrong property value type") {
}
}; // class type_exception
/**
* This exception is thrown when an unknown version number is found in the
* layer.
*/
class version_exception : public exception {
public:
/// Constructor
explicit version_exception(const uint32_t version) :
exception(std::string{"unknown vector tile version: "} +
std::to_string(version)) {
}
}; // version_exception
/**
* This exception is thrown when an index into the key or value table
* in a layer is out of range. This can only happen if the tile data is
* invalid.
*/
class out_of_range_exception : public exception {
public:
/// Constructor
explicit out_of_range_exception(const uint32_t index) :
exception(std::string{"index out of range: "} +
std::to_string(index)) {
}
}; // out_of_range_exception
} // namespace vtzero
#endif // VTZERO_EXCEPTION_HPP

302
include/vtzero/feature.hpp Normal file
View File

@ -0,0 +1,302 @@
#ifndef VTZERO_FEATURE_HPP
#define VTZERO_FEATURE_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file feature.hpp
*
* @brief Contains the feature class.
*/
#include "exception.hpp"
#include "property.hpp"
#include "property_value.hpp"
#include "types.hpp"
#include <protozero/pbf_message.hpp>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <utility>
namespace vtzero {
class layer;
/**
* A feature according to spec 4.2.
*
* Note that a feature will internally contain a pointer to the layer it
* came from. The layer has to stay valid as long as the feature is used.
*/
class feature {
using uint32_it_range = protozero::iterator_range<protozero::pbf_reader::const_uint32_iterator>;
const layer* m_layer = nullptr;
uint64_t m_id = 0; // defaults to 0, see https://github.com/mapbox/vector-tile-spec/blob/master/2.1/vector_tile.proto#L32
uint32_it_range m_properties{};
protozero::pbf_reader::const_uint32_iterator m_property_iterator{};
std::size_t m_num_properties = 0;
data_view m_geometry{};
GeomType m_geometry_type = GeomType::UNKNOWN; // defaults to UNKNOWN, see https://github.com/mapbox/vector-tile-spec/blob/master/2.1/vector_tile.proto#L41
bool m_has_id = false;
public:
/**
* Construct an invalid feature object.
*/
feature() = default;
/**
* Construct a feature object.
*
* @throws format_exception if the layer data is ill-formed.
*/
feature(const layer* layer, const data_view data) :
m_layer(layer) {
vtzero_assert(layer);
vtzero_assert(data.data());
protozero::pbf_message<detail::pbf_feature> reader{data};
while (reader.next()) {
switch (reader.tag_and_type()) {
case protozero::tag_and_type(detail::pbf_feature::id, protozero::pbf_wire_type::varint):
m_id = reader.get_uint64();
m_has_id = true;
break;
case protozero::tag_and_type(detail::pbf_feature::tags, protozero::pbf_wire_type::length_delimited):
if (m_properties.begin() != protozero::pbf_reader::const_uint32_iterator{}) {
throw format_exception{"Feature has more than one tags field"};
}
m_properties = reader.get_packed_uint32();
m_property_iterator = m_properties.begin();
break;
case protozero::tag_and_type(detail::pbf_feature::type, protozero::pbf_wire_type::varint): {
const auto type = reader.get_enum();
// spec 4.3.4 "Geometry Types"
if (type < 0 || type > 3) {
throw format_exception{"Unknown geometry type (spec 4.3.4)"};
}
m_geometry_type = static_cast<GeomType>(type);
}
break;
case protozero::tag_and_type(detail::pbf_feature::geometry, protozero::pbf_wire_type::length_delimited):
if (!m_geometry.empty()) {
throw format_exception{"Feature has more than one geometry field"};
}
m_geometry = reader.get_view();
break;
default:
reader.skip(); // ignore unknown fields
}
}
// spec 4.2 "A feature MUST contain a geometry field."
if (m_geometry.empty()) {
throw format_exception{"Missing geometry field in feature (spec 4.2)"};
}
const auto size = m_properties.size();
if (size % 2 != 0) {
throw format_exception{"unpaired property key/value indexes (spec 4.4)"};
}
m_num_properties = size / 2;
}
/**
* Is this a valid feature? Valid features are those not created from
* the default constructor.
*
* Complexity: Constant.
*/
bool valid() const noexcept {
return m_geometry.data() != nullptr;
}
/**
* Is this a valid feature? Valid features are those not created from
* the default constructor.
*
* Complexity: Constant.
*/
explicit operator bool() const noexcept {
return valid();
}
/**
* The ID of this feature. According to the spec IDs should be unique
* in a layer if they are set (spec 4.2).
*
* Complexity: Constant.
*
* Always returns 0 for invalid features.
*/
uint64_t id() const noexcept {
return m_id;
}
/**
* Does this feature have an ID?
*
* Complexity: Constant.
*
* Always returns false for invalid features.
*/
bool has_id() const noexcept {
return m_has_id;
}
/**
* The geometry type of this feature.
*
* Complexity: Constant.
*
* Always returns GeomType::UNKNOWN for invalid features.
*/
GeomType geometry_type() const noexcept {
return m_geometry_type;
}
/**
* Get the geometry of this feature.
*
* Complexity: Constant.
*
* @pre @code valid() @endcode
*/
vtzero::geometry geometry() const noexcept {
vtzero_assert_in_noexcept_function(valid());
return {m_geometry, m_geometry_type};
}
/**
* Returns true if this feature doesn't have any properties.
*
* Complexity: Constant.
*
* Always returns true for invalid features.
*/
bool empty() const noexcept {
return m_num_properties == 0;
}
/**
* Returns the number of properties in this feature.
*
* Complexity: Constant.
*
* Always returns 0 for invalid features.
*/
std::size_t num_properties() const noexcept {
return m_num_properties;
}
/**
* Get the next property in this feature.
*
* Complexity: Constant.
*
* @returns The next property or the invalid property if there are no
* more properties.
* @throws format_exception if the feature data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
* @pre @code valid() @endcode
*/
property next_property();
/**
* Get the indexes into the key/value table for the next property in
* this feature.
*
* Complexity: Constant.
*
* @returns The next index_value_pair or an invalid index_value_pair
* if there are no more properties.
* @throws format_exception if the feature data is ill-formed.
* @throws out_of_range_exception if the key or value index is not
* within the range of indexes in the layer key/value table.
* @throws any protozero exception if the protobuf encoding is invalid.
* @pre @code valid() @endcode
*/
index_value_pair next_property_indexes();
/**
* Reset the property iterator. The next time next_property() or
* next_property_indexes() is called, it will begin from the first
* property again.
*
* Complexity: Constant.
*
* @pre @code valid() @endcode
*/
void reset_property() noexcept {
vtzero_assert_in_noexcept_function(valid());
m_property_iterator = m_properties.begin();
}
/**
* Call a function for each property of this feature.
*
* @tparam TFunc The type of the function. It must take a single
* argument of type property&& and return a bool. If the
* function returns false, the iteration will be stopped.
* @param func The function to call.
* @returns true if the iteration was completed and false otherwise.
* @pre @code valid() @endcode
*/
template <typename TFunc>
bool for_each_property(TFunc&& func) const;
}; // class feature
/**
* Create some kind of mapping from property keys to property values.
*
* This can be used to read all properties into a std::map or similar
* object.
*
* @tparam TMap Map type (std::map, std::unordered_map, ...) Must support
* the emplace() method.
* @tparam TKey Key type, usually the key of the map type. The data_view
* of the property key is converted to this type before
* adding it to the map.
* @tparam TValue Value type, usally the value of the map type. The
* property_value is converted to this type before
* adding it to the map.
* @tparam TMapping A struct derived from property_value_mapping with the
* mapping for vtzero property value types to TValue-constructing
* types. (See convert_property_value() for details.)
* @param feature The feature to get the properties from.
* @returns An object of type TMap with all the properties.
* @pre @code feature.valid() @endcode
*/
template <typename TMap,
typename TKey = typename TMap::key_type,
typename TValue = typename TMap::mapped_type,
typename TMapping = property_value_mapping>
TMap create_properties_map(const vtzero::feature& feature) {
TMap map;
feature.for_each_property([&map](const property& p) {
map.emplace(TKey(p.key()), convert_property_value<TValue, TMapping>(p.value()));
return true;
});
return map;
}
} // namespace vtzero
#endif // VTZERO_FEATURE_HPP

View File

@ -0,0 +1,118 @@
#ifndef VTZERO_FEATURE_BUILDER_IMPL_HPP
#define VTZERO_FEATURE_BUILDER_IMPL_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file feature_builder_impl.hpp
*
* @brief Contains classes internal to the builder.
*/
#include "builder_impl.hpp"
#include "encoded_property_value.hpp"
#include "geometry.hpp"
#include "property.hpp"
#include "property_value.hpp"
#include <utility>
namespace vtzero {
namespace detail {
class feature_builder_base {
layer_builder_impl* m_layer;
void add_key_internal(index_value idx) {
vtzero_assert(idx.valid());
m_pbf_tags.add_element(idx.value());
}
template <typename T>
void add_key_internal(T&& key) {
add_key_internal(m_layer->add_key(data_view{std::forward<T>(key)}));
}
void add_value_internal(index_value idx) {
vtzero_assert(idx.valid());
m_pbf_tags.add_element(idx.value());
}
void add_value_internal(property_value value) {
add_value_internal(m_layer->add_value(value));
}
template <typename T>
void add_value_internal(T&& value) {
encoded_property_value v{std::forward<T>(value)};
add_value_internal(m_layer->add_value(v));
}
protected:
protozero::pbf_builder<detail::pbf_feature> m_feature_writer;
protozero::packed_field_uint32 m_pbf_tags;
explicit feature_builder_base(layer_builder_impl* layer) :
m_layer(layer),
m_feature_writer(layer->message(), detail::pbf_layer::features) {
}
~feature_builder_base() noexcept = default;
feature_builder_base(const feature_builder_base&) = delete; // NOLINT(hicpp-use-equals-delete, modernize-use-equals-delete)
feature_builder_base& operator=(const feature_builder_base&) = delete; // NOLINT(hicpp-use-equals-delete, modernize-use-equals-delete)
// The check wants these functions to be public...
feature_builder_base(feature_builder_base&&) noexcept = default;
feature_builder_base& operator=(feature_builder_base&&) noexcept = default;
void add_property_impl(const property& property) {
add_key_internal(property.key());
add_value_internal(property.value());
}
void add_property_impl(const index_value_pair idxs) {
add_key_internal(idxs.key());
add_value_internal(idxs.value());
}
template <typename TKey, typename TValue>
void add_property_impl(TKey&& key, TValue&& value) {
add_key_internal(std::forward<TKey>(key));
add_value_internal(std::forward<TValue>(value));
}
void do_commit() {
if (m_pbf_tags.valid()) {
m_pbf_tags.commit();
}
m_feature_writer.commit();
m_layer->increment_feature_count();
}
void do_rollback() {
if (m_pbf_tags.valid()) {
m_pbf_tags.rollback();
}
m_feature_writer.rollback();
}
}; // class feature_builder_base
} // namespace detail
} // namespace vtzero
#endif // VTZERO_FEATURE_BUILDER_IMPL_HPP

444
include/vtzero/geometry.hpp Normal file
View File

@ -0,0 +1,444 @@
#ifndef VTZERO_GEOMETRY_HPP
#define VTZERO_GEOMETRY_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file geometry.hpp
*
* @brief Contains classes and functions related to geometry handling.
*/
#include "exception.hpp"
#include "types.hpp"
#include <protozero/pbf_reader.hpp>
#include <cstdint>
#include <limits>
#include <utility>
namespace vtzero {
/// A simple point class
struct point {
/// X coordinate
int32_t x = 0;
/// Y coordinate
int32_t y = 0;
/// Default construct to 0 coordinates
constexpr point() noexcept = default;
/// Constructor
constexpr point(int32_t x_, int32_t y_) noexcept :
x(x_),
y(y_) {
}
}; // struct point
/**
* Type of a polygon ring. This can either be "outer", "inner", or
* "invalid". Invalid is used when the area of the ring is 0.
*/
enum class ring_type {
outer = 0,
inner = 1,
invalid = 2
}; // enum class ring_type
/**
* Helper function to create a point from any type that has members x
* and y.
*
* If your point type doesn't have members x any y, you can overload this
* function for your type and it will be used by vtzero.
*/
template <typename TPoint>
point create_vtzero_point(const TPoint& p) noexcept {
return {p.x, p.y};
}
/// Points are equal if their coordinates are
inline constexpr bool operator==(const point a, const point b) noexcept {
return a.x == b.x && a.y == b.y;
}
/// Points are not equal if their coordinates aren't
inline constexpr bool operator!=(const point a, const point b) noexcept {
return !(a == b);
}
namespace detail {
/// The command id type as specified in the vector tile spec
enum class CommandId : uint32_t {
MOVE_TO = 1,
LINE_TO = 2,
CLOSE_PATH = 7
};
inline constexpr uint32_t command_integer(CommandId id, const uint32_t count) noexcept {
return (static_cast<uint32_t>(id) & 0x7u) | (count << 3u);
}
inline constexpr uint32_t command_move_to(const uint32_t count) noexcept {
return command_integer(CommandId::MOVE_TO, count);
}
inline constexpr uint32_t command_line_to(const uint32_t count) noexcept {
return command_integer(CommandId::LINE_TO, count);
}
inline constexpr uint32_t command_close_path() noexcept {
return command_integer(CommandId::CLOSE_PATH, 1);
}
inline constexpr uint32_t get_command_id(const uint32_t command_integer) noexcept {
return command_integer & 0x7u;
}
inline constexpr uint32_t get_command_count(const uint32_t command_integer) noexcept {
return command_integer >> 3u;
}
// The maximum value for the command count according to the spec.
inline constexpr uint32_t max_command_count() noexcept {
return get_command_count(std::numeric_limits<uint32_t>::max());
}
inline constexpr int64_t det(const point a, const point b) noexcept {
return static_cast<int64_t>(a.x) * static_cast<int64_t>(b.y) -
static_cast<int64_t>(b.x) * static_cast<int64_t>(a.y);
}
template <typename T, typename Enable = void>
struct get_result {
using type = void;
template <typename TGeomHandler>
void operator()(TGeomHandler&& /*geom_handler*/) const noexcept {
}
};
template <typename T>
struct get_result<T, typename std::enable_if<!std::is_same<decltype(std::declval<T>().result()), void>::value>::type> {
using type = decltype(std::declval<T>().result());
template <typename TGeomHandler>
type operator()(TGeomHandler&& geom_handler) {
return std::forward<TGeomHandler>(geom_handler).result();
}
};
/**
* Decode a geometry as specified in spec 4.3 from a sequence of 32 bit
* unsigned integers. This templated base class can be instantiated
* with a different iterator type for testing than for normal use.
*/
template <typename TIterator>
class geometry_decoder {
public:
using iterator_type = TIterator;
private:
iterator_type m_it;
iterator_type m_end;
point m_cursor{0, 0};
// maximum value for m_count before we throw an exception
uint32_t m_max_count;
/**
* The current count value is set from the CommandInteger and
* then counted down with each next_point() call. So it must be
* greater than 0 when next_point() is called and 0 when
* next_command() is called.
*/
uint32_t m_count = 0;
public:
geometry_decoder(iterator_type begin, iterator_type end, std::size_t max) :
m_it(begin),
m_end(end),
m_max_count(static_cast<uint32_t>(max)) {
vtzero_assert(max <= detail::max_command_count());
}
uint32_t count() const noexcept {
return m_count;
}
bool done() const noexcept {
return m_it == m_end;
}
bool next_command(const CommandId expected_command_id) {
vtzero_assert(m_count == 0);
if (m_it == m_end) {
return false;
}
const auto command_id = get_command_id(*m_it);
if (command_id != static_cast<uint32_t>(expected_command_id)) {
throw geometry_exception{std::string{"expected command "} +
std::to_string(static_cast<uint32_t>(expected_command_id)) +
" but got " +
std::to_string(command_id)};
}
if (expected_command_id == CommandId::CLOSE_PATH) {
// spec 4.3.3.3 "A ClosePath command MUST have a command count of 1"
if (get_command_count(*m_it) != 1) {
throw geometry_exception{"ClosePath command count is not 1"};
}
} else {
m_count = get_command_count(*m_it);
if (m_count > m_max_count) {
throw geometry_exception{"count too large"};
}
}
++m_it;
return true;
}
point next_point() {
vtzero_assert(m_count > 0);
if (m_it == m_end || std::next(m_it) == m_end) {
throw geometry_exception{"too few points in geometry"};
}
// spec 4.3.2 "A ParameterInteger is zigzag encoded"
int64_t x = protozero::decode_zigzag32(*m_it++);
int64_t y = protozero::decode_zigzag32(*m_it++);
// x and y are int64_t so this addition can never overflow
x += m_cursor.x;
y += m_cursor.y;
// The cast is okay, because a valid vector tile can never
// contain values that would overflow here and we don't care
// what happens to invalid tiles here.
m_cursor.x = static_cast<int32_t>(x);
m_cursor.y = static_cast<int32_t>(y);
--m_count;
return m_cursor;
}
template <typename TGeomHandler>
typename detail::get_result<TGeomHandler>::type decode_point(TGeomHandler&& geom_handler) {
// spec 4.3.4.2 "MUST consist of a single MoveTo command"
if (!next_command(CommandId::MOVE_TO)) {
throw geometry_exception{"expected MoveTo command (spec 4.3.4.2)"};
}
// spec 4.3.4.2 "command count greater than 0"
if (count() == 0) {
throw geometry_exception{"MoveTo command count is zero (spec 4.3.4.2)"};
}
std::forward<TGeomHandler>(geom_handler).points_begin(count());
while (count() > 0) {
std::forward<TGeomHandler>(geom_handler).points_point(next_point());
}
// spec 4.3.4.2 "MUST consist of of a single ... command"
if (!done()) {
throw geometry_exception{"additional data after end of geometry (spec 4.3.4.2)"};
}
std::forward<TGeomHandler>(geom_handler).points_end();
return detail::get_result<TGeomHandler>{}(std::forward<TGeomHandler>(geom_handler));
}
template <typename TGeomHandler>
typename detail::get_result<TGeomHandler>::type decode_linestring(TGeomHandler&& geom_handler) {
// spec 4.3.4.3 "1. A MoveTo command"
while (next_command(CommandId::MOVE_TO)) {
// spec 4.3.4.3 "with a command count of 1"
if (count() != 1) {
throw geometry_exception{"MoveTo command count is not 1 (spec 4.3.4.3)"};
}
const auto first_point = next_point();
// spec 4.3.4.3 "2. A LineTo command"
if (!next_command(CommandId::LINE_TO)) {
throw geometry_exception{"expected LineTo command (spec 4.3.4.3)"};
}
// spec 4.3.4.3 "with a command count greater than 0"
if (count() == 0) {
throw geometry_exception{"LineTo command count is zero (spec 4.3.4.3)"};
}
std::forward<TGeomHandler>(geom_handler).linestring_begin(count() + 1);
std::forward<TGeomHandler>(geom_handler).linestring_point(first_point);
while (count() > 0) {
std::forward<TGeomHandler>(geom_handler).linestring_point(next_point());
}
std::forward<TGeomHandler>(geom_handler).linestring_end();
}
return detail::get_result<TGeomHandler>{}(std::forward<TGeomHandler>(geom_handler));
}
template <typename TGeomHandler>
typename detail::get_result<TGeomHandler>::type decode_polygon(TGeomHandler&& geom_handler) {
// spec 4.3.4.4 "1. A MoveTo command"
while (next_command(CommandId::MOVE_TO)) {
// spec 4.3.4.4 "with a command count of 1"
if (count() != 1) {
throw geometry_exception{"MoveTo command count is not 1 (spec 4.3.4.4)"};
}
int64_t sum = 0;
const point start_point = next_point();
point last_point = start_point;
// spec 4.3.4.4 "2. A LineTo command"
if (!next_command(CommandId::LINE_TO)) {
throw geometry_exception{"expected LineTo command (spec 4.3.4.4)"};
}
std::forward<TGeomHandler>(geom_handler).ring_begin(count() + 2);
std::forward<TGeomHandler>(geom_handler).ring_point(start_point);
while (count() > 0) {
const point p = next_point();
sum += detail::det(last_point, p);
last_point = p;
std::forward<TGeomHandler>(geom_handler).ring_point(p);
}
// spec 4.3.4.4 "3. A ClosePath command"
if (!next_command(CommandId::CLOSE_PATH)) {
throw geometry_exception{"expected ClosePath command (4.3.4.4)"};
}
sum += detail::det(last_point, start_point);
std::forward<TGeomHandler>(geom_handler).ring_point(start_point);
std::forward<TGeomHandler>(geom_handler).ring_end(sum > 0 ? ring_type::outer :
sum < 0 ? ring_type::inner : ring_type::invalid);
}
return detail::get_result<TGeomHandler>{}(std::forward<TGeomHandler>(geom_handler));
}
}; // class geometry_decoder
} // namespace detail
/**
* Decode a point geometry.
*
* @tparam TGeomHandler Handler class. See tutorial for details.
* @param geometry The geometry as returned by feature.geometry().
* @param geom_handler An object of TGeomHandler.
* @throws geometry_error If there is a problem with the geometry.
* @pre Geometry must be a point geometry.
*/
template <typename TGeomHandler>
typename detail::get_result<TGeomHandler>::type decode_point_geometry(const geometry& geometry, TGeomHandler&& geom_handler) {
vtzero_assert(geometry.type() == GeomType::POINT);
detail::geometry_decoder<decltype(geometry.begin())> decoder{geometry.begin(), geometry.end(), geometry.data().size() / 2};
return decoder.decode_point(std::forward<TGeomHandler>(geom_handler));
}
/**
* Decode a linestring geometry.
*
* @tparam TGeomHandler Handler class. See tutorial for details.
* @param geometry The geometry as returned by feature.geometry().
* @param geom_handler An object of TGeomHandler.
* @returns whatever geom_handler.result() returns if that function exists,
* void otherwise
* @throws geometry_error If there is a problem with the geometry.
* @pre Geometry must be a linestring geometry.
*/
template <typename TGeomHandler>
typename detail::get_result<TGeomHandler>::type decode_linestring_geometry(const geometry& geometry, TGeomHandler&& geom_handler) {
vtzero_assert(geometry.type() == GeomType::LINESTRING);
detail::geometry_decoder<decltype(geometry.begin())> decoder{geometry.begin(), geometry.end(), geometry.data().size() / 2};
return decoder.decode_linestring(std::forward<TGeomHandler>(geom_handler));
}
/**
* Decode a polygon geometry.
*
* @tparam TGeomHandler Handler class. See tutorial for details.
* @param geometry The geometry as returned by feature.geometry().
* @param geom_handler An object of TGeomHandler.
* @returns whatever geom_handler.result() returns if that function exists,
* void otherwise
* @throws geometry_error If there is a problem with the geometry.
* @pre Geometry must be a polygon geometry.
*/
template <typename TGeomHandler>
typename detail::get_result<TGeomHandler>::type decode_polygon_geometry(const geometry& geometry, TGeomHandler&& geom_handler) {
vtzero_assert(geometry.type() == GeomType::POLYGON);
detail::geometry_decoder<decltype(geometry.begin())> decoder{geometry.begin(), geometry.end(), geometry.data().size() / 2};
return decoder.decode_polygon(std::forward<TGeomHandler>(geom_handler));
}
/**
* Decode a geometry.
*
* @tparam TGeomHandler Handler class. See tutorial for details.
* @param geometry The geometry as returned by feature.geometry().
* @param geom_handler An object of TGeomHandler.
* @returns whatever geom_handler.result() returns if that function exists,
* void otherwise
* @throws geometry_error If the geometry has type UNKNOWN of if there is
* a problem with the geometry.
*/
template <typename TGeomHandler>
typename detail::get_result<TGeomHandler>::type decode_geometry(const geometry& geometry, TGeomHandler&& geom_handler) {
detail::geometry_decoder<decltype(geometry.begin())> decoder{geometry.begin(), geometry.end(), geometry.data().size() / 2};
switch (geometry.type()) {
case GeomType::POINT:
return decoder.decode_point(std::forward<TGeomHandler>(geom_handler));
case GeomType::LINESTRING:
return decoder.decode_linestring(std::forward<TGeomHandler>(geom_handler));
case GeomType::POLYGON:
return decoder.decode_polygon(std::forward<TGeomHandler>(geom_handler));
default:
break;
}
throw geometry_exception{"unknown geometry type"};
}
} // namespace vtzero
#endif // VTZERO_GEOMETRY_HPP

264
include/vtzero/index.hpp Normal file
View File

@ -0,0 +1,264 @@
#ifndef VTZERO_INDEX_HPP
#define VTZERO_INDEX_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file index.hpp
*
* @brief Contains classes for indexing the key/value tables inside layers.
*/
#include "builder.hpp"
#include "types.hpp"
#include <array>
#include <cstdint>
#include <vector>
namespace vtzero {
/**
* Used to store the mapping between property keys and the index value
* in the table stored in a layer.
*
* @tparam TMap The map class to use (std::map, std::unordered_map or
* something compatible).
*/
template <template <typename...> class TMap>
class key_index {
layer_builder& m_builder;
TMap<std::string, index_value> m_index;
public:
/**
* Construct index.
*
* @param builder The layer we are building containing the key table
* we are creating the index for.
*/
explicit key_index(layer_builder& builder) :
m_builder(builder) {
}
/**
* Get the index value for the specified key. If the key was not in
* the table, it will be added.
*
* @param key The key to store.
* @returns The index value of they key.
*/
index_value operator()(const data_view key) {
std::string text(key);
const auto it = m_index.find(text);
if (it == m_index.end()) {
const auto idx = m_builder.add_key_without_dup_check(key);
m_index.emplace(std::move(text), idx);
return idx;
}
return it->second;
}
}; // class key_index
/**
* Used to store the mapping between property values and the index value
* in the table stored in a layer. Stores the values in their original
* form (as type TExternal) which is more efficient than the way
* value_index_internal does it, but you need an instance of this class
* for each value type you use.
*
* @tparam TInternal The type used in the vector tile to encode the value.
* Must be one of string/float/double/int/uint/sint/bool_value_type.
* @tparam TExternal The type for the value used by the user of this class.
* @tparam TMap The map class to use (std::map, std::unordered_map or
* something compatible).
*/
template <typename TInternal, typename TExternal, template <typename...> class TMap>
class value_index {
layer_builder& m_builder;
TMap<TExternal, index_value> m_index;
public:
/**
* Construct index.
*
* @param builder The layer we are building containing the value
* table we are creating the index for.
*/
explicit value_index(layer_builder& builder) :
m_builder(builder) {
}
/**
* Get the index value for the specified value. If the value was not in
* the table, it will be added.
*
* @param value The value to store.
* @returns The index value of they value.
*/
index_value operator()(const TExternal& value) {
const auto it = m_index.find(value);
if (it == m_index.end()) {
const auto idx = m_builder.add_value_without_dup_check(encoded_property_value{TInternal{value}});
m_index.emplace(value, idx);
return idx;
}
return it->second;
}
}; // class value_index
/**
* Used to store the mapping between bool property values and the index
* value in the table stored in a layer.
*
* This is the most efficient index if you know that your property values
* are bools.
*/
class value_index_bool {
layer_builder& m_builder;
std::array<index_value, 2> m_index;
public:
/**
* Construct index.
*
* @param builder The layer we are building containing the value
* table we are creating the index for.
*/
explicit value_index_bool(layer_builder& builder) :
m_builder(builder) {
}
/**
* Get the index value for the specified value. If the value was not in
* the table, it will be added.
*
* @param value The value to store.
* @returns The index value of they value.
*/
index_value operator()(const bool value) {
auto& idx = m_index[static_cast<std::size_t>(value)]; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
if (!idx.valid()) {
idx = m_builder.add_value_without_dup_check(encoded_property_value{value});
}
return idx;
}
}; // class value_index_bool
/**
* Used to store the mapping between small unsigned int property values and
* the index value in the table stored in a layer.
*
* This is the most efficient index if you know that your property values
* are densly used small unsigned integers. This is usually the case for
* enums.
*
* Interally a simple vector<index_value> is used and the value is used
* as is to look up the index value in the vector.
*/
class value_index_small_uint {
layer_builder& m_builder;
std::vector<index_value> m_index;
public:
/**
* Construct index.
*
* @param builder The layer we are building containing the value
* table we are creating the index for.
*/
explicit value_index_small_uint(layer_builder& builder) :
m_builder(builder) {
}
/**
* Get the index value for the specified value. If the value was not in
* the table, it will be added.
*
* @param value The value to store.
* @returns The index value of they value.
*/
index_value operator()(const uint16_t value) {
if (value >= m_index.size()) {
m_index.resize(value + 1);
}
if (!m_index[value].valid()) {
m_index[value] = m_builder.add_value_without_dup_check(encoded_property_value{value});
}
return m_index[value];
}
}; // class value_index_small_uint
/**
* Used to store the mapping between property values and the index value
* in the table stored in a layer. Stores the values in the already
* encoded form. This is simpler to use than the value_index class, but
* has a higher overhead.
*
* @tparam TMap The map class to use (std::map, std::unordered_map or
* something compatible).
*/
template <template <typename...> class TMap>
class value_index_internal {
layer_builder& m_builder;
TMap<encoded_property_value, index_value> m_index;
public:
/**
* Construct index.
*
* @param builder The layer we are building containing the value
* table we are creating the index for.
*/
explicit value_index_internal(layer_builder& builder) :
m_builder(builder) {
}
/**
* Get the index value for the specified value. If the value was not in
* the table, it will be added.
*
* @param value The value to store.
* @returns The index value of they value.
*/
index_value operator()(const encoded_property_value& value) {
const auto it = m_index.find(value);
if (it == m_index.end()) {
const auto idx = m_builder.add_value_without_dup_check(value);
m_index.emplace(value, idx);
return idx;
}
return it->second;
}
}; // class value_index_internal
} // namespace vtzero
#endif // VTZERO_INDEX_HPP

484
include/vtzero/layer.hpp Normal file
View File

@ -0,0 +1,484 @@
#ifndef VTZERO_LAYER_HPP
#define VTZERO_LAYER_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file layer.hpp
*
* @brief Contains the layer class.
*/
#include "exception.hpp"
#include "feature.hpp"
#include "geometry.hpp"
#include "property_value.hpp"
#include "types.hpp"
#include <protozero/pbf_message.hpp>
#include <cstdint>
#include <iterator>
#include <vector>
namespace vtzero {
/**
* A layer according to spec 4.1. It contains a version, the extent,
* and a name. For the most efficient way to access the features in this
* layer call next_feature() until it returns an invalid feature:
*
* @code
* std::string data = ...;
* vector_tile tile{data};
* layer = tile.next_layer();
* while (auto feature = layer.next_feature()) {
* ...
* }
* @endcode
*
* If you know the ID of a feature, you can get it directly with
* @code
* layer.get_feature_by_id(7);
* @endcode
*/
class layer {
data_view m_data{};
uint32_t m_version = 1; // defaults to 1, see https://github.com/mapbox/vector-tile-spec/blob/master/2.1/vector_tile.proto#L55
uint32_t m_extent = 4096; // defaults to 4096, see https://github.com/mapbox/vector-tile-spec/blob/master/2.1/vector_tile.proto#L70
std::size_t m_num_features = 0;
data_view m_name{};
protozero::pbf_message<detail::pbf_layer> m_layer_reader{m_data};
mutable std::vector<data_view> m_key_table;
mutable std::vector<property_value> m_value_table;
mutable std::size_t m_key_table_size = 0;
mutable std::size_t m_value_table_size = 0;
void initialize_tables() const {
m_key_table.reserve(m_key_table_size);
m_key_table_size = 0;
m_value_table.reserve(m_value_table_size);
m_value_table_size = 0;
protozero::pbf_message<detail::pbf_layer> reader{m_data};
while (reader.next()) {
switch (reader.tag_and_type()) {
case protozero::tag_and_type(detail::pbf_layer::keys, protozero::pbf_wire_type::length_delimited):
m_key_table.push_back(reader.get_view());
break;
case protozero::tag_and_type(detail::pbf_layer::values, protozero::pbf_wire_type::length_delimited):
m_value_table.emplace_back(reader.get_view());
break;
default:
reader.skip(); // ignore unknown fields
}
}
}
public:
/**
* Construct an invalid layer object.
*/
layer() = default;
/**
* Construct a layer object. This is usually not something done in
* user code, because layers are created by the tile_iterator.
*
* @throws format_exception if the layer data is ill-formed.
* @throws version_exception if the layer contains an unsupported version
* number (only version 1 and 2 are supported)
* @throws any protozero exception if the protobuf encoding is invalid.
*/
explicit layer(const data_view data) :
m_data(data) {
protozero::pbf_message<detail::pbf_layer> reader{data};
while (reader.next()) {
switch (reader.tag_and_type()) {
case protozero::tag_and_type(detail::pbf_layer::version, protozero::pbf_wire_type::varint):
m_version = reader.get_uint32();
break;
case protozero::tag_and_type(detail::pbf_layer::name, protozero::pbf_wire_type::length_delimited):
m_name = reader.get_view();
break;
case protozero::tag_and_type(detail::pbf_layer::features, protozero::pbf_wire_type::length_delimited):
reader.skip(); // ignore features for now
++m_num_features;
break;
case protozero::tag_and_type(detail::pbf_layer::keys, protozero::pbf_wire_type::length_delimited):
reader.skip();
++m_key_table_size;
break;
case protozero::tag_and_type(detail::pbf_layer::values, protozero::pbf_wire_type::length_delimited):
reader.skip();
++m_value_table_size;
break;
case protozero::tag_and_type(detail::pbf_layer::extent, protozero::pbf_wire_type::varint):
m_extent = reader.get_uint32();
break;
default:
throw format_exception{"unknown field in layer (tag=" +
std::to_string(static_cast<uint32_t>(reader.tag())) +
", type=" +
std::to_string(static_cast<uint32_t>(reader.wire_type())) +
")"};
}
}
// This library can only handle version 1 and 2.
if (m_version < 1 || m_version > 2) {
throw version_exception{m_version};
}
// 4.1 "A layer MUST contain a name field."
if (m_name.data() == nullptr) {
throw format_exception{"missing name field in layer (spec 4.1)"};
}
}
/**
* Is this a valid layer? Valid layers are those not created from the
* default constructor.
*/
bool valid() const noexcept {
return m_data.data() != nullptr;
}
/**
* Is this a valid layer? Valid layers are those not created from the
* default constructor.
*/
explicit operator bool() const noexcept {
return valid();
}
/**
* Get a reference to the raw data this layer is created from.
*/
data_view data() const noexcept {
return m_data;
}
/**
* Return the name of the layer.
*
* @pre @code valid() @endcode
*/
data_view name() const noexcept {
vtzero_assert_in_noexcept_function(valid());
return m_name;
}
/**
* Return the version of this layer.
*
* @pre @code valid() @endcode
*/
std::uint32_t version() const noexcept {
vtzero_assert_in_noexcept_function(valid());
return m_version;
}
/**
* Return the extent of this layer.
*
* @pre @code valid() @endcode
*/
std::uint32_t extent() const noexcept {
vtzero_assert_in_noexcept_function(valid());
return m_extent;
}
/**
* Does this layer contain any features?
*
* Complexity: Constant.
*/
bool empty() const noexcept {
return m_num_features == 0;
}
/**
* The number of features in this layer.
*
* Complexity: Constant.
*/
std::size_t num_features() const noexcept {
return m_num_features;
}
/**
* Return a reference to the key table.
*
* Complexity: Amortized constant. First time the table is needed
* it needs to be created.
*
* @pre @code valid() @endcode
*/
const std::vector<data_view>& key_table() const {
vtzero_assert(valid());
if (m_key_table_size > 0) {
initialize_tables();
}
return m_key_table;
}
/**
* Return a reference to the value table.
*
* Complexity: Amortized constant. First time the table is needed
* it needs to be created.
*
* @pre @code valid() @endcode
*/
const std::vector<property_value>& value_table() const {
vtzero_assert(valid());
if (m_value_table_size > 0) {
initialize_tables();
}
return m_value_table;
}
/**
* Return the size of the key table. This returns the correct value
* whether the key table was already built or not.
*
* Complexity: Constant.
*
* @returns Size of the key table.
*/
std::size_t key_table_size() const noexcept {
return m_key_table_size > 0 ? m_key_table_size : m_key_table.size();
}
/**
* Return the size of the value table. This returns the correct value
* whether the value table was already built or not.
*
* Complexity: Constant.
*
* @returns Size of the value table.
*/
std::size_t value_table_size() const noexcept {
return m_value_table_size > 0 ? m_value_table_size : m_value_table.size();
}
/**
* Get the property key with the given index.
*
* Complexity: Amortized constant. First time the table is needed
* it needs to be created.
*
* @throws out_of_range_exception if the index is out of range.
* @pre @code valid() @endcode
*/
data_view key(index_value index) const {
vtzero_assert(valid());
const auto& table = key_table();
if (index.value() >= table.size()) {
throw out_of_range_exception{index.value()};
}
return table[index.value()];
}
/**
* Get the property value with the given index.
*
* Complexity: Amortized constant. First time the table is needed
* it needs to be created.
*
* @throws out_of_range_exception if the index is out of range.
* @pre @code valid() @endcode
*/
property_value value(index_value index) const {
vtzero_assert(valid());
const auto& table = value_table();
if (index.value() >= table.size()) {
throw out_of_range_exception{index.value()};
}
return table[index.value()];
}
/**
* Get the next feature in this layer.
*
* Note that the feature returned will internally contain a pointer to
* the layer it came from. The layer has to stay valid as long as the
* feature is used.
*
* Complexity: Constant.
*
* @returns The next feature or the invalid feature if there are no
* more features.
* @throws format_exception if the layer data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
* @pre @code valid() @endcode
*/
feature next_feature() {
vtzero_assert(valid());
const bool has_next = m_layer_reader.next(detail::pbf_layer::features,
protozero::pbf_wire_type::length_delimited);
return has_next ? feature{this, m_layer_reader.get_view()} : feature{};
}
/**
* Reset the feature iterator. The next time next_feature() is called,
* it will begin from the first feature again.
*
* Complexity: Constant.
*
* @pre @code valid() @endcode
*/
void reset_feature() noexcept {
vtzero_assert_in_noexcept_function(valid());
m_layer_reader = protozero::pbf_message<detail::pbf_layer>{m_data};
}
/**
* Call a function for each feature in this layer.
*
* @tparam The type of the function. It must take a single argument
* of type feature&& and return a bool. If the function returns
* false, the iteration will be stopped.
* @param func The function to call.
* @returns true if the iteration was completed and false otherwise.
* @pre @code valid() @endcode
*/
template <typename TFunc>
bool for_each_feature(TFunc&& func) const {
vtzero_assert(valid());
protozero::pbf_message<detail::pbf_layer> layer_reader{m_data};
while (layer_reader.next(detail::pbf_layer::features,
protozero::pbf_wire_type::length_delimited)) {
if (!std::forward<TFunc>(func)(feature{this, layer_reader.get_view()})) {
return false;
}
}
return true;
}
/**
* Get the feature with the specified ID. If there are several features
* with the same ID, it is undefined which one you'll get.
*
* Note that the feature returned will internally contain a pointer to
* the layer it came from. The layer has to stay valid as long as the
* feature is used.
*
* Complexity: Linear in the number of features.
*
* @param id The ID to look for.
* @returns Feature with the specified ID or the invalid feature if
* there is no feature with this ID.
* @throws format_exception if the layer data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
* @pre @code valid() @endcode
*/
feature get_feature_by_id(uint64_t id) const {
vtzero_assert(valid());
protozero::pbf_message<detail::pbf_layer> layer_reader{m_data};
while (layer_reader.next(detail::pbf_layer::features, protozero::pbf_wire_type::length_delimited)) {
const auto feature_data = layer_reader.get_view();
protozero::pbf_message<detail::pbf_feature> feature_reader{feature_data};
if (feature_reader.next(detail::pbf_feature::id, protozero::pbf_wire_type::varint)) {
if (feature_reader.get_uint64() == id) {
return feature{this, feature_data};
}
}
}
return feature{};
}
}; // class layer
inline property feature::next_property() {
const auto idxs = next_property_indexes();
property p{};
if (idxs.valid()) {
p = {m_layer->key(idxs.key()),
m_layer->value(idxs.value())};
}
return p;
}
inline index_value_pair feature::next_property_indexes() {
vtzero_assert(valid());
if (m_property_iterator == m_properties.end()) {
return {};
}
const auto ki = *m_property_iterator++;
if (!index_value{ki}.valid()) {
throw out_of_range_exception{ki};
}
assert(m_property_iterator != m_properties.end());
const auto vi = *m_property_iterator++;
if (!index_value{vi}.valid()) {
throw out_of_range_exception{vi};
}
if (ki >= m_layer->key_table_size()) {
throw out_of_range_exception{ki};
}
if (vi >= m_layer->value_table_size()) {
throw out_of_range_exception{vi};
}
return {ki, vi};
}
template <typename TFunc>
bool feature::for_each_property(TFunc&& func) const {
vtzero_assert(valid());
for (auto it = m_properties.begin(); it != m_properties.end();) {
const uint32_t ki = *it++;
if (!index_value{ki}.valid()) {
throw out_of_range_exception{ki};
}
assert(m_property_iterator != m_properties.end());
const uint32_t vi = *it++;
if (!index_value{vi}.valid()) {
throw out_of_range_exception{vi};
}
if (!std::forward<TFunc>(func)(property{m_layer->key(ki), m_layer->value(vi)})) {
return false;
}
}
return true;
}
} // namespace vtzero
#endif // VTZERO_LAYER_HPP

64
include/vtzero/output.hpp Normal file
View File

@ -0,0 +1,64 @@
#ifndef VTZERO_OUTPUT_HPP
#define VTZERO_OUTPUT_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file output.hpp
*
* @brief Contains overloads of operator<< for basic vtzero types.
*/
#include <vtzero/geometry.hpp>
#include <vtzero/types.hpp>
#include <iosfwd>
namespace vtzero {
/// Overload of the << operator for GeomType
template <typename TChar, typename TTraits>
std::basic_ostream<TChar, TTraits>& operator<<(std::basic_ostream<TChar, TTraits>& out, const GeomType type) {
return out << geom_type_name(type);
}
/// Overload of the << operator for property_value_type
template <typename TChar, typename TTraits>
std::basic_ostream<TChar, TTraits>& operator<<(std::basic_ostream<TChar, TTraits>& out, const property_value_type type) {
return out << property_value_type_name(type);
}
/// Overload of the << operator for index_value
template <typename TChar, typename TTraits>
std::basic_ostream<TChar, TTraits>& operator<<(std::basic_ostream<TChar, TTraits>& out, const index_value index) {
if (index.valid()) {
return out << index.value();
}
return out << "invalid";
}
/// Overload of the << operator for index_value_pair
template <typename TChar, typename TTraits>
std::basic_ostream<TChar, TTraits>& operator<<(std::basic_ostream<TChar, TTraits>& out, const index_value_pair index_pair) {
if (index_pair.valid()) {
return out << '[' << index_pair.key() << ',' << index_pair.value() << ']';
}
return out << "invalid";
}
/// Overload of the << operator for points
template <typename TChar, typename TTraits>
std::basic_ostream<TChar, TTraits>& operator<<(std::basic_ostream<TChar, TTraits>& out, const point p) {
return out << '(' << p.x << ',' << p.y << ')';
}
} // namespace vtzero
#endif // VTZERO_OUTPUT_HPP

View File

@ -0,0 +1,89 @@
#ifndef VTZERO_PROPERTY_HPP
#define VTZERO_PROPERTY_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file property.hpp
*
* @brief Contains the property class.
*/
#include "property_value.hpp"
#include "types.hpp"
namespace vtzero {
/**
* A view of a vector tile property (key and value).
*
* Doesn't hold any data itself, just views of the key and value.
*/
class property {
data_view m_key{};
property_value m_value{};
public:
/**
* The default constructor creates an invalid (empty) property.
*/
constexpr property() noexcept = default;
/**
* Create a (valid) property from a key and value.
*/
constexpr property(const data_view key, const property_value value) noexcept :
m_key(key),
m_value(value) {
}
/**
* Is this a valid property? Properties are valid if they were
* constructed using the non-default constructor.
*/
constexpr bool valid() const noexcept {
return m_key.data() != nullptr;
}
/**
* Is this a valid property? Properties are valid if they were
* constructed using the non-default constructor.
*/
explicit constexpr operator bool() const noexcept {
return valid();
}
/// Return the property key.
constexpr data_view key() const noexcept {
return m_key;
}
/// Return the property value.
constexpr property_value value() const noexcept {
return m_value;
}
}; // class property
/// properties are equal if they contain the same key & value data.
inline constexpr bool operator==(const property& lhs, const property& rhs) noexcept {
return lhs.key() == rhs.key() && lhs.value() == rhs.value();
}
/// properties are unequal if they do not contain them same key and value data.
inline constexpr bool operator!=(const property& lhs, const property& rhs) noexcept {
return !(lhs == rhs);
}
} // namespace vtzero
#endif // VTZERO_PROPERTY_HPP

View File

@ -0,0 +1,103 @@
#ifndef VTZERO_PROPERTY_MAPPER_HPP
#define VTZERO_PROPERTY_MAPPER_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file property_mapper.hpp
*
* @brief Contains the property_mapper class.
*/
#include "builder.hpp"
#include "layer.hpp"
#include <vector>
namespace vtzero {
/**
* Establishes a mapping between index values of properties of an existing
* layer and a new layer. Can be used when copying some features from an
* existing layer to a new layer.
*/
class property_mapper {
const layer& m_layer;
layer_builder& m_layer_builder;
std::vector<index_value> m_keys;
std::vector<index_value> m_values;
public:
/**
* Construct the mapping between the specified layers
*
* @param layer The existing layer from which (some) properties will
* be copied.
* @param layer_builder The new layer that is being created.
*/
property_mapper(const layer& layer, layer_builder& layer_builder) :
m_layer(layer),
m_layer_builder(layer_builder) {
m_keys.resize(layer.key_table().size());
m_values.resize(layer.value_table().size());
}
/**
* Map the value index of a key.
*
* @param index The value index of the key in the existing table.
* @returns The value index of the same key in the new table.
*/
index_value map_key(index_value index) {
auto& k = m_keys[index.value()];
if (!k.valid()) {
k = m_layer_builder.add_key_without_dup_check(m_layer.key(index));
}
return k;
}
/**
* Map the value index of a value.
*
* @param index The value index of the value in the existing table.
* @returns The value index of the same value in the new table.
*/
index_value map_value(index_value index) {
auto& v = m_values[index.value()];
if (!v.valid()) {
v = m_layer_builder.add_value_without_dup_check(m_layer.value(index));
}
return v;
}
/**
* Map the value indexes of a key/value pair.
*
* @param idxs The value indexes of the key/value pair in the existing
* table.
* @returns The value indexes of the same key/value pair in the new
* table.
*/
index_value_pair operator()(index_value_pair idxs) {
return {map_key(idxs.key()), map_value(idxs.value())};
}
}; // class property_mapper
} // namespace vtzero
#endif // VTZERO_PROPERTY_MAPPER_HPP

View File

@ -0,0 +1,398 @@
#ifndef VTZERO_PROPERTY_VALUE_HPP
#define VTZERO_PROPERTY_VALUE_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file property_value.hpp
*
* @brief Contains the property_value class.
*/
#include "exception.hpp"
#include "types.hpp"
#include <protozero/pbf_message.hpp>
#include <array>
#include <cstdint>
#include <cstring>
#include <utility>
namespace vtzero {
/**
* A view of a vector tile property value.
*
* Doesn't hold any data itself.
*/
class property_value {
data_view m_value{};
static bool check_tag_and_type(protozero::pbf_tag_type tag, protozero::pbf_wire_type type) noexcept {
static constexpr const std::array<protozero::pbf_wire_type, 7> types{{
string_value_type::wire_type,
float_value_type::wire_type,
double_value_type::wire_type,
int_value_type::wire_type,
uint_value_type::wire_type,
sint_value_type::wire_type,
bool_value_type::wire_type,
}};
if (tag < 1 || tag > types.size()) {
return false;
}
return types[tag - 1] == type; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
}
static data_view get_value_impl(protozero::pbf_message<detail::pbf_value>& value_message, string_value_type /* dummy */) {
return value_message.get_view();
}
static float get_value_impl(protozero::pbf_message<detail::pbf_value>& value_message, float_value_type /* dummy */) {
return value_message.get_float();
}
static double get_value_impl(protozero::pbf_message<detail::pbf_value>& value_message, double_value_type /* dummy */) {
return value_message.get_double();
}
static int64_t get_value_impl(protozero::pbf_message<detail::pbf_value>& value_message, int_value_type /* dummy */) {
return value_message.get_int64();
}
static uint64_t get_value_impl(protozero::pbf_message<detail::pbf_value>& value_message, uint_value_type /* dummy */) {
return value_message.get_uint64();
}
static int64_t get_value_impl(protozero::pbf_message<detail::pbf_value>& value_message, sint_value_type /* dummy */) {
return value_message.get_sint64();
}
static bool get_value_impl(protozero::pbf_message<detail::pbf_value>& value_message, bool_value_type /* dummy */) {
return value_message.get_bool();
}
template <typename T>
typename T::type get_value() const {
vtzero_assert(valid());
protozero::pbf_message<detail::pbf_value> value_message{m_value};
typename T::type result{};
bool has_result = false;
while (value_message.next(T::pvtype, T::wire_type)) {
result = get_value_impl(value_message, T());
has_result = true;
}
if (has_result) {
return result;
}
throw type_exception{};
}
public:
/**
* The default constructor creates an invalid (empty) property_value.
*/
constexpr property_value() noexcept = default;
/**
* Create a (valid) property_value from a data_view.
*/
explicit constexpr property_value(const data_view value) noexcept :
m_value(value) {
}
/**
* Is this a valid property value? Property values are valid if they
* were constructed using the non-default constructor.
*/
constexpr bool valid() const noexcept {
return m_value.data() != nullptr;
}
/**
* Get the type of this property.
*
* @pre @code valid() @endcode
* @throws format_exception if the encoding is invalid
*/
property_value_type type() const {
vtzero_assert(valid());
protozero::pbf_message<detail::pbf_value> value_message{m_value};
if (value_message.next()) {
const auto tag_val = static_cast<protozero::pbf_tag_type>(value_message.tag());
if (!check_tag_and_type(tag_val, value_message.wire_type())) {
throw format_exception{"illegal property value type"};
}
return value_message.tag();
}
throw format_exception{"missing tag value"};
}
/**
* Get the internal data_view this object was constructed with.
*/
constexpr data_view data() const noexcept {
return m_value;
}
/**
* Get string value of this object.
*
* @pre @code valid() @endcode
* @throws type_exception if the type of this property value is
* something other than string.
*/
data_view string_value() const {
return get_value<string_value_type>();
}
/**
* Get float value of this object.
*
* @pre @code valid() @endcode
* @throws type_exception if the type of this property value is
* something other than float.
*/
float float_value() const {
return get_value<float_value_type>();
}
/**
* Get double value of this object.
*
* @pre @code valid() @endcode
* @throws type_exception if the type of this property value is
* something other than double.
*/
double double_value() const {
return get_value<double_value_type>();
}
/**
* Get int value of this object.
*
* @pre @code valid() @endcode
* @throws type_exception if the type of this property value is
* something other than int.
*/
std::int64_t int_value() const {
return get_value<int_value_type>();
}
/**
* Get uint value of this object.
*
* @pre @code valid() @endcode
* @throws type_exception if the type of this property value is
* something other than uint.
*/
std::uint64_t uint_value() const {
return get_value<uint_value_type>();
}
/**
* Get sint value of this object.
*
* @pre @code valid() @endcode
* @throws type_exception if the type of this property value is
* something other than sint.
*/
std::int64_t sint_value() const {
return get_value<sint_value_type>();
}
/**
* Get bool value of this object.
*
* @pre @code valid() @endcode
* @throws type_exception if the type of this property value is
* something other than bool.
*/
bool bool_value() const {
return get_value<bool_value_type>();
}
}; // class property_value
/// property_values are equal if they contain the same data.
inline constexpr bool operator==(const property_value lhs, const property_value rhs) noexcept {
return lhs.data() == rhs.data();
}
/// property_values are unequal if they do not contain the same data.
inline constexpr bool operator!=(const property_value lhs, const property_value rhs) noexcept {
return lhs.data() != rhs.data();
}
/// property_values are ordered in the same way as the underlying data
inline bool operator<(const property_value lhs, const property_value rhs) noexcept {
return lhs.data() < rhs.data();
}
/// property_values are ordered in the same way as the underlying data
inline bool operator<=(const property_value lhs, const property_value rhs) noexcept {
return lhs.data() <= rhs.data();
}
/// property_values are ordered in the same way as the underlying data
inline bool operator>(const property_value lhs, const property_value rhs) noexcept {
return lhs.data() > rhs.data();
}
/// property_values are ordered in the same way as the underlying data
inline bool operator>=(const property_value lhs, const property_value rhs) noexcept {
return lhs.data() >= rhs.data();
}
/**
* Apply the value to a visitor.
*
* The visitor must have an overloaded call operator taking a single
* argument of each of the types data_view, float, double, int64_t,
* uint64_t, and bool. All call operators must return the same type which
* will be the return type of this function.
*/
template <typename V>
decltype(std::declval<V>()(data_view{})) apply_visitor(V&& visitor, const property_value value) {
switch (value.type()) {
case property_value_type::string_value:
return std::forward<V>(visitor)(value.string_value());
case property_value_type::float_value:
return std::forward<V>(visitor)(value.float_value());
case property_value_type::double_value:
return std::forward<V>(visitor)(value.double_value());
case property_value_type::int_value:
return std::forward<V>(visitor)(value.int_value());
case property_value_type::uint_value:
return std::forward<V>(visitor)(value.uint_value());
case property_value_type::sint_value:
return std::forward<V>(visitor)(value.sint_value());
default: // case property_value_type::bool_value:
return std::forward<V>(visitor)(value.bool_value());
}
}
namespace detail {
template <typename TVariant, typename TMapping>
struct convert_visitor {
TVariant operator()(data_view value) const {
return TVariant(typename TMapping::string_type(value));
}
TVariant operator()(float value) const {
return TVariant(typename TMapping::float_type(value));
}
TVariant operator()(double value) const {
return TVariant(typename TMapping::double_type(value));
}
TVariant operator()(int64_t value) const {
return TVariant(typename TMapping::int_type(value));
}
TVariant operator()(uint64_t value) const {
return TVariant(typename TMapping::uint_type(value));
}
TVariant operator()(bool value) const {
return TVariant(typename TMapping::bool_type(value));
}
}; // struct convert_visitor
} // namespace detail
/**
* Default mapping between the different types of a property_value to
* the types needed for a variant. Derive from this class, overwrite
* the types you want and use that class as second template parameter
* in the convert_property_value class.
*/
struct property_value_mapping {
/// mapping for string type
using string_type = std::string;
/// mapping for float type
using float_type = float;
/// mapping for double type
using double_type = double;
/// mapping for int type
using int_type = int64_t;
/// mapping for uint type
using uint_type = uint64_t;
/// mapping for bool type
using bool_type = bool;
}; // struct property_value_mapping
/**
* Convert a property_value to a different (usually variant-based)
* class.
*
* Usage: If you have a variant type like
*
* @code
* using variant_type = boost::variant<std::string, float, double, int64_t, uint64_t, bool>;
* @endcode
*
* you can use
* @code
* property_value x = ...;
* auto v = convert_property_value<variant_type>(x);
* @endcode
*
* to convert the data.
*
* Usually your variant type has to support all of the following types:
* std::string, float, double, int64_t, uint64_t, and bool. If your type
* doesn't, you can add a second template parameter with a struct
* containing the mapping between the vtzero types and your types:
*
* @code
* struct mapping : vtzero::property_value_mapping {
* using float_type = double; // convert all floats to doubles
* using bool_type = int; // convert all bools to ints
* // use default types for the rest
* // see the class vtzero::property_value_mapping for the defaults
* };
* property_value x = ...;
* auto v = convert_property_value<variant_type, mapping>(x);
* @endcode
*
* @tparam TVariant The variant type to convert to.
* @tparam TMapping A struct derived from property_value_mapping with the
* mapping for the types.
* @param value The property value to convert.
*
*/
template <typename TVariant, typename TMapping = property_value_mapping>
TVariant convert_property_value(const property_value value) {
return apply_visitor(detail::convert_visitor<TVariant, TMapping>{}, value);
}
} // namespace vtzero
#endif // VTZERO_PROPERTY_VALUE_HPP

429
include/vtzero/types.hpp Normal file
View File

@ -0,0 +1,429 @@
#ifndef VTZERO_TYPES_HPP
#define VTZERO_TYPES_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
#include <protozero/pbf_reader.hpp>
#include <cassert>
// @cond internal
// Wrappers for assert() used for testing
#ifndef vtzero_assert
# define vtzero_assert(x) assert(x)
#endif
#ifndef vtzero_assert_in_noexcept_function
# define vtzero_assert_in_noexcept_function(x) assert(x)
#endif
// @endcond
/**
* @file types.hpp
*
* @brief Contains the declaration of low-level types.
*/
/**
* @brief All parts of the vtzero header-only library are in this namespace.
*/
namespace vtzero {
/**
* Using data_view class from protozero. See the protozero documentation
* on how to change this to use a different implementation.
* https://github.com/mapbox/protozero/blob/master/doc/advanced.md#protozero_use_view
*/
using data_view = protozero::data_view;
// based on https://github.com/mapbox/vector-tile-spec/blob/master/2.1/vector_tile.proto
/// The geometry type as specified in the vector tile spec
enum class GeomType {
UNKNOWN = 0,
POINT = 1,
LINESTRING = 2,
POLYGON = 3
};
/**
* Return the name of a GeomType (for debug output etc.)
*/
inline const char* geom_type_name(GeomType type) noexcept {
static const char* names[] = {
"unknown", "point", "linestring", "polygon"
};
return names[static_cast<int>(type)]; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
}
/// The property value type as specified in the vector tile spec
enum class property_value_type : protozero::pbf_tag_type {
string_value = 1,
float_value = 2,
double_value = 3,
int_value = 4,
uint_value = 5,
sint_value = 6,
bool_value = 7
};
/**
* Return the name of a property value type (for debug output etc.)
*/
inline const char* property_value_type_name(property_value_type type) noexcept {
static const char* names[] = {
"", "string", "float", "double", "int", "uint", "sint", "bool"
};
return names[static_cast<int>(type)]; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
}
namespace detail {
enum class pbf_tile : protozero::pbf_tag_type {
layers = 3
};
enum class pbf_layer : protozero::pbf_tag_type {
name = 1,
features = 2,
keys = 3,
values = 4,
extent = 5,
version = 15
};
enum class pbf_feature : protozero::pbf_tag_type {
id = 1,
tags = 2,
type = 3,
geometry = 4
};
using pbf_value = property_value_type;
} // namespace detail
/// property value type holding a reference to a string
struct string_value_type {
/// the underlying storage type
using type = data_view;
/// @cond internal
constexpr static const property_value_type pvtype = property_value_type::string_value;
constexpr static const protozero::pbf_wire_type wire_type = protozero::pbf_wire_type::length_delimited;
/// @endcond
/// value
data_view value{};
/// Construct empty string_value_type
constexpr string_value_type() noexcept = default;
/// Construct string_value_type
explicit constexpr string_value_type(data_view v) :
value(v) {
}
}; // struct string_value_type
/// property value type holding a float
struct float_value_type {
/// the underlying storage type
using type = float;
/// @cond internal
constexpr static const property_value_type pvtype = property_value_type::float_value;
constexpr static const protozero::pbf_wire_type wire_type = protozero::pbf_wire_type::fixed32;
/// @endcond
/// value
float value = 0.0f;
/// Construct float_value_type with value 0.0
constexpr float_value_type() noexcept = default;
/// Construct float_value_type
explicit constexpr float_value_type(float v) :
value(v) {
}
}; // struct float_value_type
/// property value type holding a double
struct double_value_type {
/// the underlying storage type
using type = double;
/// @cond internal
constexpr static const property_value_type pvtype = property_value_type::double_value;
constexpr static const protozero::pbf_wire_type wire_type = protozero::pbf_wire_type::fixed64;
/// @endcond
/// value
double value = 0.0;
/// Construct double_value_type with value 0.0
constexpr double_value_type() noexcept = default;
/// Construct double_value_type
explicit constexpr double_value_type(double v) :
value(v) {
}
}; // struct double_value_type
/// property value type holding an int
struct int_value_type {
/// the underlying storage type
using type = int64_t;
/// @cond internal
constexpr static const property_value_type pvtype = property_value_type::int_value;
constexpr static const protozero::pbf_wire_type wire_type = protozero::pbf_wire_type::varint;
/// @endcond
/// value
int64_t value = 0;
/// Construct int_value_type with value 0
constexpr int_value_type() noexcept = default;
/// Construct int_value_type
explicit constexpr int_value_type(int64_t v) :
value(v) {
}
}; // struct int_value_type
/// property value type holding a uint
struct uint_value_type {
/// the underlying storage type
using type = uint64_t;
/// @cond internal
constexpr static const property_value_type pvtype = property_value_type::uint_value;
constexpr static const protozero::pbf_wire_type wire_type = protozero::pbf_wire_type::varint;
/// @endcond
/// value
uint64_t value = 0;
/// Construct uint_value_type with value 0
constexpr uint_value_type() noexcept = default;
/// Construct uint_value_type
explicit constexpr uint_value_type(uint64_t v) :
value(v) {
}
}; // struct uint_value_type
/// property value type holding an sint
struct sint_value_type {
/// the underlying storage type
using type = int64_t;
/// @cond internal
constexpr static const property_value_type pvtype = property_value_type::sint_value;
constexpr static const protozero::pbf_wire_type wire_type = protozero::pbf_wire_type::varint;
/// @endcond
/// value
int64_t value = 0;
/// Construct sint_value_type with value 0
constexpr sint_value_type() noexcept = default;
/// Construct sint_value_type
explicit constexpr sint_value_type(int64_t v) :
value(v) {
}
}; // struct sint_value_type
/// property value type holding a bool
struct bool_value_type {
/// the underlying storage type
using type = bool;
/// @cond internal
constexpr static const property_value_type pvtype = property_value_type::bool_value;
constexpr static const protozero::pbf_wire_type wire_type = protozero::pbf_wire_type::varint;
/// @endcond
/// value
bool value = false;
/// Construct bool_value_type with false value
constexpr bool_value_type() noexcept = default;
/// Construct bool_value_type
explicit constexpr bool_value_type(bool v) :
value(v) {
}
}; // struct bool_value_type
/**
* This class wraps the uint32_t used for looking up keys/values in the
* key/values tables.
*/
class index_value {
static const uint32_t invalid_value = std::numeric_limits<uint32_t>::max();
uint32_t m_value = invalid_value;
public:
/// Default construct to an invalid value.
constexpr index_value() noexcept = default;
/// Construct with the given value.
constexpr index_value(uint32_t value) noexcept : // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
m_value(value) {
}
/**
* Is this index value valid? Index values are valid if they have
* been initialized with something other than the default constructor.
*/
constexpr bool valid() const noexcept {
return m_value != invalid_value;
}
/**
* Get the value.
*
* @pre Must be valid.
*/
uint32_t value() const noexcept {
vtzero_assert_in_noexcept_function(valid());
return m_value;
}
}; // class index_value
/// Index values are equal if their values are.
inline bool operator==(const index_value lhs, const index_value rhs) noexcept {
return lhs.value() == rhs.value();
}
/// Index values are not equal if their values are not equal.
inline bool operator!=(const index_value lhs, const index_value rhs) noexcept {
return lhs.value() != rhs.value();
}
/**
* This class holds two index_values, one for a key and one for a value.
*/
class index_value_pair {
index_value m_key{};
index_value m_value{};
public:
/// Default construct to an invalid value.
constexpr index_value_pair() noexcept = default;
/// Construct with the given values.
constexpr index_value_pair(index_value key, index_value value) noexcept :
m_key(key),
m_value(value) {
}
/**
* Is this index value pair valid? Index values pairs are valid if
* both the key and the value index value are valid.
*/
constexpr bool valid() const noexcept {
return m_key.valid() && m_value.valid();
}
/**
* Is this index value pair valid? Index values pairs are valid if
* both the key and the value index value are valid.
*/
constexpr explicit operator bool() const noexcept {
return valid();
}
/// Get the key index value.
constexpr index_value key() const noexcept {
return m_key;
}
/// Get the value index value.
constexpr index_value value() const noexcept {
return m_value;
}
}; // class index_value_pair
/**
* The geometry class holds a geometry type and a reference to the data
* defining this geometry. To actually decode these geometries, use the
* decode_point_geometry(), decode_linestring_geometry(), and
* decode_polygon_geometry() classes.
*/
class geometry {
data_view m_data{};
GeomType m_type = GeomType::UNKNOWN;
public:
/**
* A forward iterator yielding 32bit unsigned integers with the
* geometry encoded according to spec 4.3.
*/
using const_iterator = protozero::pbf_reader::const_uint32_iterator;
/// Default construct to an invalid value.
constexpr geometry() noexcept = default;
/// Construct with the given values.
constexpr geometry(data_view data, GeomType type) noexcept :
m_data(data),
m_type(type) {
}
/// The data of this geometry
constexpr data_view data() const noexcept {
return m_data;
}
/// The type of this geometry
constexpr GeomType type() const noexcept {
return m_type;
}
/// Return iterator to the beginning of the data.
const_iterator begin() const noexcept {
return {m_data.data(), m_data.data() + m_data.size()};
}
/// Return iterator to one past the end of the data.
const_iterator end() const noexcept {
return {m_data.data() + m_data.size(), m_data.data() + m_data.size()};
}
}; // class geometry
} // namespace vtzero
#endif // VTZERO_TYPES_HPP

View File

@ -0,0 +1,290 @@
#ifndef VTZERO_VECTOR_TILE_HPP
#define VTZERO_VECTOR_TILE_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file vector_tile.hpp
*
* @brief Contains the vector_tile class.
*/
#include "exception.hpp"
#include "layer.hpp"
#include "types.hpp"
#include <protozero/pbf_message.hpp>
#include <cstddef>
#include <cstring>
#include <iterator>
#include <string>
namespace vtzero {
/**
* A vector tile is basically nothing more than an ordered collection
* of named layers. For the most efficient way to access the layers,
* call next_layer() until it returns an invalid layer:
*
* @code
* std::string data = ...;
* vector_tile tile{data};
* while (auto layer = tile.next_layer()) {
* ...
* }
* @endcode
*
* If you know the index of the layer, you can get it directly with
* @code
* tile.get_layer(4);
* @endcode
*
* You can also access the layer by name:
* @code
* tile.get_layer_by_name("foobar");
* @endcode
*/
class vector_tile {
data_view m_data;
protozero::pbf_message<detail::pbf_tile> m_tile_reader;
public:
/**
* Construct the vector_tile from a data_view. The vector_tile object
* will keep a reference to the data referenced by the data_view. No
* copy of the data is created.
*/
explicit vector_tile(const data_view data) noexcept :
m_data(data),
m_tile_reader(m_data) {
}
/**
* Construct the vector_tile from a string. The vector_tile object
* will keep a reference to the data referenced by the string. No
* copy of the data is created.
*/
explicit vector_tile(const std::string& data) noexcept :
m_data(data.data(), data.size()),
m_tile_reader(m_data) {
}
/**
* Construct the vector_tile from a ptr and size. The vector_tile
* object will keep a reference to the data. No copy of the data is
* created.
*/
vector_tile(const char* data, std::size_t size) noexcept :
m_data(data, size),
m_tile_reader(m_data) {
}
/**
* Is this vector tile empty?
*
* @returns true if there are no layers in this vector tile, false
* otherwise
* Complexity: Constant.
*/
bool empty() const noexcept {
return m_data.empty();
}
/**
* Return the number of layers in this tile.
*
* Complexity: Linear in the number of layers.
*
* @returns the number of layers in this tile
* @throws any protozero exception if the protobuf encoding is invalid.
*/
std::size_t count_layers() const {
std::size_t size = 0;
protozero::pbf_message<detail::pbf_tile> tile_reader{m_data};
while (tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited)) {
tile_reader.skip();
++size;
}
return size;
}
/**
* Get the next layer in this tile.
*
* Complexity: Constant.
*
* @returns layer The next layer or the invalid layer if there are no
* more layers.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer next_layer() {
const bool has_next = m_tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited);
return has_next ? layer{m_tile_reader.get_view()} : layer{};
}
/**
* Reset the layer iterator. The next time next_layer() is called,
* it will begin from the first layer again.
*
* Complexity: Constant.
*/
void reset_layer() noexcept {
m_tile_reader = protozero::pbf_message<detail::pbf_tile>{m_data};
}
/**
* Call a function for each layer in this tile.
*
* @tparam The type of the function. It must take a single argument
* of type layer&& and return a bool. If the function returns
* false, the iteration will be stopped.
* @param func The function to call.
* @returns true if the iteration was completed and false otherwise.
*/
template <typename TFunc>
bool for_each_layer(TFunc&& func) const {
protozero::pbf_message<detail::pbf_tile> tile_reader{m_data};
while (tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited)) {
if (!std::forward<TFunc>(func)(layer{tile_reader.get_view()})) {
return false;
}
}
return true;
}
/**
* Returns the layer with the specified zero-based index.
*
* Complexity: Linear in the number of layers.
*
* @returns The specified layer or the invalid layer if index is
* larger than the number of layers.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer get_layer(std::size_t index) const {
protozero::pbf_message<detail::pbf_tile> tile_reader{m_data};
while (tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited)) {
if (index == 0) {
return layer{tile_reader.get_view()};
}
tile_reader.skip();
--index;
}
return layer{};
}
/**
* Returns the layer with the specified name.
*
* Complexity: Linear in the number of layers.
*
* If there are several layers with the same name (which is against
* the spec 4.1 "A Vector Tile MUST NOT contain two or more layers
* whose name values are byte-for-byte identical.") it is unspecified
* which will be returned.
*
* @returns The specified layer or the invalid layer if there is no
* layer with this name.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer get_layer_by_name(const data_view name) const {
protozero::pbf_message<detail::pbf_tile> tile_reader{m_data};
while (tile_reader.next(detail::pbf_tile::layers,
protozero::pbf_wire_type::length_delimited)) {
const auto layer_data = tile_reader.get_view();
protozero::pbf_message<detail::pbf_layer> layer_reader{layer_data};
if (layer_reader.next(detail::pbf_layer::name,
protozero::pbf_wire_type::length_delimited)) {
if (layer_reader.get_view() == name) {
return layer{layer_data};
}
} else {
// 4.1 "A layer MUST contain a name field."
throw format_exception{"missing name in layer (spec 4.1)"};
}
}
return layer{};
}
/**
* Returns the layer with the specified name.
*
* Complexity: Linear in the number of layers.
*
* If there are several layers with the same name (which is against
* the spec 4.1 "A Vector Tile MUST NOT contain two or more layers
* whose name values are byte-for-byte identical.") it is unspecified
* which will be returned.
*
* @returns The specified layer or the invalid layer if there is no
* layer with this name.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer get_layer_by_name(const std::string& name) const {
return get_layer_by_name(data_view{name.data(), name.size()});
}
/**
* Returns the layer with the specified name.
*
* Complexity: Linear in the number of layers.
*
* If there are several layers with the same name (which is against
* the spec 4.1 "A Vector Tile MUST NOT contain two or more layers
* whose name values are byte-for-byte identical.") it is unspecified
* which will be returned.
*
* @returns The specified layer or the invalid layer if there is no
* layer with this name.
* @throws format_exception if the tile data is ill-formed.
* @throws any protozero exception if the protobuf encoding is invalid.
*/
layer get_layer_by_name(const char* name) const {
return get_layer_by_name(data_view{name, std::strlen(name)});
}
}; // class vector_tile
/**
* Helper function to determine whether some data could represent a
* vector tile. This takes advantage of the fact that the first byte of
* a vector tile is always 0x1a. It can't be 100% reliable though, because
* some other data could still contain that byte.
*
* @returns false if this is definitely no vector tile
* true if this could be a vector tile
*/
inline bool is_vector_tile(const data_view data) noexcept {
return !data.empty() && data.data()[0] == 0x1a;
}
} // namespace vtzero
#endif // VTZERO_VECTOR_TILE_HPP

View File

@ -0,0 +1,36 @@
#ifndef VTZERO_VERSION_HPP
#define VTZERO_VERSION_HPP
/*****************************************************************************
vtzero - Tiny and fast vector tile decoder and encoder in C++.
This file is from https://github.com/mapbox/vtzero where you can find more
documentation.
*****************************************************************************/
/**
* @file version.hpp
*
* @brief Contains the version number macros for the vtzero library.
*/
/// The major version number
#define VTZERO_VERSION_MAJOR 1
/// The minor version number
#define VTZERO_VERSION_MINOR 0
/// The patch number
#define VTZERO_VERSION_PATCH 1
/// The complete version number
#define VTZERO_VERSION_CODE \
(VTZERO_VERSION_MAJOR * 10000 + VTZERO_VERSION_MINOR * 100 + \
VTZERO_VERSION_PATCH)
/// Version number as string
#define VTZERO_VERSION_STRING "1.0.1"
#endif // VTZERO_VERSION_HPP

81
test/CMakeLists.txt Normal file
View File

@ -0,0 +1,81 @@
#-----------------------------------------------------------------------------
#
# CMake config
#
# vtzero tests
#
#-----------------------------------------------------------------------------
if(Boost_FOUND)
message(STATUS "Boost library found: enable testing with boost::variant")
add_definitions(-DVTZERO_TEST_WITH_VARIANT)
include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
else()
message(STATUS "Boost library not found: disable testing with boost::variant")
endif()
include_directories(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/catch")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include")
set(TEST_SOURCES builder
builder_linestring
builder_point
builder_polygon
exceptions
feature
geometry
geometry_linestring
geometry_point
geometry_polygon
index
layer
output
point
property_map
property_value
types
vector_tile)
string(REGEX REPLACE "([^;]+)" "t/test_\\1.cpp" _test_sources "${TEST_SOURCES}")
add_executable(unit-tests test_main.cpp ${_test_sources})
add_executable(fixture-tests test_main.cpp fixture_tests.cpp)
add_test(NAME unit-tests
COMMAND unit-tests
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(_fixtures ${MVT_FIXTURES}/fixtures)
if(EXISTS ${_fixtures})
message(STATUS "Found test fixtures. Enabled mvt fixture tests.")
add_test(NAME fixture-tests
COMMAND fixture-tests)
set_tests_properties(fixture-tests PROPERTIES
ENVIRONMENT "FIXTURES_DIR=${_fixtures}")
if(NOT WIN32)
set(_real_world_dir ${MVT_FIXTURES}/real-world)
file(GLOB real_world ${_real_world_dir}/bangkok/*
${_real_world_dir}/chicago/*
${_real_world_dir}/nepal/*
${_real_world_dir}/norway/*
${_real_world_dir}/sanfrancisco/*
${_real_world_dir}/uruguay/*)
if(NOT "${real_world}" STREQUAL "")
execute_process(COMMAND cat ${real_world}
OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/real-world-all.mvt)
add_test(NAME vtzero-show-real-world
COMMAND vtzero-show ${CMAKE_BINARY_DIR}/test/real-world-all.mvt)
endif()
endif()
else()
message(WARNING "Disabled mvt fixture tests, because fixtures not found.\n Install them by calling 'git submodule update --init' in ${CMAKE_SOURCE_DIR}.")
endif()
#-----------------------------------------------------------------------------

11678
test/catch/catch.hpp Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

1003
test/fixture_tests.cpp Normal file

File diff suppressed because it is too large Load Diff

37
test/include/test.hpp Normal file
View File

@ -0,0 +1,37 @@
#ifndef TEST_HPP
#define TEST_HPP
#include <catch.hpp>
#include <stdexcept>
// Define vtzero_assert() to throw this error. This allows the tests to
// check that the assert fails.
struct assert_error : public std::runtime_error {
explicit assert_error(const char* what_arg) : std::runtime_error(what_arg) {
}
};
#define vtzero_assert(x) if (!(x)) { throw assert_error{#x}; }
#define vtzero_assert_in_noexcept_function(x) if (!(x)) { got_an_assert = true; }
extern bool got_an_assert;
#define REQUIRE_ASSERT(x) x; REQUIRE(got_an_assert); got_an_assert = false;
#include <vtzero/output.hpp>
std::string load_test_tile();
struct mypoint {
int64_t p1;
int64_t p2;
};
inline vtzero::point create_vtzero_point(mypoint p) noexcept {
return {static_cast<int32_t>(p.p1),
static_cast<int32_t>(p.p2)};
}
#endif // TEST_HPP

1
test/mvt-fixtures Submodule

@ -0,0 +1 @@
Subproject commit 6ff9cefd5996abfdede6375a86bb2238803dabb9

408
test/t/test_builder.cpp Normal file
View File

@ -0,0 +1,408 @@
#include <test.hpp>
#include <vtzero/builder.hpp>
#include <vtzero/index.hpp>
#include <vtzero/output.hpp>
#include <cstdint>
#include <string>
#include <type_traits>
template <typename T>
struct movable_not_copyable {
constexpr static bool value = !std::is_copy_constructible<T>::value &&
!std::is_copy_assignable<T>::value &&
std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_assignable<T>::value;
};
static_assert(movable_not_copyable<vtzero::tile_builder>::value, "tile_builder should be nothrow movable, but not copyable");
static_assert(movable_not_copyable<vtzero::point_feature_builder>::value, "point_feature_builder should be nothrow movable, but not copyable");
static_assert(movable_not_copyable<vtzero::linestring_feature_builder>::value, "linestring_feature_builder should be nothrow movable, but not copyable");
static_assert(movable_not_copyable<vtzero::polygon_feature_builder>::value, "polygon_feature_builder should be nothrow movable, but not copyable");
static_assert(movable_not_copyable<vtzero::geometry_feature_builder>::value, "geometry_feature_builder should be nothrow movable, but not copyable");
TEST_CASE("Create tile from existing layers") {
const auto buffer = load_test_tile();
vtzero::vector_tile tile{buffer};
vtzero::tile_builder tbuilder;
SECTION("add_existing_layer(layer)") {
while (auto layer = tile.next_layer()) {
tbuilder.add_existing_layer(layer);
}
}
SECTION("add_existing_layer(data_view)") {
while (auto layer = tile.next_layer()) {
tbuilder.add_existing_layer(layer.data());
}
}
const std::string data = tbuilder.serialize();
REQUIRE(data == buffer);
}
TEST_CASE("Create layer based on existing layer") {
const auto buffer = load_test_tile();
vtzero::vector_tile tile{buffer};
const auto layer = tile.get_layer_by_name("place_label");
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, layer};
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(42);
fbuilder.add_point(10, 20);
fbuilder.commit();
const std::string data = tbuilder.serialize();
vtzero::vector_tile new_tile{data};
const auto new_layer = new_tile.next_layer();
REQUIRE(std::string(new_layer.name()) == "place_label");
REQUIRE(new_layer.version() == 1);
REQUIRE(new_layer.extent() == 4096);
}
TEST_CASE("Create layer and add keys/values") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "name"};
const auto ki1 = lbuilder.add_key_without_dup_check("key1");
const auto ki2 = lbuilder.add_key("key2");
const auto ki3 = lbuilder.add_key("key1");
REQUIRE(ki1 != ki2);
REQUIRE(ki1 == ki3);
const auto vi1 = lbuilder.add_value_without_dup_check(vtzero::encoded_property_value{"value1"});
vtzero::encoded_property_value value2{"value2"};
const auto vi2 = lbuilder.add_value_without_dup_check(vtzero::property_value{value2.data()});
const auto vi3 = lbuilder.add_value(vtzero::encoded_property_value{"value1"});
const auto vi4 = lbuilder.add_value(vtzero::encoded_property_value{19});
const auto vi5 = lbuilder.add_value(vtzero::encoded_property_value{19.0});
const auto vi6 = lbuilder.add_value(vtzero::encoded_property_value{22});
vtzero::encoded_property_value nineteen{19};
const auto vi7 = lbuilder.add_value(vtzero::property_value{nineteen.data()});
REQUIRE(vi1 != vi2);
REQUIRE(vi1 == vi3);
REQUIRE(vi1 != vi4);
REQUIRE(vi1 != vi5);
REQUIRE(vi1 != vi6);
REQUIRE(vi4 != vi5);
REQUIRE(vi4 != vi6);
REQUIRE(vi4 == vi7);
}
TEST_CASE("Committing a feature succeeds after a geometry was added") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
{ // explicit commit after geometry
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(1);
fbuilder.add_point(10, 10);
fbuilder.commit();
}
{ // explicit commit after properties
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(2);
fbuilder.add_point(10, 10);
fbuilder.add_property("foo", vtzero::encoded_property_value{"bar"});
fbuilder.commit();
}
{ // extra commits or rollbacks are okay but no other calls
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(3);
fbuilder.add_point(10, 10);
fbuilder.add_property("foo", vtzero::encoded_property_value{"bar"});
fbuilder.commit();
SECTION("superfluous commit()") {
fbuilder.commit();
}
SECTION("superfluous rollback()") {
fbuilder.rollback();
}
REQUIRE_THROWS_AS(fbuilder.set_id(10), const assert_error&);
REQUIRE_THROWS_AS(fbuilder.add_point(20, 20), const assert_error&);
REQUIRE_THROWS_AS(fbuilder.add_property("x", "y"), const assert_error&);
}
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
uint64_t n = 1;
while (auto feature = layer.next_feature()) {
REQUIRE(feature.id() == n++);
}
REQUIRE(n == 4);
}
TEST_CASE("Committing a feature fails with assert if no geometry was added") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
SECTION("explicit immediate commit") {
vtzero::point_feature_builder fbuilder{lbuilder};
REQUIRE_THROWS_AS(fbuilder.commit(), const assert_error&);
}
SECTION("explicit commit after setting id") {
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(2);
REQUIRE_THROWS_AS(fbuilder.commit(), const assert_error&);
}
}
TEST_CASE("Rollback feature") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
{
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(1);
fbuilder.add_point(10, 10);
fbuilder.commit();
}
{ // immediate rollback
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(2);
fbuilder.rollback();
}
{ // rollback after setting id
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(3);
fbuilder.rollback();
}
{ // rollback after geometry
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(4);
fbuilder.add_point(20, 20);
fbuilder.rollback();
}
{ // rollback after properties
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(5);
fbuilder.add_point(20, 20);
fbuilder.add_property("foo", vtzero::encoded_property_value{"bar"});
fbuilder.rollback();
}
{ // implicit rollback after geometry
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(6);
fbuilder.add_point(10, 10);
}
{ // implicit rollback after properties
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(7);
fbuilder.add_point(10, 10);
fbuilder.add_property("foo", vtzero::encoded_property_value{"bar"});
}
{
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(8);
fbuilder.add_point(30, 30);
fbuilder.commit();
}
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
auto feature = layer.next_feature();
REQUIRE(feature.id() == 1);
feature = layer.next_feature();
REQUIRE(feature.id() == 8);
feature = layer.next_feature();
REQUIRE_FALSE(feature);
}
static bool vector_tile_equal(const std::string& t1, const std::string& t2) {
vtzero::vector_tile vt1{t1};
vtzero::vector_tile vt2{t2};
for (auto l1 = vt1.next_layer(), l2 = vt2.next_layer();
l1 && l2;
l1 = vt1.next_layer(), l2 = vt2.next_layer()) {
if (l1.empty()) {
l1 = vt1.next_layer();
if (!l1) {
return true;
}
}
if (l2.empty()) {
l2 = vt2.next_layer();
if (!l2) {
return true;
}
}
if (!l1 ||
!l2 ||
l1.version() != l2.version() ||
l1.extent() != l2.extent() ||
l1.num_features() != l2.num_features() ||
l1.name() != l2.name()) {
return false;
}
for (auto f1 = l1.next_feature(), f2 = l2.next_feature();
f1 && f2;
f1 = l1.next_feature(), f2 = l2.next_feature()) {
if (f1.id() != f2.id() ||
f1.geometry_type() != f2.geometry_type() ||
f1.num_properties() != f2.num_properties() ||
f1.geometry().data() != f2.geometry().data()) {
return false;
}
for (auto p1 = f1.next_property(), p2 = f2.next_property();
p1 && p2;
p1 = f1.next_property(), p2 = f2.next_property()) {
if (p1.key() != p2.key() || p1.value() != p2.value()) {
return false;
}
}
}
}
return true;
}
TEST_CASE("Copy tile") {
const auto buffer = load_test_tile();
vtzero::vector_tile tile{buffer};
vtzero::tile_builder tbuilder;
while (auto layer = tile.next_layer()) {
vtzero::layer_builder lbuilder{tbuilder, layer};
while (auto feature = layer.next_feature()) {
lbuilder.add_feature(feature);
}
}
const std::string data = tbuilder.serialize();
REQUIRE(vector_tile_equal(buffer, data));
}
TEST_CASE("Copy tile using geometry_feature_builder") {
const auto buffer = load_test_tile();
vtzero::vector_tile tile{buffer};
vtzero::tile_builder tbuilder;
while (auto layer = tile.next_layer()) {
vtzero::layer_builder lbuilder{tbuilder, layer};
while (auto feature = layer.next_feature()) {
vtzero::geometry_feature_builder fbuilder{lbuilder};
fbuilder.set_id(feature.id());
fbuilder.set_geometry(feature.geometry());
while (auto property = feature.next_property()) {
fbuilder.add_property(property.key(), property.value());
}
fbuilder.commit();
}
}
const std::string data = tbuilder.serialize();
REQUIRE(vector_tile_equal(buffer, data));
}
TEST_CASE("Copy only point geometries using geometry_feature_builder") {
const auto buffer = load_test_tile();
vtzero::vector_tile tile{buffer};
vtzero::tile_builder tbuilder;
int n = 0;
while (auto layer = tile.next_layer()) {
vtzero::layer_builder lbuilder{tbuilder, layer};
while (auto feature = layer.next_feature()) {
vtzero::geometry_feature_builder fbuilder{lbuilder};
fbuilder.set_id(feature.id());
if (feature.geometry().type() == vtzero::GeomType::POINT) {
fbuilder.set_geometry(feature.geometry());
while (auto property = feature.next_property()) {
fbuilder.add_property(property.key(), property.value());
}
fbuilder.commit();
++n;
} else {
fbuilder.rollback();
}
}
}
REQUIRE(n == 17);
const std::string data = tbuilder.serialize();
n = 0;
vtzero::vector_tile result_tile{data};
while (auto layer = result_tile.next_layer()) {
while (auto feature = layer.next_feature()) {
++n;
}
}
REQUIRE(n == 17);
}
TEST_CASE("Build point feature from container with too many points") {
// fake container pretending to contain too many points
struct test_container {
std::size_t size() const noexcept {
return 1ul << 29u;
}
vtzero::point* begin() const noexcept {
return nullptr;
}
vtzero::point* end() const noexcept {
return nullptr;
}
};
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(1);
test_container tc;
REQUIRE_THROWS_AS(fbuilder.add_points_from_container(tc), const vtzero::geometry_exception&);
}
TEST_CASE("Moving a feature builder is allowed") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
auto fbuilder2 = std::move(fbuilder);
vtzero::point_feature_builder fbuilder3{std::move(fbuilder2)};
}

View File

@ -0,0 +1,312 @@
#include <test.hpp>
#include <vtzero/builder.hpp>
#include <vtzero/geometry.hpp>
#include <vtzero/index.hpp>
#include <cstdint>
#include <string>
#include <type_traits>
#include <vector>
using ls_type = std::vector<std::vector<vtzero::point>>;
struct linestring_handler {
ls_type data;
void linestring_begin(uint32_t count) {
data.emplace_back();
data.back().reserve(count);
}
void linestring_point(const vtzero::point point) {
data.back().push_back(point);
}
void linestring_end() const noexcept {
}
};
static void test_linestring_builder(bool with_id, bool with_prop) {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
{
vtzero::linestring_feature_builder fbuilder{lbuilder};
if (with_id) {
fbuilder.set_id(17);
}
fbuilder.add_linestring(3);
fbuilder.set_point(10, 20);
fbuilder.set_point(vtzero::point{20, 30});
fbuilder.set_point(mypoint{30, 40});
if (with_prop) {
fbuilder.add_property("foo", "bar");
}
fbuilder.commit();
}
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer.name() == "test");
REQUIRE(layer.version() == 2);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
REQUIRE(feature.id() == (with_id ? 17 : 0));
linestring_handler handler;
vtzero::decode_linestring_geometry(feature.geometry(), handler);
const ls_type result = {{{10, 20}, {20, 30}, {30, 40}}};
REQUIRE(handler.data == result);
}
TEST_CASE("linestring builder without id/without properties") {
test_linestring_builder(false, false);
}
TEST_CASE("linestring builder without id/with properties") {
test_linestring_builder(false, true);
}
TEST_CASE("linestring builder with id/without properties") {
test_linestring_builder(true, false);
}
TEST_CASE("linestring builder with id/with properties") {
test_linestring_builder(true, true);
}
TEST_CASE("Calling add_linestring() with bad values throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::linestring_feature_builder fbuilder{lbuilder};
SECTION("0") {
REQUIRE_THROWS_AS(fbuilder.add_linestring(0), const assert_error&);
}
SECTION("1") {
REQUIRE_THROWS_AS(fbuilder.add_linestring(1), const assert_error&);
}
SECTION("2^29") {
REQUIRE_THROWS_AS(fbuilder.add_linestring(1ul << 29u), const assert_error&);
}
}
static void test_multilinestring_builder(bool with_id, bool with_prop) {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::linestring_feature_builder fbuilder{lbuilder};
if (with_id) {
fbuilder.set_id(17);
}
fbuilder.add_linestring(3);
fbuilder.set_point(10, 20);
fbuilder.set_point(vtzero::point{20, 30});
fbuilder.set_point(mypoint{30, 40});
fbuilder.add_linestring(2);
fbuilder.set_point(1, 2);
fbuilder.set_point(2, 1);
if (with_prop) {
fbuilder.add_property("foo", vtzero::encoded_property_value{"bar"});
}
fbuilder.commit();
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer.name() == "test");
REQUIRE(layer.version() == 2);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
REQUIRE(feature.id() == (with_id ? 17 : 0));
linestring_handler handler;
vtzero::decode_linestring_geometry(feature.geometry(), handler);
const ls_type result = {{{10, 20}, {20, 30}, {30, 40}}, {{1, 2}, {2, 1}}};
REQUIRE(handler.data == result);
}
TEST_CASE("Multilinestring builder without id/without properties") {
test_multilinestring_builder(false, false);
}
TEST_CASE("Multilinestring builder without id/with properties") {
test_multilinestring_builder(false, true);
}
TEST_CASE("Multilinestring builder with id/without properties") {
test_multilinestring_builder(true, false);
}
TEST_CASE("Multilinestring builder with id/with properties") {
test_multilinestring_builder(true, true);
}
TEST_CASE("Calling add_linestring() twice throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::linestring_feature_builder fbuilder{lbuilder};
fbuilder.add_linestring(3);
REQUIRE_ASSERT(fbuilder.add_linestring(2));
}
TEST_CASE("Calling linestring_feature_builder::set_point() throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::linestring_feature_builder fbuilder{lbuilder};
REQUIRE_THROWS_AS(fbuilder.set_point(10, 10), const assert_error&);
}
TEST_CASE("Calling linestring_feature_builder::set_point() with same point throws") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::linestring_feature_builder fbuilder{lbuilder};
fbuilder.add_linestring(2);
fbuilder.set_point(10, 10);
REQUIRE_THROWS_AS(fbuilder.set_point(10, 10), const vtzero::geometry_exception&);
}
TEST_CASE("Calling linestring_feature_builder::set_point() too often throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::linestring_feature_builder fbuilder{lbuilder};
fbuilder.add_linestring(2);
fbuilder.set_point(10, 20);
fbuilder.set_point(20, 20);
REQUIRE_THROWS_AS(fbuilder.set_point(30, 20), const assert_error&);
}
TEST_CASE("Add linestring from container") {
const ls_type points = {{{10, 20}, {20, 30}, {30, 40}}};
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
{
vtzero::linestring_feature_builder fbuilder{lbuilder};
#if 0
SECTION("using iterators") {
fbuilder.add_linestring(points[0].cbegin(), points[0].cend());
}
SECTION("using iterators and size") {
fbuilder.add_linestring(points[0].cbegin(), points[0].cend(), static_cast<uint32_t>(points[0].size()));
}
#endif
SECTION("using container directly") {
fbuilder.add_linestring_from_container(points[0]);
}
fbuilder.commit();
}
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer);
REQUIRE(layer.name() == "test");
REQUIRE(layer.version() == 2);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
linestring_handler handler;
vtzero::decode_linestring_geometry(feature.geometry(), handler);
REQUIRE(handler.data == points);
}
#if 0
TEST_CASE("Add linestring from iterator with wrong count throws assert") {
const std::vector<vtzero::point> points = {{10, 20}, {20, 30}, {30, 40}};
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::linestring_feature_builder fbuilder{lbuilder};
REQUIRE_THROWS_AS(fbuilder.add_linestring(points.cbegin(),
points.cend(),
static_cast<uint32_t>(points.size() + 1)), const assert_error&);
}
#endif
TEST_CASE("Adding several linestrings with feature rollback in the middle") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
{
vtzero::linestring_feature_builder fbuilder{lbuilder};
fbuilder.set_id(1);
fbuilder.add_linestring(2);
fbuilder.set_point(10, 10);
fbuilder.set_point(20, 20);
fbuilder.commit();
}
try {
vtzero::linestring_feature_builder fbuilder{lbuilder};
fbuilder.set_id(2);
fbuilder.add_linestring(2);
fbuilder.set_point(10, 10);
fbuilder.set_point(10, 10);
fbuilder.commit();
} catch (vtzero::geometry_exception&) {
}
{
vtzero::linestring_feature_builder fbuilder{lbuilder};
fbuilder.set_id(3);
fbuilder.add_linestring(2);
fbuilder.set_point(10, 20);
fbuilder.set_point(20, 10);
fbuilder.commit();
}
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer);
REQUIRE(layer.name() == "test");
REQUIRE(layer.num_features() == 2);
auto feature = layer.next_feature();
REQUIRE(feature.id() == 1);
feature = layer.next_feature();
REQUIRE(feature.id() == 3);
}

View File

@ -0,0 +1,279 @@
#include <test.hpp>
#include <vtzero/builder.hpp>
#include <vtzero/geometry.hpp>
#include <vtzero/index.hpp>
#include <cstdint>
#include <string>
#include <type_traits>
#include <vector>
struct point_handler {
std::vector<vtzero::point> data;
void points_begin(uint32_t count) {
data.reserve(count);
}
void points_point(const vtzero::point point) {
data.push_back(point);
}
void points_end() const noexcept {
}
};
static void test_point_builder(bool with_id, bool with_prop) {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
{
vtzero::point_feature_builder fbuilder{lbuilder};
if (with_id) {
fbuilder.set_id(17);
}
SECTION("add point using coordinates / property using key/value") {
fbuilder.add_point(10, 20);
if (with_prop) {
fbuilder.add_property("foo", vtzero::encoded_property_value{"bar"});
}
}
SECTION("add point using vtzero::point / property using key/value") {
fbuilder.add_point(vtzero::point{10, 20});
if (with_prop) {
fbuilder.add_property("foo", vtzero::encoded_property_value{22});
}
}
SECTION("add point using mypoint / property using property") {
vtzero::encoded_property_value pv{3.5};
vtzero::property p{"foo", vtzero::property_value{pv.data()}};
fbuilder.add_point(mypoint{10, 20});
if (with_prop) {
fbuilder.add_property(p);
}
}
fbuilder.commit();
}
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer.name() == "test");
REQUIRE(layer.version() == 2);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
REQUIRE(feature.id() == (with_id ? 17 : 0));
point_handler handler;
vtzero::decode_point_geometry(feature.geometry(), handler);
const std::vector<vtzero::point> result = {{10, 20}};
REQUIRE(handler.data == result);
}
TEST_CASE("Point builder without id/without properties") {
test_point_builder(false, false);
}
TEST_CASE("Point builder without id/with properties") {
test_point_builder(false, true);
}
TEST_CASE("Point builder with id/without properties") {
test_point_builder(true, false);
}
TEST_CASE("Point builder with id/with properties") {
test_point_builder(true, true);
}
TEST_CASE("Calling add_points() with bad values throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
SECTION("0") {
REQUIRE_THROWS_AS(fbuilder.add_points(0), const assert_error&);
}
SECTION("2^29") {
REQUIRE_THROWS_AS(fbuilder.add_points(1ul << 29u), const assert_error&);
}
}
static void test_multipoint_builder(bool with_id, bool with_prop) {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
if (with_id) {
fbuilder.set_id(17);
}
fbuilder.add_points(3);
fbuilder.set_point(10, 20);
fbuilder.set_point(vtzero::point{20, 30});
fbuilder.set_point(mypoint{30, 40});
if (with_prop) {
fbuilder.add_property("foo", vtzero::encoded_property_value{"bar"});
}
fbuilder.commit();
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer.name() == "test");
REQUIRE(layer.version() == 2);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
REQUIRE(feature.id() == (with_id ? 17 : 0));
point_handler handler;
vtzero::decode_point_geometry(feature.geometry(), handler);
const std::vector<vtzero::point> result = {{10, 20}, {20, 30}, {30, 40}};
REQUIRE(handler.data == result);
}
TEST_CASE("Multipoint builder without id/without properties") {
test_multipoint_builder(false, false);
}
TEST_CASE("Multipoint builder without id/with properties") {
test_multipoint_builder(false, true);
}
TEST_CASE("Multipoint builder with id/without properties") {
test_multipoint_builder(true, false);
}
TEST_CASE("Multipoint builder with id/with properties") {
test_multipoint_builder(true, true);
}
TEST_CASE("Calling add_point() and then other geometry functions throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.add_point(10, 20);
SECTION("add_point()") {
REQUIRE_THROWS_AS(fbuilder.add_point(10, 20), const assert_error&);
}
SECTION("add_points()") {
REQUIRE_THROWS_AS(fbuilder.add_points(2), const assert_error&);
}
SECTION("set_point()") {
REQUIRE_THROWS_AS(fbuilder.set_point(10, 10), const assert_error&);
}
}
TEST_CASE("Calling point_feature_builder::set_point() throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
REQUIRE_THROWS_AS(fbuilder.set_point(10, 10), const assert_error&);
}
TEST_CASE("Calling add_points() and then other geometry functions throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.add_points(2);
SECTION("add_point()") {
REQUIRE_THROWS_AS(fbuilder.add_point(10, 20), const assert_error&);
}
SECTION("add_points()") {
REQUIRE_THROWS_AS(fbuilder.add_points(2), const assert_error&);
}
}
TEST_CASE("Calling point_feature_builder::set_point() too often throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.add_points(2);
fbuilder.set_point(10, 20);
fbuilder.set_point(20, 20);
REQUIRE_THROWS_AS(fbuilder.set_point(30, 20), const assert_error&);
}
TEST_CASE("Add points from container") {
const std::vector<vtzero::point> points = {{10, 20}, {20, 30}, {30, 40}};
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
{
vtzero::point_feature_builder fbuilder{lbuilder};
/* SECTION("using iterators") {
fbuilder.add_points(points.cbegin(), points.cend());
}
SECTION("using iterators and size") {
fbuilder.add_points(points.cbegin(), points.cend(), static_cast<uint32_t>(points.size()));
}*/
SECTION("using container directly") {
fbuilder.add_points_from_container(points);
}
fbuilder.commit();
}
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer);
REQUIRE(layer.name() == "test");
REQUIRE(layer.version() == 2);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
point_handler handler;
vtzero::decode_point_geometry(feature.geometry(), handler);
REQUIRE(handler.data == points);
}
/*
TEST_CASE("Add points from iterator with wrong count throws assert") {
const std::vector<vtzero::point> points = {{10, 20}, {20, 30}, {30, 40}};
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
REQUIRE_THROWS_AS(fbuilder.add_points(points.cbegin(),
points.cend(),
static_cast<uint32_t>(points.size() + 1)), const assert_error&);
}*/

View File

@ -0,0 +1,307 @@
#include <test.hpp>
#include <vtzero/builder.hpp>
#include <vtzero/geometry.hpp>
#include <vtzero/index.hpp>
#include <cstdint>
#include <string>
#include <type_traits>
#include <vector>
using polygon_type = std::vector<std::vector<vtzero::point>>;
struct polygon_handler {
polygon_type data;
void ring_begin(uint32_t count) {
data.emplace_back();
data.back().reserve(count);
}
void ring_point(const vtzero::point point) {
data.back().push_back(point);
}
void ring_end(vtzero::ring_type /* type */) const noexcept {
}
};
static void test_polygon_builder(bool with_id, bool with_prop) {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
{
vtzero::polygon_feature_builder fbuilder{lbuilder};
if (with_id) {
fbuilder.set_id(17);
}
fbuilder.add_ring(4);
fbuilder.set_point(10, 20);
fbuilder.set_point(vtzero::point{20, 30});
fbuilder.set_point(mypoint{30, 40});
fbuilder.set_point(10, 20);
if (with_prop) {
fbuilder.add_property("foo", "bar");
}
fbuilder.commit();
}
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer.name() == "test");
REQUIRE(layer.version() == 2);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
REQUIRE(feature.id() == (with_id ? 17 : 0));
polygon_handler handler;
vtzero::decode_polygon_geometry(feature.geometry(), handler);
const polygon_type result = {{{10, 20}, {20, 30}, {30, 40}, {10, 20}}};
REQUIRE(handler.data == result);
}
TEST_CASE("polygon builder without id/without properties") {
test_polygon_builder(false, false);
}
TEST_CASE("polygon builder without id/with properties") {
test_polygon_builder(false, true);
}
TEST_CASE("polygon builder with id/without properties") {
test_polygon_builder(true, false);
}
TEST_CASE("polygon builder with id/with properties") {
test_polygon_builder(true, true);
}
TEST_CASE("Calling add_ring() with bad values throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::polygon_feature_builder fbuilder{lbuilder};
SECTION("0") {
REQUIRE_THROWS_AS(fbuilder.add_ring(0), const assert_error&);
}
SECTION("1") {
REQUIRE_THROWS_AS(fbuilder.add_ring(1), const assert_error&);
}
SECTION("2") {
REQUIRE_THROWS_AS(fbuilder.add_ring(2), const assert_error&);
}
SECTION("3") {
REQUIRE_THROWS_AS(fbuilder.add_ring(3), const assert_error&);
}
SECTION("2^29") {
REQUIRE_THROWS_AS(fbuilder.add_ring(1ul << 29u), const assert_error&);
}
}
static void test_multipolygon_builder(bool with_id, bool with_prop) {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::polygon_feature_builder fbuilder{lbuilder};
if (with_id) {
fbuilder.set_id(17);
}
fbuilder.add_ring(4);
fbuilder.set_point(10, 20);
fbuilder.set_point(vtzero::point{20, 30});
fbuilder.set_point(mypoint{30, 40});
fbuilder.set_point(10, 20);
fbuilder.add_ring(5);
fbuilder.set_point(1, 1);
fbuilder.set_point(2, 1);
fbuilder.set_point(2, 2);
fbuilder.set_point(1, 2);
if (with_id) {
fbuilder.set_point(1, 1);
} else {
fbuilder.close_ring();
}
if (with_prop) {
fbuilder.add_property("foo", vtzero::encoded_property_value{"bar"});
}
fbuilder.commit();
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer.name() == "test");
REQUIRE(layer.version() == 2);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
REQUIRE(feature.id() == (with_id ? 17 : 0));
polygon_handler handler;
vtzero::decode_polygon_geometry(feature.geometry(), handler);
const polygon_type result = {{{10, 20}, {20, 30}, {30, 40}, {10, 20}},
{{1, 1}, {2, 1}, {2, 2}, {1, 2}, {1, 1}}};
REQUIRE(handler.data == result);
}
TEST_CASE("Multipolygon builder without id/without properties") {
test_multipolygon_builder(false, false);
}
TEST_CASE("Multipolygon builder without id/with properties") {
test_multipolygon_builder(false, true);
}
TEST_CASE("Multipolygon builder with id/without properties") {
test_multipolygon_builder(true, false);
}
TEST_CASE("Multipolygon builder with id/with properties") {
test_multipolygon_builder(true, true);
}
TEST_CASE("Calling add_ring() twice throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::polygon_feature_builder fbuilder{lbuilder};
fbuilder.add_ring(4);
REQUIRE_ASSERT(fbuilder.add_ring(4));
}
TEST_CASE("Calling polygon_feature_builder::set_point()/close_ring() throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::polygon_feature_builder fbuilder{lbuilder};
SECTION("set_point") {
REQUIRE_THROWS_AS(fbuilder.set_point(10, 10), const assert_error&);
}
SECTION("close_ring") {
REQUIRE_THROWS_AS(fbuilder.close_ring(), const assert_error&);
}
}
TEST_CASE("Calling polygon_feature_builder::set_point()/close_ring() too often throws assert") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::polygon_feature_builder fbuilder{lbuilder};
fbuilder.add_ring(4);
fbuilder.set_point(10, 20);
fbuilder.set_point(20, 20);
fbuilder.set_point(30, 20);
fbuilder.set_point(10, 20);
SECTION("set_point") {
REQUIRE_THROWS_AS(fbuilder.set_point(50, 20), const assert_error&);
}
SECTION("close_ring") {
REQUIRE_THROWS_AS(fbuilder.close_ring(), const assert_error&);
}
}
TEST_CASE("Calling polygon_feature_builder::set_point() with same point throws") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::polygon_feature_builder fbuilder{lbuilder};
fbuilder.add_ring(4);
fbuilder.set_point(10, 10);
REQUIRE_THROWS_AS(fbuilder.set_point(10, 10), const vtzero::geometry_exception&);
}
TEST_CASE("Calling polygon_feature_builder::set_point() creating unclosed ring throws") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::polygon_feature_builder fbuilder{lbuilder};
fbuilder.add_ring(4);
fbuilder.set_point(10, 10);
fbuilder.set_point(10, 20);
fbuilder.set_point(20, 20);
REQUIRE_THROWS_AS(fbuilder.set_point(20, 30), const vtzero::geometry_exception&);
}
TEST_CASE("Add polygon from container") {
const polygon_type points = {{{10, 20}, {20, 30}, {30, 40}, {10, 20}}};
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
{
vtzero::polygon_feature_builder fbuilder{lbuilder};
#if 0
SECTION("using iterators") {
fbuilder.add_ring(points[0].cbegin(), points[0].cend());
}
SECTION("using iterators and size") {
fbuilder.add_ring(points[0].cbegin(), points[0].cend(), static_cast<uint32_t>(points[0].size()));
}
#endif
SECTION("using container directly") {
fbuilder.add_ring_from_container(points[0]);
}
fbuilder.commit();
}
const std::string data = tbuilder.serialize();
vtzero::vector_tile tile{data};
auto layer = tile.next_layer();
REQUIRE(layer);
REQUIRE(layer.name() == "test");
REQUIRE(layer.version() == 2);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
polygon_handler handler;
vtzero::decode_polygon_geometry(feature.geometry(), handler);
REQUIRE(handler.data == points);
}
#if 0
TEST_CASE("Add polygon from iterator with wrong count throws assert") {
const std::vector<vtzero::point> points = {{10, 20}, {20, 30}, {30, 40}, {10, 20}};
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::polygon_feature_builder fbuilder{lbuilder};
REQUIRE_THROWS_AS(fbuilder.add_ring(points.cbegin(),
points.cend(),
static_cast<uint32_t>(points.size() + 1)), const assert_error&);
}
#endif

View File

@ -0,0 +1,42 @@
#include <test.hpp>
#include <vtzero/exception.hpp>
#include <string>
TEST_CASE("construct format_exception with const char*") {
vtzero::format_exception e{"broken"};
REQUIRE(std::string{e.what()} == "broken");
}
TEST_CASE("construct format_exception with const std::string") {
vtzero::format_exception e{std::string{"broken"}};
REQUIRE(std::string{e.what()} == "broken");
}
TEST_CASE("construct geometry_exception with const char*") {
vtzero::geometry_exception e{"broken"};
REQUIRE(std::string{e.what()} == "broken");
}
TEST_CASE("construct geometry_exception with std::string") {
vtzero::geometry_exception e{std::string{"broken"}};
REQUIRE(std::string{e.what()} == "broken");
}
TEST_CASE("construct type_exception") {
vtzero::type_exception e;
REQUIRE(std::string{e.what()} == "wrong property value type");
}
TEST_CASE("construct version_exception") {
vtzero::version_exception e{42};
REQUIRE(std::string{e.what()} == "unknown vector tile version: 42");
}
TEST_CASE("construct out_of_range_exception") {
vtzero::out_of_range_exception e{99};
REQUIRE(std::string{e.what()} == "index out of range: 99");
}

97
test/t/test_feature.cpp Normal file
View File

@ -0,0 +1,97 @@
#include <test.hpp>
#include <vtzero/feature.hpp>
#include <vtzero/layer.hpp>
#include <vtzero/vector_tile.hpp>
TEST_CASE("default constructed feature") {
vtzero::feature feature{};
REQUIRE_FALSE(feature.valid());
REQUIRE_FALSE(feature);
REQUIRE(feature.id() == 0);
REQUIRE_FALSE(feature.has_id());
REQUIRE(feature.geometry_type() == vtzero::GeomType::UNKNOWN);
REQUIRE_ASSERT(feature.geometry());
REQUIRE(feature.empty());
REQUIRE(feature.num_properties() == 0);
}
TEST_CASE("read a feature") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
auto layer = tile.get_layer_by_name("bridge");
REQUIRE(layer.valid());
auto feature = layer.next_feature();
REQUIRE(feature.valid());
REQUIRE(feature);
REQUIRE(feature.id() == 0);
REQUIRE(feature.has_id());
REQUIRE(feature.geometry_type() == vtzero::GeomType::LINESTRING);
REQUIRE_FALSE(feature.empty());
REQUIRE(feature.num_properties() == 4);
}
TEST_CASE("iterate over all properties of a feature") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
auto layer = tile.get_layer_by_name("bridge");
auto feature = layer.next_feature();
int count = 0;
SECTION("external iterator") {
while (auto p = feature.next_property()) {
++count;
if (p.key() == "type") {
REQUIRE(p.value().type() == vtzero::property_value_type::string_value);
REQUIRE(p.value().string_value() == "primary");
}
}
}
SECTION("internal iterator") {
feature.for_each_property([&count](const vtzero::property& p) {
++count;
if (p.key() == "type") {
REQUIRE(p.value().type() == vtzero::property_value_type::string_value);
REQUIRE(p.value().string_value() == "primary");
}
return true;
});
}
REQUIRE(count == 4);
}
TEST_CASE("iterate over some properties of a feature") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
auto layer = tile.get_layer_by_name("bridge");
REQUIRE(layer.valid());
auto feature = layer.next_feature();
REQUIRE(feature.valid());
int count = 0;
SECTION("external iterator") {
while (auto p = feature.next_property()) {
++count;
if (p.key() == "oneway") {
break;
}
}
}
SECTION("internal iterator") {
feature.for_each_property([&count](const vtzero::property& p) {
++count;
return p.key() != "oneway";
});
}
REQUIRE(count == 2);
}

334
test/t/test_geometry.cpp Normal file
View File

@ -0,0 +1,334 @@
#include <test.hpp>
#include <vtzero/geometry.hpp>
#include <cstdint>
#include <limits>
#include <vector>
using container = std::vector<uint32_t>;
using iterator = container::const_iterator;
TEST_CASE("geometry_decoder") {
const container g = {};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.done());
REQUIRE_FALSE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE_THROWS_AS(decoder.next_point(), const assert_error&);
}
TEST_CASE("geometry_decoder with point") {
const container g = {9, 50, 34};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE_THROWS_AS(decoder.next_point(), const assert_error&);
SECTION("trying to get LineTo command") {
REQUIRE_THROWS_AS(decoder.next_command(vtzero::detail::CommandId::LINE_TO), const vtzero::geometry_exception&);
}
SECTION("trying to get ClosePath command") {
REQUIRE_THROWS_WITH(decoder.next_command(vtzero::detail::CommandId::CLOSE_PATH), "expected command 7 but got 1");
}
SECTION("trying to get MoveTo command") {
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE_THROWS_AS(decoder.next_command(vtzero::detail::CommandId::MOVE_TO), const assert_error&);
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(25, 17));
REQUIRE(decoder.done());
REQUIRE_FALSE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
}
}
TEST_CASE("geometry_decoder with incomplete point") {
container g = {9, 50, 34};
SECTION("half a point") {
g.pop_back();
}
SECTION("missing point") {
g.pop_back();
g.pop_back();
}
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), 100};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE_THROWS_AS(decoder.next_point(), const vtzero::geometry_exception&);
}
TEST_CASE("geometry_decoder with multipoint") {
const container g = {17, 10, 14, 3, 9};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 2);
REQUIRE(decoder.next_point() == vtzero::point(5, 7));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(3, 2));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.done());
REQUIRE_FALSE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
}
TEST_CASE("geometry_decoder with linestring") {
const container g = {9, 4, 4, 18, 0, 16, 16, 0};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(2, 2));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 2);
REQUIRE(decoder.next_point() == vtzero::point(2, 10));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(10, 10));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.done());
REQUIRE_FALSE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
}
TEST_CASE("geometry_decoder with linestring with equal points") {
const container g = {9, 4, 4, 18, 0, 16, 0, 0};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(2, 2));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 2);
REQUIRE(decoder.next_point() == vtzero::point(2, 10));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(2, 10));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.done());
}
TEST_CASE("geometry_decoder with multilinestring") {
const container g = {9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(2, 2));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 2);
REQUIRE(decoder.next_point() == vtzero::point(2, 10));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(10, 10));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(1, 1));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(3, 5));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.done());
REQUIRE_FALSE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
}
TEST_CASE("geometry_decoder with polygon") {
const container g = {9, 6, 12, 18, 10, 12, 24, 44, 15};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(3, 6));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 2);
REQUIRE(decoder.next_point() == vtzero::point(8, 12));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(20, 34));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.next_command(vtzero::detail::CommandId::CLOSE_PATH));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.done());
REQUIRE_FALSE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
}
TEST_CASE("geometry_decoder with polygon with wrong ClosePath count 2") {
const container g = {9, 6, 12, 18, 10, 12, 24, 44, 23};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.next_point() == vtzero::point(3, 6));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.next_point() == vtzero::point(8, 12));
REQUIRE(decoder.next_point() == vtzero::point(20, 34));
REQUIRE_THROWS_AS(decoder.next_command(vtzero::detail::CommandId::CLOSE_PATH), const vtzero::geometry_exception&);
REQUIRE_THROWS_WITH(decoder.next_command(vtzero::detail::CommandId::CLOSE_PATH), "ClosePath command count is not 1");
}
TEST_CASE("geometry_decoder with polygon with wrong ClosePath count 0") {
const container g = {9, 6, 12, 18, 10, 12, 24, 44, 7};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.next_point() == vtzero::point(3, 6));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.next_point() == vtzero::point(8, 12));
REQUIRE(decoder.next_point() == vtzero::point(20, 34));
REQUIRE_THROWS_AS(decoder.next_command(vtzero::detail::CommandId::CLOSE_PATH), const vtzero::geometry_exception&);
REQUIRE_THROWS_WITH(decoder.next_command(vtzero::detail::CommandId::CLOSE_PATH), "ClosePath command count is not 1");
}
TEST_CASE("geometry_decoder with multipolygon") {
const container g = {9, 0, 0, 26, 20, 0, 0, 20, 19, 0, 15, 9, 22, 2, 26, 18,
0, 0, 18, 17, 0, 15, 9, 4, 13, 26, 0, 8, 8, 0, 0, 7, 15};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(0, 0));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 3);
REQUIRE(decoder.next_point() == vtzero::point(10, 0));
REQUIRE(decoder.count() == 2);
REQUIRE(decoder.next_point() == vtzero::point(10, 10));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(0, 10));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.next_command(vtzero::detail::CommandId::CLOSE_PATH));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(11, 11));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 3);
REQUIRE(decoder.next_point() == vtzero::point(20, 11));
REQUIRE(decoder.count() == 2);
REQUIRE(decoder.next_point() == vtzero::point(20, 20));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(11, 20));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.next_command(vtzero::detail::CommandId::CLOSE_PATH));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(13, 13));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 3);
REQUIRE(decoder.next_point() == vtzero::point(13, 17));
REQUIRE(decoder.count() == 2);
REQUIRE(decoder.next_point() == vtzero::point(17, 17));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(17, 13));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.next_command(vtzero::detail::CommandId::CLOSE_PATH));
REQUIRE(decoder.count() == 0);
REQUIRE(decoder.done());
REQUIRE_FALSE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
}
TEST_CASE("geometry_decoder decoding linestring with int32 overflow in x coordinate") {
const container g = {vtzero::detail::command_move_to(1),
protozero::encode_zigzag32(std::numeric_limits<int32_t>::max()),
protozero::encode_zigzag32(0),
vtzero::detail::command_line_to(1),
protozero::encode_zigzag32(1),
protozero::encode_zigzag32(1)
};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(std::numeric_limits<int32_t>::max(), 0));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(std::numeric_limits<int32_t>::min(), 1));
}
TEST_CASE("geometry_decoder decoding linestring with int32 overflow in y coordinate") {
const container g = {vtzero::detail::command_move_to(1),
protozero::encode_zigzag32(0),
protozero::encode_zigzag32(std::numeric_limits<int32_t>::min()),
vtzero::detail::command_line_to(1),
protozero::encode_zigzag32(-1),
protozero::encode_zigzag32(-1)
};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE(decoder.next_command(vtzero::detail::CommandId::MOVE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(0, std::numeric_limits<int32_t>::min()));
REQUIRE(decoder.next_command(vtzero::detail::CommandId::LINE_TO));
REQUIRE(decoder.count() == 1);
REQUIRE(decoder.next_point() == vtzero::point(-1, std::numeric_limits<int32_t>::max()));
}
TEST_CASE("geometry_decoder with multipoint with a huge count") {
const uint32_t huge_value = (1ul << 29u) - 1;
const container g = {vtzero::detail::command_move_to(huge_value), 10, 10};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), g.size() / 2};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE_THROWS_AS(decoder.next_command(vtzero::detail::CommandId::MOVE_TO), const vtzero::geometry_exception&);
}
TEST_CASE("geometry_decoder with multipoint with not enough points") {
const container g = {vtzero::detail::command_move_to(2), 10};
vtzero::detail::geometry_decoder<iterator> decoder{g.cbegin(), g.cend(), 1};
REQUIRE(decoder.count() == 0);
REQUIRE_FALSE(decoder.done());
REQUIRE_THROWS_AS(decoder.next_command(vtzero::detail::CommandId::MOVE_TO), const vtzero::geometry_exception&);
}

View File

@ -0,0 +1,160 @@
#include <test.hpp>
#include <vtzero/geometry.hpp>
#include <cstdint>
#include <vector>
using container = std::vector<uint32_t>;
using iterator = container::const_iterator;
class dummy_geom_handler {
int value = 0;
public:
void linestring_begin(const uint32_t /*count*/) noexcept {
++value;
}
void linestring_point(const vtzero::point /*point*/) noexcept {
value += 100;
}
void linestring_end() noexcept {
value += 10000;
}
int result() const noexcept {
return value;
}
}; // class dummy_geom_handler
TEST_CASE("Calling decode_linestring_geometry() with empty input") {
const container g;
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
dummy_geom_handler handler;
decoder.decode_linestring(dummy_geom_handler{});
REQUIRE(handler.result() == 0);
}
TEST_CASE("Calling decode_linestring_geometry() with a valid linestring") {
const container g = {9, 4, 4, 18, 0, 16, 16, 0};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
REQUIRE(decoder.decode_linestring(dummy_geom_handler{}) == 10301);
}
TEST_CASE("Calling decode_linestring_geometry() with a valid multilinestring") {
const container g = {9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
dummy_geom_handler handler;
decoder.decode_linestring(handler);
REQUIRE(handler.result() == 20502);
}
TEST_CASE("Calling decode_linestring_geometry() with a point geometry fails") {
const container g = {9, 50, 34}; // this is a point geometry
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_linestring(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_linestring(dummy_geom_handler{}),
"expected LineTo command (spec 4.3.4.3)");
}
}
TEST_CASE("Calling decode_linestring_geometry() with a polygon geometry fails") {
const container g = {9, 6, 12, 18, 10, 12, 24, 44, 15}; // this is a polygon geometry
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_linestring(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_linestring(dummy_geom_handler{}),
"expected command 1 but got 7");
}
}
TEST_CASE("Calling decode_linestring_geometry() with something other than MoveTo command") {
const container g = {vtzero::detail::command_line_to(3)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_linestring(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_linestring(dummy_geom_handler{}),
"expected command 1 but got 2");
}
}
TEST_CASE("Calling decode_linestring_geometry() with a count of 0") {
const container g = {vtzero::detail::command_move_to(0)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_linestring(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_linestring(dummy_geom_handler{}),
"MoveTo command count is not 1 (spec 4.3.4.3)");
}
}
TEST_CASE("Calling decode_linestring_geometry() with a count of 2") {
const container g = {vtzero::detail::command_move_to(2), 10, 20, 20, 10};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_linestring(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_linestring(dummy_geom_handler{}),
"MoveTo command count is not 1 (spec 4.3.4.3)");
}
}
TEST_CASE("Calling decode_linestring_geometry() with 2nd command not a LineTo") {
const container g = {vtzero::detail::command_move_to(1), 3, 4,
vtzero::detail::command_move_to(1)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_linestring(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_linestring(dummy_geom_handler{}),
"expected command 2 but got 1");
}
}
TEST_CASE("Calling decode_linestring_geometry() with LineTo and 0 count") {
const container g = {vtzero::detail::command_move_to(1), 3, 4,
vtzero::detail::command_line_to(0)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_linestring(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_linestring(dummy_geom_handler{}),
"LineTo command count is zero (spec 4.3.4.3)");
}
}

View File

@ -0,0 +1,135 @@
#include <test.hpp>
#include <vtzero/geometry.hpp>
#include <cstdint>
#include <vector>
using container = std::vector<uint32_t>;
using iterator = container::const_iterator;
class dummy_geom_handler {
int value = 0;
public:
void points_begin(const uint32_t /*count*/) noexcept {
++value;
}
void points_point(const vtzero::point /*point*/) noexcept {
value += 100;
}
void points_end() noexcept {
value += 10000;
}
int result() const noexcept {
return value;
}
}; // class dummy_geom_handler
TEST_CASE("Calling decode_point() with empty input") {
const container g;
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_point(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_point(dummy_geom_handler{}),
"expected MoveTo command (spec 4.3.4.2)");
}
}
TEST_CASE("Calling decode_point() with a valid point") {
const container g = {9, 50, 34};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
dummy_geom_handler handler;
decoder.decode_point(handler);
REQUIRE(handler.result() == 10101);
}
TEST_CASE("Calling decode_point() with a valid multipoint") {
const container g = {17, 10, 14, 3, 9};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
REQUIRE(decoder.decode_point(dummy_geom_handler{}) == 10201);
}
TEST_CASE("Calling decode_point() with a linestring geometry fails") {
const container g = {9, 4, 4, 18, 0, 16, 16, 0}; // this is a linestring geometry
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_point(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_point(dummy_geom_handler{}),
"additional data after end of geometry (spec 4.3.4.2)");
}
}
TEST_CASE("Calling decode_point() with a polygon geometry fails") {
const container g = {9, 6, 12, 18, 10, 12, 24, 44, 15}; // this is a polygon geometry
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_point(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_point(dummy_geom_handler{}),
"additional data after end of geometry (spec 4.3.4.2)");
}
}
TEST_CASE("Calling decode_point() with something other than MoveTo command") {
const container g = {vtzero::detail::command_line_to(3)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_point(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_point(dummy_geom_handler{}),
"expected command 1 but got 2");
}
}
TEST_CASE("Calling decode_point() with a count of 0") {
const container g = {vtzero::detail::command_move_to(0)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_point(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_point(dummy_geom_handler{}),
"MoveTo command count is zero (spec 4.3.4.2)");
}
}
TEST_CASE("Calling decode_point() with more data then expected") {
const container g = {9, 50, 34, 9};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_point(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_point(dummy_geom_handler{}),
"additional data after end of geometry (spec 4.3.4.2)");
}
}

View File

@ -0,0 +1,204 @@
#include <test.hpp>
#include <vtzero/geometry.hpp>
#include <cstdint>
#include <vector>
using container = std::vector<uint32_t>;
using iterator = container::const_iterator;
class dummy_geom_handler {
int value = 0;
public:
void ring_begin(const uint32_t /*count*/) noexcept {
++value;
}
void ring_point(const vtzero::point /*point*/) noexcept {
value += 100;
}
void ring_end(vtzero::ring_type /*is_outer*/) noexcept {
value += 10000;
}
int result() const noexcept {
return value;
}
}; // class dummy_geom_handler
TEST_CASE("Calling decode_polygon_geometry() with empty input") {
const container g;
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
dummy_geom_handler handler;
decoder.decode_polygon(dummy_geom_handler{});
REQUIRE(handler.result() == 0);
}
TEST_CASE("Calling decode_polygon_geometry() with a valid polygon") {
const container g = {9, 6, 12, 18, 10, 12, 24, 44, 15};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
REQUIRE(decoder.decode_polygon(dummy_geom_handler{}) == 10401);
}
TEST_CASE("Calling decode_polygon_geometry() with a duplicate end point") {
const container g = {9, 6, 12, 26, 10, 12, 24, 44, 33, 55, 15};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
dummy_geom_handler handler;
decoder.decode_polygon(handler);
REQUIRE(handler.result() == 10501);
}
TEST_CASE("Calling decode_polygon_geometry() with a valid multipolygon") {
const container g = {9, 0, 0, 26, 20, 0, 0, 20, 19, 0, 15, 9, 22, 2, 26, 18,
0, 0, 18, 17, 0, 15, 9, 4, 13, 26, 0, 8, 8, 0, 0, 7, 15};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
dummy_geom_handler handler;
decoder.decode_polygon(handler);
REQUIRE(handler.result() == 31503);
}
TEST_CASE("Calling decode_polygon_geometry() with a point geometry fails") {
const container g = {9, 50, 34}; // this is a point geometry
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_polygon(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_polygon(dummy_geom_handler{}),
"expected LineTo command (spec 4.3.4.4)");
}
}
TEST_CASE("Calling decode_polygon_geometry() with a linestring geometry fails") {
const container g = {9, 4, 4, 18, 0, 16, 16, 0}; // this is a linestring geometry
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_polygon(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_polygon(dummy_geom_handler{}),
"expected ClosePath command (4.3.4.4)");
}
}
TEST_CASE("Calling decode_polygon_geometry() with something other than MoveTo command") {
const container g = {vtzero::detail::command_line_to(3)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_polygon(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_polygon(dummy_geom_handler{}),
"expected command 1 but got 2");
}
}
TEST_CASE("Calling decode_polygon_geometry() with a count of 0") {
const container g = {vtzero::detail::command_move_to(0)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_polygon(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_polygon(dummy_geom_handler{}),
"MoveTo command count is not 1 (spec 4.3.4.4)");
}
}
TEST_CASE("Calling decode_polygon_geometry() with a count of 2") {
const container g = {vtzero::detail::command_move_to(2), 1, 2, 3, 4};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_polygon(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_polygon(dummy_geom_handler{}),
"MoveTo command count is not 1 (spec 4.3.4.4)");
}
}
TEST_CASE("Calling decode_polygon_geometry() with 2nd command not a LineTo") {
const container g = {vtzero::detail::command_move_to(1), 3, 4,
vtzero::detail::command_move_to(1)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_polygon(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_polygon(dummy_geom_handler{}),
"expected command 2 but got 1");
}
}
TEST_CASE("Calling decode_polygon_geometry() with LineTo and 0 count") {
const container g = {vtzero::detail::command_move_to(1), 3, 4,
vtzero::detail::command_line_to(0),
vtzero::detail::command_close_path()};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
dummy_geom_handler handler;
decoder.decode_polygon(handler);
REQUIRE(handler.result() == 10201);
}
TEST_CASE("Calling decode_polygon_geometry() with LineTo and 1 count") {
const container g = {vtzero::detail::command_move_to(1), 3, 4,
vtzero::detail::command_line_to(1), 5, 6,
vtzero::detail::command_close_path()};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
dummy_geom_handler handler;
decoder.decode_polygon(handler);
REQUIRE(handler.result() == 10301);
}
TEST_CASE("Calling decode_polygon_geometry() with 3nd command not a ClosePath") {
const container g = {vtzero::detail::command_move_to(1), 3, 4,
vtzero::detail::command_line_to(2), 4, 5, 6, 7,
vtzero::detail::command_line_to(0)};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
SECTION("check exception type") {
REQUIRE_THROWS_AS(decoder.decode_polygon(dummy_geom_handler{}),
const vtzero::geometry_exception&);
}
SECTION("check exception message") {
REQUIRE_THROWS_WITH(decoder.decode_polygon(dummy_geom_handler{}),
"expected command 7 but got 2");
}
}
TEST_CASE("Calling decode_polygon_geometry() on polygon with zero area") {
const container g = {vtzero::detail::command_move_to(1), 0, 0,
vtzero::detail::command_line_to(3), 2, 0, 0, 4, 2, 0,
vtzero::detail::command_close_path()};
vtzero::detail::geometry_decoder<container::const_iterator> decoder{g.begin(), g.end(), g.size() / 2};
dummy_geom_handler handler;
decoder.decode_polygon(handler);
REQUIRE(handler.result() == 10501);
}

343
test/t/test_index.cpp Normal file
View File

@ -0,0 +1,343 @@
#include <test.hpp>
#include <vtzero/builder.hpp>
#include <vtzero/index.hpp>
#include <map>
#include <string>
#include <unordered_map>
TEST_CASE("add keys to layer using key index built into layer") {
static constexpr const int max_keys = 100;
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
for (int n = 0; n < max_keys; ++n) {
const auto key = std::to_string(n);
const auto idx = lbuilder.add_key(key);
REQUIRE(n == idx.value());
}
for (int n = 0; n < max_keys; n += 2) {
const auto key = std::to_string(n);
const auto idx = lbuilder.add_key(key);
REQUIRE(n == idx.value());
}
}
TEST_CASE("add values to layer using value index built into layer") {
static constexpr const int max_values = 100;
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
for (int n = 0; n < max_values; ++n) {
const auto value = std::to_string(n);
const auto idx = lbuilder.add_value(vtzero::encoded_property_value{value});
REQUIRE(n == idx.value());
}
for (int n = 0; n < max_values; n += 2) {
const auto value = std::to_string(n);
const auto idx = lbuilder.add_value(vtzero::encoded_property_value{value});
REQUIRE(n == idx.value());
}
}
template <typename TIndex>
static void test_key_index() {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
TIndex index{lbuilder};
const auto i1 = index({"foo"});
const auto i2 = index({"bar"});
const auto i3 = index({"baz"});
const auto i4 = index({"foo"});
const auto i5 = index({"foo"});
const auto i6 = index({""});
const auto i7 = index({"bar"});
REQUIRE(i1 != i2);
REQUIRE(i1 != i3);
REQUIRE(i1 == i4);
REQUIRE(i1 == i5);
REQUIRE(i1 != i6);
REQUIRE(i1 != i7);
REQUIRE(i2 != i3);
REQUIRE(i2 != i4);
REQUIRE(i2 != i5);
REQUIRE(i2 != i6);
REQUIRE(i2 == i7);
REQUIRE(i3 != i4);
REQUIRE(i3 != i5);
REQUIRE(i3 != i6);
REQUIRE(i3 != i7);
REQUIRE(i4 == i5);
REQUIRE(i4 != i6);
REQUIRE(i4 != i7);
REQUIRE(i5 != i6);
REQUIRE(i5 != i7);
REQUIRE(i6 != i7);
}
TEST_CASE("key index based on std::unordered_map") {
test_key_index<vtzero::key_index<std::unordered_map>>();
}
TEST_CASE("key index based on std::map") {
test_key_index<vtzero::key_index<std::map>>();
}
template <typename TIndex>
static void test_value_index_internal() {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
TIndex index{lbuilder};
const auto i1 = index(vtzero::encoded_property_value{"foo"});
const auto i2 = index(vtzero::encoded_property_value{"bar"});
const auto i3 = index(vtzero::encoded_property_value{88});
const auto i4 = index(vtzero::encoded_property_value{"foo"});
const auto i5 = index(vtzero::encoded_property_value{77});
const auto i6 = index(vtzero::encoded_property_value{1.5});
const auto i7 = index(vtzero::encoded_property_value{"bar"});
REQUIRE(i1 != i2);
REQUIRE(i1 != i3);
REQUIRE(i1 == i4);
REQUIRE(i1 != i5);
REQUIRE(i1 != i6);
REQUIRE(i1 != i7);
REQUIRE(i2 != i3);
REQUIRE(i2 != i4);
REQUIRE(i2 != i5);
REQUIRE(i2 != i6);
REQUIRE(i2 == i7);
REQUIRE(i3 != i4);
REQUIRE(i3 != i5);
REQUIRE(i3 != i6);
REQUIRE(i3 != i7);
REQUIRE(i4 != i5);
REQUIRE(i4 != i6);
REQUIRE(i4 != i7);
REQUIRE(i5 != i6);
REQUIRE(i5 != i7);
REQUIRE(i6 != i7);
}
TEST_CASE("internal value index based on std::unordered_map") {
test_value_index_internal<vtzero::value_index_internal<std::unordered_map>>();
}
TEST_CASE("internal value index based on std::map") {
test_value_index_internal<vtzero::value_index_internal<std::map>>();
}
TEST_CASE("external value index") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::value_index<vtzero::string_value_type, std::string, std::map> string_index{lbuilder};
vtzero::value_index<vtzero::int_value_type, int, std::unordered_map> int_index{lbuilder};
vtzero::value_index<vtzero::sint_value_type, int, std::unordered_map> sint_index{lbuilder};
const auto i1 = string_index("foo");
const auto i2 = string_index("bar");
const auto i3 = int_index(6);
const auto i4 = sint_index(6);
const auto i5 = string_index(std::string{"foo"});
const auto i6 = int_index(6);
const auto i7 = sint_index(2);
const auto i8 = sint_index(5);
const auto i9 = sint_index(6);
REQUIRE(i1 != i2);
REQUIRE(i1 != i3);
REQUIRE(i1 != i4);
REQUIRE(i1 == i5);
REQUIRE(i1 != i6);
REQUIRE(i1 != i7);
REQUIRE(i1 != i7);
REQUIRE(i1 != i9);
REQUIRE(i2 != i3);
REQUIRE(i2 != i4);
REQUIRE(i2 != i5);
REQUIRE(i2 != i6);
REQUIRE(i2 != i7);
REQUIRE(i2 != i8);
REQUIRE(i2 != i9);
REQUIRE(i3 != i4);
REQUIRE(i3 != i5);
REQUIRE(i3 == i6);
REQUIRE(i3 != i7);
REQUIRE(i3 != i8);
REQUIRE(i3 != i9);
REQUIRE(i4 != i5);
REQUIRE(i4 != i6);
REQUIRE(i4 != i7);
REQUIRE(i4 != i8);
REQUIRE(i4 == i9);
REQUIRE(i5 != i6);
REQUIRE(i5 != i7);
REQUIRE(i5 != i8);
REQUIRE(i5 != i9);
REQUIRE(i6 != i7);
REQUIRE(i6 != i8);
REQUIRE(i6 != i9);
REQUIRE(i7 != i8);
REQUIRE(i7 != i9);
REQUIRE(i8 != i9);
}
TEST_CASE("bool value index") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::value_index_bool index{lbuilder};
const auto i1 = index(false);
const auto i2 = index(true);
const auto i3 = index(true);
const auto i4 = index(false);
REQUIRE(i1 != i2);
REQUIRE(i1 != i3);
REQUIRE(i1 == i4);
REQUIRE(i2 == i3);
REQUIRE(i2 != i4);
REQUIRE(i3 != i4);
}
TEST_CASE("small unsigned int value index") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::value_index_small_uint index{lbuilder};
const auto i1 = index(12);
const auto i2 = index(4);
const auto i3 = index(0);
const auto i4 = index(100);
const auto i5 = index(4);
const auto i6 = index(12);
REQUIRE(i1 != i2);
REQUIRE(i1 != i3);
REQUIRE(i1 != i4);
REQUIRE(i1 != i5);
REQUIRE(i1 == i6);
REQUIRE(i2 != i3);
REQUIRE(i2 != i4);
REQUIRE(i2 == i5);
REQUIRE(i2 != i6);
REQUIRE(i3 != i4);
REQUIRE(i3 != i5);
REQUIRE(i3 != i6);
REQUIRE(i4 != i5);
REQUIRE(i4 != i6);
REQUIRE(i5 != i6);
}
TEST_CASE("add features using a key index") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(7);
fbuilder.add_point(10, 20);
SECTION("no index") {
fbuilder.add_property("some_key", 12);
}
SECTION("key index using unordered_map") {
vtzero::key_index<std::unordered_map> index{lbuilder};
fbuilder.add_property(index("some_key"), 12);
}
SECTION("key index using map") {
vtzero::key_index<std::map> index{lbuilder};
fbuilder.add_property(index("some_key"), 12);
}
fbuilder.commit();
const std::string data = tbuilder.serialize();
// ============
vtzero::vector_tile tile{data};
REQUIRE(tile.count_layers() == 1);
auto layer = tile.next_layer();
REQUIRE(layer.num_features() == 1);
auto feature = layer.next_feature();
REQUIRE(feature.id() == 7);
const auto property = feature.next_property();
REQUIRE(property.value().int_value() == 12);
}
TEST_CASE("add features using a value index") {
vtzero::tile_builder tbuilder;
vtzero::layer_builder lbuilder{tbuilder, "test"};
const auto key = lbuilder.add_key("some_key");
vtzero::point_feature_builder fbuilder{lbuilder};
fbuilder.set_id(17);
fbuilder.add_point(10, 20);
SECTION("no index") {
fbuilder.add_property(key, vtzero::sint_value_type{12});
}
SECTION("external value index using unordered_map") {
vtzero::value_index<vtzero::sint_value_type, int, std::unordered_map> index{lbuilder};
fbuilder.add_property(key, index(12));
}
SECTION("external value index using map") {
vtzero::value_index<vtzero::sint_value_type, int, std::map> index{lbuilder};
fbuilder.add_property(key, index(12));
}
SECTION("property_value_type index") {
vtzero::value_index_internal<std::unordered_map> index{lbuilder};
fbuilder.add_property(key, index(vtzero::encoded_property_value{vtzero::sint_value_type{12}}));
}
fbuilder.commit();
const std::string data = tbuilder.serialize();
// ============
vtzero::vector_tile tile{data};
REQUIRE(tile.count_layers() == 1);
auto layer = tile.next_layer();
REQUIRE(layer.num_features() == 1);
auto feature = layer.next_feature();
REQUIRE(feature.id() == 17);
const auto property = feature.next_property();
REQUIRE(property.value().sint_value() == 12);
}

155
test/t/test_layer.cpp Normal file
View File

@ -0,0 +1,155 @@
#include <test.hpp>
#include <vtzero/layer.hpp>
#include <vtzero/vector_tile.hpp>
#include <cstddef>
TEST_CASE("default constructed layer") {
vtzero::layer layer{};
REQUIRE_FALSE(layer.valid());
REQUIRE_FALSE(layer);
REQUIRE(layer.data() == vtzero::data_view{});
REQUIRE_ASSERT(layer.version());
REQUIRE_ASSERT(layer.extent());
REQUIRE_ASSERT(layer.name());
REQUIRE(layer.empty());
REQUIRE(layer.num_features() == 0);
REQUIRE_THROWS_AS(layer.key_table(), const assert_error&);
REQUIRE_THROWS_AS(layer.value_table(), const assert_error&);
REQUIRE_THROWS_AS(layer.key(0), const assert_error&);
REQUIRE_THROWS_AS(layer.value(0), const assert_error&);
REQUIRE_THROWS_AS(layer.get_feature_by_id(0), const assert_error&);
REQUIRE_THROWS_AS(layer.next_feature(), const assert_error&);
REQUIRE_ASSERT(layer.reset_feature());
}
TEST_CASE("read a layer") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
auto layer = tile.get_layer_by_name("bridge");
REQUIRE(layer.valid());
REQUIRE(layer);
REQUIRE(layer.version() == 1);
REQUIRE(layer.extent() == 4096);
REQUIRE(layer.name() == "bridge");
REQUIRE_FALSE(layer.empty());
REQUIRE(layer.num_features() == 2);
const auto& kt = layer.key_table();
REQUIRE(kt.size() == 4);
REQUIRE(kt[0] == "class");
const auto& vt = layer.value_table();
REQUIRE(vt.size() == 4);
REQUIRE(vt[0].type() == vtzero::property_value_type::string_value);
REQUIRE(vt[0].string_value() == "main");
REQUIRE(vt[1].type() == vtzero::property_value_type::int_value);
REQUIRE(vt[1].int_value() == 0);
REQUIRE(layer.key(0) == "class");
REQUIRE(layer.key(1) == "oneway");
REQUIRE(layer.key(2) == "osm_id");
REQUIRE(layer.key(3) == "type");
REQUIRE_THROWS_AS(layer.key(4), const vtzero::out_of_range_exception&);
REQUIRE(layer.value(0).string_value() == "main");
REQUIRE(layer.value(1).int_value() == 0);
REQUIRE(layer.value(2).string_value() == "primary");
REQUIRE(layer.value(3).string_value() == "tertiary");
REQUIRE_THROWS_AS(layer.value(4), const vtzero::out_of_range_exception&);
}
TEST_CASE("access features in a layer by id") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
auto layer = tile.get_layer_by_name("building");
REQUIRE(layer);
REQUIRE(layer.num_features() == 937);
const auto feature = layer.get_feature_by_id(122);
REQUIRE(feature.id() == 122);
REQUIRE(feature.num_properties() == 0);
REQUIRE(feature.geometry_type() == vtzero::GeomType::POLYGON);
REQUIRE(feature.geometry().type() == vtzero::GeomType::POLYGON);
REQUIRE_FALSE(feature.geometry().data().empty());
REQUIRE_FALSE(layer.get_feature_by_id(844));
REQUIRE_FALSE(layer.get_feature_by_id(999999));
}
TEST_CASE("iterate over all features in a layer") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
auto layer = tile.get_layer_by_name("building");
REQUIRE(layer);
std::size_t count = 0;
SECTION("external iterator") {
while (auto feature = layer.next_feature()) {
++count;
}
}
SECTION("internal iterator") {
const bool done = layer.for_each_feature([&count](const vtzero::feature& /*feature*/) noexcept {
++count;
return true;
});
REQUIRE(done);
}
REQUIRE(count == 937);
}
TEST_CASE("iterate over some features in a layer") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
auto layer = tile.get_layer_by_name("building");
REQUIRE(layer);
uint64_t id_sum = 0;
SECTION("external iterator") {
while (auto feature = layer.next_feature()) {
if (feature.id() == 10) {
break;
}
id_sum += feature.id();
}
}
SECTION("internal iterator") {
const bool done = layer.for_each_feature([&id_sum](const vtzero::feature& feature) noexcept {
if (feature.id() == 10) {
return false;
}
id_sum += feature.id();
return true;
});
REQUIRE_FALSE(done);
}
const uint64_t expected = (10 - 1) * 10 / 2;
REQUIRE(id_sum == expected);
layer.reset_feature();
auto feature = layer.next_feature();
REQUIRE(feature);
REQUIRE(feature.id() == 1);
}

42
test/t/test_output.cpp Normal file
View File

@ -0,0 +1,42 @@
#include <test.hpp>
#include <sstream>
#include <string>
template <typename T>
std::string get_output(T v) {
std::stringstream ss;
ss << v;
return ss.str();
}
TEST_CASE("output GeomType") {
REQUIRE(get_output(vtzero::GeomType::UNKNOWN) == "unknown");
REQUIRE(get_output(vtzero::GeomType::POINT) == "point");
REQUIRE(get_output(vtzero::GeomType::LINESTRING) == "linestring");
REQUIRE(get_output(vtzero::GeomType::POLYGON) == "polygon");
}
TEST_CASE("output property_value_type") {
REQUIRE(get_output(vtzero::property_value_type::sint_value) == "sint");
}
TEST_CASE("output index_value") {
REQUIRE(get_output(vtzero::index_value{}) == "invalid");
REQUIRE(get_output(vtzero::index_value{5}) == "5");
}
TEST_CASE("output index_value_pair") {
const auto in = vtzero::index_value{};
const auto v2 = vtzero::index_value{2};
const auto v5 = vtzero::index_value{5};
REQUIRE(get_output(vtzero::index_value_pair{in, v2}) == "invalid");
REQUIRE(get_output(vtzero::index_value_pair{v2, v5}) == "[2,5]");
}
TEST_CASE("output point") {
REQUIRE(get_output(vtzero::point{}) == "(0,0)");
REQUIRE(get_output(vtzero::point{4, 7}) == "(4,7)");
}

25
test/t/test_point.cpp Normal file
View File

@ -0,0 +1,25 @@
#include <test.hpp>
#include <vtzero/geometry.hpp>
TEST_CASE("default constructed point") {
vtzero::point p{};
REQUIRE(p.x == 0);
REQUIRE(p.y == 0);
}
TEST_CASE("point") {
vtzero::point p1{4, 5};
vtzero::point p2{5, 4};
vtzero::point p3{4, 5};
REQUIRE(p1.x == 4);
REQUIRE(p1.y == 5);
REQUIRE_FALSE(p1 == p2);
REQUIRE(p1 != p2);
REQUIRE(p1 == p3);
}

View File

@ -0,0 +1,62 @@
#include <test.hpp>
#include <vtzero/builder.hpp>
#ifdef VTZERO_TEST_WITH_VARIANT
# include <boost/variant.hpp>
using variant_type = boost::variant<std::string, float, double, int64_t, uint64_t, bool>;
#endif
#include <map>
#include <string>
#include <unordered_map>
TEST_CASE("property map") {
vtzero::tile_builder tile;
vtzero::layer_builder layer_points{tile, "points"};
{
vtzero::point_feature_builder fbuilder{layer_points};
fbuilder.set_id(1);
fbuilder.add_points(1);
fbuilder.set_point(10, 10);
fbuilder.add_property("foo", "bar");
fbuilder.add_property("x", "y");
fbuilder.add_property("abc", "def");
fbuilder.commit();
}
std::string data = tile.serialize();
vtzero::vector_tile vt{data};
REQUIRE(vt.count_layers() == 1);
auto layer = vt.next_layer();
REQUIRE(layer.valid());
REQUIRE(layer.num_features() == 1);
const auto feature = layer.next_feature();
REQUIRE(feature.valid());
REQUIRE(feature.num_properties() == 3);
#ifdef VTZERO_TEST_WITH_VARIANT
SECTION("std::map") {
using prop_map_type = std::map<std::string, variant_type>;
auto map = vtzero::create_properties_map<prop_map_type>(feature);
REQUIRE(map.size() == 3);
REQUIRE(boost::get<std::string>(map["foo"]) == "bar");
REQUIRE(boost::get<std::string>(map["x"]) == "y");
REQUIRE(boost::get<std::string>(map["abc"]) == "def");
}
SECTION("std::unordered_map") {
using prop_map_type = std::unordered_map<std::string, variant_type>;
auto map = vtzero::create_properties_map<prop_map_type>(feature);
REQUIRE(map.size() == 3);
REQUIRE(boost::get<std::string>(map["foo"]) == "bar");
REQUIRE(boost::get<std::string>(map["x"]) == "y");
REQUIRE(boost::get<std::string>(map["abc"]) == "def");
}
#endif
}

View File

@ -0,0 +1,407 @@
#include <test.hpp>
#include <vtzero/encoded_property_value.hpp>
#include <vtzero/property.hpp>
#include <vtzero/property_value.hpp>
#include <vtzero/types.hpp>
#ifdef VTZERO_TEST_WITH_VARIANT
# include <boost/variant.hpp>
using variant_type = boost::variant<std::string, float, double, int64_t, uint64_t, bool>;
struct variant_mapping : vtzero::property_value_mapping {
using float_type = int64_t;
using double_type = int64_t;
};
#endif
#include <string>
struct visitor_test_void {
int x = 0;
template <typename T>
void operator()(T /*value*/) {
x = 1;
}
void operator()(vtzero::data_view /*value*/) {
x = 2;
}
};
struct visitor_test_int {
template <typename T>
int operator()(T /*value*/) {
return 1;
}
int operator()(vtzero::data_view /*value*/) {
return 2;
}
};
struct visitor_test_to_string {
template <typename T>
std::string operator()(T value) {
return std::to_string(value);
}
std::string operator()(vtzero::data_view value) {
return std::string{value.data(), value.size()};
}
};
struct string_conv {
std::string s;
template <typename T>
explicit string_conv(T value) :
s(std::to_string(value)) {
}
explicit operator std::string() {
return s;
}
};
struct string_mapping : vtzero::property_value_mapping {
using string_type = std::string;
using float_type = string_conv;
using double_type = string_conv;
using int_type = string_conv;
using uint_type = string_conv;
using bool_type = string_conv;
};
TEST_CASE("default constructed property_value") {
vtzero::property_value pv;
REQUIRE_FALSE(pv.valid());
REQUIRE(pv.data().data() == nullptr);
REQUIRE(pv == vtzero::property_value{});
REQUIRE_FALSE(pv != vtzero::property_value{});
}
TEST_CASE("empty property_value") {
char x[1] = {0};
vtzero::data_view dv{x, 0};
vtzero::property_value pv{dv};
REQUIRE(pv.valid());
REQUIRE_THROWS_AS(pv.type(), const vtzero::format_exception&);
}
TEST_CASE("string value") {
vtzero::encoded_property_value epv{"foo"};
vtzero::property_value pv{epv.data()};
REQUIRE(pv.string_value() == "foo");
visitor_test_void vt;
vtzero::apply_visitor(vt, pv);
REQUIRE(vt.x == 2);
const auto result = vtzero::apply_visitor(visitor_test_int{}, pv);
REQUIRE(result == 2);
const auto str = vtzero::apply_visitor(visitor_test_to_string{}, pv);
REQUIRE(str == "foo");
const std::string cs = vtzero::convert_property_value<std::string, string_mapping>(pv);
REQUIRE(cs == "foo");
#ifdef VTZERO_TEST_WITH_VARIANT
const auto vari = vtzero::convert_property_value<variant_type>(pv);
REQUIRE(boost::get<std::string>(vari) == "foo");
const auto conv = vtzero::convert_property_value<variant_type, variant_mapping>(pv);
REQUIRE(boost::get<std::string>(conv) == "foo");
#endif
}
TEST_CASE("float value") {
vtzero::encoded_property_value epv{1.2f};
vtzero::property_value pv{epv.data()};
REQUIRE(pv.float_value() == Approx(1.2));
visitor_test_void vt;
vtzero::apply_visitor(vt, pv);
REQUIRE(vt.x == 1);
const auto result = vtzero::apply_visitor(visitor_test_int{}, pv);
REQUIRE(result == 1);
const std::string cs = vtzero::convert_property_value<std::string, string_mapping>(pv);
REQUIRE(cs == "1.200000");
#ifdef VTZERO_TEST_WITH_VARIANT
const auto vari = vtzero::convert_property_value<variant_type>(pv);
REQUIRE(boost::get<float>(vari) == Approx(1.2));
const auto conv = vtzero::convert_property_value<variant_type, variant_mapping>(pv);
REQUIRE(boost::get<int64_t>(conv) == 1);
#endif
}
TEST_CASE("double value") {
vtzero::encoded_property_value epv{3.4};
vtzero::property_value pv{epv.data()};
REQUIRE(pv.double_value() == Approx(3.4));
const auto result = vtzero::apply_visitor(visitor_test_int{}, pv);
REQUIRE(result == 1);
const std::string cs = vtzero::convert_property_value<std::string, string_mapping>(pv);
REQUIRE(cs == "3.400000");
}
TEST_CASE("int value") {
vtzero::encoded_property_value epv{vtzero::int_value_type{42}};
vtzero::property_value pv{epv.data()};
REQUIRE(pv.int_value() == 42);
const auto str = vtzero::apply_visitor(visitor_test_to_string{}, pv);
REQUIRE(str == "42");
const std::string cs = vtzero::convert_property_value<std::string, string_mapping>(pv);
REQUIRE(cs == "42");
}
TEST_CASE("uint value") {
vtzero::encoded_property_value epv{vtzero::uint_value_type{99}};
vtzero::property_value pv{epv.data()};
REQUIRE(pv.uint_value() == 99);
const auto str = vtzero::apply_visitor(visitor_test_to_string{}, pv);
REQUIRE(str == "99");
const std::string cs = vtzero::convert_property_value<std::string, string_mapping>(pv);
REQUIRE(cs == "99");
}
TEST_CASE("sint value") {
vtzero::encoded_property_value epv{vtzero::sint_value_type{42}};
vtzero::property_value pv{epv.data()};
REQUIRE(pv.sint_value() == 42);
const auto str = vtzero::apply_visitor(visitor_test_to_string{}, pv);
REQUIRE(str == "42");
const std::string cs = vtzero::convert_property_value<std::string, string_mapping>(pv);
REQUIRE(cs == "42");
}
TEST_CASE("bool value") {
vtzero::encoded_property_value epv{true};
vtzero::property_value pv{epv.data()};
REQUIRE(pv.bool_value());
const auto str = vtzero::apply_visitor(visitor_test_to_string{}, pv);
REQUIRE(str == "1");
const std::string cs = vtzero::convert_property_value<std::string, string_mapping>(pv);
REQUIRE(cs == "1");
}
TEST_CASE("property and property_value equality comparisons") {
vtzero::encoded_property_value t{true};
vtzero::encoded_property_value f{false};
vtzero::encoded_property_value v1{vtzero::int_value_type{1}};
vtzero::encoded_property_value vs{"foo"};
REQUIRE(t == t);
REQUIRE_FALSE(t != t);
REQUIRE_FALSE(t == f);
REQUIRE_FALSE(t == v1);
REQUIRE_FALSE(t == vs);
using pv = vtzero::property_value;
REQUIRE(pv{t.data()} == pv{t.data()});
REQUIRE_FALSE(pv{t.data()} != pv{t.data()});
REQUIRE_FALSE(pv{t.data()} == pv{f.data()});
REQUIRE_FALSE(pv{t.data()} == pv{v1.data()});
REQUIRE_FALSE(pv{t.data()} == pv{vs.data()});
}
TEST_CASE("property and property_value ordering") {
using pv = vtzero::property_value;
vtzero::encoded_property_value t{true};
vtzero::encoded_property_value f{false};
REQUIRE_FALSE(t < f);
REQUIRE_FALSE(t <= f);
REQUIRE(t > f);
REQUIRE(t >= f);
REQUIRE_FALSE(pv{t.data()} < pv{f.data()});
REQUIRE_FALSE(pv{t.data()} <= pv{f.data()});
REQUIRE(pv{t.data()} > pv{f.data()});
REQUIRE(pv{t.data()} >= pv{f.data()});
vtzero::encoded_property_value v1{vtzero::int_value_type{22}};
vtzero::encoded_property_value v2{vtzero::int_value_type{17}};
REQUIRE_FALSE(v1 < v2);
REQUIRE_FALSE(v1 <= v2);
REQUIRE(v1 > v2);
REQUIRE(v1 >= v2);
REQUIRE_FALSE(pv{v1.data()} < pv{v2.data()});
REQUIRE_FALSE(pv{v1.data()} <= pv{v2.data()});
REQUIRE(pv{v1.data()} > pv{v2.data()});
REQUIRE(pv{v1.data()} >= pv{v2.data()});
vtzero::encoded_property_value vsf{"foo"};
vtzero::encoded_property_value vsb{"bar"};
vtzero::encoded_property_value vsx{"foobar"};
REQUIRE_FALSE(vsf < vsb);
REQUIRE_FALSE(vsf <= vsb);
REQUIRE(vsf > vsb);
REQUIRE(vsf >= vsb);
REQUIRE_FALSE(pv{vsf.data()} < pv{vsb.data()});
REQUIRE_FALSE(pv{vsf.data()} <= pv{vsb.data()});
REQUIRE(pv{vsf.data()} > pv{vsb.data()});
REQUIRE(pv{vsf.data()} >= pv{vsb.data()});
REQUIRE(vsf < vsx);
REQUIRE(vsf <= vsx);
REQUIRE_FALSE(vsf > vsx);
REQUIRE_FALSE(vsf >= vsx);
REQUIRE(pv{vsf.data()} < pv{vsx.data()});
REQUIRE(pv{vsf.data()} <= pv{vsx.data()});
REQUIRE_FALSE(pv{vsf.data()} > pv{vsx.data()});
REQUIRE_FALSE(pv{vsf.data()} >= pv{vsx.data()});
}
TEST_CASE("default constructed property") {
vtzero::property p;
REQUIRE_FALSE(p.valid());
REQUIRE_FALSE(p);
REQUIRE(p.key().data() == nullptr);
REQUIRE(p.value().data().data() == nullptr);
}
TEST_CASE("valid property") {
vtzero::data_view k{"key"};
vtzero::encoded_property_value epv{"value"};
vtzero::property_value pv{epv.data()};
vtzero::property p{k, pv};
REQUIRE(p.key() == "key");
REQUIRE(p.value().string_value() == "value");
}
TEST_CASE("create encoded property values from different string types") {
const std::string v{"value"};
vtzero::encoded_property_value epv1{vtzero::string_value_type{"value"}};
vtzero::encoded_property_value epv2{"value"};
vtzero::encoded_property_value epv3{v};
vtzero::encoded_property_value epv4{vtzero::data_view{v}};
vtzero::encoded_property_value epv5{"valuexxxxxxxxx", 5};
REQUIRE(epv1 == epv2);
REQUIRE(epv1 == epv3);
REQUIRE(epv1 == epv4);
REQUIRE(epv1 == epv5);
}
TEST_CASE("create encoded property values from different floating point types") {
vtzero::encoded_property_value f1{vtzero::float_value_type{3.2f}};
vtzero::encoded_property_value f2{3.2f};
vtzero::encoded_property_value d1{vtzero::double_value_type{3.2}};
vtzero::encoded_property_value d2{3.2};
REQUIRE(f1 == f2);
REQUIRE(d1 == d2);
vtzero::property_value pvf{f1.data()};
vtzero::property_value pvd{d1.data()};
REQUIRE(pvf.float_value() == Approx(pvd.double_value()));
}
TEST_CASE("create encoded property values from different integer types") {
vtzero::encoded_property_value i1{vtzero::int_value_type{7}};
vtzero::encoded_property_value i2{int64_t(7)};
vtzero::encoded_property_value i3{int32_t(7)};
vtzero::encoded_property_value i4{int16_t(7)};
vtzero::encoded_property_value u1{vtzero::uint_value_type{7}};
vtzero::encoded_property_value u2{uint64_t(7)};
vtzero::encoded_property_value u3{uint32_t(7)};
vtzero::encoded_property_value u4{uint16_t(7)};
vtzero::encoded_property_value s1{vtzero::sint_value_type{7}};
REQUIRE(i1 == i2);
REQUIRE(i1 == i3);
REQUIRE(i1 == i4);
REQUIRE(u1 == u2);
REQUIRE(u1 == u3);
REQUIRE(u1 == u4);
REQUIRE_FALSE(i1 == u1);
REQUIRE_FALSE(i1 == s1);
REQUIRE_FALSE(u1 == s1);
REQUIRE(i1.hash() == i2.hash());
REQUIRE(u1.hash() == u2.hash());
vtzero::property_value pvi{i1.data()};
vtzero::property_value pvu{u1.data()};
vtzero::property_value pvs{s1.data()};
REQUIRE(pvi.int_value() == pvu.uint_value());
REQUIRE(pvi.int_value() == pvs.sint_value());
}
TEST_CASE("create encoded property values from different bool types") {
vtzero::encoded_property_value b1{vtzero::bool_value_type{true}};
vtzero::encoded_property_value b2{true};
REQUIRE(b1 == b2);
REQUIRE(b1.hash() == b2.hash());
}
TEST_CASE("property equality comparison operator") {
std::string k = "key";
vtzero::encoded_property_value epv1{"value"};
vtzero::encoded_property_value epv2{"another value"};
vtzero::property_value pv1{epv1.data()};
vtzero::property_value pv2{epv2.data()};
vtzero::property p1{k, pv1};
vtzero::property p2{k, pv1};
vtzero::property p3{k, pv2};
REQUIRE(p1 == p2);
REQUIRE_FALSE(p1 == p3);
}
TEST_CASE("property inequality comparison operator") {
std::string k1 = "key";
std::string k2 = "another_key";
vtzero::encoded_property_value epv1{"value"};
vtzero::encoded_property_value epv2{"another value"};
vtzero::property_value pv1{epv1.data()};
vtzero::property_value pv2{epv2.data()};
vtzero::property p1{k1, pv1};
vtzero::property p2{k1, pv1};
vtzero::property p3{k1, pv2};
vtzero::property p4{k2, pv2};
REQUIRE_FALSE(p1 != p2);
REQUIRE(p1 != p3);
REQUIRE(p3 != p4);
}

116
test/t/test_types.cpp Normal file
View File

@ -0,0 +1,116 @@
#include <test.hpp>
#include <vtzero/types.hpp>
#include <string>
TEST_CASE("default constructed string_value_type") {
vtzero::string_value_type v;
REQUIRE(v.value.data() == nullptr);
}
TEST_CASE("string_value_type with value") {
vtzero::string_value_type v{"foo"};
REQUIRE(v.value.data()[0] == 'f');
REQUIRE(v.value.size() == 3);
}
TEST_CASE("default constructed float_value_type") {
vtzero::float_value_type v;
REQUIRE(v.value == Approx(0.0));
}
TEST_CASE("float_value_type with value") {
float x = 2.7f;
vtzero::float_value_type v{x};
REQUIRE(v.value == Approx(x));
}
TEST_CASE("default constructed double_value_type") {
vtzero::double_value_type v;
REQUIRE(v.value == Approx(0.0));
}
TEST_CASE("double_value_type with value") {
double x = 2.7;
vtzero::double_value_type v{x};
REQUIRE(v.value == Approx(x));
}
TEST_CASE("default constructed int_value_type") {
vtzero::int_value_type v;
REQUIRE(v.value == 0);
}
TEST_CASE("int_value_type with value") {
vtzero::int_value_type v{123};
REQUIRE(v.value == 123);
}
TEST_CASE("default constructed uint_value_type") {
vtzero::uint_value_type v;
REQUIRE(v.value == 0);
}
TEST_CASE("uint_value_type with value") {
vtzero::uint_value_type v{123};
REQUIRE(v.value == 123);
}
TEST_CASE("default constructed sint_value_type") {
vtzero::sint_value_type v;
REQUIRE(v.value == 0);
}
TEST_CASE("sint_value_type with value") {
vtzero::sint_value_type v{-14};
REQUIRE(v.value == -14);
}
TEST_CASE("default constructed bool_value_type") {
vtzero::bool_value_type v;
REQUIRE_FALSE(v.value);
}
TEST_CASE("bool_value_type with value") {
bool x = true;
vtzero::bool_value_type v{x};
REQUIRE(v.value);
}
TEST_CASE("property_value_type names") {
REQUIRE(std::string{vtzero::property_value_type_name(vtzero::property_value_type::string_value)} == "string");
REQUIRE(std::string{vtzero::property_value_type_name(vtzero::property_value_type::float_value)} == "float");
REQUIRE(std::string{vtzero::property_value_type_name(vtzero::property_value_type::double_value)} == "double");
REQUIRE(std::string{vtzero::property_value_type_name(vtzero::property_value_type::int_value)} == "int");
REQUIRE(std::string{vtzero::property_value_type_name(vtzero::property_value_type::uint_value)} == "uint");
REQUIRE(std::string{vtzero::property_value_type_name(vtzero::property_value_type::sint_value)} == "sint");
REQUIRE(std::string{vtzero::property_value_type_name(vtzero::property_value_type::bool_value)} == "bool");
}
TEST_CASE("default constructed index value") {
vtzero::index_value v;
REQUIRE_FALSE(v.valid());
REQUIRE_ASSERT(v.value());
}
TEST_CASE("valid index value") {
vtzero::index_value v{32};
REQUIRE(v.valid());
REQUIRE(v.value() == 32);
}
TEST_CASE("default constructed geometry") {
vtzero::geometry geom;
REQUIRE(geom.type() == vtzero::GeomType::UNKNOWN);
REQUIRE(geom.data().empty());
}
TEST_CASE("GeomType names") {
REQUIRE(std::string{vtzero::geom_type_name(vtzero::GeomType::UNKNOWN)} == "unknown");
REQUIRE(std::string{vtzero::geom_type_name(vtzero::GeomType::POINT)} == "point");
REQUIRE(std::string{vtzero::geom_type_name(vtzero::GeomType::LINESTRING)} == "linestring");
REQUIRE(std::string{vtzero::geom_type_name(vtzero::GeomType::POLYGON)} == "polygon");
}

139
test/t/test_vector_tile.cpp Normal file
View File

@ -0,0 +1,139 @@
#include <test.hpp>
#include <vtzero/vector_tile.hpp>
#include <string>
#include <vector>
TEST_CASE("open a vector tile with string") {
const auto data = load_test_tile();
REQUIRE(vtzero::is_vector_tile(data));
vtzero::vector_tile tile{data};
REQUIRE_FALSE(tile.empty());
REQUIRE(tile.count_layers() == 12);
}
TEST_CASE("open a vector tile with data_view") {
const auto data = load_test_tile();
const vtzero::data_view dv{data};
vtzero::vector_tile tile{dv};
REQUIRE_FALSE(tile.empty());
REQUIRE(tile.count_layers() == 12);
}
TEST_CASE("open a vector tile with pointer and size") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data.data(), data.size()};
REQUIRE_FALSE(tile.empty());
REQUIRE(tile.count_layers() == 12);
}
TEST_CASE("get layer by index") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
auto layer = tile.get_layer(0);
REQUIRE(layer);
REQUIRE(layer.name() == "landuse");
layer = tile.get_layer(1);
REQUIRE(layer);
REQUIRE(layer.name() == "waterway");
layer = tile.get_layer(11);
REQUIRE(layer);
REQUIRE(layer.name() == "waterway_label");
layer = tile.get_layer(12);
REQUIRE_FALSE(layer);
}
TEST_CASE("get layer by name") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
auto layer = tile.get_layer_by_name("landuse");
REQUIRE(layer);
REQUIRE(layer.name() == "landuse");
layer = tile.get_layer_by_name(std::string{"road"});
REQUIRE(layer);
REQUIRE(layer.name() == "road");
const vtzero::data_view name{"poi_label"};
layer = tile.get_layer_by_name(name);
REQUIRE(layer);
REQUIRE(layer.name() == "poi_label");
layer = tile.get_layer_by_name("unknown");
REQUIRE_FALSE(layer);
}
TEST_CASE("iterate over layers") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
std::vector<std::string> names;
SECTION("external iterator") {
while (auto layer = tile.next_layer()) {
names.emplace_back(layer.name());
}
}
SECTION("internal iterator") {
const bool done = tile.for_each_layer([&names](const vtzero::layer& layer) {
names.emplace_back(layer.name());
return true;
});
REQUIRE(done);
}
REQUIRE(names.size() == 12);
static std::vector<std::string> expected = {
"landuse", "waterway", "water", "barrier_line", "building", "road",
"bridge", "place_label", "water_label", "poi_label", "road_label",
"waterway_label"
};
REQUIRE(names == expected);
tile.reset_layer();
int num = 0;
while (auto layer = tile.next_layer()) {
++num;
}
REQUIRE(num == 12);
}
TEST_CASE("iterate over some of the layers") {
const auto data = load_test_tile();
vtzero::vector_tile tile{data};
int num_layers = 0;
SECTION("external iterator") {
while (auto layer = tile.next_layer()) {
++num_layers;
if (layer.name() == "water") {
break;
}
}
}
SECTION("internal iterator") {
const bool done = tile.for_each_layer([&num_layers](const vtzero::layer& layer) noexcept {
++num_layers;
return layer.name() != "water";
});
REQUIRE_FALSE(done);
}
REQUIRE(num_layers == 3);
}

25
test/test_main.cpp Normal file
View File

@ -0,0 +1,25 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include <test.hpp>
#include <fstream>
#include <stdexcept>
#include <string>
bool got_an_assert = false;
std::string load_test_tile() {
std::string path{"data/mapbox-streets-v6-14-8714-8017.mvt"};
std::ifstream stream{path, std::ios_base::in|std::ios_base::binary};
if (!stream.is_open()) {
throw std::runtime_error{"could not open: '" + path + "'"};
}
const std::string message{std::istreambuf_iterator<char>(stream.rdbuf()),
std::istreambuf_iterator<char>()};
stream.close();
return message;
}