[mapnik-vector-tile] 01/07: Imported Upstream version 1.0.1+dfsg

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Fri Feb 26 23:10:38 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository mapnik-vector-tile.

commit 79a8908dbfad5fccac094faa7a31e698aa8a95bc
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Fri Feb 26 21:24:56 2016 +0100

    Imported Upstream version 1.0.1+dfsg
---
 .travis.yml                                        |    2 +-
 CHANGELOG.md                                       |   25 +
 Makefile                                           |    6 +-
 ...r.pbf => enf.t5yd5cdi_14_13089_8506.vector.mvt} |  Bin
 ...ctor.pbf => multi_line_13_1310_3166.vector.mvt} |  Bin
 bench/{notes.md => readme.md}                      |   55 +-
 bench/run-bench.sh                                 |   33 +
 bench/vtile-decode.cpp                             |   38 +-
 bench/vtile-encode.cpp                             |  187 +++
 bench/vtile-transform.cpp                          |   61 +-
 bin/vtile-edit.cpp                                 |  134 ++
 examples/c++/Makefile                              |    4 +-
 examples/c++/README.md                             |    2 +-
 examples/c++/tileinfo.cpp                          |    6 +
 ...6331.vector.pbf.z => 14_2620_6331.vector.mvt.z} |  Bin
 ...716_8015.vector.pbf => 14_8716_8015.vector.mvt} |  Bin
 gyp/build.gyp                                      |   36 +-
 package.json                                       |    4 +-
 scripts/build.sh                                   |    4 +-
 src/vector_tile_backend_pbf.cpp                    |    2 -
 src/vector_tile_backend_pbf.hpp                    |   71 -
 src/vector_tile_backend_pbf.ipp                    |  160 --
 src/vector_tile_composite.hpp                      |  105 ++
 src/vector_tile_compression.hpp                    |   58 +-
 src/vector_tile_compression.ipp                    |   33 +-
 src/vector_tile_config.hpp                         |   66 +-
 src/vector_tile_datasource.cpp                     |    2 -
 src/vector_tile_datasource.hpp                     |   52 -
 src/vector_tile_datasource.ipp                     |  328 ----
 src/vector_tile_datasource_pbf.hpp                 |  107 +-
 src/vector_tile_datasource_pbf.ipp                 |  557 +++----
 src/vector_tile_featureset_pbf.cpp                 |    6 +
 src/vector_tile_featureset_pbf.hpp                 |   73 +
 src/vector_tile_featureset_pbf.ipp                 |  325 ++++
 src/vector_tile_geometry_clipper.hpp               |  479 ++++++
 src/vector_tile_geometry_decoder.cpp               |   20 +
 src/vector_tile_geometry_decoder.hpp               |  575 +------
 src/vector_tile_geometry_decoder.ipp               |  902 +++++++++++
 src/vector_tile_geometry_encoder.hpp               |  215 ---
 src/vector_tile_geometry_encoder_pbf.cpp           |    2 +
 src/vector_tile_geometry_encoder_pbf.hpp           |   67 +
 src/vector_tile_geometry_encoder_pbf.ipp           |  307 ++++
 src/vector_tile_geometry_feature.hpp               |   86 ++
 src/vector_tile_geometry_intersects.cpp            |    2 +
 src/vector_tile_geometry_intersects.hpp            |   55 +
 src/vector_tile_geometry_intersects.ipp            |   84 +
 src/vector_tile_geometry_simplifier.hpp            |   99 ++
 src/vector_tile_is_valid.hpp                       |  304 ++++
 src/vector_tile_layer.cpp                          |    2 +
 src/vector_tile_layer.hpp                          |  374 +++++
 src/vector_tile_layer.ipp                          |  131 ++
 src/vector_tile_load_tile.hpp                      |  135 ++
 src/vector_tile_merc_tile.hpp                      |   82 +
 src/vector_tile_processor.cpp                      |    2 -
 src/vector_tile_processor.hpp                      |  220 ++-
 src/vector_tile_processor.ipp                      | 1618 ++++---------------
 src/vector_tile_projection.hpp                     |   50 +-
 src/vector_tile_projection.ipp                     |   42 +-
 src/vector_tile_raster_clipper.cpp                 |    2 +
 src/vector_tile_raster_clipper.hpp                 |  102 ++
 src/vector_tile_raster_clipper.ipp                 |  523 +++++++
 src/vector_tile_strategy.hpp                       |  121 +-
 src/vector_tile_tile.cpp                           |    2 +
 src/vector_tile_tile.hpp                           |  211 +++
 src/vector_tile_tile.ipp                           |  108 ++
 src/vector_tile_util.cpp                           |    2 -
 src/vector_tile_util.hpp                           |   23 -
 src/vector_tile_util.ipp                           |  256 ----
 test/clipper_test.cpp                              |   72 +-
 test/data/0.0.0.vector-b.mvt                       |  Bin 0 -> 2835 bytes
 test/data/0.0.0.vector-b.pbf                       |  Bin 2774 -> 0 bytes
 test/data/{0.0.0.vector.pbf => 0.0.0.vector.mvt}   |  Bin
 ...field.pbf => tile_with_extra_feature_field.mvt} |    0
 ...h_extra_field.pbf => tile_with_extra_field.mvt} |    0
 ...fields.pbf => tile_with_extra_layer_fields.mvt} |    0
 ....pbf => tile_with_invalid_layer_value_type.mvt} |    0
 ...mtype.pbf => tile_with_unexpected_geomtype.mvt} |    0
 test/encoding_util.cpp                             |  131 --
 test/encoding_util.hpp                             |   25 -
 test/fixtures/expected-4.png                       |  Bin 8955 -> 9102 bytes
 test/geometry_encoding.cpp                         |  691 ---------
 test/geometry_visual_test.cpp                      |  184 ++-
 test/raster_tile.cpp                               |  312 ++--
 test/system/encode_and_datasource_decode.cpp       |  119 ++
 test/system/encode_and_decode.cpp                  |  138 ++
 test/system/processor_and_datasource.cpp           |  321 ++++
 test/system/remove_repeated_point.cpp              |   23 +
 test/system/round_trip.cpp                         |  247 +++
 test/system/round_trip_fill_type.cpp               |   65 +
 test/system/round_trip_simplification.cpp          |  144 ++
 test/test_utils.cpp                                |   21 +-
 test/test_utils.hpp                                |    2 +-
 test/unit/composite/vector.cpp                     |   15 +
 test/unit/compression/compression.cpp              |  460 ++++++
 test/unit/datasource-pbf/from_layer.cpp            |  298 ++++
 test/unit/decoding/linestring.cpp                  |  554 +++++++
 test/unit/decoding/point.cpp                       |  365 +++++
 test/unit/decoding/polygon.cpp                     | 1621 ++++++++++++++++++++
 test/unit/decoding/polygon_scaling.cpp             |  471 ++++++
 test/unit/encoding/linestring_pbf.cpp              |  439 ++++++
 test/unit/encoding/point_pbf.cpp                   |  202 +++
 test/unit/encoding/polygon_pbf.cpp                 |  588 +++++++
 test/unit/is_valid/feature_is_valid.cpp            |  134 ++
 test/unit/is_valid/value_is_valid.cpp              |  204 +++
 test/unit/tile_impl/tile.cpp                       |  496 ++++++
 test/utils/decoding_util.cpp                       |   16 +
 test/utils/decoding_util.hpp                       |   10 +
 test/utils/encoding_util.cpp                       |   82 +
 test/utils/encoding_util.hpp                       |   16 +
 test/utils/geom_to_wkt.cpp                         |   17 +
 test/utils/geom_to_wkt.hpp                         |   10 +
 test/utils/geometry_equal.hpp                      |  235 +++
 test/utils/round_trip.cpp                          |   89 ++
 test/utils/round_trip.hpp                          |   15 +
 test/vector_tile.cpp                               | 1326 ++--------------
 test/vector_tile_pbf.cpp                           |  371 +++--
 test/vector_tile_rasterize.cpp                     |  100 +-
 117 files changed, 14189 insertions(+), 6185 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index ca0a2ce..77823e8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -51,4 +51,4 @@ before_script:
 
 script:
  # make sure tileinfo command works
- - ./build/${BUILDTYPE:-Release}/tileinfo examples/data/14_2620_6331.vector.pbf.z
\ No newline at end of file
+ - ./build/${BUILDTYPE:-Release}/tileinfo examples/data/14_2620_6331.vector.mvt.z
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8697358..289e662 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,30 @@
 # Changelog
 
+## 1.0.1
+
+- Updated to protozero v1.3.0
+
+## 1.0.0
+
+Extensive redesign in mapnik-vector-tile to properly support 2.0 of the [Mapbox Vector Tile specification](https://github.com/mapbox/vector-tile-spec/). A large number of changes have occured but a summary of these changes can be described as:
+
+ - Removed `backend_pbf`
+ - Changed `processor` interface
+ - Removed requirement on libprotobuf `Tile` class when using the library.
+ - Created new `tile` and `merc_tile` class 
+ - Added different processing logic for v1 and v2 decoding
+ - Solved several small bugs around decoding and encoding
+ - Added many more exceptions around the processing of invalid tiles.
+ - Added `load_tile` and `composite` headers
+ - Organized tests directory and added many more tests
+ - Removed the concept of `is_solid`
+ - Removed the concept of `path_multiplier`
+ - Fixed bugs in `empty` concept.
+ - `tile_size` is now directly related to the layer `extent`
+ - Encoding no longer allows repeated points in lines and polygons
+ - Corrected issues with winding order being reversed in some situations when decoding polygons
+ - Changed the default configuration values for `processor`
+
 ## 0.14.3
 
 - Fixed compile against latest Mapnik master (variant upgrade)
diff --git a/Makefile b/Makefile
index 9e47cc0..6c436d1 100755
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
 MAPNIK_PLUGINDIR := $(shell mapnik-config --input-plugins)
 BUILDTYPE ?= Release
 
-CLIPPER_REVISION=7484da1
-PROTOZERO_REVISION=v1.0.0
+CLIPPER_REVISION=7484da1e7caf3250aa091925518f4fac91c05784
+PROTOZERO_REVISION=v1.3.0
 GYP_REVISION=3464008
 
 all: libvtile
@@ -16,7 +16,7 @@ all: libvtile
 ./deps/clipper:
 	git clone https://github.com/mapnik/clipper.git -b r496-mapnik ./deps/clipper && cd ./deps/clipper && git checkout $(CLIPPER_REVISION) && ./cpp/fix_members.sh
 
-build/Makefile: ./deps/gyp ./deps/clipper ./deps/protozero gyp/build.gyp test/*cpp
+build/Makefile: ./deps/gyp ./deps/clipper ./deps/protozero gyp/build.gyp test/*
 	deps/gyp/gyp gyp/build.gyp --depth=. -DMAPNIK_PLUGINDIR=\"$(MAPNIK_PLUGINDIR)\" -Goutput_dir=. --generator-output=./build -f make
 
 libvtile: build/Makefile Makefile
diff --git a/bench/enf.t5yd5cdi_14_13089_8506.vector.pbf b/bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
similarity index 100%
rename from bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
rename to bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
diff --git a/bench/multi_line_13_1310_3166.vector.pbf b/bench/multi_line_13_1310_3166.vector.mvt
similarity index 100%
rename from bench/multi_line_13_1310_3166.vector.pbf
rename to bench/multi_line_13_1310_3166.vector.mvt
diff --git a/bench/notes.md b/bench/readme.md
similarity index 72%
rename from bench/notes.md
rename to bench/readme.md
index 7948c7f..1ca057f 100644
--- a/bench/notes.md
+++ b/bench/readme.md
@@ -1,58 +1,71 @@
+### decode benchmark
+
 With reserve
 
 ```
-$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.pbf 13 1310 3166
+$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.mvt 13 1310 3166
 z:13 x:1310 y:3166 iterations:100
 message: zlib compressed
-4026.30ms (cpu 4020.96ms)   | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.pbf
+4026.30ms (cpu 4020.96ms)   | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.mvt
 ```
 
 without reserve code
 
 ```
-$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.pbf 13 1310 3166
+$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.mvt 13 1310 3166
 z:13 x:1310 y:3166 iterations:100
 message: zlib compressed
-4296.88ms (cpu 4289.05ms)   | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.pbf
+4296.88ms (cpu 4289.05ms)   | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.mvt
 ```
-
-
 ---------
 
-
 baseline (using mapnik::geometry::envelope + filter.pass)
 
-$ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.pbf 14 13089 8506 200
+```
+$ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.mvt 14 13089 8506 200
 z:14 x:13089 y:8506 iterations:200
-2822.66ms (cpu 2821.10ms)   | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
-2070.90ms (cpu 2070.44ms)   | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
+2822.66ms (cpu 2821.10ms)   | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
+2070.90ms (cpu 2070.44ms)   | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
 processed 6800 features
+```
 
-$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.pbf 13 1310 3166 100
+```
+$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.mvt 13 1310 3166 100
 z:13 x:1310 y:3166 iterations:100
 message: zlib compressed
-4289.26ms (cpu 4275.29ms)   | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.pbf
-
-
+4289.26ms (cpu 4275.29ms)   | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.mvt
+```
 
 commenting filter.pass/geometry::envelope in datasource_pbf
 
+```
 $ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.pbf 14 13089 8506 200
 z:14 x:13089 y:8506 iterations:200
-2305.45ms (cpu 2301.07ms)   | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
-2142.56ms (cpu 2140.40ms)   | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
+2305.45ms (cpu 2301.07ms)   | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
+2142.56ms (cpu 2140.40ms)   | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
 processed 6800 features
+```
 
 
 with bbox filter:
 
-$ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.pbf 14 13089 8506 200
+```
+$ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.mvt 14 13089 8506 200
 z:14 x:13089 y:8506 iterations:200
-2497.01ms (cpu 2493.40ms)   | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
-1753.55ms (cpu 1751.10ms)   | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
+2497.01ms (cpu 2493.40ms)   | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
+1753.55ms (cpu 1751.10ms)   | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
 processed 6600 features
+```
 
-$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.pbf 13 1310 3166 100
+```
+$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.mvt 13 1310 3166 100
 z:13 x:1310 y:3166 iterations:100
 message: zlib compressed
-4090.67ms (cpu 4083.65ms)   | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.pbf
+4090.67ms (cpu 4083.65ms)   | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.mvt
+```
+
+### encode benchmark:
+
+```
+$ ./build/Release/vtile-encode ./test/data/linestrings_and_point.geojson 0 0 0
+```
diff --git a/bench/run-bench.sh b/bench/run-bench.sh
new file mode 100755
index 0000000..1887a4a
--- /dev/null
+++ b/bench/run-bench.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+# encode geojson
+if [ "$1" == "encode" ] ; then
+	FILE_PATH=../test/geometry-test-data/input/*
+	COMMAND=../build/Release/vtile-encode
+	for f in $FILE_PATH
+	do
+		echo "${f##*/}"
+		for n in 10000 20000 30000 40000 50000 60000 70000 80000 90000 100000
+		do
+			$COMMAND $f 0 0 0 -i $n
+		done
+		echo " "
+		echo " "
+	done
+#decode pbf
+elif [ "$1" == "decode" ] ; then
+	FILE_PATH=../test/data/**.pbf
+	COMMAND=../build/Release/vtile-decode
+	for f in $FILE_PATH
+	do
+		echo "${f##*/}"
+		for n in 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000
+		do
+			$COMMAND $f 0 0 0 $n
+		done
+		echo " "
+		echo " "
+	done
+else
+	echo "Please specify 'encode' or 'decode'"
+fi
\ No newline at end of file
diff --git a/bench/vtile-decode.cpp b/bench/vtile-decode.cpp
index c56314a..ec11da5 100644
--- a/bench/vtile-decode.cpp
+++ b/bench/vtile-decode.cpp
@@ -1,7 +1,6 @@
 #include <mapnik/timer.hpp>
 #include <mapnik/util/file_io.hpp>
 #include "vector_tile_datasource_pbf.hpp"
-#include "vector_tile_datasource.hpp"
 #include "vector_tile_compression.hpp"
 
 #pragma GCC diagnostic push
@@ -21,7 +20,7 @@ int main(int argc, char** argv)
 
         if (argc < 4)
         {
-            std::clog << "usage: vtile-decode /path/to/tile.vector.pbf z x y [iterations]\n";
+            std::clog << "usage: vtile-decode /path/to/tile.vector.mvt z x y [iterations]\n";
             return -1;
         }
         std::string vtile(argv[1]);
@@ -72,45 +71,24 @@ int main(int argc, char** argv)
                 while (tile.next(3)) {
                     ++layer_count;
                     protozero::pbf_reader layer = tile.get_message();
-                    auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(layer,x,y,z,256);
+                    auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(layer,x,y,z);
                     mapnik::query q(ds->get_tile_extent());
                     auto fs = ds->features(q);
+                    if (!fs) continue;
                     while (fs->next()) {
                         ++feature_count;
                     }
                 }
             }
         }
-
-
-        std::size_t feature_count2 = 0;
-        std::size_t layer_count2 = 0;
+        
+        if (feature_count == 0)
         {
-            mapnik::progress_timer __stats__(std::clog, std::string("decode as datasource: ") + vtile);
-            vector_tile::Tile tiledata;
-            tiledata.ParseFromString(message);
-            for (std::size_t i=0;i<iterations;++i)
-            {
-                for (int j=0;j<tiledata.layers_size(); ++j)
-                {
-                    ++layer_count2;
-                    auto const& layer = tiledata.layers(j);
-                    auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource>(layer,x,y,z,256);
-                    mapnik::query q(ds->get_tile_extent());
-                    auto fs = ds->features(q);
-                    while (fs->next()) {
-                        ++feature_count2;
-                    }                    
-                }
-            }
-        }
-        if (feature_count!= feature_count2) {
-            std::clog << "error: tile datasource impl did not return same # of features " << feature_count << " vs " << feature_count2 << "\n";
-            return -1;
-        } else if (feature_count == 0) {
             std::clog << "error: no features processed\n";
             return -1;
-        } else {
+        }
+        else
+        {
             std::clog << "processed " << feature_count << " features\n";
         }
     }
diff --git a/bench/vtile-encode.cpp b/bench/vtile-encode.cpp
new file mode 100644
index 0000000..e3b9657
--- /dev/null
+++ b/bench/vtile-encode.cpp
@@ -0,0 +1,187 @@
+#include <mapnik/timer.hpp>
+#include <mapnik/util/file_io.hpp>
+#include "vector_tile_processor.hpp"
+#include "vector_tile_projection.hpp"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+#include <iostream>
+#include <fstream>
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/datasource_cache.hpp>
+#include <mapnik/global.hpp>
+#include <mapnik/well_known_srs.hpp>
+#include <mapnik/json/geometry_parser.hpp>
+#include <mapnik/memory_datasource.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/map.hpp>
+#include <mapnik/layer.hpp>
+#include <mapnik/request.hpp>
+
+#include <protozero/pbf_reader.hpp>
+
+int main(int argc, char** argv)
+{
+    try
+    {
+        if (argc < 4)
+        {
+            std::clog << "usage: vtile-encode /path/to/geometry.geojson z x y [-i iterations] [-l layer_count] [-o output_file] [-p epsg_code]\n";
+            return -1;
+        }
+        std::string geojson_file(argv[1]);
+
+        int z = std::stoi(argv[2]);
+        int x = std::stoi(argv[3]);
+        int y = std::stoi(argv[4]);
+
+        std::size_t iterations = 1000;
+        std::size_t layer_count = 1;
+        std::size_t layer_extent = 4096;
+        std::string output_path = "";
+        std::string layer_projection = "4326";
+        if (argc > 5)
+        {
+            for (int i = 5; i < argc; i++)
+            {
+                std::string flag = argv[i];
+                if (flag == "-i")
+                {
+                    iterations = std::stoi(argv[i + 1]);
+                }
+                if (flag == "-l")
+                {
+                    layer_count = std::stoi(argv[i + 1]);
+                }
+                if (flag == "-o")
+                {
+                    output_path = argv[i + 1];
+                }
+                if (flag == "-p")
+                {
+                    layer_projection = argv[i + 1];
+                }
+                if (flag == "-e")
+                {
+                    layer_extent = std::stoi(argv[i + 1]);
+                }
+            }
+        }
+
+        std::clog << "z:" << z << " x:" << x << " y:" << y <<  " iterations:" << iterations << std::endl;
+
+        mapnik::datasource_cache::instance().register_datasources(MAPNIK_PLUGINDIR);
+        mapnik::parameters params_1;
+        params_1["type"] = "geojson";
+        params_1["file"] = geojson_file;
+        params_1["cache_features"] = "false";
+        auto ds_1 = mapnik::datasource_cache::instance().create(params_1);
+
+        // Query
+        mapnik::box2d<double> query_ext(std::numeric_limits<double>::min(),
+                                        std::numeric_limits<double>::min(),
+                                        std::numeric_limits<double>::max(),
+                                        std::numeric_limits<double>::max());
+        mapnik::query q(query_ext);
+        auto json_fs = ds_1->features(q);
+
+        // Create memory datasource from geojson
+        mapnik::parameters params;
+        params["type"] = "memory";
+        auto ds = std::make_shared<mapnik::memory_datasource>(params);
+
+        if (!json_fs)
+        {
+            // No features in geojson so lets try to process it differently as
+            // it might be a partial geojson and we can use from_geojson
+            mapnik::util::file input(geojson_file);
+            if (!input.open())
+            {
+                std::clog << "failed to open " << geojson_file << std::endl;
+                return -1;
+            }
+            mapnik::geometry::geometry<double> geom;
+            std::string json_string(input.data().get(), input.size());
+            if (!mapnik::json::from_geojson(json_string, geom))
+            {
+                std::clog << "failed to parse geojson" << std::endl;
+                return -1;
+            }
+            mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
+            mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+            feature->set_geometry(std::move(geom));
+            ds->push(feature);
+        }
+        else
+        {
+            mapnik::feature_ptr feature = json_fs->next();
+        
+            while (feature)
+            {
+                ds->push(feature);
+                feature = json_fs->next();
+            }
+        }
+
+        // Create tile 
+        unsigned tile_size = layer_extent;
+        int buffer_size = 0;
+
+        mapnik::box2d<double> bbox = mapnik::vector_tile_impl::merc_extent(tile_size, x, y, z);
+
+        // Create a fresh map to render into a tile
+        mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+        while (layer_count > 0)
+        {
+            std::string layer_name = "layer" + std::to_string(layer_count);
+            std::string layer_proj = "+init=epsg:" + layer_projection;
+            mapnik::layer lyr(layer_name, layer_proj);
+            lyr.set_datasource(ds);
+            map.add_layer(lyr);
+            --layer_count;
+        }
+        map.zoom_to_box(bbox);
+
+        // Output buffer
+        std::string output_buffer;
+        std::size_t expected_size;
+        {
+            mapnik::vector_tile_impl::tile a_tile(bbox, tile_size, buffer_size);
+            mapnik::vector_tile_impl::processor ren(map);
+            ren.set_simplify_distance(5.0);
+            ren.update_tile(a_tile);
+            a_tile.serialize_to_string(output_buffer);
+            expected_size = a_tile.size();
+        }
+
+        {
+            mapnik::progress_timer __stats__(std::clog, std::string("encode tile: ") + geojson_file);
+            for (std::size_t i = 0; i < iterations; ++i)
+            {
+                mapnik::vector_tile_impl::tile a_tile(bbox, tile_size, buffer_size);
+                mapnik::vector_tile_impl::processor ren(map);
+                ren.set_simplify_distance(5.0);
+                ren.update_tile(a_tile);
+                assert(a_tile.size() == expected_size);
+            }
+        }
+
+        if (output_path != "")
+        {
+            std::ofstream out(output_path);
+            out << output_buffer;
+        }
+    }
+    catch (std::exception const& ex)
+    {
+        std::clog << "error: " << ex.what() << std::endl;
+        return -1;
+    }
+    return 0;
+}
diff --git a/bench/vtile-transform.cpp b/bench/vtile-transform.cpp
index b716cc3..5bc78ba 100644
--- a/bench/vtile-transform.cpp
+++ b/bench/vtile-transform.cpp
@@ -21,15 +21,21 @@
 
 */
 
-int main() {
+int main() 
+{
     mapnik::projection merc("+init=epsg:3857",true);
+    mapnik::projection merc2("+init=epsg:4326",true);
     mapnik::proj_transform prj_trans(merc,merc); // no-op
+    mapnik::proj_transform prj_trans2(merc2,merc); // op
     unsigned tile_size = 256;
     mapnik::vector_tile_impl::spherical_mercator merc_tiler(tile_size);
     double minx,miny,maxx,maxy;
     merc_tiler.xyz(9664,20435,15,minx,miny,maxx,maxy);
     mapnik::box2d<double> z15_extent(minx,miny,maxx,maxy);
-    mapnik::view_transform tr(tile_size,tile_size,z15_extent,0,0);
+    unsigned path_multiplier = 16;
+    mapnik::view_transform tr(tile_size * path_multiplier,
+                              tile_size * path_multiplier,
+                              z15_extent,0,0);
     std::string geojson_file("./test/data/poly.geojson");
     mapnik::util::file input(geojson_file);
     if (!input.open())
@@ -47,8 +53,9 @@ int main() {
     unsigned count = 0;
     unsigned count2 = 0;
     unsigned count3 = 0;
+    unsigned count4 = 0;
     {
-        mapnik::vector_tile_impl::vector_tile_strategy vs(tr, 16);
+        mapnik::vector_tile_impl::vector_tile_strategy vs(tr);
         mapnik::progress_timer __stats__(std::clog, "boost::geometry::transform");
         for (unsigned i=0;i<10000;++i)
         {
@@ -58,17 +65,21 @@ int main() {
         }
     }
     {
-        mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans,tr, 16);
+        mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans,tr);
         mapnik::progress_timer __stats__(std::clog, "transform_visitor with reserve with proj no-op");
         mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
                                        std::numeric_limits<double>::min(),
                                        std::numeric_limits<double>::max(),
                                        std::numeric_limits<double>::max());
-        mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy_proj> transit(vs, clip_extent);
+        mapnik::vector_tile_impl::geom_out_visitor<std::int64_t> out_geom;
+        mapnik::vector_tile_impl::transform_visitor<
+                    mapnik::vector_tile_impl::vector_tile_strategy_proj,
+                    mapnik::vector_tile_impl::geom_out_visitor<std::int64_t>
+                                    > transit(vs, clip_extent, out_geom);
         for (unsigned i=0;i<10000;++i)
         {
-            mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(transit,geom);        
-            auto const& poly = mapnik::util::get<mapnik::geometry::multi_polygon<std::int64_t>>(new_geom);
+            mapnik::util::apply_visitor(transit,geom);        
+            auto const& poly = mapnik::util::get<mapnik::geometry::multi_polygon<std::int64_t>>(out_geom.geom);
             count2 += poly.size();
         }
         if (count != count2)
@@ -78,17 +89,45 @@ int main() {
         }
     }
     {
-        mapnik::vector_tile_impl::vector_tile_strategy vs(tr, 16);
+        mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans2,tr);
+        mapnik::progress_timer __stats__(std::clog, "transform_visitor with reserve with proj op");
+        mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
+                                       std::numeric_limits<double>::min(),
+                                       std::numeric_limits<double>::max(),
+                                       std::numeric_limits<double>::max());
+        mapnik::vector_tile_impl::geom_out_visitor<std::int64_t> out_geom;
+        mapnik::vector_tile_impl::transform_visitor<
+                    mapnik::vector_tile_impl::vector_tile_strategy_proj,
+                    mapnik::vector_tile_impl::geom_out_visitor<std::int64_t>
+                                    > transit(vs, clip_extent, out_geom);
+        for (unsigned i=0;i<10000;++i)
+        {
+            mapnik::util::apply_visitor(transit,geom);        
+            auto const& poly = mapnik::util::get<mapnik::geometry::multi_polygon<std::int64_t>>(out_geom.geom);
+            count4 += poly.size();
+        }
+        if (count != count4)
+        {
+            std::clog << "tests did not run as expected!\n";
+            return -1;
+        }
+    }
+    {
+        mapnik::vector_tile_impl::vector_tile_strategy vs(tr);
         mapnik::progress_timer __stats__(std::clog, "transform_visitor with reserve with no proj function call overhead");
         mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
                                        std::numeric_limits<double>::min(),
                                        std::numeric_limits<double>::max(),
                                        std::numeric_limits<double>::max());
-        mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy> transit(vs, clip_extent);
+        mapnik::vector_tile_impl::geom_out_visitor<std::int64_t> out_geom;
+        mapnik::vector_tile_impl::transform_visitor<
+                    mapnik::vector_tile_impl::vector_tile_strategy,
+                    mapnik::vector_tile_impl::geom_out_visitor<std::int64_t>
+                                    > transit(vs, clip_extent, out_geom);
         for (unsigned i=0;i<10000;++i)
         {
-            mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(transit,geom);
-            auto const& poly = mapnik::util::get<mapnik::geometry::multi_polygon<std::int64_t>>(new_geom);
+            mapnik::util::apply_visitor(transit,geom);
+            auto const& poly = mapnik::util::get<mapnik::geometry::multi_polygon<std::int64_t>>(out_geom.geom);
             count3 += poly.size();
         }
         if (count != count3)
diff --git a/bin/vtile-edit.cpp b/bin/vtile-edit.cpp
new file mode 100644
index 0000000..7b3b54b
--- /dev/null
+++ b/bin/vtile-edit.cpp
@@ -0,0 +1,134 @@
+#include <mapnik/util/file_io.hpp>
+#include "vector_tile.pb.h"
+#include "vector_tile_compression.hpp"
+
+#include <iostream>
+#include <fstream>
+
+#include <protozero/pbf_reader.hpp>
+
+int main(int argc, char** argv)
+{
+    try
+    {
+        if (argc < 2)
+        {
+            std::clog << "usage: vtile-edit /path/to/tile.vector.mvt [--set-version version]" << std::endl;
+            return -1;
+        }
+        std::string vtile(argv[1]);
+        mapnik::util::file input(vtile);
+        if (!input.open())
+        {
+            std::clog << std::string("failed to open ") + vtile << "\n";
+            return -1;
+        }
+
+        unsigned version = 0;
+        bool clean_empty = false;
+        if (argc > 2)
+        {
+            for (int i = 2; i < argc; i++)
+            {
+                std::string flag = argv[i];
+                if (flag == "--set-version")
+                {
+                    version = std::stoi(argv[i + 1]);
+                }
+                if (flag == "--clean-empty")
+                {
+                    clean_empty = true;
+                }
+            }
+        }
+
+        std::string message(input.data().get(), input.size());
+
+        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_tile_impl::zlib_decompress(message,uncompressed);
+            message = uncompressed;
+        }
+
+        vector_tile::Tile tile;
+        tile.ParseFromString(message);
+        
+        for (int j=0;j<tile.layers_size(); ++j)
+        {
+            auto layer = tile.mutable_layers(j);
+            std::clog << "layer: " << layer->name() << std::endl;
+            if (version != 0)
+            {
+                std::clog << "old version: " << layer->version() << std::endl;
+                if (version != layer->version())
+                {
+                    layer->set_version(version);
+                }
+                std::clog << "new version: " << layer->version() << std::endl;
+            }
+            if (clean_empty)
+            {
+                std::clog << "Cleaning empty features" << std::endl;
+                for (int i = 0; i < layer->features_size(); ++i)
+                {
+                    auto feature = layer->mutable_features(i);
+                    if (feature->geometry_size() == 0 && !feature->has_raster())
+                    {
+                        layer->mutable_features()->DeleteSubrange(i,1);
+                        --i;
+                    }
+                }
+                if (layer->features_size() == 0) 
+                {
+                    tile.mutable_layers()->DeleteSubrange(j,1);
+                    --j;
+                }
+            }
+        }
+
+        // Serialize
+        std::string output;
+        tile.SerializeToString(&output);
+
+        // Recompress
+        bool gzip = true;
+        if (is_zlib || is_gzip)
+        {
+            if (is_zlib)
+            {
+                gzip = false;
+                std::cout << "message: zlib compressing\n";
+            }
+            else if (is_gzip)
+            {
+                gzip = true;
+                std::cout << "message: gzip compressing\n";
+            }
+            std::string compressed;
+            mapnik::vector_tile_impl::zlib_compress(output,compressed,gzip);
+            output = compressed;
+        }
+
+        std::ofstream file(vtile);
+        file << output;
+
+        std::clog << "wrote to: " << vtile << std::endl;
+    }
+    catch (std::exception const& ex)
+    {
+        std::clog << "error: " << ex.what() << "\n";
+        return -1;
+    }
+    return 0;
+}
diff --git a/examples/c++/Makefile b/examples/c++/Makefile
index 14f21fd..b9a4e9b 100755
--- a/examples/c++/Makefile
+++ b/examples/c++/Makefile
@@ -14,8 +14,8 @@ install: tileinfo
 	chmod +x /usr/local/bin/tileinfo
 
 test:
-	./tileinfo ../data/14_8716_8015.vector.pbf
-	./tileinfo ../data/14_2620_6331.vector.pbf.z
+	./tileinfo ../data/14_8716_8015.vector.mvt
+	./tileinfo ../data/14_2620_6331.vector.mvt.z
 
 clean:
 	@rm -f ./tileinfo
diff --git a/examples/c++/README.md b/examples/c++/README.md
index 695b826..b415c48 100644
--- a/examples/c++/README.md
+++ b/examples/c++/README.md
@@ -25,4 +25,4 @@ Install these dependencies on OS X:
 
 ## Usage
 
-    tileinfo ../data/14_8716_8015.vector.pbf
+    tileinfo ../data/14_8716_8015.vector.mvt
diff --git a/examples/c++/tileinfo.cpp b/examples/c++/tileinfo.cpp
index fb1e532..b4e3959 100644
--- a/examples/c++/tileinfo.cpp
+++ b/examples/c++/tileinfo.cpp
@@ -1,5 +1,11 @@
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
 #include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
 #include "vector_tile_compression.hpp"
+
 #include <vector>
 #include <iostream>
 #include <fstream>
diff --git a/examples/data/14_2620_6331.vector.pbf.z b/examples/data/14_2620_6331.vector.mvt.z
similarity index 100%
rename from examples/data/14_2620_6331.vector.pbf.z
rename to examples/data/14_2620_6331.vector.mvt.z
diff --git a/examples/data/14_8716_8015.vector.pbf b/examples/data/14_8716_8015.vector.mvt
similarity index 100%
rename from examples/data/14_8716_8015.vector.pbf
rename to examples/data/14_8716_8015.vector.mvt
diff --git a/gyp/build.gyp b/gyp/build.gyp
index 1bfccf6..5e579ea 100644
--- a/gyp/build.gyp
+++ b/gyp/build.gyp
@@ -141,7 +141,9 @@
       ],
       "include_dirs": [
         "../src",
-        '../deps/protozero/include'
+        '../deps/protozero/include',
+        '../test',
+        '../test/utils'
       ]
     },
     {
@@ -177,6 +179,38 @@
       ]
     },
     {
+      "target_name": "vtile-encode",
+      'dependencies': [ 'mapnik_vector_tile_impl' ],
+      "type": "executable",
+      "defines": [
+        "<@(common_defines)",
+        "MAPNIK_PLUGINDIR=<(MAPNIK_PLUGINDIR)"
+      ],
+      "sources": [
+        "../bench/vtile-encode.cpp"
+      ],
+      "include_dirs": [
+        "../src",
+        '../deps/protozero/include'
+      ]
+    },
+    {
+      "target_name": "vtile-edit",
+      'dependencies': [ 'mapnik_vector_tile_impl' ],
+      "type": "executable",
+      "defines": [
+        "<@(common_defines)",
+        "MAPNIK_PLUGINDIR=<(MAPNIK_PLUGINDIR)"
+      ],
+      "sources": [
+        "../bin/vtile-edit.cpp"
+      ],
+      "include_dirs": [
+        "../src",
+        '../deps/protozero/include'
+      ]
+    },
+    {
       "target_name": "tileinfo",
       'dependencies': [ 'vector_tile' ],
       "type": "executable",
diff --git a/package.json b/package.json
index 46244cb..25151c5 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
     "name": "mapnik-vector-tile",
-    "version": "0.14.3",
-    "description": "Mapnik vector tile API",
+    "version": "1.0.1",
+    "description": "Mapnik Vector Tile API",
     "main": "./package.json",
     "repository"   :  {
       "type" : "git",
diff --git a/scripts/build.sh b/scripts/build.sh
index 26410ac..c3ba34b 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -8,6 +8,6 @@ fi
 
 echo $PROJ_LIB
 ls $PROJ_LIB/
-make -j${JOBS} test BUILDTYPE=${BUILDTYPE:-Release} V=1
+make -j${JOBS:-1} test BUILDTYPE=${BUILDTYPE:-Release} V=1
 
-set +e +u
\ No newline at end of file
+set +e +u
diff --git a/src/vector_tile_backend_pbf.cpp b/src/vector_tile_backend_pbf.cpp
deleted file mode 100644
index 4404b3b..0000000
--- a/src/vector_tile_backend_pbf.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#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
deleted file mode 100644
index 510a323..0000000
--- a/src/vector_tile_backend_pbf.hpp
+++ /dev/null
@@ -1,71 +0,0 @@
-#ifndef __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__
-#define __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__
-
-// vector tile
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#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>
-#include <unordered_map>
-
-namespace mapnik
-{
-class feature_impl;
-}
-
-namespace mapnik { namespace vector_tile_impl {
-
-    struct backend_pbf
-    {
-        typedef std::map<std::string, unsigned> keys_container;
-        typedef std::unordered_map<mapnik::value, unsigned> values_container;
-    private:
-        vector_tile::Tile & tile_;
-        unsigned path_multiplier_;
-        mutable vector_tile::Tile_Layer * current_layer_;
-        keys_container keys_;
-        values_container values_;
-        int32_t x_, y_;
-    public:
-        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_;
-        }
-        inline void stop_tile_layer() {}
-
-        template <typename T>
-        inline bool add_path(T const& path)
-        {
-            if (current_feature_)
-            {
-                return encode_geometry(path,
-                                       *current_feature_,
-                                       x_,
-                                       y_);
-            }
-            // no path was added return false
-            return false;
-        }
-    };
-
-}} // 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
deleted file mode 100644
index 70df527..0000000
--- a/src/vector_tile_backend_pbf.ipp
+++ /dev/null
@@ -1,160 +0,0 @@
-// 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"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#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:
-    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),
-      x_(0),
-      y_(0),
-      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_layer_ && current_feature_)
-    {
-        if (!current_feature_->has_raster() && current_feature_->geometry_size() == 0 )
-        {
-            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_.emplace(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_.emplace(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_composite.hpp b/src/vector_tile_composite.hpp
new file mode 100644
index 0000000..aeb82b5
--- /dev/null
+++ b/src/vector_tile_composite.hpp
@@ -0,0 +1,105 @@
+#ifndef __MAPNIK_VECTOR_TILE_COMPOSITE_H__
+#define __MAPNIK_VECTOR_TILE_COMPOSITE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_merc_tile.hpp"
+#include "vector_tile_datasource_pbf.hpp"
+#include "vector_tile_processor.hpp"
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/map.hpp>
+#include <mapnik/layer.hpp>
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+void composite(merc_tile & target_vt,
+               std::vector<merc_tile_ptr> const& vtiles,
+               mapnik::Map & map,
+               processor & ren, // processor should be on map object provided
+               double scale_denominator = 0.0,
+               unsigned offset_x = 0,
+               unsigned offset_y = 0,
+               bool reencode = false)
+{
+    if (target_vt.tile_size() <= 0)
+    {
+        throw std::runtime_error("Vector tile size must be great than zero");
+    }
+
+    for (auto const& vt : vtiles)
+    {
+        if (vt->tile_size() <= 0)
+        {
+            throw std::runtime_error("Vector tile size must be great than zero");
+        }
+
+        if (vt->is_empty())
+        {
+            continue;
+        }
+
+        bool reencode_tile = reencode;
+        if (!reencode_tile && !target_vt.same_extent(*vt))
+        {
+            reencode_tile = true;
+        }
+        
+        protozero::pbf_reader tile_message(vt->get_reader());
+        // loop through the layers of the tile!
+        while (tile_message.next(Tile_Encoding::LAYERS))
+        {
+            bool reencode_layer = reencode_tile;
+            auto data_pair = tile_message.get_data();
+            protozero::pbf_reader layer_message(data_pair);
+            if (!layer_message.next(Layer_Encoding::NAME))
+            {
+                continue;
+            }
+
+            std::string layer_name = layer_message.get_string();
+            
+            if (!reencode_layer)
+            {
+                target_vt.append_layer_buffer(data_pair.first, data_pair.second, layer_name);
+            }
+            
+            if (target_vt.has_layer(layer_name))
+            {
+                continue;
+            }
+
+            protozero::pbf_reader layer_pbf(data_pair);
+            auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(
+                        layer_pbf,
+                        vt->x(),
+                        vt->y(),
+                        vt->z(),
+                        true);
+            mapnik::layer lyr(layer_name,"+init=epsg:3857");
+            ds->set_envelope(vt->get_buffered_extent());
+            lyr.set_datasource(ds);
+            map.add_layer(lyr);
+        }
+    }
+    if (!map.layers().empty())
+    {
+        ren.update_tile(target_vt,
+                        scale_denominator,
+                        offset_x,
+                        offset_y);
+    }
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_COMPOSITE_H__
diff --git a/src/vector_tile_compression.hpp b/src/vector_tile_compression.hpp
index 65205de..0d65b05 100644
--- a/src/vector_tile_compression.hpp
+++ b/src/vector_tile_compression.hpp
@@ -1,20 +1,43 @@
 #ifndef __MAPNIK_VECTOR_TILE_COMPRESSION_H__
 #define __MAPNIK_VECTOR_TILE_COMPRESSION_H__
 
-#include <string>
+// mapnik-vector-tile
 #include "vector_tile_config.hpp"
+
+// zlib
 #include <zlib.h>
 
-namespace mapnik { namespace vector_tile_impl {
+// std
+#include <string>
+
+namespace mapnik 
+{
+ 
+namespace vector_tile_impl 
+{
 
 inline bool is_zlib_compressed(const char * data, std::size_t size)
 {
-    return size > 2 && static_cast<uint8_t>(data[0]) == 0x78 && static_cast<uint8_t>(data[1]) == 0x9C;
+    return size > 2 && 
+           static_cast<uint8_t>(data[0]) == 0x78 && 
+           (
+               static_cast<uint8_t>(data[1]) == 0x9C || 
+               static_cast<uint8_t>(data[1]) == 0x01 || 
+               static_cast<uint8_t>(data[1]) == 0xDA || 
+               static_cast<uint8_t>(data[1]) == 0x5E
+           );
 }
 
 inline bool is_zlib_compressed(std::string const& data)
 {
-    return data.size() > 2 && static_cast<uint8_t>(data[0]) == 0x78 && static_cast<uint8_t>(data[1]) == 0x9C;
+    return data.size() > 2 && 
+           static_cast<uint8_t>(data[0]) == 0x78 && 
+           (
+               static_cast<uint8_t>(data[1]) == 0x9C || 
+               static_cast<uint8_t>(data[1]) == 0x01 || 
+               static_cast<uint8_t>(data[1]) == 0xDA || 
+               static_cast<uint8_t>(data[1]) == 0x5E
+           );
 }
 
 inline bool is_gzip_compressed(const char * data, std::size_t size)
@@ -29,12 +52,29 @@ inline bool is_gzip_compressed(std::string const& data)
 
 // 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, int level=Z_DEFAULT_COMPRESSION, int strategy=Z_DEFAULT_STRATEGY);
-MAPNIK_VECTOR_INLINE void zlib_decompress(const char * data, std::size_t size, std::string & output);
-MAPNIK_VECTOR_INLINE void zlib_compress(const char * data, std::size_t size, std::string & output, bool gzip=true, int level=Z_DEFAULT_COMPRESSION, int strategy=Z_DEFAULT_STRATEGY);
+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, 
+                                        int level=Z_DEFAULT_COMPRESSION, 
+                                        int strategy=Z_DEFAULT_STRATEGY);
+
+MAPNIK_VECTOR_INLINE void zlib_decompress(const char * data, 
+                                          std::size_t size, 
+                                          std::string & output);
+
+MAPNIK_VECTOR_INLINE void zlib_compress(const char * data, 
+                                        std::size_t size, 
+                                        std::string & output, 
+                                        bool gzip=true, 
+                                        int level=Z_DEFAULT_COMPRESSION, 
+                                        int strategy=Z_DEFAULT_STRATEGY);
+
+} // end ns vector_tile_impl
 
-}} // end ns
+} // end ns mapnik
 
 #if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
 #include "vector_tile_compression.ipp"
diff --git a/src/vector_tile_compression.ipp b/src/vector_tile_compression.ipp
index 75fd369..0e2d31e 100644
--- a/src/vector_tile_compression.ipp
+++ b/src/vector_tile_compression.ipp
@@ -1,7 +1,14 @@
-#include <stdexcept>
+// zlib
 #include <zlib.h>
 
-namespace mapnik { namespace vector_tile_impl {
+// std
+#include <stdexcept>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl 
+{
 
 // decodes both zlib and gzip
 // http://stackoverflow.com/a/1838702/2333354
@@ -22,8 +29,11 @@ void zlib_decompress(const char * data, std::size_t size, std::string & output)
         inflate_s.avail_out = 2 * 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);
+        if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR)
+        {
+            std::string error_msg = inflate_s.msg;
+            inflateEnd(&inflate_s);
+            throw std::runtime_error(error_msg);
         }
 
         length += (2 * size - inflate_s.avail_out);
@@ -52,7 +62,7 @@ void zlib_compress(const char * data, std::size_t size, std::string & output, bo
     }
     if (deflateInit2(&deflate_s, level, Z_DEFLATED, windowsBits, 8, strategy) != Z_OK)
     {
-        throw std::runtime_error("deflate failed");
+        throw std::runtime_error("deflate init failed");
     }
     deflate_s.next_in = (Bytef *)data;
     deflate_s.avail_in = size;
@@ -62,10 +72,11 @@ void zlib_compress(const char * data, std::size_t size, std::string & output, bo
         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);
-        }
+        // From http://www.zlib.net/zlib_how.html
+        // "deflate() has a return value that can indicate errors, yet we do not check it here. 
+        // Why not? Well, it turns out that deflate() can do no wrong here."
+        // Basically only possible error is from deflateInit not working properly
+        deflate(&deflate_s, Z_FINISH);
         length += (increase - deflate_s.avail_out);
     } while (deflate_s.avail_out == 0);
     deflateEnd(&deflate_s);
@@ -77,4 +88,6 @@ void zlib_compress(std::string const& input, std::string & output, bool gzip, in
     zlib_compress(input.data(),input.size(),output,gzip,level,strategy);
 }
 
-}}
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_config.hpp b/src/vector_tile_config.hpp
index f1a56c0..2c586c9 100644
--- a/src/vector_tile_config.hpp
+++ b/src/vector_tile_config.hpp
@@ -7,5 +7,69 @@
 #define MAPNIK_VECTOR_INLINE
 #endif
 
+#include <protozero/types.hpp>
 
-#endif // __MAPNIK_VECTOR_TILE_CONFIG_H__
\ No newline at end of file
+#define VT_LEGACY_IMAGE_SIZE 256.0
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+enum Tile_Encoding : protozero::pbf_tag_type
+{
+    LAYERS = 3
+};
+
+enum Layer_Encoding : protozero::pbf_tag_type
+{
+    VERSION = 15,
+    NAME = 1,
+    FEATURES = 2,
+    KEYS = 3,
+    VALUES = 4,
+    EXTENT = 5
+};
+
+enum Feature_Encoding : protozero::pbf_tag_type
+{
+    ID = 1,
+    TAGS = 2,
+    TYPE = 3,
+    GEOMETRY = 4,
+    RASTER = 5
+};
+
+enum Value_Encoding : protozero::pbf_tag_type
+{
+    STRING = 1,
+    FLOAT = 2,
+    DOUBLE = 3,
+    INT = 4,
+    UINT = 5,
+    SINT = 6,
+    BOOL = 7
+};
+
+enum Geometry_Type : std::uint8_t
+{
+    UNKNOWN = 0,
+    POINT = 1,
+    LINESTRING = 2,
+    POLYGON = 3
+};
+
+enum polygon_fill_type : std::uint8_t {
+    even_odd_fill = 0, 
+    non_zero_fill, 
+    positive_fill, 
+    negative_fill,
+    polygon_fill_type_max
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_CONFIG_H__
diff --git a/src/vector_tile_datasource.cpp b/src/vector_tile_datasource.cpp
deleted file mode 100644
index 9782880..0000000
--- a/src/vector_tile_datasource.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#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
deleted file mode 100644
index 60d5d07..0000000
--- a/src/vector_tile_datasource.hpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#ifndef __MAPNIK_VECTOR_TILE_DATASOURCE_H__
-#define __MAPNIK_VECTOR_TILE_DATASOURCE_H__
-
-#include <mapnik/datasource.hpp>
-#include <mapnik/box2d.hpp>
-#include "vector_tile_config.hpp"
-
-namespace vector_tile {
-    class Tile_Layer;
-}
-
-namespace mapnik { namespace vector_tile_impl {
-
-    class tile_datasource : public datasource
-    {
-    public:
-        tile_datasource(vector_tile::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<datasource_geometry_t> get_geometry_type() const;
-        layer_descriptor get_descriptor() const;
-    private:
-        mutable mapnik::layer_descriptor desc_;
-        mutable bool attributes_added_;
-        vector_tile::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_;
-    };
-
-}} // 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.ipp b/src/vector_tile_datasource.ipp
deleted file mode 100644
index acd297d..0000000
--- a/src/vector_tile_datasource.ipp
+++ /dev/null
@@ -1,328 +0,0 @@
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
-
-#include "vector_tile_projection.hpp"
-#include "vector_tile_geometry_decoder.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.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 <unicode/unistr.h>
-
-#if defined(DEBUG)
-#include <mapnik/debug.hpp>
-#endif
-
-namespace mapnik { namespace vector_tile_impl {
-
-    void add_attributes(mapnik::feature_ptr feature,
-                        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());
-        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))
-                {
-                    vector_tile::Tile_Value const& value = layer.values(key_value);
-                    if (value.has_string_value())
-                    {
-                        std::string const& 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,
-                        mapnik::box2d<double> const& unbuffered_query,
-                        std::set<std::string> const& attribute_names,
-                        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),
-              scale_(scale),
-              itr_(0),
-              end_(layer_.features_size()),
-              tr_("utf-8"),
-              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();
-            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() {}
-
-        feature_ptr next()
-        {
-            while (itr_ < end_)
-            {
-                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();
-                    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)
-                        {
-                            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 )
-                            {
-                                // 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 (f.geometry_size() <= 0)
-                {
-                    continue;
-                }
-                mapnik::vector_tile_impl::Geometry<double> geoms(f,tile_x_, tile_y_, scale_, -1*scale_);
-                mapnik::geometry::geometry<double> geom = decode_geometry<double>(geoms, f.type(), filter_.box_);
-                if (geom.is<mapnik::geometry::geometry_empty>())
-                {
-                    continue;
-                }
-                #if defined(DEBUG)
-                mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
-                if (!filter_.pass(envelope))
-                {
-                    MAPNIK_LOG_ERROR(tile_datasource_pbf) << "tile_datasource: filter:pass should not get here";
-                    continue;
-                }
-                #endif
-                mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
-                feature->set_geometry(std::move(geom));
-                add_attributes(feature,f,layer_,tr_);
-                return feature;
-            }
-            return feature_ptr();
-        }
-
-    private:
-        Filter filter_;
-        mapnik::box2d<double> tile_extent_;
-        mapnik::box2d<double> unbuffered_query_;
-        vector_tile::Tile_Layer const& layer_;
-        double tile_x_;
-        double tile_y_;
-        double scale_;
-        unsigned itr_;
-        unsigned end_;
-        mapnik::transcoder tr_;
-        mapnik::context_ptr ctx_;
-    };
-
-    // tile_datasource impl
-    tile_datasource::tile_datasource(vector_tile::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;
-    }
-
-    tile_datasource::~tile_datasource() {}
-
-    datasource::datasource_t tile_datasource::type() const
-    {
-        return datasource::Vector;
-    }
-
-    featureset_ptr tile_datasource::features(query const& q) const
-    {
-        mapnik::filter_in_box filter(q.get_bbox());
-        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_);
-    }
-
-    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 std::make_shared<tile_featureset<filter_at_point> >
-            (filter, get_tile_extent(), get_tile_extent(), names, layer_, tile_x_, tile_y_, scale_);
-    }
-
-    void tile_datasource::set_envelope(box2d<double> const& bbox)
-    {
-        extent_initialized_ = true;
-        extent_ = bbox;
-    }
-
-    box2d<double> tile_datasource::get_tile_extent() const
-    {
-        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);
-    }
-
-    box2d<double> tile_datasource::envelope() const
-    {
-        if (!extent_initialized_)
-        {
-            extent_ = get_tile_extent();
-            extent_initialized_ = true;
-        }
-        return extent_;
-    }
-
-    boost::optional<mapnik::datasource_geometry_t> tile_datasource::get_geometry_type() const
-    {
-        return mapnik::datasource_geometry_t::Collection;
-    }
-
-    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
diff --git a/src/vector_tile_datasource_pbf.hpp b/src/vector_tile_datasource_pbf.hpp
index 89024db..be5b144 100644
--- a/src/vector_tile_datasource_pbf.hpp
+++ b/src/vector_tile_datasource_pbf.hpp
@@ -1,56 +1,69 @@
 #ifndef __MAPNIK_VECTOR_TILE_DATASOURCE_PBF_H__
 #define __MAPNIK_VECTOR_TILE_DATASOURCE_PBF_H__
 
-#include <mapnik/datasource.hpp>
+// mapnik
 #include <mapnik/box2d.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/util/variant.hpp>
+
+// protozero
 #include <protozero/pbf_reader.hpp>
 
-namespace mapnik { namespace vector_tile_impl {
-
-    // TODO: consider using mapnik::value here instead
-    using pbf_attr_value_type = mapnik::util::variant<std::string, float, double, int64_t, uint64_t, bool>;
-    using layer_pbf_attr_type = std::vector<pbf_attr_value_type>;
-
-    class tile_datasource_pbf : public datasource
-    {
-    public:
-        tile_datasource_pbf(protozero::pbf_reader const& layer,
-                        unsigned x,
-                        unsigned y,
-                        unsigned z,
-                        unsigned tile_size);
-        virtual ~tile_datasource_pbf();
-        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<datasource_geometry_t> get_geometry_type() const;
-        layer_descriptor get_descriptor() const;
-        std::string const& get_name() { return name_; }
-    private:
-        mutable mapnik::layer_descriptor desc_;
-        mutable bool attributes_added_;
-        protozero::pbf_reader 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_;
-        uint32_t layer_extent_;
-
-        std::string name_;
-        std::vector<protozero::pbf_reader> features_;
-        std::vector<std::string> layer_keys_;
-        layer_pbf_attr_type layer_values_;
-    };
-
-}} // end ns
+namespace mapnik
+{
+    
+namespace vector_tile_impl
+{
+
+// TODO: consider using mapnik::value here instead
+using pbf_attr_value_type = mapnik::util::variant<std::string, float, double, int64_t, uint64_t, bool>;
+using layer_pbf_attr_type = std::vector<pbf_attr_value_type>;
+
+class tile_datasource_pbf : public datasource
+{
+public:
+    tile_datasource_pbf(protozero::pbf_reader const& layer,
+                    unsigned x,
+                    unsigned y,
+                    unsigned z,
+                    bool use_tile_extent = false);
+    virtual ~tile_datasource_pbf();
+    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<datasource_geometry_t> get_geometry_type() const;
+    layer_descriptor get_descriptor() const;
+    std::string const& get_name() { return name_; }
+    std::uint32_t get_layer_extent() { return tile_size_; }
+private:
+    mutable mapnik::layer_descriptor desc_;
+    mutable bool attributes_added_;
+    mutable bool valid_layer_;
+    protozero::pbf_reader layer_;
+    unsigned x_;
+    unsigned y_;
+    unsigned z_;
+    std::uint32_t tile_size_;
+    mutable bool extent_initialized_;
+    mutable mapnik::box2d<double> extent_;
+    double tile_x_;
+    double tile_y_;
+    double scale_;
+    uint32_t version_;
+    datasource::datasource_t type_;
+
+    std::string name_;
+    std::vector<protozero::pbf_reader> features_;
+    std::vector<std::string> layer_keys_;
+    layer_pbf_attr_type layer_values_;
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
 
 #if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
 #include "vector_tile_datasource_pbf.ipp"
diff --git a/src/vector_tile_datasource_pbf.ipp b/src/vector_tile_datasource_pbf.ipp
index e72d179..124c3db 100644
--- a/src/vector_tile_datasource_pbf.ipp
+++ b/src/vector_tile_datasource_pbf.ipp
@@ -1,421 +1,260 @@
+// mapnik-vector-tile
+#include "vector_tile_featureset_pbf.hpp"
 #include "vector_tile_projection.hpp"
-#include "vector_tile_geometry_decoder.hpp"
+#include "vector_tile_config.hpp"
 
+// mapnik
 #include <mapnik/box2d.hpp>
 #include <mapnik/coord.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/feature.hpp>
 #include <mapnik/feature_layer_desc.hpp>
-#include <mapnik/geometry.hpp>
-#include <mapnik/params.hpp>
+#include <mapnik/geom_util.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.hpp>
-#include <mapnik/image_reader.hpp>
-#include <mapnik/raster.hpp>
-#include <mapnik/view_transform.hpp>
-#include <mapnik/util/variant.hpp>
 
+// protozero
+#include <protozero/pbf_reader.hpp>
+
+// boost
+#include <boost/optional.hpp>
+
+// std
 #include <memory>
 #include <stdexcept>
 #include <string>
 
-#include <protozero/pbf_reader.hpp>
+namespace mapnik 
+{ 
 
-#include <boost/optional.hpp>
-#include <unicode/unistr.h>
+namespace vector_tile_impl 
+{
+
+// tile_datasource impl
+tile_datasource_pbf::tile_datasource_pbf(protozero::pbf_reader const& layer,
+                                         unsigned x,
+                                         unsigned y,
+                                         unsigned z,
+                                         bool use_tile_extent)
+    : datasource(parameters()),
+      desc_("in-memory PBF encoded datasource","utf-8"),
+      attributes_added_(false),
+      valid_layer_(true),
+      layer_(layer),
+      x_(x),
+      y_(y),
+      z_(z),
+      tile_size_(4096), // default for version 1
+      extent_initialized_(false),
+      tile_x_(0.0),
+      tile_y_(0.0),
+      scale_(0.0),
+      version_(1), // Version == 1 is the default because it was not required until v2 to have this field
+      type_(datasource::Vector)
+{
+    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;
 
-#if defined(DEBUG)
-#include <mapnik/debug.hpp>
-#endif
+    protozero::pbf_reader val_msg;
 
-namespace mapnik { namespace vector_tile_impl {
+    // Check that the required fields exist for version 2 of the MVT specification
+    bool has_name = false;
+    bool has_extent = false;
 
-    template <typename Filter>
-    class tile_featureset_pbf : public Featureset
+    while (layer_.next())
     {
-    public:
-        tile_featureset_pbf(Filter const& filter,
-                        mapnik::box2d<double> const& tile_extent,
-                        mapnik::box2d<double> const& unbuffered_query,
-                        std::set<std::string> const& attribute_names,
-                        std::vector<protozero::pbf_reader> const& features,
-                        double tile_x,
-                        double tile_y,
-                        double scale,
-                        std::vector<std::string> const& layer_keys,
-                        layer_pbf_attr_type const& layer_values)
-            : filter_(filter),
-              tile_extent_(tile_extent),
-              unbuffered_query_(unbuffered_query),
-              features_(features),
-              layer_keys_(layer_keys),
-              layer_values_(layer_values),
-              num_keys_(layer_keys_.size()),
-              num_values_(layer_values_.size()),
-              tile_x_(tile_x),
-              tile_y_(tile_y),
-              scale_(scale),
-              itr_(0),
-              tr_("utf-8"),
-              ctx_(std::make_shared<mapnik::context_type>())
+        switch(layer_.tag())
         {
-            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 (auto const& key : layer_keys_)
+            case Layer_Encoding::NAME:
+                name_ = layer_.get_string();
+                has_name = true;
+                break;
+            case Layer_Encoding::FEATURES:
                 {
-                    if (key == *pos)
+                    auto data_pair = layer_.get_data();
+                    protozero::pbf_reader check_feature(data_pair);
+                    while (check_feature.next(Feature_Encoding::RASTER))
                     {
-                        ctx_->push(*pos);
-                        break;
+                        type_ = datasource::Raster;
+                        check_feature.skip();
                     }
+                    protozero::pbf_reader f_msg(data_pair);
+                    features_.push_back(f_msg);
                 }
-            }
-        }
-
-        virtual ~tile_featureset_pbf() {}
-
-        feature_ptr next()
-        {
-            while ( itr_ < features_.size() )
-            {
-                protozero::pbf_reader f = features_.at(itr_);
-                // TODO: auto-increment feature id counter here
-                mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,itr_);
-
-                ++itr_;
-                int32_t geometry_type = 0; // vector_tile::Tile_GeomType_UNKNOWN
-                while (f.next())
+                break;
+            case Layer_Encoding::KEYS:
+                layer_keys_.push_back(layer_.get_string());
+                break;
+            case Layer_Encoding::VALUES:
+                val_msg = layer_.get_message();
+                while (val_msg.next())
                 {
-                    switch(f.tag())
-                    {
-                        case 1:
-                            feature->set_id(f.get_uint64());
+                    switch(val_msg.tag()) {
+                        case Value_Encoding::STRING:
+                            layer_values_.push_back(val_msg.get_string());
                             break;
-                        case 2:
-                            {
-                                auto tag_iterator = f.get_packed_uint32();
-                                for (auto _i = tag_iterator.first; _i != tag_iterator.second;)
-                                {
-                                    std::size_t key_name = *(_i++);
-                                    assert(_i != tag_iterator.second);
-                                    std::size_t key_value = *(_i++);
-                                    if (key_name < num_keys_
-                                        && key_value < num_values_)
-                                    {
-                                        std::string const& name = layer_keys_.at(key_name);
-                                        if (feature->has_key(name))
-                                        {
-                                            pbf_attr_value_type val = layer_values_.at(key_value);
-                                            if (val.is<std::string>())
-                                            {
-                                                feature->put(name, tr_.transcode(val.get<std::string>().data(), val.get<std::string>().length()));
-                                            }
-                                            else if (val.is<bool>())
-                                            {
-                                                feature->put(name, static_cast<mapnik::value_bool>(val.get<bool>()));
-                                            }
-                                            else if (val.is<int64_t>())
-                                            {
-                                                feature->put(name, static_cast<mapnik::value_integer>(val.get<int64_t>()));
-                                            }
-                                            else if (val.is<uint64_t>())
-                                            {
-                                                feature->put(name, static_cast<mapnik::value_integer>(val.get<uint64_t>()));
-                                            }
-                                            else if (val.is<double>())
-                                            {
-                                                feature->put(name, static_cast<mapnik::value_double>(val.get<double>()));
-                                            }
-                                            else if (val.is<float>())
-                                            {
-                                                feature->put(name, static_cast<mapnik::value_double>(val.get<float>()));
-                                            }
-                                            else
-                                            {
-                                                throw std::runtime_error("unknown attribute type while reading feature");
-                                            }
-                                        }
-                                    }
-                                }
-                            }
+                        case Value_Encoding::FLOAT:
+                            layer_values_.push_back(val_msg.get_float());
                             break;
-                        case 3:
-                            geometry_type = f.get_enum();
-                            switch (geometry_type)
-                            {
-                              case 1: //vector_tile::Tile_GeomType_POINT
-                              case 2: // vector_tile::Tile_GeomType_LINESTRING
-                              case 3: // vector_tile::Tile_GeomType_POLYGON
-                                break;
-                              default:  // vector_tile::Tile_GeomType_UNKNOWN or any other value
-                                throw std::runtime_error("unknown geometry type " + std::to_string(geometry_type) + " in feature");
-                            }
+                        case Value_Encoding::DOUBLE:
+                            layer_values_.push_back(val_msg.get_double());
                             break;
-                        case 5:
-                            {
-                            auto image_buffer = f.get_data();
-                            std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.first, image_buffer.second));
-                            if (reader.get())
-                            {
-                                int image_width = reader->width();
-                                int image_height = reader->height();
-                                if (image_width > 0 && image_height > 0)
-                                {
-                                    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 )
-                                    {
-                                        // 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
-                                                                      );
-                                        feature->set_raster(raster);
-                                        return feature;
-                                    }
-                                }
-                            }
-                          }
+                        case Value_Encoding::INT:
+                            layer_values_.push_back(val_msg.get_int64());
+                            break;
+                        case Value_Encoding::UINT:
+                            layer_values_.push_back(val_msg.get_uint64());
                             break;
-                        case 4:
-                            {
-                                auto geom_itr = f.get_packed_uint32();
-                                mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x_,tile_y_,scale_,-1*scale_);
-                                mapnik::geometry::geometry<double> geom = decode_geometry<double>(geoms, geometry_type, filter_.box_);
-                                if (geom.is<mapnik::geometry::geometry_empty>())
-                                {
-                                    continue;
-                                }
-                                #if defined(DEBUG)
-                                mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
-                                if (!filter_.pass(envelope))
-                                {
-                                    MAPNIK_LOG_ERROR(tile_datasource_pbf) << "tile_datasource_pbf: filter:pass should not get here";
-                                    continue;
-                                }
-                                #endif
-                                feature->set_geometry(std::move(geom));
-                                return feature;
-                            }
+                        case Value_Encoding::SINT:
+                            layer_values_.push_back(val_msg.get_sint64());
+                            break;
+                        case Value_Encoding::BOOL:
+                            layer_values_.push_back(val_msg.get_bool());
                             break;
                         default:
-                            // NOTE:  The vector_tile.proto file technically allows for extension fields
-                            //        of values 16 to max here.  Technically, we should just skip() those
-                            //        fields.
-                            //        However, if we're fed a corrupt file (or random data), we don't
-                            //        want to just blindly follow the bytes, so we have made the decision
-                            //        to abort cleanly, rather than doing GIGO.
-                            throw std::runtime_error("unknown field type " + std::to_string(f.tag()) +" in feature");
-
+                            throw std::runtime_error("unknown Value type " + std::to_string(layer_.tag()) + " in layer.values");
                     }
                 }
-            }
-            return feature_ptr();
+                break;
+            case Layer_Encoding::EXTENT:
+                tile_size_ = layer_.get_uint32();
+                has_extent = true;
+                break;
+            case Layer_Encoding::VERSION:
+                version_ = layer_.get_uint32();
+                break;
+            default:
+                throw std::runtime_error("unknown field type " + std::to_string(layer_.tag()) + " in layer");
         }
-
-    private:
-        Filter filter_;
-        mapnik::box2d<double> tile_extent_;
-        mapnik::box2d<double> unbuffered_query_;
-        std::vector<protozero::pbf_reader> const& features_;
-        std::vector<std::string> const& layer_keys_;
-        layer_pbf_attr_type const& layer_values_;
-        std::size_t num_keys_;
-        std::size_t num_values_;
-
-        double tile_x_;
-        double tile_y_;
-        double scale_;
-        unsigned itr_;
-        mapnik::transcoder tr_;
-        mapnik::context_ptr ctx_;
-
-    };
-
-    // tile_datasource impl
-    tile_datasource_pbf::tile_datasource_pbf(protozero::pbf_reader const& layer,
-                                     unsigned x,
-                                     unsigned y,
-                                     unsigned z,
-                                     unsigned tile_size)
-        : datasource(parameters()),
-          desc_("in-memory PBF encoded datasource","utf-8"),
-          attributes_added_(false),
-          layer_(layer),
-          x_(x),
-          y_(y),
-          z_(z),
-          tile_size_(tile_size),
-          extent_initialized_(false),
-          tile_x_(0.0),
-          tile_y_(0.0),
-          scale_(0.0),
-          layer_extent_(0)
+    }
+    if (version_ == 2)
     {
-        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;
-
-        protozero::pbf_reader val_msg;
-
-        while (layer_.next())
+        // Check that all required
+        if (!has_name)
         {
-            switch(layer_.tag())
-            {
-                case 1:
-                    name_ = layer_.get_string();
-                    break;
-                case 2:
-                    features_.push_back(layer_.get_message());
-                    break;
-                case 3:
-                    layer_keys_.push_back(layer_.get_string());
-                    break;
-                case 4:
-                    val_msg = layer_.get_message();
-                    while (val_msg.next())
-                    {
-                        switch(val_msg.tag()) {
-                            case 1:
-                                layer_values_.push_back(val_msg.get_string());
-                                break;
-                            case 2:
-                                layer_values_.push_back(val_msg.get_float());
-                                break;
-                            case 3:
-                                layer_values_.push_back(val_msg.get_double());
-                                break;
-                            case 4:
-                                layer_values_.push_back(val_msg.get_int64());
-                                break;
-                            case 5:
-                                layer_values_.push_back(val_msg.get_uint64());
-                                break;
-                            case 6:
-                                layer_values_.push_back(val_msg.get_sint64());
-                                break;
-                            case 7:
-                                layer_values_.push_back(val_msg.get_bool());
-                                break;
-                            default:
-                                throw std::runtime_error("unknown Value type " + std::to_string(layer_.tag()) + " in layer.values");
-                        }
-                    }
-                    break;
-                case 5:
-                    layer_extent_ = layer_.get_uint32();
-                    break;
-                case 15:
-                    layer_.skip();
-                    break;
-                default:
-                    throw std::runtime_error("unknown field type " + std::to_string(layer_.tag()) + " in layer");
-            }
+            throw std::runtime_error("The required name field is missing in a vector tile layer. Tile does not comply with Version 2 of the Mapbox Vector Tile Specification.");
         }
-        scale_ = (static_cast<double>(layer_extent_) / tile_size_) * tile_size_/resolution;
+        else if (!has_extent)
+        {
+            throw std::runtime_error("The required extent field is missing in the layer " + name_ + ". Tile does not comply with Version 2 of the Mapbox Vector Tile Specification.");
+        }
+        scale_ = static_cast<double>(tile_size_)/resolution;
     }
-
-    tile_datasource_pbf::~tile_datasource_pbf() {}
-
-    datasource::datasource_t tile_datasource_pbf::type() const
+    else if (version_ == 1)
     {
-        return datasource::Vector;
+        if (!has_name)
+        {
+            throw std::runtime_error("The required name field is missing in a vector tile layer.");
+        }
+        scale_ = static_cast<double>(tile_size_)/resolution;
     }
-
-    featureset_ptr tile_datasource_pbf::features(query const& q) const
+    else
     {
-        mapnik::filter_in_box filter(q.get_bbox());
-        return std::make_shared<tile_featureset_pbf<mapnik::filter_in_box> >
-            (filter, get_tile_extent(), q.get_unbuffered_bbox(), q.property_names(), features_, tile_x_, tile_y_, scale_, layer_keys_, layer_values_);
+        valid_layer_ = false;
     }
 
-    featureset_ptr tile_datasource_pbf::features_at_point(coord2d const& pt, double tol) const
+    if (features_.empty())
     {
-        mapnik::filter_at_point filter(pt,tol);
-        std::set<std::string> names;
-        for (auto const& key : layer_keys_)
-        {
-            names.insert(key);
-        }
-        return std::make_shared<tile_featureset_pbf<filter_at_point> >
-            (filter, get_tile_extent(), get_tile_extent(), names, features_, tile_x_, tile_y_, scale_, layer_keys_, layer_values_);
+        valid_layer_ = false;
     }
-
-    void tile_datasource_pbf::set_envelope(box2d<double> const& bbox)
+    if (use_tile_extent)
     {
-        extent_initialized_ = true;
-        extent_ = bbox;
+        params_["vector_layer_extent"] = tile_size_;
     }
+}
+
+tile_datasource_pbf::~tile_datasource_pbf() {}
+
+datasource::datasource_t tile_datasource_pbf::type() const
+{
+    return type_;
+}
 
-    box2d<double> tile_datasource_pbf::get_tile_extent() const
+featureset_ptr tile_datasource_pbf::features(query const& q) const
+{
+    if (!valid_layer_)
     {
-        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);
+        // From spec:
+        // When a Vector Tile consumer encounters a Vector Tile layer with an unknown version, 
+        // it MAY make a best-effort attempt to interpret the layer, or it MAY skip the layer. 
+        // In either case it SHOULD continue to process subsequent layers in the Vector Tile.
+    
+        // Therefore if version is invalid we will just return pointer
+        return featureset_ptr();
     }
+    mapnik::filter_in_box filter(q.get_bbox());
+    return std::make_shared<tile_featureset_pbf<mapnik::filter_in_box> >
+        (filter, get_tile_extent(), q.get_unbuffered_bbox(), q.property_names(), features_, tile_x_, tile_y_, scale_, layer_keys_, layer_values_, version_);
+}
 
-    box2d<double> tile_datasource_pbf::envelope() const
+featureset_ptr tile_datasource_pbf::features_at_point(coord2d const& pt, double tol) const
+{
+    if (!valid_layer_)
     {
-        if (!extent_initialized_)
-        {
-            extent_ = get_tile_extent();
-            extent_initialized_ = true;
-        }
-        return extent_;
+        // From spec:
+        // When a Vector Tile consumer encounters a Vector Tile layer with an unknown version, 
+        // it MAY make a best-effort attempt to interpret the layer, or it MAY skip the layer. 
+        // In either case it SHOULD continue to process subsequent layers in the Vector Tile.
+    
+        // Therefore if version is invalid we will just return pointer
+        return featureset_ptr();
+    }
+    mapnik::filter_at_point filter(pt,tol);
+    std::set<std::string> names;
+    for (auto const& key : layer_keys_)
+    {
+        names.insert(key);
     }
+    return std::make_shared<tile_featureset_pbf<filter_at_point> >
+        (filter, get_tile_extent(), get_tile_extent(), names, features_, tile_x_, tile_y_, scale_, layer_keys_, layer_values_, version_);
+}
+
+void tile_datasource_pbf::set_envelope(box2d<double> const& bbox)
+{
+    extent_initialized_ = true;
+    extent_ = bbox;
+}
 
-    boost::optional<mapnik::datasource_geometry_t> tile_datasource_pbf::get_geometry_type() const
+box2d<double> tile_datasource_pbf::get_tile_extent() const
+{
+    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);
+}
+
+box2d<double> tile_datasource_pbf::envelope() const
+{
+    if (!extent_initialized_)
     {
-        return mapnik::datasource_geometry_t::Collection;
+        extent_ = get_tile_extent();
+        extent_initialized_ = true;
     }
+    return extent_;
+}
 
-    layer_descriptor tile_datasource_pbf::get_descriptor() const
+boost::optional<mapnik::datasource_geometry_t> tile_datasource_pbf::get_geometry_type() const
+{
+    return mapnik::datasource_geometry_t::Collection;
+}
+
+layer_descriptor tile_datasource_pbf::get_descriptor() const
+{
+    if (!attributes_added_)
     {
-        if (!attributes_added_)
+        for (auto const& key : layer_keys_)
         {
-            for (auto const& key : layer_keys_)
-            {
-                // Object type here because we don't know the precise value until features are unpacked
-                desc_.add_descriptor(attribute_descriptor(key, Object));
-            }
-            attributes_added_ = true;
+            // Object type here because we don't know the precise value until features are unpacked
+            desc_.add_descriptor(attribute_descriptor(key, Object));
         }
-        return desc_;
+        attributes_added_ = true;
     }
+    return desc_;
+}
+
+} // end ns vector_tile_impl
 
-    }} // end ns
+} // end ns mapnik
diff --git a/src/vector_tile_featureset_pbf.cpp b/src/vector_tile_featureset_pbf.cpp
new file mode 100644
index 0000000..5e3c472
--- /dev/null
+++ b/src/vector_tile_featureset_pbf.cpp
@@ -0,0 +1,6 @@
+#include "vector_tile_featureset_pbf.hpp"
+#include "vector_tile_featureset_pbf.ipp"
+#include <mapnik/geom_util.hpp>
+
+template class mapnik::vector_tile_impl::tile_featureset_pbf<mapnik::filter_in_box>;
+template class mapnik::vector_tile_impl::tile_featureset_pbf<mapnik::filter_at_point>;
diff --git a/src/vector_tile_featureset_pbf.hpp b/src/vector_tile_featureset_pbf.hpp
new file mode 100644
index 0000000..1d6a142
--- /dev/null
+++ b/src/vector_tile_featureset_pbf.hpp
@@ -0,0 +1,73 @@
+#ifndef __MAPNIK_VECTOR_TILE_FEATURESET_PBF_H__
+#define __MAPNIK_VECTOR_TILE_FEATURESET_PBF_H__
+
+// protozero
+#include <protozero/pbf_reader.hpp>
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/feature.hpp>
+#include <mapnik/featureset.hpp>
+#include <mapnik/unicode.hpp>
+#include <mapnik/util/variant.hpp>
+#include <mapnik/view_transform.hpp>
+
+// std
+#include <set>
+
+namespace mapnik 
+{ 
+
+namespace vector_tile_impl 
+{
+
+using pbf_attr_value_type = mapnik::util::variant<std::string, float, double, int64_t, uint64_t, bool>;
+using layer_pbf_attr_type = std::vector<pbf_attr_value_type>;
+
+template <typename Filter>
+class tile_featureset_pbf : public Featureset
+{
+public:
+    tile_featureset_pbf(Filter const& filter,
+                    mapnik::box2d<double> const& tile_extent,
+                    mapnik::box2d<double> const& unbuffered_query,
+                    std::set<std::string> const& attribute_names,
+                    std::vector<protozero::pbf_reader> const& features,
+                    double tile_x,
+                    double tile_y,
+                    double scale,
+                    std::vector<std::string> const& layer_keys,
+                    layer_pbf_attr_type const& layer_values,
+                    unsigned version);
+    
+    virtual ~tile_featureset_pbf() {}
+
+    feature_ptr next();
+
+private:
+    Filter filter_;
+    mapnik::box2d<double> tile_extent_;
+    mapnik::box2d<double> unbuffered_query_;
+    std::vector<protozero::pbf_reader> const& features_;
+    std::vector<std::string> const& layer_keys_;
+    layer_pbf_attr_type const& layer_values_;
+    std::size_t num_keys_;
+    std::size_t num_values_;
+    double tile_x_;
+    double tile_y_;
+    double scale_;
+    unsigned itr_;
+    unsigned version_;
+    mapnik::transcoder tr_;
+    mapnik::context_ptr ctx_;
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_featureset_pbf.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_FEATURESET_PBF_H__
diff --git a/src/vector_tile_featureset_pbf.ipp b/src/vector_tile_featureset_pbf.ipp
new file mode 100644
index 0000000..441624a
--- /dev/null
+++ b/src/vector_tile_featureset_pbf.ipp
@@ -0,0 +1,325 @@
+// mapnik-vector-tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/image.hpp>
+#include <mapnik/image_reader.hpp>
+#include <mapnik/raster.hpp>
+#include <mapnik/unicode.hpp>
+#include <mapnik/view_transform.hpp>
+#if defined(DEBUG)
+#include <mapnik/debug.hpp>
+#endif
+
+// protozero
+#include <protozero/pbf_reader.hpp>
+
+namespace mapnik 
+{ 
+
+namespace vector_tile_impl 
+{
+
+template <typename Filter>
+tile_featureset_pbf<Filter>::tile_featureset_pbf(Filter const& filter,
+                                                 mapnik::box2d<double> const& tile_extent,
+                                                 mapnik::box2d<double> const& unbuffered_query,
+                                                 std::set<std::string> const& attribute_names,
+                                                 std::vector<protozero::pbf_reader> const& features,
+                                                 double tile_x,
+                                                 double tile_y,
+                                                 double scale,
+                                                 std::vector<std::string> const& layer_keys,
+                                                 layer_pbf_attr_type const& layer_values,
+                                                 unsigned version)
+        : filter_(filter),
+          tile_extent_(tile_extent),
+          unbuffered_query_(unbuffered_query),
+          features_(features),
+          layer_keys_(layer_keys),
+          layer_values_(layer_values),
+          num_keys_(layer_keys_.size()),
+          num_values_(layer_values_.size()),
+          tile_x_(tile_x),
+          tile_y_(tile_y),
+          scale_(scale),
+          itr_(0),
+          version_(version),
+          tr_("utf-8"),
+          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();
+    for ( ;pos !=end; ++pos)
+    {
+        for (auto const& key : layer_keys_)
+        {
+            if (key == *pos)
+            {
+                ctx_->push(*pos);
+                break;
+            }
+        }
+    }
+}
+
+struct value_visitor
+{
+    mapnik::transcoder & tr_;
+    mapnik::feature_ptr & feature_;
+    std::string const& name_;
+
+    value_visitor(mapnik::transcoder & tr,
+                  mapnik::feature_ptr & feature,
+                  std::string const& name)
+        : tr_(tr),
+          feature_(feature),
+          name_(name) {}
+    
+    void operator() (std::string const& val)
+    {
+        feature_->put(name_, tr_.transcode(val.data(), val.length()));
+    }
+
+    void operator() (bool const& val)
+    {
+        feature_->put(name_, static_cast<mapnik::value_bool>(val));
+    }
+
+    void operator() (int64_t const& val)
+    {
+        feature_->put(name_, static_cast<mapnik::value_integer>(val));
+    }
+
+    void operator() (uint64_t const& val)
+    {
+        feature_->put(name_, static_cast<mapnik::value_integer>(val));
+    }
+
+    void operator() (double const& val)
+    {
+        feature_->put(name_, static_cast<mapnik::value_double>(val));
+    }
+
+    void operator() (float const& val)
+    {
+        feature_->put(name_, static_cast<mapnik::value_double>(val));
+    }
+};
+
+template <typename Filter>
+feature_ptr tile_featureset_pbf<Filter>::next()
+{
+    while ( itr_ < features_.size() )
+    {
+        protozero::pbf_reader f = features_.at(itr_);
+        // TODO: auto-increment feature id counter here
+        mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,itr_);
+
+        ++itr_;
+        int32_t geometry_type = 0; // vector_tile::Tile_GeomType_UNKNOWN
+        bool has_geometry = false;
+        bool has_geometry_type = false;
+        std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+        bool has_raster = false;
+        std::pair<const char*, protozero::pbf_length_type> image_buffer;
+        while (f.next())
+        {
+            switch(f.tag())
+            {
+                case 1:
+                    feature->set_id(f.get_uint64());
+                    break;
+                case 2:
+                    {
+                        auto tag_iterator = f.get_packed_uint32();
+                        for (auto _i = tag_iterator.first; _i != tag_iterator.second;)
+                        {
+                            std::size_t key_name = *(_i++);
+                            if (_i == tag_iterator.second)
+                            {
+                                throw std::runtime_error("Vector Tile has a feature with an odd number of tags, therefore the tile is invalid.");
+                            }
+                            std::size_t key_value = *(_i++);
+                            if (key_name < num_keys_
+                                && key_value < num_values_)
+                            {
+                                std::string const& name = layer_keys_.at(key_name);
+                                if (feature->has_key(name))
+                                {
+                                    pbf_attr_value_type val = layer_values_.at(key_value);
+                                    value_visitor vv(tr_, feature, name);
+                                    mapnik::util::apply_visitor(vv, val);
+                                }
+                            }
+                            else if (version_ == 2)
+                            {
+                                throw std::runtime_error("Vector Tile has a feature with repeated attributes with an invalid key or value as it does not appear in the layer. This is invalid according to the Mapbox Vector Tile Specification Version 2");
+                            }
+                        }
+                    }
+                    break;
+                case 3:
+                    has_geometry_type = true;
+                    geometry_type = f.get_enum();
+                    switch (geometry_type)
+                    {
+                      case 1: //vector_tile::Tile_GeomType_POINT
+                      case 2: // vector_tile::Tile_GeomType_LINESTRING
+                      case 3: // vector_tile::Tile_GeomType_POLYGON
+                        break;
+                      default:  // vector_tile::Tile_GeomType_UNKNOWN or any other value
+                        throw std::runtime_error("Vector tile has an unknown geometry type " + std::to_string(geometry_type) + " in feature");
+                    }
+                    break;
+                case 5:
+                    if (has_geometry)
+                    {
+                        throw std::runtime_error("Vector Tile has a feature with a geometry and a raster, it must have only one of them");
+                    }
+                    if (has_raster)
+                    {
+                        throw std::runtime_error("Vector Tile has a feature with multiple raster fields, it must have only one of them");
+                    }
+                    has_raster = true;
+                    image_buffer = f.get_data();
+                    break;
+                case 4:
+                    if (has_raster)
+                    {
+                        throw std::runtime_error("Vector Tile has a feature with a geometry and a raster, it must have only one of them");
+                    }
+                    if (has_geometry)
+                    {
+                        throw std::runtime_error("Vector Tile has a feature with multiple geometry fields, it must have only one of them");
+                    }
+                    has_geometry = true;
+                    geom_itr = f.get_packed_uint32();
+                    break;
+                default:
+                    throw std::runtime_error("Vector Tile contains unknown field type " + std::to_string(f.tag()) +" in feature");
+
+            }
+        }
+        if (has_raster)
+        {
+            std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.first, image_buffer.second));
+            if (reader.get())
+            {
+                int image_width = reader->width();
+                int image_height = reader->height();
+                if (image_width > 0 && image_height > 0)
+                {
+                    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 )
+                    {
+                        // 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
+                                                      );
+                        feature->set_raster(raster);
+                    }
+                }
+            }
+            return feature;
+        }
+        else if (has_geometry)
+        {
+            if (!has_geometry_type)
+            {
+                if (version_ == 1)
+                {
+                    continue;
+                }
+                else
+                {
+                    throw std::runtime_error("Vector Tile has a feature that does not define the required geometry type.");
+                }
+            }
+            if (version_ != 1)
+            {
+                mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x_,tile_y_,scale_,-1*scale_);
+                mapnik::geometry::geometry<double> geom = decode_geometry(geoms, geometry_type, version_, filter_.box_);
+                if (geom.is<mapnik::geometry::geometry_empty>())
+                {
+                    continue;
+                }
+                #if defined(DEBUG)
+                mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
+                if (!filter_.pass(envelope))
+                {
+                    MAPNIK_LOG_ERROR(tile_featureset_pbf) << "tile_featureset_pbf: filter:pass should not get here";
+                    continue;
+                }
+                #endif
+                feature->set_geometry(std::move(geom));
+                return feature;
+            }
+            else
+            {
+                try
+                {
+                    mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x_,tile_y_,scale_,-1*scale_);
+                    mapnik::geometry::geometry<double> geom = decode_geometry(geoms, geometry_type, version_, filter_.box_);
+                    if (geom.is<mapnik::geometry::geometry_empty>())
+                    {
+                        continue;
+                    }
+                    #if defined(DEBUG)
+                    mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
+                    if (!filter_.pass(envelope))
+                    {
+                        MAPNIK_LOG_ERROR(tile_featureset_pbf) << "tile_featureset_pbf: filter:pass should not get here";
+                        continue;
+                    }
+                    #endif
+                    feature->set_geometry(std::move(geom));
+                }
+                catch (std::exception& e)
+                {
+                    // For v1 any invalid geometry errors lets just skip the feature
+                    continue;
+                }
+                return feature;
+            }
+        }
+        else if (version_ != 1)
+        {
+            throw std::runtime_error("Vector Tile does not have a geometry or raster");
+        }
+    }
+    return feature_ptr();
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_geometry_clipper.hpp b/src/vector_tile_geometry_clipper.hpp
new file mode 100644
index 0000000..13a8029
--- /dev/null
+++ b/src/vector_tile_geometry_clipper.hpp
@@ -0,0 +1,479 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_CLIPPER_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_CLIPPER_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+#include "vector_tile_processor.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_adapters.hpp>
+
+// angus clipper
+// http://www.angusj.com/delphi/clipper.php
+#include "clipper.hpp"
+
+// boost
+#pragma GCC diagnostic push
+#include <mapnik/warning_ignore.hpp>
+#include <boost/geometry/algorithms/intersection.hpp>
+#include <boost/geometry/algorithms/unique.hpp>
+#pragma GCC diagnostic pop
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+namespace detail
+{
+
+inline ClipperLib::PolyFillType get_angus_fill_type(polygon_fill_type type)
+{
+    switch (type) 
+    {
+    case polygon_fill_type_max:
+    case even_odd_fill:
+        return ClipperLib::pftEvenOdd;
+    case non_zero_fill: 
+        return ClipperLib::pftNonZero;
+    case positive_fill:
+        return ClipperLib::pftPositive;
+    case negative_fill:
+        return ClipperLib::pftNegative;
+    }
+}
+
+
+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.push_back(std::move(polygon));
+        }
+    }
+    for (auto * ring : polynode->Childs)
+    {
+        for (auto * sub_ring : ring->Childs)
+        {
+            process_polynode_branch(sub_ring, mp, area_threshold);
+        }
+    }
+}
+
+} // end ns detail
+
+template <typename NextProcessor>
+class geometry_clipper 
+{
+private:
+    NextProcessor & next_;
+    mapnik::box2d<int> const& tile_clipping_extent_;
+    double area_threshold_;
+    bool strictly_simple_;
+    bool multi_polygon_union_;
+    polygon_fill_type fill_type_;
+    bool process_all_rings_;
+public:
+    geometry_clipper(mapnik::box2d<int> const& tile_clipping_extent,
+                     double area_threshold,
+                     bool strictly_simple,
+                     bool multi_polygon_union,
+                     polygon_fill_type fill_type,
+                     bool process_all_rings,
+                     NextProcessor & next) :
+              next_(next),
+              tile_clipping_extent_(tile_clipping_extent),
+              area_threshold_(area_threshold),
+              strictly_simple_(strictly_simple),
+              multi_polygon_union_(multi_polygon_union),
+              fill_type_(fill_type),
+              process_all_rings_(process_all_rings)
+    {
+    }
+
+    void operator() (mapnik::geometry::geometry_empty &)
+    {
+        return;
+    }
+
+    void operator() (mapnik::geometry::point<std::int64_t> & geom)
+    {
+        next_(geom);
+    }
+
+    void operator() (mapnik::geometry::multi_point<std::int64_t> & geom)
+    {
+        // Here we remove repeated points from multi_point
+        auto last = std::unique(geom.begin(), geom.end());
+        geom.erase(last, geom.end());
+        next_(geom);
+    }
+
+    void operator() (mapnik::geometry::geometry_collection<std::int64_t> & geom)
+    {
+        for (auto & g : geom)
+        {
+            mapnik::util::apply_visitor((*this), g);
+        }
+    }
+
+    void operator() (mapnik::geometry::line_string<std::int64_t> & geom)
+    {
+        boost::geometry::unique(geom);
+        if (geom.size() < 2)
+        {
+            return;
+        }
+        //std::deque<mapnik::geometry::line_string<int64_t>> result;
+        mapnik::geometry::multi_line_string<int64_t> result;
+        mapnik::geometry::linear_ring<std::int64_t> clip_box;
+        clip_box.reserve(5);
+        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
+        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
+        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
+        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+        boost::geometry::intersection(clip_box, geom, result);
+        if (result.empty())
+        {
+            return;
+        }
+        next_(result);
+    }
+
+    void operator() (mapnik::geometry::multi_line_string<std::int64_t> & geom)
+    {
+        if (geom.empty())
+        {
+            return;
+        }
+
+        mapnik::geometry::linear_ring<std::int64_t> clip_box;
+        clip_box.reserve(5);
+        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
+        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
+        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
+        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+        boost::geometry::unique(geom);
+        mapnik::geometry::multi_line_string<int64_t> results;
+        for (auto const& line : geom)
+        {
+            if (line.size() < 2)
+            {
+               continue;
+            }
+            boost::geometry::intersection(clip_box, line, results);
+        }
+        if (results.empty())
+        {
+            return;
+        }
+        next_(results);
+    }
+
+    void operator() (mapnik::geometry::polygon<std::int64_t> & geom)
+    {
+        if ((geom.exterior_ring.size() < 3) && !process_all_rings_)
+        {
+            return;
+        }
+        
+        double clean_distance = 1.415;
+        
+        // Prepare the clipper object
+        ClipperLib::Clipper clipper;
+        
+        if (strictly_simple_) 
+        {
+            clipper.StrictlySimple(true);
+        }
+        
+        // Start processing on exterior ring
+        // if proces_all_rings is true even if the exterior
+        // ring is invalid we will continue to insert all polygon
+        // rings into the clipper
+        ClipperLib::CleanPolygon(geom.exterior_ring, clean_distance);
+        double outer_area = ClipperLib::Area(geom.exterior_ring);
+        if ((std::abs(outer_area) < area_threshold_)  && !process_all_rings_)
+        {
+            return;
+        }
+
+        // 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());
+        }
+
+        if (!clipper.AddPath(geom.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
+        {
+            return;
+        }
+
+        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 (!clipper.AddPath(ring, ClipperLib::ptSubject, true))
+            {
+                continue;
+            }
+        }
+
+        // Setup the box for clipping
+        mapnik::geometry::linear_ring<std::int64_t> clip_box;
+        clip_box.reserve(5);
+        clip_box.emplace_back(tile_clipping_extent_.minx(), tile_clipping_extent_.miny());
+        clip_box.emplace_back(tile_clipping_extent_.maxx(), tile_clipping_extent_.miny());
+        clip_box.emplace_back(tile_clipping_extent_.maxx(), tile_clipping_extent_.maxy());
+        clip_box.emplace_back(tile_clipping_extent_.minx(), tile_clipping_extent_.maxy());
+        clip_box.emplace_back(tile_clipping_extent_.minx(), tile_clipping_extent_.miny());
+        
+        // Finally add the box we will be using for clipping
+        if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+        {
+            return;
+        }
+
+        ClipperLib::PolyTree polygons;
+        ClipperLib::PolyFillType fill_type = detail::get_angus_fill_type(fill_type_);
+        clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type, ClipperLib::pftEvenOdd);
+        clipper.Clear();
+        
+        mapnik::geometry::multi_polygon<std::int64_t> mp;
+        
+        for (auto * polynode : polygons.Childs)
+        {
+            detail::process_polynode_branch(polynode, mp, area_threshold_); 
+        }
+
+        if (mp.empty())
+        {
+            return;
+        }
+        next_(mp);
+    }
+
+    void operator() (mapnik::geometry::multi_polygon<std::int64_t> & geom)
+    {
+        if (geom.empty())
+        {
+            return;
+        }
+        
+        double clean_distance = 1.415;
+        mapnik::geometry::linear_ring<std::int64_t> clip_box;
+        clip_box.reserve(5);
+        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
+        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
+        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
+        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+        
+        mapnik::geometry::multi_polygon<std::int64_t> mp;
+        
+        ClipperLib::Clipper clipper;
+        
+        if (strictly_simple_) 
+        {
+            clipper.StrictlySimple(true);
+        }
+
+        ClipperLib::PolyFillType fill_type = detail::get_angus_fill_type(fill_type_);
+
+        if (multi_polygon_union_)
+        {
+            for (auto & poly : geom)
+            {
+                // Below we attempt to skip processing of all interior rings if the exterior
+                // ring fails a variety of sanity checks for size and validity for AddPath
+                // When `process_all_rings_=true` this optimization is disabled. This is needed when
+                // the ring order of input polygons is potentially incorrect and where the
+                // "exterior_ring" might actually be an incorrectly classified exterior ring.
+                if (poly.exterior_ring.size() < 3 && !process_all_rings_)
+                {
+                    continue;
+                }
+                ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
+                double outer_area = ClipperLib::Area(poly.exterior_ring);
+                if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
+                {
+                    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());
+                }
+                if (!clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
+                {
+                    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());
+                    }
+                    clipper.AddPath(ring, ClipperLib::ptSubject, true);
+                }
+            }
+            if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+            {
+                return;
+            }
+            ClipperLib::PolyTree polygons;
+            clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type, ClipperLib::pftEvenOdd);
+            clipper.Clear();
+            
+            for (auto * polynode : polygons.Childs)
+            {
+                detail::process_polynode_branch(polynode, mp, area_threshold_); 
+            }
+        }
+        else
+        {
+            for (auto & poly : geom)
+            {
+                // Below we attempt to skip processing of all interior rings if the exterior
+                // ring fails a variety of sanity checks for size and validity for AddPath
+                // When `process_all_rings_=true` this optimization is disabled. This is needed when
+                // the ring order of input polygons is potentially incorrect and where the
+                // "exterior_ring" might actually be an incorrectly classified exterior ring.
+                if (poly.exterior_ring.size() < 3 && !process_all_rings_)
+                {
+                    continue;
+                }
+                ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
+                double outer_area = ClipperLib::Area(poly.exterior_ring);
+                if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
+                {
+                    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());
+                }
+                if (!clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
+                {
+                    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 (!clipper.AddPath(ring, ClipperLib::ptSubject, true))
+                    {
+                        continue;
+                    }
+                }
+                if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+                {
+                    return;
+                }
+                ClipperLib::PolyTree polygons;
+                clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type, ClipperLib::pftEvenOdd);
+                clipper.Clear();
+                
+                for (auto * polynode : polygons.Childs)
+                {
+                    detail::process_polynode_branch(polynode, mp, area_threshold_); 
+                }
+            }
+        }
+
+        if (mp.empty())
+        {
+            return;
+        }
+        next_(mp);
+    }
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_GEOMETRY_CLIPPER_H__
diff --git a/src/vector_tile_geometry_decoder.cpp b/src/vector_tile_geometry_decoder.cpp
new file mode 100644
index 0000000..5bbd2b9
--- /dev/null
+++ b/src/vector_tile_geometry_decoder.cpp
@@ -0,0 +1,20 @@
+#include "vector_tile_geometry_decoder.hpp"
+#include "vector_tile_geometry_decoder.ipp"
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+// Geometry classes
+template class GeometryPBF<double>;
+template class GeometryPBF<std::int64_t>;
+
+// decode geometry
+template mapnik::geometry::geometry<double> decode_geometry(GeometryPBF<double> & paths, int32_t geom_type, unsigned version);
+template mapnik::geometry::geometry<std::int64_t> decode_geometry(GeometryPBF<std::int64_t> & paths, int32_t geom_type, unsigned version);
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_geometry_decoder.hpp b/src/vector_tile_geometry_decoder.hpp
index e934ca4..a7a2254 100644
--- a/src/vector_tile_geometry_decoder.hpp
+++ b/src/vector_tile_geometry_decoder.hpp
@@ -1,64 +1,45 @@
 #ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
 #define __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
 
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
 
+//protozero
 #include <protozero/pbf_reader.hpp>
 
+//mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
 #include <mapnik/util/is_clockwise.hpp>
-//std
-#include <algorithm>
-
 #if defined(DEBUG)
 #include <mapnik/debug.hpp>
 #endif
 
-namespace mapnik { namespace vector_tile_impl {
-
-// NOTE: this object is for one-time use.  Once you've progressed to the end
-//       by calling next(), to re-iterate, you must construct a new object
-template <typename ValueType>
-class Geometry {
-
-public:
-    inline explicit Geometry(vector_tile::Tile_Feature const& f,
-                             ValueType tile_x, ValueType 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(ValueType & rx, ValueType & ry, std::uint32_t & len);
+//std
+#include <algorithm>
+#include <cmath>
+#include <stdexcept>
 
-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;
-    ValueType x, y;
-    ValueType ox, oy;
-};
+namespace mapnik 
+{ 
+    
+namespace vector_tile_impl 
+{
 
 // NOTE: this object is for one-time use.  Once you've progressed to the end
 //       by calling next(), to re-iterate, you must construct a new object
-template <typename ValueType>
-class GeometryPBF {
-
+template <typename T>
+class GeometryPBF 
+{
 public:
-    inline explicit GeometryPBF(std::pair< protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > const& geo_iterator,
-                             ValueType tile_x, ValueType tile_y,
-                             double scale_x, double scale_y);
+    using value_type = T;
+    using pbf_itr = std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator >;
+    
+    explicit GeometryPBF(pbf_itr const& geo_iterator,
+                         value_type tile_x, 
+                         value_type tile_y,
+                         double scale_x, 
+                         double scale_y);
 
     enum command : uint8_t
     {
@@ -68,494 +49,50 @@ public:
         close = 7
     };
 
-    inline command next(ValueType & rx, ValueType & ry, std::uint32_t & len);
-
-private:
-    std::pair< protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > geo_iterator_;
-    double scale_x_;
-    double scale_y_;
-    uint8_t cmd;
-    std::uint32_t length;
-    ValueType x, y;
-    ValueType ox, oy;
-};
-
 
-template <typename ValueType>
-Geometry<ValueType>::Geometry(vector_tile::Tile_Feature const& f,
-                   ValueType tile_x, ValueType 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) {}
-
-template <typename ValueType>
-typename Geometry<ValueType>::command Geometry<ValueType>::next(ValueType & rx, ValueType & ry, std::uint32_t & len)
-{
-    if (k < geoms_)
+    bool scaling_reversed_orientation() const
     {
-        if (length == 0)
-        {
-            uint32_t cmd_length = static_cast<uint32_t>(f_.geometry(k++));
-            cmd = cmd_length & 0x7;
-            len = 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<ValueType>(static_cast<double>(dx) / scale_x_);
-            y += static_cast<ValueType>(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;
+        return (scale_x_ * scale_y_) < 0;
     }
-}
 
-template <typename ValueType>
-GeometryPBF<ValueType>::GeometryPBF(std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > const& geo_iterator,
-                   ValueType tile_x, ValueType tile_y,
-                   double scale_x, double scale_y)
-    : geo_iterator_(geo_iterator),
-      scale_x_(scale_x),
-      scale_y_(scale_y),
-      cmd(1),
-      length(0),
-      x(tile_x), y(tile_y),
-      ox(0), oy(0) {}
-
-template <typename ValueType>
-typename GeometryPBF<ValueType>::command GeometryPBF<ValueType>::next(ValueType & rx, ValueType & ry, std::uint32_t & len)
-{
-    if (geo_iterator_.first != geo_iterator_.second)
+    uint32_t get_length() const
     {
-        if (length == 0)
-        {
-            uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
-            cmd = cmd_length & 0x7;
-            len = length = cmd_length >> 3;
-        }
-
-        --length;
-
-        if (cmd == move_to || cmd == line_to)
-        {
-            int32_t dx = *geo_iterator_.first++;
-            int32_t dy = *geo_iterator_.first++;
-            dx = ((dx >> 1) ^ (-(dx & 1)));
-            dy = ((dy >> 1) ^ (-(dy & 1)));
-            x += static_cast<ValueType>(static_cast<double>(dx) / scale_x_);
-            y += static_cast<ValueType>(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;
+        return length;
     }
-}
 
-namespace detail {
+    command point_next(value_type & rx, value_type & ry);
+    command line_next(value_type & rx, value_type & ry, bool skip_lineto_zero);
+    command ring_next(value_type & rx, value_type & ry, bool skip_lineto_zero);
 
-template <typename ValueType, typename T>
-void decode_point(mapnik::geometry::geometry<ValueType> & geom, T & paths, mapnik::box2d<double> const& bbox)
-{
-    typename T::command cmd;
-    ValueType x1, y1;
-    mapnik::geometry::multi_point<ValueType> mp;
-    bool first = true;
-    std::uint32_t len;
-    while ((cmd = paths.next(x1, y1, len)) != T::end)
-    {
-        // TODO: consider profiling and trying to optimize this further
-        // when all points are within the bbox filter then the `mp.reserve` should be
-        // perfect, but when some points are thrown out we will allocate more than needed
-        // the "all points intersect" case I think is going to be more common/important
-        // however worth a future look to see if the "some or few points intersect" can be optimized
-        if (first)
-        {
-            first = false;
-            mp.reserve(len);
-        }
-        if (!bbox.intersects(x1,y1))
-        {
-            continue;
-        }
-        mp.emplace_back(x1,y1);
-    }
-    std::size_t num_points = mp.size();
-    #if defined(DEBUG)
-    if (num_points > 0 && len != num_points) {
-        // BUG: https://github.com/mapbox/mapnik-vector-tile/issues/144
-        MAPNIK_LOG_ERROR(decode_point) << "warning: encountered incorrectly encoded multipoint with " << num_points << " points but only " << len << " repeated commands";
-    }
-    #endif
-    if (num_points == 0)
-    {
-        geom = mapnik::geometry::geometry_empty();
-    }
-    else if (num_points == 1)
-    {
-        geom = std::move(mp[0]);
-    }
-    else if (num_points > 1)
-    {
-        // return multipoint
-        geom = std::move(mp);
-    }
-}
-
-template <typename ValueType, typename T>
-void decode_linestring(mapnik::geometry::geometry<ValueType> & geom, T & paths, mapnik::box2d<double> const& bbox)
-{
-    typename T::command cmd;
-    ValueType x1, y1;
-    mapnik::geometry::multi_line_string<ValueType> multi_line;
-    multi_line.emplace_back();
-    bool first = true;
-    bool first_line_to = true;
-    std::uint32_t len;
-    #if defined(DEBUG)
-    std::uint32_t pre_len;
-    #endif
-    mapnik::box2d<double> part_env;
-    while ((cmd = paths.next(x1, y1, len)) != T::end)
-    {
-        if (cmd == T::move_to)
-        {
-            if (first)
-            {
-                first = false;
-            }
-            else
-            {
-                #if defined(DEBUG)
-                if (multi_line.back().size() > 0 && pre_len != multi_line.back().size())
-                {
-                    MAPNIK_LOG_ERROR(decode_linestring) << "warning: encountered incorrectly encoded line with " << multi_line.back().size() << " points but only " << pre_len << " repeated commands";
-                }
-                #endif
-                first_line_to = true;
-                if (!bbox.intersects(part_env))
-                {
-                    // remove last line
-                    multi_line.pop_back();
-                }
-                // add fresh line to start adding to
-                multi_line.emplace_back();
-            }
-            part_env.init(x1,y1,x1,y1);
-        }
-        else if (first_line_to && cmd == T::line_to)
-        {
-            first_line_to = false;
-            multi_line.back().reserve(len+1);
-            #if defined(DEBUG)
-            pre_len = len+1;
-            #endif
-        }
-        if (!first)
-        {
-            part_env.expand_to_include(x1,y1);
-        }
-        multi_line.back().add_coord(x1,y1);
-    }
-    if (!bbox.intersects(part_env))
-    {
-        // remove last line
-        multi_line.pop_back();
-    }
-    std::size_t num_lines = multi_line.size();
-    if (num_lines == 0)
-    {
-        geom = mapnik::geometry::geometry_empty();
-    }
-    else if (num_lines == 1)
-    {
-        auto itr = std::make_move_iterator(multi_line.begin());
-        if (itr->size() > 1)
-        {
-            geom = std::move(*itr);
-        }
-    }
-    else if (num_lines > 1)
-    {
-        geom = std::move(multi_line);
-    }
-}
-
-template <typename ValueType, typename T>
-void read_rings(std::vector<mapnik::geometry::linear_ring<ValueType> > & rings,
-                T & paths, mapnik::box2d<double> const& bbox)
-{
-    typename T::command cmd;
-    ValueType x1, y1;
-    rings.emplace_back();
-    ValueType x2, y2;
-    bool first = true;
-    bool first_line_to = true;
-    std::uint32_t len;
+private:
+    std::pair< protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > geo_iterator_;
+    double scale_x_;
+    double scale_y_;
+    value_type x, y;
+    value_type ox, oy;
+    uint32_t length;
+    uint8_t cmd;
     #if defined(DEBUG)
-    std::uint32_t pre_len;
+public:
+    bool already_had_error;
     #endif
-    mapnik::box2d<double> part_env;
-    while ((cmd = paths.next(x1, y1, len)) != T::end)
-    {
-        if (cmd == T::move_to)
-        {
-            x2 = x1;
-            y2 = y1;
-            if (first)
-            {
-                first = false;
-            }
-            else
-            {
-                #if defined(DEBUG)
-                // off by one is expected/okay in rare cases
-                if (rings.back().size() > 0 && (rings.back().size() > pre_len || std::fabs(pre_len - rings.back().size()) > 1))
-                {
-                    MAPNIK_LOG_ERROR(read_rings) << "warning: encountered incorrectly encoded ring with " << rings.back().size() << " points but " << pre_len << " repeated commands";
-                }
-                #endif
-                first_line_to = true;
-                if (!bbox.intersects(part_env))
-                {
-                    // remove last ring
-                    rings.pop_back();
-                }
-                rings.emplace_back();
-            }
-            part_env.init(x1,y1,x1,y1);
-        }
-        else if (first_line_to && cmd == T::line_to)
-        {
-            first_line_to = false;
-            rings.back().reserve(len+2);
-            #if defined(DEBUG)
-            pre_len = len+2;
-            #endif
-        }
-        else if (cmd == T::close)
-        {
-            auto & ring = rings.back();
-            if (ring.size() > 2 && !(ring.back().x == x2 && ring.back().y == y2))
-            {
-                ring.add_coord(x2,y2);
-            }
-            continue;
-        }
-        if (!first)
-        {
-            part_env.expand_to_include(x1,y1);
-        }
-        rings.back().add_coord(x1,y1);
-    }
-    if (!bbox.intersects(part_env))
-    {
-        // remove last ring
-        rings.pop_back();
-    }
-}
-
-template <typename ValueType, typename T>
-void decode_polygons(mapnik::geometry::geometry<ValueType> & geom, T && rings)
-{
-    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 == 0)
-    {
-        geom = mapnik::geometry::geometry_empty();
-    }
-    else if (num_rings == 1)
-    {
-        if (rings_itr->size() < 4)
-        {
-            return;
-        }
-        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<ValueType> poly;
-        poly.set_exterior_ring(std::move(*rings_itr));
-        geom = std::move(poly);
-    }
-    else
-    {
-        mapnik::geometry::multi_polygon<ValueType> multi_poly;
-        bool first = true;
-        bool is_clockwise = true;
-        for (; rings_itr != rings_end; ++rings_itr)
-        {
-            if (rings_itr->size() < 4)
-            {
-                continue; // skip degenerate rings
-            }
-            if (first)
-            {
-                is_clockwise = mapnik::util::is_clockwise(*rings_itr);
-                // first ring always exterior and sets all future winding order
-                multi_poly.emplace_back();
-                if (is_clockwise)
-                {
-                    // 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 (is_clockwise == mapnik::util::is_clockwise(*rings_itr))
-            {
-                // hit a new exterior ring, so start a new polygon
-                multi_poly.emplace_back(); // start new polygon
-                if (is_clockwise)
-                {
-                    // 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));
-            }
-            else
-            {
-                if (is_clockwise)
-                {
-                    // 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 == 1)
-        {
-            auto itr = std::make_move_iterator(multi_poly.begin());
-            geom = mapnik::geometry::polygon<ValueType>(std::move(*itr));
-        }
-        else
-        {
-            geom = std::move(multi_poly);
-        }
-    }
-}
-
-} // ns detail
+};
 
-template <typename ValueType, typename T>
-inline mapnik::geometry::geometry<ValueType> decode_geometry(T & paths, int32_t geom_type, mapnik::box2d<double> const& bbox)
-{
-    mapnik::geometry::geometry<ValueType> geom; // output geometry
-    switch (geom_type)
-    {
-    case vector_tile::Tile_GeomType_POINT:
-    {
-        detail::decode_point<ValueType, T>(geom, paths, bbox);
-        break;
-    }
-    case vector_tile::Tile_GeomType_LINESTRING:
-    {
-        detail::decode_linestring<ValueType, T>(geom, paths, bbox);
-        break;
-    }
-    case vector_tile::Tile_GeomType_POLYGON:
-    {
-        std::vector<mapnik::geometry::linear_ring<ValueType> > rings;
-        detail::read_rings<ValueType, T>(rings, paths, bbox);
-        if (rings.empty())
-        {
-            geom = mapnik::geometry::geometry_empty();
-        }
-        else
-        {
-            detail::decode_polygons(geom, rings);
-        }
-        break;
-    }
-    case vector_tile::Tile_GeomType_UNKNOWN:
-    default:
-    {
-        throw std::runtime_error("unhandled geometry type during decoding");
-        break;
-    }
-    }
-    return geom;
-}
+template <typename T>
+MAPNIK_VECTOR_INLINE mapnik::geometry::geometry<typename T::value_type> decode_geometry(T & paths, 
+                                                                   int32_t geom_type, 
+                                                                   unsigned version,
+                                                                   mapnik::box2d<double> const& bbox);
 
-// For back compatibility in tests / for cases where performance is not critical
-// TODO: consider removing and always requiring bbox arg
-template <typename ValueType, typename T>
-inline mapnik::geometry::geometry<ValueType> decode_geometry(T & paths, int32_t geom_type)
-{
+template <typename T>
+MAPNIK_VECTOR_INLINE mapnik::geometry::geometry<typename T::value_type> decode_geometry(T & paths, int32_t geom_type, unsigned version);
 
-    mapnik::box2d<double> bbox(std::numeric_limits<double>::lowest(),
-                               std::numeric_limits<double>::lowest(),
-                               std::numeric_limits<double>::max(),
-                               std::numeric_limits<double>::max());
-    return decode_geometry<ValueType>(paths,geom_type,bbox);
-}
+} // end ns vector_tile_impl
 
-}} // end ns
+} // end ns mapnik
 
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_geometry_decoder.ipp"
+#endif
 
 #endif // __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
diff --git a/src/vector_tile_geometry_decoder.ipp b/src/vector_tile_geometry_decoder.ipp
new file mode 100644
index 0000000..5a182f8
--- /dev/null
+++ b/src/vector_tile_geometry_decoder.ipp
@@ -0,0 +1,902 @@
+//protozero
+#include <protozero/pbf_reader.hpp>
+
+//mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/util/is_clockwise.hpp>
+#if defined(DEBUG)
+#include <mapnik/debug.hpp>
+#endif
+
+//std
+#include <algorithm>
+#include <cmath>
+#include <stdexcept>
+
+namespace mapnik 
+{ 
+    
+namespace vector_tile_impl 
+{
+
+namespace detail
+{
+
+template <typename value_type>
+inline void move_cursor(value_type & x, value_type & y, std::int32_t dx, std::int32_t dy, double scale_x_, double scale_y_)
+{
+    x += static_cast<value_type>(std::round(static_cast<double>(dx) / scale_x_));
+    y += static_cast<value_type>(std::round(static_cast<double>(dy) / scale_y_));
+}
+
+template <>
+inline void move_cursor<double>(double & x, double & y, std::int32_t dx, std::int32_t dy, double scale_x_, double scale_y_)
+{
+    x += static_cast<double>(dx) / scale_x_;
+    y += static_cast<double>(dy) / scale_y_;
+}
+
+template <typename T>
+void decode_point(mapnik::geometry::geometry<typename T::value_type> & geom, 
+                  T & paths, 
+                  mapnik::box2d<double> const& bbox)
+{
+    using value_type = typename T::value_type;
+    typename T::command cmd;
+    value_type x1, y1;
+    mapnik::geometry::multi_point<value_type> mp;
+    #if defined(DEBUG)
+    std::uint32_t previous_len = 0;
+    #endif
+    // Find first moveto inside bbox and then reserve points from size of geometry.
+    while (true)
+    {
+        cmd = paths.point_next(x1, y1);
+        if (cmd == T::end)
+        {
+            geom = std::move(mapnik::geometry::geometry_empty());
+            return;
+        } 
+        else if (bbox.intersects(x1,y1))
+        {
+            #if defined(DEBUG)
+            if (previous_len <= paths.get_length() && !paths.already_had_error)
+            {
+                MAPNIK_LOG_WARN(decode_point) << "warning: encountered POINT geometry that might have MOVETO commands repeated that could be fewer commands";
+                paths.already_had_error = true;
+            }
+            previous_len = paths.get_length();
+            #endif
+            mp.reserve(paths.get_length() + 1);
+            mp.emplace_back(x1,y1);
+            break;
+        }
+    }
+    while ((cmd = paths.point_next(x1, y1)) != T::end)
+    {
+        #if defined(DEBUG)
+        if (previous_len <= paths.get_length() && !paths.already_had_error)
+        {
+            MAPNIK_LOG_WARN(decode_point) << "warning: encountered POINT geometry that might have MOVETO commands repeated that could be fewer commands";
+            paths.already_had_error = true;
+        }
+        previous_len = paths.get_length();
+        #endif
+        // TODO: consider profiling and trying to optimize this further
+        // when all points are within the bbox filter then the `mp.reserve` should be
+        // perfect, but when some points are thrown out we will allocate more than needed
+        // the "all points intersect" case I think is going to be more common/important
+        // however worth a future look to see if the "some or few points intersect" can be optimized
+        if (!bbox.intersects(x1,y1))
+        {
+            continue;
+        }
+        mp.emplace_back(x1,y1);
+    }
+    std::size_t num_points = mp.size();
+    if (num_points == 0)
+    {
+        geom = std::move(mapnik::geometry::geometry_empty());
+    }
+    else if (num_points == 1)
+    {
+        geom = std::move(mp[0]);
+    }
+    else if (num_points > 1)
+    {
+        // return multipoint
+        geom = std::move(mp);
+    }
+}
+
+template <typename T>
+void decode_linestring(mapnik::geometry::geometry<typename T::value_type> & geom, 
+                       T & paths, 
+                       mapnik::box2d<double> const& bbox,
+                       unsigned version)
+{
+    using value_type = typename T::value_type;
+    typename T::command cmd;
+    value_type x0, y0;
+    value_type x1, y1;
+    mapnik::geometry::multi_line_string<value_type> multi_line;
+    #if defined(DEBUG)
+    std::uint32_t previous_len = 0;
+    #endif
+    mapnik::box2d<double> part_env;
+    cmd = paths.line_next(x0, y0, false);
+    if (cmd == T::end)
+    {
+        geom = std::move(mapnik::geometry::geometry_empty());
+        return;
+    }
+    else if (cmd != T::move_to)
+    {
+        throw std::runtime_error("Vector Tile has LINESTRING type geometry where the first command is not MOVETO.");
+    }
+
+    while (true)
+    {
+        cmd = paths.line_next(x1, y1, true);
+        if (cmd != T::line_to)
+        {
+            if (cmd == T::move_to)
+            {
+                if (version == 1)
+                {
+                    // Version 1 of the spec wasn't clearly defined and therefore
+                    // we shouldn't be strict on the reading of a tile that has two
+                    // moveto commands that are repeated, lets ignore the previous moveto.
+                    x0 = x1;
+                    y0 = y1;
+                    continue;
+                }
+                else
+                {
+                    throw std::runtime_error("Vector Tile has LINESTRING type geometry with repeated MOVETO commands.");
+                }
+            }
+            else //cmd == T::end
+            {   
+                if (version == 1)
+                {
+                    // Version 1 of the spec wasn't clearly defined and therefore
+                    // we shouldn't be strict on the reading of a tile that has only a moveto 
+                    // command. So lets just ignore this moveto command.
+                    break;
+                }
+                else
+                {
+                    throw std::runtime_error("Vector Tile has LINESTRING type geometry with a MOVETO command with no LINETO following.");
+                }
+            }
+        }
+        // add fresh line
+        multi_line.emplace_back();
+        auto & line = multi_line.back();
+        // reserve prior
+        line.reserve(paths.get_length() + 2);
+        // add moveto command position
+        line.add_coord(x0, y0);
+        part_env.init(x0, y0, x0, y0);
+        // add first lineto
+        line.add_coord(x1, y1);
+        part_env.expand_to_include(x1, y1);
+        #if defined(DEBUG)
+        previous_len = paths.get_length();
+        #endif
+        while ((cmd = paths.line_next(x1, y1, true)) == T::line_to)
+        {
+            line.add_coord(x1, y1);
+            part_env.expand_to_include(x1, y1);
+            #if defined(DEBUG)
+            if (previous_len <= paths.get_length() && !paths.already_had_error)
+            {
+                MAPNIK_LOG_WARN(decode_linestring) << "warning: encountered LINESTRING geometry that might have LINETO commands repeated that could be fewer commands";
+                paths.already_had_error = true;
+            }
+            previous_len = paths.get_length();
+            #endif
+        }
+        if (!bbox.intersects(part_env))
+        {
+            // remove last linestring
+            multi_line.pop_back();
+        }
+        if (cmd == T::end)
+        {
+            break;
+        } 
+        // else we are gauranteed it is a moveto
+        x0 = x1;
+        y0 = y1;
+    }
+    
+    std::size_t num_lines = multi_line.size();
+    if (num_lines == 0)
+    {
+        geom = std::move(mapnik::geometry::geometry_empty());
+    }
+    else if (num_lines == 1)
+    {
+        auto itr = std::make_move_iterator(multi_line.begin());
+        if (itr->size() > 1)
+        {
+            geom = std::move(*itr);
+        }
+    }
+    else if (num_lines > 1)
+    {
+        geom = std::move(multi_line);
+    }
+}
+
+template <typename T>
+void read_rings(std::vector<mapnik::geometry::linear_ring<typename T::value_type> > & rings,
+                T & paths, 
+                mapnik::box2d<double> const& bbox,
+                unsigned version)
+{
+    using value_type = typename T::value_type;
+    typename T::command cmd;
+    value_type x0, y0;
+    value_type x1, y1;
+    value_type x2, y2;
+    #if defined(DEBUG)
+    std::uint32_t previous_len;
+    #endif
+    mapnik::box2d<double> part_env;
+    cmd = paths.ring_next(x0, y0, false);
+    if (cmd == T::end)
+    {
+        return;
+    }
+    else if (cmd != T::move_to)
+    {
+        throw std::runtime_error("Vector Tile has POLYGON type geometry where the first command is not MOVETO.");
+    }
+
+    while (true)
+    {
+        cmd = paths.ring_next(x1, y1, true);
+        if (cmd != T::line_to)
+        {
+            if (cmd == T::close && version == 1)
+            {
+                // Version 1 of the specification we were not clear on the command requirements for polygons
+                // lets just to recover from this situation.
+                cmd = paths.ring_next(x0, y0, false);
+                if (cmd == T::end)
+                {
+                    break;
+                }
+                else if (cmd == T::move_to)
+                {
+                    continue;
+                }
+                else if (cmd == T::close)
+                {
+                    throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a CLOSE.");
+                }
+                else // cmd == T::line_to
+                {
+                    throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a LINETO.");
+                }
+            }
+            else // cmd == end || cmd == move_to
+            {
+                throw std::runtime_error("Vector Tile has POLYGON type geometry with a MOVETO command with out at least two LINETOs and CLOSE following.");
+            }
+        }
+        #if defined(DEBUG)
+        previous_len = paths.get_length();
+        #endif
+        cmd = paths.ring_next(x2, y2, true);
+        if (cmd != T::line_to)
+        {
+            if (cmd == T::close && version == 1)
+            {
+                // Version 1 of the specification we were not clear on the command requirements for polygons
+                // lets just to recover from this situation.
+                cmd = paths.ring_next(x0, y0, false);
+                if (cmd == T::end)
+                {
+                    break;
+                }
+                else if (cmd == T::move_to)
+                {
+                    continue;
+                }
+                else if (cmd == T::close)
+                {
+                    throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a CLOSE.");
+                }
+                else // cmd == T::line_to
+                {
+                    throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a LINETO.");
+                }
+            }
+            else // cmd == end || cmd == move_to
+            {
+                throw std::runtime_error("Vector Tile has POLYGON type geometry with a MOVETO command with out at least two LINETOs and CLOSE following.");
+            }
+        }
+        // add new ring to start adding to
+        rings.emplace_back();
+        auto & ring = rings.back();
+        // reserve prior
+        ring.reserve(paths.get_length() + 4);
+        // add moveto command position
+        ring.add_coord(x0, y0);
+        part_env.init(x0, y0, x0, y0);
+        // add first lineto
+        ring.add_coord(x1, y1);
+        part_env.expand_to_include(x1, y1);
+        // add second lineto
+        ring.add_coord(x2, y2);
+        part_env.expand_to_include(x2, y2);
+        #if defined(DEBUG)
+        if (previous_len <= paths.get_length() && !paths.already_had_error)
+        {
+            MAPNIK_LOG_WARN(read_rings) << "warning: encountered POLYGON geometry that might have LINETO commands repeated that could be fewer commands";
+            paths.already_had_error = true;
+        }
+        previous_len = paths.get_length();
+        #endif
+        while ((cmd = paths.ring_next(x1, y1, true)) == T::line_to)
+        {
+            ring.add_coord(x1,y1);
+            part_env.expand_to_include(x1,y1);
+            #if defined(DEBUG)
+            if (previous_len <= paths.get_length() && !paths.already_had_error)
+            {
+                MAPNIK_LOG_WARN(read_rings) << "warning: encountered POLYGON geometry that might have LINETO commands repeated that could be fewer commands";
+                paths.already_had_error = true;
+            }
+            previous_len = paths.get_length();
+            #endif
+        }
+        // Make sure we are now on a close command
+        if (cmd != T::close)
+        {
+            throw std::runtime_error("Vector Tile has POLYGON type geometry with a ring not closed by a CLOSE command.");
+        }
+        if (ring.back().x != x0 || ring.back().y != y0)
+        {
+            // If the previous lineto didn't already close the polygon (WHICH IT SHOULD NOT)
+            // close out the polygon ring.
+            ring.add_coord(x0,y0);
+        }
+
+        if (!bbox.intersects(part_env))
+        {
+            // remove last linestring
+            rings.pop_back();
+        }
+
+        cmd = paths.ring_next(x0, y0, false);
+        if (cmd == T::end)
+        {
+            break;
+        }
+        else if (cmd != T::move_to)
+        {
+            if (cmd == T::close)
+            {
+                throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a CLOSE.");
+            }
+            else // cmd == T::line_to
+            {
+                throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a LINETO.");
+            }
+        }
+    }
+}
+
+template <typename T1, typename T2>
+void decode_polygons(mapnik::geometry::geometry<T1> & geom, 
+                     T2 && rings, 
+                     unsigned version, 
+                     bool scaling_reversed_orientation)
+{
+    using value_type = T1;
+    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 == 0)
+    {
+        geom = std::move(mapnik::geometry::geometry_empty());
+    }
+    else if (num_rings == 1)
+    {
+        if (rings_itr->size() < 4)
+        {
+            if (version == 1)
+            {
+                geom = std::move(mapnik::geometry::geometry_empty());
+                return;
+            }
+            else
+            {
+                throw std::runtime_error("Vector Tile has POLYGON has ring with too few points. It is not valid according to v2 of VT spec.");
+            }
+        }
+        
+        // We are going to check if the current ring is clockwise, keeping in mind that
+        // the orientation could have flipped due to scaling.
+        if (mapnik::util::is_clockwise(*rings_itr))
+        {
+            if (scaling_reversed_orientation)
+            {
+                // Because the way we scaled changed the orientation of the ring (most likely due to a flipping of axis)
+                // we now have an exterior ring that clockwise. Internally in mapnik we want this to be counter clockwise
+                // so we must flip this.
+                std::reverse(rings_itr->begin(), rings_itr->end());
+            }
+            else
+            {
+                if (version == 1)
+                {
+                    std::reverse(rings_itr->begin(), rings_itr->end());
+                }
+                else
+                {
+                    throw std::runtime_error("Vector Tile has POLYGON with first ring clockwise. It is not valid according to v2 of VT spec.");
+                }
+            }
+        }
+        else if (scaling_reversed_orientation) // ring is counter clockwise
+        {
+            // ring is counter clockwise but scaling reversed it.
+            // this means in the vector tile it is incorrectly orientated.
+            if (version == 1)
+            {
+                std::reverse(rings_itr->begin(), rings_itr->end());
+            }
+            else
+            {
+                throw std::runtime_error("Vector Tile has POLYGON with first ring clockwise. It is not valid according to v2 of VT spec.");
+            }
+        }
+        // else ring is counter clockwise and scaling didn't reverse orientation.
+
+        // return the single polygon without interior rings
+        mapnik::geometry::polygon<value_type> poly;
+        poly.set_exterior_ring(std::move(*rings_itr));
+        geom = std::move(poly);
+    }
+    else if (version == 1)
+    {
+        // Version 1 didn't specify the winding order, so we are forced
+        // to do a best guess. This means assuming the first ring is an exterior
+        // ring and then basing all winding order off that. For this we can
+        // ignore if the scaling reversed the orientation.
+        mapnik::geometry::multi_polygon<value_type> multi_poly;
+        bool first = true;
+        bool first_is_clockwise = false;
+        for (; rings_itr != rings_end; ++rings_itr)
+        {
+            if (rings_itr->size() < 4)
+            {
+                continue;
+            }
+            if (first)
+            {
+                first_is_clockwise = mapnik::util::is_clockwise(*rings_itr);
+                // first ring always exterior and sets all future winding order
+                multi_poly.emplace_back();
+                if (first_is_clockwise)
+                {
+                    // 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_is_clockwise == mapnik::util::is_clockwise(*rings_itr))
+            {
+                // hit a new exterior ring, so start a new polygon
+                multi_poly.emplace_back(); // start new polygon
+                if (first_is_clockwise)
+                {
+                    // 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));
+            }
+            else
+            {
+                if (first_is_clockwise)
+                {
+                    // 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 == 1)
+        {
+            auto itr = std::make_move_iterator(multi_poly.begin());
+            geom = std::move(*itr);
+        }
+        else
+        {
+            geom = std::move(multi_poly);
+        }
+    }
+    else // if (version == 2)
+    {
+        mapnik::geometry::multi_polygon<value_type> multi_poly;
+        bool first = true;
+        for (; rings_itr != rings_end; ++rings_itr)
+        {
+            if (rings_itr->size() < 4)
+            {
+                throw std::runtime_error("Vector Tile has POLYGON has ring with too few points. It is not valid according to v2 of VT spec.");
+            }
+
+            if (first)
+            {
+                if (mapnik::util::is_clockwise(*rings_itr))
+                {
+                    if (scaling_reversed_orientation)
+                    {
+                        // Need to reverse ring so that it is proper going into mapnik.
+                        // as mapnik needs CCW exterior rings
+                        std::reverse(rings_itr->begin(), rings_itr->end());
+                    }
+                    else
+                    {
+                        throw std::runtime_error("Vector Tile has POLYGON with first ring clockwise. It is not valid according to v2 of VT spec.");
+                    }
+                } 
+                else if (scaling_reversed_orientation)
+                {
+                    throw std::runtime_error("Vector Tile has POLYGON with first ring clockwise. It is not valid according to v2 of VT spec.");
+                }
+                multi_poly.emplace_back();
+                multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+                first = false;
+            }
+            else if (mapnik::util::is_clockwise(*rings_itr)) // interior ring
+            {
+                if (scaling_reversed_orientation)
+                {
+                    // This is an exterior ring but needs to be reversed.
+                    std::reverse(rings_itr->begin(), rings_itr->end());
+                    multi_poly.emplace_back(); // start new polygon
+                    multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+                }
+                else
+                {
+                    // this is an interior ring
+                    multi_poly.back().add_hole(std::move(*rings_itr));
+                }
+            }
+            else 
+            {
+                if (scaling_reversed_orientation)
+                {
+                    // This is interior ring and it must be reversed.
+                    std::reverse(rings_itr->begin(), rings_itr->end());
+                    multi_poly.back().add_hole(std::move(*rings_itr));
+                }
+                else
+                {
+                    // hit a new exterior ring, so start a new polygon
+                    multi_poly.emplace_back(); // start new polygon
+                    multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+                }
+            }
+        }
+        
+        auto num_poly = multi_poly.size();
+        if (num_poly == 1)
+        {
+            auto itr = std::make_move_iterator(multi_poly.begin());
+            geom = std::move(*itr);
+        }
+        else
+        {
+            geom = std::move(multi_poly);
+        }
+    }
+}
+
+} // end ns detail
+
+template <typename T>
+GeometryPBF<T>::GeometryPBF(pbf_itr const& geo_iterator,
+                            value_type tile_x, 
+                            value_type tile_y,
+                            double scale_x, 
+                            double scale_y)
+    : geo_iterator_(geo_iterator),
+      scale_x_(scale_x),
+      scale_y_(scale_y),
+      x(tile_x),
+      y(tile_y),
+      ox(0), 
+      oy(0),
+      length(0),
+      cmd(move_to)
+{
+    #if defined(DEBUG)
+    already_had_error = false;
+    #endif
+}
+
+template <typename T>
+typename GeometryPBF<T>::command GeometryPBF<T>::point_next(value_type & rx, value_type & ry)
+{
+    if (length == 0) 
+    {
+        if (geo_iterator_.first != geo_iterator_.second)
+        {
+            uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
+            cmd = cmd_length & 0x7;
+            length = cmd_length >> 3;
+            if (cmd == move_to)
+            {
+                if (length == 0)
+                {
+                    throw std::runtime_error("Vector Tile has POINT geometry with a MOVETO command that has a command count of zero");
+                }
+            }
+            else
+            {
+                if (cmd == line_to)
+                {
+                    throw std::runtime_error("Vector Tile has POINT type geometry with a LINETO command.");
+                }
+                else if (cmd == close)
+                {
+                    throw std::runtime_error("Vector Tile has POINT type geometry with a CLOSE command.");
+                }
+                else
+                {
+                    throw std::runtime_error("Vector Tile has POINT type geometry with an unknown command.");
+                }
+            }
+        }
+        else 
+        {
+            return end;
+        }
+    }
+
+    --length;
+    // It is possible for the next to lines to throw because we can not check the length
+    // of the buffer to ensure that it is long enough.
+    // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+    // "end of buffer exception"
+    // While this error message is not verbose a try catch here would slow down processing.
+    int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+    int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+    detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+    rx = x;
+    ry = y;
+    return move_to;
+}
+
+template <typename T>
+typename GeometryPBF<T>::command GeometryPBF<T>::line_next(value_type & rx, 
+                                                           value_type & ry, 
+                                                           bool skip_lineto_zero)
+{
+    if (length == 0)
+    {
+        if (geo_iterator_.first != geo_iterator_.second)
+        {
+            uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
+            cmd = cmd_length & 0x7;
+            length = cmd_length >> 3;
+            if (cmd == move_to)
+            {
+                if (length != 1)
+                {
+                    throw std::runtime_error("Vector Tile has LINESTRING with a MOVETO command that is given more then one pair of parameters or not enough parameters are provided");
+                }
+                --length;
+                // It is possible for the next to lines to throw because we can not check the length
+                // of the buffer to ensure that it is long enough.
+                // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+                // "end of buffer exception"
+                // While this error message is not verbose a try catch here would slow down processing.
+                int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+                int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+                detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+                rx = x;
+                ry = y;
+                return move_to;
+            }
+            else if (cmd == line_to)
+            {
+                if (length == 0)
+                {
+                    throw std::runtime_error("Vector Tile has geometry with LINETO command that is not followed by a proper number of parameters");
+                }
+            }
+            else
+            {
+                if (cmd == close)
+                {
+                    throw std::runtime_error("Vector Tile has LINESTRING type geometry with a CLOSE command.");
+                }
+                else
+                {
+                    throw std::runtime_error("Vector Tile has LINESTRING type geometry with an unknown command.");
+                }
+            }
+        }
+        else
+        {
+            return end;
+        }
+    }
+
+    --length;
+    // It is possible for the next to lines to throw because we can not check the length
+    // of the buffer to ensure that it is long enough.
+    // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+    // "end of buffer exception"
+    // While this error message is not verbose a try catch here would slow down processing.
+    int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+    int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+    if (skip_lineto_zero && dx == 0 && dy == 0)
+    {
+        // We are going to skip this vertex as the point doesn't move call line_next again
+        return line_next(rx, ry, true);
+    }
+    detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+    rx = x;
+    ry = y;
+    return line_to;
+}
+
+template <typename T>
+typename GeometryPBF<T>::command GeometryPBF<T>::ring_next(value_type & rx, 
+                                                           value_type & ry, 
+                                                           bool skip_lineto_zero)
+{
+    if (length == 0)
+    {
+        if (geo_iterator_.first != geo_iterator_.second)
+        {
+            uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
+            cmd = cmd_length & 0x7;
+            length = cmd_length >> 3;
+            if (cmd == move_to)
+            {
+                if (length != 1)
+                {
+                    throw std::runtime_error("Vector Tile has POLYGON with a MOVETO command that is given more then one pair of parameters or not enough parameters are provided");
+                }
+                --length;
+                // It is possible for the next to lines to throw because we can not check the length
+                // of the buffer to ensure that it is long enough.
+                // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+                // "end of buffer exception"
+                // While this error message is not verbose a try catch here would slow down processing.
+                int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+                int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+                detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+                rx = x;
+                ry = y;
+                ox = x;
+                oy = y;
+                return move_to;
+            }
+            else if (cmd == line_to)
+            {
+                if (length == 0)
+                {
+                    throw std::runtime_error("Vector Tile has geometry with LINETO command that is not followed by a proper number of parameters");
+                }
+            }
+            else if (cmd == close)
+            {
+                // Just set length in case a close command provides an invalid number here.
+                // While we could throw because V2 of the spec declares it incorrect, this is not
+                // difficult to fix and has no effect on the results.
+                length = 0;
+                rx = ox;
+                ry = oy;
+                return close;
+            }
+            else
+            {
+                throw std::runtime_error("Vector Tile has POLYGON type geometry with an unknown command.");
+            }
+        }
+        else
+        {
+            return end;
+        }
+    }
+
+    --length;
+    // It is possible for the next to lines to throw because we can not check the length
+    // of the buffer to ensure that it is long enough.
+    // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+    // "end of buffer exception"
+    // While this error message is not verbose a try catch here would slow down processing.
+    int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+    int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+    if (skip_lineto_zero && dx == 0 && dy == 0)
+    {
+        // We are going to skip this vertex as the point doesn't move call ring_next again
+        return ring_next(rx, ry, true);
+    }
+    detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+    rx = x;
+    ry = y;
+    return line_to;
+}
+
+template <typename T>
+MAPNIK_VECTOR_INLINE mapnik::geometry::geometry<typename T::value_type> decode_geometry(T & paths, 
+                                                                   int32_t geom_type, 
+                                                                   unsigned version,
+                                                                   mapnik::box2d<double> const& bbox)
+{
+    using value_type = typename T::value_type;
+    mapnik::geometry::geometry<value_type> geom; // output geometry
+    switch (geom_type)
+    {
+    case Geometry_Type::POINT:
+    {
+        detail::decode_point<T>(geom, paths, bbox);
+        break;
+    }
+    case Geometry_Type::LINESTRING:
+    {
+        detail::decode_linestring<T>(geom, paths, bbox, version);
+        break;
+    }
+    case Geometry_Type::POLYGON:
+    {
+        std::vector<mapnik::geometry::linear_ring<value_type> > rings;
+        detail::read_rings<T>(rings, paths, bbox, version);
+        if (rings.empty())
+        {
+            geom = std::move(mapnik::geometry::geometry_empty());
+        }
+        else
+        {
+            detail::decode_polygons(geom, rings, version, paths.scaling_reversed_orientation());
+        }
+        break;
+    }
+    case Geometry_Type::UNKNOWN:
+    default:
+    {
+        // This was changed to not throw as unknown according to v2 of spec can simply be ignored and doesn't require
+        // it failing the processing
+        geom = std::move(mapnik::geometry::geometry_empty());
+        break;
+    }
+    }
+    return std::move(geom);
+}
+
+template <typename T>
+MAPNIK_VECTOR_INLINE mapnik::geometry::geometry<typename T::value_type> decode_geometry(T & paths, int32_t geom_type, unsigned version)
+{
+    mapnik::box2d<double> bbox(std::numeric_limits<double>::lowest(),
+                               std::numeric_limits<double>::lowest(),
+                               std::numeric_limits<double>::max(),
+                               std::numeric_limits<double>::max());
+    return decode_geometry(paths, geom_type, version, bbox);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_geometry_encoder.hpp b/src/vector_tile_geometry_encoder.hpp
deleted file mode 100644
index ed2c250..0000000
--- a/src/vector_tile_geometry_encoder.hpp
+++ /dev/null
@@ -1,215 +0,0 @@
-#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
-#define __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
-
-// vector tile
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
-
-#include <mapnik/geometry.hpp>
-#include "vector_tile_config.hpp"
-#include <protozero/varint.hpp>
-
-#include <cstdlib>
-#include <cmath>
-#include <sstream>
-#include <iostream>
-
-namespace mapnik { namespace vector_tile_impl {
-
-inline bool encode_geometry(mapnik::geometry::point<std::int64_t> const& pt,
-                        vector_tile::Tile_Feature & current_feature,
-                        int32_t & start_x,
-                        int32_t & start_y)
-{
-    current_feature.add_geometry(9); // move_to | (1 << 3)
-    int32_t dx = pt.x - start_x;
-    int32_t dy = pt.y - start_y;
-    // Manual zigzag encoding.
-    current_feature.add_geometry(protozero::encode_zigzag32(dx));
-    current_feature.add_geometry(protozero::encode_zigzag32(dy));
-    start_x = pt.x;
-    start_y = pt.y;
-    return true;
-}
-
-inline unsigned encode_length(unsigned len)
-{
-    return (len << 3u) | 2u;
-}
-
-inline bool 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)
-{
-    std::size_t line_size = line.size();
-    if (line_size <= 0)
-    {
-        return false;
-    }
-    unsigned line_to_length = static_cast<unsigned>(line_size) - 1;
-
-    enum {
-        move_to = 1,
-        line_to = 2,
-        coords = 3
-    } status = move_to;
-
-    for (auto const& pt : line)
-    {
-        if (status == move_to)
-        {
-            status = line_to;
-            current_feature.add_geometry(9); // move_to | (1 << 3)
-        }
-        else if (status == line_to)
-        {
-            status = coords;
-            current_feature.add_geometry(encode_length(line_to_length));
-        }
-        int32_t dx = pt.x - start_x;
-        int32_t dy = pt.y - start_y;
-        // Manual zigzag encoding.
-        current_feature.add_geometry(protozero::encode_zigzag32(dx));
-        current_feature.add_geometry(protozero::encode_zigzag32(dy));
-        start_x = pt.x;
-        start_y = pt.y;
-    }
-    return true;
-}
-
-inline bool 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)
-{
-    std::size_t ring_size = ring.size();
-    if (ring_size < 3)
-    {
-        return false;
-    }
-    unsigned line_to_length = static_cast<unsigned>(ring_size) - 1;
-    unsigned 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 false;
-        }
-    }
-
-    for (auto const& pt : ring)
-    {
-        if (status == move_to)
-        {
-            status = line_to;
-            current_feature.add_geometry(9); // move_to | (1 << 3)
-        }
-        else if (status == line_to)
-        {
-            status = coords;
-            current_feature.add_geometry(encode_length(line_to_length));
-        }
-        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(protozero::encode_zigzag32(dx));
-        current_feature.add_geometry(protozero::encode_zigzag32(dy));
-        start_x = pt.x;
-        start_y = pt.y;
-        ++count;
-    }
-    current_feature.add_geometry(15); // close_path
-    return true;
-}
-
-inline bool encode_geometry(mapnik::geometry::polygon<std::int64_t> const& poly,
-                            vector_tile::Tile_Feature & current_feature,
-                            int32_t & start_x,
-                            int32_t & start_y)
-{
-    if (!encode_geometry(poly.exterior_ring, current_feature, start_x, start_y))
-    {
-        return false;
-    }
-    for (auto const& ring : poly.interior_rings)
-    {
-        encode_geometry(ring, current_feature, start_x, start_y);
-    }
-    return true;
-}
-
-inline bool encode_geometry(mapnik::geometry::multi_point<std::int64_t> const& geom,
-                            vector_tile::Tile_Feature & current_feature,
-                            int32_t & start_x,
-                            int32_t & start_y)
-{
-    std::size_t geom_size = geom.size();
-    if (geom_size <= 0)
-    {
-        return false;
-    }
-    current_feature.add_geometry(1u | (geom_size << 3)); // move_to | (len << 3)
-    for (auto const& pt : geom)
-    {
-        int32_t dx = pt.x - start_x;
-        int32_t dy = pt.y - start_y;
-        // Manual zigzag encoding.
-        current_feature.add_geometry(protozero::encode_zigzag32(dx));
-        current_feature.add_geometry(protozero::encode_zigzag32(dy));
-        start_x = pt.x;
-        start_y = pt.y;
-    }
-    return true;
-}
-
-inline bool encode_geometry(mapnik::geometry::multi_line_string<std::int64_t> const& geom,
-                            vector_tile::Tile_Feature & current_feature,
-                            int32_t & start_x,
-                            int32_t & start_y)
-{
-    bool success = false;
-    for (auto const& poly : geom)
-    {
-        if (encode_geometry(poly, current_feature, start_x, start_y))
-        {
-            success = true;
-        }
-    }
-    return success;
-}
-
-inline bool encode_geometry(mapnik::geometry::multi_polygon<std::int64_t> const& geom,
-                            vector_tile::Tile_Feature & current_feature,
-                            int32_t & start_x,
-                            int32_t & start_y)
-{
-    bool success = false;
-    for (auto const& poly : geom)
-    {
-        if (encode_geometry(poly, current_feature, start_x, start_y))
-        {
-            success = true;
-        }
-    }
-    return success;
-}
-
-}} // end ns
-
-#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
diff --git a/src/vector_tile_geometry_encoder_pbf.cpp b/src/vector_tile_geometry_encoder_pbf.cpp
new file mode 100644
index 0000000..58fd224
--- /dev/null
+++ b/src/vector_tile_geometry_encoder_pbf.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_geometry_encoder_pbf.hpp"
+#include "vector_tile_geometry_encoder_pbf.ipp"
diff --git a/src/vector_tile_geometry_encoder_pbf.hpp b/src/vector_tile_geometry_encoder_pbf.hpp
new file mode 100644
index 0000000..28cbde4
--- /dev/null
+++ b/src/vector_tile_geometry_encoder_pbf.hpp
@@ -0,0 +1,67 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_PBF_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_PBF_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/varint.hpp>
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <cstdlib>
+#include <cmath>
+
+namespace mapnik
+{
+    
+namespace vector_tile_impl
+{
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::point<std::int64_t> const& pt,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_point<std::int64_t> const& geom,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::line_string<std::int64_t> const& line,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_line_string<std::int64_t> const& geom,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::polygon<std::int64_t> const& poly,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_polygon<std::int64_t> const& poly,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::geometry<std::int64_t> const& geom,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y);
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_geometry_encoder_pbf.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_PBF_H__
diff --git a/src/vector_tile_geometry_encoder_pbf.ipp b/src/vector_tile_geometry_encoder_pbf.ipp
new file mode 100644
index 0000000..a499eae
--- /dev/null
+++ b/src/vector_tile_geometry_encoder_pbf.ipp
@@ -0,0 +1,307 @@
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/varint.hpp>
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <cstdlib>
+#include <cmath>
+
+namespace mapnik
+{
+    
+namespace vector_tile_impl
+{
+
+namespace detail_pbf
+{
+
+template <typename Geometry>
+inline std::size_t repeated_point_count(Geometry const& geom)
+{
+    if (geom.size() < 2)
+    {
+        return 0;
+    }
+    std::size_t count = 0;
+    auto itr = geom.begin();
+
+    for (auto prev_itr = itr++; itr != geom.end(); ++prev_itr, ++itr)
+    {
+        if (itr->x == prev_itr->x && itr->y == prev_itr->y)
+        {
+            count++;
+        }
+    }
+    return count;
+}
+
+inline unsigned encode_length(unsigned len)
+{
+    return (len << 3u) | 2u;
+}
+
+struct encoder_visitor
+{
+    protozero::pbf_writer & feature_;
+    int32_t & x_;
+    int32_t & y_;
+
+    encoder_visitor(protozero::pbf_writer & feature,
+                    int32_t & x,
+                    int32_t & y) 
+        : feature_(feature),
+          x_(x),
+          y_(y) {}
+
+    bool operator() (mapnik::geometry::geometry_empty const&)
+    {
+        return false;
+    }
+
+    bool operator() (mapnik::geometry::geometry_collection<std::int64_t> const&)
+    {
+        throw std::runtime_error("Geometry collections can not be encoded as they may contain different geometry types");
+    }
+
+    template <typename T>
+    bool operator() (T const& geom)
+    {
+        return encode_geometry_pbf(geom, feature_, x_, y_);
+    }
+};
+
+inline bool encode_linestring(mapnik::geometry::line_string<std::int64_t> const& line,
+                              protozero::packed_field_uint32 & geometry,
+                              int32_t & start_x,
+                              int32_t & start_y)
+{
+    std::size_t line_size = line.size();
+    line_size -= detail_pbf::repeated_point_count(line);
+    if (line_size < 2)
+    {
+        return false;
+    }
+
+    unsigned line_to_length = static_cast<unsigned>(line_size) - 1;
+
+    auto pt = line.begin();
+    geometry.add_element(9); // move_to | (1 << 3)
+    geometry.add_element(protozero::encode_zigzag32(pt->x - start_x));
+    geometry.add_element(protozero::encode_zigzag32(pt->y - start_y));
+    start_x = pt->x;
+    start_y = pt->y;
+    geometry.add_element(detail_pbf::encode_length(line_to_length));
+    for (++pt; pt != line.end(); ++pt)
+    {
+        int32_t dx = pt->x - start_x;
+        int32_t dy = pt->y - start_y;
+        if (dx == 0 && dy == 0)
+        {
+            continue;
+        }
+        geometry.add_element(protozero::encode_zigzag32(dx));
+        geometry.add_element(protozero::encode_zigzag32(dy));
+        start_x = pt->x;
+        start_y = pt->y;
+    }
+    return true;
+}
+
+inline bool encode_linearring(mapnik::geometry::linear_ring<std::int64_t> const& ring,
+                              protozero::packed_field_uint32 & geometry,
+                              int32_t & start_x,
+                              int32_t & start_y)
+{
+    std::size_t ring_size = ring.size();
+    ring_size -= detail_pbf::repeated_point_count(ring);
+    if (ring_size < 3)
+    {
+        return false;
+    }
+    auto last_itr = ring.end();
+    if (ring.front() == ring.back())
+    {
+        --last_itr;
+        --ring_size;
+        if (ring_size < 3)
+        {
+            return false;
+        }
+    }
+
+    unsigned line_to_length = static_cast<unsigned>(ring_size) - 1;
+    auto pt = ring.begin();
+    geometry.add_element(9); // move_to | (1 << 3)
+    geometry.add_element(protozero::encode_zigzag32(pt->x - start_x));
+    geometry.add_element(protozero::encode_zigzag32(pt->y - start_y));
+    start_x = pt->x;
+    start_y = pt->y;
+    geometry.add_element(detail_pbf::encode_length(line_to_length));
+    for (++pt; pt != last_itr; ++pt)
+    {
+        int32_t dx = pt->x - start_x;
+        int32_t dy = pt->y - start_y;
+        if (dx == 0 && dy == 0)
+        {
+            continue;
+        }
+        geometry.add_element(protozero::encode_zigzag32(dx));
+        geometry.add_element(protozero::encode_zigzag32(dy));
+        start_x = pt->x;
+        start_y = pt->y;
+    }
+    geometry.add_element(15); // close_path
+    return true;
+}
+
+inline bool encode_polygon(mapnik::geometry::polygon<std::int64_t> const& poly,
+                           protozero::packed_field_uint32 & geometry,
+                           int32_t & start_x,
+                           int32_t & start_y)
+{
+    if (!encode_linearring(poly.exterior_ring, geometry, start_x, start_y))
+    {
+        return false;
+    }
+    for (auto const& ring : poly.interior_rings)
+    {
+        encode_linearring(ring, geometry, start_x, start_y);
+    }
+    return true;
+}
+
+} // end ns detail
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::point<std::int64_t> const& pt,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y)
+{
+    current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::POINT);
+    {
+        protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+        geometry.add_element(9);
+        int32_t dx = pt.x - start_x;
+        int32_t dy = pt.y - start_y;
+        // Manual zigzag encoding.
+        geometry.add_element(protozero::encode_zigzag32(dx));
+        geometry.add_element(protozero::encode_zigzag32(dy));
+        start_x = pt.x;
+        start_y = pt.y;
+    }
+    return true;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_point<std::int64_t> const& geom,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y)
+{
+    std::size_t geom_size = geom.size();
+    if (geom_size <= 0)
+    {
+        return false;
+    }
+    current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::POINT);
+    {
+        protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+        geometry.add_element(1u | (geom_size << 3)); // move_to | (len << 3)
+        for (auto const& pt : geom)
+        {
+            int32_t dx = pt.x - start_x;
+            int32_t dy = pt.y - start_y;
+            // Manual zigzag encoding.
+            geometry.add_element(protozero::encode_zigzag32(dx));
+            geometry.add_element(protozero::encode_zigzag32(dy));
+            start_x = pt.x;
+            start_y = pt.y;
+        }
+    }
+    return true;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::line_string<std::int64_t> const& line,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y)
+{
+    bool success = false;
+    current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::LINESTRING);
+    {
+        protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+        success = detail_pbf::encode_linestring(line, geometry, start_x, start_y);
+    }
+    return success;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_line_string<std::int64_t> const& geom,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y)
+{
+    bool success = false;
+    current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::LINESTRING);
+    {
+        protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+        for (auto const& line : geom)
+        {
+            if (detail_pbf::encode_linestring(line, geometry, start_x, start_y))
+            {
+                success = true;
+            }
+        }
+    }
+    return success;
+}
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::polygon<std::int64_t> const& poly,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y)
+{
+    bool success = false;
+    current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::POLYGON);
+    {
+        protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+        success = detail_pbf::encode_polygon(poly, geometry, start_x, start_y);
+    }
+    return success;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_polygon<std::int64_t> const& geom,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y)
+{
+    bool success = false;
+    current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::POLYGON);
+    {
+        protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+        for (auto const& poly : geom)
+        {
+            if (detail_pbf::encode_polygon(poly, geometry, start_x, start_y))
+            {
+                success = true;
+            }
+        }
+    }
+    return success;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::geometry<std::int64_t> const& geom,
+                                              protozero::pbf_writer & current_feature,
+                                              int32_t & start_x,
+                                              int32_t & start_y)
+{
+    detail_pbf::encoder_visitor ap(current_feature, start_x, start_y);
+    return mapnik::util::apply_visitor(ap, geom);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_geometry_feature.hpp b/src/vector_tile_geometry_feature.hpp
new file mode 100644
index 0000000..250f394
--- /dev/null
+++ b/src/vector_tile_geometry_feature.hpp
@@ -0,0 +1,86 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_FEATURE_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_FEATURE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+#include "vector_tile_layer.hpp"
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/feature.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/util/variant.hpp>
+
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+inline void raster_to_feature(std::string const& buffer,
+                              mapnik::feature_impl const& mapnik_feature,
+                              layer_builder_pbf & builder)
+{
+    std::vector<std::uint32_t> feature_tags;
+    protozero::pbf_writer layer_writer = builder.add_feature(mapnik_feature, feature_tags);
+    protozero::pbf_writer feature_writer(layer_writer, Layer_Encoding::FEATURES);
+    feature_writer.add_uint64(Feature_Encoding::ID, static_cast<std::uint64_t>(mapnik_feature.id()));
+    feature_writer.add_string(Feature_Encoding::RASTER, buffer);
+    feature_writer.add_packed_uint32(Feature_Encoding::TAGS, feature_tags.begin(), feature_tags.end());
+    builder.make_not_empty();
+}
+
+struct geometry_to_feature_pbf_visitor
+{
+    mapnik::feature_impl const& mapnik_feature_;
+    layer_builder_pbf & builder_;
+
+    geometry_to_feature_pbf_visitor(mapnik::feature_impl const& mapnik_feature,
+                                    layer_builder_pbf & builder)
+        : mapnik_feature_(mapnik_feature),
+          builder_(builder) {}
+
+    void operator() (mapnik::geometry::geometry_empty const&)
+    {
+        return;
+    }
+
+    template <typename T>
+    void operator() (T const& geom)
+    {
+        std::int32_t x = 0;
+        std::int32_t y = 0;
+        bool success = false;
+        std::vector<std::uint32_t> feature_tags;
+        protozero::pbf_writer layer_writer = builder_.add_feature(mapnik_feature_, feature_tags);
+        {
+            protozero::pbf_writer feature_writer(layer_writer, Layer_Encoding::FEATURES);
+            success = encode_geometry_pbf(geom, feature_writer, x, y);
+            if (success)
+            {
+                feature_writer.add_uint64(Feature_Encoding::ID, static_cast<std::uint64_t>(mapnik_feature_.id()));
+                feature_writer.add_packed_uint32(Feature_Encoding::TAGS, feature_tags.begin(), feature_tags.end());
+                builder_.make_not_empty();
+            }
+        }   
+        if (!success)
+        {
+            layer_writer.rollback();
+        }
+    }
+
+    void operator() (mapnik::geometry::geometry_collection<std::int64_t> const& collection)
+    {
+        for (auto & g : collection)
+        {
+            mapnik::util::apply_visitor((*this), g);
+        }
+    }
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_GEOMETRY_FEATURE_H__
diff --git a/src/vector_tile_geometry_intersects.cpp b/src/vector_tile_geometry_intersects.cpp
new file mode 100644
index 0000000..2238228
--- /dev/null
+++ b/src/vector_tile_geometry_intersects.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_geometry_intersects.hpp"
+#include "vector_tile_geometry_intersects.ipp"
diff --git a/src/vector_tile_geometry_intersects.hpp b/src/vector_tile_geometry_intersects.hpp
new file mode 100644
index 0000000..1b71109
--- /dev/null
+++ b/src/vector_tile_geometry_intersects.hpp
@@ -0,0 +1,55 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_INTERSECTS_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_INTERSECTS_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/util/variant.hpp>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+struct geometry_intersects 
+{
+    MAPNIK_VECTOR_INLINE geometry_intersects(mapnik::box2d<double> const& extent);
+
+    MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::geometry_empty const& geom);
+    
+    MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::point<double> const& geom);
+
+    MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::multi_point<double> const& geom);
+
+    MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::geometry_collection<double> const& geom);
+
+    MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::line_string<double> const& geom);
+    
+    MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::multi_line_string<double> const& geom);
+
+    MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::polygon<double> const& geom);
+
+    MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::multi_polygon<double> const& geom);
+    
+    mapnik::geometry::linear_ring<double> extent_;
+};
+
+inline bool intersects(mapnik::box2d<double> const& extent, mapnik::geometry::geometry<double> const& geom)
+{
+    return mapnik::util::apply_visitor(geometry_intersects(extent), geom); 
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_geometry_intersects.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_GEOMETRY_INTERSECTS_H__
+
diff --git a/src/vector_tile_geometry_intersects.ipp b/src/vector_tile_geometry_intersects.ipp
new file mode 100644
index 0000000..32fbc56
--- /dev/null
+++ b/src/vector_tile_geometry_intersects.ipp
@@ -0,0 +1,84 @@
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_adapters.hpp>
+
+// boost
+#include <boost/geometry.hpp>
+
+namespace mapnik 
+{
+
+namespace vector_tile_impl 
+{
+
+MAPNIK_VECTOR_INLINE geometry_intersects::geometry_intersects(mapnik::box2d<double> const & extent)
+    : extent_()
+{
+    extent_.reserve(5);
+    extent_.emplace_back(extent.minx(),extent.miny());
+    extent_.emplace_back(extent.maxx(),extent.miny());
+    extent_.emplace_back(extent.maxx(),extent.maxy());
+    extent_.emplace_back(extent.minx(),extent.maxy());
+    extent_.emplace_back(extent.minx(),extent.miny());
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::geometry_empty const& )
+{
+    return false;
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::point<double> const& )
+{
+    // The query should gaurantee that a point is within the bounding box!
+    return true;
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::multi_point<double> const& geom)
+{
+    for (auto const& g : geom)
+    {
+        if (boost::geometry::intersects(g, extent_))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::line_string<double> const& geom)
+{
+    return boost::geometry::intersects(geom, extent_);
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::multi_line_string<double> const& geom)
+{
+    return boost::geometry::intersects(geom, extent_);
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::polygon<double> const& geom)
+{
+    return boost::geometry::intersects(geom, extent_);
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::multi_polygon<double> const& geom)
+{
+    return boost::geometry::intersects(geom, extent_);
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::geometry_collection<double> const& geom)
+{
+    for (auto const& g : geom)
+    {
+        if (mapnik::util::apply_visitor(*this, g))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
diff --git a/src/vector_tile_geometry_simplifier.hpp b/src/vector_tile_geometry_simplifier.hpp
new file mode 100644
index 0000000..3cc117a
--- /dev/null
+++ b/src/vector_tile_geometry_simplifier.hpp
@@ -0,0 +1,99 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_SIMPLIFIER_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_SIMPLIFIER_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_adapters.hpp>
+
+// boost
+#include <boost/geometry/algorithms/simplify.hpp>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+template <typename NextProcessor>
+struct geometry_simplifier 
+{
+    geometry_simplifier(unsigned simplify_distance,
+                        NextProcessor & next)
+        : next_(next),
+          simplify_distance_(simplify_distance) {}
+
+    void operator() (mapnik::geometry::geometry_empty &)
+    {
+        return;
+    }
+
+    void operator() (mapnik::geometry::point<std::int64_t> & geom)
+    {
+        next_(geom);
+    }
+
+    void operator() (mapnik::geometry::multi_point<std::int64_t> & geom)
+    {
+        next_(geom);
+    }
+
+    void operator() (mapnik::geometry::line_string<std::int64_t> & geom)
+    {
+        mapnik::geometry::line_string<std::int64_t> simplified;
+        boost::geometry::simplify<
+                mapnik::geometry::line_string<std::int64_t>,
+                std::int64_t>
+                (geom, simplified, simplify_distance_);
+        next_(simplified);
+    }
+
+    void operator() (mapnik::geometry::multi_line_string<std::int64_t> & geom)
+    {
+        mapnik::geometry::multi_line_string<std::int64_t> simplified;
+        boost::geometry::simplify<
+                mapnik::geometry::multi_line_string<std::int64_t>,
+                std::int64_t>
+                (geom, simplified, simplify_distance_);
+        next_(simplified);
+    }
+
+    void operator() (mapnik::geometry::polygon<std::int64_t> & geom)
+    {
+        mapnik::geometry::polygon<std::int64_t> simplified;
+        boost::geometry::simplify<
+                mapnik::geometry::polygon<std::int64_t>,
+                std::int64_t>
+                (geom, simplified, simplify_distance_);
+        next_(simplified);
+    }
+
+    void operator() (mapnik::geometry::multi_polygon<std::int64_t> & geom)
+    {
+        mapnik::geometry::multi_polygon<std::int64_t> simplified;
+        boost::geometry::simplify<
+                mapnik::geometry::multi_polygon<std::int64_t>,
+                std::int64_t>
+                (geom, simplified, simplify_distance_);
+        next_(simplified);
+    }
+
+    void operator() (mapnik::geometry::geometry_collection<std::int64_t> & geom)
+    {
+        for (auto & g : geom)
+        {
+            mapnik::util::apply_visitor((*this), g);
+        }
+    }
+        
+    NextProcessor & next_;
+    unsigned simplify_distance_;
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_GEOMETRY_SIMPLIFIER_H__
diff --git a/src/vector_tile_is_valid.hpp b/src/vector_tile_is_valid.hpp
new file mode 100644
index 0000000..93dc82b
--- /dev/null
+++ b/src/vector_tile_is_valid.hpp
@@ -0,0 +1,304 @@
+#ifndef __MAPNIK_VECTOR_TILE_IS_VALID_H__
+#define __MAPNIK_VECTOR_TILE_IS_VALID_H__
+
+// mapnik protzero parsing configuration
+#include "vector_tile_config.hpp"
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+
+// std
+#include <sstream>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+enum validity_error : std::uint8_t
+{
+    TILE_REPEATED_LAYER_NAMES = 0,
+    TILE_HAS_UNKNOWN_TAG,
+    LAYER_HAS_NO_NAME,
+    LAYER_HAS_NO_EXTENT,
+    LAYER_HAS_NO_FEATURES,
+    LAYER_HAS_INVALID_VERSION,
+    LAYER_HAS_UNKNOWN_TAG,
+    VALUE_MULTIPLE_VALUES,
+    VALUE_NO_VALUE,
+    VALUE_HAS_UNKNOWN_TAG,
+    FEATURE_IS_EMPTY,
+    FEATURE_MULTIPLE_GEOM,
+    FEATURE_NO_GEOM_TYPE,
+    FEATURE_HAS_INVALID_GEOM_TYPE,
+    FEATURE_HAS_UNKNOWN_TAG,
+    INVALID_PBF_BUFFER
+};
+
+inline std::string validity_error_to_string(validity_error err)
+{
+    switch (err)
+    {
+        case TILE_REPEATED_LAYER_NAMES:
+            return "Vector Tile message has two or more layers with the same name";
+        case TILE_HAS_UNKNOWN_TAG:
+            return "Vector Tile message has an unknown tag";
+        case LAYER_HAS_NO_NAME:
+            return "Vector Tile Layer message has no name";
+        case LAYER_HAS_NO_EXTENT:
+            return "Vector Tile Layer message has no extent";
+        case LAYER_HAS_NO_FEATURES:
+            return "Vector Tile Layer message has no features";
+        case LAYER_HAS_INVALID_VERSION:
+            return "Vector Tile Layer message has an invalid version";
+        case LAYER_HAS_UNKNOWN_TAG:
+            return "Vector Tile Layer message has an unknown tag";
+        case VALUE_MULTIPLE_VALUES:
+            return "Vector Tile Value message contains more then one values";
+        case VALUE_NO_VALUE:
+            return "Vector Tile Value message contains no values";
+        case VALUE_HAS_UNKNOWN_TAG:
+            return "Vector Tile Value message has an unknown tag";
+        case FEATURE_IS_EMPTY:
+            return "Vector Tile Feature message has no geometry";
+        case FEATURE_MULTIPLE_GEOM:
+            return "Vector Tile Feature message has multiple geometries";
+        case FEATURE_NO_GEOM_TYPE:
+            return "Vector Tile Feature message is missing a geometry type";
+        case FEATURE_HAS_INVALID_GEOM_TYPE:
+            return "Vector Tile Feature message has an invalid geometry type";
+        case FEATURE_HAS_UNKNOWN_TAG:
+            return "Vector Tile Feature message has an unknown tag";
+        case INVALID_PBF_BUFFER:
+            return "Buffer is not encoded as a valid PBF";
+        default:
+            return "UNKNOWN ERROR";
+    }
+}
+
+inline void validity_error_to_string(std::set<validity_error> & errors, std::string & out)
+{
+    if (errors.empty())
+    {
+        return;
+    }
+    std::ostringstream err;
+    err << "Vector Tile Validity Errors Found:" << std::endl;
+    for (auto e : errors)
+    {
+        err << " - " << validity_error_to_string(e) << std::endl;
+    }
+    out.append(err.str());
+}
+
+inline void feature_is_valid(protozero::pbf_reader & feature_msg, std::set<validity_error> & errors)
+{
+    bool has_geom = false;
+    bool has_raster = false;
+    bool has_type = false;
+    while (feature_msg.next())
+    {
+        switch (feature_msg.tag())
+        {
+            case Feature_Encoding::ID: // id
+                feature_msg.skip();
+                break;
+            case Feature_Encoding::TAGS: // tags
+                feature_msg.get_packed_uint32();
+                break;
+            case Feature_Encoding::TYPE: // geom type
+                {
+                    std::int32_t type = feature_msg.get_enum();
+                    if (type <= 0 || type > 3)
+                    {
+                        errors.insert(FEATURE_HAS_INVALID_GEOM_TYPE);
+                    }
+                    has_type = true;
+                }
+                break;
+            case Feature_Encoding::GEOMETRY: // geometry
+                if (has_geom || has_raster)
+                {
+                    errors.insert(FEATURE_MULTIPLE_GEOM);
+                }
+                has_geom = true;
+                feature_msg.get_packed_uint32();
+                break;
+            case Feature_Encoding::RASTER: // raster
+                if (has_geom || has_raster)
+                {
+                    errors.insert(FEATURE_MULTIPLE_GEOM);
+                }
+                has_raster = true;
+                feature_msg.get_data();
+                break;
+            default:
+                errors.insert(FEATURE_HAS_UNKNOWN_TAG);
+                feature_msg.skip();
+                break;
+        }        
+    }
+    if (!has_geom && !has_raster)
+    {
+        errors.insert(FEATURE_IS_EMPTY);
+    }
+    if (has_geom && !has_type)
+    {
+        errors.insert(FEATURE_NO_GEOM_TYPE);
+    }
+}
+
+inline void value_is_valid(protozero::pbf_reader & value_msg, std::set<validity_error> & errors)
+{
+    bool has_value = false;
+    while (value_msg.next())
+    {
+        switch (value_msg.tag())
+        {
+            case Value_Encoding::STRING:
+            case Value_Encoding::FLOAT:
+            case Value_Encoding::DOUBLE:
+            case Value_Encoding::INT:
+            case Value_Encoding::UINT:
+            case Value_Encoding::SINT:
+            case Value_Encoding::BOOL:
+                if (has_value)
+                {
+                    errors.insert(VALUE_MULTIPLE_VALUES);
+                }
+                has_value = true;
+                value_msg.skip();
+                break;
+            default:
+                errors.insert(VALUE_HAS_UNKNOWN_TAG);
+                value_msg.skip();
+                break;
+        }
+    }
+    if (!has_value)
+    {
+        errors.insert(VALUE_NO_VALUE);
+    }
+}
+
+inline void layer_is_valid(protozero::pbf_reader & layer_msg, 
+                    std::set<validity_error> & errors, 
+                    std::string & layer_name,
+                    std::uint32_t & version)
+{
+    bool contains_a_feature = false;
+    bool contains_a_name = false;
+    bool contains_an_extent = false;
+    try
+    {
+        while (layer_msg.next())
+        {
+            switch (layer_msg.tag())
+            {
+                case Layer_Encoding::NAME: // name
+                    contains_a_name = true;
+                    layer_name = layer_msg.get_string();
+                    break;
+                case Layer_Encoding::FEATURES:
+                    {
+                        contains_a_feature = true;
+                        protozero::pbf_reader feature_msg = layer_msg.get_message();
+                        feature_is_valid(feature_msg, errors);
+                    }
+                    break;
+                case Layer_Encoding::KEYS: // keys
+                    layer_msg.skip();
+                    break;
+                case Layer_Encoding::VALUES: // value
+                    {
+                        protozero::pbf_reader value_msg = layer_msg.get_message();
+                        value_is_valid(value_msg, errors);
+                    }
+                    break;
+                case Layer_Encoding::EXTENT: // extent
+                    contains_an_extent = true;
+                    layer_msg.skip();
+                    break;
+                case Layer_Encoding::VERSION:
+                    version = layer_msg.get_uint32();
+                    break;
+                default:
+                    errors.insert(LAYER_HAS_UNKNOWN_TAG);
+                    layer_msg.skip();
+                    break;
+            }
+        }
+    }
+    catch (...)
+    {
+        errors.insert(INVALID_PBF_BUFFER);   
+    }
+    if (!contains_a_name)
+    {
+        errors.insert(LAYER_HAS_NO_NAME);
+    }
+    if (version > 2 || version == 0)
+    {
+        errors.insert(LAYER_HAS_INVALID_VERSION);
+    }
+    if (!contains_an_extent && version != 1)
+    {
+        errors.insert(LAYER_HAS_NO_EXTENT);
+    }
+    if (!contains_a_feature && version != 1)
+    {
+        errors.insert(LAYER_HAS_NO_FEATURES);
+    }
+}
+
+// Check some common things that could be wrong with a tile
+// does not check all items in line with the v2 spec. For
+// example does does not check validity of geometry. It also does not 
+// verify that all feature attributes are valid.
+inline void tile_is_valid(protozero::pbf_reader & tile_msg, std::set<validity_error> & errors)
+{
+    std::set<std::string> layer_names_set;
+    try
+    {
+        while (tile_msg.next())
+        {
+            switch (tile_msg.tag())
+            {
+                case Tile_Encoding::LAYERS:
+                    {
+                        protozero::pbf_reader layer_msg = tile_msg.get_message();
+                        std::string layer_name;
+                        std::uint32_t version = 1;
+                        layer_is_valid(layer_msg, errors, layer_name, version);
+                        auto p = layer_names_set.insert(layer_name);
+                        if (!p.second)
+                        {
+                            errors.insert(TILE_REPEATED_LAYER_NAMES);
+                        }
+                    }
+                    break;
+                default:
+                    errors.insert(TILE_HAS_UNKNOWN_TAG);
+                    tile_msg.skip();
+                    break;
+            }
+        }
+    }
+    catch (...)
+    {
+        errors.insert(INVALID_PBF_BUFFER);
+    }
+}
+
+inline void tile_is_valid(std::string const& tile, std::set<validity_error> & errors)
+{
+    protozero::pbf_reader tile_msg(tile);
+    tile_is_valid(tile_msg, errors);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_IS_VALID_H__
diff --git a/src/vector_tile_layer.cpp b/src/vector_tile_layer.cpp
new file mode 100644
index 0000000..2cb0e9f
--- /dev/null
+++ b/src/vector_tile_layer.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_layer.hpp"
+#include "vector_tile_layer.ipp"
diff --git a/src/vector_tile_layer.hpp b/src/vector_tile_layer.hpp
new file mode 100644
index 0000000..d70663d
--- /dev/null
+++ b/src/vector_tile_layer.hpp
@@ -0,0 +1,374 @@
+#ifndef __MAPNIK_VECTOR_TILE_LAYER_H__
+#define __MAPNIK_VECTOR_TILE_LAYER_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/feature.hpp>
+#include <mapnik/layer.hpp>
+#include <mapnik/map.hpp>
+#include <mapnik/projection.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/scale_denominator.hpp>
+#include <mapnik/value.hpp>
+#include <mapnik/view_transform.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <map>
+#include <unordered_map>
+#include <utility>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+struct layer_builder_pbf
+{
+    typedef std::map<std::string, unsigned> keys_container;
+    typedef std::unordered_map<mapnik::value, unsigned> values_container;
+
+    keys_container keys;
+    values_container values;
+    std::string & layer_buffer;
+    bool empty;
+    bool painted;
+
+    layer_builder_pbf(std::string const & name, std::uint32_t extent, std::string & _layer_buffer)
+        : keys(),
+          values(),
+          layer_buffer(_layer_buffer),
+          empty(true),
+          painted(false)
+    {
+        protozero::pbf_writer layer_writer(layer_buffer);
+        layer_writer.add_uint32(Layer_Encoding::VERSION, 2);
+        layer_writer.add_string(Layer_Encoding::NAME, name);
+        layer_writer.add_uint32(Layer_Encoding::EXTENT, extent);
+    }
+
+    void make_painted()
+    {
+        painted = true;
+    }
+
+    void make_not_empty()
+    {
+        empty = false;
+    }
+
+    MAPNIK_VECTOR_INLINE protozero::pbf_writer add_feature(mapnik::feature_impl const& mapnik_feature,
+                                                           std::vector<std::uint32_t> & feature_tags);
+};
+
+class tile_layer
+{
+private:
+    bool valid_;
+    mapnik::datasource_ptr ds_;
+    mapnik::projection target_proj_;
+    mapnik::projection source_proj_;
+    mapnik::proj_transform prj_trans_;
+    std::string buffer_;
+    std::string name_;
+    std::uint32_t layer_extent_;
+    mapnik::box2d<double> target_buffered_extent_;
+    mapnik::box2d<double> source_buffered_extent_;
+    mapnik::query query_;
+    mapnik::view_transform view_trans_;
+    bool empty_;
+    bool painted_;
+
+public:
+    tile_layer(mapnik::Map const& map,
+               mapnik::layer const& lay,
+               mapnik::box2d<double> const& tile_extent_bbox,
+               std::uint32_t tile_size,
+               std::int32_t buffer_size,
+               double scale_factor,
+               double scale_denom,
+               int offset_x,
+               int offset_y)
+        : valid_(true),
+          ds_(lay.datasource()),
+          target_proj_(map.srs(), true),
+          source_proj_(lay.srs(), true),
+          prj_trans_(target_proj_, source_proj_),
+          buffer_(),
+          name_(lay.name()),
+          layer_extent_(calc_extent(tile_size)),
+          target_buffered_extent_(calc_target_buffered_extent(tile_extent_bbox, buffer_size, lay, map)),
+          source_buffered_extent_(calc_source_buffered_extent()),
+          query_(calc_query(scale_factor, scale_denom, tile_extent_bbox, lay)),
+          view_trans_(layer_extent_, layer_extent_, tile_extent_bbox, offset_x, offset_y),
+          empty_(true),
+          painted_(false)
+    {
+    }
+
+
+    tile_layer(tile_layer && rhs)
+        : valid_(std::move(rhs.valid_)),
+          ds_(std::move(rhs.ds_)),
+          target_proj_(std::move(rhs.target_proj_)),
+          source_proj_(std::move(rhs.source_proj_)),
+          prj_trans_(target_proj_, source_proj_),
+          buffer_(std::move(rhs.buffer_)),
+          name_(std::move(rhs.name_)),
+          layer_extent_(std::move(rhs.layer_extent_)),
+          target_buffered_extent_(std::move(rhs.target_buffered_extent_)),
+          source_buffered_extent_(std::move(rhs.source_buffered_extent_)),
+          query_(std::move(rhs.query_)),
+          view_trans_(std::move(rhs.view_trans_)),
+          empty_(std::move(rhs.empty_)),
+          painted_(std::move(rhs.painted_)) {}
+
+    tile_layer& operator=(tile_layer&&) = default;
+
+    tile_layer(tile_layer const& rhs) = delete;
+    tile_layer& operator=(const tile_layer&) = delete;
+
+    std::uint32_t calc_extent(std::uint32_t layer_extent)
+    {
+        if (!ds_)
+        {
+            valid_ = false;
+            return 4096;
+        }
+        auto ds_extent = ds_->params().template get<mapnik::value_integer>("vector_layer_extent");
+        if (ds_extent)
+        {
+            layer_extent = *ds_extent;
+        }
+        if (layer_extent == 0)
+        {
+            valid_ = false;
+            layer_extent = 4096;
+        }
+        return layer_extent;
+    }
+
+    mapnik::box2d<double> calc_target_buffered_extent(mapnik::box2d<double> const& tile_extent_bbox,
+                                               std::int32_t buffer_size,
+                                               mapnik::layer const& lay,
+                                               mapnik::Map const& map) const
+    {
+        mapnik::box2d<double> ext(tile_extent_bbox);
+        double scale = ext.width() / layer_extent_;
+        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
+        {
+            if (!ds_ || ds_->type() == datasource::Vector)
+            {
+                buffer_padding *= (*layer_buffer_size) * (static_cast<double>(layer_extent_) / VT_LEGACY_IMAGE_SIZE);
+            }
+            else
+            {
+                buffer_padding *= (*layer_buffer_size) * (static_cast<double>(layer_extent_));
+            }
+        }
+        else
+        {
+            buffer_padding *= buffer_size;
+        }
+        double buffered_width = ext.width() + buffer_padding;
+        double buffered_height = ext.height() + buffer_padding;
+        if (buffered_width < 0.0)
+        {
+            buffered_width = 0.0;
+        }
+        if (buffered_height < 0.0)
+        {
+            buffered_height = 0.0;
+        }
+        ext.width(buffered_width);
+        ext.height(buffered_height);
+        
+        boost::optional<box2d<double> > const& maximum_extent = map.maximum_extent();
+        if (maximum_extent)
+        {
+            ext.clip(*maximum_extent);
+        }
+        return ext;
+    }
+    
+    mapnik::box2d<double> calc_source_buffered_extent()
+    {
+        mapnik::box2d<double> new_extent(target_buffered_extent_);
+        if (!prj_trans_.forward(new_extent, PROJ_ENVELOPE_POINTS))
+        {
+            // this modifies the layer_ext by clipping to the buffered_ext
+            valid_ = false;
+        }
+        return new_extent;
+    }
+
+    mapnik::query calc_query(double scale_factor,
+                             double scale_denom,
+                             mapnik::box2d<double> const& tile_extent_bbox,
+                             mapnik::layer const& lay)
+    {
+        // Adjust the scale denominator if required
+        if (scale_denom <= 0.0)
+        {
+            double scale = tile_extent_bbox.width() / VT_LEGACY_IMAGE_SIZE;
+            scale_denom = mapnik::scale_denominator(scale, target_proj_.is_geographic());
+        }
+        scale_denom *= scale_factor;
+        if (!lay.visible(scale_denom))
+        {
+            valid_ = false;
+        }
+
+        mapnik::box2d<double> query_extent(lay.envelope()); // source projection
+
+        // first, try intersection of map extent forward projected into layer srs
+        if (source_buffered_extent_.intersects(query_extent))
+        {
+            // this modifies the query_extent by clipping to the buffered_ext
+            query_extent.clip(source_buffered_extent_);
+        }
+        // if no intersection and projections are also equal, early return
+        else if (prj_trans_.equal())
+        {
+            valid_ = false;
+        }
+        // next try intersection of layer extent back projected into map srs
+        else if (prj_trans_.backward(query_extent, PROJ_ENVELOPE_POINTS) && target_buffered_extent_.intersects(query_extent))
+        {
+            query_extent.clip(target_buffered_extent_);
+            // forward project layer extent back into native projection
+            if (!prj_trans_.forward(query_extent, PROJ_ENVELOPE_POINTS))
+            {
+                throw std::runtime_error("vector_tile_processor: layer extent did not repoject back to map projection");
+            }
+        }
+        else
+        {
+            // if no intersection then nothing to do for layer
+            valid_ = false;    
+        }
+        double qw = tile_extent_bbox.width() > 0 ? tile_extent_bbox.width() : 1;
+        double qh = tile_extent_bbox.height() > 0 ? tile_extent_bbox.height() : 1;
+        if (!ds_ || ds_->type() == datasource::Vector)
+        {
+            qw = VT_LEGACY_IMAGE_SIZE / qw;
+            qh = VT_LEGACY_IMAGE_SIZE / qh;
+        }
+        else
+        {
+            qw = static_cast<double>(layer_extent_) / qw;
+            qh = static_cast<double>(layer_extent_) / qh;
+        }
+        mapnik::query::resolution_type res(qw, qh);
+        mapnik::query q(query_extent, res, scale_denom, tile_extent_bbox);
+        if (ds_)
+        {
+            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());
+            }
+        }
+        return q;
+    }
+
+    mapnik::datasource_ptr get_ds() const
+    {
+        return ds_;
+    }
+
+    mapnik::featureset_ptr get_features() const
+    {
+        return ds_->features(query_);
+    }
+
+    mapnik::view_transform const& get_view_transform() const
+    {
+        return view_trans_;
+    }
+
+    mapnik::proj_transform const& get_proj_transform() const
+    {
+        return prj_trans_;
+    }
+
+    mapnik::box2d<double> const& get_source_buffered_extent() const
+    {
+        return source_buffered_extent_;
+    }
+
+    mapnik::box2d<double> const& get_target_buffered_extent() const
+    {
+        return target_buffered_extent_;
+    }
+
+    bool is_valid() const
+    {
+        return valid_;
+    }
+
+    std::uint32_t layer_extent() const
+    {
+        return layer_extent_;
+    }
+
+    std::string const& name() const
+    {
+        return name_;
+    }
+    
+    void name(std::string const& val)
+    {
+        name_ = val;
+    }
+
+    bool is_empty() const
+    {
+        return empty_;
+    }
+     
+    bool is_painted() const
+    {
+        return painted_;
+    }
+    
+    void make_painted()
+    {
+        painted_ = true;
+    }
+
+    std::string const& get_data() const
+    {
+        return buffer_;
+    }
+
+    std::string & get_data()
+    {
+        return buffer_;
+    }
+
+    void build(layer_builder_pbf const& builder)
+    {
+        empty_ = builder.empty;
+        painted_ = builder.painted;
+    }
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_layer.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_LAYER_H__
diff --git a/src/vector_tile_layer.ipp b/src/vector_tile_layer.ipp
new file mode 100644
index 0000000..121450c
--- /dev/null
+++ b/src/vector_tile_layer.ipp
@@ -0,0 +1,131 @@
+// mapnik
+#include <mapnik/feature.hpp>
+#include <mapnik/util/variant.hpp>
+#include <mapnik/value.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <map>
+#include <unordered_map>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+namespace detail
+{
+
+struct to_tile_value_pbf
+{
+public:
+    to_tile_value_pbf(protozero::pbf_writer & value):
+        value_(value) {}
+
+    void operator () ( value_integer val ) const
+    {
+        value_.add_int64(Value_Encoding::INT,val);
+    }
+
+    void operator () ( mapnik::value_bool val ) const
+    {
+        value_.add_bool(Value_Encoding::BOOL,val);
+    }
+
+    void operator () ( mapnik::value_double val ) const
+    {
+        float fval = static_cast<float>(val);
+        if (val == static_cast<double>(fval))
+        {
+            value_.add_float(Value_Encoding::FLOAT, fval);
+        }
+        else
+        {
+            value_.add_double(Value_Encoding::DOUBLE, val);
+        }
+    }
+
+    void operator () ( mapnik::value_unicode_string const& val ) const
+    {
+        std::string str;
+        to_utf8(val, str);
+        value_.add_string(Value_Encoding::STRING, str);
+    }
+
+    void operator () ( mapnik::value_null const& /*val*/ ) const
+    {
+        // do nothing
+    }
+private:
+    protozero::pbf_writer & value_;
+};
+
+} // end ns detail
+
+MAPNIK_VECTOR_INLINE protozero::pbf_writer layer_builder_pbf::add_feature(mapnik::feature_impl const& mapnik_feature,
+                                                                          std::vector<std::uint32_t> & feature_tags)
+
+{
+    protozero::pbf_writer layer_writer(layer_buffer);
+    // Feature id should be unique from mapnik so we should comply with
+    // the following wording of the specification:
+    // "the value of the (feature) id SHOULD be unique among the features of the parent layer."
+
+    // note that feature.id is signed int64_t so we are casting.
+    
+    // Mapnik features can not have more then one value for
+    // a single key. Therefore, we do not have to check if
+    // key already exists in the feature as we insert each
+    // key value pair into the feature. 
+    feature_kv_iterator itr = mapnik_feature.begin();
+    feature_kv_iterator end = mapnik_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.
+                layer_writer.add_string(Layer_Encoding::KEYS, name);
+                size_t index = keys.size();
+                keys.emplace(name, index);
+                feature_tags.push_back(index);
+            }
+            else
+            {
+                feature_tags.push_back(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.
+                {
+                    protozero::pbf_writer value_writer(layer_writer, Layer_Encoding::VALUES);
+                    detail::to_tile_value_pbf visitor(value_writer);
+                    mapnik::util::apply_visitor(visitor, val);
+                }
+                size_t index = values.size();
+                values.emplace(val, index);
+                feature_tags.push_back(index);
+            }
+            else
+            {
+                feature_tags.push_back(val_itr->second);
+            }
+        }
+    }
+    return layer_writer;
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_load_tile.hpp b/src/vector_tile_load_tile.hpp
new file mode 100644
index 0000000..d83b6c1
--- /dev/null
+++ b/src/vector_tile_load_tile.hpp
@@ -0,0 +1,135 @@
+#ifndef __MAPNIK_VECTOR_TILE_LOAD_TILE_H__
+#define __MAPNIK_VECTOR_TILE_LOAD_TILE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+#include "vector_tile_compression.hpp"
+#include "vector_tile_tile.hpp"
+#include "vector_tile_datasource_pbf.hpp"
+#include "vector_tile_is_valid.hpp"
+
+// mapnik
+#include <mapnik/map.hpp>
+#include <mapnik/layer.hpp>
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <set>
+#include <string>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+void merge_from_buffer(merc_tile & t, const char * data, std::size_t size)
+{
+    using ds_ptr = std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf>;
+    protozero::pbf_reader tile_msg(data, size);
+    std::vector<ds_ptr> ds_vec;
+    while (tile_msg.next())
+    {
+        switch (tile_msg.tag())
+        {
+            case Tile_Encoding::LAYERS:
+                {
+                    auto layer_data = tile_msg.get_data();
+                    protozero::pbf_reader layer_valid_msg(layer_data);
+                    std::set<validity_error> errors;             
+                    std::string layer_name;
+                    std::uint32_t version = 1;
+                    layer_is_valid(layer_valid_msg, errors, layer_name, version);
+                    if (!errors.empty())
+                    {   
+                        std::string layer_errors;
+                        validity_error_to_string(errors, layer_errors);
+                        throw std::runtime_error(layer_errors);
+                    }
+                    if (t.has_layer(layer_name))
+                    {
+                        continue;
+                    }
+                    if (version == 1)
+                    {
+                        // v1 tiles will be convereted to v2
+                        protozero::pbf_reader layer_msg(layer_data);
+                        ds_vec.push_back(std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(
+                                    layer_msg,
+                                    t.x(),
+                                    t.y(),
+                                    t.z(),
+                                    true));
+                    }
+                    else
+                    {
+                        t.append_layer_buffer(layer_data.first, layer_data.second, layer_name);
+                    }
+                }
+                break;
+            default:
+                throw std::runtime_error("Vector Tile Buffer contains invalid tag");
+                break;
+        }
+    }
+    if (ds_vec.empty())
+    {
+        return;
+    }
+    // Convert v1 tiles to v2
+    std::int32_t prev_buffer_size = t.buffer_size();
+    t.buffer_size(4096); // very large buffer so we don't miss any buffered points
+    mapnik::Map map(t.tile_size(), t.tile_size(), "+init=epsg:3857");
+    for (auto ds : ds_vec)
+    {    
+        ds->set_envelope(t.get_buffered_extent());
+        mapnik::layer lyr(ds->get_name(), "+init=epsg:3857");
+        lyr.set_datasource(ds);
+        map.add_layer(lyr);
+    }
+    mapnik::vector_tile_impl::processor ren(map);
+    ren.set_multi_polygon_union(true);
+    ren.set_fill_type(mapnik::vector_tile_impl::even_odd_fill);
+    ren.set_process_all_rings(true);
+    //ren.set_simplify_distance(4.0);
+    ren.update_tile(t);
+    t.buffer_size(prev_buffer_size);
+}
+
+void merge_from_compressed_buffer(merc_tile & t, const char * data, std::size_t size)
+{
+    if (mapnik::vector_tile_impl::is_gzip_compressed(data,size) ||
+        mapnik::vector_tile_impl::is_zlib_compressed(data,size))
+    {
+        std::string decompressed;
+        mapnik::vector_tile_impl::zlib_decompress(data, size, decompressed);
+        return merge_from_buffer(t, decompressed.data(), decompressed.size());
+    }
+    else
+    {
+        return merge_from_buffer(t, data, size); 
+    }
+}
+
+void add_image_buffer_as_tile_layer(merc_tile & t, std::string const& layer_name, const char * data, std::size_t size)
+{
+    std::string layer_buffer;
+    protozero::pbf_writer layer_writer(layer_buffer);
+    layer_writer.add_uint32(Layer_Encoding::VERSION, 2);
+    layer_writer.add_string(Layer_Encoding::NAME, layer_name);
+    layer_writer.add_uint32(Layer_Encoding::EXTENT, 4096);
+    {
+        protozero::pbf_writer feature_writer(layer_writer, Layer_Encoding::FEATURES);
+        feature_writer.add_bytes(Feature_Encoding::RASTER, data, size);
+    }
+    t.append_layer_buffer(layer_buffer.data(), layer_buffer.size(), layer_name);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_LOAD_TILE_H__
diff --git a/src/vector_tile_merc_tile.hpp b/src/vector_tile_merc_tile.hpp
new file mode 100644
index 0000000..ffd1a31
--- /dev/null
+++ b/src/vector_tile_merc_tile.hpp
@@ -0,0 +1,82 @@
+#ifndef __MAPNIK_VECTOR_TILE_MERC_TILE_H__
+#define __MAPNIK_VECTOR_TILE_MERC_TILE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_tile.hpp"
+#include "vector_tile_projection.hpp"
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+class merc_tile : public tile
+{
+private:
+    std::uint32_t x_;
+    std::uint32_t y_;
+    std::uint32_t z_;
+public:
+    merc_tile(std::uint32_t x,
+              std::uint32_t y,
+              std::uint32_t z,
+              std::uint32_t tile_size = 4096,
+              std::int32_t buffer_size = 128)
+        : tile(merc_extent(tile_size, x, y, z), tile_size, buffer_size),
+          x_(x),
+          y_(y),
+          z_(z) {}
+
+    merc_tile(merc_tile const& rhs) = default;
+
+    merc_tile(merc_tile && rhs) = default;
+    
+    bool same_extent(merc_tile & other)
+    {
+        return x_ == other.x_ &&
+               y_ == other.y_ &&
+               z_ == other.z_;
+    }
+    
+    std::uint32_t x() const
+    {
+        return x_;
+    }
+
+    void x(std::uint32_t x)
+    {
+        x_ = x;
+        extent_ = merc_extent(tile_size_, x_, y_, z_);
+    }
+    
+    std::uint32_t y() const
+    {
+        return y_;
+    }
+    
+    void y(std::uint32_t y)
+    {
+        y_ = y;
+        extent_ = merc_extent(tile_size_, x_, y_, z_);
+    }
+    
+    std::uint32_t z() const
+    {
+        return z_;
+    }
+    
+    void z(std::uint32_t z)
+    {
+        z_ = z;
+        extent_ = merc_extent(tile_size_, x_, y_, z_);
+    }
+};
+
+typedef std::shared_ptr<merc_tile> merc_tile_ptr;
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_MERC_TILE_H__
diff --git a/src/vector_tile_processor.cpp b/src/vector_tile_processor.cpp
index 4239e31..a72b04d 100644
--- a/src/vector_tile_processor.cpp
+++ b/src/vector_tile_processor.cpp
@@ -1,4 +1,2 @@
 #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 21442ca..0868010 100644
--- a/src/vector_tile_processor.hpp
+++ b/src/vector_tile_processor.hpp
@@ -1,28 +1,27 @@
 #ifndef __MAPNIK_VECTOR_PROCESSOR_H__
 #define __MAPNIK_VECTOR_PROCESSOR_H__
 
-#include <mapnik/map.hpp>
-#include <mapnik/layer.hpp>
+// mapnik
 #include <mapnik/feature.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>
-#include "clipper.hpp"
+#include <mapnik/layer.hpp>
+#include <mapnik/map.hpp>
+#include <mapnik/request.hpp>
+#include <mapnik/util/noncopyable.hpp>
 
+// mapnik-vector-tile
 #include "vector_tile_config.hpp"
+#include "vector_tile_tile.hpp"
+#include "vector_tile_merc_tile.hpp"
 
-namespace mapnik { namespace vector_tile_impl {
+// std
+#include <future>
 
-enum polygon_fill_type : std::uint8_t {
-    even_odd_fill = 0, 
-    non_zero_fill, 
-    positive_fill, 
-    negative_fill,
-    polygon_fill_type_max
-};
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
 
 /*
   This processor combines concepts from mapnik's
@@ -33,88 +32,171 @@ enum polygon_fill_type : std::uint8_t {
   that would normally come from a style's symbolizers
 */
 
-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_;
+    std::string image_format_;
     double scale_factor_;
-    mapnik::view_transform t_;
     double area_threshold_;
-    bool strictly_simple_;
-    std::string image_format_;
-    scaling_method_e scaling_method_;
-    bool painted_;
     double simplify_distance_;
+    polygon_fill_type fill_type_;
+    scaling_method_e scaling_method_;
+    bool strictly_simple_;
     bool multi_polygon_union_;
-    ClipperLib::PolyFillType fill_type_;
     bool process_all_rings_;
+    std::launch threading_mode_;
+
 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,
-              bool strictly_simple=false,
-              std::string const& image_format="jpeg",
-              scaling_method_e scaling_method=SCALING_NEAR
-        );
-
-    inline void set_simplify_distance(double dist)
+    processor(mapnik::Map const& map)
+        : m_(map),
+          image_format_("webp"),
+          scale_factor_(1.0),
+          area_threshold_(0.1),
+          simplify_distance_(0.0),
+          fill_type_(positive_fill),
+          scaling_method_(SCALING_BILINEAR),
+          strictly_simple_(true),
+          multi_polygon_union_(false),
+          process_all_rings_(false),
+          threading_mode_(std::launch::deferred) {}
+
+    MAPNIK_VECTOR_INLINE void update_tile(tile & t,
+                                          double scale_denom = 0.0,
+                                          int offset_x = 0,
+                                          int offset_y = 0);
+
+    merc_tile create_tile(std::uint64_t x,
+                          std::uint64_t y,
+                          std::uint64_t z,
+                          std::uint32_t tile_size = 4096,
+                          std::int32_t buffer_size = 0,
+                          double scale_denom = 0.0,
+                          int offset_x = 0,
+                          int offset_y = 0)
+    {
+        merc_tile t(x, y, z, tile_size, buffer_size);
+        update_tile(t, scale_denom, offset_x, offset_y);
+        return t;
+    }
+    
+    tile create_tile(mapnik::box2d<double> const & extent,
+                     std::uint32_t tile_size = 4096,
+                     std::int32_t buffer_size = 0,
+                     double scale_denom = 0.0,
+                     int offset_x = 0,
+                     int offset_y = 0)
+    {
+        tile t(extent, tile_size, buffer_size);
+        update_tile(t, scale_denom, offset_x, offset_y);
+        return t;
+    }
+
+    void set_simplify_distance(double dist)
     {
         simplify_distance_ = dist;
     }
+    
+    double get_simplify_distance() const
+    {
+        return simplify_distance_;
+    }
+
+    void set_area_threshold(double value)
+    {
+        area_threshold_ = value;
+    }
+    
+    double get_area_threshold() const
+    {
+        return area_threshold_;
+    }
+
+    void set_scale_factor(double value)
+    {
+        scale_factor_ = value;
+    }
+    
+    double get_scale_factor() const
+    {
+        return scale_factor_;
+    }
 
-    inline void set_process_all_rings(bool value)
+    void set_process_all_rings(bool value)
     {
         process_all_rings_ = value;
     }
+    
+    bool get_process_all_rings() const
+    {
+        return process_all_rings_;
+    }
 
-    inline void set_multi_polygon_union(bool value)
+    void set_multi_polygon_union(bool value)
     {
         multi_polygon_union_ = value;
     }
-
-    inline double get_simplify_distance() const
+    
+    bool get_multi_polygon_union() const
     {
-        return simplify_distance_;
+        return multi_polygon_union_;
+    }
+    
+    void set_strictly_simple(bool value)
+    {
+        strictly_simple_ = value;
+    }
+    
+    bool get_multipolygon_union() const
+    {
+        return strictly_simple_;
+    }
+    
+    void set_fill_type(polygon_fill_type type)
+    {
+        fill_type_ = type;
+    }
+    
+    polygon_fill_type set_fill_type() const
+    {
+        return fill_type_;
     }
 
-    inline mapnik::view_transform const& get_transform()
+    void set_scaling_method(scaling_method_e type)
     {
-        return t_;
+        scaling_method_ = type;
     }
     
-    MAPNIK_VECTOR_INLINE void set_fill_type(polygon_fill_type type);
-
-    MAPNIK_VECTOR_INLINE void apply(double scale_denom=0.0);
-
-    MAPNIK_VECTOR_INLINE bool painted() const;
-
-    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);
-
-    template <typename T2>
-    MAPNIK_VECTOR_INLINE bool handle_geometry(T2 const& vs,
-                                              mapnik::feature_impl const& feature,
-                                              mapnik::geometry::geometry<double> const& geom,
-                                              mapnik::box2d<int> const& tile_clipping_extent,
-                                              mapnik::box2d<double> const& target_clipping_extent);
+    scaling_method_e set_scaling_method() const
+    {
+        return scaling_method_;
+    }
+
+    void set_image_format(std::string const& value)
+    {
+        image_format_ = value;
+    }
+
+    std::string const& get_image_format() const
+    {
+        return image_format_;
+    }
+
+    void set_threading_mode(std::launch mode)
+    {
+        threading_mode_ = mode;
+    }
+
+    std::launch set_threading_mode() const
+    {
+        return threading_mode_;
+    }
+
 };
 
-}} // end ns
+} // end ns vector_tile_impl
+
+} // end ns mapnik
 
 #if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
 #include "vector_tile_processor.ipp"
diff --git a/src/vector_tile_processor.ipp b/src/vector_tile_processor.ipp
index f521d67..c5bdc04 100644
--- a/src/vector_tile_processor.ipp
+++ b/src/vector_tile_processor.ipp
@@ -1,1443 +1,371 @@
-#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/scale_denominator.hpp>
-#include <mapnik/attribute_descriptor.hpp>
-#include <mapnik/feature_layer_desc.hpp>
-#include <mapnik/config.hpp>
+// mapnik-vector-tile
+#include "vector_tile_geometry_clipper.hpp"
+#include "vector_tile_geometry_feature.hpp"
+#include "vector_tile_geometry_intersects.hpp"
+#include "vector_tile_geometry_simplifier.hpp"
+#include "vector_tile_raster_clipper.hpp"
+#include "vector_tile_strategy.hpp"
+#include "vector_tile_tile.hpp"
+#include "vector_tile_layer.hpp"
+
+// mapnik
 #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/datasource.hpp>
+#include <mapnik/feature.hpp>
 #include <mapnik/image_scaling.hpp>
-#include <mapnik/image_compositing.hpp>
-#include <mapnik/view_transform.hpp>
-#include <mapnik/util/noncopyable.hpp>
-#include <mapnik/transform_path_adapter.hpp>
-#include <mapnik/geometry_is_empty.hpp>
-#include <mapnik/geometry_adapters.hpp>
+#include <mapnik/layer.hpp>
+#include <mapnik/map.hpp>
 #include <mapnik/geometry_transform.hpp>
 
-// 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"
-
+// boost
 #include <boost/optional.hpp>
-#include <boost/geometry/algorithms/simplify.hpp>
-#include <boost/geometry/algorithms/intersection.hpp>
-
-#include <iostream>
-#include <string>
-#include <stdexcept>
-
-#include "vector_tile_strategy.hpp"
 
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
+// std
+#include <future>
 
-namespace mapnik { namespace vector_tile_impl {
-
-template <typename T>
-struct visitor_raster_processor
+namespace mapnik 
 {
-    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_, true, true);
-        mapnik::raster target(target_ext_, std::move(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_pre;
-        using renderer_type = agg::renderer_base<pixfmt_type>;
-
-        mapnik::image_rgba8 im_tile(width_, height_, 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_);
-        mapnik::demultiply_alpha(im_tile);
-        backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
-        backend_.stop_tile_feature();
-        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,
-              bool strictly_simple,
-              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),
-      strictly_simple_(strictly_simple),
-      image_format_(image_format),
-      scaling_method_(scaling_method),
-      painted_(false),
-      simplify_distance_(0.0),
-      multi_polygon_union_(false),
-      fill_type_(ClipperLib::pftNonZero),
-      process_all_rings_(false) {}
-
-template <typename T>
-void processor<T>::apply(double scale_denom)
+namespace vector_tile_impl 
 {
-    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>::set_fill_type(polygon_fill_type type)
+namespace detail
 {
-    switch (type) 
-    {
-    case polygon_fill_type_max:
-    case even_odd_fill:
-        fill_type_ = ClipperLib::pftEvenOdd;
-        break; 
-    case non_zero_fill: 
-        fill_type_ = ClipperLib::pftNonZero;
-        break;
-    case positive_fill:
-        fill_type_ = ClipperLib::pftPositive;
-        break; 
-    case negative_fill:
-        fill_type_ = ClipperLib::pftNegative;
-        break;
-    }
-}
-
 
-template <typename T>
-void processor<T>::apply_to_layer(mapnik::layer const& lay,
-                    mapnik::projection const& target_proj,
-                    double scale,
-                    double scale_denom,
-                    unsigned width,
-                    unsigned height,
-                    box2d<double> const& extent,
-                    int buffer_size)
+inline void create_geom_layer(tile_layer & layer,
+                              double simplify_distance,
+                              double area_threshold,
+                              polygon_fill_type fill_type,
+                              bool strictly_simple,
+                              bool multi_polygon_union,
+                              bool process_all_rings)
 {
-    mapnik::datasource_ptr ds = lay.datasource();
-    if (!ds) return;
-
-    mapnik::projection source_proj(lay.srs(),true);
-
-    // set up a transform from target to source
-    // target == final map (aka tile) projection, usually epsg:3857
-    // source == projection of the data being queried
-    mapnik::proj_transform prj_trans(target_proj,source_proj);
-
-    // working version of unbuffered extent
-    box2d<double> query_ext(extent);
-
-    // working version of buffered extent
-    box2d<double> buffered_query_ext(query_ext);
-
-    // transform the user-driven buffer size into the right
-    // size buffer into the target projection
-    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);
+    layer_builder_pbf builder(layer.name(), layer.layer_extent(), layer.get_data());
 
-    // ^^ now `buffered_query_ext` is actually buffered out.
+    // query for the features
+    mapnik::featureset_ptr features = layer.get_features();
 
-    // clip buffered extent by maximum extent, if supplied
-    // Note: Carto.js used to set this by default but no longer does after:
-    // https://github.com/mapbox/carto/pull/342
-    boost::optional<box2d<double>> const& maximum_extent = m_.maximum_extent();
-    if (maximum_extent)
+    if (!features)
     {
-        buffered_query_ext.clip(*maximum_extent);
+        return;
     }
 
-    // buffered_query_ext is transformed below
-    // into the coordinate system of the source data
-    // so grab a pristine copy of it to use later
-    box2d<double> target_clipping_extent(buffered_query_ext);
+    mapnik::feature_ptr feature = features->next();
 
-    mapnik::box2d<double> layer_ext = lay.envelope();
-    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))
-    {
-        // this modifies the layer_ext by clipping to the buffered_query_ext
-        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)
+    if (!feature)
     {
         return;
     }
-
-    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())
+    
+    using encoding_process = mapnik::vector_tile_impl::geometry_to_feature_pbf_visitor;
+    using clipping_process = mapnik::vector_tile_impl::geometry_clipper<encoding_process>;
+    if (simplify_distance > 0)
     {
-        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
-        if (prj_trans.equal())
+        using simplifier_process = mapnik::vector_tile_impl::geometry_simplifier<clipping_process>;
+        if (layer.get_proj_transform().equal())
         {
-            mapnik::vector_tile_impl::vector_tile_strategy vs(t_,backend_.get_path_multiplier());
-            mapnik::geometry::point<double> p1_min(target_clipping_extent.minx(), target_clipping_extent.miny());
-            mapnik::geometry::point<double> p1_max(target_clipping_extent.maxx(), target_clipping_extent.maxy());
+            using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy;
+            using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, simplifier_process>;
+            strategy_type vs(layer.get_view_transform());
+            mapnik::box2d<double> const& buffered_extent = layer.get_target_buffered_extent();
+            mapnik::geometry::point<double> p1_min(buffered_extent.minx(), buffered_extent.miny());
+            mapnik::geometry::point<double> p1_max(buffered_extent.maxx(), buffered_extent.maxy());
             mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
             mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
-            box2d<int> tile_clipping_extent(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
+            double minx = std::min(p2_min.x, p2_max.x);
+            double maxx = std::max(p2_min.x, p2_max.x);
+            double miny = std::min(p2_min.y, p2_max.y);
+            double maxy = std::max(p2_min.y, p2_max.y);
+            mapnik::box2d<int> tile_clipping_extent(minx, miny, maxx, maxy);
             while (feature)
             {
                 mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
-                if (mapnik::geometry::is_empty(geom))
-                {
-                    feature = features->next();
-                    continue;
-                }
-                if (handle_geometry(vs,
-                                    *feature,
-                                    geom,
-                                    tile_clipping_extent,
-                                    target_clipping_extent))
-                {
-                    painted_ = true;
-                }
+                encoding_process encoder(*feature, builder);
+                clipping_process clipper(tile_clipping_extent, 
+                                         area_threshold, 
+                                         strictly_simple, 
+                                         multi_polygon_union, 
+                                         fill_type,
+                                         process_all_rings,
+                                         encoder);
+                simplifier_process simplifier(simplify_distance, clipper);
+                transform_type transformer(vs, buffered_extent, simplifier);
+                mapnik::util::apply_visitor(transformer, geom);
                 feature = features->next();
             }
         }
         else
         {
-            mapnik::vector_tile_impl::vector_tile_strategy vs(t_,backend_.get_path_multiplier());
-            mapnik::geometry::point<double> p1_min(target_clipping_extent.minx(), target_clipping_extent.miny());
-            mapnik::geometry::point<double> p1_max(target_clipping_extent.maxx(), target_clipping_extent.maxy());
+            using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy_proj;
+            using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, simplifier_process>;
+            mapnik::vector_tile_impl::vector_tile_strategy vs(layer.get_view_transform());
+            mapnik::box2d<double> const& buffered_extent = layer.get_target_buffered_extent();
+            mapnik::geometry::point<double> p1_min(buffered_extent.minx(), buffered_extent.miny());
+            mapnik::geometry::point<double> p1_max(buffered_extent.maxx(), buffered_extent.maxy());
             mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
             mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
-            box2d<int> tile_clipping_extent(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
-            mapnik::vector_tile_impl::vector_tile_strategy_proj vs2(prj_trans,t_,backend_.get_path_multiplier());
-            prj_trans.forward(target_clipping_extent, PROJ_ENVELOPE_POINTS);
+            double minx = std::min(p2_min.x, p2_max.x);
+            double maxx = std::max(p2_min.x, p2_max.x);
+            double miny = std::min(p2_min.y, p2_max.y);
+            double maxy = std::max(p2_min.y, p2_max.y);
+            mapnik::box2d<int> tile_clipping_extent(minx, miny, maxx, maxy);
+            strategy_type vs2(layer.get_proj_transform(), layer.get_view_transform());
+            mapnik::box2d<double> const& trans_buffered_extent = layer.get_source_buffered_extent();
             while (feature)
             {
                 mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
-                if (mapnik::geometry::is_empty(geom))
-                {
-                    feature = features->next();
-                    continue;
-                }
-                if (handle_geometry(vs2,
-                                    *feature,
-                                    geom,
-                                    tile_clipping_extent,
-                                    target_clipping_extent) > 0)
-                {
-                    painted_ = true;
-                }
+                encoding_process encoder(*feature, builder);
+                clipping_process clipper(tile_clipping_extent, 
+                                         area_threshold, 
+                                         strictly_simple, 
+                                         multi_polygon_union, 
+                                         fill_type,
+                                         process_all_rings,
+                                         encoder);
+                simplifier_process simplifier(simplify_distance, clipper);
+                transform_type transformer(vs2, trans_buffered_extent, simplifier);
+                mapnik::util::apply_visitor(transformer, geom);
                 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
+    else
     {
-        double outer_area = ClipperLib::Area(polygon.exterior_ring);
-        if (std::abs(outer_area) >= area_threshold)
+        if (layer.get_proj_transform().equal())
         {
-            // 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)
+            using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy;
+            using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, clipping_process>;
+            strategy_type vs(layer.get_view_transform());
+            mapnik::box2d<double> const& buffered_extent = layer.get_target_buffered_extent();
+            mapnik::geometry::point<double> p1_min(buffered_extent.minx(), buffered_extent.miny());
+            mapnik::geometry::point<double> p1_max(buffered_extent.maxx(), buffered_extent.maxy());
+            mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
+            mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
+            double minx = std::min(p2_min.x, p2_max.x);
+            double maxx = std::max(p2_min.x, p2_max.x);
+            double miny = std::min(p2_min.y, p2_max.y);
+            double maxy = std::max(p2_min.y, p2_max.y);
+            mapnik::box2d<int> tile_clipping_extent(minx, miny, maxx, maxy);
+            while (feature)
             {
-                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));
+                mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
+                encoding_process encoder(*feature, builder);
+                clipping_process clipper(tile_clipping_extent, 
+                                         area_threshold, 
+                                         strictly_simple, 
+                                         multi_polygon_union, 
+                                         fill_type,
+                                         process_all_rings,
+                                         encoder);
+                transform_type transformer(vs, buffered_extent, clipper);
+                mapnik::util::apply_visitor(transformer, geom);
+                feature = features->next();
             }
-            mp.emplace_back(std::move(polygon));
         }
-    }
-    for (auto * ring : polynode->Childs)
-    {
-        for (auto * sub_ring : ring->Childs)
+        else
         {
-            process_polynode_branch(sub_ring, mp, area_threshold);
+            using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy_proj;
+            using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, clipping_process>;
+            mapnik::vector_tile_impl::vector_tile_strategy vs(layer.get_view_transform());
+            mapnik::box2d<double> const& buffered_extent = layer.get_target_buffered_extent();
+            mapnik::geometry::point<double> p1_min(buffered_extent.minx(), buffered_extent.miny());
+            mapnik::geometry::point<double> p1_max(buffered_extent.maxx(), buffered_extent.maxy());
+            mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
+            mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
+            double minx = std::min(p2_min.x, p2_max.x);
+            double maxx = std::max(p2_min.x, p2_max.x);
+            double miny = std::min(p2_min.y, p2_max.y);
+            double maxy = std::max(p2_min.y, p2_max.y);
+            mapnik::box2d<int> tile_clipping_extent(minx, miny, maxx, maxy);
+            strategy_type vs2(layer.get_proj_transform(), layer.get_view_transform());
+            mapnik::box2d<double> const& trans_buffered_extent = layer.get_source_buffered_extent();
+            while (feature)
+            {
+                mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
+                encoding_process encoder(*feature, builder);
+                clipping_process clipper(tile_clipping_extent, 
+                                         area_threshold, 
+                                         strictly_simple, 
+                                         multi_polygon_union, 
+                                         fill_type,
+                                         process_all_rings,
+                                         encoder);
+                transform_type transformer(vs2, trans_buffered_extent, clipper);
+                mapnik::util::apply_visitor(transformer, geom);
+                feature = features->next();
+            }
         }
     }
+    layer.build(builder);
+    return;
 }
 
-template <typename T>
-struct encoder_visitor 
+inline void create_raster_layer(tile_layer & layer,
+                                std::string const& image_format,
+                                scaling_method_e scaling_method)
 {
-    typedef T backend_type;
-    encoder_visitor(backend_type & backend,
-                    mapnik::feature_impl const& feature,
-                    mapnik::box2d<int> const& tile_clipping_extent,
-                    double area_threshold,
-                    bool strictly_simple,
-                    bool multi_polygon_union,
-                    ClipperLib::PolyFillType fill_type,
-                    bool process_all_rings) :
-      backend_(backend),
-      feature_(feature),
-      tile_clipping_extent_(tile_clipping_extent),
-      area_threshold_(area_threshold),
-      strictly_simple_(strictly_simple),
-      multi_polygon_union_(multi_polygon_union),
-      fill_type_(fill_type),
-      process_all_rings_(process_all_rings) {}
+    layer_builder_pbf builder(layer.name(), layer.layer_extent(), layer.get_data());
 
-    bool operator() (mapnik::geometry::geometry_empty const&)
-    {
-        return false;
-    }
+    // query for the features
+    mapnik::featureset_ptr features = layer.get_features();
 
-    bool operator() (mapnik::geometry::geometry_collection<std::int64_t> & geom)
+    if (!features)
     {
-        bool painted = false;
-        for (auto & g : geom)
-        {
-            if (mapnik::util::apply_visitor((*this), g))
-            {
-                painted = true;
-            }
-        }
-        return painted;
+        return;
     }
 
-    bool operator() (mapnik::geometry::point<std::int64_t> const& geom)
-    {
-        backend_.start_tile_feature(feature_);
-        backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POINT);
-        bool painted = backend_.add_path(geom);
-        backend_.stop_tile_feature();
-        return painted;
-    }
+    mapnik::feature_ptr feature = features->next();
 
-    bool operator() (mapnik::geometry::multi_point<std::int64_t> const& geom)
+    if (!feature)
     {
-        bool painted = false;
-        if (!geom.empty())
-        {
-            backend_.start_tile_feature(feature_);
-            backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POINT);
-            painted = backend_.add_path(geom);
-            backend_.stop_tile_feature();
-        }
-        return painted;
+        return;
     }
-
-    bool operator() (mapnik::geometry::line_string<std::int64_t> & geom)
+    
+    mapnik::raster_ptr const& source = feature->get_raster();
+    if (!source)
     {
-        bool painted = false;
-        boost::geometry::unique(geom);
-        if (geom.size() < 2)
-        {
-            // This is false because it means the original data was invalid
-            return false;
-        }
-        std::deque<mapnik::geometry::line_string<int64_t>> result;
-        mapnik::geometry::linear_ring<std::int64_t> clip_box;
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
-        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
-        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
-        boost::geometry::intersection(clip_box,geom,result);
-        if (!result.empty())
-        {
-            // Found some data in tile so painted is now true
-            painted = true;
-            // Add the data to the tile
-            backend_.start_tile_feature(feature_);
-            backend_.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
-            for (auto const& ls : result)
-            {
-                backend_.add_path(ls);
-            }
-            backend_.stop_tile_feature();
-        }
-        return painted;
+        return;
     }
 
-    bool operator() (mapnik::geometry::multi_line_string<std::int64_t> & geom)
+    mapnik::box2d<double> target_ext = box2d<double>(source->ext_);
+    
+    layer.get_proj_transform().backward(target_ext, PROJ_ENVELOPE_POINTS);
+    mapnik::box2d<double> ext = layer.get_view_transform().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)
+    {
+        builder.make_painted();
+        raster_clipper visit(*source,
+                             target_ext,
+                             ext,
+                             layer.get_proj_transform(),
+                             image_format,
+                             scaling_method,
+                             layer.layer_extent(),
+                             layer.layer_extent(),
+                             raster_width,
+                             raster_height,
+                             start_x,
+                             start_y);
+        std::string buffer = mapnik::util::apply_visitor(visit, source->data_);
+        raster_to_feature(buffer, *feature, builder);
+    }
+    layer.build(builder);
+    return;
+}
+
+} // end ns detail
+
+MAPNIK_VECTOR_INLINE void processor::update_tile(tile & t,
+                                                 double scale_denom,
+                                                 int offset_x,
+                                                 int offset_y)
+{
+    // Futures
+    std::vector<tile_layer> tile_layers;
+    
+    for (mapnik::layer const& lay : m_.layers())
     {
-        bool painted = false;
-        mapnik::geometry::linear_ring<std::int64_t> clip_box;
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
-        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
-        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
-        bool first = true;
-        boost::geometry::unique(geom);
-        for (auto const& line : geom)
+        if (t.has_layer(lay.name()))
         {
-            if (line.size() < 2)
-            {
-               continue;
-            }
-            // If any line reaches here painted is now true because
-            std::deque<mapnik::geometry::line_string<int64_t>> result;
-            boost::geometry::intersection(clip_box,line,result);
-            if (!result.empty())
-            {
-                if (first)
-                {
-                    painted = true;
-                    first = false;
-                    backend_.start_tile_feature(feature_);
-                    backend_.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
-                }
-                for (auto const& ls : result)
-                {
-                    backend_.add_path(ls);
-                }
-            }
+            continue;
         }
-        if (!first)
+        tile_layers.emplace_back(m_,
+                             lay,
+                             t.extent(),
+                             t.tile_size(),
+                             t.buffer_size(),
+                             scale_factor_,
+                             scale_denom,
+                             offset_x,
+                             offset_y);
+        if (!tile_layers.back().is_valid())
         {
-            backend_.stop_tile_feature();
+            t.add_empty_layer(lay.name());
+            tile_layers.pop_back();
+            continue;
         }
-        return painted;
     }
-
-    bool operator() (mapnik::geometry::polygon<std::int64_t> & geom)
+ 
+    if (threading_mode_ == std::launch::deferred)
     {
-        bool painted = false;
-        if ((geom.exterior_ring.size() < 3) && !process_all_rings_)
+        for (auto & layer_ref : tile_layers)
         {
-            // Invalid geometry so will be false
-            return false;
-        }
-        // Because of geometry cleaning and other methods
-        // we automatically call this tile painted even if there is no intersections.
-        painted = true;
-        double clean_distance = 1.415;
-        mapnik::geometry::linear_ring<std::int64_t> clip_box;
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
-        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
-        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.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_)  && !process_all_rings_)
-        {
-            return painted;
-        }
-        // 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;
-        
-        if (strictly_simple_) 
-        {
-            poly_clipper.StrictlySimple(true);
-        }
-        if (!poly_clipper.AddPath(geom.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
-        {
-            return painted;
-        }
-        for (auto & ring : geom.interior_rings)
-        {
-            if (ring.size() < 3) 
+            if (layer_ref.get_ds()->type() == datasource::Vector)
             {
-                continue;
+                detail::create_geom_layer(layer_ref,
+                                          simplify_distance_,
+                                          area_threshold_,
+                                          fill_type_,
+                                          strictly_simple_,
+                                          multi_polygon_union_,
+                                          process_all_rings_
+                                         );
             }
-            ClipperLib::CleanPolygon(ring, clean_distance);
-            double inner_area = ClipperLib::Area(ring);
-            if (std::abs(inner_area) < area_threshold_)
+            else // Raster
             {
-                continue;
+                detail::create_raster_layer(layer_ref,
+                                            image_format_,
+                                            scaling_method_
+                                           );
             }
-            // 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 painted;
-        }
-        ClipperLib::PolyTree polygons;
-        poly_clipper.ReverseSolution(true);
-        poly_clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type_, fill_type_);
-        poly_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 painted;
         }
-
-        backend_.start_tile_feature(feature_);
-        backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POLYGON);
-        
-        for (auto const& poly : mp)
-        {
-            backend_.add_path(poly);
-        }
-        backend_.stop_tile_feature();
-        return painted;
     }
-
-    bool operator() (mapnik::geometry::multi_polygon<std::int64_t> & geom)
+    else
     {
-        bool painted = false;
-        //mapnik::box2d<std::int64_t> bbox = mapnik::geometry::envelope(geom);
-        if (geom.empty())
-        {
-            return painted;
-        }
-        // From this point on due to polygon cleaning etc, we just assume that the tile has some sort
-        // of intersection and is painted.
-        painted = true;   
-        double clean_distance = 1.415;
-        mapnik::geometry::linear_ring<std::int64_t> clip_box;
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
-        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
-        clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
-        clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
-        mapnik::geometry::multi_polygon<std::int64_t> mp;
-        
-        ClipperLib::Clipper clipper;
-        if (multi_polygon_union_)
+        std::vector<std::future<void> > future_layers;
+        future_layers.reserve(tile_layers.size());
+
+        for (auto & layer_ref : tile_layers)
         {
-            for (auto & poly : geom)
+            if (layer_ref.get_ds()->type() == datasource::Vector)
             {
-                // Below we attempt to skip processing of all interior rings if the exterior
-                // ring fails a variety of sanity checks for size and validity for AddPath
-                // When `process_all_rings_=true` this optimization is disabled. This is needed when
-                // the ring order of input polygons is potentially incorrect and where the
-                // "exterior_ring" might actually be an incorrectly classified exterior ring.
-                if (poly.exterior_ring.size() < 3 && !process_all_rings_)
-                {
-                    continue;
-                }
-                ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
-                double outer_area = ClipperLib::Area(poly.exterior_ring);
-                if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
-                {
-                    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());
-                }
-                if (!clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
-                {
-                    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());
-                    }
-                    clipper.AddPath(ring, ClipperLib::ptSubject, true);
-                }
+                future_layers.push_back(std::async(
+                                        threading_mode_,
+                                        detail::create_geom_layer,
+                                        std::ref(layer_ref),
+                                        simplify_distance_,
+                                        area_threshold_,
+                                        fill_type_,
+                                        strictly_simple_,
+                                        multi_polygon_union_,
+                                        process_all_rings_
+                            ));
             }
-            if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+            else // Raster
             {
-                return painted;
+                future_layers.push_back(std::async(
+                                        threading_mode_,
+                                        detail::create_raster_layer,
+                                        std::ref(layer_ref),
+                                        image_format_,
+                                        scaling_method_
+                ));
             }
-            ClipperLib::PolyTree polygons;
-            if (strictly_simple_) 
-            {
-                clipper.StrictlySimple(true);
-            }
-            clipper.ReverseSolution(true);
-            clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type_, fill_type_);
-            clipper.Clear();
-            
-            for (auto * polynode : polygons.Childs)
-            {
-                process_polynode_branch(polynode, mp, area_threshold_); 
-            }
-        }
-        else
-        {
-            for (auto & poly : geom)
-            {
-                // Below we attempt to skip processing of all interior rings if the exterior
-                // ring fails a variety of sanity checks for size and validity for AddPath
-                // When `process_all_rings_=true` this optimization is disabled. This is needed when
-                // the ring order of input polygons is potentially incorrect and where the
-                // "exterior_ring" might actually be an incorrectly classified exterior ring.
-                if (poly.exterior_ring.size() < 3 && !process_all_rings_)
-                {
-                    continue;
-                }
-                ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
-                double outer_area = ClipperLib::Area(poly.exterior_ring);
-                if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
-                {
-                    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());
-                }
-                if (!clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
-                {
-                    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 (!clipper.AddPath(ring, ClipperLib::ptSubject, true))
-                    {
-                        continue;
-                    }
-                }
-                if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
-                {
-                    return painted;
-                }
-                ClipperLib::PolyTree polygons;
-                if (strictly_simple_) 
-                {
-                    clipper.StrictlySimple(true);
-                }
-                clipper.ReverseSolution(true);
-                clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type_, fill_type_);
-                clipper.Clear();
-                
-                for (auto * polynode : polygons.Childs)
-                {
-                    process_polynode_branch(polynode, mp, area_threshold_); 
-                }
-            }
-        }
-
-        if (mp.empty())
-        {
-            return painted;
-        }
-
-        backend_.start_tile_feature(feature_);
-        backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POLYGON);
-        
-        for (auto const& poly : mp)
-        {
-            backend_.add_path(poly);
         }
-        backend_.stop_tile_feature();
-        return painted;
-    }
-
-    backend_type & backend_;
-    mapnik::feature_impl const& feature_;
-    mapnik::box2d<int> const& tile_clipping_extent_;
-    double area_threshold_;
-    bool strictly_simple_;
-    bool multi_polygon_union_;
-    ClipperLib::PolyFillType fill_type_;
-    bool process_all_rings_;
-};
-
-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) {}
-
-    bool operator() (mapnik::geometry::point<std::int64_t> const& geom)
-    {
-        return encoder_(geom);
-    }
-
-    bool operator() (mapnik::geometry::multi_point<std::int64_t> const& geom)
-    {
-        return encoder_(geom);
-    }
-
-    bool 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);
-    }
-
-    bool 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);
-    }
-
-    bool 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);
-    }
 
-    bool 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);
-    }
-
-    bool operator() (mapnik::geometry::geometry_collection<std::int64_t> const& geom)
-    {
-        bool painted = false;
-        for (auto const& g : geom)
+        for (auto && lay_future : future_layers)
         {
-            if (mapnik::util::apply_visitor((*this), g))
+            if (!lay_future.valid())
             {
-                painted = true;
+                throw std::runtime_error("unexpected invalid async return");
             }
+            lay_future.get();
         }
-        return painted;
     }
 
-    bool operator() (mapnik::geometry::geometry_empty const&)
+    for (auto & layer_ref : tile_layers)
     {
-        return false;
-    }
-
-    encoder_visitor<backend_type> & encoder_;
-    unsigned simplify_distance_;
-};
-
-
-template <typename T> template <typename T2>
-bool processor<T>::handle_geometry(T2 const& vs,
-                                   mapnik::feature_impl const& feature,
-                                   mapnik::geometry::geometry<double> const& geom,
-                                   mapnik::box2d<int> const& tile_clipping_extent,
-                                   mapnik::box2d<double> const& target_clipping_extent)
-{
-    // TODO
-    // - no need to create a new skipping_transformer per geometry
-    // - write a non-skipping / zero copy transformer to be used when no projection is needed
-    using vector_tile_strategy_type = T2;
-    mapnik::vector_tile_impl::transform_visitor<vector_tile_strategy_type> skipping_transformer(vs, target_clipping_extent);
-    mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(skipping_transformer,geom);
-    encoder_visitor<T> encoder(backend_, 
-                               feature, 
-                               tile_clipping_extent, 
-                               area_threshold_, 
-                               strictly_simple_, 
-                               multi_polygon_union_, 
-                               fill_type_,
-                               process_all_rings_);
-    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);
+        t.add_layer(layer_ref); 
     }
 }
 
-}} // end ns
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_projection.hpp b/src/vector_tile_projection.hpp
index 248411e..528fbe8 100644
--- a/src/vector_tile_projection.hpp
+++ b/src/vector_tile_projection.hpp
@@ -1,35 +1,45 @@
 #ifndef __MAPNIK_VECTOR_TILE_PROJECTION_H__
 #define __MAPNIK_VECTOR_TILE_PROJECTION_H__
 
-#include <cstdint>
-
+// mapnik-vector-tile
 #include "vector_tile_config.hpp"
 
-namespace mapnik { namespace vector_tile_impl {
+// mapnik
+#include <mapnik/box2d.hpp>
+
+namespace mapnik 
+{ 
+
+namespace vector_tile_impl 
+{
 
-    class spherical_mercator
-    {
-    private:
-        double tile_size_;
-    public:
-        MAPNIK_VECTOR_INLINE spherical_mercator(unsigned tile_size);
+class spherical_mercator
+{
+private:
+    double tile_size_;
+public:
+    spherical_mercator(unsigned tile_size)
+      : tile_size_(static_cast<double>(tile_size)) {}
 
-        MAPNIK_VECTOR_INLINE void from_pixels(double shift, double & x, double & y);
+    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);
-    };
+    MAPNIK_VECTOR_INLINE void xyz(int x,
+                                  int y,
+                                  int z,
+                                  double & minx,
+                                  double & miny,
+                                  double & maxx,
+                                  double & maxy);
+};
 
-}} // end ns
+MAPNIK_VECTOR_INLINE mapnik::box2d<double> merc_extent(std::uint32_t tile_size, int x, int y, int z);
+
+} // end vector_tile_impl ns
+
+} // end mapnik 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
index 1620872..995a195 100644
--- a/src/vector_tile_projection.ipp
+++ b/src/vector_tile_projection.ipp
@@ -1,17 +1,25 @@
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
 #include <mapnik/box2d.hpp>
 #include <mapnik/well_known_srs.hpp>
+
+// std
 #include <cmath>
+#include <cstdint>
 
 #ifndef M_PI
 #define M_PI 3.141592653589793238462643
 #endif
 
-namespace mapnik { namespace vector_tile_impl {
+namespace mapnik 
+{ 
 
-spherical_mercator::spherical_mercator(unsigned tile_size)
-  : tile_size_(static_cast<double>(tile_size)) {}
+namespace vector_tile_impl 
+{
 
-void spherical_mercator::from_pixels(double shift, double & x, double & y)
+MAPNIK_VECTOR_INLINE void spherical_mercator::from_pixels(double shift, double & x, double & y)
 {
     double b = shift/2.0;
     x = (x - b)/(shift/360.0);
@@ -19,13 +27,13 @@ void spherical_mercator::from_pixels(double shift, double & x, double & y)
     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)
+MAPNIK_VECTOR_INLINE 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_;
@@ -38,4 +46,14 @@ void spherical_mercator::xyz(int x,
     lonlat2merc(&maxx,&maxy,1);
 }
 
-}} // end ns
\ No newline at end of file
+MAPNIK_VECTOR_INLINE mapnik::box2d<double> merc_extent(std::uint32_t tile_size, int x, int y, int z)
+{
+    spherical_mercator merc(tile_size);
+    double minx,miny,maxx,maxy;
+    merc.xyz(x, y, z, minx, miny, maxx, maxy);
+    return mapnik::box2d<double>(minx,miny,maxx,maxy);
+}
+
+} // end vector_tile_impl ns
+
+} // end mapnik ns
diff --git a/src/vector_tile_raster_clipper.cpp b/src/vector_tile_raster_clipper.cpp
new file mode 100644
index 0000000..f5f7b07
--- /dev/null
+++ b/src/vector_tile_raster_clipper.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_raster_clipper.hpp"
+#include "vector_tile_raster_clipper.ipp"
diff --git a/src/vector_tile_raster_clipper.hpp b/src/vector_tile_raster_clipper.hpp
new file mode 100644
index 0000000..0a9ec9a
--- /dev/null
+++ b/src/vector_tile_raster_clipper.hpp
@@ -0,0 +1,102 @@
+#ifndef __MAPNIK_VECTOR_TILE_RASTER_CLIPPER_H__
+#define __MAPNIK_VECTOR_TILE_RASTER_CLIPPER_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/image_scaling.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/raster.hpp>
+
+// std
+#include <stdexcept>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+struct raster_clipper
+{
+private:
+    mapnik::raster const& source_;
+    box2d<double> const& target_ext_;
+    box2d<double> const& ext_;
+    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:
+    raster_clipper(mapnik::raster const& source,
+                   box2d<double> const& target_ext,
+                   box2d<double> const& ext,
+                   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),
+          target_ext_(target_ext),
+          ext_(ext),
+          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) 
+    {
+    }
+    
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_rgba8 & source_data);
+    
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray8 & source_data);
+
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray8s & source_data);
+
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray16 & source_data);
+
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray16s & source_data);
+
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray32 & source_data);
+
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray32s & source_data);
+
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray32f & source_data);
+    
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray64 & source_data);
+   
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray64s & source_data);
+
+    MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray64f & source_data);
+
+    std::string operator() (image_null &) const
+    {
+        throw std::runtime_error("Null data passed to visitor");
+    }
+
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_raster_clipper.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_RASTER_CLIPPER_H__
diff --git a/src/vector_tile_raster_clipper.ipp b/src/vector_tile_raster_clipper.ipp
new file mode 100644
index 0000000..173cca6
--- /dev/null
+++ b/src/vector_tile_raster_clipper.ipp
@@ -0,0 +1,523 @@
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/image.hpp>
+#include <mapnik/image_any.hpp>
+#include <mapnik/image_scaling.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/raster.hpp>
+#include <mapnik/warp.hpp>
+
+// agg
+#include "agg_rendering_buffer.h"
+#include "agg_pixfmt_rgba.h"
+#include "agg_pixfmt_gray.h"
+#include "agg_renderer_base.h"
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_rgba8 & source_data)
+{
+    mapnik::image_rgba8 data(raster_width_, raster_height_, true, true);
+    mapnik::raster target(target_ext_, std::move(data), source_.get_filter_factor());
+    if (!prj_trans_.equal())
+    {
+        mapnik::premultiply_alpha(source_data);
+        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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::demultiply_alpha(source_data);
+        return mapnik::save_to_string(source_data, image_format_);    
+    }
+    else
+    {
+        mapnik::premultiply_alpha(source_data);
+        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_pre;
+    using renderer_type = agg::renderer_base<pixfmt_type>;
+
+    mapnik::image_rgba8 im_tile(width_, height_, 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_);
+    mapnik::demultiply_alpha(im_tile);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::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 if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+    {
+        mapnik::image_any any(source_data);
+        return mapnik::save_to_string(any, image_format_);    
+    }
+    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_);
+    return mapnik::save_to_string(im_tile, image_format_);    
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_strategy.hpp b/src/vector_tile_strategy.hpp
index a8f535f..2701325 100644
--- a/src/vector_tile_strategy.hpp
+++ b/src/vector_tile_strategy.hpp
@@ -11,11 +11,9 @@
 #include "clipper.hpp"
 
 #pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wunused-local-typedef"
+#include <mapnik/warning_ignore.hpp>
 #include <boost/geometry/core/coordinate_type.hpp>
 #include <boost/geometry/core/access.hpp>
-#include <boost/numeric/conversion/cast.hpp>
 #pragma GCC diagnostic pop
 
 namespace mapnik {
@@ -27,10 +25,8 @@ static constexpr double coord_min = -1 * static_cast<double>(ClipperLib::hiRange
 
 struct vector_tile_strategy
 {
-    vector_tile_strategy(view_transform const& tr,
-                          double scaling)
-        : tr_(tr),
-          scaling_(scaling) {}
+    vector_tile_strategy(view_transform const& tr)
+        : tr_(tr) {}
 
     template <typename P1, typename P2>
     inline bool apply(P1 const& p1, P2 & p2) const
@@ -39,8 +35,8 @@ struct vector_tile_strategy
         double x = boost::geometry::get<0>(p1);
         double y = boost::geometry::get<1>(p1);
         tr_.forward(&x,&y);
-        x = std::round(x * scaling_);
-        y = std::round(y * scaling_);
+        x = std::round(x);
+        y = std::round(y);
         if (x <= coord_min || x >= coord_max ||
             y <= coord_min || y >= coord_max) return false;
         boost::geometry::set<0>(p2, static_cast<p2_type>(x));
@@ -57,18 +53,14 @@ struct vector_tile_strategy
     }
 
     view_transform const& tr_;
-    double const scaling_;
 };
 
 struct vector_tile_strategy_proj
 {
     vector_tile_strategy_proj(proj_transform const& prj_trans,
-                         view_transform const& tr,
-                         double scaling)
+                              view_transform const& tr)
         : prj_trans_(prj_trans),
-          tr_(tr),
-          scaling_(scaling),
-          not_equal_(!prj_trans_.equal()) {}
+          tr_(tr) {}
 
     template <typename P1, typename P2>
     inline bool apply(P1 const& p1, P2 & p2) const
@@ -77,10 +69,10 @@ struct vector_tile_strategy_proj
         double x = boost::geometry::get<0>(p1);
         double y = boost::geometry::get<1>(p1);
         double z = 0.0;
-        if (not_equal_ && !prj_trans_.backward(x, y, z)) return false;
+        if (!prj_trans_.backward(x, y, z)) return false;
         tr_.forward(&x,&y);
-        x = std::round(x * scaling_);
-        y = std::round(y * scaling_);
+        x = std::round(x);
+        y = std::round(y);
         if (x <= coord_min || x >= coord_max ||
             y <= coord_min || y >= coord_max) return false;
         boost::geometry::set<0>(p2, static_cast<p2_type>(x));
@@ -98,27 +90,50 @@ struct vector_tile_strategy_proj
 
     proj_transform const& prj_trans_;
     view_transform const& tr_;
-    double const scaling_;
-    bool not_equal_;
+};
+
+template <typename T>
+struct geom_out_visitor
+{
+    mapnik::geometry::geometry<T> geom;
+
+    template <typename T1>
+    void operator() (T1 const& g)
+    {
+        geom = mapnik::geometry::geometry<T>(g);
+    }
 };
 
 // TODO - avoid creating degenerate polygons when first/last point of ring is skipped
-template <typename TransformType>
-struct transform_visitor {
+template <typename TransformType, typename NextProcessor>
+struct transform_visitor
+{
+    TransformType const& tr_;
+    NextProcessor & next_;
+    box2d<double> const& target_clipping_extent_;
 
-    transform_visitor(TransformType const& tr, box2d<double> const& target_clipping_extent) :
+    transform_visitor(TransformType const& tr, 
+                      box2d<double> const& target_clipping_extent,
+                      NextProcessor & next) :
       tr_(tr),
+      next_(next),
       target_clipping_extent_(target_clipping_extent) {}
 
-    inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::point<double> const& geom)
+    inline void operator() (mapnik::geometry::point<double> const& geom)
     {
-        if (!target_clipping_extent_.intersects(geom.x,geom.y)) return mapnik::geometry::geometry_empty(); 
+        if (!target_clipping_extent_.intersects(geom.x,geom.y))
+        {
+            return;
+        }
         mapnik::geometry::point<std::int64_t> new_geom;
-        if (!tr_.apply(geom,new_geom)) return mapnik::geometry::geometry_empty();
-        return new_geom;
+        if (!tr_.apply(geom,new_geom))
+        {
+            return;
+        }
+        return next_(new_geom);
     }
 
-    inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::multi_point<double> const& geom)
+    inline void operator() (mapnik::geometry::multi_point<double> const& geom)
     {
         mapnik::geometry::multi_point<std::int64_t> new_geom;
         new_geom.reserve(geom.size());
@@ -130,16 +145,19 @@ struct transform_visitor {
                 new_geom.push_back(std::move(pt2));
             }
         }
-        if (new_geom.empty()) return mapnik::geometry::geometry_empty();
-        return new_geom;
+        if (new_geom.empty())
+        {
+            return;
+        }
+        return next_(new_geom);
     }
 
-    inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::line_string<double> const& geom)
+    inline void operator() (mapnik::geometry::line_string<double> const& geom)
     {
         mapnik::box2d<double> geom_bbox = mapnik::geometry::envelope(geom);
         if (!target_clipping_extent_.intersects(geom_bbox)) 
         {
-            return mapnik::geometry::geometry_empty();
+            return;
         }
         mapnik::geometry::line_string<std::int64_t> new_geom;
         new_geom.reserve(geom.size());
@@ -151,10 +169,10 @@ struct transform_visitor {
                 new_geom.push_back(std::move(pt2));
             }
         }
-        return new_geom;
+        return next_(new_geom);
     }
 
-    inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::multi_line_string<double> const& geom)
+    inline void operator() (mapnik::geometry::multi_line_string<double> const& geom)
     {
         mapnik::geometry::multi_line_string<std::int64_t> new_geom;
         new_geom.reserve(geom.size());
@@ -174,16 +192,19 @@ struct transform_visitor {
             }
             new_geom.push_back(std::move(new_line));
         }
-        if (new_geom.empty()) return mapnik::geometry::geometry_empty();
-        return new_geom;
+        if (new_geom.empty())
+        {
+            return;
+        }
+        return next_(new_geom);
     }
 
-    inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::polygon<double> const& geom)
+    inline void operator() (mapnik::geometry::polygon<double> const& geom)
     {
         mapnik::box2d<double> ext_bbox = mapnik::geometry::envelope(geom);
         if (!target_clipping_extent_.intersects(ext_bbox))
         {
-            return mapnik::geometry::geometry_empty();
+            return;
         }
         mapnik::geometry::polygon<std::int64_t> new_geom;
         new_geom.exterior_ring.reserve(geom.exterior_ring.size());
@@ -214,10 +235,10 @@ struct transform_visitor {
             }
             new_geom.interior_rings.push_back(std::move(new_ring));
         }
-        return new_geom;
+        return next_(new_geom);
     }
 
-    inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::multi_polygon<double> const& geom)
+    inline void operator() (mapnik::geometry::multi_polygon<double> const& geom)
     {
         mapnik::geometry::multi_polygon<std::int64_t> new_geom;
         new_geom.reserve(geom.size());
@@ -259,27 +280,25 @@ struct transform_visitor {
             }
             new_geom.push_back(std::move(new_poly));
         }
-        if (new_geom.empty()) return mapnik::geometry::geometry_empty();
-        return new_geom;
+        if (new_geom.empty())
+        {
+            return;
+        }
+        return next_(new_geom);
     }
 
-    inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::geometry_collection<double> const& geom)
+    inline void operator() (mapnik::geometry::geometry_collection<double> const& geom)
     {
-        mapnik::geometry::geometry_collection<std::int64_t> new_geom;
-        new_geom.reserve(geom.size());
         for (auto const& g : geom)
         {
-            new_geom.push_back(mapnik::util::apply_visitor((*this), g));
+            mapnik::util::apply_visitor((*this), g);
         }
-        return new_geom;
      }
 
-    inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::geometry_empty const& geom)
+    inline void operator() (mapnik::geometry::geometry_empty const&)
     {
-        return geom;
+        return;
     }
-    TransformType const& tr_;
-    box2d<double> const& target_clipping_extent_;
 };
 
 }
diff --git a/src/vector_tile_tile.cpp b/src/vector_tile_tile.cpp
new file mode 100644
index 0000000..0843827
--- /dev/null
+++ b/src/vector_tile_tile.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_tile.hpp"
+#include "vector_tile_tile.ipp"
diff --git a/src/vector_tile_tile.hpp b/src/vector_tile_tile.hpp
new file mode 100644
index 0000000..0bbf2e5
--- /dev/null
+++ b/src/vector_tile_tile.hpp
@@ -0,0 +1,211 @@
+#ifndef __MAPNIK_VECTOR_TILE_TILE_H__
+#define __MAPNIK_VECTOR_TILE_TILE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+
+// mapnik
+#include <mapnik/box2d.hpp>
+
+// std
+#include <set>
+#include <string>
+#include <vector>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+// fwd declare
+class tile_layer;
+
+class tile
+{
+protected:
+    std::string buffer_;
+    std::set<std::string> painted_layers_;
+    std::set<std::string> empty_layers_;
+    std::set<std::string> layers_set_;
+    std::vector<std::string> layers_;
+    mapnik::box2d<double> extent_;
+    std::uint32_t tile_size_;
+    std::int32_t buffer_size_;
+
+public:
+    tile(mapnik::box2d<double> const& extent,
+         std::uint32_t tile_size = 4096,
+         std::int32_t buffer_size = 128)
+        : buffer_(),
+          painted_layers_(),
+          empty_layers_(),
+          layers_set_(),
+          layers_(),
+          extent_(extent),
+          tile_size_(tile_size),
+          buffer_size_(buffer_size) {}
+
+    tile(tile const& rhs) = default;
+
+    tile(tile && rhs) = default;
+
+    MAPNIK_VECTOR_INLINE bool add_layer(tile_layer const& layer);
+
+    void add_empty_layer(std::string const& name)
+    {
+        empty_layers_.insert(name);
+    }
+
+    const char * data() const
+    {
+        return buffer_.data();
+    }
+    
+    std::size_t size() const
+    {
+        return buffer_.size();
+    }
+
+    std::string const& get_buffer() const
+    {
+        return buffer_;
+    }
+
+    double scale() const
+    {
+        if (tile_size_ > 0)
+        {
+            return extent_.width()/tile_size_;
+        }
+        return extent_.width();
+    }
+
+    box2d<double> get_buffered_extent() const
+    {
+        double extra = 2.0 * scale() * buffer_size_;
+        box2d<double> ext(extent_);
+        double extra_width = extent_.width() + extra;
+        double extra_height = extent_.height() + extra;
+        if (extra_width < 0.0)
+        {
+            extra_width = 0.0;
+        }
+        if (extra_height < 0.0)
+        {
+            extra_height = 0.0;
+        }
+        ext.width(extra_width);
+        ext.height(extra_height);
+        return ext;
+    }
+    
+    void append_to_string(std::string & str) const
+    {
+        str.append(buffer_);
+    }
+
+    void serialize_to_string(std::string & str) const
+    {
+        str = buffer_;
+    }
+
+    bool is_painted() const
+    {
+        return !painted_layers_.empty();
+    }
+
+    bool is_empty() const
+    {
+        return layers_.empty();
+    }
+
+    box2d<double> const& extent() const
+    {
+        return extent_;
+    }
+
+    std::uint32_t tile_size() const
+    {
+        return tile_size_;
+    }
+
+    void tile_size(std::uint32_t val)
+    {
+        tile_size_ = val;
+    }
+
+    std::int32_t buffer_size() const
+    {
+        return buffer_size_;
+    }
+
+    void buffer_size(std::int32_t val)
+    {
+        buffer_size_ = val;
+    }
+
+    MAPNIK_VECTOR_INLINE bool append_layer_buffer(const char * data, std::size_t size, std::string const& name);
+
+    std::set<std::string> const& get_painted_layers() const
+    {
+        return painted_layers_;
+    }
+
+    std::set<std::string> const& get_empty_layers() const
+    {
+        return empty_layers_;
+    }
+    
+    std::vector<std::string> const& get_layers() const
+    {
+        return layers_;
+    }
+
+    std::set<std::string> const& get_layers_set() const
+    {
+        return layers_set_;
+    }
+
+    bool same_extent(tile const& other) const
+    {
+        return extent_ == other.extent_;
+    }
+
+    void clear()
+    {
+        buffer_.clear();
+        empty_layers_.clear();
+        layers_.clear();
+        layers_set_.clear();
+        painted_layers_.clear();
+    }
+    
+    bool has_layer(std::string const& name)
+    {
+        auto itr = layers_set_.find(name);
+        return itr != layers_set_.end();
+    }
+
+    protozero::pbf_reader get_reader() const
+    {
+        return protozero::pbf_reader(buffer_.data(), buffer_.size());
+    }
+
+    MAPNIK_VECTOR_INLINE bool layer_reader(std::string const& name, protozero::pbf_reader & layer_msg) const;
+
+    MAPNIK_VECTOR_INLINE bool layer_reader(std::size_t index, protozero::pbf_reader & layer_msg) const;
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_tile.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_TILE_H__
diff --git a/src/vector_tile_tile.ipp b/src/vector_tile_tile.ipp
new file mode 100644
index 0000000..d807686
--- /dev/null
+++ b/src/vector_tile_tile.ipp
@@ -0,0 +1,108 @@
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+#include "vector_tile_layer.hpp"
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <set>
+#include <string>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+MAPNIK_VECTOR_INLINE bool tile::add_layer(tile_layer const& layer)
+{
+    std::string const& new_name = layer.name();
+    if (layer.is_empty())
+    {
+        empty_layers_.insert(new_name);
+        if (layer.is_painted())
+        {
+            painted_layers_.insert(new_name);
+        }
+    }
+    else
+    {
+        painted_layers_.insert(new_name);
+        auto p = layers_set_.insert(new_name);
+        if (!p.second)
+        {
+            // Layer already in tile
+            return false;
+        }
+        layers_.push_back(new_name);
+        protozero::pbf_writer tile_writer(buffer_);
+        tile_writer.add_message(Tile_Encoding::LAYERS, layer.get_data());
+        auto itr = empty_layers_.find(new_name);
+        if (itr != empty_layers_.end())
+        {
+            empty_layers_.erase(itr);
+        }
+    }
+    return true;
+}
+
+MAPNIK_VECTOR_INLINE bool tile::append_layer_buffer(const char * data, std::size_t size, std::string const& name)
+{
+    painted_layers_.insert(name);
+    auto p = layers_set_.insert(name);
+    if (!p.second)
+    {
+        // Layer already in tile
+        return false;
+    }
+    layers_.push_back(name);
+    protozero::pbf_writer writer(buffer_);
+    writer.add_message(3, data, size);
+    auto itr = empty_layers_.find(name);
+    if (itr != empty_layers_.end())
+    {
+        empty_layers_.erase(itr);
+    }
+    return true;
+}
+
+MAPNIK_VECTOR_INLINE bool tile::layer_reader(std::string const& name, protozero::pbf_reader & layer_msg) const
+{
+    protozero::pbf_reader item(buffer_.data(), buffer_.size());
+    while (item.next(Tile_Encoding::LAYERS))
+    {
+        layer_msg = item.get_message();
+        protozero::pbf_reader lay(layer_msg);
+        while (lay.next(Layer_Encoding::NAME))
+        {
+            if (lay.get_string() == name)
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+MAPNIK_VECTOR_INLINE bool tile::layer_reader(std::size_t index, protozero::pbf_reader & layer_msg) const
+{
+    protozero::pbf_reader item(buffer_.data(), buffer_.size());
+    std::size_t idx = 0;
+    while (item.next(Tile_Encoding::LAYERS))
+    {
+        if (idx == index)
+        {
+            layer_msg = item.get_message();
+            return true;
+        }
+        ++idx;
+        item.skip();
+    }
+    return false;
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_util.cpp b/src/vector_tile_util.cpp
deleted file mode 100644
index 6b05777..0000000
--- a/src/vector_tile_util.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#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
deleted file mode 100644
index fae87dd..0000000
--- a/src/vector_tile_util.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef __MAPNIK_VECTOR_TILE_UTIL_H__
-#define __MAPNIK_VECTOR_TILE_UTIL_H__
-
-#include <string>
-
-#include "vector_tile_config.hpp"
-
-namespace vector_tile {
-    class Tile;
-}
-
-namespace mapnik { namespace vector_tile_impl {
-
-    MAPNIK_VECTOR_INLINE bool is_solid_extent(vector_tile::Tile const& tile, std::string & key);
-    MAPNIK_VECTOR_INLINE bool is_solid_extent(std::string const& tile, std::string & key);
-
-}}
-
-#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
-#include "vector_tile_util.ipp"
-#endif
-
-#endif // __MAPNIK_VECTOR_TILE_UTIL_H__
diff --git a/src/vector_tile_util.ipp b/src/vector_tile_util.ipp
deleted file mode 100644
index ac747ac..0000000
--- a/src/vector_tile_util.ipp
+++ /dev/null
@@ -1,256 +0,0 @@
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
-
-#include <mapnik/vertex.hpp>
-#include <mapnik/box2d.hpp>
-#include <mapnik/geometry.hpp>
-
-#include "vector_tile_geometry_decoder.hpp"
-
-#include <protozero/pbf_reader.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;
-    }
-
-    bool is_solid_extent(std::string const& tile, std::string & key)
-    {
-        protozero::pbf_reader item(tile);
-        unsigned i = 0;
-        while (item.next(3)) {
-            protozero::pbf_reader layer_msg = item.get_message();
-            unsigned extent = 0;
-            std::string name;
-            std::vector<protozero::pbf_reader> feature_collection;
-            while (layer_msg.next())
-            {
-                switch(layer_msg.tag())
-                {
-                    case 1:
-                        name = layer_msg.get_string();
-                        break;
-                    case 2:
-                        feature_collection.push_back(layer_msg.get_message());
-                        break;
-                    case 5:
-                        extent = layer_msg.get_uint32();
-                        break;
-                    default:
-                        layer_msg.skip();
-                        break;
-                }
-            }
-            unsigned side = extent - 1;
-            mapnik::box2d<int> container(2, 2, extent-2, extent-2);
-            double extent_area = side * side;
-            for (auto & features : feature_collection)
-            {
-                while (features.next(4)) {
-                    mapnik::vector_tile_impl::GeometryPBF<double> paths(features.get_packed_uint32(), 0, 0, 1, 1);
-                    mapnik::vector_tile_impl::GeometryPBF<double>::command cmd;
-                    double x0, y0, x1, y1;
-                    mapnik::box2d<int> box;
-                    bool first = true;
-                    std::uint32_t len;
-                    while ((cmd = paths.next(x1, y1, len)) != mapnik::vector_tile_impl::GeometryPBF<double>::end)
-                    {
-                        if (cmd == mapnik::vector_tile_impl::GeometryPBF<double>::move_to || cmd == mapnik::vector_tile_impl::GeometryPBF<double>::line_to)
-                        {
-                            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);
-                            }
-                        }
-                    }
-                    // 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 = name;
-                    } else if (i > 0) {
-                        key += std::string("-") + name;
-                    }
-                }
-            }
-            ++i;
-        }
-        return true;
-    }
-
-}} // end ns
diff --git a/test/clipper_test.cpp b/test/clipper_test.cpp
index 4eb1c25..691dfdc 100644
--- a/test/clipper_test.cpp
+++ b/test/clipper_test.cpp
@@ -2,7 +2,6 @@
 #include <iostream>
 #include <mapnik/projection.hpp>
 #include <mapnik/geometry_transform.hpp>
-//#include <mapnik/util/geometry_to_geojson.hpp>
 
 #include "vector_tile_strategy.hpp"
 #include "vector_tile_projection.hpp"
@@ -11,17 +10,22 @@
 #include "catch.hpp"
 #include "clipper.hpp"
 
-TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
+TEST_CASE("vector_tile_strategy -- should not overflow")
+{
     mapnik::projection merc("+init=epsg:3857",true);
     mapnik::proj_transform prj_trans(merc,merc); // no-op
-    unsigned tile_size = 256;
+    unsigned tile_size = 4096;
     mapnik::vector_tile_impl::spherical_mercator merc_tiler(tile_size);
     double minx,miny,maxx,maxy;
     merc_tiler.xyz(9664,20435,15,minx,miny,maxx,maxy);
     mapnik::box2d<double> z15_extent(minx,miny,maxx,maxy);
-    mapnik::view_transform tr(tile_size,tile_size,z15_extent,0,0);
     {
-        mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr, 16);
+        mapnik::view_transform tr(tile_size,
+                                  tile_size,
+                                  z15_extent,
+                                  0,
+                                  0);
+        mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr);
         // even an invalid point is not expected to result in values beyond hirange
         mapnik::geometry::point<double> g(-20037508.342789*2.0,-20037508.342789*2.0);
         mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::geometry::transform<std::int64_t>(g, vs);
@@ -41,8 +45,12 @@ TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
     g.exterior_ring.add_coord(minx,miny);
     {
         // absurdly large but still should not result in values beyond hirange
-        double path_multiplier = 100000000000.0;
-        mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr, path_multiplier);
+        mapnik::view_transform tr(std::numeric_limits<int>::max(),
+                                  std::numeric_limits<int>::max(),
+                                  z15_extent,
+                                  0,
+                                  0);
+        mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr);
         mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::geometry::transform<std::int64_t>(g, vs);
         REQUIRE( new_geom.is<mapnik::geometry::polygon<std::int64_t>>() );
         auto const& poly = mapnik::util::get<mapnik::geometry::polygon<std::int64_t>>(new_geom);
@@ -56,10 +64,19 @@ TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
             REQUIRE( (-pt.y < ClipperLib::hiRange) );
         }
     }
+    /*
+     * This test was originally something that would require an
+     * exception for situations where scaling would cause an out of range
+     * situation, however, scaling was removed and the view transform
+     * is the limited to an int32 in range currently within mapnik.
     {
         // expected to trigger values above hirange
-        double path_multiplier = 1000000000000.0;
-        mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr, path_multiplier);
+        mapnik::view_transform tr(std::numeric_limits<int>::max(),
+                                  std::numeric_limits<int>::max(),
+                                  z15_extent,
+                                  0,
+                                  0);
+        mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr);
         CHECK_THROWS( mapnik::geometry::transform<std::int64_t>(g, vs) );
         mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
                                        std::numeric_limits<double>::min(),
@@ -79,26 +96,36 @@ TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
             REQUIRE( (-pt.y < ClipperLib::hiRange) );
         }
     }
+     */
 }
 
-TEST_CASE( "vector_tile_strategy2", "invalid mercator coord in interior ring" ) {
+TEST_CASE("vector_tile_strategy2 -- invalid mercator coord in interior ring")
+{
     mapnik::geometry::geometry<double> geom = testing::read_geojson("./test/data/invalid-interior-ring.json");
     mapnik::projection longlat("+init=epsg:4326",true);
     mapnik::proj_transform prj_trans(longlat,longlat); // no-op
-    unsigned tile_size = 256;
+    unsigned tile_size = 4096;
     mapnik::vector_tile_impl::spherical_mercator merc_tiler(tile_size);
     double minx,miny,maxx,maxy;
     merc_tiler.xyz(9664,20435,15,minx,miny,maxx,maxy);
     mapnik::box2d<double> z15_extent(minx,miny,maxx,maxy);
-    mapnik::view_transform tr(tile_size,tile_size,z15_extent,0,0);
-    mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr, 16);
+    mapnik::view_transform tr(tile_size,
+                              tile_size,
+                              z15_extent,
+                              0,
+                              0);
+    mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr);
     CHECK_THROWS( mapnik::geometry::transform<std::int64_t>(geom, vs) );
     mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
                                    std::numeric_limits<double>::min(),
                                    std::numeric_limits<double>::max(),
                                    std::numeric_limits<double>::max());
-    mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy_proj> skipping_transformer(vs, clip_extent);
-    mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(skipping_transformer,geom);
+    mapnik::vector_tile_impl::geom_out_visitor<int64_t> out_geom;
+    mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy_proj,
+                                                mapnik::vector_tile_impl::geom_out_visitor<int64_t> > 
+                                           skipping_transformer(vs, clip_extent, out_geom);
+    mapnik::util::apply_visitor(skipping_transformer,geom);
+    mapnik::geometry::geometry<std::int64_t> new_geom = out_geom.geom;
     REQUIRE( new_geom.is<mapnik::geometry::polygon<std::int64_t>>() );
     auto const& poly = mapnik::util::get<mapnik::geometry::polygon<std::int64_t>>(new_geom);
     for (auto const& pt : poly.exterior_ring)
@@ -124,7 +151,8 @@ TEST_CASE( "vector_tile_strategy2", "invalid mercator coord in interior ring" )
     }
 }
 
-TEST_CASE( "clipper IntPoint", "should accept 64bit values" ) {
+TEST_CASE("clipper IntPoint -- should accept 64bit values")
+{
     std::int64_t x = 4611686018427387903;
     std::int64_t y = 4611686018427387903;
     auto x0 = std::numeric_limits<std::int64_t>::max();
@@ -149,7 +177,8 @@ TEST_CASE( "clipper IntPoint", "should accept 64bit values" ) {
     CHECK( (pt != pt3) );
 }
 
-TEST_CASE( "clipper AddPath 1", "should not throw within range coords" ) {
+TEST_CASE("clipper AddPath 1", "should not throw within range coords")
+{
     ClipperLib::Clipper clipper;
     ClipperLib::Path clip_box; // actually mapnik::geometry::line_string<std::int64_t>
     // values that should just barely work since they are one below the
@@ -166,7 +195,8 @@ TEST_CASE( "clipper AddPath 1", "should not throw within range coords" ) {
     CHECK( clipper.AddPath(clip_box,ClipperLib::ptClip,true) );
 }
 
-TEST_CASE( "clipper AddPath 2", "should throw on out of range coords" ) {
+TEST_CASE("clipper AddPath 2 -- should throw on out of range coords")
+{
     ClipperLib::Clipper clipper;
     ClipperLib::Path clip_box; // actually mapnik::geometry::line_string<std::int64_t>
     auto x0 = std::numeric_limits<std::int64_t>::min()+1;
@@ -189,8 +219,8 @@ TEST_CASE( "clipper AddPath 2", "should throw on out of range coords" ) {
     }        
 }
 
-TEST_CASE( "clipper polytree error" ) {
-
+TEST_CASE("clipper polytree error")
+{
     // http://sourceforge.net/p/polyclipping/bugs/132/
     ClipperLib::Clipper clipper;
     ClipperLib::Paths polygons;
@@ -241,6 +271,4 @@ TEST_CASE( "clipper polytree error" ) {
     REQUIRE(solution.Childs[0]->Childs.size() == 1);
     REQUIRE(solution.Childs[0]->Childs[0]->Childs.size() == 1);
     REQUIRE(solution.Childs[0]->Childs[0]->Childs[0]->Childs.size() == 0);
-
 }
-
diff --git a/test/data/0.0.0.vector-b.mvt b/test/data/0.0.0.vector-b.mvt
new file mode 100644
index 0000000..295bf58
Binary files /dev/null and b/test/data/0.0.0.vector-b.mvt differ
diff --git a/test/data/0.0.0.vector-b.pbf b/test/data/0.0.0.vector-b.pbf
deleted file mode 100644
index a26263f..0000000
Binary files a/test/data/0.0.0.vector-b.pbf and /dev/null differ
diff --git a/test/data/0.0.0.vector.pbf b/test/data/0.0.0.vector.mvt
similarity index 100%
rename from test/data/0.0.0.vector.pbf
rename to test/data/0.0.0.vector.mvt
diff --git a/test/data/tile_with_extra_feature_field.pbf b/test/data/tile_with_extra_feature_field.mvt
similarity index 100%
rename from test/data/tile_with_extra_feature_field.pbf
rename to test/data/tile_with_extra_feature_field.mvt
diff --git a/test/data/tile_with_extra_field.pbf b/test/data/tile_with_extra_field.mvt
similarity index 100%
rename from test/data/tile_with_extra_field.pbf
rename to test/data/tile_with_extra_field.mvt
diff --git a/test/data/tile_with_extra_layer_fields.pbf b/test/data/tile_with_extra_layer_fields.mvt
similarity index 100%
rename from test/data/tile_with_extra_layer_fields.pbf
rename to test/data/tile_with_extra_layer_fields.mvt
diff --git a/test/data/tile_with_invalid_layer_value_type.pbf b/test/data/tile_with_invalid_layer_value_type.mvt
similarity index 100%
rename from test/data/tile_with_invalid_layer_value_type.pbf
rename to test/data/tile_with_invalid_layer_value_type.mvt
diff --git a/test/data/tile_with_unexpected_geomtype.pbf b/test/data/tile_with_unexpected_geomtype.mvt
similarity index 100%
rename from test/data/tile_with_unexpected_geomtype.pbf
rename to test/data/tile_with_unexpected_geomtype.mvt
diff --git a/test/encoding_util.cpp b/test/encoding_util.cpp
deleted file mode 100644
index 7454f6f..0000000
--- a/test/encoding_util.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-#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"
-
-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&) const
-    {
-        std::cerr << "COLLECTION" << std::endl;
-    }
-    template <typename T>
-    void operator() (T const& geom) const
-    {
-        std::cerr << boost::geometry::wkt(geom) << std::endl;
-    }
-};
-
-}
-
-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)
-            {
-                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();
-    }
-};
-
-struct encoder_visitor
-{
-    vector_tile::Tile_Feature & feature_;
-    int32_t x_;
-    int32_t y_;
-    encoder_visitor(vector_tile::Tile_Feature & feature) :
-      feature_(feature),
-      x_(0),
-      y_(0) { }
-
-    void operator() (geometry_empty const&)
-    {
-    }
-
-    void operator()(mapnik::geometry::geometry_collection<std::int64_t> const& path)
-    {
-        for (auto const& p : path)
-        {
-            mapnik::util::apply_visitor((*this), p);
-        }
-    }
-
-    template <typename T>
-    void operator()(T const& geom)
-    {
-        mapnik::vector_tile_impl::encode_geometry(geom,feature_,x_,y_);
-    }
-};
-
-vector_tile::Tile_Feature geometry_to_feature(mapnik::geometry::geometry<std::int64_t> const& g)
-{
-    vector_tile::Tile_Feature feature;
-    if (g.template is<mapnik::geometry::point<std::int64_t>>() || g.template is<mapnik::geometry::multi_point<std::int64_t>>())
-    {
-        feature.set_type(vector_tile::Tile_GeomType_POINT);
-    }
-    else if (g.template is<mapnik::geometry::line_string<std::int64_t>>() || g.template is<mapnik::geometry::multi_line_string<std::int64_t>>())
-    {
-        feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
-    }
-    else if (g.template is<mapnik::geometry::polygon<std::int64_t>>() || g.template is<mapnik::geometry::multi_polygon<std::int64_t>>())
-    {
-        feature.set_type(vector_tile::Tile_GeomType_POLYGON);
-    }
-    else
-    {
-        throw std::runtime_error("could not detect valid geometry type");
-    }
-    encoder_visitor ap(feature);
-    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::geometry<std::int64_t> const& g)
-{
-    vector_tile::Tile_Feature feature = geometry_to_feature(g);
-    mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
-    auto g2 = mapnik::vector_tile_impl::decode_geometry<double>(geoms,feature.type());
-    return decode_to_path_string(g2);
-}
diff --git a/test/encoding_util.hpp b/test/encoding_util.hpp
deleted file mode 100644
index 169ba49..0000000
--- a/test/encoding_util.hpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#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"
-
-namespace {
-
-using namespace mapnik::geometry;
-
-struct print;
-
-}
-
-struct show_path;
-
-struct encoder_visitor;
-
-vector_tile::Tile_Feature geometry_to_feature(mapnik::geometry::geometry<std::int64_t> const& g);
-
-template <typename T>
-std::string decode_to_path_string(mapnik::geometry::geometry<T> const& g);
-
-std::string compare(mapnik::geometry::geometry<std::int64_t> const& g);
diff --git a/test/fixtures/expected-4.png b/test/fixtures/expected-4.png
index 4db812f..89a5508 100644
Binary files a/test/fixtures/expected-4.png and b/test/fixtures/expected-4.png differ
diff --git a/test/geometry_encoding.cpp b/test/geometry_encoding.cpp
deleted file mode 100644
index bcc8313..0000000
--- a/test/geometry_encoding.cpp
+++ /dev/null
@@ -1,691 +0,0 @@
-#include "catch.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>
-
-//#include <mapnik/geometry_unique.hpp>
-
-/*
-
-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(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(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(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(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(line);
-    CHECK( feature.geometry_size() == 0 );
-    auto geom = mapnik::vector_tile_impl::decode_geometry<double>(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(g);
-    CHECK( feature.geometry_size() == 6 );
-    auto geom = mapnik::vector_tile_impl::decode_geometry<double>(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(g);
-    CHECK( feature.type() == vector_tile::Tile_GeomType_LINESTRING );
-    CHECK( feature.geometry_size() == 8 );
-    auto geom = mapnik::vector_tile_impl::decode_geometry<double>(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);
-}
-
-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,mapnik::geometry::geometry<std::int64_t>(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(p0);
-    // since first ring is degenerate the whole polygon should be culled
-    mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
-    auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(geoms, feature.type());
-    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(p0);
-    // since first ring is degenerate the whole polygon should be culled
-    mapnik::vector_tile_impl::Geometry geoms(feature,0.0,0.0,1.0,1.0);
-    auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(geoms, feature.type());
-    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,mapnik::geometry::geometry<std::int64_t>(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(p0);
-    mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
-    auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(geoms, feature.type());
-    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,mapnik::geometry::geometry<std::int64_t>(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(p0);
-    mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
-    auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(geoms, feature.type());
-    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( "Polygon with de-generate ring(s)", "should skip invalid ring(s)" )
-{
-    // create invalid (exterior) polygon
-    mapnik::geometry::polygon<std::int64_t> p0;
-    p0.exterior_ring.add_coord(10,10);
-    p0.exterior_ring.add_coord(10,10);
-    p0.exterior_ring.add_coord(10,10);
-    p0.exterior_ring.add_coord(10,10);
-    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,mapnik::geometry::geometry<std::int64_t>(p0) ) );
-    std::string expected_wkt0("POLYGON((10 10,10 10,10 10,10 10),(-7 7,-3 7,-3 3,-7 3,-7 7))");
-    std::string expected_wkt1("POLYGON((-7 7,-7 3,-3 3,-3 7,-7 7))");
-    CHECK( wkt0 == expected_wkt0);
-
-    vector_tile::Tile_Feature feature = geometry_to_feature(p0);
-    mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
-    auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(geoms, feature.type());
-    CHECK( p1.is<mapnik::geometry::polygon<double> >() );
-    wkt0.clear();
-    CHECK( mapnik::util::to_wkt(wkt0,p1) );
-    CHECK( wkt0 == expected_wkt1);
-}
-
-
-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,mapnik::geometry::geometry<std::int64_t>(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,mapnik::geometry::geometry<std::int64_t>(p0)) );
-    CHECK( wkt0 == expected_wkt0);
-
-    vector_tile::Tile_Feature feature = geometry_to_feature(p0);
-    mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
-    auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(geoms, feature.type());
-    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);
-
-}
-
-// 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) == expected);
-}*/
-
-/*
-TEST_CASE( "test 2b", "should drop vertices" ) {
-    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) == expected);
-}*/
-
-TEST_CASE( "test 3", "should not drop first move_to or last vertex in line" ) {
-    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(2,2)\n"
-    "line_to(3,3)\n"
-    );
-    CHECK(compare(g) == expected);
-}
-
-/*
-
-TEST_CASE( "test 4", "should not drop first move_to or last vertex in polygon" ) {
-    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"
-    "move_to(0,0)\n"
-    "line_to(1,1)\n"
-    "close_path(0,0)\n"
-    );
-    CHECK(compare(g,1000) == expected);
-}
-
-
-
-TEST_CASE( "test 5", "can drop duplicate move_to" ) {
-
-    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"
-    );
-    CHECK(compare(g,2) == expected);
-}
-
-
-TEST_CASE( "test 5b", "can drop duplicate move_to" ) {
-    mapnik::geometry::line_string g;
-    g.move_to(0,0);
-    g.move_to(1,1);
-    g.line_to(2,2);
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(2,2)\n"
-    );
-    CHECK(compare(g,3) == expected);
-}
-
-TEST_CASE( "test 5c", "can drop duplicate move_to but not second" ) {
-    mapnik::geometry::line_string g;
-    g.move_to(0,0);
-    g.move_to(1,1);
-    g.line_to(2,2);
-    g.move_to(3,3);
-    g.line_to(4,4);
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(2,2)\n"
-    "move_to(3,3)\n"
-    "line_to(4,4)\n"
-    );
-    CHECK(compare(g,3) == expected);
-}
-
-TEST_CASE( "test 6", "should not drop last line_to if repeated" ) {
-    mapnik::geometry::line_string g;
-    g.move_to(0,0);
-    g.line_to(2,2);
-    g.line_to(1000,1000); // skipped
-    g.line_to(1001,1001); // skipped
-    g.line_to(1001,1001);
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(2,2)\n"
-    "line_to(1001,1001)\n"
-    );
-    CHECK(compare(g,2) == expected);
-}
-
-TEST_CASE( "test 7", "ensure proper handling of skipping + close commands" ) {
-    mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
-    g.move_to(0,0);
-    g.line_to(2,2);
-    g.close_path();
-    g.move_to(5,5);
-    g.line_to(10,10); // skipped
-    g.line_to(21,21);
-    g.close_path();
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(2,2)\n"
-    "close_path(0,0)\n"
-    "move_to(5,5)\n"
-    "line_to(21,21)\n"
-    "close_path(0,0)\n"
-    );
-    CHECK(compare(g,100) == expected);
-}
-
-TEST_CASE( "test 8", "should drop repeated close commands" ) {
-    mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
-    g.move_to(0,0);
-    g.line_to(2,2);
-    g.close_path();
-    g.close_path();
-    g.close_path();
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(2,2)\n"
-    "close_path(0,0)\n"
-    );
-    CHECK(compare(g,100) == expected);
-}
-
-TEST_CASE( "test 9a", "should not drop last vertex" ) {
-    mapnik::geometry::line_string g;
-    g.move_to(0,0);
-    g.line_to(9,0); // skipped
-    g.line_to(0,10);
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(0,10)\n"
-    );
-    CHECK(compare(g,11) == expected);
-}
-
-TEST_CASE( "test 9b", "should not drop last vertex" ) {
-    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);
-    g.close_path();
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(0,10)\n"
-    "close_path(0,0)\n"
-    );
-    CHECK(compare(g,11) == expected);
-}
-
-TEST_CASE( "test 9c", "should not drop last vertex" ) {
-    mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
-    g.move_to(0,0);
-    g.line_to(0,10);
-    g.close_path();
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(0,10)\n"
-    "close_path(0,0)\n"
-    );
-    CHECK(compare(g,11) == expected);
-}
-
-TEST_CASE( "test 10", "should skip repeated close and coincident line_to commands" ) {
-    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
-    g.line_to(20,20);
-    g.line_to(20,20); // skipped, but added back and replaces previous
-    g.close_path();
-    g.close_path(); // skipped
-    g.close_path(); // skipped
-    g.close_path(); // skipped
-    g.move_to(0,0);
-    g.line_to(10,10);
-    g.line_to(20,20);
-    g.close_path();
-    g.close_path(); // skipped
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(10,10)\n"
-    "line_to(20,20)\n"
-    "close_path(0,0)\n"
-    "move_to(0,0)\n"
-    "line_to(10,10)\n"
-    "line_to(20,20)\n"
-    "close_path(0,0)\n"
-    );
-    CHECK(compare(g,1) == expected);
-}
-
-TEST_CASE( "test 11", "should correctly encode multiple paths" ) {
-    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::geometry_type::types::Polygon);
-    g0.move_to(0,0);
-    g0.line_to(-10,-10);
-    g0.line_to(-20,-20);
-    g0.close_path();
-    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::geometry_type::types::Polygon);
-    g1.move_to(1000,1000);
-    g1.line_to(1010,1010);
-    g1.line_to(1020,1020);
-    g1.close_path();
-    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::geometry_type::types::Polygon);
-    double x0 = 0;
-    double y0 = 0;
-    decode_geometry<double>(feature0,g2,x0,y0,path_multiplier);
-    mapnik::vertex_adapter va2(g2);
-    std::string actual = show_path(va2);
-    std::string expected(
-    "move_to(0,0)\n"
-    "line_to(-20,-20)\n"
-    "close_path(0,0)\n"
-    "move_to(1000,1000)\n"
-    "line_to(1020,1020)\n"
-    "close_path(0,0)\n"
-    );
-    CHECK(actual == expected);
-}
-
-TEST_CASE( "test 12", "should correctly encode multiple paths" ) {
-    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::geometry_type::types::Polygon);
-    g.move_to(0,0);
-    g.line_to(100,0);
-    g.line_to(100,100);
-    g.line_to(0,100);
-    g.line_to(0,5);
-    g.line_to(0,0);
-    g.close_path();
-    g.move_to(20,20);
-    g.line_to(20,60);
-    g.line_to(60,60);
-    g.line_to(60,20);
-    g.line_to(25,20);
-    g.line_to(20,20);
-    g.close_path();
-    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::geometry_type::types::Polygon);
-    double x0 = 0;
-    double y0 = 0;
-    decode_geometry<double>(feature0,g2,x0,y0,path_multiplier);
-    mapnik::vertex_adapter va2(g2);
-    std::string actual = show_path(va2);
-    std::string expected(
-        "move_to(0,0)\n"
-        "line_to(100,0)\n"
-        "line_to(100,100)\n"
-        "line_to(0,100)\n"
-        "line_to(0,0)\n"
-        "close_path(0,0)\n"
-        "move_to(20,20)\n"
-        "line_to(20,60)\n"
-        "line_to(60,60)\n"
-        "line_to(60,20)\n"
-        "line_to(20,20)\n"
-        "close_path(0,0)\n"
-        );
-    CHECK(actual == expected);
-}
-*/
diff --git a/test/geometry_visual_test.cpp b/test/geometry_visual_test.cpp
index 1692c40..16698ea 100644
--- a/test/geometry_visual_test.cpp
+++ b/test/geometry_visual_test.cpp
@@ -1,32 +1,37 @@
-#include <iostream>
-#include <fstream>
-#include <sstream>
-#include <cmath>
-#include <mapnik/util/fs.hpp>
-#include <mapnik/projection.hpp>
-#include <mapnik/geometry_transform.hpp>
-#include <mapnik/util/geometry_to_geojson.hpp>
-#include <mapnik/geometry_correct.hpp>
-#include <mapnik/util/file_io.hpp>
-#include <mapnik/save_map.hpp>
-#include <mapnik/geometry_reprojection.hpp>
-#include <mapnik/geometry_strategy.hpp>
-#include <mapnik/proj_strategy.hpp>
-#include <mapnik/view_strategy.hpp>
-#include <mapnik/geometry_is_valid.hpp>
-#include <mapnik/geometry_is_simple.hpp>
-
+// catch
 #include "catch.hpp"
-#include "encoding_util.hpp"
-#include "test_utils.hpp"
+
+// mapnik-vector-tile
 #include "vector_tile_strategy.hpp"
 #include "vector_tile_processor.hpp"
-#include "vector_tile_backend_pbf.hpp"
-#include "vector_tile_projection.hpp"
 #include "vector_tile_geometry_decoder.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_simple.hpp>
+#include <mapnik/geometry_is_valid.hpp>
+#include <mapnik/json/geometry_parser.hpp>
+#include <mapnik/save_map.hpp>
+#include <mapnik/util/file_io.hpp>
+#include <mapnik/util/fs.hpp>
+#include <mapnik/util/geometry_to_geojson.hpp>
+
+// boost
 #include <boost/filesystem/operations.hpp>
 
-void clip_geometry(std::string const& file,
+// test utils
+#include "test_utils.hpp"
+#include "geometry_equal.hpp"
+
+// std
+#include <cmath>
+#include <fstream>
+#include <sstream>
+
+// protozero
+#include <protozero/pbf_reader.hpp>
+
+void clip_geometry(mapnik::Map const& map,
+                   std::string const& file,
                    mapnik::box2d<double> const& bbox,
                    int simplify_distance,
                    bool strictly_simple,
@@ -34,74 +39,86 @@ void clip_geometry(std::string const& file,
                    bool mpu,
                    bool process_all)
 {
-    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 tile_size = 4096;
+    int buffer_size = 0;
     std::string geojson_string;
-    std::shared_ptr<mapnik::memory_datasource> ds = testing::build_geojson_ds(file);
-    tile_type tile;
-    backend_type backend(tile,1000);
-
-    mapnik::Map map(256,256,"+init=epsg:4326");
-    mapnik::layer lyr("layer","+init=epsg:4326");
-    lyr.set_datasource(ds);
-    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,
-        1.0,
-        0,
-        0,
-        0.1,
-        strictly_simple
-    );
+    
+    mapnik::vector_tile_impl::processor ren(map);
+    // TODO - test these booleans https://github.com/mapbox/mapnik-vector-tile/issues/165
+    ren.set_strictly_simple(strictly_simple);
     ren.set_simplify_distance(simplify_distance);
     ren.set_fill_type(fill_type);
-    // TODO - test these booleans https://github.com/mapbox/mapnik-vector-tile/issues/165
     ren.set_process_all_rings(process_all);
     ren.set_multi_polygon_union(mpu);
-    ren.apply();
+    
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(bbox, tile_size, buffer_size);
+    mapnik::geometry::geometry<double> geom4326_pbf;
+    
     std::string buffer;
-    tile.SerializeToString(&buffer);
-    if (buffer.size() > 0 && tile.layers_size() > 0 && tile.layers_size() != false)
+    out_tile.serialize_to_string(buffer);
+    if (!buffer.empty())
     {
-        vector_tile::Tile_Layer const& layer = tile.layers(0);
-        if (layer.features_size() != false)
+        protozero::pbf_reader tile_reader(buffer);
+        if(!tile_reader.next(mapnik::vector_tile_impl::Tile_Encoding::LAYERS))
         {
-            vector_tile::Tile_Feature const& f = layer.features(0);
-            mapnik::vector_tile_impl::Geometry<double> geoms(f,0.0,0.0,32.0,32.0);
-            mapnik::geometry::geometry<double> geom = mapnik::vector_tile_impl::decode_geometry<double>(geoms,f.type());
-            mapnik::geometry::scale_strategy ss(1.0/32.0);
-            mapnik::view_transform vt(256, 256, bbox);
-            mapnik::unview_strategy uvs(vt);
-            using sg_type = strategy_group_first<mapnik::geometry::scale_strategy, mapnik::unview_strategy >;
-            sg_type sg(ss, uvs);
-            mapnik::geometry::geometry<double> geom4326 = transform<double>(geom, sg);
+            throw std::runtime_error("tile buffer contains no layer!");
+        }
+        protozero::pbf_reader layer_reader = tile_reader.get_message();
+        if (layer_reader.next(mapnik::vector_tile_impl::Layer_Encoding::FEATURES))
+        {
+            protozero::pbf_reader feature_reader = layer_reader.get_message();
+            int32_t geometry_type = mapnik::vector_tile_impl::Geometry_Type::UNKNOWN; 
+            std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+            while (feature_reader.next())
+            {
+                if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::GEOMETRY)
+                {
+                    geom_itr = feature_reader.get_packed_uint32();
+                }
+                else if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::TYPE)
+                {
+                    geometry_type = feature_reader.get_enum();
+                }
+                else
+                {
+                    feature_reader.skip();
+                }
+            }
+            double sx = static_cast<double>(tile_size) / bbox.width();
+            double sy = static_cast<double>(tile_size) / bbox.height();
+            double i_x = bbox.minx();
+            double i_y = bbox.maxy();
+            
+            mapnik::vector_tile_impl::GeometryPBF<double> geoms2_pbf(geom_itr, i_x, i_y, 1.0 * sx, -1.0 * sy);
+            geom4326_pbf = mapnik::vector_tile_impl::decode_geometry(geoms2_pbf, geometry_type, 2);
+            //mapnik::vector_tile_impl::GeometryPBF<double> geoms2_pbf(geom_itr, 0.0, 0.0, 1.0, 1.0);
+            //geom4326_pbf = std::move(mapnik::vector_tile_impl::decode_geometry(geoms2_pbf, geometry_type, 2));
+            
             std::string reason;
             std::string is_valid = "false";
             std::string is_simple = "false";
-            if (mapnik::geometry::is_valid(geom4326, reason))
+            if (mapnik::geometry::is_valid(geom4326_pbf, reason))
+            {
                 is_valid = "true";
-            if (mapnik::geometry::is_simple(geom4326))
+            }
+            if (mapnik::geometry::is_simple(geom4326_pbf))
+            {
                 is_simple = "true";
-
+            }
             unsigned int n_err = 0;
-            mapnik::util::to_geojson(geojson_string,geom4326);
+            mapnik::util::to_geojson(geojson_string,geom4326_pbf);
 
             geojson_string = geojson_string.substr(0, geojson_string.size()-1);
             geojson_string += ",\"properties\":{\"is_valid\":"+is_valid+", \"is_simple\":"+is_simple+", \"message\":\""+reason+"\"}}";
         }
         else
         {
-            geojson_string = "{\"type\": \"Feature\", \"coordinates\":null, \"properties\":{\"message\":\"Tile layer had no features\"}}";
+            geojson_string = "{\"type\": \"Point\", \"coordinates\":[], \"properties\":{\"message\":\"Tile layer had no features\"}}";
         }
     }
     else
     {
-        geojson_string = "{\"type\": \"Feature\", \"coordinates\":null, \"properties\":{\"message\":\"Tile had no layers\"}}";
+        geojson_string = "{\"type\": \"Point\", \"coordinates\":[], \"properties\":{\"message\":\"Tile had no layers\"}}";
     }
 
     std::string fixture_name = mapnik::util::basename(file);
@@ -137,11 +154,15 @@ void clip_geometry(std::string const& file,
         mapnik::util::file input(file_path);
         if (!input.open())
         {
-            throw std::runtime_error("failed to open geojson");
+            throw std::runtime_error("failed to open test geojson");
         }
-        mapnik::geometry::geometry<double> geom;
         std::string expected_string(input.data().get(), input.size());
+        mapnik::geometry::geometry<double> geom_expected;
         CHECK(expected_string == geojson_string);
+        if (mapnik::json::from_geojson(expected_string, geom_expected))
+        {
+            assert_g_equal(geom_expected, geom4326_pbf);
+        }
     }
 }
 
@@ -223,9 +244,8 @@ mapnik::box2d<double> zoomed_out(mapnik::box2d<double> const& bbox)
     return new_bbox;
 }
 
-TEST_CASE( "geometries", "should reproject, clip, and simplify")
+TEST_CASE("geometries visual tests")
 {
-
     std::vector<std::string> geometries = mapnik::util::list_directory("./test/geometry-test-data/input");
     std::vector<std::string> benchmarks = mapnik::util::list_directory("./test/geometry-test-data/benchmark");
     if (std::getenv("BENCHMARK") != nullptr)
@@ -234,8 +254,12 @@ TEST_CASE( "geometries", "should reproject, clip, and simplify")
     }
     for (std::string const& file: geometries)
     {
-        std::shared_ptr<mapnik::memory_datasource> ds = testing::build_geojson_ds(file);
+        mapnik::datasource_ptr ds = testing::build_geojson_fs_ds(file);
         mapnik::box2d<double> bbox = ds->envelope();
+        mapnik::Map map(256, 256, "+init=epsg:4326");
+        mapnik::layer lyr("layer","+init=epsg:4326");
+        lyr.set_datasource(ds);
+        map.add_layer(lyr);
         for (int simplification_distance : std::vector<int>({0, 4, 8}))
         {
             for (bool strictly_simple : std::vector<bool>({false, true}))
@@ -245,19 +269,19 @@ TEST_CASE( "geometries", "should reproject, clip, and simplify")
                 types.emplace_back(mapnik::vector_tile_impl::non_zero_fill);
                 types.emplace_back(mapnik::vector_tile_impl::positive_fill); 
                 types.emplace_back(mapnik::vector_tile_impl::negative_fill);
-                for (auto type : types)
+                for (auto const& type : types)
                 {
                     for (bool mpu : std::vector<bool>({false, true}))
                     {
                         for (bool process_all : std::vector<bool>({false, true}))
                         {
-                            clip_geometry(file, bbox, simplification_distance, strictly_simple, type, mpu, process_all);
-                            clip_geometry(file, middle_fifty(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
-                            clip_geometry(file, top_left(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
-                            clip_geometry(file, top_right(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
-                            clip_geometry(file, bottom_left(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
-                            clip_geometry(file, bottom_right(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
-                            clip_geometry(file, zoomed_out(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(map, file, bbox, simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(map, file, middle_fifty(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(map, file, top_left(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(map, file, top_right(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(map, file, bottom_left(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(map, file, bottom_right(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(map, file, zoomed_out(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
                         }
                     }
                 }
diff --git a/test/raster_tile.cpp b/test/raster_tile.cpp
index 3afb8d6..466773b 100644
--- a/test/raster_tile.cpp
+++ b/test/raster_tile.cpp
@@ -6,10 +6,9 @@
 
 // vector output api
 #include "vector_tile_processor.hpp"
-#include "vector_tile_backend_pbf.hpp"
-#include "vector_tile_util.hpp"
-#include "vector_tile_datasource.hpp"
+#include "vector_tile_datasource_pbf.hpp"
 
+// mapnik
 #include <mapnik/util/fs.hpp>
 #include <mapnik/datasource_cache.hpp>
 #include <mapnik/agg_renderer.hpp>
@@ -19,24 +18,28 @@
 #include <mapnik/feature_factory.hpp>
 #include <mapnik/raster.hpp>
 
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
 #include <sstream>
 #include <fstream>
 
-TEST_CASE( "raster tile output 1", "should create raster tile with one raster layer" ) {
-    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_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);
+TEST_CASE("raster tile output 1")
+{
+    // this test should create raster tile with one raster layer
+    unsigned _x = 0;
+    unsigned _y = 0;
+    unsigned _z = 1;
     unsigned tile_size = 512;
-    tile_type tile;
-    backend_type backend(tile,16);
+    int buffer_size = 256;
+    
+    // setup map object
     mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
-    map.set_buffer_size(256);
+    map.set_buffer_size(buffer_size);
     mapnik::layer lyr("layer",map.srs());
     mapnik::parameters params;
     params["type"] = "gdal";
@@ -44,17 +47,21 @@ TEST_CASE( "raster tile output 1", "should create raster tile with one raster la
     // 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/data/natural_earth.tif";
-    std::shared_ptr<mapnik::datasource> ds =
-        mapnik::datasource_cache::instance().create(params);
+    std::shared_ptr<mapnik::datasource> ds = mapnik::datasource_cache::instance().create(params);
     lyr.set_datasource(ds);
     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,false,"jpeg",mapnik::SCALING_BILINEAR);
-    ren.apply();
-    std::string key("");
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
-    CHECK("" == key);
+
+    // Create the processor
+    mapnik::vector_tile_impl::processor ren(map);
+    ren.set_image_format("jpeg");
+    ren.set_scaling_method(mapnik::SCALING_BILINEAR);
+    
+    // Request the tile
+    mapnik::vector_tile_impl::merc_tile out_tile = ren.create_tile(_x, _y, _z, tile_size, buffer_size);
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
+    
+    // Test that tile is correct
     CHECK(1 == tile.layers_size());
     vector_tile::Tile_Layer const& layer = tile.layers(0);
     CHECK(std::string("layer") == layer.name());
@@ -68,45 +75,49 @@ TEST_CASE( "raster tile output 1", "should create raster tile with one raster la
     // debug
     bool debug = false;
 
-    if (!mapnik::util::exists("test/fixtures/expected-2.jpeg")) {
+    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) {
+        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()) {
+    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) {
+    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;
-    if (!debug) {
+    std::size_t expected_vtile_size = expected_image_size + 26;
+    if (!debug)
+    {
         CHECK(expected_image_size == ras_buffer.size());
         CHECK(expected_vtile_size == tile.ByteSize());
     }
     std::string buffer;
     CHECK(tile.SerializeToString(&buffer));
-    if (!debug) {
+    if (!debug) 
+    {
         CHECK(expected_vtile_size == buffer.size());
     }
     // now read back and render image
     mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
-    map2.set_buffer_size(256);
-    tile_type tile2;
+    map2.set_buffer_size(buffer_size);
+    
+    vector_tile::Tile 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());
     vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
     CHECK(std::string("layer") == layer2.name());
@@ -116,62 +127,71 @@ TEST_CASE( "raster tile output 1", "should create raster tile with one raster la
     CHECK(0 == f2.geometry_size());
     CHECK(f2.has_raster());
     CHECK(!f2.raster().empty());
-    if (!debug) {
+    if (!debug)
+    {
         CHECK(expected_image_size == f2.raster().size());
     }
     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,_x,_y,_z,map2.width());
+    protozero::pbf_reader layer_reader;
+    out_tile.layer_reader(0, layer_reader);
+    std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
+                                    mapnik::vector_tile_impl::tile_datasource_pbf>(
+                                        layer_reader,_x,_y,_z);
     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);
+    map2.zoom_all();
     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-2.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) {
+    if (diff > 0)
+    {
         mapnik::save_to_file(im_data,"test/fixtures/actual-2.png","png32");
     }
 }
 
-TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
-    typedef vector_tile::Tile tile_type;
-    tile_type tile;
+TEST_CASE("raster tile output 2")
+{
+    // the test is to check if you can overzoom a raster after encoding it into a vector tile
+    unsigned tile_size = 256;
+    int buffer_size = 1024;
+    mapnik::vector_tile_impl::merc_tile out_tile(0, 0, 0, tile_size, buffer_size);
     {
-        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";
+        mapnik::box2d<double> const& bbox = out_tile.extent();
         std::ostringstream s;
         s << std::fixed << std::setprecision(16)
           << bbox.minx() << ',' << bbox.miny() << ','
           << bbox.maxx() << ',' << bbox.maxy();
+        
+        // build map
+        mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+        map.set_buffer_size(buffer_size);
+        mapnik::layer lyr("layer",map.srs());
+        mapnik::parameters params;
+        params["type"] = "gdal";
         params["extent"] = s.str();
         params["file"] = "test/data/256x256.png";
-        std::shared_ptr<mapnik::datasource> ds =
-            mapnik::datasource_cache::instance().create(params);
+        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,false,"jpeg",mapnik::SCALING_BILINEAR);
-        ren.apply();
+        
+        // build processor
+        mapnik::vector_tile_impl::processor ren(map);
+        ren.set_image_format("jpeg");
+        ren.set_scaling_method(mapnik::SCALING_BILINEAR);
+        
+        // Update the tile
+        ren.update_tile(out_tile);
     }
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
     // Done creating test data, now test created tile
     CHECK(1 == tile.layers_size());
     vector_tile::Tile_Layer const& layer = tile.layers(0);
@@ -183,9 +203,11 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
     CHECK(f.has_raster());
     std::string const& ras_buffer = f.raster();
     CHECK(!ras_buffer.empty());
+    
     // debug
     bool debug = false;
-    if (debug) {
+    if (debug)
+    {
         std::ofstream file("out2.png", std::ios::out|std::ios::trunc|std::ios::binary);
         file << ras_buffer;
         file.close();
@@ -193,17 +215,19 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
 
     // confirm tile looks correct as encoded
     std::size_t expected_image_size = 1654;
-    int expected_vtile_size = expected_image_size + 23;
-    if (!debug) {
+    std::size_t 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) {
+    out_tile.serialize_to_string(buffer);
+    if (!debug)
+    {
         CHECK(expected_vtile_size == buffer.size());
     }
-    tile_type tile2;
+    vector_tile::Tile tile2;
     CHECK(tile2.ParseFromString(buffer));
     CHECK(1 == tile2.layers_size());
     vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
@@ -214,7 +238,8 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
     CHECK(0 == f2.geometry_size());
     CHECK(f2.has_raster());
     CHECK(!f2.raster().empty());
-    if (!debug) {
+    if (!debug)
+    {
         CHECK(expected_image_size == f2.raster().size());
     }
 
@@ -228,9 +253,11 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
     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);
+    protozero::pbf_reader layer_reader;
+    out_tile.layer_reader(0, layer_reader);
+    std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
+                                    mapnik::vector_tile_impl::tile_datasource_pbf>(
+                                        layer_reader,0,0,0);
     lyr2.set_datasource(ds2);
     lyr2.add_style("style");
     map2.add_layer(lyr2);
@@ -239,21 +266,53 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
     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")) {
+    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) {
+    if (diff > 0)
+    {
         mapnik::save_to_file(im,"test/fixtures/actual-3.png","png32");
     }
 }
 
-TEST_CASE( "raster tile output 3", "should be able to round trip image with alpha" ) {
-    mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
-
+TEST_CASE("raster tile output 3 -- should be able to round trip image with alpha")
+{
     // create a vtile from scratch with a raster
+    unsigned tile_size = 256;
+    mapnik::vector_tile_impl::merc_tile out_tile(0, 0, 0, tile_size);
+    mapnik::box2d<double> const& bbox = out_tile.extent();
+
+    {
+        // build map
+        mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+        mapnik::layer lyr("layer",map.srs());
+        mapnik::parameters params;
+        params["type"] = "raster";
+        params["lox"] = bbox.minx();
+        params["loy"] = bbox.miny();
+        params["hix"] = bbox.maxx();
+        params["hiy"] = bbox.maxy();
+        params["file"] = "test/fixtures/alpha-white-2.png";
+        std::shared_ptr<mapnik::datasource> ds = mapnik::datasource_cache::instance().create(params);
+        lyr.set_datasource(ds);
+        map.add_layer(lyr);
+        
+        // build processor
+        mapnik::vector_tile_impl::processor ren(map);
+        ren.set_image_format("png32");
+        ren.set_scaling_method(mapnik::SCALING_NEAR);
+        
+        // Update the tile
+        ren.update_tile(out_tile);
+    }
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
+    // Done creating test data, now test created tile
 
+/*
     vector_tile::Tile tile;
     std::string format("png32");
     {
@@ -271,6 +330,7 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
         backend.stop_tile_feature();
         backend.stop_tile_layer();
     }
+*/
     {
         REQUIRE(1 == tile.layers_size());
         vector_tile::Tile_Layer const& layer = tile.layers(0);
@@ -279,16 +339,18 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
         REQUIRE(f.has_raster());
         std::string const& ras_buffer = f.raster();
         REQUIRE(!ras_buffer.empty());
-        std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(ras_buffer.data(),ras_buffer.size()));
-        if (!reader.get()) {
+        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/alpha-white-2.png",0,true);
+        mapnik::image_rgba8 im_data(reader->width(), reader->height());
+        reader->read(0, 0, im_data);
+        unsigned diff = testing::compare_images(im_data, "./test/fixtures/alpha-white-2.png", 0, true);
         CHECK(0 == diff);
-        if (diff > 0) {
-            mapnik::save_to_file(im_data,"test/fixtures/actual-4.png",format);
+        if (diff > 0)
+        {
+            mapnik::save_to_file(im_data, "test/fixtures/actual-4.png", "png32");
         }
 
     }
@@ -296,11 +358,12 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
     // Now actually re-render to trigger the raster being passed through the processor
     // and confirm raster still looks correct
     {
+        protozero::pbf_reader layer_reader;
+        out_tile.layer_reader(0, layer_reader);
         // create datasource wrapping raster
-        unsigned tile_size = 256;
-        std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds = std::make_shared<
-                                        mapnik::vector_tile_impl::tile_datasource>(
-                                            tile.layers(0),0,0,0,tile_size);
+        std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
+                                        mapnik::vector_tile_impl::tile_datasource_pbf>(
+                                            layer_reader,0,0,0);
         // before rendering let's query the raster directly to ensure
         // the datasource returns it correctly.
         mapnik::query q(bbox);
@@ -309,10 +372,11 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
         mapnik::raster_ptr const& source = feat->get_raster();
         CHECK(source->data_.is<mapnik::image_rgba8>());
         mapnik::image_rgba8 const& source_data = mapnik::util::get<mapnik::image_rgba8>(source->data_);
-        unsigned diff = testing::compare_images(source_data,"./test/fixtures/alpha-white-2.png",0,true);
+        unsigned diff = testing::compare_images(source_data, "./test/fixtures/alpha-white-2.png", 0, true);
         CHECK(0 == diff);
-        if (diff > 0) {
-            mapnik::save_to_file(source_data,"test/fixtures/actual-5.png",format);
+        if (diff > 0)
+        {
+            mapnik::save_to_file(source_data, "test/fixtures/actual-5.png", "png32");
         }
 
         // okay, now we'll re-render another vector tile from original
@@ -324,52 +388,50 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
         map.add_layer(lyr);
         map.zoom_to_box(bbox);
 
-        typedef mapnik::vector_tile_impl::backend_pbf backend_type;
-        vector_tile::Tile round_tipped_tile;
-        backend_type backend(round_tipped_tile,16);
-        typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
-        mapnik::request m_req(tile_size,tile_size,bbox);
-        renderer_type ren(backend,
-                          map,
-                          m_req,
-                          1.0,
-                          0,
-                          0,
-                          1,
-                          false,
-                          format,
-                          mapnik::SCALING_BILINEAR);
-        ren.apply();
-        REQUIRE(1 == round_tipped_tile.layers_size());
-        vector_tile::Tile_Layer const& layer = round_tipped_tile.layers(0);
+        // build processor
+        mapnik::vector_tile_impl::processor ren(map);
+        ren.set_image_format("png32");
+        //ren.set_scaling_method(mapnik::SCALING_NEAR);
+        
+        // Update the tile
+        mapnik::vector_tile_impl::tile new_tile = ren.create_tile(0, 0, 0, tile_size); //, buffer_size);
+        
+        vector_tile::Tile round_tripped_tile;
+        round_tripped_tile.ParseFromString(new_tile.get_buffer());
+
+        REQUIRE(1 == round_tripped_tile.layers_size());
+        vector_tile::Tile_Layer const& layer = round_tripped_tile.layers(0);
         REQUIRE(1 == layer.features_size());
         vector_tile::Tile_Feature const& f = layer.features(0);
         REQUIRE(f.has_raster());
         std::string const& ras_buffer = f.raster();
         REQUIRE(!ras_buffer.empty());
         std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(ras_buffer.data(),ras_buffer.size()));
-        if (!reader.get()) {
+        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);
+        mapnik::image_rgba8 im_data(reader->width(), reader->height());
+        reader->read(0, 0, im_data);
 
-        if (!mapnik::util::exists("test/fixtures/expected-4.png")) {
-            mapnik::save_to_file(im_data,"test/fixtures/expected-4.png","png32");
+        if (!mapnik::util::exists("test/fixtures/expected-4.png"))
+        {
+            mapnik::save_to_file(im_data, "test/fixtures/expected-4.png", "png32");
         }
 
-        diff = testing::compare_images(im_data,"test/fixtures/expected-4.png",0,true);
+        diff = testing::compare_images(im_data, "test/fixtures/expected-4.png", 0, true);
         CHECK(0 == diff);
-        if (diff > 0) {
-            mapnik::save_to_file(im_data,"test/fixtures/actual-7.png",format);
+        if (diff > 0)
+        {
+            mapnik::save_to_file(im_data, "test/fixtures/actual-7.png", "png32");
         }
 
-        // visually identical but scaling causes some pixels shifts compared to original
-        diff = testing::compare_images(im_data,"./test/fixtures/alpha-white-2.png",0,true);
-        CHECK(13853 == diff);
-        if (diff > 0) {
-            mapnik::save_to_file(im_data,"test/fixtures/actual-7.png",format);
+        diff = testing::compare_images(im_data, "./test/fixtures/alpha-white-2.png", 0, true);
+        CHECK(0 == diff);
+        if (diff > 0)
+        {
+            mapnik::save_to_file(im_data, "test/fixtures/actual-7.png", "png32");
         }
     }
 
diff --git a/test/system/encode_and_datasource_decode.cpp b/test/system/encode_and_datasource_decode.cpp
new file mode 100644
index 0000000..a9ef5e6
--- /dev/null
+++ b/test/system/encode_and_datasource_decode.cpp
@@ -0,0 +1,119 @@
+#include "catch.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_strategy.hpp>
+#include <mapnik/geometry_transform.hpp>
+
+// mapnik-vector-tile
+#include "vector_tile_geometry_encoder_pbf.hpp"
+#include "vector_tile_datasource_pbf.hpp"
+
+// vector tile
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+TEST_CASE("encoding multi line string and check output datasource")
+{
+    mapnik::geometry::multi_line_string<std::int64_t> geom;
+    {
+        mapnik::geometry::line_string<std::int64_t> ring;
+        ring.add_coord(0,0);
+        ring.add_coord(2,2);
+        geom.emplace_back(std::move(ring));
+    }
+    {
+        mapnik::geometry::line_string<std::int64_t> ring;
+        ring.add_coord(1,1);
+        ring.add_coord(2,2);
+        geom.emplace_back(std::move(ring));
+    }
+    
+    vector_tile::Tile tile;
+    vector_tile::Tile_Layer * t_layer = tile.add_layers();
+    t_layer->set_name("layer");
+    t_layer->set_version(2);
+    t_layer->set_extent(4096);
+    vector_tile::Tile_Feature * t_feature = t_layer->add_features();
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    CHECK(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+    t_feature->ParseFromString(feature_str);
+
+    REQUIRE(1 == tile.layers_size());
+    vector_tile::Tile_Layer const& layer = tile.layers(0);
+    CHECK(1 == layer.features_size());
+    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
+    CHECK(0 == f.geometry(2)); // y:0
+    CHECK(10 == f.geometry(3)); // 1 line_to
+    CHECK(4 == f.geometry(4)); // x:2
+    CHECK(4 == f.geometry(5)); // y:2
+    CHECK(9 == f.geometry(6)); // 1 move_to
+    CHECK(1 == f.geometry(7)); // x:1
+    CHECK(1 == f.geometry(8)); // y:1
+    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;
+
+    std::string buffer;
+    tile.SerializeToString(&buffer);
+    protozero::pbf_reader pbf_tile(buffer);
+    pbf_tile.next();
+    protozero::pbf_reader layer2 = pbf_tile.get_message();
+    mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
+    mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+    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 and decoding with datasource simple polygon")
+{
+    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));
+    }
+    
+    unsigned path_multiplier = 16;
+    mapnik::geometry::scale_rounding_strategy scale_strat(path_multiplier);
+    mapnik::geometry::geometry<std::int64_t> geom2 = mapnik::geometry::transform<std::int64_t>(geom, scale_strat);
+
+    // encode geometry
+    vector_tile::Tile tile;
+    vector_tile::Tile_Layer * t_layer = tile.add_layers();
+    vector_tile::Tile_Feature * t_feature = t_layer->add_features();
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    CHECK(mapnik::vector_tile_impl::encode_geometry_pbf(geom2, feature_writer, x, y));
+    t_feature->ParseFromString(feature_str);
+    
+    // test results
+    REQUIRE(1 == tile.layers_size());
+    vector_tile::Tile_Layer const& layer = tile.layers(0);
+    CHECK(1 == layer.features_size());
+    vector_tile::Tile_Feature const& f = layer.features(0);
+    CHECK(11 == f.geometry_size());
+}
diff --git a/test/system/encode_and_decode.cpp b/test/system/encode_and_decode.cpp
new file mode 100644
index 0000000..6ec7b32
--- /dev/null
+++ b/test/system/encode_and_decode.cpp
@@ -0,0 +1,138 @@
+#include "catch.hpp"
+
+// test utils
+#include "encoding_util.hpp"
+
+//
+// Point Round Trip
+//
+
+TEST_CASE("encode and decode point") 
+{
+    mapnik::geometry::point<std::int64_t> g(0,0);
+    std::string expected(
+    "move_to(0,0)\n"
+    );
+
+    SECTION("protozero decoder VT Spec v1") 
+    {
+        CHECK(compare_pbf(g,1) == expected);
+    }
+
+    SECTION("protozero decoder VT Spec v2") 
+    {
+        CHECK(compare_pbf(g,2) == expected);
+    }
+}
+
+TEST_CASE( "encode and decode multipoint" ) 
+{
+    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"
+    );
+    
+    SECTION("protozero decoder VT Spec v1") 
+    {
+        CHECK(compare_pbf(g,1) == expected);
+    }
+
+    SECTION("protozero decoder VT Spec v2") 
+    {
+        CHECK(compare_pbf(g,2) == expected);
+    }
+}
+
+//
+// Linestring Round Trip
+//
+
+TEST_CASE( "encode and decode linestring" )
+{
+    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"
+    );
+
+    SECTION("protozero decoder VT Spec v1") 
+    {
+        CHECK(compare_pbf(g,1) == expected);
+    }
+
+    SECTION("protozero decoder VT Spec v2") 
+    {
+        CHECK(compare_pbf(g,2) == expected);
+    }
+}
+
+TEST_CASE( "encode and decode multi_line_string" )
+{
+    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"
+    );
+
+    SECTION("protozero decoder VT Spec v1") 
+    {
+        CHECK(compare_pbf(g,1) == expected);
+    }
+
+    SECTION("protozero decoder VT Spec v2") 
+    {
+        CHECK(compare_pbf(g,2) == expected);
+    }
+}
+
+TEST_CASE( "encode and decode polygon" ) 
+{
+    mapnik::geometry::polygon<std::int64_t> g;
+    g.exterior_ring.add_coord(0,0);
+    g.exterior_ring.add_coord(100,0);
+    g.exterior_ring.add_coord(100,100);
+    g.exterior_ring.add_coord(0,0);
+    std::string expected(
+    "move_to(0,0)\n"
+    "line_to(100,0)\n"
+    "line_to(100,100)\n"
+    "close_path(0,0)\n"
+    );
+
+    SECTION("protozero decoder VT Spec v1") 
+    {
+        CHECK(compare_pbf(g,1) == expected);
+    }
+
+    SECTION("protozero decoder VT Spec v2") 
+    {
+        CHECK(compare_pbf(g,2) == expected);
+    }
+}
diff --git a/test/system/processor_and_datasource.cpp b/test/system/processor_and_datasource.cpp
new file mode 100644
index 0000000..5748075
--- /dev/null
+++ b/test/system/processor_and_datasource.cpp
@@ -0,0 +1,321 @@
+#include "catch.hpp"
+
+// mapnik-vector-tile
+#include "vector_tile_datasource_pbf.hpp"
+#include "vector_tile_processor.hpp"
+
+// mapnik
+#include <mapnik/agg_renderer.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/load_map.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/util/fs.hpp>
+
+// test utils
+#include "test_utils.hpp"
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// boost
+#include <boost/optional/optional_io.hpp>
+
+// std
+#include <set>
+
+TEST_CASE("vector tile output -- simple two points")
+{
+    // This test should create vector tile with two points
+
+    // Build Map
+    mapnik::Map map(256, 256, "+init=epsg:3857");
+    mapnik::layer lyr("layer",map.srs());
+    lyr.set_datasource(testing::build_ds(0,0,true));
+    map.add_layer(lyr);
+    
+    // Create processor
+    mapnik::vector_tile_impl::processor ren(map);
+    
+    // Request Tile
+    mapnik::vector_tile_impl::merc_tile out_tile = ren.create_tile(0,0,0);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+
+    // Now check that the tile is correct.
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
+    REQUIRE(1 == tile.layers_size());
+    vector_tile::Tile_Layer const& layer = tile.layers(0);
+    CHECK(std::string("layer") == layer.name());
+    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()));
+    REQUIRE(3 == f.geometry_size());
+    CHECK(9 == f.geometry(0));
+    CHECK(4096 == f.geometry(1));
+    CHECK(4096 == f.geometry(2));
+    CHECK(190 == tile.ByteSize());
+    std::string buffer;
+    CHECK(tile.SerializeToString(&buffer));
+    CHECK(190 == buffer.size());
+}
+
+TEST_CASE("vector tile output -- empty tile")
+{
+    // test adding empty layers should result in empty tile
+    mapnik::Map map(256,256,"+init=epsg:3857");
+    map.add_layer(mapnik::layer("layer",map.srs()));
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+    CHECK(out_tile.is_painted() == false);
+    CHECK(out_tile.is_empty() == true);
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
+    CHECK(0 == tile.layers_size());
+    std::string buffer;
+    out_tile.serialize_to_string(buffer);
+    CHECK(buffer.empty());
+}
+
+TEST_CASE("vector tile output -- layers outside extent")
+{
+    // adding layers with geometries outside rendering extent should not add layer
+    unsigned tile_size = 4096;
+    // Create map
+    mapnik::Map map(256,256,"+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::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.add_layer(lyr);
+
+    mapnik::box2d<double> custom_bbox(0,0,10,10);
+    
+    // Build processor and create tile
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(custom_bbox, tile_size);
+    CHECK(out_tile.is_painted() == false);
+    CHECK(out_tile.is_empty() == true);
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
+    CHECK(0 == tile.layers_size());
+    std::string buffer;
+    out_tile.serialize_to_string(buffer);
+    CHECK(buffer.empty());
+}
+
+TEST_CASE("vector tile output is empty -- degenerate geometries")
+{
+    // adding layers with degenerate geometries should not add layer
+    unsigned tile_size = 4096;
+    mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+    mapnik::Map map(256,256,"+init=epsg:3857");
+    mapnik::layer lyr("layer",map.srs());
+    // create a datasource with a feature outside the map
+    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.add_layer(lyr);
+    
+    // Build processor and create tile
+    mapnik::vector_tile_impl::processor ren(map);
+    
+    // Request Tile
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(bbox, tile_size);
+    
+    // Check output
+    CHECK(out_tile.is_painted() == false);
+    CHECK(out_tile.is_empty() == true);
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
+    CHECK(0 == tile.layers_size());
+    std::string buffer;
+    out_tile.serialize_to_string(buffer);
+    CHECK(buffer.empty());
+}
+
+TEST_CASE("vector tile render simple point")
+{
+    // should be able to parse message and render point
+    unsigned tile_size = 4096;
+    mapnik::Map map(256, 256, "+init=epsg:3857");
+    mapnik::layer lyr("layer", map.srs());
+    lyr.set_datasource(testing::build_ds(0,0));
+    map.add_layer(lyr);
+    
+    // create processor
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+    // serialize to message
+    std::string buffer;
+    out_tile.serialize_to_string(buffer);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+    CHECK(147 == buffer.size());
+
+    // create a new vector tile from the buffer
+    vector_tile::Tile tile2;
+    CHECK(tile2.ParseFromString(buffer));
+
+    // Validate the new tile.
+    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());
+    
+    // Create another map
+    mapnik::Map map2(256, 256, "+init=epsg:3857");
+    mapnik::layer lyr2("layer",map.srs());
+    
+    // Create datasource from tile.
+    protozero::pbf_reader layer_reader;
+    out_tile.layer_reader(0, layer_reader);
+    std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
+                                    mapnik::vector_tile_impl::tile_datasource_pbf>(
+                                        layer_reader,0,0,0);
+    CHECK( ds->type() == mapnik::datasource::Vector );
+    CHECK( ds->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
+    
+    // Check that all the names are in the list
+    mapnik::layer_descriptor lay_desc = ds->get_descriptor();
+    std::set<std::string> expected_names;
+    expected_names.insert("bool");
+    expected_names.insert("boolf");
+    expected_names.insert("double");
+    expected_names.insert("float");
+    expected_names.insert("int");
+    expected_names.insert("name");
+    expected_names.insert("uint");
+    std::size_t desc_count = 0;
+    for (auto const& desc : lay_desc.get_descriptors())
+    {
+        ++desc_count;
+        CHECK(expected_names.count(desc.get_name()) == 1);
+    }
+    CHECK(desc_count == expected_names.size());
+    
+    // Add datasource to layer and map
+    lyr2.set_datasource(ds);
+    lyr2.add_style("style");
+    map2.add_layer(lyr2);
+    // Load map style
+    mapnik::load_map(map2,"test/data/style.xml");
+    map2.zoom_all();
+    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-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")
+{
+    mapnik::Map map(256,256,"+init=epsg:3857");
+    mapnik::layer lyr("layer",map.srs());
+    lyr.set_datasource(testing::build_ds(0,0));
+    map.add_layer(lyr);
+    
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+
+    // serialize to message
+    std::string buffer;
+    out_tile.serialize_to_string(buffer);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+    
+    // check that vector tile contains proper information
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
+    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());
+    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
+    // create a datasource from the vector tile
+    protozero::pbf_reader layer_reader;
+    out_tile.layer_reader(0, layer_reader);
+    mapnik::vector_tile_impl::tile_datasource_pbf ds(layer_reader,0,0,0);
+
+    // ensure we can query single feature
+    mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+    mapnik::featureset_ptr fs;
+    fs = ds.features(mapnik::query(bbox));
+    mapnik::feature_ptr feat = fs->next();
+    // Check that feature is not empty.
+    CHECK(feat != mapnik::feature_ptr());
+    CHECK(feat->size() == 0);
+    // Check that this was the only feature so next should be empty
+    CHECK(fs->next() == mapnik::feature_ptr());
+
+    // Now query for another feature.
+    mapnik::query qq = mapnik::query(mapnik::box2d<double>(-1,-1,1,1));
+    qq.add_property_name("name");
+    fs = ds.features(qq);
+    feat = fs->next();
+    // Check that feature is not empty
+    CHECK(feat != mapnik::feature_ptr());
+    CHECK(feat->size() == 1);
+    CHECK(feat->get("name") == mapnik::value_unicode_string("null island"));
+    CHECK(fs->next() == mapnik::feature_ptr());
+
+    // now check that datasource api throws out feature which is outside extent
+    fs = ds.features(mapnik::query(mapnik::box2d<double>(-10,-10,-10,-10)));
+    CHECK(fs->next() == mapnik::feature_ptr());
+
+    // ensure same behavior for feature_at_point
+    fs = ds.features_at_point(mapnik::coord2d(0.0,0.0),0.0001);
+    CHECK(fs->next() != mapnik::feature_ptr());
+
+    fs = ds.features_at_point(mapnik::coord2d(1.0,1.0),1.0001);
+    CHECK(fs->next() != mapnik::feature_ptr());
+
+    fs = ds.features_at_point(mapnik::coord2d(-10,-10),0);
+    CHECK(fs->next() == mapnik::feature_ptr());
+
+    // finally, make sure attributes are also filtered
+    mapnik::feature_ptr f_ptr;
+    fs = ds.features(mapnik::query(bbox));
+    f_ptr = fs->next();
+    CHECK(f_ptr != mapnik::feature_ptr());
+    // no attributes
+    CHECK(f_ptr->context()->size() == 0);
+
+    mapnik::query q(bbox);
+    q.add_property_name("name");
+    fs = ds.features(q);
+    f_ptr = fs->next();
+    CHECK(f_ptr != mapnik::feature_ptr());
+    // one attribute
+    CHECK(f_ptr->context()->size() == 1);
+}
diff --git a/test/system/remove_repeated_point.cpp b/test/system/remove_repeated_point.cpp
new file mode 100644
index 0000000..bf77324
--- /dev/null
+++ b/test/system/remove_repeated_point.cpp
@@ -0,0 +1,23 @@
+#include "catch.hpp"
+
+// test-utils
+#include "round_trip.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_empty.hpp>
+
+TEST_CASE("vector tile multi_point encoding with repeated points should be removed")
+{
+    mapnik::geometry::multi_point<double> geom;
+    geom.emplace_back(0,0);
+    geom.emplace_back(0,0);
+    geom.emplace_back(1,1);
+    geom.emplace_back(1,1);
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "MULTIPOINT(128 -128,128.711 -126.578)" );
+    CHECK( new_geom.is<mapnik::geometry::multi_point<double> >() );
+}
diff --git a/test/system/round_trip.cpp b/test/system/round_trip.cpp
new file mode 100644
index 0000000..8009979
--- /dev/null
+++ b/test/system/round_trip.cpp
@@ -0,0 +1,247 @@
+#include "catch.hpp"
+
+// test-utils
+#include "round_trip.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_empty.hpp>
+
+TEST_CASE("vector tile round trip point encoding")
+{
+    mapnik::geometry::point<double> geom(0,0);
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POINT(128 -128)" );
+    CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE("vector tile geometry collection encoding")
+{
+    mapnik::geometry::point<double> geom_p(0,0);
+    mapnik::geometry::geometry_collection<double> geom;
+    geom.push_back(geom_p);
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POINT(128 -128)" );
+    CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE("vector tile geometry collection encoding x2")
+{
+    mapnik::geometry::point<double> geom_p(0,0);
+    mapnik::geometry::geometry_collection<double> geom_t;
+    geom_t.push_back(geom_p);
+    mapnik::geometry::geometry_collection<double> geom;
+    geom.push_back(std::move(geom_t));
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    std::string wkt;
+    CHECK( test_utils::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 single point")
+{
+    mapnik::geometry::multi_point<double> geom;
+    geom.emplace_back(0,0);
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    std::string wkt;
+    CHECK( test_utils::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")
+{
+    mapnik::geometry::multi_point<double> geom;
+    geom.emplace_back(0,0);
+    geom.emplace_back(1,1);
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    std::string wkt;
+    CHECK( test_utils::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")
+{
+    mapnik::geometry::line_string<double> geom;
+    geom.add_coord(0,0);
+    geom.add_coord(100,100);
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "LINESTRING(128 -128,192 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")
+{
+    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 = test_utils::round_trip(geom);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "LINESTRING(128 -128,192 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")
+{
+    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 = test_utils::round_trip(geom);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "MULTILINESTRING((128 -128,192 0),(120.889 -128,63.289 -256))" );
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::multi_line_string<double> >() );
+}
+
+TEST_CASE("vector tile polygon encoding")
+{
+    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 = test_utils::round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128))");
+}
+
+TEST_CASE("vector tile multi_polygon encoding of single polygon")
+{
+    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 = test_utils::round_trip(geom);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128))");
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE("vector tile multi_polygon with multipolygon union")
+{
+    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::polygon<double> poly2;
+    poly2.exterior_ring.add_coord(0,0);
+    poly2.exterior_ring.add_coord(0,10);
+    poly2.exterior_ring.add_coord(-10,10);
+    poly2.exterior_ring.add_coord(-10,0);
+    poly2.exterior_ring.add_coord(0,0);
+    mapnik::geometry::multi_polygon<double> geom;
+    geom.emplace_back(std::move(poly));
+    geom.emplace_back(std::move(poly2));
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom, 0, mapnik::vector_tile_impl::non_zero_fill, true);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128))");
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE("vector tile multi_polygon with out multipolygon union")
+{
+    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::polygon<double> poly2;
+    poly2.exterior_ring.add_coord(0,0);
+    poly2.exterior_ring.add_coord(0,10);
+    poly2.exterior_ring.add_coord(-10,10);
+    poly2.exterior_ring.add_coord(-10,0);
+    poly2.exterior_ring.add_coord(0,0);
+    mapnik::geometry::multi_polygon<double> geom;
+    geom.emplace_back(std::move(poly));
+    geom.emplace_back(std::move(poly2));
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom, 0, mapnik::vector_tile_impl::non_zero_fill, false);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "MULTIPOLYGON(((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128)),((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128)))");
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
+}
+
+TEST_CASE("vector tile multi_polygon encoding of actual 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);
+    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 = test_utils::round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
+}
+
+TEST_CASE("vector tile multi_polygon encoding overlapping multipolygons")
+{
+    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);
+    geom.emplace_back(std::move(poly));
+    mapnik::geometry::polygon<double> poly2;
+    poly2.exterior_ring.add_coord(-5,5);
+    poly2.exterior_ring.add_coord(-5,15);
+    poly2.exterior_ring.add_coord(-15,15);
+    poly2.exterior_ring.add_coord(-15,5);
+    poly2.exterior_ring.add_coord(-5,5);
+    geom.emplace_back(std::move(poly2));
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
+}
diff --git a/test/system/round_trip_fill_type.cpp b/test/system/round_trip_fill_type.cpp
new file mode 100644
index 0000000..2d2e6f0
--- /dev/null
+++ b/test/system/round_trip_fill_type.cpp
@@ -0,0 +1,65 @@
+#include "catch.hpp"
+
+// test-utils
+#include "round_trip.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_empty.hpp>
+
+TEST_CASE("vector tile polygon even odd fill")
+{
+    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 = test_utils::round_trip(poly,0,mapnik::vector_tile_impl::even_odd_fill);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128),(123.022 -118.044,125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -118.044))");
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE("vector tile polygon non zero fill")
+{
+    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 = test_utils::round_trip(poly,0,mapnik::vector_tile_impl::non_zero_fill);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128),(123.022 -118.044,125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -118.044))");
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
diff --git a/test/system/round_trip_simplification.cpp b/test/system/round_trip_simplification.cpp
new file mode 100644
index 0000000..356ac03
--- /dev/null
+++ b/test/system/round_trip_simplification.cpp
@@ -0,0 +1,144 @@
+#include "catch.hpp"
+
+// test-utils
+#include "round_trip.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_empty.hpp>
+
+TEST_CASE("vector tile point correctly passed through simplification code path")
+{
+    mapnik::geometry::point<double> geom(-122,48);
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom,500);
+    std::string wkt;
+    CHECK( test_utils::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")
+{
+    mapnik::geometry::multi_point<double> geom;
+    geom.emplace_back(-122,48);
+    geom.emplace_back(-123,49);
+    mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom,500);
+    std::string wkt;
+    CHECK( test_utils::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")
+{
+    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 = test_utils::round_trip(line,500);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "LINESTRING(128 -128,192 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")
+{
+    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 = test_utils::round_trip(geom,500);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "LINESTRING(128 -128,192 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")
+{
+    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 = test_utils::round_trip(poly,500);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128),(123.022 -118.044,125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -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")
+{
+    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 = test_utils::round_trip(mp,500);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128),(123.022 -118.044,125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -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 = test_utils::round_trip(geom,100);
+    std::string wkt;
+    CHECK( test_utils::to_wkt(wkt, new_geom) );
+    // yep this test is weird - more of a fuzz than anything
+    CHECK( wkt == "LINESTRING(0 -128,256 -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 );
+}
diff --git a/test/test_utils.cpp b/test/test_utils.cpp
index 68a8d42..f56ae15 100644
--- a/test/test_utils.cpp
+++ b/test/test_utils.cpp
@@ -1,3 +1,5 @@
+#include "test_utils.hpp"
+
 // mapnik
 #include <mapnik/map.hpp>
 #include <mapnik/layer.hpp>
@@ -20,9 +22,11 @@
 #include <string>
 #include <memory>
 
-namespace testing {
+namespace testing
+{
 
-std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool second) {
+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);
@@ -41,7 +45,8 @@ std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool seco
     feature->put_new("boolf",false);
     feature->set_geometry(mapnik::geometry::point<double>(x,y));
     ds->push(feature);
-    if (second) {
+    if (second)
+    {
         ctx->push("name2");
         mapnik::feature_ptr feature2(mapnik::feature_factory::create(ctx,1));
         feature2->put("name",tr.transcode("null island"));
@@ -52,7 +57,8 @@ std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool seco
     return ds;
 }
 
-std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& geojson_file) {
+std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& geojson_file)
+{
     mapnik::util::file input(geojson_file);
     if (!input.open())
     {
@@ -76,7 +82,8 @@ std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& g
     return ds;
 }
 
-std::shared_ptr<mapnik::datasource> build_geojson_fs_ds(std::string const& geojson_file) {
+mapnik::datasource_ptr build_geojson_fs_ds(std::string const& geojson_file)
+{
     mapnik::parameters params;
     params["type"] = "geojson";
     params["file"] = geojson_file;
@@ -84,7 +91,8 @@ std::shared_ptr<mapnik::datasource> build_geojson_fs_ds(std::string const& geojs
     return mapnik::datasource_cache::instance().create(params);
 }
 
-mapnik::geometry::geometry<double> read_geojson(std::string const& geojson_file) {
+mapnik::geometry::geometry<double> read_geojson(std::string const& geojson_file)
+{
     mapnik::util::file input(geojson_file);
     if (!input.open())
     {
@@ -121,5 +129,4 @@ unsigned compare_images(mapnik::image_rgba8 const& src1,
     return mapnik::compare(src1,src2,threshold,alpha);
 }
 
-
 } // end ns
diff --git a/test/test_utils.hpp b/test/test_utils.hpp
index 96d9fbc..4da046d 100644
--- a/test/test_utils.hpp
+++ b/test/test_utils.hpp
@@ -12,7 +12,7 @@ namespace testing {
 std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool second=false);
 mapnik::geometry::geometry<double> read_geojson(std::string const& geojson_file);
 std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& geojson_file);
-std::shared_ptr<mapnik::memory_datasource> build_geojson_fs_ds(std::string const& geojson_file);
+mapnik::datasource_ptr build_geojson_fs_ds(std::string const& geojson_file);
 
 unsigned compare_images(std::string const& src_fn,
                         std::string const& dest_fn,
diff --git a/test/unit/composite/vector.cpp b/test/unit/composite/vector.cpp
new file mode 100644
index 0000000..2ffadd6
--- /dev/null
+++ b/test/unit/composite/vector.cpp
@@ -0,0 +1,15 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_composite.hpp"
+
+//
+// Unit tests for vt compositing
+//
+
+TEST_CASE("composite")
+{
+    CHECK(true);
+}
+
+
diff --git a/test/unit/compression/compression.cpp b/test/unit/compression/compression.cpp
new file mode 100644
index 0000000..444bcaf
--- /dev/null
+++ b/test/unit/compression/compression.cpp
@@ -0,0 +1,460 @@
+#include "catch.hpp"
+
+// mapnik-vector-tile
+#include "vector_tile_compression.hpp"
+
+TEST_CASE("invalid decompression")
+{
+    std::string data("this is a string that should be compressed data");
+    // data is not compressed but we will try to decompress it
+    std::string output;
+    CHECK_THROWS(mapnik::vector_tile_impl::zlib_decompress(data, output));
+}
+
+TEST_CASE("round trip compression - zlib")
+{
+    std::string data("this is a sentence that will be compressed into something");
+    CHECK(!mapnik::vector_tile_impl::is_zlib_compressed(data));
+    
+    int strategy;
+
+    SECTION("strategy - invalid compression")
+    {
+        strategy = 99;
+        int level = Z_DEFAULT_COMPRESSION;
+        std::string compressed_data;
+        CHECK_THROWS(mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy));
+    }
+    
+    SECTION("compression level - invalid")
+    {
+        strategy = Z_DEFAULT_STRATEGY;
+        int level = 99;
+        std::string compressed_data;
+        CHECK_THROWS(mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy));
+    }
+
+    SECTION("strategy - default")
+    {
+        strategy = Z_DEFAULT_STRATEGY;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+
+    SECTION("strategy - filtered")
+    {
+        strategy = Z_FILTERED;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+
+    SECTION("strategy - huffman only")
+    {
+        strategy = Z_HUFFMAN_ONLY;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+
+    SECTION("strategy - rle")
+    {
+        strategy = Z_RLE;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+
+    SECTION("strategy - fixed")
+    {
+        strategy = Z_FIXED;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+}
+
+TEST_CASE("round trip compression - gzip")
+{
+    std::string data("this is a sentence that will be compressed into something");
+    CHECK(!mapnik::vector_tile_impl::is_gzip_compressed(data));
+    
+    int strategy;
+
+    SECTION("strategy - invalid compression")
+    {
+        strategy = 99;
+        int level = Z_DEFAULT_COMPRESSION;
+        std::string compressed_data;
+        CHECK_THROWS(mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy));
+    }
+
+    SECTION("compression level - invalid")
+    {
+        strategy = Z_DEFAULT_STRATEGY;
+        int level = 99;
+        std::string compressed_data;
+        CHECK_THROWS(mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy));
+    }
+
+    SECTION("strategy - default")
+    {
+        strategy = Z_DEFAULT_STRATEGY;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+
+    SECTION("strategy - filtered")
+    {
+        strategy = Z_FILTERED;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+
+    SECTION("strategy - huffman only")
+    {
+        strategy = Z_HUFFMAN_ONLY;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+
+    SECTION("strategy - rle")
+    {
+        strategy = Z_RLE;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+
+    SECTION("strategy - fixed")
+    {
+        strategy = Z_FIXED;
+
+        SECTION("no compression")
+        {
+            int level = Z_NO_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("default compression level")
+        {
+            int level = Z_DEFAULT_COMPRESSION;
+            std::string compressed_data;
+            mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+            CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+            std::string new_data;
+            mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+            CHECK(data == new_data);
+        }
+
+        SECTION("compression level -- min to max")
+        {
+            for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+            {
+                std::string compressed_data;
+                mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+                CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+                std::string new_data;
+                mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+                CHECK(data == new_data);
+            }
+        }
+    }
+}
diff --git a/test/unit/datasource-pbf/from_layer.cpp b/test/unit/datasource-pbf/from_layer.cpp
new file mode 100644
index 0000000..557b134
--- /dev/null
+++ b/test/unit/datasource-pbf/from_layer.cpp
@@ -0,0 +1,298 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_datasource_pbf.hpp"
+#include "vector_tile_projection.hpp"
+
+// mapnik
+#include <mapnik/util/geometry_to_wkt.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// Boost
+#include <boost/optional.hpp>
+
+
+TEST_CASE( "cannot create datasource from layer pbf without name" )
+{
+    std::string buffer;
+    vector_tile::Tile_Layer layer;
+
+    SECTION("VT Spec v1")
+    {
+        layer.set_version(1);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        try
+        {
+            mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+            FAIL( "expected exception" );
+        }
+        catch(std::exception const& ex)
+        {
+            CHECK(std::string(ex.what()) == "The required name field is missing in a vector tile layer.");
+        }
+    }
+
+    SECTION("VT Spec v2")
+    {
+        layer.set_version(2);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        try
+        {
+            mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+            FAIL( "expected exception" );
+        }
+        catch(std::exception const& ex)
+        {
+            CHECK(std::string(ex.what()) == "The required name field is missing in a vector tile layer. Tile does not comply with Version 2 of the Mapbox Vector Tile Specification.");
+        }
+    }
+}
+
+TEST_CASE( "can create datasource from layer pbf with name but without extent" )
+{
+    std::string buffer;
+    vector_tile::Tile_Layer layer;
+    layer.set_name("test_name");
+
+    SECTION("VT Spec v1")
+    {
+        layer.set_version(1);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+        CHECK(ds.get_name() == "test_name");
+    }
+
+    SECTION("VT Spec v2")
+    {
+        layer.set_version(2);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        try
+        {
+            mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+            FAIL( "expected exception" );
+        }
+        catch(std::exception const& ex)
+        {
+            CHECK(std::string(ex.what()) == "The required extent field is missing in the layer test_name. Tile does not comply with Version 2 of the Mapbox Vector Tile Specification.");
+        }
+    }
+}
+
+TEST_CASE( "can create datasource from layer pbf with name and extent" )
+{
+    std::string buffer;
+    vector_tile::Tile_Layer layer;
+    layer.set_name("test_name");
+    layer.set_extent(4096);
+
+    SECTION("VT Spec v1")
+    {
+        layer.set_version(1);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+        CHECK(ds.get_name() == "test_name");
+    }
+
+    SECTION("VT Spec v2")
+    {
+        layer.set_version(2);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+        CHECK(ds.get_name() == "test_name");
+    }
+}
+
+TEST_CASE( "extent of a tile effects the scale of features" )
+{
+    std::string buffer;
+    vector_tile::Tile_Layer layer;
+    layer.set_name("test_name");
+
+    // Add feature to layer
+    vector_tile::Tile_Feature * new_feature = layer.add_features();
+    new_feature->set_type(vector_tile::Tile_GeomType_POINT);
+    // MoveTo(5,5)
+    new_feature->add_geometry(9); // move_to | (1 << 3)
+    new_feature->add_geometry(protozero::encode_zigzag32(5));
+    new_feature->add_geometry(protozero::encode_zigzag32(5));
+
+    SECTION("default for v1 is 4096")
+    {
+        layer.set_version(1);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+        mapnik::query q(ds.get_tile_extent());
+        mapnik::featureset_ptr featureset = ds.features(q);
+        REQUIRE(featureset);
+        mapnik::feature_ptr feature = featureset->next();
+
+        std::string wkt0;
+        mapnik::util::to_wkt(wkt0, feature->get_geometry());
+        CHECK(wkt0 == "POINT(-19988588.6446867 19988588.6446867)");
+    }
+
+    SECTION("geometry coordinates change if layer has different extent")
+    {
+        layer.set_extent(2048);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+        mapnik::query q(ds.get_tile_extent());
+        mapnik::featureset_ptr featureset = ds.features(q);
+        REQUIRE(featureset);
+        mapnik::feature_ptr feature = featureset->next();
+
+        std::string wkt0;
+        mapnik::util::to_wkt(wkt0, feature->get_geometry());
+        CHECK(wkt0 == "POINT(-19939668.9465842 19939668.9465842)");
+    }
+}
+
+TEST_CASE( "datasource of empty layer pbf returns a null featureset pointer" )
+{
+    // From the spec: A layer SHOULD contain at least one feature.
+    // Unknown behavior when that is not the case. Current behavior is to
+    // return a null featureset.
+    std::string buffer;
+    vector_tile::Tile_Layer layer;
+    layer.set_name("test_name");
+    layer.set_extent(4096);
+
+    SECTION("VT Spec v1")
+    {
+        layer.set_version(1);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+        mapnik::query q(ds.get_tile_extent());
+        mapnik::featureset_ptr featureset = ds.features(q);
+
+        CHECK(!featureset);
+    }
+
+    SECTION("VT Spec v2")
+    {
+        layer.set_version(2);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+        mapnik::query q(ds.get_tile_extent());
+        mapnik::featureset_ptr featureset = ds.features(q);
+
+        CHECK(!featureset);
+    }
+}
+
+TEST_CASE( "datasource of pbf with unkown version returns a null featureset pointer" )
+{
+    // From spec:
+    // When a Vector Tile consumer encounters a Vector Tile layer with an unknown
+    // version, it MAY make a best-effort attempt to interpret the layer, or it MAY
+    // skip the layer. In either case it SHOULD continue to process subsequent layers
+    // in the Vector Tile.
+    std::string buffer;
+    vector_tile::Tile_Layer layer;
+    layer.set_name("test_name");
+    layer.set_extent(4096);
+
+    layer.set_version(3);
+    layer.SerializePartialToString(&buffer);
+    protozero::pbf_reader pbf_layer(buffer);
+
+    mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+    mapnik::query q(ds.get_tile_extent());
+    mapnik::featureset_ptr featureset = ds.features(q);
+
+    CHECK(!featureset);
+}
+
+TEST_CASE( "datasource of empty layer pbf returns a null featureset pointer for features_at_point query" )
+{
+    // From the spec: A layer SHOULD contain at least one feature.
+    // Unknown behavior when that is not the case. Current behavior is to
+    // return a null featureset.
+    std::string buffer;
+    vector_tile::Tile_Layer layer;
+    layer.set_name("test_name");
+    layer.set_extent(4096);
+
+    SECTION("VT Spec v1")
+    {
+        layer.set_version(1);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+        mapnik::featureset_ptr featureset = ds.features_at_point(mapnik::coord2d(0.0,0.0),0.0001);
+
+        CHECK(!featureset);
+    }
+
+    SECTION("VT Spec v2")
+    {
+        layer.set_version(2);
+        layer.SerializePartialToString(&buffer);
+        protozero::pbf_reader pbf_layer(buffer);
+
+        mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+        mapnik::featureset_ptr featureset = ds.features_at_point(mapnik::coord2d(0.0,0.0),0.0001);
+
+        CHECK(!featureset);
+    }
+}
+
+TEST_CASE( "datasource of pbf with unknown version returns a null featureset pointer" )
+{
+    // From spec:
+    // When a Vector Tile consumer encounters a Vector Tile layer with an unknown
+    // version, it MAY make a best-effort attempt to interpret the layer, or it MAY
+    // skip the layer. In either case it SHOULD continue to process subsequent layers
+    // in the Vector Tile.
+    std::string buffer;
+    vector_tile::Tile_Layer layer;
+    layer.set_name("test_name");
+    layer.set_extent(4096);
+
+    layer.set_version(3);
+    layer.SerializePartialToString(&buffer);
+    protozero::pbf_reader pbf_layer(buffer);
+
+    mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+    mapnik::featureset_ptr featureset = ds.features_at_point(mapnik::coord2d(0.0,0.0),0.0001);
+
+    CHECK(!featureset);
+}
diff --git a/test/unit/decoding/linestring.cpp b/test/unit/decoding/linestring.cpp
new file mode 100644
index 0000000..275c092
--- /dev/null
+++ b/test/unit/decoding/linestring.cpp
@@ -0,0 +1,554 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// test utils
+#include "decoding_util.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+//
+// Unit tests for geometry decoding of linestrings
+//
+
+TEST_CASE("decode simple linestring")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(10,10)
+    feature.add_geometry(protozero::encode_zigzag32(8));
+    feature.add_geometry(protozero::encode_zigzag32(8));
+    // LineTo(0,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "LINESTRING(1 1,2 2,10 10,0 10)");
+        CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "LINESTRING(1 1,2 2,10 10,0 10)");
+        CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+    }
+}
+
+TEST_CASE("decode degenerate line_string only moveto")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2));
+    }
+}
+
+TEST_CASE("decode degenerate line_string lineto(0,0)")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(1,1)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2));
+    }
+}
+
+TEST_CASE("decode line_string with first lineto command having delta zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(1,1),LineTo(2,2)
+    feature.add_geometry((2 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "LINESTRING(1 1,2 2)");
+        CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "LINESTRING(1 1,2 2)");
+        CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+    }
+}
+
+TEST_CASE("decode line_string with second lineto command having delta zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2),LineTo(2,2)
+    feature.add_geometry((2 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "LINESTRING(1 1,2 2)");
+        CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "LINESTRING(1 1,2 2)");
+        CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+    }
+}
+
+TEST_CASE("decode line_string with third lineto command having delta zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2),LineTo(3,3),LineTo(3,3)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "LINESTRING(1 1,2 2,3 3)");
+        CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "LINESTRING(1 1,2 2,3 3)");
+        CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+    }
+}
+
+TEST_CASE("decode degenerate linestring with close command at end")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // Close Path
+    feature.add_geometry(15); // close_path
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode degenerate linestring with close command first")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // Close Path
+    feature.add_geometry(15); // close_path
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode degenerate linestring with moveto command count greater then 1")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry((2 << 3u) | 1u); // command count 2
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // MoveTo(2,2)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(3,3)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode degenerate linestring with moveto command count of zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry((0 << 3u) | 1u); // command count 0
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(3,3)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode degenerate linestring with lineto command count of zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo command count 0
+    feature.add_geometry((0 << 3u) | 2u);
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode degenerate linestring that starts with unknown command")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    feature.add_geometry((1 << 3u) | 5u); // invalid command
+    // MoveTo(1,1)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode degenerate linestring that ends with unknown command")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry((1 << 3u) | 5u); // invalid command
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode degenerate linestring that begins with lineto")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // LineTo(1,1)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // MoveTo(2,2)
+    feature.add_geometry((1 << 3u) | 1u); 
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(3,3)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode degenerate linestring that begins with lineto delta zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // LineTo(0,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // MoveTo(1,1)
+    feature.add_geometry((1 << 3u) | 1u); 
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode degenerate linestring that begins with close")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // Close
+    feature.add_geometry(15);
+    // MoveTo(1,1)
+    feature.add_geometry((1 << 3u) | 1u); 
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
+
+TEST_CASE("decode linestring that begins with two moveto commands")
+{
+    // This should work with v1 but throw with v2
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo(1,1)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // MoveTo(2,2)
+    feature.add_geometry((1 << 3u) | 1u); 
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(3,3)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "LINESTRING(2 2,3 3)");
+        CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+    }
+}
diff --git a/test/unit/decoding/point.cpp b/test/unit/decoding/point.cpp
new file mode 100644
index 0000000..0345dc3
--- /dev/null
+++ b/test/unit/decoding/point.cpp
@@ -0,0 +1,365 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// test utils
+#include "decoding_util.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// std
+#include <limits>
+
+//
+// Unit tests for geometry decoding of points
+//
+
+TEST_CASE("decode simple point")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    // MoveTo(5,5)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(5));
+    feature.add_geometry(protozero::encode_zigzag32(5));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(5 5)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(5 5)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+}
+
+TEST_CASE("decode simple negative point")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    // MoveTo(-5,-5)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(-5));
+    feature.add_geometry(protozero::encode_zigzag32(-5));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(-5 -5)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(-5 -5)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+}
+
+TEST_CASE("point with delta of max int32")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    feature.add_geometry(9); // move_to | (1 << 3)
+    std::int64_t max_32t = std::numeric_limits<int32_t>::max();
+    feature.add_geometry(protozero::encode_zigzag32(max_32t));
+    feature.add_geometry(protozero::encode_zigzag32(max_32t));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(2147483647 2147483647)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(2147483647 2147483647)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+}
+
+TEST_CASE("point with delta of min int32")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    feature.add_geometry(9); // move_to | (1 << 3)
+    std::int64_t min_32t = std::numeric_limits<int32_t>::min();
+    feature.add_geometry(protozero::encode_zigzag32(min_32t));
+    feature.add_geometry(protozero::encode_zigzag32(min_32t));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(-2147483648 -2147483648)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(-2147483648 -2147483648)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+}
+
+TEST_CASE("point with delta of min int32 + 1")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    feature.add_geometry(9); // move_to | (1 << 3)
+    std::int64_t min_32t = std::numeric_limits<int32_t>::min() + 1;
+    feature.add_geometry(protozero::encode_zigzag32(min_32t));
+    feature.add_geometry(protozero::encode_zigzag32(min_32t));
+    
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(-2147483647 -2147483647)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POINT(-2147483647 -2147483647)");
+        CHECK( geom.is<mapnik::geometry::point<double> >() );
+    }
+}
+
+TEST_CASE("degenerate point with close command")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // Close Path
+    feature.add_geometry(15); // close_path
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+    }
+}
+
+TEST_CASE("degenerate point with lineto command")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(1,1)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+    }
+}
+
+TEST_CASE("degenerate point with moveto with out enough parameters")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+    }
+}
+
+TEST_CASE("degenerate point with moveto with out enough parameters - case 2")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    // MoveTo(1,1)
+    feature.add_geometry((2 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+    }
+}
+
+TEST_CASE("degenerate point with moveto with command count of zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    // MoveTo(1,1)
+    feature.add_geometry((0 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+    }
+}
+
+TEST_CASE("degenerate point with invalid command")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POINT);
+    // MoveTo(1,1)
+    feature.add_geometry((1 << 3u) | 5u); // 5 isn't valid
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+    
+    SECTION("VT Spec v1") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+    }
+
+    SECTION("VT Spec v2") 
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+    }
+}
+
+TEST_CASE("multipoint with three movetos with command count 1")
+{
+    // While this is not the proper way to encode two movetwos we want to make sure
+    // that it still works properly in the decoder.
+    vector_tile::Tile_Feature feature;
+    //feature.set_type(vector_tile::Tile_GeomType_POINT);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // MoveTo(2,2)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // MoveTo(3,3)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v2") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "MULTIPOINT(1 1,2 2,3 3)");
+        CHECK( geom.is<mapnik::geometry::multi_point<double> >() );
+    }
+
+    SECTION("VT Spec v1") 
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "MULTIPOINT(1 1,2 2,3 3)");
+        CHECK( geom.is<mapnik::geometry::multi_point<double> >() );
+    }
+}
diff --git a/test/unit/decoding/polygon.cpp b/test/unit/decoding/polygon.cpp
new file mode 100644
index 0000000..d04e67b
--- /dev/null
+++ b/test/unit/decoding/polygon.cpp
@@ -0,0 +1,1621 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// test utils
+#include "decoding_util.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+//
+// Unit tests for geometry decoding of polygons
+//
+
+TEST_CASE("decode simple polygon")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon - int64 decode")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<std::int64_t> geoms = feature_to_pbf_geometry<std::int64_t>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon with hole")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // This ring is counter clockwise
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+    // This ring is clockwise!
+    // Cursor is still at -10,0
+    // MoveTo(-7,7)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(3));
+    feature.add_geometry(protozero::encode_zigzag32(7));
+    // LineTo(-3,7)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-3,3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    // LineTo(-7,3)
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode simple multipolygon")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // This ring is counter clockwise
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+    // This ring is counter clockwise -- so it is not a hole but a new polygon
+    // Cursor is still at -10,0
+    // MoveTo(-7,7)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(3));
+    feature.add_geometry(protozero::encode_zigzag32(7));
+    // LineTo(-7,3)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    // LineTo(-3,3)
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-3,7)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0)),((-7 7,-7 3,-3 3,-3 7,-7 7)))");
+        CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0)),((-7 7,-7 3,-3 3,-3 7,-7 7)))");
+        CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode polygon with hole where winding orders are reversed.")
+{
+    // This should work with v1 parser
+    // but fail with the v2 parsers.
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // This ring is clockwise
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(0,10)
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+    // This ring is counter clockwise -- so it is not a hole but a new polygon
+    // Cursor is still at 0,10
+    // MoveTo(-7,7)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(-7));
+    feature.add_geometry(protozero::encode_zigzag32(-3));
+    // LineTo(-7,3)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    // LineTo(-3,3)
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-3,7)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2));
+    }
+}
+
+TEST_CASE("decode simple multi polygon with hole")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // This ring is counter clockwise
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+    // This ring is clockwise!
+    // Cursor is still at -10,0
+    // MoveTo(-7,7)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(3));
+    feature.add_geometry(protozero::encode_zigzag32(7));
+    // LineTo(-3,7)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-3,3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    // LineTo(-7,3)
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+    // This ring is counter clockwise
+    // Cursor is still at -7,3
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(7));
+    feature.add_geometry(protozero::encode_zigzag32(-3));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+    // This ring is clockwise!
+    // Cursor is still at -10,0
+    // MoveTo(-7,7)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(3));
+    feature.add_geometry(protozero::encode_zigzag32(7));
+    // LineTo(-3,7)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-3,3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    // LineTo(-7,3)
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)),((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)))");
+        CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)),((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)))");
+        CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode multi polygon with holes - first ring invalid")
+{
+    // One of the problems with the v1 logic is that because we are
+    // not sure about the correct winding order if we run into an exterior ring
+    // that is invalid in the first ring it could reverse all the rest of the winding orders.
+    // Within v2 this should simply throw.
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // This ring is bad exterior ring. For v1, it will make the next interior ring as the first
+    // exterior ring!
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+    // This ring is clockwise!
+    // Cursor is still at -10,0
+    // MoveTo(-7,7)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(3));
+    feature.add_geometry(protozero::encode_zigzag32(7));
+    // LineTo(-3,7)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-3,3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    // LineTo(-7,3)
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+    // This ring is counter clockwise
+    // Cursor is still at -7,3
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(7));
+    feature.add_geometry(protozero::encode_zigzag32(-3));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+    // This ring is clockwise!
+    // Cursor is still at -10,0
+    // MoveTo(-7,7)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(3));
+    feature.add_geometry(protozero::encode_zigzag32(7));
+    // LineTo(-3,7)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-3,3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    // LineTo(-7,3)
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "MULTIPOLYGON(((-7 7,-7 3,-3 3,-3 7,-7 7),(0 0,-10 0,-10 10,0 10,0 0)),((-7 7,-7 3,-3 3,-3 7,-7 7)))");
+        CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2));
+    }
+}
+
+TEST_CASE("decode simple polygon -- incorrect exterior winding order")
+{
+    // winding order is clockwise
+    // v1 will reverse the order
+    // v2 will throw as invalid
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(10,10)
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 10,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto and close")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto and close followed by close")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // Close
+    feature.add_geometry(15);
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto and close followed by lineto")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // Close
+    feature.add_geometry(15);
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto and close followed by lineto -- delta zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // Close
+    feature.add_geometry(15);
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - moveto and close followed by real polygon")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // Close
+    feature.add_geometry(15);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(-1));
+    feature.add_geometry(protozero::encode_zigzag32(-1));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto and lineto")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(10,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - moveto and lineto followed by real polygon")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(-2));
+    feature.add_geometry(protozero::encode_zigzag32(-2));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+
+TEST_CASE("decode polygon - only moveto lineto and close")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(10,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, and close followed by close")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(10,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, and close followed by lineto")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(10,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+    // LineTo(11,1)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+        
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, and close followed by lineto -- delta zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(10,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+    // LineTo(10,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - moveto lineto and close followed by real polygon")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(1,1)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(2,2)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // Close
+    feature.add_geometry(15);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(-2));
+    feature.add_geometry(protozero::encode_zigzag32(-2));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - both delta zero one command")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,0)
+    feature.add_geometry((2 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - both delta zero two commands")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - first delta zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(1,1)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - second delta zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(1,1)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // LineTo(1,1)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+    }
+    
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - lineto two commands")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(10,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(10,10)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - lineto one command")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(10,0)
+    feature.add_geometry((2 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode polygon -- moveto command count zero")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry((0 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- moveto command count two")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry((2 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- lineto command count 0")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo no commands
+    feature.add_geometry((0 << 3u) | 2u);
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- lineto command count 2 when it should be 3")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((2 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- lineto command count 4 when it should be 3")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((4 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- close is first command")
+{
+    // It is important that in this test the lineto has dx and dy of 0.
+    // This checks that the skip dx,dy 0 on lineto doesn't prevent
+    // a series of bad commands from throwing.
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // Close
+    feature.add_geometry(15);
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- extra close command")
+{
+    // It is important that in this test the lineto has dx and dy of 0.
+    // This checks that the skip dx,dy 0 on lineto doesn't prevent
+    // a series of bad commands from throwing.
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- lineto is first command -- delta zero")
+{
+    // It is important that in this test the lineto has dx and dy of 0.
+    // This checks that the skip dx,dy 0 on lineto doesn't prevent
+    // a series of bad commands from throwing.
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // LineTo(0,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- lineto is first command")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // LineTo(1,1)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- lineto is last command -- delta zero")
+{
+    // It is important that in this test the lineto has dx and dy of 0.
+    // This checks that the skip dx,dy 0 on lineto doesn't prevent
+    // a series of bad commands from throwing.
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+    // LineTo(0,0)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- lineto is last command")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+    // LineTo(1,1)
+    feature.add_geometry((1 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(1));
+    feature.add_geometry(protozero::encode_zigzag32(1));
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
+
+TEST_CASE("decode polygon -- has invalid command first")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    feature.add_geometry((1 << 3u) | 5u); // Invalid command
+    // MoveTo(0,0)
+    feature.add_geometry((1 << 3u) | 1u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+    SECTION("VT Spec v1")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+    }
+
+    SECTION("VT Spec v2")
+    {
+        CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+    }
+}
diff --git a/test/unit/decoding/polygon_scaling.cpp b/test/unit/decoding/polygon_scaling.cpp
new file mode 100644
index 0000000..33f10d2
--- /dev/null
+++ b/test/unit/decoding/polygon_scaling.cpp
@@ -0,0 +1,471 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// test utils
+#include "decoding_util.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+//
+// Unit tests for geometry decoding of polygons with scaling
+//
+
+TEST_CASE("decode simple polygon - scale 2")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 2.0, 2.0);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 5,-5 5,-5 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 5,-5 5,-5 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon - scale 2 - int64 decode")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<std::int64_t> geoms = feature_to_pbf_geometry<std::int64_t>(feature_string, 2.0, 2.0);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 5,-5 5,-5 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 5,-5 5,-5 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon - scale 3.214")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 3.214, 3.214);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 3.11138767890479,-3.11138767890479 3.11138767890479,-3.11138767890479 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 3.11138767890479,-3.11138767890479 3.11138767890479,-3.11138767890479 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon - scale 3.214 - int64 decode")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<std::int64_t> geoms = feature_to_pbf_geometry<std::int64_t>(feature_string, 3.214, 3.214);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 3,-3 3,-3 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 3,-3 3,-3 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon - scale 0.46")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 0.46, 0.46);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 21.7391304347826,-21.7391304347826 21.7391304347826,-21.7391304347826 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 21.7391304347826,-21.7391304347826 21.7391304347826,-21.7391304347826 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon - scale 0.46 - int64 decode")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<std::int64_t> geoms = feature_to_pbf_geometry<std::int64_t>(feature_string, 0.46, 0.46);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 22,-22 22,-22 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 22,-22 22,-22 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon - inverted y axis")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 1.0, -1.0);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,-10 0,-10 -10,0 -10,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,-10 0,-10 -10,0 -10,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon - inverted x axis")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, -1.0, 1.0);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 10,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 10,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon - inverted x axis and y axis")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, -1.0, -1.0);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 -10,10 -10,10 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,0 -10,10 -10,10 0,0 0))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
+
+TEST_CASE("decode simple polygon with hole - invert y axis")
+{
+    vector_tile::Tile_Feature feature;
+    feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+    // This ring is counter clockwise
+    // MoveTo(0,0)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(0,10)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(10));
+    // LineTo(-10,10)
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-10,0)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-10));
+    // Close
+    feature.add_geometry(15);
+    // This ring is clockwise!
+    // Cursor is still at -10,0
+    // MoveTo(-7,7)
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(3));
+    feature.add_geometry(protozero::encode_zigzag32(7));
+    // LineTo(-3,7)
+    feature.add_geometry((3 << 3u) | 2u);
+    feature.add_geometry(protozero::encode_zigzag32(4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // LineTo(-3,3)
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    // LineTo(-7,3)
+    feature.add_geometry(protozero::encode_zigzag32(-4));
+    feature.add_geometry(protozero::encode_zigzag32(0));
+    // Close
+    feature.add_geometry(15);
+
+    std::string feature_string = feature.SerializeAsString();
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 1.0, -1.0);
+
+    SECTION("VT Spec v1")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,-10 0,-10 -10,0 -10,0 0),(-7 -7,-7 -3,-3 -3,-3 -7,-7 -7))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+
+    SECTION("VT Spec v2")
+    {
+        auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+        std::string wkt0;
+        CHECK( test_utils::to_wkt(wkt0,geom) );
+        CHECK( wkt0 == "POLYGON((0 0,-10 0,-10 -10,0 -10,0 0),(-7 -7,-7 -3,-3 -3,-3 -7,-7 -7))");
+        CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+    }
+}
diff --git a/test/unit/encoding/linestring_pbf.cpp b/test/unit/encoding/linestring_pbf.cpp
new file mode 100644
index 0000000..7f4d7e6
--- /dev/null
+++ b/test/unit/encoding/linestring_pbf.cpp
@@ -0,0 +1,439 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// std
+#include <limits>
+
+//
+// Unit tests for geometry encoding of linestrings
+//
+
+TEST_CASE("encode pbf simple line_string")
+{
+    mapnik::geometry::line_string<std::int64_t> line;
+    line.add_coord(10,10);
+    line.add_coord(20,20);
+    line.add_coord(30,30);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Therefore 2 commands + 6 parameters = 8
+    REQUIRE(feature.geometry_size() == 8);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 20);
+    CHECK(feature.geometry(2) == 20);
+    // LineTo(20,20)
+    CHECK(feature.geometry(3) == ((2 << 3) | 2u)); 
+    CHECK(feature.geometry(4) == 20);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(30,30)
+    CHECK(feature.geometry(6) == 20);
+    CHECK(feature.geometry(7) == 20);
+}
+
+TEST_CASE("encode pbf simple line_string -- geometry type")
+{
+    mapnik::geometry::line_string<std::int64_t> line;
+    line.add_coord(10,10);
+    line.add_coord(20,20);
+    line.add_coord(30,30);
+    mapnik::geometry::geometry<std::int64_t> geom(line);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Therefore 2 commands + 6 parameters = 8
+    REQUIRE(feature.geometry_size() == 8);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 20);
+    CHECK(feature.geometry(2) == 20);
+    // LineTo(20,20)
+    CHECK(feature.geometry(3) == ((2 << 3) | 2u)); 
+    CHECK(feature.geometry(4) == 20);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(30,30)
+    CHECK(feature.geometry(6) == 20);
+    CHECK(feature.geometry(7) == 20);
+}
+
+TEST_CASE("encode pbf overlapping line_string")
+{
+    mapnik::geometry::line_string<std::int64_t> line;
+    line.add_coord(10,10);
+    line.add_coord(20,20);
+    line.add_coord(10,10);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Therefore 2 commands + 6 parameters = 8
+    REQUIRE(feature.geometry_size() == 8);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 20);
+    CHECK(feature.geometry(2) == 20);
+    // LineTo(20,20)
+    CHECK(feature.geometry(3) == ((2 << 3) | 2u)); 
+    CHECK(feature.geometry(4) == 20);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(10,10)
+    CHECK(feature.geometry(6) == 19);
+    CHECK(feature.geometry(7) == 19);
+}
+
+TEST_CASE("encode pbf line_string with repeated points")
+{
+    mapnik::geometry::line_string<std::int64_t> line;
+    line.add_coord(10,10);
+    line.add_coord(10,10);
+    line.add_coord(10,10);
+    line.add_coord(20,20);
+    line.add_coord(20,20);
+    line.add_coord(20,20);
+    line.add_coord(30,30);
+    line.add_coord(30,30);
+    line.add_coord(30,30);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    
+    // All of the repeated points should be removed resulting in the following:
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Therefore 2 commands + 6 parameters = 8
+    REQUIRE(feature.geometry_size() == 8);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 20);
+    CHECK(feature.geometry(2) == 20);
+    // LineTo(20,20)
+    CHECK(feature.geometry(3) == ((2 << 3) | 2u)); 
+    CHECK(feature.geometry(4) == 20);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(30,30)
+    CHECK(feature.geometry(6) == 20);
+    CHECK(feature.geometry(7) == 20);
+}
+
+TEST_CASE("encode pbf degenerate line_string")
+{
+    mapnik::geometry::line_string<std::int64_t> line;
+    line.add_coord(10,10);
+    
+    // since the line is degenerate the whole line should be culled during encoding
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.has_type());
+    CHECK(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encode pbf degenerate line_string all repeated points")
+{
+    mapnik::geometry::line_string<std::int64_t> line;
+    line.add_coord(10,10);
+    line.add_coord(10,10);
+    line.add_coord(10,10);
+    line.add_coord(10,10);
+    
+    // since the line is degenerate the whole line should be culled during encoding
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    CHECK(feature.has_type());
+    CHECK(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encode pbf incredibly large segments")
+{
+    // This is a test case added that is known to completely break the logic
+    // within the encoder. 
+    std::int64_t val = std::numeric_limits<std::int64_t>::max();
+    mapnik::geometry::line_string<std::int64_t> line;
+    line.add_coord(0,0);
+    line.add_coord(val,val);
+    line.add_coord(0,0);
+
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Therefore 2 commands + 6 parameters = 8
+    REQUIRE(feature.geometry_size() == 8);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(0,0)
+    CHECK(feature.geometry(3) == ((2 << 3) | 2u)); 
+    CHECK(feature.geometry(4) == 1);
+    CHECK(feature.geometry(5) == 1);
+    // LineTo(1,1)
+    CHECK(feature.geometry(6) == 2);
+    CHECK(feature.geometry(7) == 2);
+}
+
+TEST_CASE("encode pbf simple multi_line_string")
+{
+    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);
+    l1.add_coord(2,2);
+    g.push_back(std::move(l1));
+    mapnik::geometry::line_string<std::int64_t> l2;
+    l2.add_coord(5,5);
+    l2.add_coord(0,0);
+    g.push_back(std::move(l2));
+
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(g, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger
+    // Therefore 4 commands + 10 parameters = 14
+    REQUIRE(feature.geometry_size() == 14);
+    // MoveTo(0,0)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(1,1)
+    CHECK(feature.geometry(3) == ((2 << 3) | 2u)); 
+    CHECK(feature.geometry(4) == 2);
+    CHECK(feature.geometry(5) == 2);
+    // LineTo(2,2)
+    CHECK(feature.geometry(6) == 2);
+    CHECK(feature.geometry(7) == 2);
+    // MoveTo(5,5)
+    CHECK(feature.geometry(8) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(9) == 6);
+    CHECK(feature.geometry(10) == 6);
+    // LineTo(0,0)
+    CHECK(feature.geometry(11) == ((1 << 3) | 2u)); 
+    CHECK(feature.geometry(12) == 9);
+    CHECK(feature.geometry(13) == 9);
+}
+
+TEST_CASE("encode pbf simple multi_line_string -- geometry type")
+{
+    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);
+    l1.add_coord(2,2);
+    g.push_back(std::move(l1));
+    mapnik::geometry::line_string<std::int64_t> l2;
+    l2.add_coord(5,5);
+    l2.add_coord(0,0);
+    g.push_back(std::move(l2));
+    mapnik::geometry::geometry<std::int64_t> geom(g);
+
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger
+    // Therefore 4 commands + 10 parameters = 14
+    REQUIRE(feature.geometry_size() == 14);
+    // MoveTo(0,0)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(1,1)
+    CHECK(feature.geometry(3) == ((2 << 3) | 2u)); 
+    CHECK(feature.geometry(4) == 2);
+    CHECK(feature.geometry(5) == 2);
+    // LineTo(2,2)
+    CHECK(feature.geometry(6) == 2);
+    CHECK(feature.geometry(7) == 2);
+    // MoveTo(5,5)
+    CHECK(feature.geometry(8) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(9) == 6);
+    CHECK(feature.geometry(10) == 6);
+    // LineTo(0,0)
+    CHECK(feature.geometry(11) == ((1 << 3) | 2u)); 
+    CHECK(feature.geometry(12) == 9);
+    CHECK(feature.geometry(13) == 9);
+}
+
+TEST_CASE("encode pbf multi_line_string with repeated points")
+{
+    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(0,0);
+    l1.add_coord(0,0);
+    l1.add_coord(1,1);
+    l1.add_coord(1,1);
+    l1.add_coord(1,1);
+    l1.add_coord(2,2);
+    l1.add_coord(2,2);
+    l1.add_coord(2,2);
+    g.push_back(std::move(l1));
+    mapnik::geometry::line_string<std::int64_t> l2;
+    l2.add_coord(5,5);
+    l2.add_coord(5,5);
+    l2.add_coord(5,5);
+    l2.add_coord(0,0);
+    l2.add_coord(0,0);
+    l2.add_coord(0,0);
+    g.push_back(std::move(l2));
+
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(g, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    
+    // Repeated commands should be removed points should be as follows:
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger
+    // Therefore 4 commands + 10 parameters = 14
+    REQUIRE(feature.geometry_size() == 14);
+    // MoveTo(0,0)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(1,1)
+    CHECK(feature.geometry(3) == ((2 << 3) | 2u)); 
+    CHECK(feature.geometry(4) == 2);
+    CHECK(feature.geometry(5) == 2);
+    // LineTo(2,2)
+    CHECK(feature.geometry(6) == 2);
+    CHECK(feature.geometry(7) == 2);
+    // MoveTo(5,5)
+    CHECK(feature.geometry(8) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(9) == 6);
+    CHECK(feature.geometry(10) == 6);
+    // LineTo(0,0)
+    CHECK(feature.geometry(11) == ((1 << 3) | 2u)); 
+    CHECK(feature.geometry(12) == 9);
+    CHECK(feature.geometry(13) == 9);
+}
+
+TEST_CASE("encode pbf multi_line_string with two degenerate linestrings")
+{
+    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(5,0);
+    l2.add_coord(5,0);
+    l2.add_coord(5,0);
+    g.push_back(std::move(l2));
+    mapnik::geometry::line_string<std::int64_t> l3;
+    l3.add_coord(5,5);
+    l3.add_coord(0,0);
+    g.push_back(std::move(l3));
+
+    // Should remove first line string as it does not have enough points
+    // and second linestring should be removed because it only has repeated
+    // points and therefore is too small
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(g, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger
+    // Therefore 2 commands + 4 parameters = 6
+    REQUIRE(feature.geometry_size() == 6);
+    // MoveTo(5,5)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 10);
+    CHECK(feature.geometry(2) == 10);
+    // LineTo(0,0)
+    CHECK(feature.geometry(3) == ((1 << 3) | 2u)); 
+    CHECK(feature.geometry(4) == 9);
+    CHECK(feature.geometry(5) == 9);
+}
diff --git a/test/unit/encoding/point_pbf.cpp b/test/unit/encoding/point_pbf.cpp
new file mode 100644
index 0000000..0781be0
--- /dev/null
+++ b/test/unit/encoding/point_pbf.cpp
@@ -0,0 +1,202 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// std
+#include <limits>
+
+//
+// Unit tests for geometry encoding of points
+//
+
+TEST_CASE("encode pbf simple point")
+{
+    mapnik::geometry::point<std::int64_t> point(10,10);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(point, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // Therefore 1 commands + 2 parameters = 3
+    REQUIRE(feature.geometry_size() == 3);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 20);
+    CHECK(feature.geometry(2) == 20);
+}
+
+TEST_CASE("encode pbf simple point -- geometry type")
+{
+    mapnik::geometry::point<std::int64_t> point(10,10);
+    mapnik::geometry::geometry<std::int64_t> geom(point);
+
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // Therefore 1 commands + 2 parameters = 3
+    REQUIRE(feature.geometry_size() == 3);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 20);
+    CHECK(feature.geometry(2) == 20);
+}
+
+TEST_CASE("encode pbf simple negative point")
+{
+    mapnik::geometry::point<std::int64_t> point(-10,-10);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(point, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // Therefore 1 commands + 2 parameters = 3
+    REQUIRE( feature.geometry_size() == 3);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 19);
+    CHECK(feature.geometry(2) == 19);
+}
+
+TEST_CASE("encode pbf simple multi point -- geometry type")
+{
+    mapnik::geometry::multi_point<std::int64_t> mp;
+    mp.add_coord(10,10);
+    mp.add_coord(20,20);
+    mp.add_coord(30,30);
+    mapnik::geometry::geometry<std::int64_t> geom(mp);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+    
+    // MoveTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Therefore 1 commands + 6 parameters = 7
+    REQUIRE( feature.geometry_size() == 7);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((3 << 3) | 1u)); // 25
+    CHECK(feature.geometry(1) == 20);
+    CHECK(feature.geometry(2) == 20);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(3) == 20);
+    CHECK(feature.geometry(4) == 20);
+    // MoveTo(20,20)
+    CHECK(feature.geometry(5) == 20);
+    CHECK(feature.geometry(6) == 20);
+}
+
+TEST_CASE("encode pbf simple multi point")
+{
+    mapnik::geometry::multi_point<std::int64_t> mp;
+    mp.add_coord(10,10);
+    mp.add_coord(20,20);
+    mp.add_coord(30,30);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+    
+    // MoveTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Therefore 1 commands + 6 parameters = 7
+    REQUIRE( feature.geometry_size() == 7);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((3 << 3) | 1u)); // 25
+    CHECK(feature.geometry(1) == 20);
+    CHECK(feature.geometry(2) == 20);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(3) == 20);
+    CHECK(feature.geometry(4) == 20);
+    // MoveTo(20,20)
+    CHECK(feature.geometry(5) == 20);
+    CHECK(feature.geometry(6) == 20);
+}
+
+TEST_CASE("encode pbf multi point with repeated points")
+{
+    mapnik::geometry::multi_point<std::int64_t> mp;
+    mp.add_coord(10,10);
+    mp.add_coord(10,10);
+    mp.add_coord(20,20);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+    
+    // MoveTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Therefore 1 commands + 6 parameters = 7
+    REQUIRE( feature.geometry_size() == 7);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(0) == ((3 << 3) | 1u)); // 25
+    CHECK(feature.geometry(1) == 20);
+    CHECK(feature.geometry(2) == 20);
+    // MoveTo(10,10)
+    CHECK(feature.geometry(3) == 0);
+    CHECK(feature.geometry(4) == 0);
+    // MoveTo(20,20)
+    CHECK(feature.geometry(5) == 20);
+    CHECK(feature.geometry(6) == 20);
+}
+
+TEST_CASE("encode pbf empty multi point geometry")
+{
+    mapnik::geometry::multi_point<std::int64_t> mp;
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(!feature.has_type());
+    
+    REQUIRE(feature.geometry_size() == 0);
+}
+
diff --git a/test/unit/encoding/polygon_pbf.cpp b/test/unit/encoding/polygon_pbf.cpp
new file mode 100644
index 0000000..511f961
--- /dev/null
+++ b/test/unit/encoding/polygon_pbf.cpp
@@ -0,0 +1,588 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+//
+// Unit tests for geometry encoding pbf of polygons
+//
+
+TEST_CASE("encoding pbf simple polygon")
+{
+    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);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // 3 commands + 8 Params = 11 
+    REQUIRE(feature.geometry_size() == 11);
+    // MoveTo(0,0)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(0,10)
+    CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(4) == 0);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(-10,10)
+    CHECK(feature.geometry(6) == 19);
+    CHECK(feature.geometry(7) == 0);
+    // LineTo(-10,0)
+    CHECK(feature.geometry(8) == 0);
+    CHECK(feature.geometry(9) == 19);
+    // Close
+    CHECK(feature.geometry(10) == 15);
+}
+
+TEST_CASE("encoding pbf simple polygon -- geometry")
+{
+    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::geometry<std::int64_t> geom(p0);
+    
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // 3 commands + 8 Params = 11 
+    REQUIRE(feature.geometry_size() == 11);
+    // MoveTo(0,0)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(0,10)
+    CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(4) == 0);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(-10,10)
+    CHECK(feature.geometry(6) == 19);
+    CHECK(feature.geometry(7) == 0);
+    // LineTo(-10,0)
+    CHECK(feature.geometry(8) == 0);
+    CHECK(feature.geometry(9) == 19);
+    // Close
+    CHECK(feature.geometry(10) == 15);
+}
+
+TEST_CASE("encoding pbf simple polygon with hole")
+{
+    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));
+
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // 6 commands + 16 Params = 22 
+    REQUIRE(feature.geometry_size() == 22);
+    // MoveTo(0,0)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(0,10)
+    CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(4) == 0);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(-10,10)
+    CHECK(feature.geometry(6) == 19);
+    CHECK(feature.geometry(7) == 0);
+    // LineTo(-10,0)
+    CHECK(feature.geometry(8) == 0);
+    CHECK(feature.geometry(9) == 19);
+    // Close
+    CHECK(feature.geometry(10) == 15);
+    // Remember the cursor didn't move after the close
+    // so it is at -10,0
+    // MoveTo(-7,7)
+    CHECK(feature.geometry(11) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(12) == 6);
+    CHECK(feature.geometry(13) == 14);
+    // LineTo(-3,7)
+    CHECK(feature.geometry(14) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(15) == 8);
+    CHECK(feature.geometry(16) == 0);
+    // LineTo(-3,3)
+    CHECK(feature.geometry(17) == 0);
+    CHECK(feature.geometry(18) == 7);
+    // LineTo(-7,3)
+    CHECK(feature.geometry(19) == 7);
+    CHECK(feature.geometry(20) == 0);
+    // Close
+    CHECK(feature.geometry(21) == 15);
+}
+
+TEST_CASE("encoding pbf empty polygon")
+{
+    mapnik::geometry::polygon<std::int64_t> p;
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(p, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.has_type());
+    CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    REQUIRE(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf multi polygons with holes")
+{
+    mapnik::geometry::multi_polygon<std::int64_t> mp;
+    {
+        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));
+        mp.push_back(p0);
+    }
+    // yeah so its the same polygon -- haters gonna hate.
+    {
+        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));
+        mp.push_back(p0);
+    }
+
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // 12 commands + 32 Params = 44
+    REQUIRE(feature.geometry_size() == 44);
+    // MoveTo(0,0)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(0,10)
+    CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(4) == 0);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(-10,10)
+    CHECK(feature.geometry(6) == 19);
+    CHECK(feature.geometry(7) == 0);
+    // LineTo(-10,0)
+    CHECK(feature.geometry(8) == 0);
+    CHECK(feature.geometry(9) == 19);
+    // Close
+    CHECK(feature.geometry(10) == 15);
+    // Remember the cursor didn't move after the close
+    // so it is at -10,0
+    // MoveTo(-7,7)
+    CHECK(feature.geometry(11) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(12) == 6);
+    CHECK(feature.geometry(13) == 14);
+    // LineTo(-3,7)
+    CHECK(feature.geometry(14) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(15) == 8);
+    CHECK(feature.geometry(16) == 0);
+    // LineTo(-3,3)
+    CHECK(feature.geometry(17) == 0);
+    CHECK(feature.geometry(18) == 7);
+    // LineTo(-7,3)
+    CHECK(feature.geometry(19) == 7);
+    CHECK(feature.geometry(20) == 0);
+    // Close
+    CHECK(feature.geometry(21) == 15);
+    // Remember the cursor didn't move after the close
+    // so it is at -7,3
+    // MoveTo(0,0)
+    CHECK(feature.geometry(22) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(23) == 14);
+    CHECK(feature.geometry(24) == 5);
+    // LineTo(0,10)
+    CHECK(feature.geometry(25) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(26) == 0);
+    CHECK(feature.geometry(27) == 20);
+    // LineTo(-10,10)
+    CHECK(feature.geometry(28) == 19);
+    CHECK(feature.geometry(29) == 0);
+    // LineTo(-10,0)
+    CHECK(feature.geometry(30) == 0);
+    CHECK(feature.geometry(31) == 19);
+    // Close
+    CHECK(feature.geometry(32) == 15);
+    // Remember the cursor didn't move after the close
+    // so it is at -10,0
+    // MoveTo(-7,7)
+    CHECK(feature.geometry(33) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(34) == 6);
+    CHECK(feature.geometry(35) == 14);
+    // LineTo(-3,7)
+    CHECK(feature.geometry(36) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(37) == 8);
+    CHECK(feature.geometry(38) == 0);
+    // LineTo(-3,3)
+    CHECK(feature.geometry(39) == 0);
+    CHECK(feature.geometry(40) == 7);
+    // LineTo(-7,3)
+    CHECK(feature.geometry(41) == 7);
+    CHECK(feature.geometry(42) == 0);
+    // Close
+    CHECK(feature.geometry(43) == 15);
+}
+
+TEST_CASE("encoding pbf multi polygons with holes -- geometry type")
+{
+    mapnik::geometry::multi_polygon<std::int64_t> mp;
+    {
+        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));
+        mp.push_back(p0);
+    }
+    // yeah so its the same polygon -- haters gonna hate.
+    {
+        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));
+        mp.push_back(p0);
+    }
+    mapnik::geometry::geometry<std::int64_t> geom(mp);
+
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // 12 commands + 32 Params = 44
+    REQUIRE(feature.geometry_size() == 44);
+    // MoveTo(0,0)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(0,10)
+    CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(4) == 0);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(-10,10)
+    CHECK(feature.geometry(6) == 19);
+    CHECK(feature.geometry(7) == 0);
+    // LineTo(-10,0)
+    CHECK(feature.geometry(8) == 0);
+    CHECK(feature.geometry(9) == 19);
+    // Close
+    CHECK(feature.geometry(10) == 15);
+    // Remember the cursor didn't move after the close
+    // so it is at -10,0
+    // MoveTo(-7,7)
+    CHECK(feature.geometry(11) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(12) == 6);
+    CHECK(feature.geometry(13) == 14);
+    // LineTo(-3,7)
+    CHECK(feature.geometry(14) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(15) == 8);
+    CHECK(feature.geometry(16) == 0);
+    // LineTo(-3,3)
+    CHECK(feature.geometry(17) == 0);
+    CHECK(feature.geometry(18) == 7);
+    // LineTo(-7,3)
+    CHECK(feature.geometry(19) == 7);
+    CHECK(feature.geometry(20) == 0);
+    // Close
+    CHECK(feature.geometry(21) == 15);
+    // Remember the cursor didn't move after the close
+    // so it is at -7,3
+    // MoveTo(0,0)
+    CHECK(feature.geometry(22) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(23) == 14);
+    CHECK(feature.geometry(24) == 5);
+    // LineTo(0,10)
+    CHECK(feature.geometry(25) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(26) == 0);
+    CHECK(feature.geometry(27) == 20);
+    // LineTo(-10,10)
+    CHECK(feature.geometry(28) == 19);
+    CHECK(feature.geometry(29) == 0);
+    // LineTo(-10,0)
+    CHECK(feature.geometry(30) == 0);
+    CHECK(feature.geometry(31) == 19);
+    // Close
+    CHECK(feature.geometry(32) == 15);
+    // Remember the cursor didn't move after the close
+    // so it is at -10,0
+    // MoveTo(-7,7)
+    CHECK(feature.geometry(33) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(34) == 6);
+    CHECK(feature.geometry(35) == 14);
+    // LineTo(-3,7)
+    CHECK(feature.geometry(36) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(37) == 8);
+    CHECK(feature.geometry(38) == 0);
+    // LineTo(-3,3)
+    CHECK(feature.geometry(39) == 0);
+    CHECK(feature.geometry(40) == 7);
+    // LineTo(-7,3)
+    CHECK(feature.geometry(41) == 7);
+    CHECK(feature.geometry(42) == 0);
+    // Close
+    CHECK(feature.geometry(43) == 15);
+}
+
+TEST_CASE("encoding pbf empty multi polygon")
+{
+    mapnik::geometry::multi_polygon<std::int64_t> mp;
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.has_type());
+    CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    REQUIRE(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf polygon with degenerate exterior ring full of repeated points")
+{
+    mapnik::geometry::polygon<std::int64_t> p0;
+    // invalid exterior ring
+    p0.exterior_ring.add_coord(0,0);
+    p0.exterior_ring.add_coord(0,10);
+    p0.exterior_ring.add_coord(0,10);
+    p0.exterior_ring.add_coord(0,10);
+    p0.exterior_ring.add_coord(0,10);
+    p0.exterior_ring.add_coord(0,10);
+    p0.exterior_ring.add_coord(0,10);
+    p0.exterior_ring.add_coord(0,10);
+
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.has_type());
+    CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf polygon with degenerate exterior ring")
+{
+    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::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.has_type());
+    CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf polygon with degenerate exterior ring and interior ring")
+{
+    mapnik::geometry::polygon<std::int64_t> p0;
+    // invalid exterior ring
+    p0.exterior_ring.add_coord(0,0);
+    p0.exterior_ring.add_coord(0,10);
+    // invalid interior ring -- is counter clockwise
+    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));
+
+    // encoder should cull the exterior invalid ring, which triggers
+    // the entire polygon to be culled.
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.has_type());
+    CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf polygon with valid exterior ring but degenerate interior ring")
+{
+    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::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    vector_tile::Tile_Feature feature;
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+    feature.ParseFromString(feature_str);
+    REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+    
+    // MoveTo, ParameterInteger, ParameterInteger
+    // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+    // Close
+    // 3 commands + 8 Params = 11 
+    REQUIRE(feature.geometry_size() == 11);
+    // MoveTo(0,0)
+    CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+    CHECK(feature.geometry(1) == 0);
+    CHECK(feature.geometry(2) == 0);
+    // LineTo(0,10)
+    CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+    CHECK(feature.geometry(4) == 0);
+    CHECK(feature.geometry(5) == 20);
+    // LineTo(-10,10)
+    CHECK(feature.geometry(6) == 19);
+    CHECK(feature.geometry(7) == 0);
+    // LineTo(-10,0)
+    CHECK(feature.geometry(8) == 0);
+    CHECK(feature.geometry(9) == 19);
+    // Close
+    CHECK(feature.geometry(10) == 15);
+}
+
diff --git a/test/unit/is_valid/feature_is_valid.cpp b/test/unit/is_valid/feature_is_valid.cpp
new file mode 100644
index 0000000..b3e9ce6
--- /dev/null
+++ b/test/unit/is_valid/feature_is_valid.cpp
@@ -0,0 +1,134 @@
+#include "catch.hpp"
+
+// mvt
+#include "vector_tile_is_valid.hpp"
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+#include "vector_tile.pb.h"
+
+typedef std::set<mapnik::vector_tile_impl::validity_error> error_set_T;
+
+TEST_CASE( "invalid empty feature" )
+{
+    std::string buffer;
+    vector_tile::Tile_Feature feature;
+    error_set_T errs;
+
+    feature.SerializeToString(&buffer);
+    protozero::pbf_reader pbf_feature(buffer);
+
+    feature_is_valid(pbf_feature, errs);
+
+    CHECK(errs.empty() == false);
+    CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_IS_EMPTY) == 1);
+}
+
+TEST_CASE( "invalid geometry without type" )
+{
+    std::string buffer;
+    vector_tile::Tile_Feature feature;
+    error_set_T errs;
+
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(5));
+    feature.add_geometry(protozero::encode_zigzag32(5));
+
+    feature.SerializeToString(&buffer);
+    protozero::pbf_reader pbf_feature(buffer);
+
+    feature_is_valid(pbf_feature, errs);
+
+    CHECK(errs.empty() == false);
+    CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_NO_GEOM_TYPE) == 1);
+}
+
+TEST_CASE( "valid raster feature" )
+{
+    std::string buffer;
+    vector_tile::Tile_Feature feature;
+    error_set_T errs;
+
+    feature.set_raster("raster-blaster");
+
+    feature.SerializeToString(&buffer);
+    protozero::pbf_reader pbf_feature(buffer);
+
+    feature_is_valid(pbf_feature, errs);
+
+    CHECK(errs.empty() == true);
+}
+
+TEST_CASE( "valid geometry feature" )
+{
+    std::string buffer;
+    vector_tile::Tile_Feature feature;
+    error_set_T errs;
+
+    feature.set_type(vector_tile::Tile_GeomType::Tile_GeomType_POINT);
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(5));
+    feature.add_geometry(protozero::encode_zigzag32(5));
+
+    feature.SerializeToString(&buffer);
+    protozero::pbf_reader pbf_feature(buffer);
+
+    feature_is_valid(pbf_feature, errs);
+
+    CHECK(errs.empty() == true);
+}
+
+TEST_CASE( "geometry feature with invalid type" )
+{
+    std::string buffer;
+    error_set_T errs;
+
+    protozero::pbf_writer pbf_message(buffer);
+    pbf_message.add_uint32(3, 4);
+
+    protozero::pbf_reader pbf_feature(buffer);
+
+    feature_is_valid(pbf_feature, errs);
+
+    CHECK(errs.empty() == false);
+    CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_HAS_INVALID_GEOM_TYPE) == 1);
+}
+
+TEST_CASE( "invalid feature with geometry and raster" )
+{
+    std::string buffer;
+    vector_tile::Tile_Feature feature;
+    error_set_T errs;
+
+    feature.set_type(vector_tile::Tile_GeomType::Tile_GeomType_POINT);
+    feature.add_geometry(9); // move_to | (1 << 3)
+    feature.add_geometry(protozero::encode_zigzag32(5));
+    feature.add_geometry(protozero::encode_zigzag32(5));
+
+    feature.set_raster("raster-blaster");
+
+    feature.SerializeToString(&buffer);
+    protozero::pbf_reader pbf_feature(buffer);
+
+    feature_is_valid(pbf_feature, errs);
+
+    CHECK(errs.empty() == false);
+    CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_MULTIPLE_GEOM) == 1);
+}
+
+TEST_CASE( "invalid unknown tag in feature" )
+{
+    std::string buffer;
+    error_set_T errs;
+
+    protozero::pbf_writer pbf_message(buffer);
+    pbf_message.add_string(8, "unknown field");
+
+    protozero::pbf_reader pbf_value(buffer);
+
+    feature_is_valid(pbf_value, errs);
+
+    CHECK(errs.empty() == false);
+    CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_HAS_UNKNOWN_TAG) == 1);
+}
diff --git a/test/unit/is_valid/value_is_valid.cpp b/test/unit/is_valid/value_is_valid.cpp
new file mode 100644
index 0000000..02d4467
--- /dev/null
+++ b/test/unit/is_valid/value_is_valid.cpp
@@ -0,0 +1,204 @@
+#include "catch.hpp"
+
+// mvt
+#include "vector_tile_is_valid.hpp"
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+#include "vector_tile.pb.h"
+
+
+typedef std::set<mapnik::vector_tile_impl::validity_error> error_set_T;
+
+TEST_CASE( "invalid empty value" )
+{
+    std::string buffer;
+    vector_tile::Tile_Value value;
+    error_set_T errs;
+
+    value.SerializeToString(&buffer);
+    protozero::pbf_reader pbf_value(buffer);
+
+    value_is_valid(pbf_value, errs);
+
+    CHECK(errs.empty() == false);
+    CHECK(errs.count(mapnik::vector_tile_impl::validity_error::VALUE_NO_VALUE) == 1);
+}
+
+TEST_CASE( "valid values" )
+{
+    SECTION("valid string")
+    {
+        std::string buffer;
+        vector_tile::Tile_Value value;
+        error_set_T errs;
+
+        value.set_string_value("just another mapnik monday");
+
+        value.SerializeToString(&buffer);
+        protozero::pbf_reader pbf_value(buffer);
+
+        value_is_valid(pbf_value, errs);
+
+        CHECK(errs.empty() == true);
+    }
+
+    SECTION("valid float")
+    {
+        std::string buffer;
+        vector_tile::Tile_Value value;
+        error_set_T errs;
+
+        value.set_float_value(2015.0);
+
+        value.SerializeToString(&buffer);
+        protozero::pbf_reader pbf_value(buffer);
+
+        value_is_valid(pbf_value, errs);
+
+        CHECK(errs.empty() == true);
+    }
+
+    SECTION("valid double")
+    {
+        std::string buffer;
+        vector_tile::Tile_Value value;
+        error_set_T errs;
+
+        value.set_double_value(2016.0);
+
+        value.SerializeToString(&buffer);
+        protozero::pbf_reader pbf_value(buffer);
+
+        value_is_valid(pbf_value, errs);
+
+        CHECK(errs.empty() == true);
+    }
+
+    SECTION("valid int")
+    {
+        std::string buffer;
+        vector_tile::Tile_Value value;
+        error_set_T errs;
+
+        value.set_int_value(2017);
+
+        value.SerializeToString(&buffer);
+        protozero::pbf_reader pbf_value(buffer);
+
+        value_is_valid(pbf_value, errs);
+
+        CHECK(errs.empty() == true);
+    }
+
+    SECTION("valid uint")
+    {
+        std::string buffer;
+        vector_tile::Tile_Value value;
+        error_set_T errs;
+
+        value.set_uint_value(2017);
+
+        value.SerializeToString(&buffer);
+        protozero::pbf_reader pbf_value(buffer);
+
+        value_is_valid(pbf_value, errs);
+
+        CHECK(errs.empty() == true);
+    }
+
+    SECTION("valid sint")
+    {
+        std::string buffer;
+        vector_tile::Tile_Value value;
+        error_set_T errs;
+
+        value.set_sint_value(2017);
+
+        value.SerializeToString(&buffer);
+        protozero::pbf_reader pbf_value(buffer);
+
+        value_is_valid(pbf_value, errs);
+
+        CHECK(errs.empty() == true);
+    }
+
+    SECTION("valid bool")
+    {
+        std::string buffer;
+        vector_tile::Tile_Value value;
+        error_set_T errs;
+
+        value.set_bool_value(false);
+
+        value.SerializeToString(&buffer);
+        protozero::pbf_reader pbf_value(buffer);
+
+        value_is_valid(pbf_value, errs);
+
+        CHECK(errs.empty() == true);
+    }
+}
+
+TEST_CASE( "invalid multiple values" )
+{
+    std::string buffer;
+    vector_tile::Tile_Value value;
+    error_set_T errs;
+
+    value.set_bool_value(false);
+    value.set_string_value("false");
+
+    value.SerializeToString(&buffer);
+    protozero::pbf_reader pbf_value(buffer);
+
+    value_is_valid(pbf_value, errs);
+
+    CHECK(errs.empty() == false);
+    CHECK(errs.count(mapnik::vector_tile_impl::validity_error::VALUE_MULTIPLE_VALUES) == 1);
+}
+
+TEST_CASE( "invalid unknown values" )
+{
+    std::string buffer;
+    vector_tile::Tile_Value value;
+    error_set_T errs;
+
+    value.set_bool_value(false);
+    value.set_string_value("false");
+
+    value.SerializeToString(&buffer);
+    protozero::pbf_reader pbf_value(buffer);
+
+    value_is_valid(pbf_value, errs);
+
+    CHECK(errs.empty() == false);
+    CHECK(errs.count(mapnik::vector_tile_impl::validity_error::VALUE_MULTIPLE_VALUES) == 1);
+}
+
+TEST_CASE( "invalid garbage value throws" )
+{
+    std::string buffer = "garbage";
+    error_set_T errs;
+
+    protozero::pbf_reader pbf_value(buffer);
+
+    CHECK_THROWS(value_is_valid(pbf_value, errs));
+}
+
+TEST_CASE( "invalid unknown tag" )
+{
+    std::string buffer;
+    error_set_T errs;
+
+    protozero::pbf_writer pbf_message(buffer);
+    pbf_message.add_string(8, "unknown field");
+
+    protozero::pbf_reader pbf_value(buffer);
+
+    value_is_valid(pbf_value, errs);
+
+    CHECK(errs.empty() == false);
+    CHECK(errs.count(mapnik::vector_tile_impl::validity_error::VALUE_HAS_UNKNOWN_TAG) == 1);
+}
diff --git a/test/unit/tile_impl/tile.cpp b/test/unit/tile_impl/tile.cpp
new file mode 100644
index 0000000..41e6b92
--- /dev/null
+++ b/test/unit/tile_impl/tile.cpp
@@ -0,0 +1,496 @@
+#include "catch.hpp"
+#include <memory>
+
+// mapnik vector tile tile class
+#include "vector_tile_tile.hpp"
+#include "vector_tile_layer.hpp"
+
+// mapnik
+#include <mapnik/feature.hpp>
+#include <mapnik/util/file_io.hpp>
+#include <mapnik/json/feature_parser.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+TEST_CASE("Vector tile base class")
+{
+    mapnik::box2d<double> global_extent(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+
+    SECTION("default constructed")
+    {
+        mapnik::vector_tile_impl::tile default_tile(global_extent);
+
+        CHECK(default_tile.size() == 0);
+        CHECK(default_tile.data()[0] == '\0');
+        CHECK(std::abs(default_tile.scale() - 9783.9396205024) < 0.00001);
+
+        std::string str;
+        default_tile.serialize_to_string(str);
+        CHECK(str == "");
+        CHECK(default_tile.is_painted() == false);
+        CHECK(default_tile.is_empty() == true);
+        
+        mapnik::box2d<double> global_buffered_extent(-21289852.6142133139073849,-21289852.6142133139073849,21289852.6142133139073849,21289852.6142133139073849);
+        CHECK(default_tile.extent() == global_extent);
+        CHECK(default_tile.get_buffered_extent() == global_buffered_extent);
+        CHECK(default_tile.tile_size() == 4096);
+
+        CHECK(default_tile.get_painted_layers().empty() == true);
+        CHECK(default_tile.get_empty_layers().empty() == true);
+        CHECK(default_tile.get_layers().empty() == true);
+        CHECK(default_tile.get_layers_set().empty() == true);
+
+        CHECK(default_tile.has_layer("anything") == false);
+
+        vector_tile::Tile t;
+        t.ParseFromString(default_tile.get_buffer());
+        CHECK(t.layers_size() == 0);
+    }
+
+    SECTION("construction with zero tile_size")
+    {
+        mapnik::vector_tile_impl::tile zero_size_tile(global_extent, 0, 0);
+
+        CHECK(zero_size_tile.tile_size() == 0);
+        CHECK(std::abs(zero_size_tile.scale() - 40075016.6855780035) < 0.00001);
+        CHECK(zero_size_tile.extent() == global_extent);
+        CHECK(zero_size_tile.get_buffered_extent() == global_extent);
+    }
+
+    SECTION("construction with negative tile_size")
+    {
+        mapnik::vector_tile_impl::tile negative_size_tile(global_extent, -1, 0);
+
+        CHECK(negative_size_tile.tile_size() == 4294967295);
+        CHECK(std::abs(negative_size_tile.scale() - 0.0093306919) < 0.0000001);
+        CHECK(negative_size_tile.get_buffered_extent() == global_extent);
+    }
+
+    SECTION("construction with positive buffer size")
+    {
+        mapnik::vector_tile_impl::tile positive_buffer_tile(global_extent, 4096, 10);
+
+        mapnik::box2d<double> buffered_extent(-20135347.7389940246939659,-20135347.7389940246939659,20135347.7389940246939659,20135347.7389940246939659);
+        CHECK(positive_buffer_tile.get_buffered_extent() == buffered_extent);
+        CHECK(positive_buffer_tile.buffer_size() == 10);
+    }
+
+    SECTION("construction with very negative buffer size")
+    {
+        mapnik::vector_tile_impl::tile negative_buffer_tile(global_extent, 4096, -4000);
+        mapnik::box2d<double> buffered_extent(0.0, 0.0, 0.0, 0.0);
+        CHECK(negative_buffer_tile.get_buffered_extent() == buffered_extent);
+        CHECK(negative_buffer_tile.buffer_size() == -4000);
+    }
+
+    SECTION("add bogus layer buffer")
+    {
+        mapnik::vector_tile_impl::tile tile(global_extent);
+        std::string bogus_layer_buffer = "blahblah";
+
+        tile.append_layer_buffer(bogus_layer_buffer.data(), bogus_layer_buffer.length(), "bogus");
+
+        const std::set<std::string> expected_set{"bogus"};
+        const std::set<std::string> empty_set;
+        const std::vector<std::string> expected_vec{"bogus"};
+
+        CHECK(tile.get_painted_layers() == expected_set);
+        CHECK(tile.get_empty_layers() == empty_set);
+        CHECK(tile.get_layers() == expected_vec);
+        CHECK(tile.get_layers_set() == expected_set);
+        CHECK(tile.has_layer("bogus") == true);
+        CHECK(tile.is_painted() == true);
+        CHECK(tile.is_empty() == false);
+
+        CHECK(tile.size() == 10);
+
+        std::string str;
+        tile.serialize_to_string(str);
+        CHECK(str == "\32\10blahblah");
+
+        std::string buffer(tile.data());
+
+        // Check the buffer itself
+        protozero::pbf_reader read_back(buffer);
+        CHECK(read_back.next(3) == true);
+        std::string blah_blah = read_back.get_string();
+        CHECK(blah_blah == "blahblah");
+
+        // Check the provided reader
+        protozero::pbf_reader tile_reader = tile.get_reader();
+        CHECK(tile_reader.next(3) == true);
+        blah_blah = tile_reader.get_string();
+        CHECK(blah_blah == "blahblah");
+
+        protozero::pbf_reader layer_reader;
+        CHECK_THROWS_AS(tile.layer_reader("bogus", layer_reader), protozero::end_of_buffer_exception);
+
+        protozero::pbf_reader layer_reader_by_index;
+        bool status = tile.layer_reader(0, layer_reader_by_index);
+
+        CHECK(status == true);
+        CHECK_THROWS_AS(layer_reader_by_index.next(1), protozero::end_of_buffer_exception);
+
+        vector_tile::Tile bogus_tile;
+        bogus_tile.ParseFromString(tile.get_buffer());
+        CHECK(bogus_tile.layers_size() == 1);
+        vector_tile::Tile_Layer bogus_layer = bogus_tile.layers(0);
+        CHECK(bogus_layer.version() == 1);
+        CHECK(bogus_layer.name() == "");
+        CHECK(bogus_layer.features_size() == 0);
+        CHECK(bogus_layer.keys_size() == 0);
+        CHECK(bogus_layer.values_size() == 0);
+        CHECK(bogus_layer.extent() == 4096);
+    }
+
+    SECTION("Add valid layer with layer buffer")
+    {
+        mapnik::vector_tile_impl::tile tile(global_extent);
+
+        // Create layer
+        vector_tile::Tile_Layer layer;
+        layer.set_version(2);
+        layer.set_name("valid");
+
+        std::string layer_buffer;
+        layer.SerializePartialToString(&layer_buffer);
+        tile.append_layer_buffer(layer_buffer.data(), layer_buffer.length(), "valid");
+
+        const std::set<std::string> expected_set{"valid"};
+        const std::set<std::string> empty_set;
+        const std::vector<std::string> expected_vec{"valid"};
+
+        CHECK(tile.get_painted_layers() == expected_set);
+        CHECK(tile.get_empty_layers() == empty_set);
+        CHECK(tile.get_layers() == expected_vec);
+        CHECK(tile.get_layers_set() == expected_set);
+        CHECK(tile.has_layer("valid") == true);
+        CHECK(tile.is_painted() == true);
+        CHECK(tile.is_empty() == false);
+
+        protozero::pbf_reader layer_reader_by_name;
+        bool status_by_name = tile.layer_reader("valid", layer_reader_by_name);
+        CHECK(status_by_name == true);
+        CHECK(layer_reader_by_name.next(1) == true);
+        CHECK(layer_reader_by_name.get_string() == "valid");
+
+        protozero::pbf_reader layer_reader_by_index;
+        bool status_by_index = tile.layer_reader(0, layer_reader_by_index);
+        CHECK(status_by_index == true);
+        CHECK(layer_reader_by_index.next(1) == true);
+        CHECK(layer_reader_by_index.get_string() == "valid");
+
+        vector_tile::Tile parsed_tile;
+        parsed_tile.ParseFromString(tile.get_buffer());
+        CHECK(parsed_tile.layers_size() == 1);
+        vector_tile::Tile_Layer parsed_layer = parsed_tile.layers(0);
+        CHECK(parsed_layer.version() == 2);
+        CHECK(parsed_layer.name() == "valid");
+    }
+/*
+    SECTION("Add valid empty layer with tile_layer")
+    {
+        mapnik::vector_tile_impl::tile tile(global_extent);
+
+        // Create layer
+        mapnik::vector_tile_impl::tile_layer layer;
+        mapnik::vector_tile_impl::layer_builder_pbf builder("empty", 4096, layer.get_data());
+        layer.build(builder);
+
+        CHECK(layer.is_empty() == true);
+        CHECK(layer.name() == "empty");
+
+        tile.add_layer(layer);
+
+        const std::set<std::string> empty_set{"empty"};
+
+        CHECK(tile.get_empty_layers().size() == 1);
+        CHECK(tile.is_painted() == false);
+        CHECK(tile.is_empty() == true);
+    }
+
+    SECTION("Add valid layer with tile_layer")
+    {
+        mapnik::vector_tile_impl::tile tile(global_extent);
+
+        mapnik::vector_tile_impl::tile_layer layer;
+        layer.name("valid");
+        // Create layer builder and add feature
+        mapnik::vector_tile_impl::layer_builder_pbf builder("valid", 4096, layer.get_data());
+        protozero::pbf_writer feature_writer = builder.get_feature_writer();
+
+        // Get geojson file string
+        mapnik::util::file input("./test/data/linestrings_and_point.geojson");
+        std::string geojson(input.data().get(), input.size());
+        auto context = std::make_shared<mapnik::context_type>();
+        auto mapnik_feature = std::make_shared<mapnik::feature_impl>(context, 0);
+        mapnik::json::from_geojson(geojson, *mapnik_feature);
+
+        builder.add_feature(feature_writer, *mapnik_feature);
+
+        // Create layer
+        layer.build(builder);
+
+        // Check properties of layer
+        CHECK(layer.is_empty() == false);
+        CHECK(layer.name() == "valid");
+
+        // Add layer to tile
+        tile.add_layer(layer);
+
+        const std::set<std::string> expected_set{"valid"};
+        const std::set<std::string> empty_set;
+        const std::vector<std::string> expected_vec{"valid"};
+
+        CHECK(tile.get_painted_layers() == expected_set);
+        CHECK(tile.get_empty_layers() == empty_set);
+        CHECK(tile.get_layers() == expected_vec);
+        CHECK(tile.get_layers_set() == expected_set);
+        CHECK(tile.has_layer("valid") == true);
+        CHECK(tile.is_painted() == true);
+        CHECK(tile.is_empty() == false);
+
+        protozero::pbf_reader layer_reader_by_name;
+        bool status_by_name = tile.layer_reader("valid", layer_reader_by_name);
+        CHECK(status_by_name == true);
+        CHECK(layer_reader_by_name.next(1) == true);
+        CHECK(layer_reader_by_name.get_string() == "valid");
+
+        vector_tile::Tile parsed_tile;
+        parsed_tile.ParseFromString(tile.get_buffer());
+        CHECK(parsed_tile.layers_size() == 1);
+        vector_tile::Tile_Layer parsed_layer = parsed_tile.layers(0);
+        CHECK(parsed_layer.version() == 2);
+        CHECK(parsed_layer.name() == "valid");
+    }
+
+    SECTION("cannot add the same layer with add_layer")
+    {
+        mapnik::vector_tile_impl::tile tile(global_extent);
+
+        // Add empty layer to tile
+        tile.add_empty_layer("valid");
+        std::set<std::string> empty_set{"valid"};
+        CHECK(tile.get_empty_layers() == empty_set);
+
+        // Create layer builder and add feature
+        mapnik::vector_tile_impl::tile_layer layer;
+        layer.name("valid");
+        mapnik::vector_tile_impl::layer_builder_pbf builder("valid", 4096, layer.get_data());
+        protozero::pbf_writer feature_writer = builder.get_feature_writer();
+
+        // Get geojson file string
+        mapnik::util::file input("./test/data/linestrings_and_point.geojson");
+        std::string geojson(input.data().get(), input.size());
+        auto context = std::make_shared<mapnik::context_type>();
+        auto mapnik_feature = std::make_shared<mapnik::feature_impl>(context, 0);
+        mapnik::json::from_geojson(geojson, *mapnik_feature);
+
+        builder.add_feature(feature_writer, *mapnik_feature);
+
+        // Create layer
+        layer.build(builder);
+
+        // Add layer to tile
+        tile.add_layer(layer);
+        empty_set.clear();
+        CHECK(tile.get_empty_layers() == empty_set);
+    }
+
+    SECTION("add_layer takes layer out of empty layers")
+    {
+        mapnik::vector_tile_impl::tile tile(global_extent);
+
+        // Create layer builder and add feature
+        mapnik::vector_tile_impl::tile_layer layer;
+        layer.name("valid");
+        mapnik::vector_tile_impl::layer_builder_pbf builder("valid", 4096, layer.get_data());
+        protozero::pbf_writer feature_writer = builder.get_feature_writer();
+
+        // Get geojson file string
+        mapnik::util::file input("./test/data/linestrings_and_point.geojson");
+        std::string geojson(input.data().get(), input.size());
+        auto context = std::make_shared<mapnik::context_type>();
+        auto mapnik_feature = std::make_shared<mapnik::feature_impl>(context, 0);
+        mapnik::json::from_geojson(geojson, *mapnik_feature);
+
+        builder.add_feature(feature_writer, *mapnik_feature);
+
+        // Create layer
+        layer.build(builder);
+
+        // Check properties of layer
+        CHECK(layer.is_empty() == false);
+        CHECK(layer.name() == "valid");
+
+        // Add layer to tile
+        bool status1 = tile.add_layer(layer);
+        CHECK(status1 == true);
+
+        bool status2 = tile.add_layer(layer);
+        CHECK(status2 == false);
+    }
+*/
+    SECTION("layer_reader by name works by name in buffer")
+    {
+        // Note - if the names of the layer are different
+        // between the layer in the buffer and in the
+        // tile object, `has_layer` will use the one
+        // in the tile object, but `layer_reader` will
+        // use the name in the buffer
+        mapnik::vector_tile_impl::tile tile(global_extent);
+
+        // Create layer
+        vector_tile::Tile_Layer layer;
+        layer.set_version(2);
+        layer.set_name("buffer name");
+
+        // Add layer to tile
+        std::string layer_buffer;
+        layer.SerializePartialToString(&layer_buffer);
+        tile.append_layer_buffer(layer_buffer.data(), layer_buffer.length(), "layer name");
+
+        const std::set<std::string> expected_set{"layer name"};
+        const std::vector<std::string> expected_vec{"layer name"};
+
+        // Confirm the use of "layer name" in these methods
+        CHECK(tile.get_painted_layers() == expected_set);
+        CHECK(tile.get_layers() == expected_vec);
+        CHECK(tile.get_layers_set() == expected_set);
+        CHECK(tile.has_layer("layer name") == true);
+        CHECK(tile.has_layer("buffer name") == false);
+
+        // Confirm the use of "buffer name" in this method
+        protozero::pbf_reader layer_reader_by_buffer_name;
+        bool status_by_buffer_name = tile.layer_reader("buffer name", layer_reader_by_buffer_name);
+        CHECK(status_by_buffer_name == true);
+        CHECK(layer_reader_by_buffer_name.next(1) == true);
+        CHECK(layer_reader_by_buffer_name.get_string() == "buffer name");
+
+        protozero::pbf_reader layer_reader_by_name;
+        bool status_by_layer_name = tile.layer_reader("layer name", layer_reader_by_name);
+        CHECK(status_by_layer_name == false);
+    }
+
+    SECTION("layer ordering is deterministic")
+    {
+        // Newly added layers from buffers are added to the end of
+        // the tile, and are read from the tile in the same order
+        // as they are added
+        mapnik::vector_tile_impl::tile tile(global_extent);
+
+        // Create layers
+        vector_tile::Tile_Layer layer1, layer2;
+        layer1.set_version(2);
+        layer1.set_name("layer1");
+
+        layer2.set_version(2);
+        layer2.set_name("layer2");
+
+        std::string layer1_buffer, layer2_buffer;
+        layer1.SerializePartialToString(&layer1_buffer);
+        tile.append_layer_buffer(layer1_buffer.data(), layer1_buffer.length(), "layer1");
+
+        layer2.SerializePartialToString(&layer2_buffer);
+        tile.append_layer_buffer(layer2_buffer.data(), layer2_buffer.length(), "layer2");
+
+        const std::vector<std::string> expected_vec{"layer1", "layer2"};
+
+        // Both of the layers are here, in order
+        CHECK(tile.get_layers() == expected_vec);
+        CHECK(tile.has_layer("layer1") == true);
+        CHECK(tile.has_layer("layer2") == true);
+
+        // layer_reader reads them in the same order
+        protozero::pbf_reader layer_reader1, layer_reader2;
+        bool status1 = tile.layer_reader(0, layer_reader1);
+        CHECK(status1 == true);
+        CHECK(layer_reader1.next(1) == true);
+        CHECK(layer_reader1.get_string() == "layer1");
+
+        bool status2 = tile.layer_reader(1, layer_reader2);
+        CHECK(status2 == true);
+        CHECK(layer_reader2.next(1) == true);
+        CHECK(layer_reader2.get_string() == "layer2");
+    }
+
+    SECTION("cannot add same layer buffer twice")
+    {
+        // Newly added layers from buffers are added to the end of
+        // the tile, and are read from the tile in the same order
+        // as they are added
+        mapnik::vector_tile_impl::tile tile(global_extent);
+
+        // Create layers
+        vector_tile::Tile_Layer layer1, layer2;
+        layer1.set_version(2);
+        layer1.set_name("layer");
+
+        layer2.set_version(2);
+        layer2.set_name("layer");
+
+        std::string layer1_buffer, layer2_buffer;
+        layer1.SerializePartialToString(&layer1_buffer);
+        bool status1 = tile.append_layer_buffer(layer1_buffer.data(), layer1_buffer.length(), "layer");
+        CHECK(status1 == true);
+
+        layer2.SerializePartialToString(&layer2_buffer);
+        bool status2 = tile.append_layer_buffer(layer2_buffer.data(), layer2_buffer.length(), "layer");
+        CHECK(status2 == false);
+    }
+
+    SECTION("index out of bounds for layer_reader method")
+    {
+        mapnik::vector_tile_impl::tile tile(global_extent);
+
+        // Read a layer from an empty tile
+        protozero::pbf_reader layer_reader;
+        bool status = tile.layer_reader(0, layer_reader);
+        CHECK(status == false);
+        CHECK(layer_reader.next(1) == false);
+    }
+
+    SECTION("adding a valid layer takes name out of empty layers")
+    {
+        mapnik::vector_tile_impl::tile tile(global_extent);
+        tile.add_empty_layer("layer");
+
+        const std::set<std::string> expected_set{"layer"};
+
+        CHECK(tile.get_empty_layers() == expected_set);
+        CHECK(tile.has_layer("layer") == false);
+        CHECK(tile.is_painted() == false);
+        CHECK(tile.is_empty() == true);
+
+        // Create layers
+        vector_tile::Tile_Layer layer;
+        layer.set_version(2);
+        layer.set_name("layer");
+
+        std::string layer_buffer;
+        layer.SerializePartialToString(&layer_buffer);
+        tile.append_layer_buffer(layer_buffer.data(), layer_buffer.length(), "layer");
+
+        const std::set<std::string> empty_set;
+
+        CHECK(tile.get_empty_layers() == empty_set);
+        CHECK(tile.get_painted_layers() == expected_set);
+        CHECK(tile.has_layer("layer") == true);
+        CHECK(tile.is_painted() == true);
+        CHECK(tile.is_empty() == false);
+    }
+
+    SECTION("has same extent works correctly")
+    {
+        mapnik::vector_tile_impl::tile tile1(global_extent);
+        mapnik::vector_tile_impl::tile tile2(global_extent);
+
+        CHECK(tile1.same_extent(tile2) == true);
+        CHECK(tile2.same_extent(tile1) == true);
+    }
+}
diff --git a/test/utils/decoding_util.cpp b/test/utils/decoding_util.cpp
new file mode 100644
index 0000000..27cd3b3
--- /dev/null
+++ b/test/utils/decoding_util.cpp
@@ -0,0 +1,16 @@
+// test utils
+#include "decoding_util.hpp"
+
+// mapnik-vector-tile
+#include "vector_tile_geometry_decoder.hpp"
+
+template <typename T>
+mapnik::vector_tile_impl::GeometryPBF<T> feature_to_pbf_geometry(std::string const& feature_string, double scale_x, double scale_y)
+{
+    protozero::pbf_reader feature_pbf(feature_string);
+    feature_pbf.next(4);
+    return mapnik::vector_tile_impl::GeometryPBF<T>(feature_pbf.get_packed_uint32(),0.0,0.0,scale_x,scale_y);
+}
+
+template mapnik::vector_tile_impl::GeometryPBF<double> feature_to_pbf_geometry<double>(std::string const& feature_string, double scale_x, double scale_y);
+template mapnik::vector_tile_impl::GeometryPBF<std::int64_t> feature_to_pbf_geometry<std::int64_t>(std::string const& feature_string, double scale_x, double scale_y);
diff --git a/test/utils/decoding_util.hpp b/test/utils/decoding_util.hpp
new file mode 100644
index 0000000..d8becfa
--- /dev/null
+++ b/test/utils/decoding_util.hpp
@@ -0,0 +1,10 @@
+#ifndef __MAPNIK_VECTOR_TILE_TEST_DECODING_UTIL_H__
+#define __MAPNIK_VECTOR_TILE_TEST_DECODING_UTIL_H__
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+template <typename T>
+mapnik::vector_tile_impl::GeometryPBF<T> feature_to_pbf_geometry(std::string const& feature_string, double scale_x = 1.0, double scale_y = 1.0);
+
+#endif // __MAPNIK_VECTOR_TILE_TEST_DECODING_UTIL_H__
diff --git a/test/utils/encoding_util.cpp b/test/utils/encoding_util.cpp
new file mode 100644
index 0000000..6f28b87
--- /dev/null
+++ b/test/utils/encoding_util.cpp
@@ -0,0 +1,82 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/vertex.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_adapters.hpp>
+#include <mapnik/vertex_processor.hpp>
+
+// test utils
+#include "encoding_util.hpp"
+#include "decoding_util.hpp"
+
+using namespace mapnik::geometry;
+
+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)
+            {
+                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 decode_to_path_string(mapnik::geometry::geometry<T> const& g)
+{
+    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_pbf(mapnik::geometry::geometry<std::int64_t> const& g, unsigned version)
+{
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(g, feature_writer, x, y));
+    protozero::pbf_reader feature_reader(feature_str);
+    int32_t geometry_type = mapnik::vector_tile_impl::Geometry_Type::UNKNOWN; 
+    std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+    while (feature_reader.next())
+    {
+        if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::GEOMETRY)
+        {
+            geom_itr = feature_reader.get_packed_uint32();
+        }
+        else if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::TYPE)
+        {
+            geometry_type = feature_reader.get_enum();
+        }
+    }
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, 0.0, 0.0, 1.0, 1.0);
+    auto g2 = mapnik::vector_tile_impl::decode_geometry(geoms, geometry_type, version);
+    return decode_to_path_string(g2);
+}
diff --git a/test/utils/encoding_util.hpp b/test/utils/encoding_util.hpp
new file mode 100644
index 0000000..e3dc831
--- /dev/null
+++ b/test/utils/encoding_util.hpp
@@ -0,0 +1,16 @@
+#ifndef __MAPNIK_VECTOR_TILE_TEST_ENCODING_UTIL_H__
+#define __MAPNIK_VECTOR_TILE_TEST_ENCODING_UTIL_H__
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+std::string compare_pbf(mapnik::geometry::geometry<std::int64_t> const& g, unsigned version);
+
+#endif // __MAPNIK_VECTOR_TILE_TEST_ENCODING_UTIL_H__
diff --git a/test/utils/geom_to_wkt.cpp b/test/utils/geom_to_wkt.cpp
new file mode 100644
index 0000000..16e2e01
--- /dev/null
+++ b/test/utils/geom_to_wkt.cpp
@@ -0,0 +1,17 @@
+// mapnik
+#include <mapnik/util/geometry_to_wkt.hpp>
+
+namespace test_utils
+{
+
+bool to_wkt(std::string & wkt,  mapnik::geometry::geometry<double> const& geom)
+{
+    return mapnik::util::to_wkt(wkt, geom);
+}
+
+bool to_wkt(std::string & wkt,  mapnik::geometry::geometry<std::int64_t> const& geom)
+{
+    return mapnik::util::to_wkt(wkt, geom);
+}
+
+} // end ns test_utils
diff --git a/test/utils/geom_to_wkt.hpp b/test/utils/geom_to_wkt.hpp
new file mode 100644
index 0000000..236d9f3
--- /dev/null
+++ b/test/utils/geom_to_wkt.hpp
@@ -0,0 +1,10 @@
+// mapnik
+#include <mapnik/geometry.hpp>
+
+namespace test_utils
+{
+
+bool to_wkt(std::string & wkt,  mapnik::geometry::geometry<double> const& geom);
+bool to_wkt(std::string & wkt,  mapnik::geometry::geometry<std::int64_t> const& geom);
+
+} // end ns test_utils
diff --git a/test/utils/geometry_equal.hpp b/test/utils/geometry_equal.hpp
new file mode 100644
index 0000000..fa53446
--- /dev/null
+++ b/test/utils/geometry_equal.hpp
@@ -0,0 +1,235 @@
+#include "catch.hpp"
+
+// boost
+#include <type_traits>
+#include <iterator>
+
+#pragma GCC diagnostic push
+#include <mapnik/warning_ignore.hpp>
+#include <boost/tuple/tuple.hpp>
+#include <boost/iterator/zip_iterator.hpp>
+#include <boost/range/iterator_range.hpp>
+#pragma GCC diagnostic pop
+
+// helper namespace to ensure correct functionality
+namespace aux{
+namespace adl{
+using std::begin;
+using std::end;
+
+template<class T>
+auto do_begin(T& v) -> decltype(begin(v));
+template<class T>
+auto do_end(T& v) -> decltype(end(v));
+} // adl::
+
+template<class... Its>
+using zipper_it = boost::zip_iterator<boost::tuple<Its...>>;
+
+template<class T>
+T const& as_const(T const& v){ return v; }
+} // aux::
+
+template<class... Conts>
+auto zip_begin(Conts&... conts)
+  -> aux::zipper_it<decltype(aux::adl::do_begin(conts))...>
+{
+  using std::begin;
+  return {boost::make_tuple(begin(conts)...)};
+}
+
+template<class... Conts>
+auto zip_end(Conts&... conts)
+  -> aux::zipper_it<decltype(aux::adl::do_end(conts))...>
+{
+  using std::end;
+  return {boost::make_tuple(end(conts)...)};
+}
+
+template<class... Conts>
+auto zip_range(Conts&... conts)
+  -> boost::iterator_range<decltype(zip_begin(conts...))>
+{
+  return {zip_begin(conts...), zip_end(conts...)};
+}
+
+// for const access
+template<class... Conts>
+auto zip_cbegin(Conts&... conts)
+  -> decltype(zip_begin(aux::as_const(conts)...))
+{
+  using std::begin;
+  return zip_begin(aux::as_const(conts)...);
+}
+
+template<class... Conts>
+auto zip_cend(Conts&... conts)
+  -> decltype(zip_end(aux::as_const(conts)...))
+{
+  using std::end;
+  return zip_end(aux::as_const(conts)...);
+}
+
+template<class... Conts>
+auto zip_crange(Conts&... conts)
+  -> decltype(zip_range(aux::as_const(conts)...))
+{
+  return zip_range(aux::as_const(conts)...);
+}
+
+// mapnik
+#include <mapnik/geometry.hpp>
+#include <mapnik/util/variant.hpp>
+
+#include <string>
+#include <cstdlib>
+#include <cxxabi.h>
+
+template<typename T>
+std::string type_name()
+{
+    int status;
+    std::string tname = typeid(T).name();
+    char *demangled_name = abi::__cxa_demangle(tname.c_str(), NULL, NULL, &status);
+    if(status == 0) {
+        tname = demangled_name;
+        std::free(demangled_name);
+    }   
+    return tname;
+}
+
+using namespace mapnik::geometry;
+
+template <typename T>
+void assert_g_equal(geometry<T> const& g1, geometry<T> const& g2);
+
+struct geometry_equal_visitor
+{
+    template <typename T1, typename T2>
+    void operator() (T1 const&, T2 const&)
+    {
+        // comparing two different types!
+        INFO(type_name<T1>());
+        INFO(type_name<T2>());
+        REQUIRE(false);
+    }
+
+    void operator() (geometry_empty const&, geometry_empty const&)
+    {
+        REQUIRE(true);
+    }
+
+    template <typename T>
+    void operator() (point<T> const& p1, point<T> const& p2)
+    {
+        REQUIRE(p1.x == Approx(p2.x));
+        REQUIRE(p1.y == Approx(p2.y));
+    }
+
+    template <typename T>
+    void operator() (line_string<T> const& ls1, line_string<T> const& ls2)
+    {
+        if (ls1.size() != ls2.size())
+        {
+            REQUIRE(false);
+        }
+
+        for(auto const& p : zip_crange(ls1, ls2))
+        {
+            REQUIRE(p.template get<0>().x == Approx(p.template get<1>().x));
+            REQUIRE(p.template get<0>().y == Approx(p.template get<1>().y));
+        }
+    }
+
+    template <typename T>
+    void operator() (polygon<T> const& p1, polygon<T> const& p2)
+    {
+        (*this)(static_cast<line_string<T> const&>(p1.exterior_ring), static_cast<line_string<T> const&>(p2.exterior_ring));
+
+        if (p1.interior_rings.size() != p2.interior_rings.size())
+        {
+            REQUIRE(false);
+        }
+
+        for (auto const& p : zip_crange(p1.interior_rings, p2.interior_rings))
+        {
+            (*this)(static_cast<line_string<T> const&>(p.template get<0>()),static_cast<line_string<T> const&>(p.template get<1>()));
+        }
+    }
+
+    template <typename T>
+    void operator() (multi_point<T> const& mp1, multi_point<T> const& mp2)
+    {
+        (*this)(static_cast<line_string<T> const&>(mp1), static_cast<line_string<T> const&>(mp2));
+    }
+
+    template <typename T>
+    void operator() (multi_line_string<T> const& mls1, multi_line_string<T> const& mls2)
+    {
+        if (mls1.size() != mls2.size())
+        {
+            REQUIRE(false);
+        }
+
+        for (auto const& ls : zip_crange(mls1, mls2))
+        {
+            (*this)(ls.template get<0>(),ls.template get<1>());
+        }
+    }
+
+    template <typename T>
+    void operator() (multi_polygon<T> const& mpoly1, multi_polygon<T> const& mpoly2)
+    {
+        if (mpoly1.size() != mpoly2.size())
+        {
+            REQUIRE(false);
+        }
+
+        for (auto const& poly : zip_crange(mpoly1, mpoly2))
+        {
+            (*this)(poly.template get<0>(),poly.template get<1>());
+        }
+    }
+
+    template <typename T>
+    void operator() (mapnik::util::recursive_wrapper<geometry_collection<T> > const& c1_, mapnik::util::recursive_wrapper<geometry_collection<T> > const& c2_)
+    {
+        geometry_collection<T> const& c1 = static_cast<geometry_collection<T> const&>(c1_);
+        geometry_collection<T> const& c2 = static_cast<geometry_collection<T> const&>(c2_);
+        if (c1.size() != c2.size())
+        {
+            REQUIRE(false);
+        }
+
+        for (auto const& g : zip_crange(c1, c2))
+        {
+            assert_g_equal(g.template get<0>(),g.template get<1>());
+        }
+    }
+
+    template <typename T>
+    void operator() (geometry_collection<T> const& c1, geometry_collection<T> const& c2)
+    {
+        if (c1.size() != c2.size())
+        {
+            REQUIRE(false);
+        }
+
+        for (auto const& g : zip_crange(c1, c2))
+        {
+            assert_g_equal(g.template get<0>(),g.template get<1>());
+        }
+    }
+};
+
+template <typename T>
+void assert_g_equal(geometry<T> const& g1, geometry<T> const& g2)
+{
+    return mapnik::util::apply_visitor(geometry_equal_visitor(), g1, g2);
+}
+
+template <typename T>
+void assert_g_equal(T const& g1, T const& g2)
+{
+    return geometry_equal_visitor()(g1,g2);
+}
diff --git a/test/utils/round_trip.cpp b/test/utils/round_trip.cpp
new file mode 100644
index 0000000..420a2b2
--- /dev/null
+++ b/test/utils/round_trip.cpp
@@ -0,0 +1,89 @@
+// test utils
+#include "round_trip.hpp"
+
+// mapnik-vector-tile
+#include "vector_tile_processor.hpp"
+#include "vector_tile_strategy.hpp"
+#include "vector_tile_geometry_decoder.hpp"
+
+// mapnik
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/memory_datasource.hpp>
+
+// std
+#include <exception>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+namespace test_utils
+{
+
+mapnik::geometry::geometry<double> round_trip(mapnik::geometry::geometry<double> const& geom,
+                                              double simplify_distance,
+                                              mapnik::vector_tile_impl::polygon_fill_type fill_type,
+                                              bool mpu)
+{
+    unsigned tile_size = 256 * 1000;
+    // Create map note its not 3857 -- round trip as 4326
+    mapnik::Map map(tile_size,tile_size,"+init=epsg:4326");
+    // create layer
+    mapnik::layer lyr("layer",map.srs());
+    // create feature with geometry
+    mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
+    mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+    mapnik::geometry::geometry<double> g(geom);
+    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.add_layer(lyr);
+    
+    // Build request
+    mapnik::box2d<double> bbox(-180,-90,180,90);
+
+    // Build processor and create tile
+    mapnik::vector_tile_impl::processor ren(map);
+    ren.set_simplify_distance(simplify_distance);
+    ren.set_fill_type(fill_type);
+    ren.set_multi_polygon_union(mpu);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(bbox, tile_size);
+    
+    if (out_tile.get_layers().size() != 1)
+    {
+        std::stringstream s;
+        s << "expected 1 layer in `round_trip` found " << out_tile.get_layers().size();
+        throw std::runtime_error(s.str());
+    }
+
+    protozero::pbf_reader layer_reader;
+    out_tile.layer_reader(0, layer_reader);
+    if (!layer_reader.next(mapnik::vector_tile_impl::Layer_Encoding::FEATURES))
+    {
+        throw std::runtime_error("Expected at least one feature in layer");
+    }
+    protozero::pbf_reader feature_reader = layer_reader.get_message();
+    int32_t geometry_type = mapnik::vector_tile_impl::Geometry_Type::UNKNOWN; 
+    std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+    while (feature_reader.next())
+    {
+        if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::GEOMETRY)
+        {
+            geom_itr = feature_reader.get_packed_uint32();
+        }
+        else if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::TYPE)
+        {
+            geometry_type = feature_reader.get_enum();
+        }
+    }
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, 0, 0, 1000, -1000);
+    return mapnik::vector_tile_impl::decode_geometry(geoms, geometry_type, 2);
+}
+
+} // end ns
diff --git a/test/utils/round_trip.hpp b/test/utils/round_trip.hpp
new file mode 100644
index 0000000..361116a
--- /dev/null
+++ b/test/utils/round_trip.hpp
@@ -0,0 +1,15 @@
+// mapnik-vector-tile
+#include "vector_tile_processor.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+namespace test_utils
+{
+
+mapnik::geometry::geometry<double> round_trip(mapnik::geometry::geometry<double> const& geom,
+                                      double simplify_distance=0.0,
+                                      mapnik::vector_tile_impl::polygon_fill_type fill_type = mapnik::vector_tile_impl::non_zero_fill,
+                                      bool mpu = false);
+
+} // end ns
diff --git a/test/vector_tile.cpp b/test/vector_tile.cpp
index bb07901..1f3ee0d 100644
--- a/test/vector_tile.cpp
+++ b/test/vector_tile.cpp
@@ -2,1112 +2,54 @@
 
 // test utils
 #include "test_utils.hpp"
-#include <mapnik/memory_datasource.hpp>
-#include <mapnik/util/fs.hpp>
+
+// mapnik
 #include <mapnik/agg_renderer.hpp>
+#include <mapnik/datasource_cache.hpp>
 #include <mapnik/feature_factory.hpp>
-#include <mapnik/load_map.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_is_empty.hpp>
+#include <mapnik/geometry_reprojection.hpp>
 #include <mapnik/image_util.hpp>
-#include <mapnik/vertex_adapters.hpp>
+#include <mapnik/load_map.hpp>
+#include <mapnik/memory_datasource.hpp>
 #include <mapnik/projection.hpp>
 #include <mapnik/proj_transform.hpp>
-#include <mapnik/geometry_is_empty.hpp>
+#include <mapnik/util/fs.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 <mapnik/datasource_cache.hpp>
+#include <mapnik/vertex_adapters.hpp>
 
+// boost
 #include <boost/optional/optional_io.hpp>
 
-// vector output api
-#include "vector_tile_compression.hpp"
+// mapnik-vector-tile
 #include "vector_tile_processor.hpp"
-#include "vector_tile_strategy.hpp"
-#include "vector_tile_backend_pbf.hpp"
-#include "vector_tile_util.hpp"
-#include "vector_tile_projection.hpp"
 #include "vector_tile_geometry_decoder.hpp"
+#include "vector_tile_datasource_pbf.hpp"
 
-// vector input api
-#include "vector_tile_datasource.hpp"
-#include "protozero/pbf_writer.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 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);
-    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( 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());
-    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()));
-    REQUIRE(3 == f.geometry_size());
-    CHECK(9 == f.geometry(0));
-    CHECK(4096 == f.geometry(1));
-    CHECK(4096 == f.geometry(2));
-    CHECK(194 == tile.ByteSize());
-    std::string buffer;
-    CHECK(tile.SerializeToString(&buffer));
-    CHECK(194 == buffer.size());
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
-}
-
-TEST_CASE( "vector tile output 2", "adding empty layers should result in empty tile" ) {
-    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);
-    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());
-    std::string buffer;
-    CHECK(tile.SerializeToString(&buffer));
-    CHECK(true == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
-}
-
-TEST_CASE( "vector tile output 3", "adding layers with geometries outside rendering extent should not add layer" ) {
-    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);
-    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::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.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();
-    std::string key("");
-    CHECK(true == mapnik::vector_tile_impl::is_solid_extent(tile,key));
-    CHECK("" == key);
-    CHECK(0 == tile.layers_size());
-    std::string buffer;
-    CHECK(tile.SerializeToString(&buffer));
-    CHECK(true == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
-}
-
-TEST_CASE( "vector tile output 4", "adding layers with degenerate geometries should not add layer" ) {
-    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);
-    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
-    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.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());
-    std::string buffer;
-    CHECK(tile.SerializeToString(&buffer));
-    CHECK(true == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
-}
-
-TEST_CASE( "vector tile input", "should be able to parse message and render point" ) {
-    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);
-    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);
-    ren.apply();
-    // serialize to message
-    std::string buffer;
-    CHECK(tile.SerializeToString(&buffer));
-    CHECK(151 == buffer.size());
-    // now create new objects
-    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(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
-
-    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());
-
-    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("bool");
-    expected_names.push_back("boolf");
-    expected_names.push_back("double");
-    expected_names.push_back("float");
-    expected_names.push_back("int");
-    expected_names.push_back("name");
-    expected_names.push_back("uint");
-    std::vector<std::string> names;
-    for (auto const& desc : lay_desc.get_descriptors())
-    {
-        names.push_back(desc.get_name());
-    }
-    CHECK(names == expected_names);
-    lyr2.set_datasource(ds);
-    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_rgba8 im(map2.width(),map2.height());
-    mapnik::agg_renderer<mapnik::image_rgba8> ren2(map2,im);
-    ren2.apply();
-    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_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);
-    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);
-    std::string buffer;
-    CHECK(tile.SerializeToString(&buffer));
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,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());
-    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_impl::tile_datasource ds(layer,0,0,0,tile_size);
-    mapnik::featureset_ptr fs;
-
-    // ensure we can query single feature
-    fs = ds.features(mapnik::query(bbox));
-    mapnik::feature_ptr feat = fs->next();
-    CHECK(feat != mapnik::feature_ptr());
-    CHECK(feat->size() == 0);
-    CHECK(fs->next() == mapnik::feature_ptr());
-    mapnik::query qq = mapnik::query(mapnik::box2d<double>(-1,-1,1,1));
-    qq.add_property_name("name");
-    fs = ds.features(qq);
-    feat = fs->next();
-    CHECK(feat != mapnik::feature_ptr());
-    CHECK(feat->size() == 1);
-//    CHECK(feat->get("name") == "null island");
-
-    // now check that datasource api throws out feature which is outside extent
-    fs = ds.features(mapnik::query(mapnik::box2d<double>(-10,-10,-10,-10)));
-    CHECK(fs->next() == mapnik::feature_ptr());
-
-    // ensure same behavior for feature_at_point
-    fs = ds.features_at_point(mapnik::coord2d(0.0,0.0),0.0001);
-    CHECK(fs->next() != mapnik::feature_ptr());
-
-    fs = ds.features_at_point(mapnik::coord2d(1.0,1.0),1.0001);
-    CHECK(fs->next() != mapnik::feature_ptr());
-
-    fs = ds.features_at_point(mapnik::coord2d(-10,-10),0);
-    CHECK(fs->next() == mapnik::feature_ptr());
-
-    // finally, make sure attributes are also filtered
-    mapnik::feature_ptr f_ptr;
-    fs = ds.features(mapnik::query(bbox));
-    f_ptr = fs->next();
-    CHECK(f_ptr != mapnik::feature_ptr());
-    // no attributes
-    CHECK(f_ptr->context()->size() == 0);
-
-    mapnik::query q(bbox);
-    q.add_property_name("name");
-    fs = ds.features(q);
-    f_ptr = fs->next();
-    CHECK(f_ptr != mapnik::feature_ptr());
-    // one attribute
-    CHECK(f_ptr->context()->size() == 1);
-}
-
-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 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;
-    // now create the testing data
-    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(std::make_shared<mapnik::context_type>(),1));
-    backend.start_tile_feature(*feature);
-    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.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);
-    std::string buffer;
-    CHECK(tile.SerializeToString(&buffer));
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
-    REQUIRE(1 == tile.layers_size());
-    vector_tile::Tile_Layer const& layer = tile.layers(0);
-    CHECK(1 == layer.features_size());
-    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
-    CHECK(0 == f.geometry(2)); // y:0
-    CHECK(10 == f.geometry(3)); // 1 line_to
-    CHECK(4 == f.geometry(4)); // x:2
-    CHECK(4 == f.geometry(5)); // y:2
-    CHECK(9 == f.geometry(6)); // 1 move_to
-    CHECK(1 == f.geometry(7)); // x:1
-    CHECK(1 == f.geometry(8)); // y:1
-    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
-    // this works because we are staying in integer space for this test
-    unsigned path_multiplier = 1;
-    // 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;
-    // now create the testing data
-    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(std::make_shared<mapnik::context_type>(),1));
-    backend.start_tile_feature(*feature);
-    /*
-    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
-    */
-    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);
-    std::string buffer;
-    CHECK(tile.SerializeToString(&buffer));
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
-    REQUIRE(1 == tile.layers_size());
-    vector_tile::Tile_Layer const& layer = tile.layers(0);
-    CHECK(1 == layer.features_size());
-    vector_tile::Tile_Feature const& f = layer.features(0);
-    // sequence of 10 geometries given tolerance of 2
-    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);
-    // (1      << 3) | (1       & ((1 << 3) -1)) == 9
-    // (length << 3) | (MOVE_TO & ((1 << 3) -1))
-    CHECK(9 == move_value);
-    unsigned move_cmd = move_value & ((1 << 3) - 1);
-    CHECK(1 == move_cmd);
-    unsigned move_length = move_value >> 3;
-    CHECK(1 == move_length);
-    // 2nd and 3rd are the x,y of the one move_to command
-    CHECK(0 == f.geometry(1));
-    CHECK(0 == f.geometry(2));
-    // 4th is the line_to (which is an enum of 2) and a number indicating how many line_to commands are repeated
-    // 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(34 == line_value);
-    unsigned line_cmd = line_value & ((1 << 3) - 1);
-    CHECK(2 == line_cmd);
-    unsigned line_length = line_value >> 3;
-    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
-    unsigned four = protozero::encode_zigzag32(2u);
-    CHECK(4 == four);
-    CHECK(4 == f.geometry(4));
-    CHECK(4 == f.geometry(5));
-    // 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(1996 == f.geometry(6));
-    CHECK(1996 == f.geometry(7));
-}
-
-// 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;
-    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(std::make_shared<mapnik::context_type>(),1));
-    backend.start_tile_feature(*feature);
-    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_rounding_strategy scale_strat(backend.get_path_multiplier());
-    mapnik::geometry::geometry<std::int64_t> geom2 = mapnik::geometry::transform<std::int64_t>(geom, scale_strat);
-    std::string foo;
-    mapnik::util::to_wkt(foo, geom2);
-    INFO(foo);
-    backend.current_feature_->set_type(vector_tile::Tile_GeomType_POLYGON);
-    REQUIRE( geom2.is<mapnik::geometry::polygon<std::int64_t> >() );
-    auto const& poly = mapnik::util::get<mapnik::geometry::polygon<std::int64_t>>(geom2);
-    backend.add_path(poly);
-    backend.stop_tile_feature();
-    backend.stop_tile_layer();
-    std::string key("");
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
-    CHECK("" == key);
-    std::string buffer;
-    CHECK(tile.SerializeToString(&buffer));
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
-    REQUIRE(1 == tile.layers_size());
-    vector_tile::Tile_Layer const& layer = tile.layers(0);
-    CHECK(1 == layer.features_size());
-    vector_tile::Tile_Feature const& f = layer.features(0);
-    CHECK(11 == f.geometry_size());
-}
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
 
-mapnik::geometry::geometry<double> round_trip(mapnik::geometry::geometry<double> const& geom,
-                                      double simplify_distance=0.0,
-                                      mapnik::vector_tile_impl::polygon_fill_type fill_type = mapnik::vector_tile_impl::non_zero_fill,
-                                      bool mpu = false)
+TEST_CASE("vector tile from simplified geojson")
 {
-    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.set_fill_type(fill_type);
-    ren.set_multi_polygon_union(mpu);
-    mapnik::vector_tile_impl::vector_tile_strategy_proj vs2(prj_trans,ren.get_transform(),backend.get_path_multiplier());
-    mapnik::vector_tile_impl::vector_tile_strategy vs(ren.get_transform(),backend.get_path_multiplier());
-    mapnik::geometry::point<double> p1_min(bbox.minx(), bbox.miny());
-    mapnik::geometry::point<double> p1_max(bbox.maxx(), bbox.maxy());
-    mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
-    mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
-    mapnik::box2d<int> clipping_extent(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
-    ren.handle_geometry(vs2,*feature,geom,clipping_extent, 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;
-
-    mapnik::vector_tile_impl::Geometry<double> geoms(f,0,0,scale,-1*scale);
-    return mapnik::vector_tile_impl::decode_geometry<double>(geoms, f.type());
-}
-
-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) );
-    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 geometry collection encoding", "should create vector tile with data" ) {
-    mapnik::geometry::point<double> geom_p(0,0);
-    mapnik::geometry::geometry_collection<double> geom;
-    geom.push_back(geom_p);
-    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 geometry collection encoding x2", "should create vector tile with data" ) {
-    mapnik::geometry::point<double> geom_p(0,0);
-    mapnik::geometry::geometry_collection<double> geom_t;
-    geom_t.push_back(geom_p);
-    mapnik::geometry::geometry_collection<double> geom;
-    geom.push_back(std::move(geom_t));
-    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 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 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 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 0),(120.889 -128,63.289 -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((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -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((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778))" );
-    CHECK( !mapnik::geometry::is_empty(new_geom) );
-    CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-TEST_CASE( "vector tile multi_polygon with multipolygon union", "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::polygon<double> poly2;
-    poly2.exterior_ring.add_coord(0,0);
-    poly2.exterior_ring.add_coord(0,10);
-    poly2.exterior_ring.add_coord(-10,10);
-    poly2.exterior_ring.add_coord(-10,0);
-    poly2.exterior_ring.add_coord(0,0);
-    mapnik::geometry::multi_polygon<double> geom;
-    geom.emplace_back(std::move(poly));
-    geom.emplace_back(std::move(poly2));
-    mapnik::geometry::geometry<double> new_geom = round_trip(geom, 0, mapnik::vector_tile_impl::non_zero_fill, true);
-    std::string wkt;
-    CHECK( mapnik::util::to_wkt(wkt, new_geom) );
-    CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778))" );
-    CHECK( !mapnik::geometry::is_empty(new_geom) );
-    CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-TEST_CASE( "vector tile multi_polygon with out multipolygon union", "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::polygon<double> poly2;
-    poly2.exterior_ring.add_coord(0,0);
-    poly2.exterior_ring.add_coord(0,10);
-    poly2.exterior_ring.add_coord(-10,10);
-    poly2.exterior_ring.add_coord(-10,0);
-    poly2.exterior_ring.add_coord(0,0);
-    mapnik::geometry::multi_polygon<double> geom;
-    geom.emplace_back(std::move(poly));
-    geom.emplace_back(std::move(poly2));
-    mapnik::geometry::geometry<double> new_geom = round_trip(geom, 0, mapnik::vector_tile_impl::non_zero_fill, false);
-    std::string wkt;
-    CHECK( mapnik::util::to_wkt(wkt, new_geom) );
-    CHECK( wkt == "MULTIPOLYGON(((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778)),((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778)))" );
-    CHECK( !mapnik::geometry::is_empty(new_geom) );
-    CHECK( new_geom.is<mapnik::geometry::multi_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> >() );
-}
-
-TEST_CASE( "vector tile multi_polygon encoding overlapping multipolygons", "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(-5,5);
-    poly2.exterior_ring.add_coord(-5,15);
-    poly2.exterior_ring.add_coord(-15,15);
-    poly2.exterior_ring.add_coord(-15,5);
-    poly2.exterior_ring.add_coord(-5,5);
-    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 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 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 even odd fill", "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,0,mapnik::vector_tile_impl::even_odd_fill);
-    std::string wkt;
-    CHECK( mapnik::util::to_wkt(wkt, new_geom) );
-    CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044,125.867 -123.733))");
-    CHECK( !mapnik::geometry::is_empty(new_geom) );
-    REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-TEST_CASE( "vector tile polygon non zero fill", "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,0,mapnik::vector_tile_impl::non_zero_fill);
-    std::string wkt;
-    CHECK( mapnik::util::to_wkt(wkt, new_geom) );
-    CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044,125.867 -123.733))");
-    CHECK( !mapnik::geometry::is_empty(new_geom) );
-    REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-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((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044,125.867 -123.733))");
-    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((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044,125.867 -123.733))" );
-    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(0 -128,256 -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");
+    unsigned tile_size = 256 * 1000;
+    mapnik::Map map(256,256,"+init=epsg:3857");
     mapnik::layer lyr("layer","+init=epsg:4326");
     std::shared_ptr<mapnik::memory_datasource> ds = testing::build_geojson_ds("./test/data/poly.geojson");
     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 );
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+    
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
     REQUIRE(1 == tile.layers_size());
     vector_tile::Tile_Layer const& layer = tile.layers(0);
     CHECK(std::string("layer") == layer.name());
@@ -1119,9 +61,26 @@ TEST_CASE( "vector tile from simplified geojson", "should create vector tile wit
     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;
-    mapnik::vector_tile_impl::Geometry<double> geoms(f,tile_x, tile_y,scale,-1*scale);
-    auto geom = mapnik::vector_tile_impl::decode_geometry<double>(geoms,f.type());
+    double scale = static_cast<double>(layer.extent())/resolution;
+    protozero::pbf_reader layer_reader;
+    out_tile.layer_reader(0, layer_reader);
+    REQUIRE(layer_reader.next(mapnik::vector_tile_impl::Layer_Encoding::FEATURES));
+    protozero::pbf_reader feature_reader = layer_reader.get_message();
+    int32_t geometry_type = mapnik::vector_tile_impl::Geometry_Type::UNKNOWN; 
+    std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+    while (feature_reader.next())
+    {
+        if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::GEOMETRY)
+        {
+            geom_itr = feature_reader.get_packed_uint32();
+        }
+        else if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::TYPE)
+        {
+            geometry_type = feature_reader.get_enum();
+        }
+    }
+    mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x, tile_y, scale, -1*scale);
+    auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, geometry_type, 2);
 
     unsigned int n_err = 0;
     mapnik::projection wgs84("+init=epsg:4326",true);
@@ -1131,107 +90,14 @@ TEST_CASE( "vector tile from simplified geojson", "should create vector tile wit
     CHECK( n_err == 0 );
     std::string geojson_string;
     CHECK( mapnik::util::to_geojson(geojson_string,projected_geom) );
-    CHECK( geojson_string == "{\"type\":\"Polygon\",\"coordinates\":[[[160.40671875,11.3976701817587],[160.396875,11.3935345987524],[160.39828125,11.4018057045896],[160.39265625,11.4004272036667],[160.38984375,11.3811274888866],[160.3940625,11.3838846711709],[160.3771875,11.3521754635814],[160.33921875,11.3590690696413],[160.35046875,11.3645838345287],[160.3575,11.3645838345287],[160.3575,11.3756130442004],[160.29421875,11.3507967223837],[160.2928125,11.3480392200086],[160.28859375,11.34 [...]
+    //std::clog << geojson_string << std::endl;
+    CHECK( geojson_string == "{\"type\":\"Polygon\",\"coordinates\":[[[160.36171875,11.2997786224589],[160.3828125,11.3011576095711],[160.408125,11.3039155638972],[160.408125,11.2997786224589],[160.425,11.3094313929343],[160.41234375,11.3411453475587],[160.3996875,11.3301148056307],[160.40953125,11.3700984927314],[160.39265625,11.3618264654176],[160.396875,11.3797488877286],[160.4053125,11.3893989555911],[160.40953125,11.3866418267411],[160.419375,11.4004272036667],[160.41515625,11.40594 [...]
 }
 
-mapnik::geometry::geometry<double> round_trip2(mapnik::geometry::geometry<double> const& geom,
-                                      double simplify_distance=0.0)
+TEST_CASE("vector tile transform -- should not throw on coords outside merc range")
 {
-    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");
-    }
-    mapnik::vector_tile_impl::vector_tile_strategy_proj vs2(prj_trans,ren.get_transform(),backend.get_path_multiplier());
-    mapnik::vector_tile_impl::vector_tile_strategy vs(ren.get_transform(),backend.get_path_multiplier());
-    mapnik::geometry::point<double> p1_min(bbox.minx(), bbox.miny());
-    mapnik::geometry::point<double> p1_max(bbox.maxx(), bbox.maxy());
-    mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
-    mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
-    mapnik::box2d<int> clipping_extent(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
-    ren.handle_geometry(vs2,*feature,geom,clipping_extent, 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;
-    mapnik::vector_tile_impl::Geometry<double> geoms(f,tile_x, tile_y,scale,-1*scale);
-    return mapnik::vector_tile_impl::decode_geometry<double>(geoms,f.type());
-}
-
-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,11.25 1.99992945603165),(11.25 7.99994115658818,7.99996948242188 7.99994115658818,7.99996948242188 11.1784018737118),(2.00006103515625 11.1784018737118,2.00006103515625 7.99994115658818,0.0000000000000005 7.99994115658818))" );
-    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() == 4 );
-}
-
-TEST_CASE( "vector tile transform", "should not throw on coords outside merc range" ) {
-    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,64);
-    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");
+    unsigned tile_size = 256 * 64;
+    mapnik::Map map(256,256,"+init=epsg:3857");
     // Note: 4269 is key. 4326 will trigger custom mapnik reprojection code
     // that does not hit proj4 and clamps values
     mapnik::layer lyr("layer","+init=epsg:4269");
@@ -1242,35 +108,37 @@ TEST_CASE( "vector tile transform", "should not throw on coords outside merc ran
         mapnik::datasource_cache::instance().create(params);
     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);
-    // should no longer throw after https://github.com/mapbox/mapnik-vector-tile/issues/116
-    ren.apply();
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+    
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
 
     // serialize to message
     std::string buffer;
     CHECK(tile.SerializeToString(&buffer));
     CHECK(70 == buffer.size());
     // now create new objects
-    mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
-    tile_type tile2;
+    mapnik::Map map2(256,256,"+init=epsg:3857");
+    vector_tile::Tile tile2;
     CHECK(tile2.ParseFromString(buffer));
-    std::string key("");
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
-    CHECK("" == key);
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
     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());
+    CHECK(1 == layer2.features(0).id());
+    CHECK(3 == layer2.features(0).type());
 
     mapnik::layer lyr2("layer",map.srs());
-    std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds2 = std::make_shared<
-                                    mapnik::vector_tile_impl::tile_datasource>(
-                                        layer2,0,0,0,map2.width());
-    ds2->set_envelope(bbox);
+
+    protozero::pbf_reader layer_reader;
+    out_tile.layer_reader(0, layer_reader);
+    std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
+                                    mapnik::vector_tile_impl::tile_datasource_pbf>(
+                                        layer_reader,0,0,0);
+    //ds2->set_envelope(bbox);
     CHECK( ds2->type() == mapnik::datasource::Vector );
     CHECK( ds2->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
     mapnik::layer_descriptor lay_desc = ds2->get_descriptor();
@@ -1287,29 +155,27 @@ TEST_CASE( "vector tile transform", "should not throw on coords outside merc ran
     map2.add_layer(lyr2);
     mapnik::load_map(map2,"test/data/polygon-style.xml");
     //std::clog << mapnik::save_map_to_string(map2) << "\n";
-    map2.zoom_to_box(bbox);
+    map2.zoom_all();
     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/transform-expected-1.png")) {
+    if (!mapnik::util::exists("test/fixtures/transform-expected-1.png"))
+    {
         mapnik::save_to_file(im,"test/fixtures/transform-expected-1.png","png32");
     }
     unsigned diff = testing::compare_images(im,"test/fixtures/transform-expected-1.png");
     CHECK(0 == diff);
-    if (diff > 0) {
+    if (diff > 0)
+    {
         mapnik::save_to_file(im,"test/fixtures/transform-actual-1.png","png32");
     }
 }
 
-TEST_CASE( "vector tile transform2", "should not throw reprojected data from local NZ projection" ) {
-    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,64);
-    unsigned tile_size = 256;
+TEST_CASE("vector tile transform2 -- should not throw reprojected data from local NZ projection")
+{
+    unsigned tile_size = 256 * 64;
     mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
-    mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+    mapnik::Map map(256,256,"+init=epsg:3857");
     // Note: 4269 is key. 4326 will trigger custom mapnik reprojection code
     // that does not hit proj4 and clamps values
     mapnik::layer lyr("layer","+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=intl +units=m +no_defs");
@@ -1320,34 +186,34 @@ TEST_CASE( "vector tile transform2", "should not throw reprojected data from loc
         mapnik::datasource_cache::instance().create(params);
     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);
-    // should no longer throw after https://github.com/mapbox/mapnik-vector-tile/pull/128
-    ren.apply();
+    
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+    
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
 
     // serialize to message
     std::string buffer;
     CHECK(tile.SerializeToString(&buffer));
     CHECK(231 == buffer.size());
     // now create new objects
-    mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
-    tile_type tile2;
+    mapnik::Map map2(256,256,"+init=epsg:3857");
+    vector_tile::Tile tile2;
     CHECK(tile2.ParseFromString(buffer));
-    std::string key("");
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
-    CHECK("" == key);
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
     CHECK(1 == tile2.layers_size());
     vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
     CHECK(std::string("layer") == layer2.name());
     CHECK(2 == layer2.features_size());
 
     mapnik::layer lyr2("layer",map.srs());
-    std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds2 = std::make_shared<
-                                    mapnik::vector_tile_impl::tile_datasource>(
-                                        layer2,0,0,0,map2.width());
+    protozero::pbf_reader layer_reader;
+    out_tile.layer_reader(0, layer_reader);
+    std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
+                                    mapnik::vector_tile_impl::tile_datasource_pbf>(
+                                        layer_reader,0,0,0);
     ds2->set_envelope(bbox);
     CHECK( ds2->type() == mapnik::datasource::Vector );
     CHECK( ds2->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
diff --git a/test/vector_tile_pbf.cpp b/test/vector_tile_pbf.cpp
index 6db96c2..6150bad 100644
--- a/test/vector_tile_pbf.cpp
+++ b/test/vector_tile_pbf.cpp
@@ -2,6 +2,8 @@
 
 // test utils
 #include "test_utils.hpp"
+
+// mapnik
 #include <mapnik/memory_datasource.hpp>
 #include <mapnik/util/fs.hpp>
 #include <mapnik/agg_renderer.hpp>
@@ -13,7 +15,6 @@
 #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>
@@ -21,55 +22,53 @@
 #include <mapnik/geometry.hpp>
 #include <mapnik/datasource_cache.hpp>
 
+// boost
 #include <boost/optional/optional_io.hpp>
 
-// vector output api
+// mapnik-vector-tile
 #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"
+#include "vector_tile_geometry_encoder_pbf.hpp"
 #include "vector_tile_datasource_pbf.hpp"
 
+//protozero
 #include "protozero/pbf_reader.hpp"
 
+//std
 #include <string>
 #include <fstream>
 #include <streambuf>
 
-TEST_CASE( "pbf vector tile input", "should be able to parse message and render point" ) {
-    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);
-    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");
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+TEST_CASE("pbf vector tile input")
+{
+    unsigned tile_size = 4096;
+    mapnik::Map map(256,256,"+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);
-    ren.apply();
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
     // serialize to message
     std::string buffer;
     CHECK(tile.SerializeToString(&buffer));
-    CHECK(151 == buffer.size());
+    CHECK(147 == buffer.size());
     // now create new objects
-    mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
-    tile_type tile2;
+    mapnik::Map map2(256,256,"+init=epsg:3857");
+    vector_tile::Tile tile2;
     CHECK(tile2.ParseFromString(buffer));
-    std::string key("");
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
-    CHECK("" == key);
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
     CHECK(1 == tile2.layers_size());
     vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
     CHECK(std::string("layer") == layer2.name());
@@ -83,69 +82,70 @@ TEST_CASE( "pbf vector tile input", "should be able to parse message and render
 
     std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
                                     mapnik::vector_tile_impl::tile_datasource_pbf>(
-                                        layer3,0,0,0,map2.width());
+                                        layer3,0,0,0);
     CHECK(ds->get_name() == "layer");
+    mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
     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("bool");
-    expected_names.push_back("boolf");
-    expected_names.push_back("double");
-    expected_names.push_back("float");
-    expected_names.push_back("int");
-    expected_names.push_back("name");
-    expected_names.push_back("uint");
-    std::vector<std::string> names;
+    std::set<std::string> expected_names;
+    expected_names.insert("bool");
+    expected_names.insert("boolf");
+    expected_names.insert("double");
+    expected_names.insert("float");
+    expected_names.insert("int");
+    expected_names.insert("name");
+    expected_names.insert("uint");
+    std::size_t desc_count = 0;
     for (auto const& desc : lay_desc.get_descriptors())
     {
-        names.push_back(desc.get_name());
+        ++desc_count;
+        CHECK(expected_names.count(desc.get_name()) == 1);
     }
-
-    CHECK(names == expected_names);
+    CHECK(desc_count == expected_names.size());
     lyr2.set_datasource(ds);
     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);
+    map2.zoom_all();
     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-1.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) {
+    if (diff > 0)
+    {
         mapnik::save_to_file(im,"test/fixtures/actual-1.png","png32");
     }
 }
 
-
-TEST_CASE( "pbf vector tile datasource", "should filter features outside extent" ) {
-    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);
-    unsigned tile_size = 256;
-    mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+TEST_CASE("pbf vector tile datasource")
+{
+    unsigned tile_size = 4096;
     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);
+    
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+
+    // serialize to message
     std::string buffer;
-    tile.SerializeToString(&buffer);
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
+    out_tile.serialize_to_string(buffer);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+    
+    // check that vector tile contains proper information
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
+    
     REQUIRE(1 == tile.layers_size());
     vector_tile::Tile_Layer const& layer = tile.layers(0);
     CHECK(std::string("layer") == layer.name());
@@ -162,10 +162,11 @@ TEST_CASE( "pbf vector tile datasource", "should filter features outside extent"
     protozero::pbf_reader layer2 = pbf_tile.get_message();
 
     // now actually start the meat of the test
-    mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+    mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
     mapnik::featureset_ptr fs;
 
     // ensure we can query single feature
+    mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
     fs = ds.features(mapnik::query(bbox));
     mapnik::feature_ptr feat = fs->next();
     CHECK(feat != mapnik::feature_ptr());
@@ -177,7 +178,7 @@ TEST_CASE( "pbf vector tile datasource", "should filter features outside extent"
     feat = fs->next();
     CHECK(feat != mapnik::feature_ptr());
     CHECK(feat->size() == 1);
-//    CHECK(feat->get("name") == "null island");
+    CHECK(feat->get("name") == mapnik::value_unicode_string("null island"));
 
     // now check that datasource api throws out feature which is outside extent
     fs = ds.features(mapnik::query(mapnik::box2d<double>(-10,-10,-10,-10)));
@@ -210,61 +211,37 @@ TEST_CASE( "pbf vector tile datasource", "should filter features outside extent"
     CHECK(f_ptr->context()->size() == 1);
 }
 
-// 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( "pbf 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 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;
-    // now create the testing data
-    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(std::make_shared<mapnik::context_type>(),1));
-    backend.start_tile_feature(*feature);
+TEST_CASE("pbf encoding multi line")
+{
     mapnik::geometry::multi_line_string<std::int64_t> geom;
     {
-        mapnik::geometry::linear_ring<std::int64_t> ring;
+        mapnik::geometry::line_string<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;
+        mapnik::geometry::line_string<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.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);
+    vector_tile::Tile tile;
+    vector_tile::Tile_Layer * t_layer = tile.add_layers();
+    t_layer->set_name("layer");
+    t_layer->set_version(2);
+    t_layer->set_extent(4096);
+    vector_tile::Tile_Feature * t_feature = t_layer->add_features();
+    std::int32_t x = 0;
+    std::int32_t y = 0;
+
+    std::string feature_str;
+    protozero::pbf_writer feature_writer(feature_str);
+    CHECK(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+    t_feature->ParseFromString(feature_str);
+
     std::string buffer;
     tile.SerializeToString(&buffer);
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
     REQUIRE(1 == tile.layers_size());
     vector_tile::Tile_Layer const& layer = tile.layers(0);
     REQUIRE(1 == layer.features_size());
@@ -290,8 +267,9 @@ TEST_CASE( "pbf encoding multi line as one path", "should maintain second move_t
     pbf_tile.next();
     protozero::pbf_reader layer2 = pbf_tile.get_message();
 
-
-    mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+    unsigned tile_size = 4096;
+    mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
+    mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
     fs = ds.features(mapnik::query(bbox));
     f_ptr = fs->next();
     CHECK(f_ptr != mapnik::feature_ptr());
@@ -319,35 +297,32 @@ TEST_CASE( "pbf decoding empty buffer", "should throw exception" ) {
 TEST_CASE( "pbf decoding garbage buffer", "should throw exception" ) {
     std::string buffer("daufyglwi3h7fseuhfas8w3h,dksufasdf");
     protozero::pbf_reader pbf_tile(buffer);
-    pbf_tile.next();
+    REQUIRE_THROWS_AS(pbf_tile.next(), protozero::unknown_pbf_wire_type_exception);
     protozero::pbf_reader layer2;
     REQUIRE_THROWS(layer2 = pbf_tile.get_message());
 }
 
 
-TEST_CASE( "pbf decoding some truncated buffers", "should throw exception" ) {
-
-    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);
-    unsigned tile_size = 256;
+TEST_CASE("pbf decoding some truncated buffers")
+{
+    unsigned tile_size = 4096;
     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);
-    std::string buffer;
-    tile.SerializeToString(&buffer);
-    CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
-    CHECK("" == key);
+
+    // Create processor
+    mapnik::vector_tile_impl::processor ren(map);
+    
+    // Request Tile
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+    
+    // Now check that the tile is correct.
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
     REQUIRE(1 == tile.layers_size());
     vector_tile::Tile_Layer const& layer = tile.layers(0);
     CHECK(std::string("layer") == layer.name());
@@ -359,47 +334,48 @@ TEST_CASE( "pbf decoding some truncated buffers", "should throw exception" ) {
     CHECK(4096 == f.geometry(1));
     CHECK(4096 == f.geometry(2));
 
-
+    std::string buffer;
+    out_tile.serialize_to_string(buffer);
     // We will test truncating the generated protobuf at every increment.
     //  Most cases should fail, except for the lucky bites where we chop
     // it off at a point that would be valid anyway.
     for (std::size_t i=1; i< buffer.size(); ++i)
     {
-      CHECK_THROWS({
+      CHECK_THROWS(
+      {
           protozero::pbf_reader pbf_tile(buffer.c_str(), i);
           pbf_tile.next();
           protozero::pbf_reader layer2 = pbf_tile.get_message();
-          mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+          mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
           mapnik::featureset_ptr fs;
           mapnik::feature_ptr f_ptr;
           fs = ds.features(mapnik::query(bbox));
           f_ptr = fs->next();
-          while (f_ptr != mapnik::feature_ptr()) {
+          while (f_ptr != mapnik::feature_ptr())
+          {
               f_ptr = fs->next();
           }
-      });
+      }
+      );
     }
 }
 
-TEST_CASE( "pbf 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);
+TEST_CASE("pbf vector tile from simplified geojson")
+{
+    unsigned tile_size = 256 * 1000;
     mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
     mapnik::layer lyr("layer","+init=epsg:4326");
     std::shared_ptr<mapnik::memory_datasource> ds = testing::build_geojson_ds("./test/data/poly.geojson");
     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 );
+    mapnik::vector_tile_impl::processor ren(map);
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+    
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
     REQUIRE(1 == tile.layers_size());
     vector_tile::Tile_Layer const& layer = tile.layers(0);
     CHECK(std::string("layer") == layer.name());
@@ -420,14 +396,16 @@ TEST_CASE( "pbf vector tile from simplified geojson", "should create vector tile
     protozero::pbf_reader pbf_layer = pbf_tile.get_message();
     // Need to loop because they could be encoded in any order
     bool found = false;
-    while (!found && pbf_layer.next(2)) {
+    while (!found && pbf_layer.next(2))
+    {
       // But there will be only one in our tile, so we'll break out of loop once we find it
       protozero::pbf_reader pbf_feature = pbf_layer.get_message();
-      while (!found && pbf_feature.next(4)) {
+      while (!found && pbf_feature.next(4))
+      {
           found = true;
           std::pair< protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > geom_itr = pbf_feature.get_packed_uint32();
           mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x,tile_y,scale,-1*scale);
-          auto geom = mapnik::vector_tile_impl::decode_geometry<double>(geoms, f.type());
+          auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, f.type(),2);
               unsigned int n_err = 0;
           mapnik::projection wgs84("+init=epsg:4326",true);
           mapnik::projection merc("+init=epsg:3857",true);
@@ -436,45 +414,48 @@ TEST_CASE( "pbf vector tile from simplified geojson", "should create vector tile
           CHECK( n_err == 0 );
           std::string geojson_string;
           CHECK( mapnik::util::to_geojson(geojson_string,projected_geom) );
-          CHECK( geojson_string == "{\"type\":\"Polygon\",\"coordinates\":[[[160.40671875,11.3976701817587],[160.396875,11.3935345987524],[160.39828125,11.4018057045896],[160.39265625,11.4004272036667],[160.38984375,11.3811274888866],[160.3940625,11.3838846711709],[160.3771875,11.3521754635814],[160.33921875,11.3590690696413],[160.35046875,11.3645838345287],[160.3575,11.3645838345287],[160.3575,11.3756130442004],[160.29421875,11.3507967223837],[160.2928125,11.3480392200086],[160.28859375 [...]
+          //std::clog << geojson_string << std::endl;
+          CHECK( geojson_string == "{\"type\":\"Polygon\",\"coordinates\":[[[160.36171875,11.2997786224589],[160.3828125,11.3011576095711],[160.408125,11.3039155638972],[160.408125,11.2997786224589],[160.425,11.3094313929343],[160.41234375,11.3411453475587],[160.3996875,11.3301148056307],[160.40953125,11.3700984927314],[160.39265625,11.3618264654176],[160.396875,11.3797488877286],[160.4053125,11.3893989555911],[160.40953125,11.3866418267411],[160.419375,11.4004272036667],[160.41515625,11 [...]
           break;
       }
     }
     REQUIRE( found );
 }
 
-
-TEST_CASE( "pbf raster tile output", "should be able to overzoom raster" ) {
-    typedef vector_tile::Tile tile_type;
-    tile_type tile;
+TEST_CASE("pbf raster tile output -- should be able to overzoom raster")
+{
+    unsigned tile_size = 4096;
+    int buffer_size = 1024;
+    mapnik::vector_tile_impl::merc_tile out_tile(0, 0, 0, tile_size, buffer_size);
     {
-        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";
+        mapnik::box2d<double> const& bbox = out_tile.extent();
         std::ostringstream s;
         s << std::fixed << std::setprecision(16)
           << bbox.minx() << ',' << bbox.miny() << ','
           << bbox.maxx() << ',' << bbox.maxy();
+        
+        // build map
+        mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+        map.set_buffer_size(buffer_size);
+        mapnik::layer lyr("layer",map.srs());
+        mapnik::parameters params;
+        params["type"] = "gdal";
         params["extent"] = s.str();
         params["file"] = "test/data/256x256.png";
-        std::shared_ptr<mapnik::datasource> ds =
-            mapnik::datasource_cache::instance().create(params);
+        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,false,"jpeg",mapnik::SCALING_BILINEAR);
-        ren.apply();
+        
+        // build processor
+        mapnik::vector_tile_impl::processor ren(map);
+        ren.set_image_format("jpeg");
+        ren.set_scaling_method(mapnik::SCALING_BILINEAR);
+        
+        // Update the tile
+        ren.update_tile(out_tile);
     }
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
     // Done creating test data, now test created tile
     REQUIRE(1 == tile.layers_size());
     vector_tile::Tile_Layer const& layer = tile.layers(0);
@@ -507,7 +488,7 @@ TEST_CASE( "pbf raster tile output", "should be able to overzoom raster" ) {
     mapnik::layer lyr2("layer",map2.srs());
     std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
                                     mapnik::vector_tile_impl::tile_datasource_pbf>(
-                                        layer2,0,0,0,256);
+                                        layer2,0,0,0);
     lyr2.set_datasource(ds2);
     lyr2.add_style("style");
     map2.add_layer(lyr2);
@@ -516,36 +497,38 @@ TEST_CASE( "pbf raster tile output", "should be able to overzoom raster" ) {
     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")) {
+    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) {
+    if (diff > 0)
+    {
         mapnik::save_to_file(im,"test/fixtures/actual-3.png","png32");
     }
 }
 
-TEST_CASE("Check that we throw on various valid-but-we-don't-handle PBF encoded files","Should be throwing exceptions")
+TEST_CASE("Check that we throw on various valid-but-we-don't-handle PBF encoded files")
 {
     std::vector<std::string> filenames = {"test/data/tile_with_extra_feature_field.pbf",
                                           "test/data/tile_with_extra_layer_fields.pbf",
                                           "test/data/tile_with_invalid_layer_value_type.pbf",
                                           "test/data/tile_with_unexpected_geomtype.pbf"};
 
-    for (auto const& f : filenames) {
-
+    for (auto const& f : filenames)
+    {
       CHECK_THROWS({
         std::ifstream t(f);
         std::string buffer((std::istreambuf_iterator<char>(t)),
                             std::istreambuf_iterator<char>());
 
         mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
-        unsigned tile_size = 256;
+        unsigned tile_size = 4096;
           protozero::pbf_reader pbf_tile(buffer);
           pbf_tile.next();
           protozero::pbf_reader layer2 = pbf_tile.get_message();
-          mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+          mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
           mapnik::featureset_ptr fs;
           mapnik::feature_ptr f_ptr;
           fs = ds.features(mapnik::query(bbox));
@@ -558,24 +541,23 @@ TEST_CASE("Check that we throw on various valid-but-we-don't-handle PBF encoded
 
 }
 
-TEST_CASE( "pbf vector tile from linestring 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);
+TEST_CASE("pbf vector tile from linestring geojson")
+{
+    unsigned tile_size = 4096;
     mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
     mapnik::layer lyr("layer","+init=epsg:4326");
     auto ds = testing::build_geojson_fs_ds("./test/data/linestrings_and_point.geojson");
     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 );
+    mapnik::vector_tile_impl::processor ren(map);
+    // Request Tile
+    mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+    CHECK(out_tile.is_painted() == true);
+    CHECK(out_tile.is_empty() == false);
+
+    // Now check that the tile is correct.
+    vector_tile::Tile tile;
+    tile.ParseFromString(out_tile.get_buffer());
     REQUIRE(1 == tile.layers_size());
     vector_tile::Tile_Layer const& layer = tile.layers(0);
     CHECK(std::string("layer") == layer.name());
@@ -587,8 +569,10 @@ TEST_CASE( "pbf vector tile from linestring geojson", "should create vector tile
     protozero::pbf_reader layer3 = pbf_tile.get_message();
     std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
                                     mapnik::vector_tile_impl::tile_datasource_pbf>(
-                                        layer3,0,0,0,256);
+                                        layer3,0,0,0);
     CHECK(ds2->get_name() == "layer");
+    
+    mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
     mapnik::query q(bbox);
     // note: https://github.com/mapbox/mapnik-vector-tile/issues/132 does not occur
     // if we uncomment these lines
@@ -616,5 +600,4 @@ TEST_CASE( "pbf vector tile from linestring geojson", "should create vector tile
     // only three features
     f_ptr = fs->next();
     CHECK(f_ptr == mapnik::feature_ptr());
-
 }
diff --git a/test/vector_tile_rasterize.cpp b/test/vector_tile_rasterize.cpp
index 6954017..61dfcfb 100644
--- a/test/vector_tile_rasterize.cpp
+++ b/test/vector_tile_rasterize.cpp
@@ -13,21 +13,28 @@
 #include "vector_tile_compression.hpp"
 #include "vector_tile_processor.hpp"
 #include "vector_tile_strategy.hpp"
-#include "vector_tile_backend_pbf.hpp"
-#include "vector_tile_util.hpp"
 #include "vector_tile_projection.hpp"
 #include "vector_tile_geometry_decoder.hpp"
-#include "vector_tile_datasource.hpp"
 #include "vector_tile_datasource_pbf.hpp"
 #include "protozero/pbf_reader.hpp"
 
+// boost
 #include <boost/optional/optional_io.hpp>
 
+// std
 #include <fstream>
 
-TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+TEST_CASE("vector tile rasterize -- should try to decode windfail tile")
+{
     // open vtile
-    std::ifstream stream("./test/data/0.0.0.vector.pbf",std::ios_base::in|std::ios_base::binary);
+    std::ifstream stream("./test/data/0.0.0.vector.mvt",std::ios_base::in|std::ios_base::binary);
     REQUIRE(stream.is_open());
     std::string buffer(std::istreambuf_iterator<char>(stream.rdbuf()),(std::istreambuf_iterator<char>()));
     REQUIRE(buffer.size() == 3812);
@@ -37,31 +44,27 @@ TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
     mapnik::vector_tile_impl::zlib_decompress(buffer,uncompressed);
     REQUIRE(uncompressed.size() == 4934);
 
-    typedef vector_tile::Tile tile_type;
-    tile_type tile;
-    unsigned tile_size = 256;
     mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
 
     // first we render the raw tile directly to an image
     {
-        mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
-        tile_type tile2;
+        mapnik::Map map(256,256,"+init=epsg:3857");
+        vector_tile::Tile tile2;
         CHECK(tile2.ParseFromString(uncompressed));
-        std::string key("");
-        CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
-        CHECK("" == key);
-        CHECK(false == mapnik::vector_tile_impl::is_solid_extent(uncompressed,key));
-        CHECK("" == key);
 
         CHECK(1 == tile2.layers_size());
         vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
         CHECK(std::string("water") == layer2.name());
+        CHECK(1 == layer2.version());
         CHECK(23 == layer2.features_size());
 
+        protozero::pbf_reader tile_reader(uncompressed);
+        REQUIRE(tile_reader.next(mapnik::vector_tile_impl::Tile_Encoding::LAYERS));
+        protozero::pbf_reader layer_reader = tile_reader.get_message();
         mapnik::layer lyr2("water",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,map.width());
+        std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
+                                        mapnik::vector_tile_impl::tile_datasource_pbf>(
+                                            layer_reader,0,0,0);
         ds->set_envelope(bbox);
         CHECK( ds->type() == mapnik::datasource::Vector );
         CHECK( ds->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
@@ -83,7 +86,8 @@ TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
         mapnik::image_rgba8 im(map.width(),map.height());
         mapnik::agg_renderer<mapnik::image_rgba8> ren(map,im);
         ren.apply();
-        if (!mapnik::util::exists("test/fixtures/rasterize-expected-1.png")) {
+        if (!mapnik::util::exists("test/fixtures/rasterize-expected-1.png"))
+        {
             mapnik::save_to_file(im,"test/fixtures/rasterize-expected-1.png","png32");
         }
     }
@@ -91,61 +95,57 @@ TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
     // set up to "re-render" it
     // the goal here is to trigger the geometries to pass through
     // the decoder and encoder again
+    mapnik::vector_tile_impl::tile out_tile(bbox);
+    
     {
-        typedef mapnik::vector_tile_impl::backend_pbf backend_type;
-        typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
-        backend_type backend(tile,16);
         std::string merc_srs("+init=epsg:3857");
-        mapnik::Map map(tile_size,tile_size,merc_srs);
-        map.zoom_to_box(bbox);
-        mapnik::request m_req(map.width(),map.height(),map.get_current_extent());
-        protozero::pbf_reader message(uncompressed.data(),uncompressed.size());
-        while (message.next(3)) {
+        mapnik::Map map(256, 256, merc_srs);
+        protozero::pbf_reader message(uncompressed.data(), uncompressed.size());
+        while (message.next(3))
+        {
             protozero::pbf_reader layer_msg = message.get_message();
             auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(
                         layer_msg,
                         0,
                         0,
-                        0,
-                        tile_size);
+                        0);
             mapnik::layer lyr(ds->get_name(),merc_srs);
-            ds->set_envelope(m_req.get_buffered_extent());
+            ds->set_envelope(out_tile.get_buffered_extent());
             lyr.set_datasource(ds);
             map.add_layer(lyr);
         }
-        renderer_type ren(backend,map,m_req);
+        mapnik::vector_tile_impl::processor ren(map);
         ren.set_process_all_rings(true);
-        ren.apply();
+        ren.set_fill_type(mapnik::vector_tile_impl::non_zero_fill);
+        ren.update_tile(out_tile);
     }
     // now `tile` should contain all the data
     std::string buffer2;
-    CHECK(tile.SerializeToString(&buffer2));
-    CHECK(2774 == buffer2.size());
+    out_tile.serialize_to_string(buffer2);
+    CHECK(2835 == buffer2.size());
 
-    std::ofstream stream_out("./test/data/0.0.0.vector-b.pbf",std::ios_base::out|std::ios_base::binary);
+    std::ofstream stream_out("./test/data/0.0.0.vector-b.mvt",std::ios_base::out|std::ios_base::binary);
     stream_out << buffer2;
     stream_out.close();
 
     // let's now render this to a image and make sure it looks right
     {
-        mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
-        tile_type tile2;
+        mapnik::Map map(256,256,"+init=epsg:3857");
+        vector_tile::Tile tile2;
         CHECK(tile2.ParseFromString(buffer2));
-        std::string key("");
-        CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
-        CHECK("" == key);
-        CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer2,key));
-        CHECK("" == key);
-
         CHECK(1 == tile2.layers_size());
         vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
         CHECK(std::string("water") == layer2.name());
         CHECK(5 == layer2.features_size());
+        CHECK(2 == layer2.version());
 
         mapnik::layer lyr2("water",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,map.width());
+        protozero::pbf_reader tile_reader(buffer2);
+        REQUIRE(tile_reader.next(mapnik::vector_tile_impl::Tile_Encoding::LAYERS));
+        protozero::pbf_reader layer_reader = tile_reader.get_message();
+        std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
+                                        mapnik::vector_tile_impl::tile_datasource_pbf>(
+                                            layer_reader,0,0,0);
         ds->set_envelope(bbox);
         CHECK( ds->type() == mapnik::datasource::Vector );
         CHECK( ds->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
@@ -168,12 +168,12 @@ TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
         mapnik::agg_renderer<mapnik::image_rgba8> ren(map,im);
         ren.apply();
         unsigned diff = testing::compare_images(im,"test/fixtures/rasterize-expected-1.png");
-        // should be almost equal (49 is good enough since re-rendering filters a few small degenerates)
-        CHECK(49 == diff);
-        if (diff > 49) {
+        // should be almost equal (50 is good enough since re-rendering filters a few small degenerates)
+        CHECK(50 == diff);
+        if (diff > 50)
+        {
             mapnik::save_to_file(im,"test/fixtures/rasterize-actual-1.png","png32");
         }
     }
-
 }
 

-- 
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