[mapnik-vector-tile] 11/12: Imported Upstream version 0.8.0+dfsg
Jérémy Lal
kapouer at moszumanska.debian.org
Wed May 27 20:10:29 UTC 2015
This is an automated email from the git hooks/post-receive script.
kapouer pushed a commit to branch master
in repository mapnik-vector-tile.
commit c7a1ef381ae43ebd1a084b88a58d032c8940fdad
Author: Jérémy Lal <kapouer at melix.org>
Date: Wed May 27 22:09:44 2015 +0200
Imported Upstream version 0.8.0+dfsg
---
.gitignore | 5 +-
.travis.yml | 69 +-
CHANGELOG.md | 52 +-
Makefile | 49 +-
README.md | 21 +-
bootstrap.sh | 69 ++
examples/c++/tileinfo.cpp | 35 +-
gyp/build.gyp | 153 +++
gyp/common.gypi | 143 +++
package.json | 2 +-
proto/vector_tile.proto | 67 +-
scripts/build.sh | 11 +
scripts/coverage.sh | 25 +
src/mapnik3x_compatibility.hpp | 41 -
src/vector_tile_backend_pbf.cpp | 2 +
src/vector_tile_backend_pbf.hpp | 186 +--
src/vector_tile_backend_pbf.ipp | 157 +++
src/vector_tile_compression.cpp | 2 +
src/vector_tile_compression.hpp | 77 +-
...compression.hpp => vector_tile_compression.ipp} | 25 +-
src/vector_tile_config.hpp | 11 +
src/vector_tile_datasource.cpp | 2 +
src/vector_tile_datasource.hpp | 361 +-----
...e_datasource.hpp => vector_tile_datasource.ipp} | 228 ++--
src/vector_tile_geometry_decoder.hpp | 328 +++++
src/vector_tile_geometry_encoder.hpp | 270 ++---
src/vector_tile_processor.cpp | 4 +
src/vector_tile_processor.hpp | 468 +------
src/vector_tile_processor.ipp | 1277 ++++++++++++++++++++
src/vector_tile_projection.cpp | 2 +
src/vector_tile_projection.hpp | 47 +-
src/vector_tile_projection.ipp | 41 +
src/vector_tile_util.cpp | 2 +
src/vector_tile_util.hpp | 209 +---
src/vector_tile_util.ipp | 160 +++
test/data/256x256.png | Bin 0 -> 915 bytes
test/data/natural_earth.tif | Bin 0 -> 3299605 bytes
test/data/poly.geojson | 1 +
test/{ => data}/raster_style.xml | 0
test/{ => data}/style.xml | 0
test/encoding_util.hpp | 201 ++-
test/fixtures/expected-1.png | Bin 0 -> 503 bytes
test/fixtures/expected-2.jpeg | Bin 0 -> 45660 bytes
test/fixtures/expected-2.png | Bin 0 -> 423130 bytes
test/fixtures/expected-3.png | Bin 0 -> 915 bytes
test/geometry_encoding.cpp | 553 +++++++--
test/python/test.py | 163 ---
test/raster_tile.cpp | 200 ++-
test/test_main.cpp | 14 +
test/test_utils.cpp | 89 ++
test/test_utils.hpp | 54 +-
test/vector_tile.cpp | 877 ++++++++++++--
52 files changed, 4574 insertions(+), 2179 deletions(-)
diff --git a/.gitignore b/.gitignore
index 19f5f78..02cdd31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+*.swp
+build
+deps
archive
python
.DS_Store
@@ -10,4 +13,4 @@ test/run-raster-test
*pyc
archive
TODO.md
-examples/c++/tileinfo
\ No newline at end of file
+examples/c++/tileinfo
diff --git a/.travis.yml b/.travis.yml
index eb72b61..165123e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,37 +1,50 @@
-language: cpp
+language: c
-compiler:
- - clang
- - gcc
+git:
+ depth: 10
+
+sudo: false
+
+matrix:
+ include:
+ # Broken: https://github.com/mapbox/mapnik-vector-tile/issues/103
+ #- os: linux
+ # compiler: gcc
+ # env: JOBS=2 CXX=g++-4.9
+ # addons:
+ # apt:
+ # sources: ['ubuntu-toolchain-r-test']
+ # packages: ['g++-4.9']
+ - os: linux
+ compiler: clang
+ env: JOBS=10 CXX=clang++-3.5
+ sudo: false
+ addons:
+ apt:
+ sources: ['ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5']
+ packages: ['clang-3.5']
+ - os: linux
+ compiler: clang
+ env: JOBS=10 CXX=clang++-3.5 BUILDTYPE=Debug
+ sudo: false
+ addons:
+ apt:
+ sources: ['ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5']
+ packages: ['clang-3.5']
+ - os: osx
+ compiler: clang
+ env: JOBS=10 COVERAGE=true BUILDTYPE=Debug
+ sudo: false
before_install:
- #- sudo apt-add-repository --yes ppa:mapnik/v2.2.0
- - sudo apt-add-repository --yes ppa:mapnik/nightly-2.3
- #- sudo apt-add-repository --yes ppa:mapnik/nightly-trunk
- - sudo apt-get update -y
+ - source ./bootstrap.sh
install:
- - sudo apt-get -y install libprotobuf7 libprotobuf-dev protobuf-compiler g++ gcc
- #- sudo apt-get -y install gcc-4.8 g++-4.8
+ - source ./scripts/build.sh
before_script:
- #- sudo apt-get -y install libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0*
- #- make clean
- #- make test
- #- sudo apt-get purge libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0*
+ - ./scripts/coverage.sh
script:
- - sudo apt-get -y install libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* mapnik-input-plugin*=2.3.0*
- - make clean
- - make test
- #- sudo apt-get purge libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* mapnik-input-plugin*=2.3.0*
- #- sudo apt-get -qq install libmapnik=3.0.0* mapnik-utils=3.0.0* libmapnik-dev=3.0.0* mapnik-input-plugin*=3.0.0*
- #- if [ "${CXX}" = 'g++' ]; then export CXX="g++-4.8" && export CC="gcc-4.8"; fi;
- #- make clean
- #- make test
-
-notifications:
- irc:
- channels:
- - "irc.freenode.org#mapnik"
- use_notice: true
+ # make sure tileinfo command works
+ - ./build/${BUILDTYPE:-Release}/tileinfo examples/data/14_2620_6331.vector.pbf.z
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 495c3db..72c1c12 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,54 @@
-# Changlog
+# Changelog
+
+## 0.8.0
+
+ - Now using `boost::geometry` to clip lines and `ClipperLib` to clip polygons
+ - Now splitting geometry collections into multiple features
+ - Updated to new Mapnik 3.x geometry storage
+ - Added support for simplifying geometries using `boost::geometry::simplify`
+ - Added `area_threshold` option to throw out small polygons
+
+## 0.7.1
+
+ - Minor build fixes
+
+## 0.7.0
+
+ - First release series to exclusively focus on upcoming Mapnik 3.x release.
+
+## 0.6.2
+
+ - The 0.6.x series will no longer support Mapnik 3.x going forward and will
+ instead only maintain Mapnik 2.x support
+ - Minor fixes to compile against latest Mapnik 3.x (works with ec2d644f6b698f)
+
+## 0.6.1
+
+ - `tile_datasource` now has optional 6th arg to trigger exploding multipart geometries when decoding
+
+## 0.6.0
+
+ - Adapted to vector tile v1.0.1 spec
+ - Fixed compile with g++ / clashing namespace with protobuf
+
+## 0.5.6
+
+ - Fix build against latest Mapnik 3.x
+
+## 0.5.5
+
+ - Optimize raster rendering by clipping rasters to unbuffered tile extent when overzooming
+
+## 0.5.4
+
+ - Fixed bug in line intersection test
+
+## 0.5.3
+
+ - Fixed setting of `painted` property when a raster is successfully added
+ - Added support for testing line intersections in is_solid check
+ - Updated to work with latest Mapnik 3.x
+ - Improved test coverage
## 0.5.2
diff --git a/Makefile b/Makefile
index d6f07d6..71b405d 100755
--- a/Makefile
+++ b/Makefile
@@ -1,46 +1,23 @@
-PROTOBUF_CXXFLAGS=$(shell pkg-config protobuf --cflags)
-PROTOBUF_LDFLAGS=$(shell pkg-config protobuf --libs-only-L) -lprotobuf-lite
-MAPNIK_CXXFLAGS=$(shell mapnik-config --cflags) -Wsign-compare
-MAPNIK_LDFLAGS=$(shell mapnik-config --libs --ldflags --dep-libs)
-COMMON_FLAGS = -Wall -Wshadow -pedantic -Wno-c++11-long-long -Wno-c++11-extensions
-#-Wsign-compare -Wsign-conversion -Wunused-parameter
-# inherit from env
-CXX := $(CXX)
-CXXFLAGS := $(CXXFLAGS)
-LDFLAGS := $(LDFLAGS)
MAPNIK_PLUGINDIR := $(shell mapnik-config --input-plugins)
+BUILDTYPE ?= Release
-all: mapnik-vector-tile
+all: libvtile
-mapnik-vector-tile: src/vector_tile.pb.cc Makefile
+./deps/gyp:
+ git clone https://chromium.googlesource.com/external/gyp.git ./deps/gyp && cd ./deps/gyp && git checkout 3464008
-src/vector_tile.pb.cc: proto/vector_tile.proto
- protoc -Iproto/ --cpp_out=./src proto/vector_tile.proto
+build/Makefile: ./deps/gyp gyp/build.gyp test/*cpp
+ deps/gyp/gyp gyp/build.gyp --depth=. -DMAPNIK_PLUGINDIR=\"$(MAPNIK_PLUGINDIR)\" -Goutput_dir=. --generator-output=./build -f make
-test/run-test: Makefile src/vector_tile.pb.cc test/vector_tile.cpp test/test_utils.hpp src/*
- $(CXX) -o ./test/run-test test/vector_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field
+libvtile: build/Makefile Makefile
+ @$(MAKE) -C build/ BUILDTYPE=$(BUILDTYPE) V=$(V)
-test: test/run-test test/run-geom-test ./test/run-raster-test src/vector_tile.pb.cc test/catch.hpp
- ./test/run-test
- ./test/run-geom-test
- ./test/run-raster-test
-
-./test/run-raster-test: Makefile src/vector_tile.pb.cc test/raster_tile.cpp test/encoding_util.hpp test/catch.hpp
- $(CXX) -o ./test/run-raster-test test/raster_tile.cpp src/vector_tile.pb.cc -DMAPNIK_PLUGINDIR=\"$(MAPNIK_PLUGINDIR)\" -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field
- ./test/run-raster-test
-
-./test/run-geom-test: Makefile src/vector_tile.pb.cc test/geometry_encoding.cpp test/encoding_util.hpp src/vector_tile_geometry_encoder.hpp test/catch.hpp
- @$(CXX) -o ./test/run-geom-test test/geometry_encoding.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field
-
-geom: test/run-geom-test src/vector_tile.pb.cc
- ./test/run-geom-test
+test: libvtile
+ ./build/$(BUILDTYPE)/tests
clean:
- @rm -f ./src/vector_tile.pb.cc
- @rm -f ./src/vector_tile.pb.h
- @rm -f ./test/test-cfg.h
- @rm -f ./test/run-test
- @rm -f ./test/run-geom-test
- @rm -f ./test/run-raster-test
+ rm -rf ./build
.PHONY: test
+
+
diff --git a/README.md b/README.md
index 0314ef4..f47e297 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,19 @@
## mapnik-vector-tile
-[![Build Status](https://secure.travis-ci.org/mapbox/mapnik-vector-tile.png)](http://travis-ci.org/mapbox/mapnik-vector-tile)
+A Mapnik implemention of [Mapbox Vector Tile specification](https://github.com/mapbox/vector-tile-spec).
-A high performance library for working with vector tiles.
+Provides C++ headers that support rendering geodata into vector tiles and rendering vector tiles into images.
-Provides C++ headers that support rendering geodata into vector tiles
-and rendering vector tiles into images.
+ - Master: [![Build Status](https://travis-ci.org/mapbox/mapnik-vector-tile.svg?branch=master)](https://travis-ci.org/mapbox/mapnik-vector-tile)
+ - 0.6.x series: [![Build Status](https://secure.travis-ci.org/mapbox/mapnik-vector-tile.svg?branch=0.6.x)](http://travis-ci.org/mapbox/mapnik-vector-tile)
+
+[![Coverage Status](https://coveralls.io/repos/mapbox/mapnik-vector-tile/badge.svg?branch=coverage)](https://coveralls.io/r/mapbox/mapnik-vector-tile?branch=coverage)
## Depends
- - Mapnik > = v2.2.x: `libmapnik` and `mapnik-config`
+ - mapnik-vector-tile 0.7.x depends on Mapnik v3.0.x (until 3.0.0 is released this means latest mapnik HEAD)
+ - mapnik-vector-tile 0.6.x and previous work with Mapnik v2.2.x or v2.3.x
+ - You will need `libmapnik` and `mapnik-config` available
- Protobuf: `libprotobuf` and `protoc`
## Implementation details
@@ -20,9 +24,10 @@ For more details see [vector-tile-spec](https://github.com/mapbox/vector-tile-sp
### Ubuntu Dependencies Installation
- apt-get install libprotobuf7 libprotobuf-dev protobuf-compiler
- apt-add-repository ppa:mapnik/v2.2.0
- apt-get update && apt-get install libmapnik libmapnik-dev
+ sudo apt-get install -y libprotobuf7 libprotobuf-dev protobuf-compiler
+ sudo apt-add-repository --yes ppa:mapnik/nightly-2.3
+ sudo apt-get update -y
+ sudo apt-get -y install libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* mapnik-input-plugin*=2.3.0*
### OS X Dependencies Installation
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..d98f431
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+
+function setup_mason() {
+ if [[ ! -d ./.mason ]]; then
+ git clone --depth 1 https://github.com/mapbox/mason.git ./.mason
+ else
+ echo "Updating to latest mason"
+ (cd ./.mason && git pull)
+ fi
+ export MASON_DIR=$(pwd)/.mason
+ export PATH=$(pwd)/.mason:$PATH
+ export CXX=${CXX:-clang++}
+ export CC=${CC:-clang}
+}
+
+function install() {
+ MASON_PLATFORM_ID=$(mason env MASON_PLATFORM_ID)
+ if [[ ! -d ./mason_packages/${MASON_PLATFORM_ID}/${1}/ ]]; then
+ mason install $1 $2
+ mason link $1 $2
+ fi
+}
+
+function install_mason_deps() {
+ install mapnik latest
+ install protobuf 2.6.1
+ install freetype 2.5.5
+ install harfbuzz 0.9.40
+ install jpeg_turbo 1.4.0
+ install libxml2 2.9.2
+ install libpng 1.6.16
+ install webp 0.4.2
+ install icu 54.1
+ install proj 4.8.0
+ install libtiff 4.0.4beta
+ install boost 1.57.0
+ install boost_libsystem 1.57.0
+ install boost_libthread 1.57.0
+ install boost_libfilesystem 1.57.0
+ install boost_libprogram_options 1.57.0
+ install boost_libregex 1.57.0
+ install boost_libpython 1.57.0
+ install pixman 0.32.6
+ install cairo 1.12.18
+}
+
+function setup_runtime_settings() {
+ local MASON_LINKED_ABS=$(pwd)/mason_packages/.link
+ export PROJ_LIB=${MASON_LINKED_ABS}/share/proj
+ export ICU_DATA=${MASON_LINKED_ABS}/share/icu/54.1
+ export GDAL_DATA=${MASON_LINKED_ABS}/share/gdal
+ if [[ $(uname -s) == 'Darwin' ]]; then
+ export DYLD_LIBRARY_PATH=$(pwd)/mason_packages/.link/lib:${DYLD_LIBRARY_PATH}
+ else
+ export LD_LIBRARY_PATH=$(pwd)/mason_packages/.link/lib:${LD_LIBRARY_PATH}
+ fi
+ export PATH=$(pwd)/mason_packages/.link/bin:${PATH}
+}
+
+function main() {
+ setup_mason
+ install_mason_deps
+ setup_runtime_settings
+ echo "Ready, now run:"
+ echo ""
+ echo " make test"
+}
+
+main
diff --git a/examples/c++/tileinfo.cpp b/examples/c++/tileinfo.cpp
index 222d6ee..397ffbb 100644
--- a/examples/c++/tileinfo.cpp
+++ b/examples/c++/tileinfo.cpp
@@ -1,5 +1,5 @@
-#include "../../src/vector_tile.pb.h"
-#include "../../src/vector_tile_compression.hpp"
+#include "vector_tile.pb.h"
+#include "vector_tile_compression.hpp"
#include <vector>
#include <iostream>
#include <fstream>
@@ -36,7 +36,7 @@ int main(int argc, char** argv)
if (args.empty())
{
- std::clog << "please pass the path to an uncompressed or zlib-compressed protobuf tile\n";
+ std::clog << "please pass the path to an uncompressed, zlib-compressed, or gzip compressed protobuf tile\n";
return -1;
}
@@ -53,12 +53,22 @@ int main(int argc, char** argv)
std::string message(std::istreambuf_iterator<char>(stream.rdbuf()),(std::istreambuf_iterator<char>()));
stream.close();
- // now attemp to open protobuf
- mapnik::vector::tile tile;
- if (mapnik::vector::is_compressed(message))
+ // now attempt to open protobuf
+ vector_tile::Tile tile;
+ bool is_zlib = mapnik::vector_tile_impl::is_zlib_compressed(message);
+ bool is_gzip = mapnik::vector_tile_impl::is_gzip_compressed(message);
+ if (is_zlib || is_gzip)
{
+ if (is_zlib)
+ {
+ std::cout << "message: zlib compressed\n";
+ }
+ else if (is_gzip)
+ {
+ std::cout << "message: gzip compressed\n";
+ }
std::string uncompressed;
- mapnik::vector::decompress(message,uncompressed);
+ mapnik::vector_tile_impl::zlib_decompress(message,uncompressed);
if (!tile.ParseFromString(uncompressed))
{
std::clog << "failed to parse compressed protobuf\n";
@@ -66,6 +76,7 @@ int main(int argc, char** argv)
}
else
{
+ std::cout << "message: appears not to be compressed\n";
if (!tile.ParseFromString(message))
{
std::clog << "failed to parse protobuf\n";
@@ -75,7 +86,7 @@ int main(int argc, char** argv)
std::cout << "layers: " << tile.layers_size() << "\n";
for (unsigned i=0;i<tile.layers_size();++i)
{
- mapnik::vector::tile_layer const& layer = tile.layers(i);
+ vector_tile::Tile_Layer const& layer = tile.layers(i);
std::cout << layer.name() << ":\n";
std::cout << " version: " << layer.version() << "\n";
std::cout << " extent: " << layer.extent() << "\n";
@@ -91,7 +102,7 @@ int main(int argc, char** argv)
unsigned degenerate = 0;
for (unsigned j=0;j<layer.features_size();++j)
{
- mapnik::vector::tile_feature const & f = layer.features(j);
+ vector_tile::Tile_Feature const & f = layer.features(j);
total_repeated += f.geometry_size();
eGeomType g_type = static_cast<eGeomType>(f.type());
int cmd = -1;
@@ -150,7 +161,7 @@ int main(int argc, char** argv)
} else {
for (unsigned i=0;i<tile.layers_size();++i)
{
- mapnik::vector::tile_layer const& layer = tile.layers(i);
+ vector_tile::Tile_Layer const& layer = tile.layers(i);
std::cout << "layer: " << layer.name() << "\n";
std::cout << " version: " << layer.version() << "\n";
std::cout << " extent: " << layer.extent() << "\n";
@@ -167,7 +178,7 @@ int main(int argc, char** argv)
std::cout << " values: ";
for (unsigned i=0;i<layer.values_size();++i)
{
- mapnik::vector::tile_value const & value = layer.values(i);
+ vector_tile::Tile_Value const & value = layer.values(i);
if (value.has_string_value()) {
std::cout << value.string_value();
} else if (value.has_int_value()) {
@@ -192,7 +203,7 @@ int main(int argc, char** argv)
std::cout << "\n";
for (unsigned i=0;i<layer.features_size();++i)
{
- mapnik::vector::tile_feature const & feat = layer.features(i);
+ vector_tile::Tile_Feature const & feat = layer.features(i);
std::cout << " feature: " << feat.id() << "\n";
std::cout << " type: ";
unsigned feat_type = feat.type();
diff --git a/gyp/build.gyp b/gyp/build.gyp
new file mode 100644
index 0000000..3c145d3
--- /dev/null
+++ b/gyp/build.gyp
@@ -0,0 +1,153 @@
+{
+ "includes": [
+ "common.gypi"
+ ],
+ 'variables': {
+ 'MAPNIK_PLUGINDIR%': ''
+ },
+ "targets": [
+ {
+ 'target_name': 'make_vector_tile',
+ 'type': 'none',
+ 'hard_dependency': 1,
+ 'actions': [
+ {
+ 'action_name': 'run_protoc',
+ 'inputs': [
+ '../proto/vector_tile.proto'
+ ],
+ 'outputs': [
+ "<(SHARED_INTERMEDIATE_DIR)/vector_tile.pb.cc",
+ "<(SHARED_INTERMEDIATE_DIR)/vector_tile.pb.h"
+ ],
+ 'action': ['protoc','-I../proto/','--cpp_out=<(SHARED_INTERMEDIATE_DIR)/','../proto/vector_tile.proto']
+ }
+ ]
+ },
+
+ {
+ "target_name": "vector_tile",
+ 'dependencies': [ 'make_vector_tile' ],
+ 'hard_dependency': 1,
+ "type": "static_library",
+ "sources": [
+ "<(SHARED_INTERMEDIATE_DIR)/vector_tile.pb.cc"
+ ],
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)/'
+ ],
+ 'cflags_cc' : [
+ '-D_THREAD_SAFE',
+ '<!@(mapnik-config --cflags)' # assume protobuf headers are here
+ ],
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS':[
+ '-D_THREAD_SAFE',
+ '<!@(mapnik-config --cflags)' # assume protobuf headers are here
+ ],
+ },
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)/'
+ ],
+ 'libraries':[
+ '-lprotobuf-lite'
+ ],
+ 'cflags_cc' : [
+ '-D_THREAD_SAFE'
+ ],
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS':[
+ '-D_THREAD_SAFE',
+ ],
+ },
+ }
+ },
+ {
+ "target_name": "mapnik_vector_tile_impl",
+ 'dependencies': [ 'vector_tile' ],
+ 'hard_dependency': 1,
+ "type": "static_library",
+ "sources": [
+ "<!@(find ../src/ -name '*.cpp')"
+ ],
+ 'defines' : [
+ 'MAPNIK_VECTOR_TILE_LIBRARY=1'
+ ],
+ 'cflags_cc' : [
+ '<!@(mapnik-config --cflags)'
+ ],
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS':[
+ '<!@(mapnik-config --cflags)'
+ ],
+ },
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)/'
+ ],
+ 'defines' : [
+ 'MAPNIK_VECTOR_TILE_LIBRARY=1'
+ ],
+ 'cflags_cc' : [
+ '<!@(mapnik-config --cflags)'
+ ],
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS':[
+ '<!@(mapnik-config --cflags)'
+ ],
+ },
+ 'libraries':[
+ '<!@(mapnik-config --libs)',
+ '<!@(mapnik-config --ldflags)',
+ '-lmapnik-wkt',
+ '-lmapnik-json',
+ '<!@(mapnik-config --dep-libs)',
+ '-lprotobuf-lite',
+ '-lz'
+ ],
+ }
+ },
+ {
+ "target_name": "tests",
+ 'dependencies': [ 'mapnik_vector_tile_impl' ],
+ "type": "executable",
+ "defines": [
+ "MAPNIK_PLUGINDIR=<(MAPNIK_PLUGINDIR)"
+ ],
+ "sources": [
+ "<!@(find ../test/ -name '*.cpp')"
+ ],
+ "include_dirs": [
+ "../src"
+ ]
+ },
+ {
+ "target_name": "tileinfo",
+ 'dependencies': [ 'vector_tile' ],
+ "type": "executable",
+ "sources": [
+ "../examples/c++/tileinfo.cpp"
+ ],
+ "include_dirs": [
+ "../src"
+ ],
+ 'libraries':[
+ '-L<!@(mapnik-config --prefix)/lib',
+ '<!@(mapnik-config --ldflags)',
+ '-lz'
+ ],
+ 'cflags_cc' : [
+ '-D_THREAD_SAFE',
+ '<!@(mapnik-config --cflags)' # assume protobuf headers are here
+ ],
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS':[
+ '-D_THREAD_SAFE',
+ '<!@(mapnik-config --cflags)' # assume protobuf headers are here
+ ],
+ }
+ }
+
+ ]
+}
\ No newline at end of file
diff --git a/gyp/common.gypi b/gyp/common.gypi
new file mode 100644
index 0000000..491252b
--- /dev/null
+++ b/gyp/common.gypi
@@ -0,0 +1,143 @@
+{
+ "conditions": [
+ ["OS=='win'", {
+ "target_defaults": {
+ "default_configuration": "Release_x64",
+ "msbuild_toolset":"CTP_Nov2013",
+ "msvs_settings": {
+ "VCCLCompilerTool": {
+ "ExceptionHandling": 1, # /EHsc
+ "RuntimeTypeInfo": "true" # /GR
+ }
+ },
+ "configurations": {
+ "Debug_Win32": {
+ "msvs_configuration_platform": "Win32",
+ "defines": [ "DEBUG","_DEBUG"],
+ "msvs_settings": {
+ "VCCLCompilerTool": {
+ "RuntimeLibrary": "1", # static debug /MTd
+ "Optimization": 0, # /Od, no optimization
+ "MinimalRebuild": "false",
+ "OmitFramePointers": "false",
+ "BasicRuntimeChecks": 3 # /RTC1
+ }
+ }
+ },
+ "Debug_x64": {
+ "msvs_configuration_platform": "x64",
+ "defines": [ "DEBUG","_DEBUG"],
+ "msvs_settings": {
+ "VCCLCompilerTool": {
+ "RuntimeLibrary": "1", # static debug /MTd
+ "Optimization": 0, # /Od, no optimization
+ "MinimalRebuild": "false",
+ "OmitFramePointers": "false",
+ "BasicRuntimeChecks": 3 # /RTC1
+ }
+ }
+ },
+ "Release_Win32": {
+ "msvs_configuration_platform": "Win32",
+ "defines": [ "NDEBUG"],
+ "msvs_settings": {
+ "VCCLCompilerTool": {
+ "RuntimeLibrary": 0, # static release
+ "Optimization": 3, # /Ox, full optimization
+ "FavorSizeOrSpeed": 1, # /Ot, favour speed over size
+ "InlineFunctionExpansion": 2, # /Ob2, inline anything eligible
+ "WholeProgramOptimization": "true", # /GL, whole program optimization, needed for LTCG
+ "OmitFramePointers": "true",
+ "EnableFunctionLevelLinking": "true",
+ "EnableIntrinsicFunctions": "true",
+ "AdditionalOptions": [
+ "/MP", # compile across multiple CPUs
+ ],
+ "DebugInformationFormat": "0"
+ },
+ "VCLibrarianTool": {
+ "AdditionalOptions": [
+ "/LTCG" # link time code generation
+ ],
+ },
+ "VCLinkerTool": {
+ "LinkTimeCodeGeneration": 1, # link-time code generation
+ "OptimizeReferences": 2, # /OPT:REF
+ "EnableCOMDATFolding": 2, # /OPT:ICF
+ "LinkIncremental": 1, # disable incremental linking
+ "GenerateDebugInformation": "false"
+ }
+ }
+ },
+ "Release_x64": {
+ "msvs_configuration_platform": "x64",
+ "defines": [ "NDEBUG"],
+ "msvs_settings": {
+ "VCCLCompilerTool": {
+ "RuntimeLibrary": 0, # static release
+ "Optimization": 3, # /Ox, full optimization
+ "FavorSizeOrSpeed": 1, # /Ot, favour speed over size
+ "InlineFunctionExpansion": 2, # /Ob2, inline anything eligible
+ "WholeProgramOptimization": "true", # /GL, whole program optimization, needed for LTCG
+ "OmitFramePointers": "true",
+ "EnableFunctionLevelLinking": "true",
+ "EnableIntrinsicFunctions": "true",
+ "AdditionalOptions": [
+ "/MP", # compile across multiple CPUs
+ ],
+ "DebugInformationFormat": "0"
+ },
+ "VCLibrarianTool": {
+ "AdditionalOptions": [
+ "/LTCG" # link time code generation
+ ],
+ },
+ "VCLinkerTool": {
+ "LinkTimeCodeGeneration": 1, # link-time code generation
+ "OptimizeReferences": 2, # /OPT:REF
+ "EnableCOMDATFolding": 2, # /OPT:ICF
+ "LinkIncremental": 1, # disable incremental linking
+ "GenerateDebugInformation": "false"
+ }
+ }
+ }
+ }
+ }
+ }, {
+ "target_defaults": {
+ "default_configuration": "Release",
+ "xcode_settings": {
+ "CLANG_CXX_LIBRARY": "libc++",
+ "CLANG_CXX_LANGUAGE_STANDARD":"c++11",
+ "GCC_VERSION": "com.apple.compilers.llvm.clang.1_0",
+ },
+ "cflags_cc": ["-std=c++11"],
+ "configurations": {
+ "Debug": {
+ "defines": [
+ "DEBUG"
+ ],
+ "xcode_settings": {
+ "GCC_OPTIMIZATION_LEVEL": "0",
+ "GCC_GENERATE_DEBUGGING_SYMBOLS": "YES",
+ "OTHER_CPLUSPLUSFLAGS": [ "-Wall", "-Wextra", "-pedantic", "-g", "-O0" ]
+ }
+ },
+ "Release": {
+ "defines": [
+ "NDEBUG"
+ ],
+ "xcode_settings": {
+ "GCC_OPTIMIZATION_LEVEL": "3",
+ "GCC_GENERATE_DEBUGGING_SYMBOLS": "NO",
+ "DEAD_CODE_STRIPPING": "YES",
+ "GCC_INLINES_ARE_PRIVATE_EXTERN": "YES",
+ "OTHER_CPLUSPLUSFLAGS": [ "-Wall", "-Wno-unknown-pragmas", "-O3" ]
+ }
+ }
+ }
+ }
+ }]
+ ]
+}
+
diff --git a/package.json b/package.json
index aea5e4c..d7c65ca 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mapnik-vector-tile",
- "version": "0.5.2",
+ "version": "0.8.0",
"description": "Mapnik vector tile API",
"main": "./package.json",
"repository" : {
diff --git a/proto/vector_tile.proto b/proto/vector_tile.proto
index d17e1f1..5380d35 100644
--- a/proto/vector_tile.proto
+++ b/proto/vector_tile.proto
@@ -1,19 +1,19 @@
// Protocol Version 1
-package mapnik.vector;
+package vector_tile;
option optimize_for = LITE_RUNTIME;
-message tile {
+message Tile {
enum GeomType {
- Unknown = 0;
- Point = 1;
- LineString = 2;
- Polygon = 3;
+ UNKNOWN = 0;
+ POINT = 1;
+ LINESTRING = 2;
+ POLYGON = 3;
}
// Variant type encoding
- message value {
+ message Value {
// Exactly one of these values may be present in a valid message
optional string string_value = 1;
optional float float_value = 2;
@@ -26,17 +26,20 @@ message tile {
extensions 8 to max;
}
- message feature {
- optional uint64 id = 1;
+ message Feature {
+ optional uint64 id = 1 [ default = 0 ];
- // Tags of this feature. Even numbered values refer to the nth
- // value in the keys list on the tile message, odd numbered
- // values refer to the nth value in the values list on the tile
- // message.
+ // Tags of this feature are encoded as repeated pairs of
+ // integers. Even indexed values (n, beginning with 0) are
+ // themselves indexes into the layer's keys list. Odd indexed
+ // values (n+1) are indexes into the layer's values list.
+ // The first (n=0) tag of a feature, therefore, has a key of
+ // layer.keys[feature.tags[0]] and a value of
+ // layer.values[feature.tags[1]].
repeated uint32 tags = 2 [ packed = true ];
// The type of geometry stored in this feature.
- optional GeomType type = 3 [ default = Unknown ];
+ optional GeomType type = 3 [ default = UNKNOWN ];
// Contains a stream of commands and parameters (vertices). The
// repeat count is shifted to the left by 3 bits. This means
@@ -47,25 +50,29 @@ message tile {
// - LineTo: 2 (2 parameters follow)
// - ClosePath: 7 (no parameters follow)
//
+ // Commands are encoded as uint32 varints. Vertex parameters
+ // are encoded as deltas to the previous position and, as they
+ // may be negative, are further "zigzag" encoded as unsigned
+ // 32-bit ints:
+ //
+ // n = (n << 1) ^ (n >> 31)
+ //
// Ex.: MoveTo(3, 6), LineTo(8, 12), LineTo(20, 34), ClosePath
- // Encoded as: [ 9 3 6 18 5 6 12 22 15 ]
- // == command type 7 (ClosePath), length 1
- // ===== relative LineTo(+12, +22) == LineTo(20, 34)
- // === relative LineTo(+5, +6) == LineTo(8, 12)
- // == [00010 010] = command type 2 (LineTo), length 2
- // === relative MoveTo(+3, +6)
- // == [00001 001] = command type 1 (MoveTo), length 1
- // Commands are encoded as uint32 varints, vertex parameters are
- // encoded as sint32 varints (zigzag). Vertex parameters are
- // also encoded as deltas to the previous position. The original
- // position is (0,0)
+ // Encoded as: [ 9 6 12 18 10 12 24 44 15 ]
+ // | | `> [00001 111] command type 7 (ClosePath), length 1
+ // | | ===== relative LineTo(+12, +22) == LineTo(20, 34)
+ // | | ===== relative LineTo(+5, +6) == LineTo(8, 12)
+ // | `> [00010 010] = command type 2 (LineTo), length 2
+ // | ==== relative MoveTo(+3, +6)
+ // `> [00001 001] = command type 1 (MoveTo), length 1
+ //
+ // The original position is (0,0).
repeated uint32 geometry = 4 [ packed = true ];
optional bytes raster = 5;
-
}
- message layer {
+ message Layer {
// Any compliant implementation must first read the version
// number encoded in this message and choose the correct
// implementation for this version number before proceeding to
@@ -75,13 +82,13 @@ message tile {
required string name = 1;
// The actual features in this tile.
- repeated feature features = 2;
+ repeated Feature features = 2;
// Dictionary encoding for keys
repeated string keys = 3;
// Dictionary encoding for values
- repeated value values = 4;
+ repeated Value values = 4;
// The bounding box in this tile spans from 0..4095 units
optional uint32 extent = 5 [ default = 4096 ];
@@ -89,7 +96,7 @@ message tile {
extensions 16 to max;
}
- repeated layer layers = 3;
+ repeated Layer layers = 3;
extensions 16 to 8191;
}
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 0000000..6cd618b
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,11 @@
+set -e -u
+set -o pipefail
+
+if [[ ${COVERAGE:-false} == true ]]; then
+ export LDFLAGS="--coverage"
+ export CXXFLAGS="--coverage"
+fi
+
+make -j${JOBS} test BUILDTYPE=${BUILDTYPE:-Release} V=1
+
+set +e +u
\ No newline at end of file
diff --git a/scripts/coverage.sh b/scripts/coverage.sh
new file mode 100755
index 0000000..3d0e223
--- /dev/null
+++ b/scripts/coverage.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -e -u
+set -o pipefail
+
+COVERAGE=${COVERAGE:-false}
+
+if [[ ${COVERAGE} == true ]]; then
+ PYTHONUSERBASE=$(pwd)/mason_packages/.link pip install --user cpp-coveralls
+ if [[ $(uname -s) == 'Linux' ]]; then
+ export PYTHONPATH=$(pwd)/mason_packages/.link/lib/python2.7/site-packages
+ else
+ export PYTHONPATH=$(pwd)/mason_packages/.link/lib/python/site-packages
+ fi
+ ./mason_packages/.link/bin/cpp-coveralls \
+ --build-root build \
+ --gcov-options '\-lp' \
+ --exclude test/catch.hpp \
+ --exclude examples \
+ --exclude build/Debug/obj/gen/ \
+ --exclude mason_packages \
+ --exclude scripts > /dev/null
+fi
+
+set +e +u
\ No newline at end of file
diff --git a/src/mapnik3x_compatibility.hpp b/src/mapnik3x_compatibility.hpp
deleted file mode 100644
index 6765579..0000000
--- a/src/mapnik3x_compatibility.hpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef __MAPNIK_VECTOR_TILE_COMPATIBILITY_H__
-#define __MAPNIK_VECTOR_TILE_COMPATIBILITY_H__
-
-#include <mapnik/config.hpp>
-#include <mapnik/version.hpp>
-
-#if MAPNIK_VERSION >= 300000
- #define MAPNIK_UNIQUE_PTR std::unique_ptr
- #define MAPNIK_SHARED_INCLUDE <memory>
- #define MAPNIK_MAKE_SHARED_INCLUDE <memory>
- #define MAPNIK_MAKE_SHARED std::make_shared
- #define MAPNIK_SHARED_PTR std::shared_ptr
- #define MAPNIK_ADD_LAYER add_layer
- #define MAPNIK_GET std::get
- #define MAPNIK_GEOM_TYPE mapnik::geometry_type::types
- #define MAPNIK_POINT mapnik::geometry_type::types::Point
- #define MAPNIK_POLYGON mapnik::geometry_type::types::Polygon
- #define MAPNIK_LINESTRING mapnik::geometry_type::types::LineString
- #define MAPNIK_UNKNOWN mapnik::geometry_type::types::Unknown
- #define MAPNIK_VARIANT_INCLUDE <mapnik/util/variant.hpp>
- #define MAPNIK_APPLY_VISITOR mapnik::util::apply_visitor
- #define MAPNIK_STATIC_VISITOR mapnik::util::static_visitor
-#else
- #define MAPNIK_UNIQUE_PTR std::auto_ptr
- #define MAPNIK_SHARED_INCLUDE <boost/shared_ptr.hpp>
- #define MAPNIK_MAKE_SHARED_INCLUDE <boost/make_shared.hpp>
- #define MAPNIK_MAKE_SHARED boost::make_shared
- #define MAPNIK_SHARED_PTR boost::shared_ptr
- #define MAPNIK_ADD_LAYER addLayer
- #define MAPNIK_GET boost::get
- #define MAPNIK_GEOM_TYPE mapnik::eGeomType
- #define MAPNIK_POINT mapnik::Point
- #define MAPNIK_POLYGON mapnik::Polygon
- #define MAPNIK_LINESTRING mapnik::LineString
- #define MAPNIK_UNKNOWN mapnik::Unknown
- #define MAPNIK_VARIANT_INCLUDE <boost/variant.hpp>
- #define MAPNIK_APPLY_VISITOR boost::apply_visitor
- #define MAPNIK_STATIC_VISITOR boost::static_visitor
-#endif
-
-#endif
diff --git a/src/vector_tile_backend_pbf.cpp b/src/vector_tile_backend_pbf.cpp
new file mode 100644
index 0000000..4404b3b
--- /dev/null
+++ b/src/vector_tile_backend_pbf.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_backend_pbf.hpp"
+#include "vector_tile_backend_pbf.ipp"
\ No newline at end of file
diff --git a/src/vector_tile_backend_pbf.hpp b/src/vector_tile_backend_pbf.hpp
index e2577b2..05c2bbe 100644
--- a/src/vector_tile_backend_pbf.hpp
+++ b/src/vector_tile_backend_pbf.hpp
@@ -1,191 +1,61 @@
#ifndef __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__
#define __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__
-#include "mapnik3x_compatibility.hpp"
-#include MAPNIK_VARIANT_INCLUDE
-
-// mapnik
-#include <mapnik/feature.hpp>
-#include <mapnik/value_types.hpp>
-
// vector tile
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+#include "vector_tile_config.hpp"
#include "vector_tile_geometry_encoder.hpp"
+#include <mapnik/value.hpp>
+#include <mapnik/geometry.hpp>
// boost
#include <boost/unordered_map.hpp>
-#include <boost/foreach.hpp>
-
-#include MAPNIK_VARIANT_INCLUDE
-
-namespace mapnik { namespace vector {
-
- struct to_tile_value: public MAPNIK_STATIC_VISITOR<>
- {
- public:
- to_tile_value(tile_value * value):
- value_(value) {}
- void operator () ( value_integer val ) const
- {
- // TODO: figure out shortest varint encoding.
- value_->set_int_value(val);
- }
+namespace mapnik {
+ class feature_impl;
+}
- void operator () ( mapnik::value_bool val ) const
- {
- value_->set_bool_value(val);
- }
-
- void operator () ( mapnik::value_double val ) const
- {
- // TODO: Figure out how we can encode 32 bit floats in some cases.
- value_->set_double_value(val);
- }
-
- void operator () ( mapnik::value_unicode_string const& val ) const
- {
- std::string str;
- to_utf8(val, str);
- value_->set_string_value(str.data(), str.length());
- }
-
- void operator () ( mapnik::value_null const& /*val*/ ) const
- {
- // do nothing
- }
- private:
- tile_value * value_;
- };
+namespace mapnik { namespace vector_tile_impl {
struct backend_pbf
{
typedef std::map<std::string, unsigned> keys_container;
typedef boost::unordered_map<mapnik::value, unsigned> values_container;
private:
- tile & tile_;
+ vector_tile::Tile & tile_;
unsigned path_multiplier_;
- mutable tile_layer * current_layer_;
- mutable tile_feature * current_feature_;
+ mutable vector_tile::Tile_Layer * current_layer_;
keys_container keys_;
values_container values_;
int32_t x_, y_;
public:
- explicit backend_pbf(tile & _tile,
- unsigned path_multiplier)
- : tile_(_tile),
- path_multiplier_(path_multiplier),
- current_layer_(NULL),
- current_feature_(NULL)
+ mutable vector_tile::Tile_Feature * current_feature_;
+ MAPNIK_VECTOR_INLINE explicit backend_pbf(vector_tile::Tile & _tile,
+ unsigned path_multiplier);
+
+ MAPNIK_VECTOR_INLINE void add_tile_feature_raster(std::string const& image_buffer);
+ MAPNIK_VECTOR_INLINE void stop_tile_feature();
+ MAPNIK_VECTOR_INLINE void start_tile_feature(mapnik::feature_impl const& feature);
+ MAPNIK_VECTOR_INLINE void start_tile_layer(std::string const& name);
+ MAPNIK_VECTOR_INLINE unsigned get_path_multiplier()
{
+ return path_multiplier_;
}
-
- void add_tile_feature_raster(std::string const& image_buffer)
- {
- if (current_feature_)
- {
- current_feature_->set_raster(image_buffer);
- }
- }
-
- void stop_tile_feature()
- {
- if (current_feature_)
- {
- if (current_feature_->geometry_size() == 0 && current_layer_)
- {
- current_layer_->mutable_features()->RemoveLast();
- }
- }
- }
-
- void start_tile_feature(mapnik::feature_impl const& feature)
- {
- current_feature_ = current_layer_->add_features();
- x_ = y_ = 0;
-
- // TODO - encode as sint64: (n << 1) ^ ( n >> 63)
- // test current behavior with negative numbers
- current_feature_->set_id(feature.id());
-
- feature_kv_iterator itr = feature.begin();
- feature_kv_iterator end = feature.end();
- for ( ;itr!=end; ++itr)
- {
- std::string const& name = MAPNIK_GET<0>(*itr);
- mapnik::value const& val = MAPNIK_GET<1>(*itr);
- if (!val.is_null())
- {
- // Insert the key index
- keys_container::const_iterator key_itr = keys_.find(name);
- if (key_itr == keys_.end())
- {
- // The key doesn't exist yet in the dictionary.
- current_layer_->add_keys(name.c_str(), name.length());
- size_t index = keys_.size();
- keys_.insert(keys_container::value_type(name, index));
- current_feature_->add_tags(index);
- }
- else
- {
- current_feature_->add_tags(key_itr->second);
- }
-
- // Insert the value index
- values_container::const_iterator val_itr = values_.find(val);
- if (val_itr == values_.end())
- {
- // The value doesn't exist yet in the dictionary.
- to_tile_value visitor(current_layer_->add_values());
- MAPNIK_APPLY_VISITOR(visitor, val.base());
-
- size_t index = values_.size();
- values_.insert(values_container::value_type(val, index));
- current_feature_->add_tags(index);
- }
- else
- {
- current_feature_->add_tags(val_itr->second);
- }
- }
- }
- }
-
- void start_tile_layer(std::string const& name)
- {
- // Key/value dictionary is per-layer.
- keys_.clear();
- values_.clear();
-
- current_layer_ = tile_.add_layers();
- current_layer_->set_name(name);
- current_layer_->set_version(1);
-
- // We currently use path_multiplier as a factor to scale the coordinates.
- // Eventually, we should replace this with the extent specifying the
- // bounding box in both dimensions. E.g. an extent of 4096 means that
- // the coordinates encoded in this tile should be visible in the range
- // from 0..4095.
- current_layer_->set_extent(256 * path_multiplier_);
- }
-
- inline void stop_tile_layer()
- {
- //std::cerr << "stop_tile_layer()" << std::endl;
- }
+ inline void stop_tile_layer() {}
template <typename T>
- inline unsigned add_path(T & path, unsigned tolerance, MAPNIK_GEOM_TYPE type)
+ inline unsigned add_path(T const& path)
{
if (current_feature_)
{
return encode_geometry(path,
- static_cast<tile_GeomType>(type),
*current_feature_,
x_,
- y_,
- tolerance,
- path_multiplier_);
+ y_);
}
return 0;
}
@@ -193,4 +63,8 @@ namespace mapnik { namespace vector {
}} // end ns
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_backend_pbf.ipp"
+#endif
+
#endif // __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__
diff --git a/src/vector_tile_backend_pbf.ipp b/src/vector_tile_backend_pbf.ipp
new file mode 100644
index 0000000..dbc07a0
--- /dev/null
+++ b/src/vector_tile_backend_pbf.ipp
@@ -0,0 +1,157 @@
+// mapnik
+#include <mapnik/version.hpp>
+#include <mapnik/feature.hpp>
+#include <mapnik/value_types.hpp>
+#include <mapnik/util/variant.hpp>
+
+// vector tile
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+#include "vector_tile_geometry_encoder.hpp"
+
+#include <unordered_map>
+
+namespace mapnik { namespace vector_tile_impl {
+
+struct to_tile_value: public mapnik::util::static_visitor<>
+{
+public:
+ to_tile_value(vector_tile::Tile_Value * value):
+ value_(value) {}
+
+ void operator () ( value_integer val ) const
+ {
+ // TODO: figure out shortest varint encoding.
+ value_->set_int_value(val);
+ }
+
+ void operator () ( mapnik::value_bool val ) const
+ {
+ value_->set_bool_value(val);
+ }
+
+ void operator () ( mapnik::value_double val ) const
+ {
+ // TODO: Figure out how we can encode 32 bit floats in some cases.
+ value_->set_double_value(val);
+ }
+
+ void operator () ( mapnik::value_unicode_string const& val ) const
+ {
+ std::string str;
+ to_utf8(val, str);
+ value_->set_string_value(str.data(), str.length());
+ }
+
+ void operator () ( mapnik::value_null const& /*val*/ ) const
+ {
+ // do nothing
+ }
+private:
+ vector_tile::Tile_Value * value_;
+};
+
+backend_pbf::backend_pbf(vector_tile::Tile & _tile,
+ unsigned path_multiplier)
+ : tile_(_tile),
+ path_multiplier_(path_multiplier),
+ current_layer_(NULL),
+ current_feature_(NULL)
+{
+}
+
+void backend_pbf::add_tile_feature_raster(std::string const& image_buffer)
+{
+ if (current_feature_)
+ {
+ current_feature_->set_raster(image_buffer);
+ }
+}
+
+void backend_pbf::stop_tile_feature()
+{
+ if (current_feature_)
+ {
+ if (current_feature_->geometry_size() == 0 && current_layer_)
+ {
+ current_layer_->mutable_features()->RemoveLast();
+ }
+ }
+}
+
+void backend_pbf::start_tile_feature(mapnik::feature_impl const& feature)
+{
+ if (current_layer_)
+ {
+ current_feature_ = current_layer_->add_features();
+ x_ = y_ = 0;
+
+ // TODO - encode as sint64: (n << 1) ^ ( n >> 63)
+ // test current behavior with negative numbers
+ current_feature_->set_id(feature.id());
+
+ feature_kv_iterator itr = feature.begin();
+ feature_kv_iterator end = feature.end();
+ for ( ;itr!=end; ++itr)
+ {
+ std::string const& name = std::get<0>(*itr);
+ mapnik::value const& val = std::get<1>(*itr);
+ if (!val.is_null())
+ {
+ // Insert the key index
+ keys_container::const_iterator key_itr = keys_.find(name);
+ if (key_itr == keys_.end())
+ {
+ // The key doesn't exist yet in the dictionary.
+ current_layer_->add_keys(name.c_str(), name.length());
+ size_t index = keys_.size();
+ keys_.insert(keys_container::value_type(name, index));
+ current_feature_->add_tags(index);
+ }
+ else
+ {
+ current_feature_->add_tags(key_itr->second);
+ }
+
+ // Insert the value index
+ values_container::const_iterator val_itr = values_.find(val);
+ if (val_itr == values_.end())
+ {
+ // The value doesn't exist yet in the dictionary.
+ to_tile_value visitor(current_layer_->add_values());
+ mapnik::util::apply_visitor(visitor, val);
+ size_t index = values_.size();
+ values_.insert(values_container::value_type(val, index));
+ current_feature_->add_tags(index);
+ }
+ else
+ {
+ current_feature_->add_tags(val_itr->second);
+ }
+ }
+ }
+ }
+}
+
+void backend_pbf::start_tile_layer(std::string const& name)
+{
+ // Key/value dictionary is per-layer.
+ keys_.clear();
+ values_.clear();
+
+ current_layer_ = tile_.add_layers();
+ current_layer_->set_name(name);
+ current_layer_->set_version(1);
+
+ // We currently use path_multiplier as a factor to scale the coordinates.
+ // Eventually, we should replace this with the extent specifying the
+ // bounding box in both dimensions. E.g. an extent of 4096 means that
+ // the coordinates encoded in this tile should be visible in the range
+ // from 0..4095.
+ current_layer_->set_extent(256 * path_multiplier_);
+}
+
+}} // end ns
diff --git a/src/vector_tile_compression.cpp b/src/vector_tile_compression.cpp
new file mode 100644
index 0000000..a156984
--- /dev/null
+++ b/src/vector_tile_compression.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_compression.hpp"
+#include "vector_tile_compression.ipp"
\ No newline at end of file
diff --git a/src/vector_tile_compression.hpp b/src/vector_tile_compression.hpp
index 1afd618..09c57b8 100644
--- a/src/vector_tile_compression.hpp
+++ b/src/vector_tile_compression.hpp
@@ -1,65 +1,30 @@
-#include <stdexcept>
-#include <zlib.h>
+#ifndef __MAPNIK_VECTOR_TILE_COMPRESSION_H__
+#define __MAPNIK_VECTOR_TILE_COMPRESSION_H__
-namespace mapnik { namespace vector {
+#include <string>
+#include "vector_tile_config.hpp"
-inline bool is_compressed(std::string const& data)
-{
- return data.size() > 2 && (uint8_t)data[0] == 0x78 && (uint8_t)data[1] == 0x9C;
-}
+namespace mapnik { namespace vector_tile_impl {
-inline void decompress(std::string const& input, std::string & output)
+inline bool is_zlib_compressed(std::string const& data)
{
- z_stream inflate_s;
- inflate_s.zalloc = Z_NULL;
- inflate_s.zfree = Z_NULL;
- inflate_s.opaque = Z_NULL;
- inflate_s.avail_in = 0;
- inflate_s.next_in = Z_NULL;
- inflateInit(&inflate_s);
- inflate_s.next_in = (Bytef *)input.data();
- inflate_s.avail_in = input.size();
- size_t length = 0;
- do {
- output.resize(length + 2 * input.size());
- inflate_s.avail_out = 2 * input.size();
- inflate_s.next_out = (Bytef *)(output.data() + length);
- int ret = inflate(&inflate_s, Z_FINISH);
- if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) {
- throw std::runtime_error(inflate_s.msg);
- }
-
- length += (2 * input.size() - inflate_s.avail_out);
- } while (inflate_s.avail_out == 0);
- inflateEnd(&inflate_s);
- output.resize(length);
+ return data.size() > 2 && static_cast<uint8_t>(data[0]) == 0x78 && static_cast<uint8_t>(data[1]) == 0x9C;
}
-inline void compress(std::string const& input, std::string & output)
+inline bool is_gzip_compressed(std::string const& data)
{
- z_stream deflate_s;
- deflate_s.zalloc = Z_NULL;
- deflate_s.zfree = Z_NULL;
- deflate_s.opaque = Z_NULL;
- deflate_s.avail_in = 0;
- deflate_s.next_in = Z_NULL;
- deflateInit(&deflate_s, Z_DEFAULT_COMPRESSION);
- deflate_s.next_in = (Bytef *)input.data();
- deflate_s.avail_in = input.size();
- size_t length = 0;
- do {
- size_t increase = input.size() / 2 + 1024;
- output.resize(length + increase);
- deflate_s.avail_out = increase;
- deflate_s.next_out = (Bytef *)(output.data() + length);
- int ret = deflate(&deflate_s, Z_FINISH);
- if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) {
- throw std::runtime_error(deflate_s.msg);
- }
- length += (increase - deflate_s.avail_out);
- } while (deflate_s.avail_out == 0);
- deflateEnd(&deflate_s);
- output.resize(length);
+ return data.size() > 2 && static_cast<uint8_t>(data[0]) == 0x1F && static_cast<uint8_t>(data[1]) == 0x8B;
}
-}}
+// decodes both zlib and gzip
+// http://stackoverflow.com/a/1838702/2333354
+MAPNIK_VECTOR_INLINE void zlib_decompress(std::string const& input, std::string & output);
+MAPNIK_VECTOR_INLINE void zlib_compress(std::string const& input, std::string & output, bool gzip=true);
+
+}} // end ns
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_compression.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_COMPRESSION_H__
diff --git a/src/vector_tile_compression.hpp b/src/vector_tile_compression.ipp
similarity index 74%
copy from src/vector_tile_compression.hpp
copy to src/vector_tile_compression.ipp
index 1afd618..03a539c 100644
--- a/src/vector_tile_compression.hpp
+++ b/src/vector_tile_compression.ipp
@@ -1,14 +1,11 @@
#include <stdexcept>
#include <zlib.h>
-namespace mapnik { namespace vector {
+namespace mapnik { namespace vector_tile_impl {
-inline bool is_compressed(std::string const& data)
-{
- return data.size() > 2 && (uint8_t)data[0] == 0x78 && (uint8_t)data[1] == 0x9C;
-}
-
-inline void decompress(std::string const& input, std::string & output)
+// decodes both zlib and gzip
+// http://stackoverflow.com/a/1838702/2333354
+void zlib_decompress(std::string const& input, std::string & output)
{
z_stream inflate_s;
inflate_s.zalloc = Z_NULL;
@@ -16,7 +13,7 @@ inline void decompress(std::string const& input, std::string & output)
inflate_s.opaque = Z_NULL;
inflate_s.avail_in = 0;
inflate_s.next_in = Z_NULL;
- inflateInit(&inflate_s);
+ inflateInit2(&inflate_s, 32 + 15);
inflate_s.next_in = (Bytef *)input.data();
inflate_s.avail_in = input.size();
size_t length = 0;
@@ -35,7 +32,7 @@ inline void decompress(std::string const& input, std::string & output)
output.resize(length);
}
-inline void compress(std::string const& input, std::string & output)
+void zlib_compress(std::string const& input, std::string & output, bool gzip)
{
z_stream deflate_s;
deflate_s.zalloc = Z_NULL;
@@ -43,7 +40,15 @@ inline void compress(std::string const& input, std::string & output)
deflate_s.opaque = Z_NULL;
deflate_s.avail_in = 0;
deflate_s.next_in = Z_NULL;
- deflateInit(&deflate_s, Z_DEFAULT_COMPRESSION);
+ int windowsBits = 15;
+ if (gzip)
+ {
+ windowsBits = windowsBits | 16;
+ }
+ if (deflateInit2(&deflate_s, Z_BEST_COMPRESSION, Z_DEFLATED, windowsBits, 8, Z_DEFAULT_STRATEGY) != Z_OK)
+ {
+ throw std::runtime_error("deflate failed");
+ }
deflate_s.next_in = (Bytef *)input.data();
deflate_s.avail_in = input.size();
size_t length = 0;
diff --git a/src/vector_tile_config.hpp b/src/vector_tile_config.hpp
new file mode 100644
index 0000000..f1a56c0
--- /dev/null
+++ b/src/vector_tile_config.hpp
@@ -0,0 +1,11 @@
+#ifndef __MAPNIK_VECTOR_TILE_CONFIG_H__
+#define __MAPNIK_VECTOR_TILE_CONFIG_H__
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#define MAPNIK_VECTOR_INLINE inline
+#else
+#define MAPNIK_VECTOR_INLINE
+#endif
+
+
+#endif // __MAPNIK_VECTOR_TILE_CONFIG_H__
\ No newline at end of file
diff --git a/src/vector_tile_datasource.cpp b/src/vector_tile_datasource.cpp
new file mode 100644
index 0000000..9782880
--- /dev/null
+++ b/src/vector_tile_datasource.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_datasource.hpp"
+#include "vector_tile_datasource.ipp"
\ No newline at end of file
diff --git a/src/vector_tile_datasource.hpp b/src/vector_tile_datasource.hpp
index bc878d5..60d5d07 100644
--- a/src/vector_tile_datasource.hpp
+++ b/src/vector_tile_datasource.hpp
@@ -1,269 +1,20 @@
#ifndef __MAPNIK_VECTOR_TILE_DATASOURCE_H__
#define __MAPNIK_VECTOR_TILE_DATASOURCE_H__
-#include "vector_tile.pb.h"
-#include "vector_tile_projection.hpp"
-
-#include <mapnik/box2d.hpp>
-#include <mapnik/coord.hpp>
-#include <mapnik/feature_layer_desc.hpp>
-#include <mapnik/geometry.hpp>
-#include <mapnik/params.hpp>
-#include <mapnik/query.hpp>
-#include <mapnik/unicode.hpp>
-#include <mapnik/version.hpp>
-#include <mapnik/value_types.hpp>
-#include <mapnik/well_known_srs.hpp>
-#include <mapnik/version.hpp>
-#include <mapnik/vertex.hpp>
#include <mapnik/datasource.hpp>
-#include <mapnik/feature.hpp>
-#include <mapnik/feature_factory.hpp>
-#include <mapnik/geom_util.hpp>
-#include <mapnik/image_reader.hpp>
-#include <mapnik/raster.hpp>
-
-#include <memory>
-#include <stdexcept>
-#include <string>
-
-#include <boost/optional.hpp>
-#include <boost/ptr_container/ptr_vector.hpp>
-#include <unicode/unistr.h>
-
-#include "mapnik3x_compatibility.hpp"
-#include MAPNIK_MAKE_SHARED_INCLUDE
-#include MAPNIK_SHARED_INCLUDE
-
-namespace mapnik { namespace vector {
-
- void add_attributes(mapnik::feature_ptr feature,
- mapnik::vector::tile_feature const& f,
- mapnik::vector::tile_layer const& layer,
- mapnik::transcoder const& tr)
- {
- std::size_t num_keys = static_cast<std::size_t>(layer.keys_size());
- std::size_t num_values = static_cast<std::size_t>(layer.values_size());
- for (int m = 0; m < f.tags_size(); m += 2)
- {
- std::size_t key_name = f.tags(m);
- std::size_t key_value = f.tags(m + 1);
- if (key_name < num_keys
- && key_value < num_values)
- {
- std::string const& name = layer.keys(key_name);
- if (feature->has_key(name))
- {
- mapnik::vector::tile_value const& value = layer.values(key_value);
- if (value.has_string_value())
- {
- std::string str = value.string_value();
- feature->put(name, tr.transcode(str.data(), str.length()));
- }
- else if (value.has_int_value())
- {
- feature->put(name, static_cast<mapnik::value_integer>(value.int_value()));
- }
- else if (value.has_double_value())
- {
- feature->put(name, static_cast<mapnik::value_double>(value.double_value()));
- }
- else if (value.has_float_value())
- {
- feature->put(name, static_cast<mapnik::value_double>(value.float_value()));
- }
- else if (value.has_bool_value())
- {
- feature->put(name, static_cast<mapnik::value_bool>(value.bool_value()));
- }
- else if (value.has_sint_value())
- {
- feature->put(name, static_cast<mapnik::value_integer>(value.sint_value()));
- }
- else if (value.has_uint_value())
- {
- feature->put(name, static_cast<mapnik::value_integer>(value.uint_value()));
- }
- }
- }
- }
- }
-
- template <typename Filter>
- class tile_featureset : public Featureset
- {
- public:
- tile_featureset(Filter const& filter,
- mapnik::box2d<double> const& tile_extent,
- std::set<std::string> const& attribute_names,
- mapnik::vector::tile_layer const& layer,
- double tile_x,
- double tile_y,
- double scale)
- : filter_(filter),
- tile_extent_(tile_extent),
- layer_(layer),
- tile_x_(tile_x),
- tile_y_(tile_y),
- scale_(scale),
- itr_(0),
- end_(layer_.features_size()),
- tr_("utf-8"),
- ctx_(MAPNIK_MAKE_SHARED<mapnik::context_type>())
- {
- std::set<std::string>::const_iterator pos = attribute_names.begin();
- std::set<std::string>::const_iterator end = attribute_names.end();
- for ( ;pos !=end; ++pos)
- {
- for (int i = 0; i < layer_.keys_size(); ++i)
- {
- if (layer_.keys(i) == *pos)
- {
- ctx_->push(*pos);
- break;
- }
- }
- }
- }
-
- virtual ~tile_featureset() {}
+#include <mapnik/box2d.hpp>
+#include "vector_tile_config.hpp"
- feature_ptr next()
- {
- while (itr_ < end_)
- {
- mapnik::vector::tile_feature const& f = layer_.features(itr_);
- mapnik::value_integer feature_id = itr_++;
- if (f.has_raster())
- {
- std::string const& image_buffer = f.raster();
- MAPNIK_UNIQUE_PTR<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.data(),image_buffer.size()));
- if (reader.get())
- {
- int image_width = reader->width();
- int image_height = reader->height();
- if (image_width > 0 && image_height > 0)
- {
- if (f.has_id())
- {
- feature_id = f.id();
- }
- #if MAPNIK_VERSION >= 300000
- double filter_factor = 1.0;
- #endif
- bool premultiplied = false;
- mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
- mapnik::raster_ptr raster = MAPNIK_MAKE_SHARED<mapnik::raster>(
- tile_extent_,
- image_width,
- image_height,
- #if MAPNIK_VERSION >= 300000
- filter_factor,
- #endif
- premultiplied
- );
- reader->read(0,0,raster->data_);
- feature->set_raster(raster);
- add_attributes(feature,f,layer_,tr_);
- return feature;
- }
- }
- }
- if (f.geometry_size() <= 0)
- {
- continue;
- }
- MAPNIK_UNIQUE_PTR<mapnik::geometry_type> geom(
- new mapnik::geometry_type(
- MAPNIK_GEOM_TYPE(f.type())));
- int cmd = -1;
- const int cmd_bits = 3;
- unsigned length = 0;
- double x = tile_x_, y = tile_y_;
- bool first = true;
- mapnik::box2d<double> envelope;
- double first_x=0;
- double first_y=0;
- for (int k = 0; k < f.geometry_size();)
- {
- if (!length) {
- unsigned cmd_length = f.geometry(k++);
- cmd = cmd_length & ((1 << cmd_bits) - 1);
- length = cmd_length >> cmd_bits;
- }
- if (length > 0) {
- length--;
- if (cmd == mapnik::SEG_MOVETO || cmd == mapnik::SEG_LINETO)
- {
- int32_t dx = f.geometry(k++);
- int32_t dy = f.geometry(k++);
- dx = ((dx >> 1) ^ (-(dx & 1)));
- dy = ((dy >> 1) ^ (-(dy & 1)));
- x += (static_cast<double>(dx) / scale_);
- y -= (static_cast<double>(dy) / scale_);
- if (cmd == mapnik::SEG_MOVETO)
- {
- first_x = x;
- first_y = y;
- }
- if (first)
- {
- envelope.init(x,y,x,y);
- first = false;
- }
- else
- {
- envelope.expand_to_include(x,y);
- }
- geom->push_vertex(x, y, static_cast<mapnik::CommandType>(cmd));
- }
- else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1)))
- {
- geom->push_vertex(first_x, first_y, mapnik::SEG_LINETO);
- geom->push_vertex(0, 0, mapnik::SEG_CLOSE);
- }
- else
- {
- std::stringstream msg;
- msg << "Unknown command type (tile_featureset): "
- << cmd;
- throw std::runtime_error(msg.str());
- }
- }
- }
- if (!filter_.pass(envelope))
- {
- continue;
- }
- if (f.has_id())
- {
- feature_id = f.id();
- }
- mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
- feature->paths().push_back(geom.release());
- add_attributes(feature,f,layer_,tr_);
- return feature;
- }
- return feature_ptr();
- }
+namespace vector_tile {
+ class Tile_Layer;
+}
- private:
- Filter filter_;
- mapnik::box2d<double> tile_extent_;
- mapnik::vector::tile_layer const& layer_;
- double tile_x_;
- double tile_y_;
- double scale_;
- unsigned itr_;
- unsigned end_;
- mapnik::transcoder tr_;
- mapnik::context_ptr ctx_;
- };
+namespace mapnik { namespace vector_tile_impl {
class tile_datasource : public datasource
{
public:
- tile_datasource(mapnik::vector::tile_layer const& layer,
+ tile_datasource(vector_tile::Tile_Layer const& layer,
unsigned x,
unsigned y,
unsigned z,
@@ -275,12 +26,12 @@ namespace mapnik { namespace vector {
void set_envelope(box2d<double> const& bbox);
box2d<double> get_tile_extent() const;
box2d<double> envelope() const;
- boost::optional<geometry_t> get_geometry_type() const;
+ boost::optional<datasource_geometry_t> get_geometry_type() const;
layer_descriptor get_descriptor() const;
private:
mutable mapnik::layer_descriptor desc_;
mutable bool attributes_added_;
- mapnik::vector::tile_layer const& layer_;
+ vector_tile::Tile_Layer const& layer_;
unsigned x_;
unsigned y_;
unsigned z_;
@@ -292,96 +43,10 @@ namespace mapnik { namespace vector {
double scale_;
};
-// tile_datasource impl
- inline tile_datasource::tile_datasource(mapnik::vector::tile_layer const& layer,
- unsigned x,
- unsigned y,
- unsigned z,
- unsigned tile_size)
- : datasource(parameters()),
- desc_("in-memory datasource","utf-8"),
- attributes_added_(false),
- layer_(layer),
- x_(x),
- y_(y),
- z_(z),
- tile_size_(tile_size),
- extent_initialized_(false) {
- double resolution = mapnik::EARTH_CIRCUMFERENCE/(1 << z_);
- tile_x_ = -0.5 * mapnik::EARTH_CIRCUMFERENCE + x_ * resolution;
- tile_y_ = 0.5 * mapnik::EARTH_CIRCUMFERENCE - y_ * resolution;
- scale_ = (static_cast<double>(layer_.extent()) / tile_size_) * tile_size_/resolution;
- }
-
- inline tile_datasource::~tile_datasource() {}
-
- inline datasource::datasource_t tile_datasource::type() const
- {
- return datasource::Vector;
- }
-
- inline featureset_ptr tile_datasource::features(query const& q) const
- {
- mapnik::filter_in_box filter(q.get_bbox());
- return MAPNIK_MAKE_SHARED<tile_featureset<mapnik::filter_in_box> >
- (filter, get_tile_extent(), q.property_names(), layer_, tile_x_, tile_y_, scale_);
- }
-
- inline featureset_ptr tile_datasource::features_at_point(coord2d const& pt, double tol) const
- {
- mapnik::filter_at_point filter(pt,tol);
- std::set<std::string> names;
- for (int i = 0; i < layer_.keys_size(); ++i)
- {
- names.insert(layer_.keys(i));
- }
- return MAPNIK_MAKE_SHARED<tile_featureset<filter_at_point> >
- (filter, get_tile_extent(), names, layer_, tile_x_, tile_y_, scale_);
- }
-
- inline void tile_datasource::set_envelope(box2d<double> const& bbox)
- {
- extent_initialized_ = true;
- extent_ = bbox;
- }
-
- inline box2d<double> tile_datasource::get_tile_extent() const
- {
- mapnik::vector::spherical_mercator merc(tile_size_);
- double minx,miny,maxx,maxy;
- merc.xyz(x_,y_,z_,minx,miny,maxx,maxy);
- return box2d<double>(minx,miny,maxx,maxy);
- }
-
- inline box2d<double> tile_datasource::envelope() const
- {
- if (!extent_initialized_)
- {
- extent_ = get_tile_extent();
- extent_initialized_ = true;
- }
- return extent_;
- }
-
- inline boost::optional<datasource::geometry_t> tile_datasource::get_geometry_type() const
- {
- return datasource::Collection;
- }
-
- inline layer_descriptor tile_datasource::get_descriptor() const
- {
- if (!attributes_added_)
- {
- for (int i = 0; i < layer_.keys_size(); ++i)
- {
- // Object type here because we don't know the precise value until features are unpacked
- desc_.add_descriptor(attribute_descriptor(layer_.keys(i), Object));
- }
- attributes_added_ = true;
- }
- return desc_;
- }
+}} // end ns
- }} // end ns
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_datasource.ipp"
+#endif
#endif // __MAPNIK_VECTOR_TILE_DATASOURCE_H__
diff --git a/src/vector_tile_datasource.hpp b/src/vector_tile_datasource.ipp
similarity index 51%
copy from src/vector_tile_datasource.hpp
copy to src/vector_tile_datasource.ipp
index bc878d5..1176b71 100644
--- a/src/vector_tile_datasource.hpp
+++ b/src/vector_tile_datasource.ipp
@@ -1,8 +1,6 @@
-#ifndef __MAPNIK_VECTOR_TILE_DATASOURCE_H__
-#define __MAPNIK_VECTOR_TILE_DATASOURCE_H__
-
#include "vector_tile.pb.h"
#include "vector_tile_projection.hpp"
+#include "vector_tile_geometry_decoder.hpp"
#include <mapnik/box2d.hpp>
#include <mapnik/coord.hpp>
@@ -20,26 +18,23 @@
#include <mapnik/feature.hpp>
#include <mapnik/feature_factory.hpp>
#include <mapnik/geom_util.hpp>
+#include <mapnik/image.hpp>
#include <mapnik/image_reader.hpp>
#include <mapnik/raster.hpp>
+#include <mapnik/view_transform.hpp>
#include <memory>
#include <stdexcept>
#include <string>
#include <boost/optional.hpp>
-#include <boost/ptr_container/ptr_vector.hpp>
#include <unicode/unistr.h>
-#include "mapnik3x_compatibility.hpp"
-#include MAPNIK_MAKE_SHARED_INCLUDE
-#include MAPNIK_SHARED_INCLUDE
-
-namespace mapnik { namespace vector {
+namespace mapnik { namespace vector_tile_impl {
void add_attributes(mapnik::feature_ptr feature,
- mapnik::vector::tile_feature const& f,
- mapnik::vector::tile_layer const& layer,
+ vector_tile::Tile_Feature const& f,
+ vector_tile::Tile_Layer const& layer,
mapnik::transcoder const& tr)
{
std::size_t num_keys = static_cast<std::size_t>(layer.keys_size());
@@ -54,10 +49,10 @@ namespace mapnik { namespace vector {
std::string const& name = layer.keys(key_name);
if (feature->has_key(name))
{
- mapnik::vector::tile_value const& value = layer.values(key_value);
+ vector_tile::Tile_Value const& value = layer.values(key_value);
if (value.has_string_value())
{
- std::string str = value.string_value();
+ std::string const& str = value.string_value();
feature->put(name, tr.transcode(str.data(), str.length()));
}
else if (value.has_int_value())
@@ -95,13 +90,15 @@ namespace mapnik { namespace vector {
public:
tile_featureset(Filter const& filter,
mapnik::box2d<double> const& tile_extent,
+ mapnik::box2d<double> const& unbuffered_query,
std::set<std::string> const& attribute_names,
- mapnik::vector::tile_layer const& layer,
+ vector_tile::Tile_Layer const& layer,
double tile_x,
double tile_y,
double scale)
: filter_(filter),
tile_extent_(tile_extent),
+ unbuffered_query_(unbuffered_query),
layer_(layer),
tile_x_(tile_x),
tile_y_(tile_y),
@@ -109,7 +106,7 @@ namespace mapnik { namespace vector {
itr_(0),
end_(layer_.features_size()),
tr_("utf-8"),
- ctx_(MAPNIK_MAKE_SHARED<mapnik::context_type>())
+ ctx_(std::make_shared<mapnik::context_type>())
{
std::set<std::string>::const_iterator pos = attribute_names.begin();
std::set<std::string>::const_iterator end = attribute_names.end();
@@ -132,40 +129,60 @@ namespace mapnik { namespace vector {
{
while (itr_ < end_)
{
- mapnik::vector::tile_feature const& f = layer_.features(itr_);
+ vector_tile::Tile_Feature const& f = layer_.features(itr_);
mapnik::value_integer feature_id = itr_++;
+ if (f.has_id())
+ {
+ feature_id = f.id();
+ }
if (f.has_raster())
{
std::string const& image_buffer = f.raster();
- MAPNIK_UNIQUE_PTR<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.data(),image_buffer.size()));
+ std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.data(),image_buffer.size()));
if (reader.get())
{
int image_width = reader->width();
int image_height = reader->height();
if (image_width > 0 && image_height > 0)
{
- if (f.has_id())
+ mapnik::view_transform t(image_width, image_height, tile_extent_, 0, 0);
+ box2d<double> intersect = tile_extent_.intersect(unbuffered_query_);
+ box2d<double> ext = t.forward(intersect);
+ if (ext.width() > 0.5 && ext.height() > 0.5 )
{
- feature_id = f.id();
+ // select minimum raster containing whole ext
+ int x_off = static_cast<int>(std::floor(ext.minx() +.5));
+ int y_off = static_cast<int>(std::floor(ext.miny() +.5));
+ int end_x = static_cast<int>(std::floor(ext.maxx() +.5));
+ int end_y = static_cast<int>(std::floor(ext.maxy() +.5));
+
+ // clip to available data
+ if (x_off < 0)
+ x_off = 0;
+ if (y_off < 0)
+ y_off = 0;
+ if (end_x > image_width)
+ end_x = image_width;
+ if (end_y > image_height)
+ end_y = image_height;
+ int width = end_x - x_off;
+ int height = end_y - y_off;
+ box2d<double> feature_raster_extent(x_off,
+ y_off,
+ x_off + width,
+ y_off + height);
+ intersect = t.backward(feature_raster_extent);
+ double filter_factor = 1.0;
+ mapnik::image_any data = reader->read(x_off, y_off, width, height);
+ mapnik::raster_ptr raster = std::make_shared<mapnik::raster>(intersect,
+ data,
+ filter_factor
+ );
+ mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
+ feature->set_raster(raster);
+ add_attributes(feature,f,layer_,tr_);
+ return feature;
}
- #if MAPNIK_VERSION >= 300000
- double filter_factor = 1.0;
- #endif
- bool premultiplied = false;
- mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
- mapnik::raster_ptr raster = MAPNIK_MAKE_SHARED<mapnik::raster>(
- tile_extent_,
- image_width,
- image_height,
- #if MAPNIK_VERSION >= 300000
- filter_factor,
- #endif
- premultiplied
- );
- reader->read(0,0,raster->data_);
- feature->set_raster(raster);
- add_attributes(feature,f,layer_,tr_);
- return feature;
}
}
}
@@ -173,74 +190,18 @@ namespace mapnik { namespace vector {
{
continue;
}
- MAPNIK_UNIQUE_PTR<mapnik::geometry_type> geom(
- new mapnik::geometry_type(
- MAPNIK_GEOM_TYPE(f.type())));
- int cmd = -1;
- const int cmd_bits = 3;
- unsigned length = 0;
- double x = tile_x_, y = tile_y_;
- bool first = true;
- mapnik::box2d<double> envelope;
- double first_x=0;
- double first_y=0;
- for (int k = 0; k < f.geometry_size();)
+ mapnik::geometry::geometry<double> geom = decode_geometry(f,tile_x_,tile_y_,scale_,-1*scale_);
+ if (geom.is<mapnik::geometry::geometry_empty>())
{
- if (!length) {
- unsigned cmd_length = f.geometry(k++);
- cmd = cmd_length & ((1 << cmd_bits) - 1);
- length = cmd_length >> cmd_bits;
- }
- if (length > 0) {
- length--;
- if (cmd == mapnik::SEG_MOVETO || cmd == mapnik::SEG_LINETO)
- {
- int32_t dx = f.geometry(k++);
- int32_t dy = f.geometry(k++);
- dx = ((dx >> 1) ^ (-(dx & 1)));
- dy = ((dy >> 1) ^ (-(dy & 1)));
- x += (static_cast<double>(dx) / scale_);
- y -= (static_cast<double>(dy) / scale_);
- if (cmd == mapnik::SEG_MOVETO)
- {
- first_x = x;
- first_y = y;
- }
- if (first)
- {
- envelope.init(x,y,x,y);
- first = false;
- }
- else
- {
- envelope.expand_to_include(x,y);
- }
- geom->push_vertex(x, y, static_cast<mapnik::CommandType>(cmd));
- }
- else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1)))
- {
- geom->push_vertex(first_x, first_y, mapnik::SEG_LINETO);
- geom->push_vertex(0, 0, mapnik::SEG_CLOSE);
- }
- else
- {
- std::stringstream msg;
- msg << "Unknown command type (tile_featureset): "
- << cmd;
- throw std::runtime_error(msg.str());
- }
- }
+ continue;
}
+ mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
if (!filter_.pass(envelope))
{
continue;
}
- if (f.has_id())
- {
- feature_id = f.id();
- }
mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
- feature->paths().push_back(geom.release());
+ feature->set_geometry(std::move(geom));
add_attributes(feature,f,layer_,tr_);
return feature;
}
@@ -250,7 +211,8 @@ namespace mapnik { namespace vector {
private:
Filter filter_;
mapnik::box2d<double> tile_extent_;
- mapnik::vector::tile_layer const& layer_;
+ mapnik::box2d<double> unbuffered_query_;
+ vector_tile::Tile_Layer const& layer_;
double tile_x_;
double tile_y_;
double scale_;
@@ -260,40 +222,8 @@ namespace mapnik { namespace vector {
mapnik::context_ptr ctx_;
};
- class tile_datasource : public datasource
- {
- public:
- tile_datasource(mapnik::vector::tile_layer const& layer,
- unsigned x,
- unsigned y,
- unsigned z,
- unsigned tile_size);
- virtual ~tile_datasource();
- datasource::datasource_t type() const;
- featureset_ptr features(query const& q) const;
- featureset_ptr features_at_point(coord2d const& pt, double tol = 0) const;
- void set_envelope(box2d<double> const& bbox);
- box2d<double> get_tile_extent() const;
- box2d<double> envelope() const;
- boost::optional<geometry_t> get_geometry_type() const;
- layer_descriptor get_descriptor() const;
- private:
- mutable mapnik::layer_descriptor desc_;
- mutable bool attributes_added_;
- mapnik::vector::tile_layer const& layer_;
- unsigned x_;
- unsigned y_;
- unsigned z_;
- unsigned tile_size_;
- mutable bool extent_initialized_;
- mutable mapnik::box2d<double> extent_;
- double tile_x_;
- double tile_y_;
- double scale_;
- };
-
-// tile_datasource impl
- inline tile_datasource::tile_datasource(mapnik::vector::tile_layer const& layer,
+ // tile_datasource impl
+ tile_datasource::tile_datasource(vector_tile::Tile_Layer const& layer,
unsigned x,
unsigned y,
unsigned z,
@@ -313,21 +243,21 @@ namespace mapnik { namespace vector {
scale_ = (static_cast<double>(layer_.extent()) / tile_size_) * tile_size_/resolution;
}
- inline tile_datasource::~tile_datasource() {}
+ tile_datasource::~tile_datasource() {}
- inline datasource::datasource_t tile_datasource::type() const
+ datasource::datasource_t tile_datasource::type() const
{
return datasource::Vector;
}
- inline featureset_ptr tile_datasource::features(query const& q) const
+ featureset_ptr tile_datasource::features(query const& q) const
{
mapnik::filter_in_box filter(q.get_bbox());
- return MAPNIK_MAKE_SHARED<tile_featureset<mapnik::filter_in_box> >
- (filter, get_tile_extent(), q.property_names(), layer_, tile_x_, tile_y_, scale_);
+ return std::make_shared<tile_featureset<mapnik::filter_in_box> >
+ (filter, get_tile_extent(), q.get_unbuffered_bbox(), q.property_names(), layer_, tile_x_, tile_y_, scale_);
}
- inline featureset_ptr tile_datasource::features_at_point(coord2d const& pt, double tol) const
+ featureset_ptr tile_datasource::features_at_point(coord2d const& pt, double tol) const
{
mapnik::filter_at_point filter(pt,tol);
std::set<std::string> names;
@@ -335,25 +265,25 @@ namespace mapnik { namespace vector {
{
names.insert(layer_.keys(i));
}
- return MAPNIK_MAKE_SHARED<tile_featureset<filter_at_point> >
- (filter, get_tile_extent(), names, layer_, tile_x_, tile_y_, scale_);
+ return std::make_shared<tile_featureset<filter_at_point> >
+ (filter, get_tile_extent(), get_tile_extent(), names, layer_, tile_x_, tile_y_, scale_);
}
- inline void tile_datasource::set_envelope(box2d<double> const& bbox)
+ void tile_datasource::set_envelope(box2d<double> const& bbox)
{
extent_initialized_ = true;
extent_ = bbox;
}
- inline box2d<double> tile_datasource::get_tile_extent() const
+ box2d<double> tile_datasource::get_tile_extent() const
{
- mapnik::vector::spherical_mercator merc(tile_size_);
+ spherical_mercator merc(tile_size_);
double minx,miny,maxx,maxy;
merc.xyz(x_,y_,z_,minx,miny,maxx,maxy);
return box2d<double>(minx,miny,maxx,maxy);
}
- inline box2d<double> tile_datasource::envelope() const
+ box2d<double> tile_datasource::envelope() const
{
if (!extent_initialized_)
{
@@ -363,12 +293,12 @@ namespace mapnik { namespace vector {
return extent_;
}
- inline boost::optional<datasource::geometry_t> tile_datasource::get_geometry_type() const
+ boost::optional<mapnik::datasource_geometry_t> tile_datasource::get_geometry_type() const
{
- return datasource::Collection;
+ return mapnik::datasource_geometry_t::Collection;
}
- inline layer_descriptor tile_datasource::get_descriptor() const
+ layer_descriptor tile_datasource::get_descriptor() const
{
if (!attributes_added_)
{
@@ -383,5 +313,3 @@ namespace mapnik { namespace vector {
}
}} // end ns
-
-#endif // __MAPNIK_VECTOR_TILE_DATASOURCE_H__
diff --git a/src/vector_tile_geometry_decoder.hpp b/src/vector_tile_geometry_decoder.hpp
new file mode 100644
index 0000000..938d2e4
--- /dev/null
+++ b/src/vector_tile_geometry_decoder.hpp
@@ -0,0 +1,328 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
+
+#include "vector_tile.pb.h"
+
+#include <mapnik/util/is_clockwise.hpp>
+
+//std
+#include <algorithm>
+
+namespace mapnik { namespace vector_tile_impl {
+
+class Geometry {
+
+public:
+ inline explicit Geometry(vector_tile::Tile_Feature const& f,
+ double tile_x, double tile_y,
+ double scale_x, double scale_y);
+
+ enum command : uint8_t {
+ end = 0,
+ move_to = 1,
+ line_to = 2,
+ close = 7
+ };
+
+ inline command next(double& rx, double& ry);
+
+private:
+ vector_tile::Tile_Feature const& f_;
+ double scale_x_;
+ double scale_y_;
+ uint32_t k;
+ uint32_t geoms_;
+ uint8_t cmd;
+ uint32_t length;
+ double x, y;
+ double ox, oy;
+};
+
+Geometry::Geometry(vector_tile::Tile_Feature const& f,
+ double tile_x, double tile_y,
+ double scale_x, double scale_y)
+ : f_(f),
+ scale_x_(scale_x),
+ scale_y_(scale_y),
+ k(0),
+ geoms_(f_.geometry_size()),
+ cmd(1),
+ length(0),
+ x(tile_x), y(tile_y),
+ ox(0), oy(0) {}
+
+Geometry::command Geometry::next(double& rx, double& ry) {
+ if (k < geoms_) {
+ if (length == 0) {
+ uint32_t cmd_length = static_cast<uint32_t>(f_.geometry(k++));
+ cmd = cmd_length & 0x7;
+ length = cmd_length >> 3;
+ }
+
+ --length;
+
+ if (cmd == move_to || cmd == line_to) {
+ int32_t dx = f_.geometry(k++);
+ int32_t dy = f_.geometry(k++);
+ dx = ((dx >> 1) ^ (-(dx & 1)));
+ dy = ((dy >> 1) ^ (-(dy & 1)));
+ x += (static_cast<double>(dx) / scale_x_);
+ y += (static_cast<double>(dy) / scale_y_);
+ rx = x;
+ ry = y;
+ if (cmd == move_to) {
+ ox = x;
+ oy = y;
+ return move_to;
+ } else {
+ return line_to;
+ }
+ } else if (cmd == close) {
+ rx = ox;
+ ry = oy;
+ return close;
+ } else {
+ fprintf(stderr, "unknown command: %d\n", cmd);
+ return end;
+ }
+ } else {
+ return end;
+ }
+}
+
+inline mapnik::geometry::geometry<double> decode_geometry(vector_tile::Tile_Feature const& f,
+ double tile_x, double tile_y,
+ double scale_x, double scale_y,
+ bool treat_all_rings_as_exterior=false)
+{
+ Geometry::command cmd;
+ Geometry geoms(f,tile_x,tile_y,scale_x,scale_y);
+ double x1, y1;
+ mapnik::geometry::geometry<double> geom; // output geometry
+
+ switch (f.type())
+ {
+ case vector_tile::Tile_GeomType_POINT:
+ {
+ mapnik::geometry::multi_point<double> mp;
+ while ((cmd = geoms.next(x1, y1)) != Geometry::end)
+ {
+ mp.emplace_back(mapnik::geometry::point<double>(x1,y1));
+ }
+ std::size_t num_points = mp.size();
+ if (num_points == 1)
+ {
+ // return the single point
+ geom = std::move(mp[0]);
+ return geom;
+ }
+ else if (num_points > 1)
+ {
+ // return multipoint
+ geom = std::move(mp);
+ return geom;
+ }
+ break;
+ }
+ case vector_tile::Tile_GeomType_LINESTRING:
+ {
+ mapnik::geometry::multi_line_string<double> multi_line;
+ multi_line.emplace_back();
+ bool first = true;
+ while ((cmd = geoms.next(x1, y1)) != Geometry::end)
+ {
+ if (cmd == Geometry::move_to)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ multi_line.emplace_back();
+ }
+ }
+ multi_line.back().add_coord(x1,y1);
+ }
+ if (multi_line.empty())
+ {
+ return geom;
+ }
+ std::size_t num_lines = multi_line.size();
+ if (num_lines == 1)
+ {
+ // return the single line
+ auto itr = std::make_move_iterator(multi_line.begin());
+ if (itr->size() > 1)
+ {
+ geom = std::move(*itr);
+ }
+ return geom;
+ }
+ else if (num_lines > 1)
+ {
+ // return multiline
+ geom = std::move(multi_line);
+ return geom;
+ }
+ break;
+ }
+ case vector_tile::Tile_GeomType_POLYGON:
+ {
+ std::vector<mapnik::geometry::linear_ring<double>> rings;
+ rings.emplace_back();
+ double x2,y2;
+ bool first = true;
+ while ((cmd = geoms.next(x1, y1)) != Geometry::end)
+ {
+ if (cmd == Geometry::move_to)
+ {
+ x2 = x1;
+ y2 = y1;
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ rings.emplace_back();
+ }
+ }
+ else if (cmd == Geometry::close)
+ {
+ rings.back().add_coord(x2,y2);
+ continue;
+ }
+ rings.back().add_coord(x1,y1);
+ }
+
+ auto rings_itr = std::make_move_iterator(rings.begin());
+ auto rings_end = std::make_move_iterator(rings.end());
+ std::size_t num_rings = rings.size();
+ if (num_rings == 1)
+ {
+ if (rings_itr->size() < 4)
+ {
+ return geom;
+ }
+ if (mapnik::util::is_clockwise(*rings_itr))
+ {
+ // Its clockwise, so lets reverse it.
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ }
+ // return the single polygon without interior rings
+ mapnik::geometry::polygon<double> poly;
+ poly.set_exterior_ring(std::move(*rings_itr));
+ geom = std::move(poly);
+ return geom;
+ }
+
+ // Multiple rings represent either:
+ // 1) a polygon with interior ring(s)
+ // 2) a multipolygon with polygons with no interior ring(s)
+ // 3) a multipolygon with polygons with interior ring(s)
+ mapnik::geometry::multi_polygon<double> multi_poly;
+ first = true;
+ // back compatibility mode to previous Mapnik (pre new geometry)
+ // which pushed all rings into single path
+ if (treat_all_rings_as_exterior)
+ {
+ for (; rings_itr != rings_end; ++rings_itr)
+ {
+ bool degenerate_ring = (rings_itr->size() < 4);
+ if (degenerate_ring) continue;
+ multi_poly.emplace_back();
+ if (mapnik::util::is_clockwise(*rings_itr))
+ {
+ // Its clockwise, so lets reverse it.
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ }
+ multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+ }
+ }
+ else
+ {
+ bool exterior_was_degenerate = false;
+ bool first_winding_order = true;
+ for (; rings_itr != rings_end; ++rings_itr)
+ {
+ bool degenerate_ring = (rings_itr->size() < 4);
+ if (first)
+ {
+ if (degenerate_ring)
+ {
+ exterior_was_degenerate = true;
+ continue;
+ }
+ first_winding_order = mapnik::util::is_clockwise(*rings_itr);
+ // first ring always exterior and sets all future winding order
+ multi_poly.emplace_back();
+ if (first_winding_order)
+ {
+ // Going into mapnik we want the outer ring to be CCW
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ }
+ multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+ first = false;
+ }
+ else if (first_winding_order == mapnik::util::is_clockwise(*rings_itr))
+ {
+ if (degenerate_ring) continue;
+ // hit a new exterior ring, so start a new polygon
+ multi_poly.emplace_back(); // start new polygon
+ if (first_winding_order)
+ {
+ // Going into mapnik we want the outer ring to be CCW,
+ // since first winding order was CW, we need to reverse
+ // these rings.
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ }
+ multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+ exterior_was_degenerate = false;
+ }
+ else
+ {
+ if (exterior_was_degenerate || degenerate_ring) continue;
+ if (first_winding_order)
+ {
+ // Going into mapnik we want the inner ring to be CW,
+ // since first winding order of the outer ring CW, we
+ // need to reverse these rings as they are CCW.
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ }
+ multi_poly.back().add_hole(std::move(*rings_itr));
+ }
+ }
+ }
+ auto num_poly = multi_poly.size();
+ if (num_poly == 0)
+ {
+ return geom;
+ }
+ else if (num_poly == 1)
+ {
+ auto itr = std::make_move_iterator(multi_poly.begin());
+ geom = std::move(mapnik::geometry::polygon<double>(std::move(*itr)));
+ return geom;
+ }
+ else
+ {
+ geom = std::move(multi_poly);
+ return geom;
+ }
+ break;
+ }
+ case vector_tile::Tile_GeomType_UNKNOWN:
+ default:
+ {
+ throw std::runtime_error("unhandled geometry type during decoding");
+ break;
+ }
+ }
+ return geom;
+}
+
+}} // end ns
+
+
+#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
diff --git a/src/vector_tile_geometry_encoder.hpp b/src/vector_tile_geometry_encoder.hpp
index 67bbd32..f14f543 100644
--- a/src/vector_tile_geometry_encoder.hpp
+++ b/src/vector_tile_geometry_encoder.hpp
@@ -3,189 +3,141 @@
// vector tile
#include "vector_tile.pb.h"
-#include <mapnik/vertex.hpp>
-#include <mapnik/version.hpp>
+#include <mapnik/geometry.hpp>
+#include "vector_tile_config.hpp"
+#include <cstdlib>
+#include <cmath>
+#include <sstream>
+#include <iostream>
-namespace mapnik { namespace vector {
+namespace mapnik { namespace vector_tile_impl {
-inline void handle_skipped_last(tile_feature & current_feature,
- int32_t skipped_index,
- int32_t cur_x,
- int32_t cur_y,
- int32_t & x_,
- int32_t & y_)
+inline unsigned encode_geometry(mapnik::geometry::point<std::int64_t> const& pt,
+ vector_tile::Tile_Feature & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
{
- uint32_t last_x = current_feature.geometry(skipped_index - 2);
- uint32_t last_y = current_feature.geometry(skipped_index - 1);
- int32_t last_dx = ((last_x >> 1) ^ (-(last_x & 1)));
- int32_t last_dy = ((last_y >> 1) ^ (-(last_y & 1)));
- int32_t dx = cur_x - x_ + last_dx;
- int32_t dy = cur_y - y_ + last_dy;
- x_ = cur_x;
- y_ = cur_y;
- current_feature.set_geometry(skipped_index - 2, ((dx << 1) ^ (dx >> 31)));
- current_feature.set_geometry(skipped_index - 1, ((dy << 1) ^ (dy >> 31)));
+ current_feature.add_geometry(9); // 1 | (move_to << 3)
+ int32_t dx = pt.x - start_x;
+ int32_t dy = pt.y - start_y;
+ // Manual zigzag encoding.
+ current_feature.add_geometry((dx << 1) ^ (dx >> 31));
+ current_feature.add_geometry((dy << 1) ^ (dy >> 31));
+ start_x = pt.x;
+ start_y = pt.y;
+ return 1;
}
-template <typename T>
-unsigned encode_geometry(T & path,
- tile_GeomType type,
- tile_feature & current_feature,
- int32_t & x_,
- int32_t & y_,
- unsigned tolerance,
- unsigned path_multiplier)
+inline unsigned encode_geometry(mapnik::geometry::line_string<std::int64_t> const& line,
+ vector_tile::Tile_Feature & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
{
- unsigned count = 0;
- path.rewind(0);
- current_feature.set_type(type);
-
- vertex2d vtx(vertex2d::no_init);
- int cmd = -1;
- int prev_cmd = -1;
- int cmd_idx = -1;
const int cmd_bits = 3;
- unsigned length = 0;
- bool skipped_last = false;
- int32_t skipped_index = -1;
- int32_t cur_x = 0;
- int32_t cur_y = 0;
+ int32_t line_to_length = static_cast<int32_t>(line.size()) - 1;
- // See vector_tile.proto for a description of how vertex command
- // encoding works.
+ enum {
+ move_to = 1,
+ line_to = 2,
+ coords = 3
+ } status = move_to;
- std::vector<vertex2d> output;
- const std::size_t buffer_size = 8;
- output.reserve(buffer_size);
- bool done = false;
- bool cache = true;
- while (true)
+ for (auto const& pt : line)
{
- if (cache)
+ if (status == move_to)
{
- // read
- vertex2d v(vertex2d::no_init);
- while ((v.cmd = path.vertex(&v.x, &v.y)) != SEG_END)
- {
-#if MAPNIK_VERSION >= 300000
- output.push_back(std::move(v));
-#else
- output.push_back(v);
-#endif
- if (output.size() == buffer_size) break;
- }
- cache = false;
- if (v.cmd == SEG_END)
- {
- done = true;
- }
+ status = line_to;
+ current_feature.add_geometry(9); // 1 | (move_to << 3)
}
- else
+ else if (status == line_to)
{
- if (done && output.empty()) break;
- // process
- vtx = output.front();
- {
- if (static_cast<int>(vtx.cmd) != cmd)
- {
- if (cmd_idx >= 0)
- {
- // Encode the previous length/command value.
- current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
- }
- cmd = static_cast<int>(vtx.cmd);
- length = 0;
- cmd_idx = current_feature.geometry_size();
- current_feature.add_geometry(0); // placeholder added in first pass
- }
-
- switch (vtx.cmd)
- {
- case SEG_MOVETO:
- case SEG_LINETO:
- {
- if (cmd == SEG_MOVETO && skipped_last && skipped_index > 1) // at least one vertex + cmd/length
- {
- // if we skipped previous vertex we just update it to the last one here.
- handle_skipped_last(current_feature, skipped_index, cur_x, cur_y, x_, y_);
- }
+ status = coords;
+ current_feature.add_geometry((line_to_length << cmd_bits) | 2); // len | (line_to << 3)
+ }
+ int32_t dx = pt.x - start_x;
+ int32_t dy = pt.y - start_y;
+ // Manual zigzag encoding.
+ current_feature.add_geometry((dx << 1) ^ (dx >> 31));
+ current_feature.add_geometry((dy << 1) ^ (dy >> 31));
+ start_x = pt.x;
+ start_y = pt.y;
+ }
+ return line.size();
+}
- // Compute delta to the previous coordinate.
- cur_x = static_cast<int32_t>(std::floor((vtx.x * path_multiplier) + 0.5));
- cur_y = static_cast<int32_t>(std::floor((vtx.y * path_multiplier) + 0.5));
- int32_t dx = cur_x - x_;
- int32_t dy = cur_y - y_;
- bool sharp_turn_ahead = false;
- if (output.size() > 1)
- {
- vertex2d const& next_vtx = output[1];
- if (next_vtx.cmd == SEG_LINETO)
- {
- uint32_t next_dx = std::abs(cur_x - static_cast<int32_t>(std::floor((next_vtx.x * path_multiplier) + 0.5)));
- uint32_t next_dy = std::abs(cur_y - static_cast<int32_t>(std::floor((next_vtx.y * path_multiplier) + 0.5)));
- if ((next_dx == 0 && next_dy >= tolerance) || (next_dy == 0 && next_dx >= tolerance))
- {
- sharp_turn_ahead = true;
- }
- }
- }
- // Keep all move_to commands, but omit other movements that are
- // not >= the tolerance threshold and should be considered no-ops.
- // NOTE: length == 0 indicates the command has changed and will
- // preserve any non duplicate move_to or line_to
- if ( length == 0 || sharp_turn_ahead ||
- (static_cast<unsigned>(std::abs(dx)) >= tolerance) ||
- (static_cast<unsigned>(std::abs(dy)) >= tolerance)
- )
- {
- // Manual zigzag encoding.
- current_feature.add_geometry((dx << 1) ^ (dx >> 31));
- current_feature.add_geometry((dy << 1) ^ (dy >> 31));
- x_ = cur_x;
- y_ = cur_y;
- skipped_last = false;
- ++length;
- }
- else
- {
- skipped_last = true;
- skipped_index = current_feature.geometry_size();
- }
- break;
- }
+inline unsigned encode_geometry(mapnik::geometry::linear_ring<std::int64_t> const& ring,
+ vector_tile::Tile_Feature & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ if (ring.size() < 3) return 0;
+ const int cmd_bits = 3;
+ int32_t line_to_length = static_cast<int32_t>(ring.size()) - 1;
+ int count = 0;
+ enum {
+ move_to = 1,
+ line_to = 2,
+ coords = 3
+ } status = move_to;
+ bool drop_last = false;
+ if (ring.size() > 2 && ring.front() == ring.back())
+ {
+ drop_last = true;
+ line_to_length -= 1;
+ if (line_to_length < 2)
+ {
+ return 0;
+ }
+ }
- case SEG_CLOSE:
- {
- if (prev_cmd != SEG_CLOSE) ++length;
- break;
- }
- default:
- std::stringstream msg;
- msg << "Unknown command type (backend_pbf): "
- << cmd;
- throw std::runtime_error(msg.str());
- break;
- }
- }
- ++count;
- prev_cmd = cmd;
- output.erase(output.begin());
- if (output.size() < 2) cache = true;
+ for (auto const& pt : ring)
+ {
+ if (status == move_to)
+ {
+ status = line_to;
+ current_feature.add_geometry(9); // 1 | (move_to << 3)
+ }
+ else if (status == line_to)
+ {
+ status = coords;
+ current_feature.add_geometry((line_to_length<< cmd_bits) | 2); // len | (line_to << 3)
}
+ else if (drop_last && count == line_to_length + 1)
+ {
+ continue;
+ }
+ int32_t dx = pt.x - start_x;
+ int32_t dy = pt.y - start_y;
+ // Manual zigzag encoding.
+ current_feature.add_geometry((dx << 1) ^ (dx >> 31));
+ current_feature.add_geometry((dy << 1) ^ (dy >> 31));
+ start_x = pt.x;
+ start_y = pt.y;
+ ++count;
}
- if (skipped_last && skipped_index > 1) // at least one vertex + cmd/length
+ current_feature.add_geometry(15); // close_path
+ return line_to_length;
+}
+
+inline unsigned encode_geometry(mapnik::geometry::polygon<std::int64_t> const& poly,
+ vector_tile::Tile_Feature & current_feature,
+ int32_t & x_,
+ int32_t & y_)
+{
+ unsigned count = 0;
+ count += encode_geometry(poly.exterior_ring, current_feature, x_, y_);
+ if (count == 0)
{
- // if we skipped previous vertex we just update it to the last one here.
- handle_skipped_last(current_feature, skipped_index, cur_x, cur_y, x_, y_);
+ return count;
}
- // Update the last length/command value.
- if (cmd_idx >= 0)
+ for (auto const& ring : poly.interior_rings)
{
- current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
+ count += encode_geometry(ring, current_feature, x_, y_);
}
return count;
}
+
}} // end ns
#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
diff --git a/src/vector_tile_processor.cpp b/src/vector_tile_processor.cpp
new file mode 100644
index 0000000..4239e31
--- /dev/null
+++ b/src/vector_tile_processor.cpp
@@ -0,0 +1,4 @@
+#include "vector_tile_processor.hpp"
+#include "vector_tile_processor.ipp"
+#include "vector_tile_backend_pbf.hpp"
+template class mapnik::vector_tile_impl::processor<mapnik::vector_tile_impl::backend_pbf>;
diff --git a/src/vector_tile_processor.hpp b/src/vector_tile_processor.hpp
index bb1f97e..3cd41ea 100644
--- a/src/vector_tile_processor.hpp
+++ b/src/vector_tile_processor.hpp
@@ -2,54 +2,18 @@
#define __MAPNIK_VECTOR_PROCESSOR_H__
#include <mapnik/map.hpp>
-#include <mapnik/request.hpp>
#include <mapnik/layer.hpp>
-#include <mapnik/query.hpp>
-#include <mapnik/ctrans.hpp>
-#include <mapnik/geometry.hpp>
#include <mapnik/feature.hpp>
-#include <mapnik/datasource.hpp>
-#include <mapnik/projection.hpp>
-#include <mapnik/proj_transform.hpp>
-#include <mapnik/scale_denominator.hpp>
-#include <mapnik/attribute_descriptor.hpp>
-#include <mapnik/feature_layer_desc.hpp>
-#include <mapnik/config.hpp>
-#include <mapnik/box2d.hpp>
-#include <mapnik/version.hpp>
-#include <mapnik/noncopyable.hpp>
-#include <mapnik/image_util.hpp>
-#include <mapnik/raster.hpp>
-#include <mapnik/warp.hpp>
-#include <mapnik/version.hpp>
+#include <mapnik/util/noncopyable.hpp>
+#include <mapnik/request.hpp>
+#include <mapnik/view_transform.hpp>
#include <mapnik/image_scaling.hpp>
#include <mapnik/image_compositing.hpp>
+#include <mapnik/geometry.hpp>
-// agg
-#ifdef CONV_CLIPPER
-#include "agg_path_storage.h"
-#include "agg_conv_clipper.h"
-#else
-#include "agg_conv_clip_polygon.h"
-#endif
-
-#include "agg_conv_clip_polyline.h"
-#include "agg_rendering_buffer.h"
-#include "agg_pixfmt_rgba.h"
-
-#include <boost/foreach.hpp>
-#include <boost/optional.hpp>
-#include <boost/ptr_container/ptr_vector.hpp>
-
-#include <iostream>
-#include <string>
-#include <stdexcept>
-
-#include "mapnik3x_compatibility.hpp"
-#include MAPNIK_MAKE_SHARED_INCLUDE
-#include MAPNIK_SHARED_INCLUDE
+#include "vector_tile_config.hpp"
-namespace mapnik { namespace vector {
+namespace mapnik { namespace vector_tile_impl {
/*
@@ -61,377 +25,67 @@ namespace mapnik { namespace vector {
that would normally come from a style's symbolizers
*/
- template <typename T>
- class processor : private mapnik::noncopyable
+template <typename T>
+class processor : private mapnik::util::noncopyable
+{
+public:
+ typedef T backend_type;
+private:
+ backend_type & backend_;
+ mapnik::Map const& m_;
+ mapnik::request const& m_req_;
+ double scale_factor_;
+ mapnik::view_transform t_;
+ double area_threshold_;
+ std::string image_format_;
+ scaling_method_e scaling_method_;
+ bool painted_;
+ double simplify_distance_;
+public:
+ MAPNIK_VECTOR_INLINE processor(T & backend,
+ mapnik::Map const& map,
+ mapnik::request const& m_req,
+ double scale_factor=1.0,
+ unsigned offset_x=0,
+ unsigned offset_y=0,
+ double area_threshold=0.1,
+ std::string const& image_format="jpeg",
+ scaling_method_e scaling_method=SCALING_NEAR
+ );
+
+ inline void set_simplify_distance(double dist)
+ {
+ simplify_distance_ = dist;
+ }
+
+ inline double get_simplify_distance() const
{
- public:
- typedef T backend_type;
- private:
- backend_type & backend_;
- mapnik::Map const& m_;
- mapnik::request const& m_req_;
- double scale_factor_;
- mapnik::CoordTransform t_;
- unsigned tolerance_;
- std::string image_format_;
- scaling_method_e scaling_method_;
- bool painted_;
- public:
- processor(T & backend,
- mapnik::Map const& map,
- mapnik::request const& m_req,
- double scale_factor=1.0,
- unsigned offset_x=0,
- unsigned offset_y=0,
- unsigned tolerance=1,
- std::string const& image_format="jpeg",
- scaling_method_e scaling_method=SCALING_NEAR
- )
- : backend_(backend),
- m_(map),
- m_req_(m_req),
- scale_factor_(scale_factor),
- t_(m_req.width(),m_req.height(),m_req.extent(),offset_x,offset_y),
- tolerance_(tolerance),
- image_format_(image_format),
- scaling_method_(scaling_method),
- painted_(false) {}
+ return simplify_distance_;
+ }
- void apply(double scale_denom=0.0)
- {
- mapnik::projection proj(m_.srs(),true);
- if (scale_denom <= 0.0)
- {
- scale_denom = mapnik::scale_denominator(m_req_.scale(),proj.is_geographic());
- }
- scale_denom *= scale_factor_;
- BOOST_FOREACH ( mapnik::layer const& lay, m_.layers() )
- {
- if (lay.visible(scale_denom))
- {
- apply_to_layer(lay,
- proj,
- m_req_.scale(),
- scale_denom,
- m_req_.width(),
- m_req_.height(),
- m_req_.extent(),
- m_req_.buffer_size());
- }
- }
- }
+ MAPNIK_VECTOR_INLINE void apply(double scale_denom=0.0);
- bool painted() const
- {
- return painted_;
- }
+ MAPNIK_VECTOR_INLINE bool painted() const;
- void apply_to_layer(mapnik::layer const& lay,
- mapnik::projection const& proj0,
- double scale,
- double scale_denom,
- unsigned width,
- unsigned height,
- box2d<double> const& extent,
- int buffer_size)
- {
- mapnik::datasource_ptr ds = lay.datasource();
- if (!ds)
- {
- return;
- }
- mapnik::projection proj1(lay.srs(),true);
- mapnik::proj_transform prj_trans(proj0,proj1);
- box2d<double> query_ext = extent; // unbuffered
- box2d<double> buffered_query_ext(query_ext); // buffered
- double buffer_padding = 2.0 * scale;
- boost::optional<int> layer_buffer_size = lay.buffer_size();
- if (layer_buffer_size) // if layer overrides buffer size, use this value to compute buffered extent
- {
- buffer_padding *= *layer_buffer_size;
- }
- else
- {
- buffer_padding *= buffer_size;
- }
- buffered_query_ext.width(query_ext.width() + buffer_padding);
- buffered_query_ext.height(query_ext.height() + buffer_padding);
- // clip buffered extent by maximum extent, if supplied
- boost::optional<box2d<double> > const& maximum_extent = m_.maximum_extent();
- if (maximum_extent)
- {
- buffered_query_ext.clip(*maximum_extent);
- }
- mapnik::box2d<double> layer_ext = lay.envelope();
- bool fw_success = false;
- bool early_return = false;
- // first, try intersection of map extent forward projected into layer srs
- if (prj_trans.forward(buffered_query_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext))
- {
- fw_success = true;
- layer_ext.clip(buffered_query_ext);
- }
- // if no intersection and projections are also equal, early return
- else if (prj_trans.equal())
- {
- early_return = true;
- }
- // next try intersection of layer extent back projected into map srs
- else if (prj_trans.backward(layer_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext))
- {
- layer_ext.clip(buffered_query_ext);
- // forward project layer extent back into native projection
- if (! prj_trans.forward(layer_ext, PROJ_ENVELOPE_POINTS))
- {
- std::cerr << "feature_style_processor: Layer=" << lay.name()
- << " extent=" << layer_ext << " in map projection "
- << " did not reproject properly back to layer projection";
- }
- }
- else
- {
- // if no intersection then nothing to do for layer
- early_return = true;
- }
- if (early_return)
- {
- return;
- }
+ MAPNIK_VECTOR_INLINE void apply_to_layer(mapnik::layer const& lay,
+ mapnik::projection const& proj0,
+ double scale,
+ double scale_denom,
+ unsigned width,
+ unsigned height,
+ box2d<double> const& extent,
+ int buffer_size);
- // if we've got this far, now prepare the unbuffered extent
- // which is used as a bbox for clipping geometries
- if (maximum_extent)
- {
- query_ext.clip(*maximum_extent);
- }
- mapnik::box2d<double> layer_ext2 = lay.envelope();
- if (fw_success)
- {
- if (prj_trans.forward(query_ext, PROJ_ENVELOPE_POINTS))
- {
- layer_ext2.clip(query_ext);
- }
- }
- else
- {
- if (prj_trans.backward(layer_ext2, PROJ_ENVELOPE_POINTS))
- {
- layer_ext2.clip(query_ext);
- prj_trans.forward(layer_ext2, PROJ_ENVELOPE_POINTS);
- }
- }
- double qw = query_ext.width()>0 ? query_ext.width() : 1;
- double qh = query_ext.height()>0 ? query_ext.height() : 1;
- mapnik::query::resolution_type res(width/qw,
- height/qh);
- mapnik::query q(layer_ext,res,scale_denom,extent);
- mapnik::layer_descriptor lay_desc = ds->get_descriptor();
- BOOST_FOREACH(mapnik::attribute_descriptor const& desc, lay_desc.get_descriptors())
- {
- q.add_property_name(desc.get_name());
- }
- mapnik::featureset_ptr features = ds->features(q);
- if (!features)
- {
- return;
- }
- mapnik::feature_ptr feature = features->next();
- if (feature) {
- backend_.start_tile_layer(lay.name());
- raster_ptr const& source = feature->get_raster();
- if (source)
- {
- box2d<double> target_ext = box2d<double>(source->ext_);
- prj_trans.backward(target_ext, PROJ_ENVELOPE_POINTS);
- box2d<double> ext = t_.forward(target_ext);
- int start_x = static_cast<int>(std::floor(ext.minx()+.5));
- int start_y = static_cast<int>(std::floor(ext.miny()+.5));
- int end_x = static_cast<int>(std::floor(ext.maxx()+.5));
- int end_y = static_cast<int>(std::floor(ext.maxy()+.5));
- int raster_width = end_x - start_x;
- int raster_height = end_y - start_y;
- if (raster_width > 0 && raster_height > 0)
- {
- #if MAPNIK_VERSION >= 300000
- raster target(target_ext, raster_width, raster_height, source->get_filter_factor());
- #else
- raster target(target_ext, raster_width, raster_height);
- #endif
- if (!source->premultiplied_alpha_)
- {
- agg::rendering_buffer buffer(source->data_.getBytes(),
- source->data_.width(),
- source->data_.height(),
- source->data_.width() * 4);
- agg::pixfmt_rgba32 pixf(buffer);
- pixf.premultiply();
- }
- if (!prj_trans.equal())
- {
- double offset_x = ext.minx() - start_x;
- double offset_y = ext.miny() - start_y;
- #if MAPNIK_VERSION >= 300000
- reproject_and_scale_raster(target, *source, prj_trans,
- offset_x, offset_y,
- width,
- scaling_method_);
- #else
- reproject_and_scale_raster(target, *source, prj_trans,
- offset_x, offset_y,
- width,
- 2.0,
- scaling_method_);
- #endif
- }
- else
- {
- double image_ratio_x = ext.width() / source->data_.width();
- double image_ratio_y = ext.height() / source->data_.height();
- #if MAPNIK_VERSION >= 300000
- scale_image_agg<image_data_32>(target.data_,
- source->data_,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source->get_filter_factor());
- #else
- scale_image_agg<image_data_32>(target.data_,
- source->data_,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- 2.0);
- #endif
- }
- mapnik::image_data_32 im_tile(width,height);
- composite(im_tile, target.data_,
- src_over, 1,
- start_x, start_y, false);
- agg::rendering_buffer buffer(im_tile.getBytes(),
- im_tile.width(),
- im_tile.height(),
- im_tile.width() * 4);
- agg::pixfmt_rgba32 pixf(buffer);
- pixf.demultiply();
- backend_.start_tile_feature(*feature);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- }
- backend_.stop_tile_layer();
- return;
- }
- // vector pathway
- while (feature)
- {
- boost::ptr_vector<mapnik::geometry_type> & paths = feature->paths();
- if (paths.empty()) {
- feature = features->next();
- continue;
- }
- backend_.start_tile_feature(*feature);
- BOOST_FOREACH( mapnik::geometry_type & geom, paths)
- {
- mapnik::box2d<double> geom_box = geom.envelope();
- if (!geom_box.intersects(buffered_query_ext))
- {
- continue;
- }
- if (handle_geometry(geom,
- prj_trans,
- buffered_query_ext) > 0)
- {
- painted_ = true;
- }
- }
- backend_.stop_tile_feature();
- feature = features->next();
- }
- backend_.stop_tile_layer();
- }
- }
+ MAPNIK_VECTOR_INLINE unsigned handle_geometry(mapnik::feature_impl const& feature,
+ mapnik::geometry::geometry<double> const& geom,
+ mapnik::proj_transform const& prj_trans,
+ mapnik::box2d<double> const& buffered_query_ext);
+};
- unsigned handle_geometry(mapnik::geometry_type & geom,
- mapnik::proj_transform const& prj_trans,
- mapnik::box2d<double> const& buffered_query_ext)
- {
- unsigned path_count = 0;
- switch (geom.type())
- {
- case MAPNIK_POINT:
- {
- if (geom.size() > 0)
- {
- typedef mapnik::coord_transform<mapnik::CoordTransform,
- mapnik::geometry_type> path_type;
- path_type path(t_, geom, prj_trans);
- path_count = backend_.add_path(path, tolerance_, geom.type());
- }
- break;
- }
- case MAPNIK_LINESTRING:
- {
- if (geom.size() > 1)
- {
- typedef agg::conv_clip_polyline<mapnik::geometry_type> line_clipper;
- line_clipper clipped(geom);
- clipped.clip_box(
- buffered_query_ext.minx(),
- buffered_query_ext.miny(),
- buffered_query_ext.maxx(),
- buffered_query_ext.maxy());
- typedef mapnik::coord_transform<mapnik::CoordTransform, line_clipper> path_type;
- path_type path(t_, clipped, prj_trans);
- path_count = backend_.add_path(path, tolerance_, geom.type());
- }
- break;
- }
- case MAPNIK_POLYGON:
- {
- if (geom.size() > 2)
- {
-#ifdef CONV_CLIPPER
- agg::path_storage ps;
- ps.move_to(buffered_query_ext.minx(), buffered_query_ext.miny());
- ps.line_to(buffered_query_ext.minx(), buffered_query_ext.maxy());
- ps.line_to(buffered_query_ext.maxx(), buffered_query_ext.maxy());
- ps.line_to(buffered_query_ext.maxx(), buffered_query_ext.miny());
- ps.close_polygon();
- typedef agg::conv_clipper<mapnik::geometry_type, agg::path_storage> poly_clipper;
- poly_clipper clipped(geom,ps,
- agg::clipper_and,
- agg::clipper_non_zero,
- agg::clipper_non_zero,
- 1);
- //clipped.rewind(0);
-#else
- typedef agg::conv_clip_polygon<mapnik::geometry_type> poly_clipper;
- poly_clipper clipped(geom);
- clipped.clip_box(
- buffered_query_ext.minx(),
- buffered_query_ext.miny(),
- buffered_query_ext.maxx(),
- buffered_query_ext.maxy());
-#endif
- typedef mapnik::coord_transform<mapnik::CoordTransform, poly_clipper> path_type;
- path_type path(t_, clipped, prj_trans);
- path_count = backend_.add_path(path, tolerance_, geom.type());
- }
- break;
- }
- case MAPNIK_UNKNOWN:
- default:
- {
- throw std::runtime_error("unhandled geometry type");
- break;
- }
- }
- return path_count;
- }
- };
+}} // end ns
- }} // end ns
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_processor.ipp"
+#endif
#endif // __MAPNIK_VECTOR_TILE_PROCESSOR_H__
diff --git a/src/vector_tile_processor.ipp b/src/vector_tile_processor.ipp
new file mode 100644
index 0000000..6d639a3
--- /dev/null
+++ b/src/vector_tile_processor.ipp
@@ -0,0 +1,1277 @@
+#include <mapnik/map.hpp>
+#include <mapnik/request.hpp>
+#include <mapnik/layer.hpp>
+#include <mapnik/query.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/vertex_adapters.hpp>
+#include <mapnik/feature.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/projection.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/proj_strategy.hpp>
+#include <mapnik/scale_denominator.hpp>
+#include <mapnik/attribute_descriptor.hpp>
+#include <mapnik/feature_layer_desc.hpp>
+#include <mapnik/config.hpp>
+#include <mapnik/box2d.hpp>
+#include <mapnik/version.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/raster.hpp>
+#include <mapnik/warp.hpp>
+#include <mapnik/version.hpp>
+#include <mapnik/image_scaling.hpp>
+#include <mapnik/image_compositing.hpp>
+#include <mapnik/view_transform.hpp>
+#include <mapnik/view_strategy.hpp>
+#include <mapnik/util/noncopyable.hpp>
+#include <mapnik/transform_path_adapter.hpp>
+#include <mapnik/geometry_is_empty.hpp>
+#include <mapnik/geometry_envelope.hpp>
+#include <mapnik/geometry_adapters.hpp>
+#include <mapnik/geometry_strategy.hpp>
+#include <mapnik/geometry_transform.hpp>
+
+// agg
+#include "agg_path_storage.h"
+
+// http://www.angusj.com/delphi/clipper.php
+#include "clipper.hpp"
+
+#include "agg_rendering_buffer.h"
+#include "agg_pixfmt_rgba.h"
+#include "agg_pixfmt_gray.h"
+#include "agg_renderer_base.h"
+
+#include <boost/optional.hpp>
+#include <boost/geometry/algorithms/simplify.hpp>
+#include <boost/geometry/algorithms/intersection.hpp>
+
+#include <iostream>
+#include <string>
+#include <stdexcept>
+
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+namespace mapnik { namespace vector_tile_impl {
+
+template <typename T>
+struct visitor_raster_processor
+{
+ typedef T backend_type;
+private:
+ mapnik::raster const& source_;
+ mapnik::feature_impl const& feature_;
+ box2d<double> const& target_ext_;
+ box2d<double> const& ext_;
+ backend_type & backend_;
+ bool & painted_;
+ mapnik::proj_transform const& prj_trans_;
+ std::string const& image_format_;
+ scaling_method_e scaling_method_;
+ unsigned width_;
+ unsigned height_;
+ unsigned raster_width_;
+ unsigned raster_height_;
+ int start_x_;
+ int start_y_;
+public:
+ visitor_raster_processor(mapnik::raster const& source,
+ mapnik::feature_impl const& feature,
+ box2d<double> const& target_ext,
+ box2d<double> const& ext,
+ backend_type & backend,
+ bool & painted,
+ mapnik::proj_transform const& prj_trans,
+ std::string const& image_format,
+ scaling_method_e scaling_method,
+ unsigned width,
+ unsigned height,
+ unsigned raster_width,
+ unsigned raster_height,
+ int start_x,
+ int start_y)
+ : source_(source),
+ feature_(feature),
+ target_ext_(target_ext),
+ ext_(ext),
+ backend_(backend),
+ painted_(painted),
+ prj_trans_(prj_trans),
+ image_format_(image_format),
+ scaling_method_(scaling_method),
+ width_(width),
+ height_(height),
+ raster_width_(raster_width),
+ raster_height_(raster_height),
+ start_x_(start_x),
+ start_y_(start_y) {}
+
+ void operator() (mapnik::image_rgba8 & source_data)
+ {
+ mapnik::image_rgba8 data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ mapnik::premultiply_alpha(source_data);
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_rgba8>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_rgba32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_rgba8, true, true);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray8 & source_data)
+ {
+ mapnik::image_gray8 data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray8>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray8;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray8);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray8s & source_data)
+ {
+ mapnik::image_gray8s data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray8s>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray8;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray8s);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray16 & source_data)
+ {
+ mapnik::image_gray16 data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray16>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray16;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray16);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray16s & source_data)
+ {
+ mapnik::image_gray16s data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray16s>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray16;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray16s);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray32 & source_data)
+ {
+ mapnik::image_gray32 data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray32>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray32);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray32s & source_data)
+ {
+ mapnik::image_gray32s data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray32s>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray32s);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray32f & source_data)
+ {
+ mapnik::image_gray32f data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray32f>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray32f);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray64 & source_data)
+ {
+ mapnik::image_gray64 data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray64>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray64);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray64s & source_data)
+ {
+ mapnik::image_gray64s data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray64s>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray64s);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (mapnik::image_gray64f & source_data)
+ {
+ mapnik::image_gray64f data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray64f>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray64f);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ backend_.start_tile_feature(feature_);
+ backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+ painted_ = true;
+ }
+
+ void operator() (image_null &) const
+ {
+ throw std::runtime_error("Null data passed to visitor");
+ }
+
+};
+
+/*
+ This processor combines concepts from mapnik's
+ feature_style_processor and agg_renderer. It
+ differs in that here we only process layers in
+ isolation of their styles, and because of this we need
+ options for clipping and simplification, for example,
+ that would normally come from a style's symbolizers
+*/
+
+template <typename T>
+processor<T>::processor(T & backend,
+ mapnik::Map const& map,
+ mapnik::request const& m_req,
+ double scale_factor,
+ unsigned offset_x,
+ unsigned offset_y,
+ double area_threshold,
+ std::string const& image_format,
+ scaling_method_e scaling_method)
+ : backend_(backend),
+ m_(map),
+ m_req_(m_req),
+ scale_factor_(scale_factor),
+ t_(m_req.width(),m_req.height(),m_req.extent(),offset_x,offset_y),
+ area_threshold_(area_threshold),
+ image_format_(image_format),
+ scaling_method_(scaling_method),
+ painted_(false),
+ simplify_distance_(0.0) {}
+
+template <typename T>
+void processor<T>::apply(double scale_denom)
+{
+ mapnik::projection proj(m_.srs(),true);
+ if (scale_denom <= 0.0)
+ {
+ scale_denom = mapnik::scale_denominator(m_req_.scale(),proj.is_geographic());
+ }
+ scale_denom *= scale_factor_;
+ for (mapnik::layer const& lay : m_.layers())
+ {
+ if (lay.visible(scale_denom))
+ {
+ apply_to_layer(lay,
+ proj,
+ m_req_.scale(),
+ scale_denom,
+ m_req_.width(),
+ m_req_.height(),
+ m_req_.extent(),
+ m_req_.buffer_size());
+ }
+ }
+}
+
+template <typename T>
+bool processor<T>::painted() const
+{
+ return painted_;
+}
+
+
+template <typename T>
+void processor<T>::apply_to_layer(mapnik::layer const& lay,
+ mapnik::projection const& proj0,
+ double scale,
+ double scale_denom,
+ unsigned width,
+ unsigned height,
+ box2d<double> const& extent,
+ int buffer_size)
+{
+ mapnik::datasource_ptr ds = lay.datasource();
+ if (!ds) return;
+
+ mapnik::projection proj1(lay.srs(),true);
+ mapnik::proj_transform prj_trans(proj0,proj1);
+ box2d<double> query_ext = extent; // unbuffered
+ box2d<double> buffered_query_ext(query_ext); // buffered
+ double buffer_padding = 2.0 * scale;
+ boost::optional<int> layer_buffer_size = lay.buffer_size();
+ if (layer_buffer_size) // if layer overrides buffer size, use this value to compute buffered extent
+ {
+ buffer_padding *= *layer_buffer_size;
+ }
+ else
+ {
+ buffer_padding *= buffer_size;
+ }
+ buffered_query_ext.width(query_ext.width() + buffer_padding);
+ buffered_query_ext.height(query_ext.height() + buffer_padding);
+ // clip buffered extent by maximum extent, if supplied
+ boost::optional<box2d<double> > const& maximum_extent = m_.maximum_extent();
+ if (maximum_extent)
+ {
+ buffered_query_ext.clip(*maximum_extent);
+ }
+ mapnik::box2d<double> layer_ext = lay.envelope();
+ bool fw_success = false;
+ bool early_return = false;
+ // first, try intersection of map extent forward projected into layer srs
+ if (prj_trans.forward(buffered_query_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext))
+ {
+ fw_success = true;
+ layer_ext.clip(buffered_query_ext);
+ }
+ // if no intersection and projections are also equal, early return
+ else if (prj_trans.equal())
+ {
+ early_return = true;
+ }
+ // next try intersection of layer extent back projected into map srs
+ else if (prj_trans.backward(layer_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext))
+ {
+ layer_ext.clip(buffered_query_ext);
+ // forward project layer extent back into native projection
+ if (! prj_trans.forward(layer_ext, PROJ_ENVELOPE_POINTS))
+ {
+ std::cerr << "feature_style_processor: Layer=" << lay.name()
+ << " extent=" << layer_ext << " in map projection "
+ << " did not reproject properly back to layer projection";
+ }
+ }
+ else
+ {
+ // if no intersection then nothing to do for layer
+ early_return = true;
+ }
+ if (early_return)
+ {
+ return;
+ }
+
+ // if we've got this far, now prepare the unbuffered extent
+ // which is used as a bbox for clipping geometries
+ if (maximum_extent)
+ {
+ query_ext.clip(*maximum_extent);
+ }
+ mapnik::box2d<double> layer_ext2 = lay.envelope();
+ if (fw_success)
+ {
+ if (prj_trans.forward(query_ext, PROJ_ENVELOPE_POINTS))
+ {
+ layer_ext2.clip(query_ext);
+ }
+ }
+ else
+ {
+ if (prj_trans.backward(layer_ext2, PROJ_ENVELOPE_POINTS))
+ {
+ layer_ext2.clip(query_ext);
+ prj_trans.forward(layer_ext2, PROJ_ENVELOPE_POINTS);
+ }
+ }
+ double qw = query_ext.width()>0 ? query_ext.width() : 1;
+ double qh = query_ext.height()>0 ? query_ext.height() : 1;
+ mapnik::query::resolution_type res(width/qw,
+ height/qh);
+ mapnik::query q(layer_ext,res,scale_denom,extent);
+ mapnik::layer_descriptor lay_desc = ds->get_descriptor();
+ for (mapnik::attribute_descriptor const& desc : lay_desc.get_descriptors())
+ {
+ q.add_property_name(desc.get_name());
+ }
+ mapnik::featureset_ptr features = ds->features(q);
+
+ if (!features) return;
+
+ mapnik::feature_ptr feature = features->next();
+ if (feature)
+ {
+ backend_.start_tile_layer(lay.name());
+ raster_ptr const& source = feature->get_raster();
+ if (source)
+ {
+ box2d<double> target_ext = box2d<double>(source->ext_);
+ prj_trans.backward(target_ext, PROJ_ENVELOPE_POINTS);
+ box2d<double> ext = t_.forward(target_ext);
+ int start_x = static_cast<int>(std::floor(ext.minx()+.5));
+ int start_y = static_cast<int>(std::floor(ext.miny()+.5));
+ int end_x = static_cast<int>(std::floor(ext.maxx()+.5));
+ int end_y = static_cast<int>(std::floor(ext.maxy()+.5));
+ int raster_width = end_x - start_x;
+ int raster_height = end_y - start_y;
+ if (raster_width > 0 && raster_height > 0)
+ {
+ visitor_raster_processor<T> visit(*source,
+ *feature,
+ target_ext,
+ ext,
+ backend_,
+ painted_,
+ prj_trans,
+ image_format_,
+ scaling_method_,
+ width,
+ height,
+ raster_width,
+ raster_height,
+ start_x,
+ start_y);
+ mapnik::util::apply_visitor(visit, source->data_);
+ }
+ backend_.stop_tile_layer();
+ return;
+ }
+ // vector pathway
+ while (feature)
+ {
+ mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
+ if (mapnik::geometry::is_empty(geom))
+ {
+ feature = features->next();
+ continue;
+ }
+ if (geom.is<mapnik::geometry::geometry_collection<double> >())
+ {
+ auto const& collection = mapnik::util::get<mapnik::geometry::geometry_collection<double> >(geom);
+ for (auto const& part : collection)
+ {
+ if (handle_geometry(*feature,
+ part,
+ prj_trans,
+ buffered_query_ext) > 0)
+ {
+ painted_ = true;
+ }
+ }
+ }
+ else
+ {
+ if (handle_geometry(*feature,
+ geom,
+ prj_trans,
+ buffered_query_ext) > 0)
+ {
+ painted_ = true;
+ }
+ }
+ feature = features->next();
+ }
+ backend_.stop_tile_layer();
+ }
+}
+
+inline void process_polynode_branch(ClipperLib::PolyNode* polynode,
+ mapnik::geometry::multi_polygon<std::int64_t> & mp,
+ double area_threshold)
+{
+ mapnik::geometry::polygon<std::int64_t> polygon;
+ polygon.set_exterior_ring(std::move(polynode->Contour));
+ if (polygon.exterior_ring.size() > 2) // Throw out invalid polygons
+ {
+ double outer_area = ClipperLib::Area(polygon.exterior_ring);
+ if (std::abs(outer_area) >= area_threshold)
+ {
+ // The view transform inverts the y axis so this should be positive still despite now
+ // being clockwise for the exterior ring. If it is not lets invert it.
+ if (outer_area > 0)
+ {
+ std::reverse(polygon.exterior_ring.begin(), polygon.exterior_ring.end());
+ }
+
+ // children of exterior ring are always interior rings
+ for (auto * ring : polynode->Childs)
+ {
+ if (ring->Contour.size() < 3) continue; // Throw out invalid holes
+ double inner_area = ClipperLib::Area(ring->Contour);
+ if (std::abs(inner_area) < area_threshold) continue;
+ if (inner_area < 0)
+ {
+ std::reverse(ring->Contour.begin(), ring->Contour.end());
+ }
+ polygon.add_hole(std::move(ring->Contour));
+ }
+ mp.emplace_back(std::move(polygon));
+ }
+ }
+ for (auto * ring : polynode->Childs)
+ {
+ for (auto * sub_ring : ring->Childs)
+ {
+ process_polynode_branch(sub_ring, mp, area_threshold);
+ }
+ }
+}
+
+template <typename T>
+struct encoder_visitor {
+ typedef T backend_type;
+ encoder_visitor(backend_type & backend,
+ mapnik::feature_impl const& feature,
+ mapnik::box2d<int> const& buffered_query_ext,
+ double area_threshold) :
+ backend_(backend),
+ feature_(feature),
+ buffered_query_ext_(buffered_query_ext),
+ area_threshold_(area_threshold) {}
+
+ unsigned operator() (mapnik::geometry::geometry_empty const& geom)
+ {
+ return 0;
+ }
+
+ unsigned operator() (mapnik::geometry::geometry_collection<std::int64_t> const& geom)
+ {
+ //throw std::runtime_error("geometry_collections not supported in encoder_visitor");
+ return 0;
+ }
+
+ unsigned operator() (mapnik::geometry::point<std::int64_t> const& geom)
+ {
+ unsigned path_count = 0;
+ if (buffered_query_ext_.intersects(geom.x,geom.y))
+ {
+ backend_.start_tile_feature(feature_);
+ backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POINT);
+ path_count = backend_.add_path(geom);
+ backend_.stop_tile_feature();
+ }
+ return path_count;
+ }
+
+ unsigned operator() (mapnik::geometry::multi_point<std::int64_t> const& geom)
+ {
+ unsigned path_count = 0;
+ bool first = true;
+ for (auto const& pt : geom)
+ {
+ if (buffered_query_ext_.intersects(pt.x,pt.y))
+ {
+ if (first)
+ {
+ first = false;
+ backend_.start_tile_feature(feature_);
+ backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POINT);
+ }
+ path_count += backend_.add_path(pt);
+ }
+ }
+ if (!first)
+ {
+ backend_.stop_tile_feature();
+ }
+ return path_count;
+ }
+
+ unsigned operator() (mapnik::geometry::line_string<std::int64_t> & geom)
+ {
+ unsigned path_count = 0;
+ if (geom.size() < 2) return 0;
+ std::deque<mapnik::geometry::line_string<int64_t>> result;
+ mapnik::geometry::linear_ring<std::int64_t> clip_box;
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.maxy());
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.maxy());
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ boost::geometry::intersection(clip_box,geom,result);
+ if (!result.empty())
+ {
+ backend_.start_tile_feature(feature_);
+ backend_.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
+ for (auto const& ls : result)
+ {
+ path_count += backend_.add_path(ls);
+ }
+ backend_.stop_tile_feature();
+ }
+ return path_count;
+ }
+
+ unsigned operator() (mapnik::geometry::multi_line_string<std::int64_t> & geom)
+ {
+ unsigned path_count = 0;
+ mapnik::geometry::linear_ring<std::int64_t> clip_box;
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.maxy());
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.maxy());
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ bool first = true;
+ for (auto const& line : geom)
+ {
+ if (line.size() < 2)
+ {
+ continue;
+ }
+ std::deque<mapnik::geometry::line_string<int64_t>> result;
+ boost::geometry::intersection(clip_box,line,result);
+ if (!result.empty())
+ {
+ if (first)
+ {
+ first = false;
+ backend_.start_tile_feature(feature_);
+ backend_.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
+ }
+ for (auto const& ls : result)
+ {
+ path_count += backend_.add_path(ls);
+ }
+ }
+ }
+ if (!first)
+ {
+ backend_.stop_tile_feature();
+ }
+ return path_count;
+ }
+
+ unsigned operator() (mapnik::geometry::polygon<std::int64_t> & geom)
+ {
+ unsigned path_count = 0;
+ if (geom.exterior_ring.size() < 3) return 0;
+ double clean_distance = 1.415;
+ mapnik::geometry::line_string<std::int64_t> clip_box;
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.maxy());
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.maxy());
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ ClipperLib::Clipper clipper;
+ ClipperLib::CleanPolygon(geom.exterior_ring, clean_distance);
+ double outer_area = ClipperLib::Area(geom.exterior_ring);
+ if (std::abs(outer_area) < area_threshold_)
+ {
+ return 0;
+ }
+ // The view transform inverts the y axis so this should be positive still despite now
+ // being clockwise for the exterior ring. If it is not lets invert it.
+ if (outer_area > 0)
+ {
+ std::reverse(geom.exterior_ring.begin(), geom.exterior_ring.end());
+ }
+ ClipperLib::Clipper poly_clipper;
+ //poly_clipper.StrictlySimple(true);
+ if (!poly_clipper.AddPath(geom.exterior_ring, ClipperLib::ptSubject, true))
+ {
+ return 0;
+ }
+ for (auto & ring : geom.interior_rings)
+ {
+ if (ring.size() < 3) continue;
+ ClipperLib::CleanPolygon(ring, clean_distance);
+ double inner_area = ClipperLib::Area(ring);
+ if (std::abs(inner_area) < area_threshold_) continue;
+ // This should be a negative area, the y axis is down, so the ring will be "CCW" rather
+ // then "CW" after the view transform, but if it is not lets reverse it
+ if (inner_area < 0)
+ {
+ std::reverse(ring.begin(), ring.end());
+ }
+ if (!poly_clipper.AddPath(ring, ClipperLib::ptSubject, true))
+ {
+ continue;
+ }
+ }
+ if (!poly_clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+ {
+ return 0;
+ }
+ mapnik::geometry::multi_line_string<std::int64_t> output_paths;
+ poly_clipper.Execute(ClipperLib::ctIntersection, output_paths, ClipperLib::pftNonZero);
+ poly_clipper.Clear();
+ ClipperLib::CleanPolygons(output_paths, clean_distance);
+ if (!clipper.AddPaths(output_paths, ClipperLib::ptSubject, true))
+ {
+ return 0;
+ }
+
+ ClipperLib::PolyTree polygons;
+ //clipper.StrictlySimple(true);
+ clipper.Execute(ClipperLib::ctUnion, polygons); //, ClipperLib::pftNonZero);
+ clipper.Clear();
+
+ mapnik::geometry::multi_polygon<std::int64_t> mp;
+
+ for (auto * polynode : polygons.Childs)
+ {
+ process_polynode_branch(polynode, mp, area_threshold_);
+ }
+
+ if (mp.empty())
+ {
+ return 0;
+ }
+
+ backend_.start_tile_feature(feature_);
+ backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POLYGON);
+
+ for (auto const& poly : mp)
+ {
+ path_count += backend_.add_path(poly);
+ }
+ backend_.stop_tile_feature();
+ return path_count;
+ }
+
+ unsigned operator() (mapnik::geometry::multi_polygon<std::int64_t> & geom)
+ {
+ unsigned path_count = 0;
+ //mapnik::box2d<std::int64_t> bbox = mapnik::geometry::envelope(geom);
+ if (geom.empty()) return 0;
+
+ double clean_distance = 1.415;
+ mapnik::geometry::line_string<std::int64_t> clip_box;
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.maxy());
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.maxy());
+ clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ ClipperLib::Clipper clipper;
+ for (auto & poly : geom)
+ {
+ if (poly.exterior_ring.size() < 3)
+ {
+ continue;
+ }
+ ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
+ double outer_area = ClipperLib::Area(poly.exterior_ring);
+ if (std::abs(outer_area) < area_threshold_) continue;
+ // The view transform inverts the y axis so this should be positive still despite now
+ // being clockwise for the exterior ring. If it is not lets invert it.
+ if (outer_area > 0)
+ {
+ std::reverse(poly.exterior_ring.begin(), poly.exterior_ring.end());
+ }
+ ClipperLib::Clipper poly_clipper;
+ //poly_clipper.StrictlySimple(true);
+ if (!poly_clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, true))
+ {
+ continue;
+ }
+ for (auto & ring : poly.interior_rings)
+ {
+ if (ring.size() < 3) continue;
+ ClipperLib::CleanPolygon(ring, clean_distance);
+ double inner_area = ClipperLib::Area(ring);
+ if (std::abs(inner_area) < area_threshold_) continue;
+ // This should be a negative area, the y axis is down, so the ring will be "CCW" rather
+ // then "CW" after the view transform, but if it is not lets reverse it
+ if (inner_area < 0)
+ {
+ std::reverse(ring.begin(), ring.end());
+ }
+ if (!poly_clipper.AddPath(ring, ClipperLib::ptSubject, true))
+ {
+ continue;
+ }
+ }
+ if (!poly_clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+ {
+ return 0;
+ }
+ mapnik::geometry::multi_line_string<std::int64_t> output_paths;
+ poly_clipper.Execute(ClipperLib::ctIntersection, output_paths);//, ClipperLib::pftNonZero);
+ poly_clipper.Clear();
+ if (output_paths.empty())
+ {
+ continue;
+ }
+ ClipperLib::CleanPolygons(output_paths, clean_distance);
+ clipper.AddPaths(output_paths, ClipperLib::ptSubject, true);
+ }
+
+ ClipperLib::PolyTree polygons;
+ //clipper.StrictlySimple(true);
+ clipper.Execute(ClipperLib::ctUnion, polygons); //, ClipperLib::pftNonZero);
+ clipper.Clear();
+
+ mapnik::geometry::multi_polygon<std::int64_t> mp;
+
+ for (auto * polynode : polygons.Childs)
+ {
+ process_polynode_branch(polynode, mp, area_threshold_);
+ }
+
+ if (mp.empty())
+ {
+ return 0;
+ }
+
+ backend_.start_tile_feature(feature_);
+ backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POLYGON);
+
+ for (auto const& poly : mp)
+ {
+ path_count += backend_.add_path(poly);
+ }
+ backend_.stop_tile_feature();
+ return path_count;
+ }
+
+ backend_type & backend_;
+ mapnik::feature_impl const& feature_;
+ mapnik::box2d<int> const& buffered_query_ext_;
+ double area_threshold_;
+};
+
+template <typename T>
+struct simplify_visitor {
+ typedef T backend_type;
+ simplify_visitor(double simplify_distance,
+ encoder_visitor<backend_type> & encoder) :
+ encoder_(encoder),
+ simplify_distance_(simplify_distance) {}
+
+ unsigned operator() (mapnik::geometry::point<std::int64_t> const& geom)
+ {
+ return encoder_(geom);
+ }
+
+ unsigned operator() (mapnik::geometry::multi_point<std::int64_t> const& geom)
+ {
+ return encoder_(geom);
+ }
+
+ unsigned operator() (mapnik::geometry::line_string<std::int64_t> const& geom)
+ {
+ mapnik::geometry::line_string<std::int64_t> simplified;
+ boost::geometry::simplify(geom,simplified,simplify_distance_);
+ return encoder_(simplified);
+ }
+
+ unsigned operator() (mapnik::geometry::multi_line_string<std::int64_t> const& geom)
+ {
+ mapnik::geometry::multi_line_string<std::int64_t> simplified;
+ boost::geometry::simplify(geom,simplified,simplify_distance_);
+ return encoder_(simplified);
+ }
+
+ unsigned operator() (mapnik::geometry::polygon<std::int64_t> const& geom)
+ {
+ mapnik::geometry::polygon<std::int64_t> simplified;
+ boost::geometry::simplify(geom,simplified,simplify_distance_);
+ return encoder_(simplified);
+ }
+
+ unsigned operator() (mapnik::geometry::multi_polygon<std::int64_t> const& geom)
+ {
+ mapnik::geometry::multi_polygon<std::int64_t> simplified;
+ boost::geometry::simplify(geom,simplified,simplify_distance_);
+ return encoder_(simplified);
+ }
+
+ unsigned operator() (mapnik::geometry::geometry_collection<std::int64_t> const& geom)
+ {
+ //throw std::runtime_error("geometry_collection not supported in simplify_visitor");
+ return 0;
+ }
+
+ unsigned operator() (mapnik::geometry::geometry_empty const& geom)
+ {
+ return 0;
+ }
+
+ encoder_visitor<backend_type> & encoder_;
+ unsigned simplify_distance_;
+};
+
+
+template <typename T>
+unsigned processor<T>::handle_geometry(mapnik::feature_impl const& feature,
+ mapnik::geometry::geometry<double> const& geom,
+ mapnik::proj_transform const& prj_trans,
+ mapnik::box2d<double> const& buffered_query_ext)
+{
+ mapnik::proj_backward_strategy proj_strat(prj_trans);
+ mapnik::view_strategy view_strat(t_);
+ mapnik::geometry::scale_strategy scale_strat(backend_.get_path_multiplier(), 0.5);
+ using sg_type = mapnik::geometry::strategy_group<mapnik::proj_backward_strategy,
+ mapnik::view_strategy,
+ mapnik::geometry::scale_strategy >;
+ sg_type sg(proj_strat, view_strat, scale_strat);
+ mapnik::geometry::point<double> p1_min(buffered_query_ext.minx(), buffered_query_ext.miny());
+ mapnik::geometry::point<double> p1_max(buffered_query_ext.maxx(), buffered_query_ext.maxy());
+ mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, sg);
+ mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, sg);
+ box2d<int> bbox(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
+ mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::geometry::transform<std::int64_t>(geom, sg);
+ encoder_visitor<T> encoder(backend_,feature,bbox, area_threshold_);
+ if (simplify_distance_ > 0)
+ {
+ simplify_visitor<T> simplifier(simplify_distance_,encoder);
+ return mapnik::util::apply_visitor(simplifier,new_geom);
+ }
+ else
+ {
+ return mapnik::util::apply_visitor(encoder,new_geom);
+ }
+}
+
+}} // end ns
diff --git a/src/vector_tile_projection.cpp b/src/vector_tile_projection.cpp
new file mode 100644
index 0000000..87bb291
--- /dev/null
+++ b/src/vector_tile_projection.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_projection.hpp"
+#include "vector_tile_projection.ipp"
diff --git a/src/vector_tile_projection.hpp b/src/vector_tile_projection.hpp
index 1dddb11..248411e 100644
--- a/src/vector_tile_projection.hpp
+++ b/src/vector_tile_projection.hpp
@@ -1,52 +1,35 @@
#ifndef __MAPNIK_VECTOR_TILE_PROJECTION_H__
#define __MAPNIK_VECTOR_TILE_PROJECTION_H__
-#include <mapnik/box2d.hpp>
-#include <mapnik/well_known_srs.hpp>
-#include <cmath>
+#include <cstdint>
-#ifndef M_PI
-#define M_PI 3.141592653589793238462643
-#endif
+#include "vector_tile_config.hpp"
-namespace mapnik { namespace vector {
+namespace mapnik { namespace vector_tile_impl {
class spherical_mercator
{
private:
double tile_size_;
public:
- spherical_mercator(unsigned tile_size)
- : tile_size_(static_cast<double>(tile_size)) {}
-
- void from_pixels(double shift, double & x, double & y)
- {
- double b = shift/2.0;
- x = (x - b)/(shift/360.0);
- double g = (y - b)/-(shift/(2 * M_PI));
- y = R2D * (2.0 * std::atan(std::exp(g)) - M_PI_by2);
- }
-
- void xyz(int x,
+ MAPNIK_VECTOR_INLINE spherical_mercator(unsigned tile_size);
+
+ MAPNIK_VECTOR_INLINE void from_pixels(double shift, double & x, double & y);
+
+ MAPNIK_VECTOR_INLINE void xyz(int x,
int y,
int z,
double & minx,
double & miny,
double & maxx,
- double & maxy)
- {
- minx = x * tile_size_;
- miny = (y + 1.0) * tile_size_;
- maxx = (x + 1.0) * tile_size_;
- maxy = y * tile_size_;
- double shift = std::pow(2.0,z) * tile_size_;
- from_pixels(shift,minx,miny);
- from_pixels(shift,maxx,maxy);
- lonlat2merc(&minx,&miny,1);
- lonlat2merc(&maxx,&maxy,1);
- }
+ double & maxy);
};
- }} // end ns
+}} // end ns
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_projection.ipp"
+#endif
+
#endif // __MAPNIK_VECTOR_TILE_PROJECTION_H__
diff --git a/src/vector_tile_projection.ipp b/src/vector_tile_projection.ipp
new file mode 100644
index 0000000..1620872
--- /dev/null
+++ b/src/vector_tile_projection.ipp
@@ -0,0 +1,41 @@
+#include <mapnik/box2d.hpp>
+#include <mapnik/well_known_srs.hpp>
+#include <cmath>
+
+#ifndef M_PI
+#define M_PI 3.141592653589793238462643
+#endif
+
+namespace mapnik { namespace vector_tile_impl {
+
+spherical_mercator::spherical_mercator(unsigned tile_size)
+ : tile_size_(static_cast<double>(tile_size)) {}
+
+void spherical_mercator::from_pixels(double shift, double & x, double & y)
+{
+ double b = shift/2.0;
+ x = (x - b)/(shift/360.0);
+ double g = (y - b)/-(shift/(2 * M_PI));
+ y = R2D * (2.0 * std::atan(std::exp(g)) - M_PI_by2);
+}
+
+void spherical_mercator::xyz(int x,
+ int y,
+ int z,
+ double & minx,
+ double & miny,
+ double & maxx,
+ double & maxy)
+{
+ minx = x * tile_size_;
+ miny = (y + 1.0) * tile_size_;
+ maxx = (x + 1.0) * tile_size_;
+ maxy = y * tile_size_;
+ double shift = std::pow(2.0,z) * tile_size_;
+ from_pixels(shift,minx,miny);
+ from_pixels(shift,maxx,maxy);
+ lonlat2merc(&minx,&miny,1);
+ lonlat2merc(&maxx,&maxy,1);
+}
+
+}} // end ns
\ No newline at end of file
diff --git a/src/vector_tile_util.cpp b/src/vector_tile_util.cpp
new file mode 100644
index 0000000..6b05777
--- /dev/null
+++ b/src/vector_tile_util.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_util.hpp"
+#include "vector_tile_util.ipp"
\ No newline at end of file
diff --git a/src/vector_tile_util.hpp b/src/vector_tile_util.hpp
index abc3581..800db92 100644
--- a/src/vector_tile_util.hpp
+++ b/src/vector_tile_util.hpp
@@ -1,213 +1,22 @@
#ifndef __MAPNIK_VECTOR_TILE_UTIL_H__
#define __MAPNIK_VECTOR_TILE_UTIL_H__
-#include "vector_tile.pb.h"
-#include <mapnik/vertex.hpp>
-#include <mapnik/box2d.hpp>
-#include <mapnik/geometry.hpp>
-#include <stdexcept>
#include <string>
-#ifdef CONV_CLIPPER
-#include "clipper.hpp"
-#endif
-
-namespace mapnik { namespace vector {
-
-#ifdef CONV_CLIPPER
- bool is_solid_clipper(mapnik::vector::tile const& tile, std::string & key)
- {
- ClipperLib::Clipper clipper;
-
- for (int i = 0; i < tile.layers_size(); i++)
- {
- mapnik::vector::tile_layer const& layer = tile.layers(i);
- unsigned extent = layer.extent();
- unsigned side = extent - 1;
- double extent_area = side * side;
-
- ClipperLib::Polygon clip_box;
- clip_box.push_back(ClipperLib::IntPoint(0, 0));
- clip_box.push_back(ClipperLib::IntPoint(side, 0));
- clip_box.push_back(ClipperLib::IntPoint(side, side));
- clip_box.push_back(ClipperLib::IntPoint(0, side));
- clip_box.push_back(ClipperLib::IntPoint(0, 0));
-
- for (int j = 0; j < layer.features_size(); j++)
- {
- mapnik::vector::tile_feature const& feature = layer.features(j);
-
- int cmd = -1;
- const int cmd_bits = 3;
- unsigned length = 0;
- int32_t x = 0, y = 0;
- int32_t start_x = 0, start_y = 0;
-
- ClipperLib::Polygons geometry;
- ClipperLib::Polygon polygon;
+#include "vector_tile_config.hpp"
- for (int k = 0; k < feature.geometry_size();)
- {
- if (!length) {
- unsigned cmd_length = feature.geometry(k++);
- cmd = cmd_length & ((1 << cmd_bits) - 1);
- length = cmd_length >> cmd_bits;
- }
+namespace vector_tile {
+ class Tile;
+}
- if (length > 0) {
- length--;
+namespace mapnik { namespace vector_tile_impl {
- if (cmd == mapnik::SEG_MOVETO || cmd == mapnik::SEG_LINETO)
- {
- int32_t dx = feature.geometry(k++);
- int32_t dy = feature.geometry(k++);
- x += ((dx >> 1) ^ (-(dx & 1)));
- y += ((dy >> 1) ^ (-(dy & 1)));
+ MAPNIK_VECTOR_INLINE bool is_solid_extent(vector_tile::Tile const& tile, std::string & key);
- // We can abort early if this feature has a vertex that is
- // inside the bbox.
- if ((x > 0 && x < static_cast<int>(side)) && (y > 0 && y < static_cast<int>(side))) {
- return false;
- }
+}}
- if (cmd == mapnik::SEG_MOVETO) {
- start_x = x;
- start_y = y;
- geometry.push_back(polygon);
- polygon.clear();
- }
-
- polygon.push_back(ClipperLib::IntPoint(x, y));
- }
- else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1)))
- {
- polygon.push_back(ClipperLib::IntPoint(start_x, start_y));
- geometry.push_back(polygon);
- polygon.clear();
- }
- else
- {
- std::stringstream msg;
- msg << "Unknown command type (is_solid_clipper): "
- << cmd;
- throw std::runtime_error(msg.str());
- }
- }
- }
-
- ClipperLib::Polygons solution;
-
- clipper.Clear();
- clipper.AddPolygons(geometry, ClipperLib::ptSubject);
- clipper.AddPolygon(clip_box, ClipperLib::ptClip);
- clipper.Execute(ClipperLib::ctIntersection, solution, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
-
- // If there are more than one result polygons, it can't be covered by
- // a single feature. Similarly, if there's no result, this geometry
- // is completely outside the bounding box.
- if (solution.size() != 1) {
- return false;
- }
-
- // Once we have only one clipped result polygon, we can compare the
- // areas and return early if they don't match.
- double area = ClipperLib::Area(solution.front());
- if (area != extent_area) {
- return false;
- }
- if (i == 0) {
- key = layer.name();
- } else if (i > 0) {
- key += std::string("-") + layer.name();
- }
- }
- }
- // It's either empty or doesn't have features that have vertices that are
- // not on the border of the bbox.
- return true;
- }
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_util.ipp"
#endif
- bool is_solid_extent(mapnik::vector::tile const& tile, std::string & key)
- {
- for (int i = 0; i < tile.layers_size(); i++)
- {
- mapnik::vector::tile_layer const& layer = tile.layers(i);
- unsigned extent = layer.extent();
- unsigned side = extent - 1;
- double extent_area = side * side;
- for (int j = 0; j < layer.features_size(); j++)
- {
- mapnik::vector::tile_feature const& feature = layer.features(j);
- int cmd = -1;
- const int cmd_bits = 3;
- unsigned length = 0;
- bool first = true;
- mapnik::box2d<double> box;
- int32_t x = 0, y = 0;
- for (int k = 0; k < feature.geometry_size();)
- {
- if (!length) {
- unsigned cmd_length = feature.geometry(k++);
- cmd = cmd_length & ((1 << cmd_bits) - 1);
- length = cmd_length >> cmd_bits;
- }
- if (length > 0) {
- length--;
- if (cmd == mapnik::SEG_MOVETO || cmd == mapnik::SEG_LINETO)
- {
- int32_t dx = feature.geometry(k++);
- int32_t dy = feature.geometry(k++);
- x += ((dx >> 1) ^ (-(dx & 1)));
- y += ((dy >> 1) ^ (-(dy & 1)));
- // We can abort early if this feature has a vertex that is
- // inside the bbox.
- if ((x > 0 && x < static_cast<int>(side)) && (y > 0 && y < static_cast<int>(side))) {
- return false;
- }
- if (first)
- {
- box.init(x,y,x,y);
- first = false;
- }
- else
- {
- box.expand_to_include(x,y);
- }
- }
- else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1)))
- {
- // pass
- }
- else
- {
- std::stringstream msg;
- msg << "Unknown command type (is_solid_extent): "
- << cmd;
- throw std::runtime_error(msg.str());
- }
- }
- }
- // Once we have only one clipped result polygon, we can compare the
- // areas and return early if they don't match.
- double geom_area = box.width() * box.height();
- if (geom_area < (extent_area - 32) )
- {
- return false;
- }
- if (i == 0) {
- key = layer.name();
- } else if (i > 0) {
- key += std::string("-") + layer.name();
- }
- }
- }
-
- // It's either empty or doesn't have features that have vertices that are
- // not on the border of the bbox.
- return true;
- }
-
- }} // end ns
-
#endif // __MAPNIK_VECTOR_TILE_UTIL_H__
diff --git a/src/vector_tile_util.ipp b/src/vector_tile_util.ipp
new file mode 100644
index 0000000..807659c
--- /dev/null
+++ b/src/vector_tile_util.ipp
@@ -0,0 +1,160 @@
+#include "vector_tile.pb.h"
+#include <mapnik/vertex.hpp>
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
+#include <stdexcept>
+#include <string>
+#include <sstream>
+
+namespace mapnik { namespace vector_tile_impl {
+
+ // ported from http://stackoverflow.com/a/1968345/2333354
+ bool line_intersects(int p0_x, int p0_y, int p1_x, int p1_y,
+ int p2_x, int p2_y, int p3_x, int p3_y)
+ {
+ float s1_x = p1_x - p0_x;
+ float s1_y = p1_y - p0_y;
+ float s2_x = p3_x - p2_x;
+ float s2_y = p3_y - p2_y;
+ float a = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y));
+ float b = (-s2_x * s1_y + s1_x * s2_y);
+ if (b == 0 ) return false;
+ float s = a / b;
+ float c = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x));
+ float d = (-s2_x * s1_y + s1_x * s2_y);
+ if (d == 0 ) return false;
+ float t = c / d;
+ if (s >= 0 && s <= 1 && t >= 0 && t <= 1) return true;
+ return false;
+ }
+
+ bool line_intersects_box(int p0_x, int p0_y, int p1_x, int p1_y, mapnik::box2d<int> box)
+ {
+ // possible early return for degenerate line
+ if (p0_x == p1_x && p0_y == p1_y) return false;
+
+ // check intersections with 4 sides of box
+ /*
+ 0,1 ------ 2,1
+ | |
+ | |
+ 0,3 ------ 2,3
+ */
+ // bottom side: 0,3,2,3
+ if (line_intersects(p0_x,p0_y,p1_x,p1_y,
+ box[0],box[3],box[2],box[3])) {
+ return true;
+ }
+ // right side: 2,3,2,1
+ else if (line_intersects(p0_x,p0_y,p1_x,p1_y,
+ box[2],box[3],box[2],box[1])) {
+ return true;
+ }
+ // top side: 0,1,2,1
+ else if (line_intersects(p0_x,p0_y,p1_x,p1_y,
+ box[0],box[1],box[2],box[1])) {
+ return true;
+ }
+ // left side: 0,1,0,3
+ else if (line_intersects(p0_x,p0_y,p1_x,p1_y,
+ box[0],box[1],box[0],box[3])) {
+ return true;
+ }
+ return false;
+ }
+
+ bool is_solid_extent(vector_tile::Tile const& tile, std::string & key)
+ {
+ for (int i = 0; i < tile.layers_size(); i++)
+ {
+ vector_tile::Tile_Layer const& layer = tile.layers(i);
+ unsigned extent = layer.extent();
+ unsigned side = extent - 1;
+ // TODO: need to account for buffer here
+ // NOTE: insetting box by 2 pixels is needed to account for
+ // rounding issues (at least on right and bottom)
+ mapnik::box2d<int> container(2, 2, extent-2, extent-2);
+ double extent_area = side * side;
+ for (int j = 0; j < layer.features_size(); j++)
+ {
+ vector_tile::Tile_Feature const& feature = layer.features(j);
+ int cmd = -1;
+ const int cmd_bits = 3;
+ unsigned length = 0;
+ bool first = true;
+ mapnik::box2d<int> box;
+ int32_t x1 = 0;
+ int32_t y1 = 0;
+ int32_t x0 = 0;
+ int32_t y0 = 0;
+ for (int k = 0; k < feature.geometry_size();)
+ {
+ if (!length) {
+ unsigned cmd_length = feature.geometry(k++);
+ cmd = cmd_length & ((1 << cmd_bits) - 1);
+ length = cmd_length >> cmd_bits;
+ }
+ if (length > 0) {
+ length--;
+ if (cmd == mapnik::SEG_MOVETO || cmd == mapnik::SEG_LINETO)
+ {
+ int32_t dx = feature.geometry(k++);
+ int32_t dy = feature.geometry(k++);
+ dx = ((dx >> 1) ^ (-(dx & 1)));
+ dy = ((dy >> 1) ^ (-(dy & 1)));
+ x1 += dx;
+ y1 += dy;
+ if ((x1 > 0 && x1 < static_cast<int>(side)) && (y1 > 0 && y1 < static_cast<int>(side))) {
+ // We can abort early if this feature has a vertex that is
+ // inside the bbox.
+ return false;
+ } else if (!first && line_intersects_box(x0,y0,x1,y1,container)) {
+ // or if the last line segment intersects with the expected bounding rectangle
+ return false;
+ }
+ x0 = x1;
+ y0 = y1;
+ if (first)
+ {
+ box.init(x1,y1,x1,y1);
+ first = false;
+ }
+ else
+ {
+ box.expand_to_include(x1,y1);
+ }
+ }
+ else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1)))
+ {
+ // pass
+ }
+ else
+ {
+ std::stringstream msg;
+ msg << "Unknown command type (is_solid_extent): "
+ << cmd;
+ throw std::runtime_error(msg.str());
+ }
+ }
+ }
+ // Once we have only one clipped result polygon, we can compare the
+ // areas and return early if they don't match.
+ int geom_area = box.width() * box.height();
+ if (geom_area < (extent_area - 32) )
+ {
+ return false;
+ }
+ if (i == 0) {
+ key = layer.name();
+ } else if (i > 0) {
+ key += std::string("-") + layer.name();
+ }
+ }
+ }
+
+ // It's either empty or doesn't have features that have vertices that are
+ // not on the border of the bbox.
+ return true;
+ }
+
+}} // end ns
\ No newline at end of file
diff --git a/test/data/256x256.png b/test/data/256x256.png
new file mode 100644
index 0000000..04469ac
Binary files /dev/null and b/test/data/256x256.png differ
diff --git a/test/data/natural_earth.tif b/test/data/natural_earth.tif
new file mode 100644
index 0000000..43500b0
Binary files /dev/null and b/test/data/natural_earth.tif differ
diff --git a/test/data/poly.geojson b/test/data/poly.geojson
new file mode 100644
index 0000000..73c1d5e
--- /dev/null
+++ b/test/data/poly.geojson
@@ -0,0 +1 @@
+{ "type": "MultiPolygon", "coordinates": [ [ [ [ 160.660729764719179, 11.102727405686379 ], [ 160.659111562675008, 11.10018834845026 ], [ 160.657515531432665, 11.096796477789358 ], [ 160.65753255704405, 11.095962241612344 ], [ 160.657375012797871, 11.09562687517214 ], [ 160.657257784441185, 11.093621141333658 ], [ 160.657464541452669, 11.092293712699473 ], [ 160.656872585485729, 11.091568738552658 ], [ 160.656485804363939, 11.091093324773974 ], [ 160.656439239325323, 11.089673932247242 ] [...]
\ No newline at end of file
diff --git a/test/raster_style.xml b/test/data/raster_style.xml
similarity index 100%
rename from test/raster_style.xml
rename to test/data/raster_style.xml
diff --git a/test/style.xml b/test/data/style.xml
similarity index 100%
rename from test/style.xml
rename to test/data/style.xml
diff --git a/test/encoding_util.hpp b/test/encoding_util.hpp
index ad69bda..cf10bde 100644
--- a/test/encoding_util.hpp
+++ b/test/encoding_util.hpp
@@ -1,86 +1,155 @@
#include <mapnik/vertex.hpp>
#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_adapters.hpp>
+#include <mapnik/vertex_processor.hpp>
+#include "vector_tile_geometry_decoder.hpp"
#include "vector_tile_geometry_encoder.hpp"
-void decode_geometry(mapnik::vector::tile_feature const& f,
- mapnik::geometry_type & geom,
- double & x,
- double & y,
- double scale)
+namespace {
+
+using namespace mapnik::geometry;
+
+struct print
+{
+ void operator() (geometry_empty const&) const
+ {
+ std::cerr << "EMPTY" << std::endl;
+ }
+ template <typename T>
+ void operator() (geometry_collection<T> const& collection) const
+ {
+ }
+ template <typename T>
+ void operator() (T const& geom) const
+ {
+ std::cerr << boost::geometry::wkt(geom) << std::endl;
+ }
+};
+
+}
+
+struct encode_geometry
{
- int cmd = -1;
- const int cmd_bits = 3;
- unsigned length = 0;
- for (int k = 0; k < f.geometry_size();)
+ vector_tile::Tile_Feature & feature_;
+ int32_t x_;
+ int32_t y_;
+ encode_geometry(vector_tile::Tile_Feature & feature) :
+ feature_(feature),
+ x_(0),
+ y_(0) { }
+
+ void operator() (geometry_empty const&)
+ {
+ }
+
+ template <typename T>
+ void operator()(T const& path)
+ {
+ mapnik::vector_tile_impl::encode_geometry(path,feature_,x_,y_);
+ }
+
+ void operator()(mapnik::geometry::multi_point<std::int64_t> const & path)
{
- if (!length) {
- unsigned cmd_length = f.geometry(k++);
- cmd = cmd_length & ((1 << cmd_bits) - 1);
- length = cmd_length >> cmd_bits;
+ for (auto const& pt : path)
+ {
+ mapnik::vector_tile_impl::encode_geometry(pt,feature_,x_,y_);
}
- if (length > 0) {
- length--;
- if (cmd == mapnik::SEG_MOVETO || cmd == mapnik::SEG_LINETO)
- {
- int32_t dx = f.geometry(k++);
- int32_t dy = f.geometry(k++);
- dx = ((dx >> 1) ^ (-(dx & 1)));
- dy = ((dy >> 1) ^ (-(dy & 1)));
- x += (static_cast<double>(dx) / scale);
- y += (static_cast<double>(dy) / scale);
- geom.push_vertex(x, y, static_cast<mapnik::CommandType>(cmd));
- }
- else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1)))
- {
- geom.push_vertex(0, 0, mapnik::SEG_CLOSE);
- }
- else
+ }
+
+ void operator()(mapnik::geometry::multi_line_string<std::int64_t> const & path)
+ {
+ for (auto const& ls : path)
+ {
+ mapnik::vector_tile_impl::encode_geometry(ls,feature_,x_,y_);
+ }
+ }
+
+ void operator()(mapnik::geometry::multi_polygon<std::int64_t> const& path)
+ {
+ for (auto const& p : path)
+ {
+ mapnik::vector_tile_impl::encode_geometry(p,feature_,x_,y_);
+ }
+ }
+
+ void operator()(mapnik::geometry::geometry_collection<std::int64_t> const& path)
+ {
+ for (auto const& p : path)
+ {
+ mapnik::util::apply_visitor((*this), p);
+ }
+ }
+};
+
+struct show_path
+{
+ std::string & str_;
+ show_path(std::string & out) :
+ str_(out) {}
+
+ template <typename T>
+ void operator()(T & path)
+ {
+ unsigned cmd = -1;
+ double x = 0;
+ double y = 0;
+ std::ostringstream s;
+ path.rewind(0);
+ while ((cmd = path.vertex(&x, &y)) != mapnik::SEG_END)
+ {
+ switch (cmd)
{
- std::stringstream msg;
- msg << "Unknown command type (decode_geometry): "
- << cmd;
- throw std::runtime_error(msg.str());
+ case mapnik::SEG_MOVETO: s << "move_to("; break;
+ case mapnik::SEG_LINETO: s << "line_to("; break;
+ case mapnik::SEG_CLOSE: s << "close_path("; break;
+ default: std::clog << "unhandled cmd " << cmd << "\n"; break;
}
+ s << x << "," << y << ")\n";
}
+ str_ += s.str();
}
-}
+};
template <typename T>
-std::string show_path(T & path)
+vector_tile::Tile_Feature geometry_to_feature(mapnik::geometry::geometry<T> const& g)
{
- unsigned cmd = -1;
- double x = 0;
- double y = 0;
- std::ostringstream s;
- path.rewind(0);
- while ((cmd = path.vertex(&x, &y)) != mapnik::SEG_END)
+ vector_tile::Tile_Feature feature;
+ encode_geometry ap(feature);
+ if (g.template is<mapnik::geometry::point<T> >() || g.template is<mapnik::geometry::multi_point<T> >())
{
- switch (cmd)
- {
- case mapnik::SEG_MOVETO: s << "move_to("; break;
- case mapnik::SEG_LINETO: s << "line_to("; break;
- case mapnik::SEG_CLOSE: s << "close_path("; break;
- default: std::clog << "unhandled cmd " << cmd << "\n"; break;
- }
- s << x << "," << y << ")\n";
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ }
+ else if (g.template is<mapnik::geometry::line_string<T> >() || g.template is<mapnik::geometry::multi_line_string<T> >())
+ {
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
}
- return s.str();
+ else if (g.template is<mapnik::geometry::polygon<T> >() || g.template is<mapnik::geometry::multi_polygon<T> >())
+ {
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ }
+ else
+ {
+ throw std::runtime_error("could not detect valid geometry type");
+ }
+ mapnik::util::apply_visitor(ap,g);
+ return feature;
+}
+
+template <typename T>
+std::string decode_to_path_string(mapnik::geometry::geometry<T> const& g)
+{
+ //mapnik::util::apply_visitor(print(), g2);
+ using decode_path_type = mapnik::geometry::vertex_processor<show_path>;
+ std::string out;
+ show_path sp(out);
+ mapnik::util::apply_visitor(decode_path_type(sp), g);
+ return out;
}
-std::string compare(mapnik::geometry_type const & g,
- unsigned tolerance=0,
- unsigned path_multiplier=1)
+template <typename T>
+std::string compare(mapnik::geometry::geometry<T> const& g)
{
- using namespace mapnik::vector;
- // encode
- tile_feature feature;
- int32_t x = 0;
- int32_t y = 0;
- encode_geometry(g,(tile_GeomType)g.type(),feature,x,y,tolerance,path_multiplier);
- // decode
- mapnik::geometry_type g2(MAPNIK_POLYGON);
- double x0 = 0;
- double y0 = 0;
- decode_geometry(feature,g2,x0,y0,path_multiplier);
- return show_path(g2);
+ vector_tile::Tile_Feature feature = geometry_to_feature(g);
+ auto g2 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ return decode_to_path_string(g2);
}
diff --git a/test/fixtures/expected-1.png b/test/fixtures/expected-1.png
new file mode 100644
index 0000000..ddd522d
Binary files /dev/null and b/test/fixtures/expected-1.png differ
diff --git a/test/fixtures/expected-2.jpeg b/test/fixtures/expected-2.jpeg
new file mode 100644
index 0000000..95b8c4e
Binary files /dev/null and b/test/fixtures/expected-2.jpeg differ
diff --git a/test/fixtures/expected-2.png b/test/fixtures/expected-2.png
new file mode 100644
index 0000000..72c4b0c
Binary files /dev/null and b/test/fixtures/expected-2.png differ
diff --git a/test/fixtures/expected-3.png b/test/fixtures/expected-3.png
new file mode 100644
index 0000000..5fe3168
Binary files /dev/null and b/test/fixtures/expected-3.png differ
diff --git a/test/geometry_encoding.cpp b/test/geometry_encoding.cpp
index 1533e49..31a8805 100644
--- a/test/geometry_encoding.cpp
+++ b/test/geometry_encoding.cpp
@@ -1,79 +1,480 @@
-// https://github.com/philsquared/Catch/wiki/Supplying-your-own-main()
-// http://www.levelofindirection.com/journal/2013/6/28/catch-10.html
-#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
-#include "mapnik3x_compatibility.hpp"
#include "encoding_util.hpp"
+#include <mapnik/geometry_is_valid.hpp>
+#include <mapnik/geometry_is_simple.hpp>
+#include <mapnik/geometry_correct.hpp>
+#include <mapnik/geometry_envelope.hpp>
+#include <mapnik/util/geometry_to_wkt.hpp>
-// https://github.com/mapbox/mapnik-vector-tile/issues/36
+//#include <mapnik/geometry_unique.hpp>
-TEST_CASE( "test 1", "should round trip without changes" ) {
- mapnik::geometry_type g(MAPNIK_POLYGON);
- g.move_to(0,0);
- g.line_to(1,1);
- g.line_to(100,100);
- g.close_path();
+/*
+
+low level encoding and decoding that skips clipping
+
+*/
+
+TEST_CASE( "point", "should round trip without changes" ) {
+ mapnik::geometry::point<std::int64_t> g(0,0);
+ std::string expected(
+ "move_to(0,0)\n"
+ );
+ CHECK(compare<std::int64_t>(g) == expected);
+}
+
+TEST_CASE( "multi_point", "should round trip without changes" ) {
+ mapnik::geometry::multi_point<std::int64_t> g;
+ g.add_coord(0,0);
+ g.add_coord(1,1);
+ g.add_coord(2,2);
+ std::string expected(
+ "move_to(0,0)\n"
+ "move_to(1,1)\n"
+ "move_to(2,2)\n"
+ );
+ CHECK(compare<std::int64_t>(g) == expected);
+}
+
+TEST_CASE( "line_string", "should round trip without changes" ) {
+ mapnik::geometry::line_string<std::int64_t> g;
+ g.add_coord(0,0);
+ g.add_coord(1,1);
+ g.add_coord(100,100);
+ std::string expected(
+ "move_to(0,0)\n"
+ "line_to(1,1)\n"
+ "line_to(100,100)\n"
+ );
+ CHECK(compare<std::int64_t>(g) == expected);
+}
+
+TEST_CASE( "multi_line_string", "should round trip without changes" ) {
+ mapnik::geometry::multi_line_string<std::int64_t> g;
+ {
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(0,0);
+ line.add_coord(1,1);
+ line.add_coord(100,100);
+ g.emplace_back(std::move(line));
+ }
+ {
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(-10,-10);
+ line.add_coord(-20,-20);
+ line.add_coord(-100,-100);
+ g.emplace_back(std::move(line));
+ }
+ std::string expected(
+ "move_to(0,0)\n"
+ "line_to(1,1)\n"
+ "line_to(100,100)\n"
+ "move_to(-10,-10)\n"
+ "line_to(-20,-20)\n"
+ "line_to(-100,-100)\n"
+ );
+ CHECK(compare<std::int64_t>(g) == expected);
+}
+
+/*TEST_CASE( "degenerate line_string", "should be culled" ) {
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(10,10);
+
+ std::string wkt0;
+ CHECK( mapnik::util::to_wkt(wkt0,line) );
+ // wkt writer copes with busted line_string
+ std::string expected_wkt0("LINESTRING(10 10)");
+ CHECK( wkt0 == expected_wkt0);
+
+ vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(line);
+ CHECK( feature.geometry_size() == 0 );
+ auto geom = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+}*/
+
+/*
+TEST_CASE( "multi_line_string with degenerate first part", "should be culled" ) {
+ mapnik::geometry::multi_line_string<std::int64_t> g;
+ mapnik::geometry::line_string<std::int64_t> l1;
+ l1.add_coord(0,0);
+ g.push_back(std::move(l1));
+ mapnik::geometry::line_string<std::int64_t> l2;
+ l2.add_coord(2,2);
+ l2.add_coord(3,3);
+ g.push_back(std::move(l2));
+
+ std::string wkt0;
+ CHECK( mapnik::util::to_wkt(wkt0,g) );
+ // wkt writer copes with busted line_string
+ std::string expected_wkt0("MULTILINESTRING((0 0),(2 2,3 3))");
+ CHECK( wkt0 == expected_wkt0);
+
+ vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(g);
+ CHECK( feature.geometry_size() == 6 );
+ auto geom = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+
+ wkt0.clear();
+ CHECK( mapnik::util::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(2 2,3 3)");
+ CHECK( geom.is<mapnik::geometry::line_string<std::int64_t> >() );
+}*/
+
+/*TEST_CASE( "multi_line_string with degenerate second part", "should be culled" ) {
+ mapnik::geometry::multi_line_string<std::int64_t> g;
+ {
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(0,0);
+ line.add_coord(1,1);
+ line.add_coord(100,100);
+ g.emplace_back(std::move(line));
+ }
+ {
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(-10,-10);
+ g.emplace_back(std::move(line));
+ }
+
+ std::string wkt0;
+ CHECK( mapnik::util::to_wkt(wkt0,g) );
+ // wkt writer copes with busted line_string
+ std::string expected_wkt0("MULTILINESTRING((0 0,1 1,100 100),(-10 -10))");
+ CHECK( wkt0 == expected_wkt0);
+
+ vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(g);
+ CHECK( feature.type() == vector_tile::Tile_GeomType_LINESTRING );
+ CHECK( feature.geometry_size() == 8 );
+ auto geom = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+
+ wkt0.clear();
+ CHECK( mapnik::util::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(0 0,1 1,100 100)");
+ CHECK( geom.is<mapnik::geometry::line_string<std::int64_t> >() );
+}
+*/
+TEST_CASE( "polygon", "should round trip without changes" ) {
+ mapnik::geometry::polygon<std::int64_t> g;
+ g.exterior_ring.add_coord(0,0);
+ g.exterior_ring.add_coord(1,1);
+ g.exterior_ring.add_coord(100,100);
+ g.exterior_ring.add_coord(0,0);
std::string expected(
"move_to(0,0)\n"
"line_to(1,1)\n"
"line_to(100,100)\n"
"close_path(0,0)\n"
);
- CHECK(compare(g) == expected);
+ CHECK(compare<std::int64_t>(g) == expected);
}
-TEST_CASE( "test 2", "should drop coincident line_to moves" ) {
- mapnik::geometry_type g(MAPNIK_LINESTRING);
- g.move_to(0,0);
- g.line_to(3,3);
- g.line_to(3,3);
- g.line_to(3,3);
- g.line_to(3,3);
- g.line_to(4,4);
+TEST_CASE( "polygon with degenerate exterior ring ", "should be culled" ) {
+ mapnik::geometry::polygon<std::int64_t> p0;
+ // invalid exterior ring
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+
+ std::string wkt0;
+ CHECK( mapnik::util::to_wkt(wkt0,p0) );
+ // wkt writer copes with busted polygon
+ std::string expected_wkt0("POLYGON((0 0,0 10))");
+ CHECK( wkt0 == expected_wkt0);
+
+ vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
+ // since first ring is degenerate the whole polygon should be culled
+ auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ CHECK( p1.is<mapnik::geometry::geometry_empty>() );
+}
+
+/*TEST_CASE( "polygon with degenerate exterior ring will drop valid interior ring", "should be culled" ) {
+ mapnik::geometry::polygon<std::int64_t> p0;
+ // invalid exterior ring
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ // valid interior ring
+ mapnik::geometry::linear_ring<std::int64_t> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-3,7);
+ hole.add_coord(-3,3);
+ hole.add_coord(-7,3);
+ hole.add_coord(-7,7);
+ p0.add_hole(std::move(hole));
+
+ std::string wkt0;
+ CHECK( mapnik::util::to_wkt(wkt0,p0) );
+ // wkt writer copes with busted polygon
+ std::string expected_wkt0("POLYGON((0 0,0 10),(-7 7,-3 7,-3 3,-7 3,-7 7))");
+ CHECK( wkt0 == expected_wkt0);
+
+ vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
+ // since first ring is degenerate the whole polygon should be culled
+ auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ CHECK( p1.is<mapnik::geometry::geometry_empty>() );
+}*/
+
+TEST_CASE( "polygon with valid exterior ring but degenerate interior ring", "should be culled" ) {
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ // invalid interior ring
+ mapnik::geometry::linear_ring<std::int64_t> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-3,7);
+ p0.add_hole(std::move(hole));
+
+ std::string wkt0;
+ CHECK( mapnik::util::to_wkt(wkt0,p0) );
+ // wkt writer copes with busted polygon
+ std::string expected_wkt0("POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7))");
+ CHECK( wkt0 == expected_wkt0);
+
+ vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
+ auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ CHECK( p1.is<mapnik::geometry::polygon<double> >() );
+ auto const& poly = mapnik::util::get<mapnik::geometry::polygon<double> >(p1);
+ // since interior ring is degenerate it should have been culled when decoded
+ auto const& holes = poly.interior_rings;
+ CHECK( holes.empty() == true );
+}
+
+TEST_CASE( "polygon with valid exterior ring but one degenerate interior ring of two", "should be culled" ) {
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ // invalid interior ring
+ {
+ mapnik::geometry::linear_ring<std::int64_t> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-3,7);
+ p0.add_hole(std::move(hole));
+ }
+ // valid interior ring
+ {
+ mapnik::geometry::linear_ring<std::int64_t> hole_in_hole;
+ hole_in_hole.add_coord(-6,4);
+ hole_in_hole.add_coord(-6,6);
+ hole_in_hole.add_coord(-4,6);
+ hole_in_hole.add_coord(-4,4);
+ hole_in_hole.add_coord(-6,4);
+ p0.add_hole(std::move(hole_in_hole));
+ }
+
+ std::string wkt0;
+ CHECK( mapnik::util::to_wkt(wkt0,p0) );
+ // wkt writer copes with busted polygon
+ std::string expected_wkt0("POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7),(-6 4,-6 6,-4 6,-4 4,-6 4))");
+ CHECK( wkt0 == expected_wkt0);
+
+ vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
+ auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ CHECK( p1.is<mapnik::geometry::polygon<double> >() );
+ auto const& poly = mapnik::util::get<mapnik::geometry::polygon<double> >(p1);
+ // since first interior ring is degenerate it should have been culled when decoded
+ auto const& holes = poly.interior_rings;
+ // the second one is kept: somewhat dubious since it is actually a hole in a hole
+ // but this is probably the best we can do
+ CHECK( holes.size() == 1 );
+}
+
+TEST_CASE( "(multi)polygon with hole", "should round trip without changes" ) {
+ // NOTE: this polygon should have correct winding order:
+ // CCW for exterior, CW for interior
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ mapnik::geometry::linear_ring<std::int64_t> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-3,7);
+ hole.add_coord(-3,3);
+ hole.add_coord(-7,3);
+ hole.add_coord(-7,7);
+ p0.add_hole(std::move(hole));
+
+ mapnik::box2d<double> extent = mapnik::geometry::envelope(p0);
+
+ std::string wkt0;
+ CHECK( mapnik::util::to_wkt(wkt0,p0) );
+ std::string expected_wkt0("POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7))");
+ CHECK( wkt0 == expected_wkt0);
+ // ensure correcting geometry has no effect
+ // as a way of confirming the original was correct
+ mapnik::geometry::correct(p0);
+ wkt0.clear();
+ CHECK( mapnik::util::to_wkt(wkt0,p0) );
+ CHECK( wkt0 == expected_wkt0);
+
+ vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
+ auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ CHECK( p1.is<mapnik::geometry::polygon<double> >() );
+ CHECK( extent == mapnik::geometry::envelope(p1) );
+
+ wkt0.clear();
+ CHECK( mapnik::util::to_wkt(wkt0,p1) );
+ CHECK( wkt0 == expected_wkt0);
+
+ // now test back compatibility mode where we decode all rings into exterior rings
+ // for polygons rings that were encoded correctly in vtiles (CCW exterior, CW interior)
+ // then this should be unneeded, but for rings with incorrect order then this style of
+ // decoding should allow them still to be queried correctly using the current mapnik hit_test algos
+ auto _p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0,true);
+ wkt0.clear();
+ CHECK( mapnik::util::to_wkt(wkt0,_p1) );
+ CHECK( _p1.is<mapnik::geometry::multi_polygon<double> >() );
+ std::string expected_wkt2("MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0)),((-7 7,-7 3,-3 3,-3 7,-7 7)))");
+ CHECK( wkt0 == expected_wkt2 );
+ mapnik::geometry::correct(_p1);
+ wkt0.clear();
+ CHECK( mapnik::util::to_wkt(wkt0,_p1) );
+ CHECK( wkt0 == expected_wkt2 );
+
+ std::string expected_p0(
+ "move_to(0,0)\n"
+ "line_to(0,10)\n"
+ "line_to(-10,10)\n"
+ "line_to(-10,0)\n"
+ "close_path(0,0)\n"
+ "move_to(-7,7)\n"
+ "line_to(-3,7)\n"
+ "line_to(-3,3)\n"
+ "line_to(-7,3)\n"
+ "close_path(0,0)\n"
+ );
+
+ CHECK(decode_to_path_string(p1) == expected_p0);
+
+ std::string expected_p1(
+ "move_to(0,0)\n"
+ "line_to(0,10)\n"
+ "line_to(-10,10)\n"
+ "line_to(-10,0)\n"
+ "close_path(0,0)\n"
+ "move_to(-7,7)\n"
+ "line_to(-7,3)\n"
+ "line_to(-3,3)\n"
+ "line_to(-3,7)\n"
+ "close_path(0,0)\n"
+ );
+
+ CHECK(decode_to_path_string(_p1) == expected_p1);
+
+ // make into multi_polygon
+ mapnik::geometry::multi_polygon<std::int64_t> multi_poly;
+ multi_poly.push_back(std::move(p0));
+ mapnik::geometry::polygon<std::int64_t> p2;
+ p2.exterior_ring.add_coord(-6,4);
+ p2.exterior_ring.add_coord(-4,4);
+ p2.exterior_ring.add_coord(-4,6);
+ p2.exterior_ring.add_coord(-6,6);
+ p2.exterior_ring.add_coord(-6,4);
+ multi_poly.push_back(std::move(p2));
+
+ mapnik::box2d<double> multi_extent = mapnik::geometry::envelope(multi_poly);
+
+ vector_tile::Tile_Feature feature1 = geometry_to_feature<std::int64_t>(multi_poly);
+ auto mp = mapnik::vector_tile_impl::decode_geometry(feature1,0.0,0.0,1.0,1.0);
+ CHECK( mp.is<mapnik::geometry::multi_polygon<double> >() );
+
+ CHECK( multi_extent == mapnik::geometry::envelope(mp) );
+
+ wkt0.clear();
+ CHECK( mapnik::util::to_wkt(wkt0,mp) );
+ std::string expected_wkt3("MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)),((-6 4,-4 4,-4 6,-6 6,-6 4)))");
+ CHECK( wkt0 == expected_wkt3);
+ // ensure correcting geometry has no effect
+ // as a way of confirming the original was correct
+ mapnik::geometry::correct(mp);
+ wkt0.clear();
+ CHECK( mapnik::util::to_wkt(wkt0,mp) );
+ CHECK( wkt0 == expected_wkt3);
+
+ std::string expected_multi = expected_p0 += std::string(
+ "move_to(-6,4)\n"
+ "line_to(-4,4)\n"
+ "line_to(-4,6)\n"
+ "line_to(-6,6)\n"
+ "close_path(0,0)\n"
+ );
+
+ CHECK(decode_to_path_string(mp) == expected_multi);
+ CHECK(mapnik::geometry::is_valid(mp));
+ CHECK(mapnik::geometry::is_simple(mp));
+
+}
+
+// We no longer drop coincidental points in the encoder it should be
+// done prior to reaching encoder.
+/*TEST_CASE( "test 2", "should drop coincident line_to commands" ) {
+ mapnik::geometry::line_string<std::int64_t> g;
+ g.add_coord(0,0);
+ g.add_coord(3,3);
+ g.add_coord(3,3);
+ g.add_coord(3,3);
+ g.add_coord(3,3);
+ g.add_coord(4,4);
std::string expected(
"move_to(0,0)\n"
"line_to(3,3)\n"
"line_to(4,4)\n"
);
- CHECK(compare(g,1) == expected);
-}
+ CHECK( compare<std::int64_t>(g) == expected);
+}*/
+/*
TEST_CASE( "test 2b", "should drop vertices" ) {
- mapnik::geometry_type g(MAPNIK_LINESTRING);
- g.move_to(0,0);
- g.line_to(0,0);
- g.line_to(1,1);
+ mapnik::geometry::line_string<std::int64_t> g;
+ g.add_coord(0,0);
+ g.add_coord(0,0);
+ g.add_coord(1,1);
std::string expected(
"move_to(0,0)\n"
"line_to(0,0)\n" // TODO - should we try to drop this?
"line_to(1,1)\n"
);
- CHECK(compare(g,1) == expected);
-}
+ CHECK(compare<std::int64_t>(g) == expected);
+}*/
TEST_CASE( "test 3", "should not drop first move_to or last vertex in line" ) {
- mapnik::geometry_type g(MAPNIK_LINESTRING);
- g.move_to(0,0);
- g.line_to(1,1);
- g.move_to(0,0);
- g.line_to(1,1);
+ mapnik::geometry::multi_line_string<std::int64_t> g;
+ mapnik::geometry::line_string<std::int64_t> l1;
+ l1.add_coord(0,0);
+ l1.add_coord(1,1);
+ g.push_back(std::move(l1));
+ mapnik::geometry::line_string<std::int64_t> l2;
+ l2.add_coord(2,2);
+ l2.add_coord(3,3);
+ g.push_back(std::move(l2));
+
std::string expected(
"move_to(0,0)\n"
"line_to(1,1)\n"
- "move_to(0,0)\n"
- "line_to(1,1)\n"
+ "move_to(2,2)\n"
+ "line_to(3,3)\n"
);
- CHECK(compare(g,1000) == expected);
+ CHECK(compare<std::int64_t>(g) == expected);
}
+/*
+
TEST_CASE( "test 4", "should not drop first move_to or last vertex in polygon" ) {
- mapnik::geometry_type g(MAPNIK_POLYGON);
- g.move_to(0,0);
- g.line_to(1,1);
- g.move_to(0,0);
- g.line_to(1,1);
- g.close_path();
+ mapnik::geometry::multi_polygon g;
+ mapnik::geometry::polygon p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(1,0);
+ g.push_back(std::move(p0));
+
+ mapnik::geometry::polygon p1;
+ p1.exterior_ring.add_coord(1,1);
+ p1.exterior_ring.add_coord(0,1);
+ p1.exterior_ring.add_coord(1,1);
+ g.push_back(std::move(p1));
+
std::string expected(
"move_to(0,0)\n"
"line_to(1,1)\n"
@@ -84,12 +485,16 @@ TEST_CASE( "test 4", "should not drop first move_to or last vertex in polygon" )
CHECK(compare(g,1000) == expected);
}
+
+
TEST_CASE( "test 5", "can drop duplicate move_to" ) {
- mapnik::geometry_type g(MAPNIK_LINESTRING);
+
+ mapnik::geometry::path p(mapnik::path::LineString);
g.move_to(0,0);
g.move_to(1,1); // skipped
g.line_to(4,4); // skipped
g.line_to(5,5);
+
std::string expected(
"move_to(0,0)\n" // TODO - should we keep move_to(1,1) instead?
"line_to(5,5)\n"
@@ -97,8 +502,9 @@ TEST_CASE( "test 5", "can drop duplicate move_to" ) {
CHECK(compare(g,2) == expected);
}
+
TEST_CASE( "test 5b", "can drop duplicate move_to" ) {
- mapnik::geometry_type g(MAPNIK_LINESTRING);
+ mapnik::geometry::line_string g;
g.move_to(0,0);
g.move_to(1,1);
g.line_to(2,2);
@@ -110,7 +516,7 @@ TEST_CASE( "test 5b", "can drop duplicate move_to" ) {
}
TEST_CASE( "test 5c", "can drop duplicate move_to but not second" ) {
- mapnik::geometry_type g(MAPNIK_LINESTRING);
+ mapnik::geometry::line_string g;
g.move_to(0,0);
g.move_to(1,1);
g.line_to(2,2);
@@ -126,7 +532,7 @@ TEST_CASE( "test 5c", "can drop duplicate move_to but not second" ) {
}
TEST_CASE( "test 6", "should not drop last line_to if repeated" ) {
- mapnik::geometry_type g(MAPNIK_LINESTRING);
+ mapnik::geometry::line_string g;
g.move_to(0,0);
g.line_to(2,2);
g.line_to(1000,1000); // skipped
@@ -141,7 +547,7 @@ TEST_CASE( "test 6", "should not drop last line_to if repeated" ) {
}
TEST_CASE( "test 7", "ensure proper handling of skipping + close commands" ) {
- mapnik::geometry_type g(MAPNIK_POLYGON);
+ mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
g.move_to(0,0);
g.line_to(2,2);
g.close_path();
@@ -161,7 +567,7 @@ TEST_CASE( "test 7", "ensure proper handling of skipping + close commands" ) {
}
TEST_CASE( "test 8", "should drop repeated close commands" ) {
- mapnik::geometry_type g(MAPNIK_POLYGON);
+ mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
g.move_to(0,0);
g.line_to(2,2);
g.close_path();
@@ -176,7 +582,7 @@ TEST_CASE( "test 8", "should drop repeated close commands" ) {
}
TEST_CASE( "test 9a", "should not drop last vertex" ) {
- mapnik::geometry_type g(MAPNIK_LINESTRING);
+ mapnik::geometry::line_string g;
g.move_to(0,0);
g.line_to(9,0); // skipped
g.line_to(0,10);
@@ -188,7 +594,7 @@ TEST_CASE( "test 9a", "should not drop last vertex" ) {
}
TEST_CASE( "test 9b", "should not drop last vertex" ) {
- mapnik::geometry_type g(MAPNIK_POLYGON);
+ mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
g.move_to(0,0);
g.line_to(10,0); // skipped
g.line_to(0,10);
@@ -202,7 +608,7 @@ TEST_CASE( "test 9b", "should not drop last vertex" ) {
}
TEST_CASE( "test 9c", "should not drop last vertex" ) {
- mapnik::geometry_type g(MAPNIK_POLYGON);
+ mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
g.move_to(0,0);
g.line_to(0,10);
g.close_path();
@@ -215,7 +621,7 @@ TEST_CASE( "test 9c", "should not drop last vertex" ) {
}
TEST_CASE( "test 10", "should skip repeated close and coincident line_to commands" ) {
- mapnik::geometry_type g(MAPNIK_POLYGON);
+ mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
g.move_to(0,0);
g.line_to(10,10);
g.line_to(10,10); // skipped
@@ -244,33 +650,36 @@ TEST_CASE( "test 10", "should skip repeated close and coincident line_to command
}
TEST_CASE( "test 11", "should correctly encode multiple paths" ) {
- using namespace mapnik::vector;
- tile_feature feature0;
+ using namespace mapnik::vector_tile_impl;
+ vector_tile::Tile_Feature feature0;
int32_t x = 0;
int32_t y = 0;
unsigned path_multiplier = 1;
unsigned tolerance = 10000;
- mapnik::geometry_type g0(MAPNIK_POLYGON);
+ mapnik::geometry_type g0(mapnik::geometry_type::types::Polygon);
g0.move_to(0,0);
g0.line_to(-10,-10);
g0.line_to(-20,-20);
g0.close_path();
- encode_geometry(g0,(tile_GeomType)g0.type(),feature0,x,y,tolerance,path_multiplier);
+ mapnik::vertex_adapter va(g0);
+ encode_geometry(va,(vector_tile::Tile_GeomType)g0.type(),feature0,x,y,tolerance,path_multiplier);
CHECK(x == -20);
CHECK(y == -20);
- mapnik::geometry_type g1(MAPNIK_POLYGON);
+ mapnik::geometry_type g1(mapnik::geometry_type::types::Polygon);
g1.move_to(1000,1000);
g1.line_to(1010,1010);
g1.line_to(1020,1020);
g1.close_path();
- encode_geometry(g1,(tile_GeomType)g1.type(),feature0,x,y,tolerance,path_multiplier);
+ mapnik::vertex_adapter va1(g1);
+ encode_geometry(va1,(vector_tile::Tile_GeomType)g1.type(),feature0,x,y,tolerance,path_multiplier);
CHECK(x == 1020);
CHECK(y == 1020);
- mapnik::geometry_type g2(MAPNIK_POLYGON);
+ mapnik::geometry_type g2(mapnik::geometry_type::types::Polygon);
double x0 = 0;
double y0 = 0;
decode_geometry(feature0,g2,x0,y0,path_multiplier);
- std::string actual = show_path(g2);
+ mapnik::vertex_adapter va2(g2);
+ std::string actual = show_path(va2);
std::string expected(
"move_to(0,0)\n"
"line_to(-20,-20)\n"
@@ -283,13 +692,13 @@ TEST_CASE( "test 11", "should correctly encode multiple paths" ) {
}
TEST_CASE( "test 12", "should correctly encode multiple paths" ) {
- using namespace mapnik::vector;
- tile_feature feature0;
+ using namespace mapnik::vector_tile_impl;
+ vector_tile::Tile_Feature feature0;
int32_t x = 0;
int32_t y = 0;
unsigned path_multiplier = 1;
unsigned tolerance = 10;
- mapnik::geometry_type g(MAPNIK_POLYGON);
+ mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
g.move_to(0,0);
g.line_to(100,0);
g.line_to(100,100);
@@ -304,13 +713,15 @@ TEST_CASE( "test 12", "should correctly encode multiple paths" ) {
g.line_to(25,20);
g.line_to(20,20);
g.close_path();
- encode_geometry(g,(tile_GeomType)g.type(),feature0,x,y,tolerance,path_multiplier);
+ mapnik::vertex_adapter va(g);
+ encode_geometry(va,(vector_tile::Tile_GeomType)g.type(),feature0,x,y,tolerance,path_multiplier);
- mapnik::geometry_type g2(MAPNIK_POLYGON);
+ mapnik::geometry_type g2(mapnik::geometry_type::types::Polygon);
double x0 = 0;
double y0 = 0;
decode_geometry(feature0,g2,x0,y0,path_multiplier);
- std::string actual = show_path(g2);
+ mapnik::vertex_adapter va2(g2);
+ std::string actual = show_path(va2);
std::string expected(
"move_to(0,0)\n"
"line_to(100,0)\n"
@@ -327,12 +738,4 @@ TEST_CASE( "test 12", "should correctly encode multiple paths" ) {
);
CHECK(actual == expected);
}
-
-int main (int argc, char* const argv[])
-{
- GOOGLE_PROTOBUF_VERIFY_VERSION;
- int result = Catch::Session().run( argc, argv );
- if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n");
- google::protobuf::ShutdownProtobufLibrary();
- return result;
-}
+*/
diff --git a/test/python/test.py b/test/python/test.py
deleted file mode 100644
index f08ba0d..0000000
--- a/test/python/test.py
+++ /dev/null
@@ -1,163 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import sys
-import unittest
-import json
-# put local python module dir on path
-sys.path.append("./python")
-
-import renderer
-
-class TestRendererParts(unittest.TestCase):
- def test_lonlat2merc(self):
- # projection transform
- # should roughtly match: echo -180 -85 | cs2cs -f "%.10f" +init=epsg:4326 +to +init=epsg:3857
- x,y = renderer.lonlat2merc(-180,-85)
- self.assertAlmostEqual(-20037508.342789244,x)
- self.assertAlmostEqual(-19971868.8804085888,y)
-
- def test_box2d(self):
- box = renderer.Box2d(-180,-85,180,85)
- assert box.minx == -180
- assert box.miny == -85
- assert box.maxx == 180
- assert box.maxy == 85
- assert box.intersects(0,0)
- assert not box.intersects(-180,-90)
- self.assertAlmostEqual(box.bounds(),[box.minx,box.miny,box.maxx,box.maxy])
-
- def test_spherical_mercator(self):
- merc = renderer.SphericalMercator()
- z0_extent = merc.bbox(0,0,0)
-
- def test_request(self):
- req = renderer.Request(0,0,0)
- self.assertAlmostEqual(req.get_width(),40075016.68557849)
- self.assertAlmostEqual(req.get_height(),40075016.68557849)
-
- def test_ctrans(self):
- req = renderer.Request(0,0,0)
- x,y = renderer.lonlat2merc(-180,-85)
- ctrans = renderer.CoordTransform(req)
- px,py = ctrans.forward(x,y)
- self.assertAlmostEqual(px,0.0)
- self.assertAlmostEqual(py,255.5806938147701)
- px2,py2 = ctrans.forward(-20037508.34,-20037508.34)
- self.assertAlmostEqual(px2,0.0)
- self.assertAlmostEqual(py2,256.0)
- px3,py3 = ctrans.forward(-20037508.34/2,-20037508.34/2)
- self.assertAlmostEqual(px2,0.0)
- self.assertAlmostEqual(py2,256.0)
-
- def test_adding_duplicate_points(self):
- req = renderer.Request(0,0,0)
- vtile = renderer.VectorTile(req)
- vtile.add_point(0,0,{})
- vtile.add_point(0,0,{})
- j_obj = json.loads(vtile.to_geojson())
- self.assertEqual(len(j_obj['features']),1)
-
- def test_vtile_attributes(self):
- req = renderer.Request(0,0,0)
- vtile = renderer.VectorTile(req)
- attr = {"name":"DC",
- "integer":10,
- "bigint":sys.maxint,
- "nbigint":-1 * sys.maxint,
- "float":1.5,
- "bigfloat":float(sys.maxint),
- "unistr":u"élan",
- "bool":True,
- "bool2":False
- }
- vtile.add_point(0,0,attr)
- j_obj = json.loads(vtile.to_geojson())
- self.assertEqual(j_obj['type'],"FeatureCollection")
- self.assertEqual(len(j_obj['features']),1)
- feature = j_obj['features'][0]
- self.assertDictEqual(feature['properties'],attr)
-
- def test_vtile_z0(self):
- req = renderer.Request(0,0,0)
- vtile = renderer.VectorTile(req)
- x,y = -8526703.378081053, 4740318.745473632
- vtile.add_point(x,y,{})
- j_obj = json.loads(vtile.to_geojson())
- feature = j_obj['features'][0]
- self.assertEqual(feature['type'],'Feature')
- self.assertEqual(feature['geometry']['type'],'Point')
- coords = feature['geometry']['coordinates']
- self.assertAlmostEqual(coords[0],x,-4)
- self.assertAlmostEqual(coords[1],y,-4)
-
- def test_vtile_z20(self):
- merc = renderer.SphericalMercator()
- x,y = -8526703.378081053, 4740318.745473632
- xyz_bounds = merc.xyz([x,y,x,y],20)
- req = renderer.Request(xyz_bounds[0],xyz_bounds[1],20)
- vtile = renderer.VectorTile(req)
- vtile.add_point(x,y,{"name":"DC","integer":10,"float":1.5})
- j_obj = json.loads(vtile.to_geojson())
- self.assertEqual(j_obj['type'],"FeatureCollection")
- self.assertEqual(len(j_obj['features']),1)
- feature = j_obj['features'][0]
- self.assertDictEqual(feature['properties'],{ "integer": 10,
- "float": 1.5,
- "name": "DC"
- })
- self.assertEqual(feature['type'],'Feature')
- self.assertEqual(feature['geometry']['type'],'Point')
- coords = feature['geometry']['coordinates']
- self.assertAlmostEqual(coords[0],x,2)
- self.assertAlmostEqual(coords[1],y,2)
-
- def test_vtile_z20_higher_precision(self):
- merc = renderer.SphericalMercator()
- x,y = -8526703.378081053, 4740318.745473632
- xyz_bounds = merc.xyz([x,y,x,y],20)
- req = renderer.Request(xyz_bounds[0],xyz_bounds[1],20)
- vtile = renderer.VectorTile(req,512)
- vtile.add_point(x,y,{})
- j_obj = json.loads(vtile.to_geojson())
- feature = j_obj['features'][0]
- coords = feature['geometry']['coordinates']
- self.assertAlmostEqual(coords[0],x,3)
- self.assertAlmostEqual(coords[1],y,3)
-
- def test_vtile_z22(self):
- merc = renderer.SphericalMercator()
- x,y = -8526703.378081053, 4740318.745473632
- xyz_bounds = merc.xyz([x,y,x,y],22)
- req = renderer.Request(xyz_bounds[0],xyz_bounds[1],22)
- vtile = renderer.VectorTile(req)
- vtile.add_point(x,y,{"name":"DC","integer":10,"float":1.5})
- j_obj = json.loads(vtile.to_geojson())
- self.assertEqual(j_obj['type'],"FeatureCollection")
- self.assertEqual(len(j_obj['features']),1)
- feature = j_obj['features'][0]
- self.assertDictEqual(feature['properties'],{ "integer": 10,
- "float": 1.5,
- "name": "DC"
- })
- self.assertEqual(feature['type'],'Feature')
- self.assertEqual(feature['geometry']['type'],'Point')
- coords = feature['geometry']['coordinates']
- self.assertAlmostEqual(coords[0],x,2)
- self.assertAlmostEqual(coords[1],y,1)
-
- def test_vtile_z22_higher_precision(self):
- merc = renderer.SphericalMercator()
- x,y = -8526703.378081053, 4740318.745473632
- xyz_bounds = merc.xyz([x,y,x,y],22)
- req = renderer.Request(xyz_bounds[0],xyz_bounds[1],22)
- vtile = renderer.VectorTile(req,512)
- vtile.add_point(x,y,{})
- j_obj = json.loads(vtile.to_geojson())
- feature = j_obj['features'][0]
- coords = feature['geometry']['coordinates']
- self.assertAlmostEqual(coords[0],x,4)
- self.assertAlmostEqual(coords[1],y,4)
-
-if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
diff --git a/test/raster_tile.cpp b/test/raster_tile.cpp
index e813093..1cf98b2 100644
--- a/test/raster_tile.cpp
+++ b/test/raster_tile.cpp
@@ -1,10 +1,7 @@
-// https://github.com/philsquared/Catch/wiki/Supplying-your-own-main()
-#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
// test utils
#include "test_utils.hpp"
-
#include "vector_tile_projection.hpp"
// vector output api
@@ -13,18 +10,24 @@
#include "vector_tile_util.hpp"
#include "vector_tile_datasource.hpp"
-
-#include <mapnik/graphics.hpp>
+#include <mapnik/util/fs.hpp>
#include <mapnik/datasource_cache.hpp>
+#include <mapnik/agg_renderer.hpp>
+#include <mapnik/load_map.hpp>
+#include <mapnik/image_reader.hpp>
+#include <mapnik/image_util.hpp>
-TEST_CASE( "vector tile output 1", "should create vector tile with one point" ) {
+#include <sstream>
+#include <fstream>
+
+TEST_CASE( "raster tile output 1", "should create raster tile with one raster layer" ) {
mapnik::datasource_cache::instance().register_datasources(MAPNIK_PLUGINDIR);
- typedef mapnik::vector::backend_pbf backend_type;
- typedef mapnik::vector::processor<backend_type> renderer_type;
- typedef mapnik::vector::tile tile_type;
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
unsigned _x=0,_y=0,_z=1;
double minx,miny,maxx,maxy;
- mapnik::vector::spherical_mercator merc(512);
+ mapnik::vector_tile_impl::spherical_mercator merc(512);
merc.xyz(_x,_y,_z,minx,miny,maxx,maxy);
mapnik::box2d<double> bbox;
bbox.init(minx,miny,maxx,maxy);
@@ -39,20 +42,23 @@ TEST_CASE( "vector tile output 1", "should create vector tile with one point" )
// created with:
// wget http://www.nacis.org/naturalearth/50m/raster/NE2_50m_SR_W.zip
// gdalwarp -t_srs EPSG:3857 -ts 1048 1048 -r bilinear NE2_50M_SR_W.tif natural_earth.tif
- params["file"] = "test/natural_earth.tif";
- MAPNIK_SHARED_PTR<mapnik::datasource> ds =
+ params["file"] = "test/data/natural_earth.tif";
+ std::shared_ptr<mapnik::datasource> ds =
mapnik::datasource_cache::instance().create(params);
lyr.set_datasource(ds);
- map.MAPNIK_ADD_LAYER(lyr);
+ map.add_layer(lyr);
mapnik::request m_req(tile_size,tile_size,bbox);
m_req.set_buffer_size(map.buffer_size());
renderer_type ren(backend,map,m_req,1.0,0,0,1,"jpeg",mapnik::SCALING_BILINEAR);
ren.apply();
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
CHECK(1 == tile.layers_size());
- mapnik::vector::tile_layer const& layer = tile.layers(0);
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
CHECK(1 == layer.features_size());
- mapnik::vector::tile_feature const& f = layer.features(0);
+ vector_tile::Tile_Feature const& f = layer.features(0);
CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f.id()));
CHECK(0 == f.geometry_size());
CHECK(f.has_raster());
@@ -60,11 +66,26 @@ TEST_CASE( "vector tile output 1", "should create vector tile with one point" )
CHECK(!ras_buffer.empty());
// debug
bool debug = false;
- if (debug) {
- std::ofstream file("out.jpeg", std::ios::out|std::ios::trunc|std::ios::binary);
+
+ if (!mapnik::util::exists("test/fixtures/expected-2.jpeg")) {
+ std::ofstream file("test/fixtures/expected-2.jpeg", std::ios::out|std::ios::trunc|std::ios::binary);
+ if (!file) {
+ throw std::runtime_error("could not write image");
+ }
file << ras_buffer;
file.close();
}
+ std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(ras_buffer.data(),ras_buffer.size()));
+ if (!reader.get()) {
+ throw std::runtime_error("could not open image bytes");
+ }
+ mapnik::image_rgba8 im_data(reader->width(),reader->height());
+ reader->read(0,0,im_data);
+ unsigned diff = testing::compare_images(im_data,"test/fixtures/expected-2.jpeg");
+ CHECK(0 == diff);
+ if (diff > 0) {
+ mapnik::save_to_file(im_data,"test/fixtures/actual-2.jpeg","jpeg");
+ }
std::size_t expected_image_size = 45660;
int expected_vtile_size = expected_image_size + 26;
@@ -82,11 +103,14 @@ TEST_CASE( "vector tile output 1", "should create vector tile with one point" )
map2.set_buffer_size(256);
tile_type tile2;
CHECK(tile2.ParseFromString(buffer));
+ std::string key2("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key2));
+ CHECK("" == key2);
CHECK(1 == tile2.layers_size());
- mapnik::vector::tile_layer const& layer2 = tile2.layers(0);
+ vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
CHECK(std::string("layer") == layer2.name());
CHECK(1 == layer2.features_size());
- mapnik::vector::tile_feature const& f2 = layer2.features(0);
+ vector_tile::Tile_Feature const& f2 = layer2.features(0);
CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f2.id()));
CHECK(0 == f2.geometry_size());
CHECK(f2.has_raster());
@@ -95,30 +119,132 @@ TEST_CASE( "vector tile output 1", "should create vector tile with one point" )
CHECK(expected_image_size == f2.raster().size());
}
mapnik::layer lyr2("layer",map2.srs());
- MAPNIK_SHARED_PTR<mapnik::vector::tile_datasource> ds2 = MAPNIK_MAKE_SHARED<
- mapnik::vector::tile_datasource>(
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds2 = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource>(
layer2,_x,_y,_z,map2.width());
- ds2->set_envelope(bbox);
lyr2.set_datasource(ds2);
lyr2.add_style("style");
- map2.MAPNIK_ADD_LAYER(lyr2);
- mapnik::load_map(map2,"test/raster_style.xml");
+ map2.add_layer(lyr2);
+ mapnik::load_map(map2,"test/data/raster_style.xml");
map2.zoom_to_box(bbox);
- mapnik::image_32 im(map2.width(),map2.height());
- mapnik::agg_renderer<mapnik::image_32> ren2(map2,im);
+ mapnik::image_rgba8 im(map2.width(),map2.height());
+ mapnik::agg_renderer<mapnik::image_rgba8> ren2(map2,im);
ren2.apply();
- if (debug) {
- mapnik::save_to_file(im,"image.png");
+ if (!mapnik::util::exists("test/fixtures/expected-2.png")) {
+ mapnik::save_to_file(im,"test/fixtures/expected-2.png","png32");
+ }
+ diff = testing::compare_images(im,"test/fixtures/expected-2.png");
+ CHECK(0 == diff);
+ if (diff > 0) {
+ mapnik::save_to_file(im_data,"test/fixtures/actual-2.png","png32");
}
- unsigned rgba = im.data()(128,128);
- CHECK(rgba != 0);
}
-int main (int argc, char* const argv[])
-{
- GOOGLE_PROTOBUF_VERIFY_VERSION;
- int result = Catch::Session().run( argc, argv );
- if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n");
- google::protobuf::ShutdownProtobufLibrary();
- return result;
+TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
+ mapnik::datasource_cache::instance().register_datasources(MAPNIK_PLUGINDIR);
+ typedef vector_tile::Tile tile_type;
+ tile_type tile;
+ {
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ double minx,miny,maxx,maxy;
+ mapnik::vector_tile_impl::spherical_mercator merc(256);
+ merc.xyz(0,0,0,minx,miny,maxx,maxy);
+ mapnik::box2d<double> bbox(minx,miny,maxx,maxy);
+ backend_type backend(tile,16);
+ mapnik::Map map(256,256,"+init=epsg:3857");
+ map.set_buffer_size(1024);
+ mapnik::layer lyr("layer",map.srs());
+ mapnik::parameters params;
+ params["type"] = "gdal";
+ std::ostringstream s;
+ s << std::fixed << std::setprecision(16)
+ << bbox.minx() << ',' << bbox.miny() << ','
+ << bbox.maxx() << ',' << bbox.maxy();
+ params["extent"] = s.str();
+ params["file"] = "test/data/256x256.png";
+ std::shared_ptr<mapnik::datasource> ds =
+ mapnik::datasource_cache::instance().create(params);
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+ mapnik::request m_req(256,256,bbox);
+ m_req.set_buffer_size(map.buffer_size());
+ renderer_type ren(backend,map,m_req,1.0,0,0,1,"jpeg",mapnik::SCALING_BILINEAR);
+ ren.apply();
+ }
+ // Done creating test data, now test created tile
+ CHECK(1 == tile.layers_size());
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ CHECK(std::string("layer") == layer.name());
+ CHECK(1 == layer.features_size());
+ vector_tile::Tile_Feature const& f = layer.features(0);
+ CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f.id()));
+ CHECK(0 == f.geometry_size());
+ CHECK(f.has_raster());
+ std::string const& ras_buffer = f.raster();
+ CHECK(!ras_buffer.empty());
+ // debug
+ bool debug = false;
+ if (debug) {
+ std::ofstream file("out2.png", std::ios::out|std::ios::trunc|std::ios::binary);
+ file << ras_buffer;
+ file.close();
+ }
+
+ // confirm tile looks correct as encoded
+ std::size_t expected_image_size = 1654;
+ int expected_vtile_size = expected_image_size + 23;
+ if (!debug) {
+ CHECK(expected_image_size == ras_buffer.size());
+ CHECK(expected_vtile_size == tile.ByteSize());
+ }
+ std::string buffer;
+ CHECK(tile.SerializeToString(&buffer));
+ if (!debug) {
+ CHECK(expected_vtile_size == buffer.size());
+ }
+ tile_type tile2;
+ CHECK(tile2.ParseFromString(buffer));
+ CHECK(1 == tile2.layers_size());
+ vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
+ CHECK(std::string("layer") == layer2.name());
+ CHECK(1 == layer2.features_size());
+ vector_tile::Tile_Feature const& f2 = layer2.features(0);
+ CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f2.id()));
+ CHECK(0 == f2.geometry_size());
+ CHECK(f2.has_raster());
+ CHECK(!f2.raster().empty());
+ if (!debug) {
+ CHECK(expected_image_size == f2.raster().size());
+ }
+
+ // now read back and render image at larger size
+ // and zoomed in
+ double minx,miny,maxx,maxy;
+ mapnik::vector_tile_impl::spherical_mercator merc(256);
+ // 2/0/1.png
+ merc.xyz(0,1,2,minx,miny,maxx,maxy);
+ mapnik::box2d<double> bbox(minx,miny,maxx,maxy);
+ mapnik::Map map2(256,256,"+init=epsg:3857");
+ map2.set_buffer_size(1024);
+ mapnik::layer lyr2("layer",map2.srs());
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds2 = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource>(
+ layer2,0,0,0,256);
+ lyr2.set_datasource(ds2);
+ lyr2.add_style("style");
+ map2.add_layer(lyr2);
+ mapnik::load_map(map2,"test/data/raster_style.xml");
+ map2.zoom_to_box(bbox);
+ mapnik::image_rgba8 im(map2.width(),map2.height());
+ mapnik::agg_renderer<mapnik::image_rgba8> ren2(map2,im);
+ ren2.apply();
+ if (!mapnik::util::exists("test/fixtures/expected-3.png")) {
+ mapnik::save_to_file(im,"test/fixtures/expected-3.png","png32");
+ }
+ unsigned diff = testing::compare_images(im,"test/fixtures/expected-3.png");
+ CHECK(0 == diff);
+ if (diff > 0) {
+ mapnik::save_to_file(im,"test/fixtures/actual-3.png","png32");
+ }
}
diff --git a/test/test_main.cpp b/test/test_main.cpp
new file mode 100644
index 0000000..8d5a3a8
--- /dev/null
+++ b/test/test_main.cpp
@@ -0,0 +1,14 @@
+// https://github.com/philsquared/Catch/wiki/Supplying-your-own-main()
+#define CATCH_CONFIG_RUNNER
+#include "catch.hpp"
+
+#include <google/protobuf/stubs/common.h>
+
+int main (int argc, char* const argv[])
+{
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ int result = Catch::Session().run( argc, argv );
+ if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n");
+ google::protobuf::ShutdownProtobufLibrary();
+ return result;
+}
diff --git a/test/test_utils.cpp b/test/test_utils.cpp
new file mode 100644
index 0000000..7378d5e
--- /dev/null
+++ b/test/test_utils.cpp
@@ -0,0 +1,89 @@
+// mapnik
+#include <mapnik/map.hpp>
+#include <mapnik/layer.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/agg_renderer.hpp>
+#include <mapnik/save_map.hpp>
+#include <mapnik/map.hpp>
+#include <mapnik/feature.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/unicode.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/load_map.hpp>
+#include <mapnik/memory_datasource.hpp>
+#include <mapnik/image.hpp>
+#include <mapnik/image_reader.hpp>
+#include <mapnik/util/file_io.hpp>
+#include <mapnik/json/geometry_parser.hpp>
+#include <string>
+#include <memory>
+
+namespace testing {
+
+std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool second) {
+ mapnik::parameters params;
+ params["type"] = "memory";
+ std::shared_ptr<mapnik::memory_datasource> ds = std::make_shared<mapnik::memory_datasource>(params);
+ mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
+ ctx->push("name");
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+ mapnik::transcoder tr("utf-8");
+ feature->put("name",tr.transcode("null island"));
+ feature->set_geometry(mapnik::geometry::point<double>(x,y));
+ ds->push(feature);
+ if (second) {
+ ctx->push("name2");
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+ mapnik::transcoder tr("utf-8");
+ feature->put("name",tr.transcode("null island"));
+ feature->put("name2",tr.transcode("null island 2"));
+ feature->set_geometry(mapnik::geometry::point<double>(x+1,y+1));
+ ds->push(feature);
+ }
+ return ds;
+}
+
+std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& geojson_file) {
+ mapnik::util::file input(geojson_file);
+ auto json = input.data();
+ mapnik::geometry::geometry<double> geom;
+ std::string json_string(json.get());
+ if (!mapnik::json::from_geojson(json_string, geom))
+ {
+ throw std::runtime_error("failed to parse geojson");
+ }
+ mapnik::parameters params;
+ params["type"] = "memory";
+ std::shared_ptr<mapnik::memory_datasource> ds = std::make_shared<mapnik::memory_datasource>(params);
+ mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
+ ctx->push("name");
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+ feature->set_geometry(std::move(geom));
+ ds->push(feature);
+ return ds;
+}
+
+unsigned compare_images(mapnik::image_rgba8 const& src1,
+ std::string const& filepath,
+ int threshold,
+ bool alpha)
+{
+ boost::optional<std::string> type = mapnik::type_from_filename(filepath);
+ if (!type)
+ {
+ throw mapnik::image_reader_exception("Failed to detect type of: " + filepath);
+ }
+ std::unique_ptr<mapnik::image_reader> reader2(mapnik::get_image_reader(filepath,*type));
+ if (!reader2.get())
+ {
+ throw mapnik::image_reader_exception("Failed to load: " + filepath);
+ }
+ mapnik::image_any const& image_2 = reader2->read(0,0,reader2->width(),reader2->height());
+
+ mapnik::image_rgba8 const& src2 = mapnik::util::get<mapnik::image_rgba8>(image_2);
+ return mapnik::compare(src1,src2,threshold,alpha);
+}
+
+
+} // end ns
diff --git a/test/test_utils.hpp b/test/test_utils.hpp
index 3e91716..25a4b17 100644
--- a/test/test_utils.hpp
+++ b/test/test_utils.hpp
@@ -1,36 +1,30 @@
+#ifndef __MAPNIK_VECTOR_TEST_UTILS_H__
+#define __MAPNIK_VECTOR_TEST_UTILS_H__
+
// mapnik
-#include "mapnik3x_compatibility.hpp"
-#include <mapnik/map.hpp>
-#include <mapnik/layer.hpp>
-#include <mapnik/image_util.hpp>
-#include <mapnik/graphics.hpp>
-#include <mapnik/agg_renderer.hpp>
-#include <mapnik/save_map.hpp>
-#include <mapnik/map.hpp>
-#include <mapnik/feature.hpp>
-#include <mapnik/feature_factory.hpp>
-#include <mapnik/unicode.hpp>
-#include <mapnik/geometry.hpp>
-#include <mapnik/datasource.hpp>
-#include <mapnik/load_map.hpp>
#include <mapnik/memory_datasource.hpp>
+#include <mapnik/image.hpp>
+#include <mapnik/image_any.hpp>
+#include <memory>
+
+namespace testing {
-// boost
-#include MAPNIK_SHARED_INCLUDE
-#include MAPNIK_MAKE_SHARED_INCLUDE
+std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool second=false);
+std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& geojson_file);
-#include <string>
+unsigned compare_images(std::string const& src_fn,
+ std::string const& dest_fn,
+ int threshold=16,
+ bool alpha=true);
+unsigned compare_images(mapnik::image_rgba8 const& src1,
+ std::string const& filepath,
+ int threshold=16,
+ bool alpha=true);
+unsigned compare_images(mapnik::image_any const& src1,
+ std::string const& filepath,
+ int threshold=16,
+ bool alpha=true);
-MAPNIK_SHARED_PTR<mapnik::memory_datasource> build_ds(double x,double y) {
- mapnik::context_ptr ctx = MAPNIK_MAKE_SHARED<mapnik::context_type>();
- ctx->push("name");
- mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
- mapnik::transcoder tr("utf-8");
- feature->put("name",tr.transcode("null island"));
- mapnik::geometry_type * pt = new mapnik::geometry_type(MAPNIK_POINT);
- pt->move_to(x,y);
- feature->add_geometry(pt);
- MAPNIK_SHARED_PTR<mapnik::memory_datasource> ds = MAPNIK_MAKE_SHARED<mapnik::memory_datasource>();
- ds->push(feature);
- return ds;
}
+
+#endif // __MAPNIK_VECTOR_TEST_UTILS_H__
diff --git a/test/vector_tile.cpp b/test/vector_tile.cpp
index 4ea681e..04ecb1c 100644
--- a/test/vector_tile.cpp
+++ b/test/vector_tile.cpp
@@ -1,28 +1,77 @@
-// https://github.com/philsquared/Catch/wiki/Supplying-your-own-main()
-#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
// test utils
#include "test_utils.hpp"
+#include <mapnik/memory_datasource.hpp>
+#include <mapnik/util/fs.hpp>
+#include <mapnik/agg_renderer.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/load_map.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/vertex_adapters.hpp>
+#include <mapnik/projection.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/geometry_is_empty.hpp>
+#include <mapnik/util/geometry_to_geojson.hpp>
+#include <mapnik/util/geometry_to_wkt.hpp>
+#include <mapnik/geometry_reprojection.hpp>
+#include <mapnik/geometry_transform.hpp>
+#include <mapnik/geometry_strategy.hpp>
+#include <mapnik/proj_strategy.hpp>
+#include <mapnik/geometry.hpp>
-#include "vector_tile_projection.hpp"
-
-const unsigned _x=0,_y=0,_z=0;
-const unsigned tile_size = 256;
-mapnik::box2d<double> bbox;
+#include <boost/optional/optional_io.hpp>
// vector output api
+#include "vector_tile_compression.hpp"
#include "vector_tile_processor.hpp"
#include "vector_tile_backend_pbf.hpp"
#include "vector_tile_util.hpp"
+#include "vector_tile_projection.hpp"
+#include "vector_tile_geometry_decoder.hpp"
// vector input api
#include "vector_tile_datasource.hpp"
+/*
+TEST_CASE( "vector tile negative id", "hmm" ) {
+ vector_tile::Tile tile;
+ vector_tile::Tile_Layer * layer = tile.add_layers();
+ vector_tile::Tile_Feature * feat = layer->add_features();
+ feat->set_id(-1);
+ std::clog << feat->id() << "\n";
+ //CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon);
+}
+*/
+
+TEST_CASE( "vector tile compression", "should be able to round trip gzip and zlib data" ) {
+ std::string data("amazing data");
+ CHECK(!mapnik::vector_tile_impl::is_zlib_compressed(data));
+ CHECK(!mapnik::vector_tile_impl::is_gzip_compressed(data));
+ std::string zlibbed;
+ mapnik::vector_tile_impl::zlib_compress(data,zlibbed,false);
+ // failing - why?
+ //CHECK(mapnik::vector_tile_impl::is_zlib_compressed(zlibbed));
+ CHECK(!mapnik::vector_tile_impl::is_gzip_compressed(zlibbed));
+
+ std::string unzlibbed;
+ mapnik::vector_tile_impl::zlib_decompress(zlibbed,unzlibbed);
+ CHECK(data == unzlibbed);
+
+ std::string gzipped;
+ mapnik::vector_tile_impl::zlib_compress(data,gzipped,true);
+ CHECK(!mapnik::vector_tile_impl::is_zlib_compressed(gzipped));
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(gzipped));
+
+ std::string ungzipped;
+ mapnik::vector_tile_impl::zlib_decompress(gzipped,ungzipped);
+ CHECK(data == ungzipped);
+}
+
TEST_CASE( "vector tile projection 1", "should support z/x/y to bbox conversion at 0/0/0" ) {
- mapnik::vector::spherical_mercator merc(256);
+ mapnik::vector_tile_impl::spherical_mercator merc(256);
double minx,miny,maxx,maxy;
- merc.xyz(_x,_y,_z,minx,miny,maxx,maxy);
+ merc.xyz(0,0,0,minx,miny,maxx,maxy);
mapnik::box2d<double> map_extent(minx,miny,maxx,maxy);
mapnik::box2d<double> e(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
double epsilon = 0.000001;
@@ -33,7 +82,7 @@ TEST_CASE( "vector tile projection 1", "should support z/x/y to bbox conversion
}
TEST_CASE( "vector tile projection 2", "should support z/x/y to bbox conversion up to z33" ) {
- mapnik::vector::spherical_mercator merc(256);
+ mapnik::vector_tile_impl::spherical_mercator merc(256);
int x = 2145960701;
int y = 1428172928;
int z = 32;
@@ -48,107 +97,134 @@ TEST_CASE( "vector tile projection 2", "should support z/x/y to bbox conversion
CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon);
}
-TEST_CASE( "vector tile output 1", "should create vector tile with one point" ) {
- typedef mapnik::vector::backend_pbf backend_type;
- typedef mapnik::vector::processor<backend_type> renderer_type;
- typedef mapnik::vector::tile tile_type;
+TEST_CASE( "vector tile output 1", "should create vector tile with two points" ) {
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
tile_type tile;
backend_type backend(tile,16);
- mapnik::Map map(tile_size,tile_size);
- mapnik::layer lyr("layer");
- lyr.set_datasource(build_ds(0,0));
- map.MAPNIK_ADD_LAYER(lyr);
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
+ lyr.set_datasource(testing::build_ds(0,0,true));
+ map.add_layer(lyr);
mapnik::request m_req(tile_size,tile_size,bbox);
renderer_type ren(backend,map,m_req);
ren.apply();
- CHECK(1 == tile.layers_size());
- mapnik::vector::tile_layer const& layer = tile.layers(0);
+ CHECK( ren.painted() == true );
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
+ REQUIRE(1 == tile.layers_size());
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
- CHECK(1 == layer.features_size());
- mapnik::vector::tile_feature const& f = layer.features(0);
+ REQUIRE(2 == layer.features_size());
+ vector_tile::Tile_Feature const& f = layer.features(0);
CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f.id()));
- CHECK(3 == f.geometry_size());
+ REQUIRE(3 == f.geometry_size());
CHECK(9 == f.geometry(0));
CHECK(4096 == f.geometry(1));
CHECK(4096 == f.geometry(2));
- CHECK(52 == tile.ByteSize());
+ CHECK(95 == tile.ByteSize());
std::string buffer;
CHECK(tile.SerializeToString(&buffer));
- CHECK(52 == buffer.size());
+ CHECK(95 == buffer.size());
}
TEST_CASE( "vector tile output 2", "adding empty layers should result in empty tile" ) {
- typedef mapnik::vector::backend_pbf backend_type;
- typedef mapnik::vector::processor<backend_type> renderer_type;
- typedef mapnik::vector::tile tile_type;
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
tile_type tile;
backend_type backend(tile,16);
- mapnik::Map map(tile_size,tile_size);
- map.MAPNIK_ADD_LAYER(mapnik::layer("layer"));
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ map.add_layer(mapnik::layer("layer",map.srs()));
map.zoom_to_box(bbox);
mapnik::request m_req(tile_size,tile_size,bbox);
renderer_type ren(backend,map,m_req);
ren.apply();
+ CHECK( ren.painted() == false );
+ std::string key("");
+ CHECK(true == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
CHECK(0 == tile.layers_size());
}
TEST_CASE( "vector tile output 3", "adding layers with geometries outside rendering extent should not add layer" ) {
- typedef mapnik::vector::backend_pbf backend_type;
- typedef mapnik::vector::processor<backend_type> renderer_type;
- typedef mapnik::vector::tile tile_type;
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
tile_type tile;
backend_type backend(tile,16);
- mapnik::Map map(tile_size,tile_size);
- mapnik::layer lyr("layer");
- mapnik::context_ptr ctx = MAPNIK_MAKE_SHARED<mapnik::context_type>();
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
+ mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
- MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_LINESTRING));
- g->move_to(0,0);
- g->line_to(1,1);
- feature->add_geometry(g.release());
- MAPNIK_SHARED_PTR<mapnik::memory_datasource> ds = MAPNIK_MAKE_SHARED<mapnik::memory_datasource>();
+ mapnik::geometry::line_string<double> g;
+ g.add_coord(-10,-10);
+ g.add_coord(-11,-11);
+ feature->set_geometry(std::move(g));
+ mapnik::parameters params;
+ params["type"] = "memory";
+ std::shared_ptr<mapnik::memory_datasource> ds = std::make_shared<mapnik::memory_datasource>(params);
ds->push(feature);
lyr.set_datasource(ds);
- map.MAPNIK_ADD_LAYER(lyr);
- map.zoom_to_box(bbox);
- mapnik::request m_req(tile_size,tile_size,bbox);
+ map.add_layer(lyr);
+ mapnik::box2d<double> custom_bbox(0,0,10,10);
+ map.zoom_to_box(custom_bbox);
+ mapnik::request m_req(tile_size,tile_size,custom_bbox);
renderer_type ren(backend,map,m_req);
ren.apply();
- CHECK(1 == tile.layers_size());
+ std::string key("");
+ CHECK(true == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
+ CHECK(0 == tile.layers_size());
}
TEST_CASE( "vector tile output 4", "adding layers with degenerate geometries should not add layer" ) {
- typedef mapnik::vector::backend_pbf backend_type;
- typedef mapnik::vector::processor<backend_type> renderer_type;
- typedef mapnik::vector::tile tile_type;
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
tile_type tile;
backend_type backend(tile,16);
- mapnik::Map map(tile_size,tile_size);
- mapnik::layer lyr("layer");
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
// create a datasource with a feature outside the map
- MAPNIK_SHARED_PTR<mapnik::memory_datasource> ds = build_ds(bbox.minx()-1,bbox.miny()-1);
+ std::shared_ptr<mapnik::memory_datasource> ds = testing::build_ds(bbox.minx()-1,bbox.miny()-1);
// but fake the overall envelope to ensure the layer is still processed
// and then removed given no intersecting features will be added
ds->set_envelope(bbox);
lyr.set_datasource(ds);
- map.MAPNIK_ADD_LAYER(lyr);
+ map.add_layer(lyr);
map.zoom_to_box(bbox);
mapnik::request m_req(tile_size,tile_size,bbox);
renderer_type ren(backend,map,m_req);
ren.apply();
+ std::string key("");
+ CHECK(true == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
CHECK(0 == tile.layers_size());
}
TEST_CASE( "vector tile input", "should be able to parse message and render point" ) {
- typedef mapnik::vector::backend_pbf backend_type;
- typedef mapnik::vector::processor<backend_type> renderer_type;
- typedef mapnik::vector::tile tile_type;
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
tile_type tile;
backend_type backend(tile,16);
- mapnik::Map map(tile_size,tile_size);
- mapnik::layer lyr("layer");
- lyr.set_datasource(build_ds(0,0));
- map.MAPNIK_ADD_LAYER(lyr);
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
+ lyr.set_datasource(testing::build_ds(0,0));
+ map.add_layer(lyr);
map.zoom_to_box(bbox);
mapnik::request m_req(map.width(),map.height(),map.get_current_extent());
renderer_type ren(backend,map,m_req);
@@ -158,63 +234,83 @@ TEST_CASE( "vector tile input", "should be able to parse message and render poin
CHECK(tile.SerializeToString(&buffer));
CHECK(52 == buffer.size());
// now create new objects
- mapnik::Map map2(tile_size,tile_size);
+ mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
tile_type tile2;
CHECK(tile2.ParseFromString(buffer));
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
+ CHECK("" == key);
CHECK(1 == tile2.layers_size());
- mapnik::vector::tile_layer const& layer2 = tile2.layers(0);
+ vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
CHECK(std::string("layer") == layer2.name());
- mapnik::layer lyr2("layer");
- MAPNIK_SHARED_PTR<mapnik::vector::tile_datasource> ds = MAPNIK_MAKE_SHARED<
- mapnik::vector::tile_datasource>(
- layer2,_x,_y,_z,map2.width());
+ CHECK(1 == layer2.features_size());
+
+ mapnik::layer lyr2("layer",map.srs());
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource>(
+ layer2,0,0,0,map2.width());
ds->set_envelope(bbox);
+ CHECK( ds->type() == mapnik::datasource::Vector );
+ CHECK( ds->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
mapnik::layer_descriptor lay_desc = ds->get_descriptor();
std::vector<std::string> expected_names;
expected_names.push_back("name");
std::vector<std::string> names;
- BOOST_FOREACH(mapnik::attribute_descriptor const& desc, lay_desc.get_descriptors())
+ for (auto const& desc : lay_desc.get_descriptors())
{
names.push_back(desc.get_name());
}
CHECK(names == expected_names);
lyr2.set_datasource(ds);
- map2.MAPNIK_ADD_LAYER(lyr2);
- mapnik::load_map(map2,"test/style.xml");
+ lyr2.add_style("style");
+ map2.add_layer(lyr2);
+ mapnik::load_map(map2,"test/data/style.xml");
+ //std::clog << mapnik::save_map_to_string(map2) << "\n";
map2.zoom_to_box(bbox);
- mapnik::image_32 im(map2.width(),map2.height());
- mapnik::agg_renderer<mapnik::image_32> ren2(map2,im);
+ mapnik::image_rgba8 im(map2.width(),map2.height());
+ mapnik::agg_renderer<mapnik::image_rgba8> ren2(map2,im);
ren2.apply();
- unsigned rgba = im.data()(128,128);
- CHECK(0 == rgba);
- //mapnik::save_to_file(im,"test.png");
+ if (!mapnik::util::exists("test/fixtures/expected-1.png")) {
+ mapnik::save_to_file(im,"test/fixtures/expected-1.png","png32");
+ }
+ unsigned diff = testing::compare_images(im,"test/fixtures/expected-1.png");
+ CHECK(0 == diff);
+ if (diff > 0) {
+ mapnik::save_to_file(im,"test/fixtures/actual-1.png","png32");
+ }
}
+
TEST_CASE( "vector tile datasource", "should filter features outside extent" ) {
- typedef mapnik::vector::backend_pbf backend_type;
- typedef mapnik::vector::processor<backend_type> renderer_type;
- typedef mapnik::vector::tile tile_type;
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
tile_type tile;
backend_type backend(tile,16);
- mapnik::Map map(tile_size,tile_size);
- mapnik::layer lyr("layer");
- lyr.set_datasource(build_ds(0,0));
- map.MAPNIK_ADD_LAYER(lyr);
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
+ lyr.set_datasource(testing::build_ds(0,0));
+ map.add_layer(lyr);
mapnik::request m_req(tile_size,tile_size,bbox);
renderer_type ren(backend,map,m_req);
ren.apply();
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
CHECK(1 == tile.layers_size());
- mapnik::vector::tile_layer const& layer = tile.layers(0);
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
CHECK(1 == layer.features_size());
- mapnik::vector::tile_feature const& f = layer.features(0);
+ vector_tile::Tile_Feature const& f = layer.features(0);
CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f.id()));
CHECK(3 == f.geometry_size());
CHECK(9 == f.geometry(0));
CHECK(4096 == f.geometry(1));
CHECK(4096 == f.geometry(2));
// now actually start the meat of the test
- mapnik::vector::tile_datasource ds(layer,_x,_y,_z,tile_size);
+ mapnik::vector_tile_impl::tile_datasource ds(layer,0,0,0,tile_size);
mapnik::featureset_ptr fs;
// ensure we can query single feature
@@ -262,37 +358,75 @@ TEST_CASE( "vector tile datasource", "should filter features outside extent" ) {
CHECK(f_ptr->context()->size() == 1);
}
-// NOTE: encoding a multiple lines as one path is technically incorrect
-// because in Mapnik the protocol is to split geometry parts into separate paths
-// however this case should still be supported in error and its an optimization in the
-// case where you know that lines do not need to be labeled in custom ways.
+TEST_CASE( "backend does not crash on misusage", "adding feature before layer" ) {
+ vector_tile::Tile tile;
+ mapnik::vector_tile_impl::backend_pbf backend(tile,1);
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(std::make_shared<mapnik::context_type>(),1));
+ backend.start_tile_feature(*feature);
+}
+
+TEST_CASE( "backend does not crash on misusage 2", "adding path before feature" ) {
+ vector_tile::Tile tile;
+ mapnik::vector_tile_impl::backend_pbf backend(tile,1);
+ mapnik::geometry::point<std::int64_t> geom;
+ CHECK(0 == backend.add_path(geom) );
+}
+
+// NOTE: encoding multiple lines as one path is technically incorrect
+// because in Mapnik the protocol is to split geometry parts into separate paths.
+// However this case should still be supported because keeping a single flat array is an
+// important optimization in the case that lines do not need to be labeled in custom ways
+// or represented as GeoJSON
TEST_CASE( "encoding multi line as one path", "should maintain second move_to command" ) {
// Options
// here we use a multiplier of 1 to avoid rounding numbers
// and stay in integer space for simplity
unsigned path_multiplier = 1;
- // here we use an extreme tolerance to prove tht all vertices are maintained no matter
+ // here we use an extreme tolerance to prove that all vertices are maintained no matter
// the tolerance because we never want to drop a move_to or the first line_to
- unsigned tolerance = 2000000;
+ //unsigned tolerance = 2000000;
// now create the testing data
- mapnik::vector::tile tile;
- mapnik::vector::backend_pbf backend(tile,path_multiplier);
+ vector_tile::Tile tile;
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::vector_tile_impl::backend_pbf backend(tile,path_multiplier);
backend.start_tile_layer("layer");
- mapnik::feature_ptr feature(mapnik::feature_factory::create(MAPNIK_MAKE_SHARED<mapnik::context_type>(),1));
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(std::make_shared<mapnik::context_type>(),1));
backend.start_tile_feature(*feature);
- MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_LINESTRING));
+ mapnik::geometry::multi_line_string<std::int64_t> geom;
+ {
+ mapnik::geometry::linear_ring<std::int64_t> ring;
+ ring.add_coord(0,0);
+ ring.add_coord(2,2);
+ geom.emplace_back(std::move(ring));
+ }
+ {
+ mapnik::geometry::linear_ring<std::int64_t> ring;
+ ring.add_coord(1,1);
+ ring.add_coord(2,2);
+ geom.emplace_back(std::move(ring));
+ }
+ /*
g->move_to(0,0); // takes 3 geoms: command length,x,y
g->line_to(2,2); // new command, so again takes 3 geoms: command length,x,y | total 6
g->move_to(1,1); // takes 3 geoms: command length,x,y
g->line_to(2,2); // new command, so again takes 3 geoms: command length,x,y | total 6
- backend.add_path(*g, tolerance, g->type());
+ */
+ backend.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
+ for (auto const& line : geom)
+ {
+ backend.add_path(line);
+ }
backend.stop_tile_feature();
backend.stop_tile_layer();
// done encoding single feature/geometry
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
CHECK(1 == tile.layers_size());
- mapnik::vector::tile_layer const& layer = tile.layers(0);
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(1 == layer.features_size());
- mapnik::vector::tile_feature const& f = layer.features(0);
+ vector_tile::Tile_Feature const& f = layer.features(0);
CHECK(12 == f.geometry_size());
CHECK(9 == f.geometry(0)); // 1 move_to
CHECK(0 == f.geometry(1)); // x:0
@@ -306,8 +440,21 @@ TEST_CASE( "encoding multi line as one path", "should maintain second move_to co
CHECK(10 == f.geometry(9)); // 1 line_to
CHECK(2 == f.geometry(10)); // x:2
CHECK(2 == f.geometry(11)); // y:2
+
+ mapnik::featureset_ptr fs;
+ mapnik::feature_ptr f_ptr;
+
+ mapnik::vector_tile_impl::tile_datasource ds(layer,0,0,0,tile_size);
+ fs = ds.features(mapnik::query(bbox));
+ f_ptr = fs->next();
+ CHECK(f_ptr != mapnik::feature_ptr());
+ // no attributes
+ CHECK(f_ptr->context()->size() == 0);
+
+ CHECK(f_ptr->get_geometry().is<mapnik::geometry::multi_line_string<double> >());
}
+
TEST_CASE( "encoding single line 1", "should maintain start/end vertex" ) {
// Options
// here we use a multiplier of 1 to avoid rounding numbers
@@ -316,29 +463,41 @@ TEST_CASE( "encoding single line 1", "should maintain start/end vertex" ) {
// here we use a tolerance of 2. Along with a multiplier of 1 this
// says to discard any verticies that are not at least >= 2 different
// in both the x and y from the previous vertex
- unsigned tolerance = 2;
+ // unsigned tolerance = 2;
// now create the testing data
- mapnik::vector::tile tile;
- mapnik::vector::backend_pbf backend(tile,path_multiplier);
+ vector_tile::Tile tile;
+ mapnik::vector_tile_impl::backend_pbf backend(tile,path_multiplier);
backend.start_tile_layer("layer");
- mapnik::feature_ptr feature(mapnik::feature_factory::create(MAPNIK_MAKE_SHARED<mapnik::context_type>(),1));
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(std::make_shared<mapnik::context_type>(),1));
backend.start_tile_feature(*feature);
- MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_LINESTRING));
+ /*
+ std::unique_ptr<mapnik::geometry_type> g(new mapnik::geometry_type(mapnik::geometry_type::types::LineString));
g->move_to(0,0); // takes 3 geoms: command length,x,y
g->line_to(2,2); // new command, so again takes 3 geoms: command length,x,y | total 6
g->line_to(1000,1000); // repeated line_to, so only takes 2 geoms: x,y | total 8
g->line_to(1001,1001); // should skip given tolerance of 2 | total 8
g->line_to(1001,1001); // should not skip given it is the endpoint, added 2 geoms | total 10
- backend.add_path(*g, tolerance, g->type());
+ */
+ mapnik::geometry::line_string<std::int64_t> geom;
+ geom.add_coord(0,0);
+ geom.add_coord(2,2);
+ geom.add_coord(1000,1000);
+ geom.add_coord(1001,1001);
+ geom.add_coord(1001,1001);
+ backend.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
+ backend.add_path(geom);
backend.stop_tile_feature();
backend.stop_tile_layer();
// done encoding single feature/geometry
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
CHECK(1 == tile.layers_size());
- mapnik::vector::tile_layer const& layer = tile.layers(0);
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(1 == layer.features_size());
- mapnik::vector::tile_feature const& f = layer.features(0);
+ vector_tile::Tile_Feature const& f = layer.features(0);
// sequence of 10 geometries given tolerance of 2
- CHECK(8 == f.geometry_size());
+ CHECK(12 == f.geometry_size());
// first geometry is 9, which packs both the command and how many verticies are encoded with that same command
// It is 9 because it is a move_to (which is an enum of 1) and there is one command (length == 1)
unsigned move_value = f.geometry(0);
@@ -356,11 +515,11 @@ TEST_CASE( "encoding single line 1", "should maintain start/end vertex" ) {
// in this case there should be 2 because two were skipped
unsigned line_value = f.geometry(3);
// (2 << 3) | (2 & ((1 << 3) -1)) == 18
- CHECK(18 == line_value);
+ CHECK(34 == line_value);
unsigned line_cmd = line_value & ((1 << 3) - 1);
CHECK(2 == line_cmd);
unsigned line_length = line_value >> 3;
- CHECK(2 == line_length);
+ CHECK(4 == line_length);
// 5th and 6th are the x,y of the first line_to command
// due zigzag encoding the 2,2 should be 4,4
// delta encoding has no impact since the previous coordinate was 0,0
@@ -371,23 +530,24 @@ TEST_CASE( "encoding single line 1", "should maintain start/end vertex" ) {
// 7th and 8th are x,y of the second line_to command
// due to delta encoding 1001-2 becomes 999
// zigzag encoded 999 becomes 1998 == (999 << 1) ^ (999 >> 31)
- CHECK(1998 == f.geometry(6));
- CHECK(1998 == f.geometry(7));
+ CHECK(1996 == f.geometry(6));
+ CHECK(1996 == f.geometry(7));
}
-// testcase for avoiding error in is_solid_extent of
+// testcase for avoiding error in mapnik::vector_tile_impl::is_solid_extent of
// "Unknown command type (is_solid_extent): 0"
// not yet clear if this test is correct
// ported from shapefile test in tilelive-bridge (a:should render a (1.0.1))
TEST_CASE( "encoding single line 2", "should maintain start/end vertex" ) {
unsigned path_multiplier = 16;
- unsigned tolerance = 5;
- mapnik::vector::tile tile;
- mapnik::vector::backend_pbf backend(tile,path_multiplier);
+ //unsigned tolerance = 5;
+ vector_tile::Tile tile;
+ mapnik::vector_tile_impl::backend_pbf backend(tile,path_multiplier);
backend.start_tile_layer("layer");
- mapnik::feature_ptr feature(mapnik::feature_factory::create(MAPNIK_MAKE_SHARED<mapnik::context_type>(),1));
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(std::make_shared<mapnik::context_type>(),1));
backend.start_tile_feature(*feature);
- MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_POLYGON));
+ /*
+ std::unique_ptr<mapnik::geometry_type> g(new mapnik::geometry_type(mapnik::geometry_type::types::Polygon));
g->move_to(168.267850,-24.576888);
g->line_to(167.982618,-24.697145);
g->line_to(168.114561,-24.783548);
@@ -396,28 +556,481 @@ TEST_CASE( "encoding single line 2", "should maintain start/end vertex" ) {
g->close_path();
//g->push_vertex(256.000000,-0.00000, mapnik::SEG_CLOSE);
// todo - why does shape_io result in on-zero close path x,y?
- backend.add_path(*g, tolerance, g->type());
+ mapnik::vertex_adapter va(*g);
+ backend.add_path(va, tolerance, g->type());
+ */
+ mapnik::geometry::polygon<double> geom;
+ {
+ mapnik::geometry::linear_ring<double> ring;
+ ring.add_coord(168.267850,-24.576888);
+ ring.add_coord(167.982618,-24.697145);
+ ring.add_coord(168.114561,-24.783548);
+ ring.add_coord(168.267850,-24.576888);
+ ring.add_coord(168.267850,-24.576888);
+ geom.set_exterior_ring(std::move(ring));
+ }
+ mapnik::geometry::scale_strategy scale_strat(backend.get_path_multiplier(), 0.5);
+ mapnik::geometry::polygon<std::int64_t> poly = mapnik::geometry::transform<std::int64_t>(geom, scale_strat);
+ std::string foo;
+ mapnik::util::to_wkt(foo, poly);
+ INFO(foo);
+ backend.current_feature_->set_type(vector_tile::Tile_GeomType_POLYGON);
+ backend.add_path(poly);
backend.stop_tile_feature();
backend.stop_tile_layer();
- std::string key("test");
- is_solid_extent(tile,key); // should not throw!
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
CHECK(1 == tile.layers_size());
- mapnik::vector::tile_layer const& layer = tile.layers(0);
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(1 == layer.features_size());
- mapnik::vector::tile_feature const& f = layer.features(0);
- CHECK(7 == f.geometry_size());
+ vector_tile::Tile_Feature const& f = layer.features(0);
+ CHECK(11 == f.geometry_size());
}
-int main (int argc, char* const argv[])
+mapnik::geometry::geometry<double> round_trip(mapnik::geometry::geometry<double> const& geom,
+ double simplify_distance=0.0)
{
- GOOGLE_PROTOBUF_VERIFY_VERSION;
- // set up bbox
- double minx,miny,maxx,maxy;
- mapnik::vector::spherical_mercator merc(256);
- merc.xyz(_x,_y,_z,minx,miny,maxx,maxy);
- bbox.init(minx,miny,maxx,maxy);
- int result = Catch::Session().run( argc, argv );
- if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n");
- google::protobuf::ShutdownProtobufLibrary();
- return result;
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
+ tile_type tile;
+ unsigned path_multiplier = 1000;
+ backend_type backend(tile,path_multiplier);
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-180,-90,180,90);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:4326");
+ mapnik::request m_req(tile_size,tile_size,bbox);
+ renderer_type ren(backend,map,m_req,1,0,0,0);
+ // instead of calling apply, let's cheat and test `handle_geometry` directly by adding features
+ backend.start_tile_layer("layer");
+ mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+ mapnik::projection wgs84("+init=epsg:4326",true);
+ mapnik::projection merc("+init=epsg:4326",true);
+ mapnik::proj_transform prj_trans(merc,wgs84);
+ ren.set_simplify_distance(simplify_distance);
+ ren.handle_geometry(*feature,geom,prj_trans,bbox);
+ backend.stop_tile_layer();
+ if (tile.layers_size() != 1)
+ {
+ std::stringstream s;
+ s << "expected 1 layer in `round_trip` found " << tile.layers_size();
+ throw std::runtime_error(s.str());
+ }
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ if (layer.features_size() != 1)
+ {
+ std::stringstream s;
+ s << "expected 1 feature in `round_trip` found " << layer.features_size();
+ throw std::runtime_error(s.str());
+ }
+ vector_tile::Tile_Feature const& f = layer.features(0);
+ double scale = (double)path_multiplier;
+ return mapnik::vector_tile_impl::decode_geometry(f,0,0,scale,-1*scale);
+}
+
+TEST_CASE( "vector tile point encoding", "should create vector tile with data" ) {
+ mapnik::geometry::point<double> geom(0,0);
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE( "vector tile multi_point encoding of single point", "should create vector tile with data" ) {
+ mapnik::geometry::multi_point<double> geom;
+ geom.emplace_back(0,0);
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POINT(128 -128)" );
+ CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE( "vector tile multi_point encoding of actual multi_point", "should create vector tile with data" ) {
+ mapnik::geometry::multi_point<double> geom;
+ geom.emplace_back(0,0);
+ geom.emplace_back(1,1);
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "MULTIPOINT(128 -128,128.711 -126.578)" );
+ CHECK( new_geom.is<mapnik::geometry::multi_point<double> >() );
+}
+
+TEST_CASE( "vector tile line_string encoding", "should create vector tile with data" ) {
+ mapnik::geometry::line_string<double> geom;
+ geom.add_coord(0,0);
+ geom.add_coord(100,100);
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "LINESTRING(128 -128,192.001 0)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
+}
+
+TEST_CASE( "vector tile multi_line_string encoding of single line_string", "should create vector tile with data" ) {
+ mapnik::geometry::multi_line_string<double> geom;
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(0,0);
+ line.add_coord(100,100);
+ geom.emplace_back(std::move(line));
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "LINESTRING(128 -128,192.001 0)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
+}
+
+TEST_CASE( "vector tile multi_line_string encoding of actual multi_line_string", "should create vector tile with data" ) {
+ mapnik::geometry::multi_line_string<double> geom;
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(0,0);
+ line.add_coord(100,100);
+ geom.emplace_back(std::move(line));
+ mapnik::geometry::line_string<double> line2;
+ line2.add_coord(-10,-0);
+ line2.add_coord(-100,-100);
+ geom.emplace_back(std::move(line2));
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "MULTILINESTRING((128 -128,192.001 0),(120.889 -128,63.288 -256))" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::multi_line_string<double> >() );
+}
+
+
+TEST_CASE( "vector tile polygon encoding", "should create vector tile with data" ) {
+ mapnik::geometry::polygon<double> geom;
+ geom.exterior_ring.add_coord(0,0);
+ geom.exterior_ring.add_coord(0,10);
+ geom.exterior_ring.add_coord(-10,10);
+ geom.exterior_ring.add_coord(-10,0);
+ geom.exterior_ring.add_coord(0,0);
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -113.778,120.889 -113.778,120.889 -128,128 -128,128 -113.778))" );
+}
+
+
+TEST_CASE( "vector tile multi_polygon encoding of single polygon", "should create vector tile with data" ) {
+ mapnik::geometry::polygon<double> poly;
+ poly.exterior_ring.add_coord(0,0);
+ poly.exterior_ring.add_coord(0,10);
+ poly.exterior_ring.add_coord(-10,10);
+ poly.exterior_ring.add_coord(-10,0);
+ poly.exterior_ring.add_coord(0,0);
+ mapnik::geometry::multi_polygon<double> geom;
+ geom.emplace_back(std::move(poly));
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -113.778,120.889 -113.778,120.889 -128,128 -128,128 -113.778))" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE( "vector tile multi_polygon encoding of actual multi_polygon", "should create vector tile with data a multi polygon" ) {
+ mapnik::geometry::multi_polygon<double> geom;
+ mapnik::geometry::polygon<double> poly;
+ poly.exterior_ring.add_coord(0,0);
+ poly.exterior_ring.add_coord(0,10);
+ poly.exterior_ring.add_coord(-10,10);
+ poly.exterior_ring.add_coord(-10,0);
+ poly.exterior_ring.add_coord(0,0);
+ /*
+ // This is an interior ring that touches nothing.
+ poly.interior_rings.emplace_back();
+ poly.interior_rings.back().add_coord(-1,1);
+ poly.interior_rings.back().add_coord(-1,2);
+ poly.interior_rings.back().add_coord(-2,2);
+ poly.interior_rings.back().add_coord(-2,1);
+ poly.interior_rings.back().add_coord(-1,1);
+ // This is an interior ring that touches exterior edge.
+ poly.interior_rings.emplace_back();
+ poly.interior_rings.back().add_coord(-10,7);
+ poly.interior_rings.back().add_coord(-10,5);
+ poly.interior_rings.back().add_coord(-8,5);
+ poly.interior_rings.back().add_coord(-8,7);
+ poly.interior_rings.back().add_coord(-10,7);
+ */
+ geom.emplace_back(std::move(poly));
+ mapnik::geometry::polygon<double> poly2;
+ poly2.exterior_ring.add_coord(11,11);
+ poly2.exterior_ring.add_coord(11,21);
+ poly2.exterior_ring.add_coord(1,21);
+ poly2.exterior_ring.add_coord(1,11);
+ poly2.exterior_ring.add_coord(11,11);
+ geom.emplace_back(std::move(poly2));
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
+}
+
+// simplification
+
+TEST_CASE( "vector tile point correctly passed through simplification code path", "should create vector tile with data" ) {
+ mapnik::geometry::point<double> geom(-122,48);
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom,500);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POINT(41.244 -59.733)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE( "vector tile mulit_point correctly passed through simplification code path", "should create vector tile with data" ) {
+ mapnik::geometry::multi_point<double> geom;
+ geom.emplace_back(-122,48);
+ geom.emplace_back(-123,49);
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom,500);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "MULTIPOINT(41.244 -59.733,40.533 -58.311)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::multi_point<double> >() );
+}
+
+TEST_CASE( "vector tile line_string is simplified", "should create vector tile with data" ) {
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(0,0);
+ line.add_coord(1,1);
+ line.add_coord(2,2);
+ line.add_coord(100,100);
+ mapnik::geometry::geometry<double> new_geom = round_trip(line,500);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "LINESTRING(128 -128,192.001 0)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
+ auto const& line2 = mapnik::util::get<mapnik::geometry::line_string<double> >(new_geom);
+ CHECK( line2.size() == 2 );
+}
+
+TEST_CASE( "vector tile multi_line_string is simplified", "should create vector tile with data" ) {
+ mapnik::geometry::multi_line_string<double> geom;
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(0,0);
+ line.add_coord(1,1);
+ line.add_coord(2,2);
+ line.add_coord(100,100);
+ geom.emplace_back(std::move(line));
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom,500);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "LINESTRING(128 -128,192.001 0)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
+ auto const& line2 = mapnik::util::get<mapnik::geometry::line_string<double> >(new_geom);
+ CHECK( line2.size() == 2 );
+}
+
+TEST_CASE( "vector tile polygon is simplified", "should create vector tile with data" ) {
+ using namespace mapnik::geometry;
+ polygon<double> poly;
+ {
+ linear_ring<double> ring;
+ ring.add_coord(0,0);
+ ring.add_coord(-10,0);
+ ring.add_coord(-10,10);
+ ring.add_coord(0,10);
+ ring.add_coord(0,0);
+ poly.set_exterior_ring(std::move(ring));
+ linear_ring<double> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-7,3);
+ hole.add_coord(-3,3);
+ hole.add_coord(-3,7);
+ hole.add_coord(-7,7);
+ poly.add_hole(std::move(hole));
+ }
+ mapnik::geometry::geometry<double> new_geom = round_trip(poly,500);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -113.778,120.889 -113.778,120.889 -128,128 -128,128 -113.778),(125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044))" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE( "vector tile mulit_polygon is simplified", "should create vector tile with data" ) {
+ using namespace mapnik::geometry;
+ polygon<double> poly;
+ {
+ linear_ring<double> ring;
+ ring.add_coord(0,0);
+ ring.add_coord(-10,0);
+ ring.add_coord(-10,10);
+ ring.add_coord(0,10);
+ ring.add_coord(0,0);
+ poly.set_exterior_ring(std::move(ring));
+ linear_ring<double> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-7,3);
+ hole.add_coord(-3,3);
+ hole.add_coord(-3,7);
+ hole.add_coord(-7,7);
+ poly.add_hole(std::move(hole));
+ }
+ multi_polygon<double> mp;
+ mp.push_back(poly);
+ mapnik::geometry::geometry<double> new_geom = round_trip(mp,500);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -113.778,120.889 -113.778,120.889 -128,128 -128,128 -113.778),(125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044))" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE( "vector tile line_string is simplified when outside bounds", "should create vector tile with data" ) {
+ mapnik::geometry::multi_line_string<double> geom;
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(-10000,0);
+ line.add_coord(-10000.1,0);
+ line.add_coord(100000,0);
+ geom.emplace_back(std::move(line));
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom,100);
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ // yep this test is weird - more of a fuzz than anything
+ CHECK( wkt == "LINESTRING(-7369.526 -128,-7113.526 -128)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
+ auto const& line2 = mapnik::util::get<mapnik::geometry::line_string<double> >(new_geom);
+ CHECK( line2.size() == 2 );
+}
+
+TEST_CASE( "vector tile from simplified geojson", "should create vector tile with data" ) {
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
+ tile_type tile;
+ backend_type backend(tile,1000);
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ mapnik::layer lyr("layer","+init=epsg:4326");
+ // create a datasource with a feature outside the map
+ std::shared_ptr<mapnik::memory_datasource> ds = testing::build_geojson_ds("./test/data/poly.geojson");
+ // but fake the overall envelope to ensure the layer is still processed
+ // and then removed given no intersecting features will be added
+ ds->set_envelope(mapnik::box2d<double>(160.147311,11.047284,160.662858,11.423830));
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+ map.zoom_to_box(bbox);
+ mapnik::request m_req(tile_size,tile_size,bbox);
+ renderer_type ren(backend,map,m_req);
+ ren.apply();
+ CHECK( ren.painted() == true );
+ CHECK(1 == tile.layers_size());
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ CHECK(std::string("layer") == layer.name());
+ CHECK(1 == layer.features_size());
+ vector_tile::Tile_Feature const& f = layer.features(0);
+ unsigned z = 0;
+ unsigned x = 0;
+ unsigned y = 0;
+ double resolution = mapnik::EARTH_CIRCUMFERENCE/(1 << z);
+ double tile_x = -0.5 * mapnik::EARTH_CIRCUMFERENCE + x * resolution;
+ double tile_y = 0.5 * mapnik::EARTH_CIRCUMFERENCE - y * resolution;
+ double scale = (static_cast<double>(layer.extent()) / tile_size) * tile_size/resolution;
+ auto geom = mapnik::vector_tile_impl::decode_geometry(f,tile_x,tile_y,scale,-1*scale);
+
+ unsigned int n_err = 0;
+ mapnik::projection wgs84("+init=epsg:4326",true);
+ mapnik::projection merc("+init=epsg:3857",true);
+ mapnik::proj_transform prj_trans(merc,wgs84);
+ mapnik::geometry::geometry<double> projected_geom = mapnik::geometry::reproject_copy(geom,prj_trans,n_err);
+ CHECK( n_err == 0 );
+ std::string geojson_string;
+ CHECK( mapnik::util::to_geojson(geojson_string,projected_geom) );
+ CHECK( geojson_string == "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[160.42640625,11.4238608092025],[160.41375,11.404562686369],[160.3996875,11.3949131331061],[160.3996875,11.3990486960562],[160.39265625,11.4031841988239],[160.3940625,11.3976701817588],[160.38703125,11.3838846711709],[160.39265625,11.3825060833676],[160.39125,11.3618264654176],[160.3378125,11.3397665531013],[160.3434375,11.3604477708622],[160.26609375,11.3094313929343],[160.28296875,11.3011576095711],[160.29,11.2 [...]
+}
+
+mapnik::geometry::geometry<double> round_trip2(mapnik::geometry::geometry<double> const& geom,
+ double simplify_distance=0.0)
+{
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
+ tile_type tile;
+ backend_type backend(tile,160);
+ unsigned tile_size = 256;
+ mapnik::projection wgs84("+init=epsg:4326",true);
+ mapnik::projection merc("+init=epsg:3857",true);
+ mapnik::proj_transform prj_trans(merc,wgs84);
+ mapnik::box2d<double> bbox(0,0,11.25,11.178401873711785);
+ prj_trans.backward(bbox);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ mapnik::request m_req(tile_size,tile_size,bbox);
+ renderer_type ren(backend,map,m_req,1,0,0,0);
+ // instead of calling apply, let's cheat and test `handle_geometry` directly by adding features
+ backend.start_tile_layer("layer");
+ mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+ ren.set_simplify_distance(simplify_distance);
+ double simp2 = ren.get_simplify_distance();
+ if (simp2 != simplify_distance)
+ {
+ throw std::runtime_error("simplify_distance setter did not work");
+ }
+ ren.handle_geometry(*feature,geom,prj_trans,bbox);
+ backend.stop_tile_layer();
+ if (tile.layers_size() != 1)
+ {
+ throw std::runtime_error("expected 1 layer in `round_trip`");
+ }
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ if (layer.features_size() != 1)
+ {
+ throw std::runtime_error("expected 1 feature in `round_trip`");
+ }
+ vector_tile::Tile_Feature const& f = layer.features(0);
+ unsigned z = 5;
+ unsigned x = 16;
+ unsigned y = 15;
+ double resolution = mapnik::EARTH_CIRCUMFERENCE/(1 << z);
+ double tile_x = -0.5 * mapnik::EARTH_CIRCUMFERENCE + x * resolution;
+ double tile_y = 0.5 * mapnik::EARTH_CIRCUMFERENCE - y * resolution;
+ double scale = (static_cast<double>(layer.extent()) / tile_size) * tile_size/resolution;
+ return mapnik::vector_tile_impl::decode_geometry(f,tile_x,tile_y,scale,-1*scale);
+}
+
+TEST_CASE( "vector tile line_string is verify direction", "should line string with proper directions" ) {
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(-20,2);
+ line.add_coord(2,2);
+ line.add_coord(2,-20);
+ line.add_coord(8,-20);
+ line.add_coord(8,2);
+ line.add_coord(60,2);
+ line.add_coord(60,8);
+ line.add_coord(8,8);
+ line.add_coord(8,60);
+ line.add_coord(2,60);
+ line.add_coord(2,8);
+ line.add_coord(-20,8);
+
+ mapnik::geometry::geometry<double> new_geom = round_trip2(line);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::multi_line_string<double> >() );
+ mapnik::projection wgs84("+init=epsg:4326",true);
+ mapnik::projection merc("+init=epsg:3857",true);
+ mapnik::proj_transform prj_trans(merc,wgs84);
+ mapnik::proj_strategy proj_strat(prj_trans);
+ mapnik::geometry::geometry<double> xgeom = mapnik::geometry::transform<double>(new_geom, proj_strat);
+ std::string wkt;
+ mapnik::util::to_wkt(wkt, xgeom);
+ CHECK( wkt == "MULTILINESTRING((0 1.99992945603165,2.00006103515625 1.99992945603165,2.00006103515625 0),(7.99996948242188 0,7.99996948242188 1.99992945603165,59.9999084472656 1.99992945603165,59.9999084472656 7.99994115658818,7.99996948242188 7.99994115658818,7.99996948242188 59.9998101102059,2.00006103515625 59.9998101102059,2.00006103515625 7.99994115658817,0.0000000000000005 7.99994115658817))" );
+ REQUIRE( !mapnik::geometry::is_empty(xgeom) );
+ REQUIRE( new_geom.is<mapnik::geometry::multi_line_string<double> >() );
+ auto const& line2 = mapnik::util::get<mapnik::geometry::multi_line_string<double> >(new_geom);
+ CHECK( line2.size() == 2 );
}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mapnik-vector-tile.git
More information about the Pkg-grass-devel
mailing list