[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