[mapnik-vector-tile] 04/14: Imported Upstream version 0.5.1+dfsg

Jérémy Lal kapouer at moszumanska.debian.org
Wed Aug 20 21:26:20 UTC 2014


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

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

commit 16e0df8b187dd21d40d3326ac3828252b4298cb7
Author: Jérémy Lal <kapouer at melix.org>
Date:   Wed Aug 20 19:03:09 2014 +0200

    Imported Upstream version 0.5.1+dfsg
---
 .gitignore                           |   5 +-
 .travis.yml                          |  16 +-
 CHANGELOG.md                         |  43 +++++
 Makefile                             |  34 +++-
 README.md                            |  46 +----
 examples/c++/Makefile                |  11 +-
 examples/c++/README.md               |  28 +++
 examples/c++/tileinfo.cpp            | 100 ++++++++++-
 package.json                         |   5 +-
 proto/vector_tile.proto              |   3 +
 src/hash_variant.hpp                 |  36 ----
 src/mapnik3x_compatibility.hpp       |  34 ++++
 src/vector_tile_backend_pbf.hpp      | 100 +++--------
 src/vector_tile_datasource.hpp       | 205 ++++++++++++---------
 src/vector_tile_geometry_encoder.hpp | 191 ++++++++++++++++++++
 src/vector_tile_processor.hpp        | 153 +++++++++++++---
 src/vector_tile_util.hpp             |  29 ++-
 test/encoding_util.hpp               |  86 +++++++++
 test/geometry_encoding.cpp           | 338 +++++++++++++++++++++++++++++++++++
 test/raster_style.xml                |   7 +
 test/raster_tile.cpp                 | 125 +++++++++++++
 test/style.xml                       |   7 +
 test/test_utils.hpp                  |  23 ++-
 test/vector_tile.cpp                 | 257 ++++++++++++++++++++++----
 24 files changed, 1549 insertions(+), 333 deletions(-)

diff --git a/.gitignore b/.gitignore
index f0fe95c..19f5f78 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,9 @@ src/vector_tile.pb.cc
 src/vector_tile.pb.h
 python/vector_tile_pb2.py
 test/run-test
+test/run-geom-test
+test/run-raster-test
 *pyc
 archive
-TODO.md
\ No newline at end of file
+TODO.md
+examples/c++/tileinfo
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index a422bfc..04883b0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,22 +7,28 @@ compiler:
 before_install:
  - sudo apt-add-repository --yes ppa:mapnik/v2.2.0
  - sudo apt-add-repository --yes ppa:mapnik/nightly-2.3
- - sudo apt-get update -qq
+ #- sudo apt-add-repository --yes ppa:mapnik/nightly-trunk
+ - sudo apt-get update -y
 
 install:
- - sudo apt-get -qq install libprotobuf7 libprotobuf-dev protobuf-compiler
+ - sudo apt-get -y install libprotobuf7 libprotobuf-dev protobuf-compiler g++ gcc
+ #- sudo apt-get -y install gcc-4.8 g++-4.8
 
 before_script:
- - sudo apt-get -qq install libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0*
+ - sudo apt-get -y install libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0*
  - make clean
  - make test
  - sudo apt-get purge libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0*
 
 script:
- - sudo apt-get -qq install libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0*
+ - sudo apt-get -y install libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* mapnik-input-plugin*=2.3.0*
  - make clean
  - make test
- - sudo apt-get purge libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0*
+ #- sudo apt-get purge libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* mapnik-input-plugin*=2.3.0*
+ #- sudo apt-get -qq install libmapnik=3.0.0* mapnik-utils=3.0.0* libmapnik-dev=3.0.0* mapnik-input-plugin*=3.0.0*
+ #- if [ "${CXX}" = 'g++' ]; then export CXX="g++-4.8" && export CC="gcc-4.8"; fi;
+ #- make clean
+ #- make test
 
 notifications:
   irc:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60593b4..c22ad80 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,50 @@
 # Changlog
 
+## 0.5.1
+
+ - Minor build fixes
+
+## 0.5.0
+
+ - Experimental support for encoding images in vector tile features.
+ - Fixed potential hang if trying to render a feature without geometries
+
+## 0.4.2
+
+ - Additional optimizations and fixes to geometry encoding (#38 - avoid dropping vertex that forms horizontal or vertical right angle)
+
+## 0.4.1
+
+ - Added initial support for Mapnik 3.x
+
+## 0.4.0
+
+ - Refactored geometry encoder with fixes to drop duplicated/no-op verticies and/or
+  close commands
+ - `npm install` no longer runs `protoc` - the responsibility for this is now up to `node-mapnik`
+ - Optimized is_solid check
+
+## 0.3.7
+
+ - Add back protoc running to avoid unintended node-mapnik breakages with older versions.
+
+## 0.3.6
+
+ - Avoided 'Unknown command type (is_solid_extent): 0'
+
+## 0.3.5
+
+ - `npm install` no longer runs `protoc` - the responsibility for this is now up to `node-mapnik`
+ - Improved tile encoding: empty layers are no longer added
+ - All move_to commands and the last vertex in lines is no longer thrown out even with high `tolerance`
+ - Rolled back the change from v0.3.4 - multipart geometries are now again not decoded correctly, but this
+   needs to stay this way for performance reasons at the cost of correct marker/labeling placement on each
+   geometry part - long term solutions tracked at mapnik/mapnik#2151. Re-enabled to v0.3.4 behavior by setting
+   `-DEXPLODE_PARTS` in `CXXFLAGS`.
+
 ## 0.3.4
 
+ - Fixed tile_datasource geometry decoding such that it polygons are closed (for hit_test results)
  - Fixed tile_datasource geometry decoding such that it respects multipart geometries (#19)
 
 ## 0.3.3
diff --git a/Makefile b/Makefile
index 33179c3..2b2203f 100755
--- a/Makefile
+++ b/Makefile
@@ -2,12 +2,17 @@ PROTOBUF_CXXFLAGS=$(shell pkg-config protobuf --cflags)
 PROTOBUF_LDFLAGS=$(shell pkg-config protobuf --libs-only-L) -lprotobuf-lite
 MAPNIK_CXXFLAGS=$(shell mapnik-config --cflags) -Wsign-compare
 MAPNIK_LDFLAGS=$(shell mapnik-config --libs --ldflags --dep-libs)
-CXXFLAGS := $(CXXFLAGS) # inherit from env
-LDFLAGS := $(LDFLAGS) # inherit from env
+COMMON_FLAGS = -Wall -Wshadow -pedantic -Wno-c++11-long-long -Wno-c++11-extensions
+#-Wsign-compare -Wsign-conversion -Wunused-parameter
+# inherit from env
+CXX := $(CXX)
+CXXFLAGS := $(CXXFLAGS)
+LDFLAGS := $(LDFLAGS)
+MAPNIK_PLUGINDIR := $(shell mapnik-config --input-plugins)
 
 all: mapnik-vector-tile
 
-mapnik-vector-tile: src/vector_tile.pb.cc
+mapnik-vector-tile: src/vector_tile.pb.cc Makefile
 
 src/vector_tile.pb.cc: proto/vector_tile.proto
 	protoc -Iproto/ --cpp_out=./src proto/vector_tile.proto
@@ -17,11 +22,26 @@ python/vector_tile_pb2.py: proto/vector_tile.proto
 
 python: python/vector_tile_pb2.py
 
-test/run-test: src/vector_tile.pb.cc test/vector_tile.cpp test/test_utils.hpp src/*
-	$(CXX) -o ./test/run-test test/vector_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field
+test/run-test: Makefile src/vector_tile.pb.cc test/vector_tile.cpp test/test_utils.hpp src/*
+	@$(CXX) -o ./test/run-test test/vector_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field
 
-test: test/run-test src/vector_tile.pb.cc
+test/test-cfg.h:
+	echo "#define MAPNIK_PLUGINDIR \"$(MAPNIK_PLUGINDIR)\"" > test/test-cfg.h
+
+test: test/test-cfg.h test/run-test test/run-geom-test ./test/run-raster-test src/vector_tile.pb.cc test/catch.hpp
 	./test/run-test
+	./test/run-geom-test
+	./test/run-raster-test
+
+./test/run-raster-test: Makefile src/vector_tile.pb.cc test/raster_tile.cpp test/encoding_util.hpp test/catch.hpp
+	@$(CXX) -o ./test/run-raster-test test/raster_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field
+	./test/run-raster-test
+
+./test/run-geom-test: Makefile src/vector_tile.pb.cc test/geometry_encoding.cpp test/encoding_util.hpp src/vector_tile_geometry_encoder.hpp test/catch.hpp
+	@$(CXX) -o ./test/run-geom-test test/geometry_encoding.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field
+
+geom: test/run-geom-test src/vector_tile.pb.cc
+	./test/run-geom-test
 
 python-test: python/vector_tile_pb2.py
 	python ./test/python/test.py
@@ -30,6 +50,8 @@ clean:
 	@rm -f ./src/vector_tile.pb.cc
 	@rm -f ./src/vector_tile.pb.h
 	@rm -f ./test/run-test
+	@rm -f ./test/run-geom-test
+	@rm -f ./test/run-raster-test
 	@rm -f ./python/vector_tile_pb2.py
 	@rm -f ./python/*pyc
 
diff --git a/README.md b/README.md
index 445fbf5..f2f9142 100644
--- a/README.md
+++ b/README.md
@@ -2,55 +2,21 @@
 
 [![Build Status](https://secure.travis-ci.org/mapbox/mapnik-vector-tile.png)](http://travis-ci.org/mapbox/mapnik-vector-tile)
 
-A high performance library for working with vector tiles with Mapnik.
+A high performance library for working with vector tiles.
 
 Provides C++ headers that support rendering geodata into vector tiles
 and rendering vector tiles into images.
 
-Many efforts at vector tiles use [GeoJSON](http://www.geojson.org/) or other
-text based formats that are easy to parse in the browser.
-
-This approach is different: tiles are encoded as protobuf messages and
-storage is tightly designed around the [Mapnik rendering engine](http://mapnik.org).
-In this case Mapnik is the client rather than the browser. This provides a geodata
-format that is a drop-in replacement for datasources like postgis and shapefiles
-without compromising on speed and can be styled using existing design tools
-like [TileMill](http://tilemill.com).
+## Depends
 
+ - Mapnik > = v2.2.x: `libmapnik` and `mapnik-config`
+ - Protobuf: `libprotobuf` and `protoc`
 
 ## Implementation details
 
-Vector tiles in this code represent a direct serialization of Mapnik layers
-optimized for space efficient storage and fast deserialization directly back
-into Mapnik objects. For those familiar with the Mapnik API vector tiles
-here can be considered a named array of `mapnik::featureset_ptr` whose geometries
-have been pre-tiled.
-
-A vector tile can consist of one or more ordered layers identified by name
-and containing one or more features.
-
-Features contain attributes and geometries: either point, linestring, or polygon.
-Features expect single geometries so multipolygons or multilinestrings are represented
-as multiple features, each storing a single geometry part.
-
-Geometries are stored as an x,y,command stream (where `command` is a rendering command
-like move_to or line_to). Geometries are clipped, reprojected into the map srs,
-converted to screen coordinates, and [delta](http://en.wikipedia.org/wiki/Delta_encoding)
-and [zigzag](https://developers.google.com/protocol-buffers/docs/encoding#types) encoded.
-
-Feature attributes are encoded as key:value pairs which are dictionary encoded
-at the layer level for compact storage of any repeated keys or values. Values use variant
-type encoding supporting both unicode strings, boolean values, and various integer and
-floating point types.
-
-Vector tiles are serialized as protobuf messages which are then zlib compressed.
-
-The assumed projection is Spherical Mercator (`epsg:3857`).
-
-## Requires
+Vector tiles in this code represent a direct serialization of Mapnik layers optimized for space efficient storage and fast deserialization. For those familiar with the Mapnik API vector tiles here can be considered a named array of `mapnik::featureset_ptr` whose geometries have been pre-tiled.
 
-- Mapnik v2.2.0: `libmapnik` and `mapnik-config`
-- Protobuf: `libprotobuf` and `protoc`
+For more details see [vector-tile-spec](https://github.com/mapbox/vector-tile-spec).
 
 ### Ubuntu Dependencies Installation
 
diff --git a/examples/c++/Makefile b/examples/c++/Makefile
index d2870c9..14f21fd 100755
--- a/examples/c++/Makefile
+++ b/examples/c++/Makefile
@@ -6,16 +6,17 @@ LDFLAGS := $(LDFLAGS) # inherit from env
 all: tileinfo
 
 tileinfo: tileinfo.cpp ../../src/vector_tile.pb.cc
-	$(CXX) tileinfo.cpp ../../src/vector_tile.pb.cc -o tileinfo -lprotobuf-lite -lz
+	$(CXX) $(CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(LDFLAGS) $(PROTOBUF_LDFLAGS) tileinfo.cpp ../../src/vector_tile.pb.cc -o tileinfo -lprotobuf-lite -lz
+
+install: tileinfo
+	mkdir -p /usr/local/bin
+	cp ./tileinfo /usr/local/bin/tileinfo
+	chmod +x /usr/local/bin/tileinfo
 
 test:
 	./tileinfo ../data/14_8716_8015.vector.pbf
 	./tileinfo ../data/14_2620_6331.vector.pbf.z
 
-install:
-	cp ./tileinfo /usr/local/bin/tileinfo
-	chmod +x /usr/local/bin/tileinfo
-
 clean:
 	@rm -f ./tileinfo
 
diff --git a/examples/c++/README.md b/examples/c++/README.md
new file mode 100644
index 0000000..695b826
--- /dev/null
+++ b/examples/c++/README.md
@@ -0,0 +1,28 @@
+
+# tileinfo
+
+A commandline tool to dump details about what layers, features, and geometries exist in a vector tile.
+
+Vector tiles can be either zlib deflated (compressed) or not.
+
+
+## Depends
+
+ - C++ compiler
+ - libprotobuf
+
+Install these dependencies on Ubuntu:
+
+    apt-get install pkg-config libprotobuf7 libprotobuf-dev protobuf-compiler build-essential g++
+
+Install these dependencies on OS X:
+
+    brew install pkg-config protobuf
+
+## Installation
+
+    make
+
+## Usage
+
+    tileinfo ../data/14_8716_8015.vector.pbf
diff --git a/examples/c++/tileinfo.cpp b/examples/c++/tileinfo.cpp
index 65b90c2..222d6ee 100644
--- a/examples/c++/tileinfo.cpp
+++ b/examples/c++/tileinfo.cpp
@@ -4,6 +4,21 @@
 #include <iostream>
 #include <fstream>
 #include <stdexcept>
+#include <sstream>
+
+enum CommandType {
+    SEG_END    = 0,
+    SEG_MOVETO = 1,
+    SEG_LINETO = 2,
+    SEG_CLOSE = (0x40 | 0x0f)
+};
+
+enum eGeomType {
+    Unknown = 0,
+    Point = 1,
+    LineString = 2,
+    Polygon = 3
+};
 
 int main(int argc, char** argv)
 {
@@ -67,6 +82,70 @@ int main(int argc, char** argv)
                 std::cout << "  features: " << layer.features_size() << "\n";
                 std::cout << "  keys: " << layer.keys_size() << "\n";
                 std::cout << "  values: " << layer.values_size() << "\n";
+                unsigned total_repeated = 0;
+                unsigned num_commands = 0;
+                unsigned num_move_to = 0;
+                unsigned num_line_to = 0;
+                unsigned num_close = 0;
+                unsigned num_empty = 0;
+                unsigned degenerate = 0;
+                for (unsigned j=0;j<layer.features_size();++j)
+                {
+                    mapnik::vector::tile_feature const & f = layer.features(j);
+                    total_repeated += f.geometry_size();
+                    eGeomType g_type = static_cast<eGeomType>(f.type());
+                    int cmd = -1;
+                    const int cmd_bits = 3;
+                    unsigned length = 0;
+                    unsigned g_length = 0;
+                    for (int k = 0; k < f.geometry_size();)
+                    {
+                        if (!length) {
+                            unsigned cmd_length = f.geometry(k++);
+                            cmd = cmd_length & ((1 << cmd_bits) - 1);
+                            length = cmd_length >> cmd_bits;
+                            if (length <= 0) num_empty++;
+                            num_commands++;
+                            g_length = 0;
+                        }
+                        if (length > 0) {
+                            length--;
+                            if (cmd == SEG_MOVETO || cmd == SEG_LINETO)
+                            {
+                                f.geometry(k++);
+                                f.geometry(k++);
+                                g_length++;
+                                if (cmd == SEG_MOVETO)
+                                {
+                                    num_move_to++;
+                                }
+                                else if (cmd == SEG_LINETO)
+                                {
+                                    num_line_to++;
+                                }
+                            }
+                            else if (cmd == (SEG_CLOSE & ((1 << cmd_bits) - 1)))
+                            {
+                                if (g_length <= 2) degenerate++;
+                                num_close++;
+                            }
+                            else
+                            {
+                                std::stringstream s;
+                                s << "Unknown command type: " << cmd;
+                                throw std::runtime_error(s.str());
+                            }
+                        }
+                    }
+                }
+                std::cout << "  geometry summary:\n";
+                std::cout << "    total: " << total_repeated << "\n";
+                std::cout << "    commands: " << num_commands << "\n";
+                std::cout << "    move_to: " << num_move_to << "\n";
+                std::cout << "    line_to: " << num_line_to << "\n";
+                std::cout << "    close: " << num_close << "\n";
+                std::cout << "    degenerate polygons: " << degenerate << "\n";
+                std::cout << "    empty geoms: " << num_empty << "\n";
             }
         } else {
             for (unsigned i=0;i<tile.layers_size();++i)
@@ -79,7 +158,10 @@ int main(int argc, char** argv)
                 for (unsigned i=0;i<layer.keys_size();++i)
                 {
                      std::string const& key = layer.keys(i);
-                     std::cout << key << ",";
+                     std::cout << key;
+                     if (i<layer.keys_size()-1) {
+                        std::cout << ",";
+                     }
                 }
                 std::cout << "\n";
                 std::cout << "  values: ";
@@ -87,7 +169,7 @@ int main(int argc, char** argv)
                 {
                      mapnik::vector::tile_value const & value = layer.values(i);
                      if (value.has_string_value()) {
-                          std::cout << "'" << value.string_value();
+                          std::cout << value.string_value();
                      } else if (value.has_int_value()) {
                           std::cout << value.int_value();
                      } else if (value.has_double_value()) {
@@ -111,7 +193,19 @@ int main(int argc, char** argv)
                  for (unsigned i=0;i<layer.features_size();++i)
                  {
                      mapnik::vector::tile_feature const & feat = layer.features(i);
-                     std::cout << "  feature: " << feat.id() << " " << feat.type() << "\n";
+                     std::cout << "  feature: " << feat.id() << "\n";
+                     std::cout << "    type: ";
+                     unsigned feat_type = feat.type();
+                     if (feat_type == 0) {
+                         std::cout << "Unknown";
+                     } else if (feat_type == 1) {
+                         std::cout << "Point";
+                     } else if (feat_type == 2) {
+                         std::cout << "LineString";
+                     } else if (feat_type == 3) {
+                         std::cout << "Polygon";
+                     }
+                     std::cout << "\n";
                      std::cout << "    tags: ";
                      for (unsigned j=0;j<feat.tags_size();++j)
                      {
diff --git a/package.json b/package.json
index 41602ee..82f8ffe 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,10 @@
 {
     "name": "mapnik-vector-tile",
-    "version": "0.3.4",
+    "version": "0.5.1",
     "description": "Mapnik vector tile API",
     "main": "./package.json",
     "repository"   :  {
       "type" : "git",
       "url"  : "git://github.com/mapbox/mapnik-vector-tile.git"
-    },
-    "scripts": {
-      "install" : "protoc -Iproto/ --cpp_out=./src/ ./proto/vector_tile.proto"
     }
 }
diff --git a/proto/vector_tile.proto b/proto/vector_tile.proto
index 110f37c..d17e1f1 100644
--- a/proto/vector_tile.proto
+++ b/proto/vector_tile.proto
@@ -60,6 +60,9 @@ message tile {
                 // also encoded as deltas to the previous position. The original
                 // position is (0,0)
                 repeated uint32 geometry = 4 [ packed = true ];
+
+                optional bytes raster = 5;
+
         }
 
         message layer {
diff --git a/src/hash_variant.hpp b/src/hash_variant.hpp
deleted file mode 100644
index 57af0da..0000000
--- a/src/hash_variant.hpp
+++ /dev/null
@@ -1,36 +0,0 @@
-// TODO: Remove this file once the minimum Boost version is bumped to 1.50
-
-#ifndef BOOST_HASH_VARIANT_FUNCTION_HPP
-#define BOOST_HASH_VARIANT_FUNCTION_HPP
-
-#if defined(_MSC_VER) && (_MSC_VER >= 1020)
-# pragma once
-#endif
-
-#include <boost/variant/variant_fwd.hpp>
-#include <boost/variant/static_visitor.hpp>
-#include <boost/variant/apply_visitor.hpp>
-#include <boost/functional/hash_fwd.hpp>
-
-namespace boost {
-
-namespace detail { namespace variant {
-    struct variant_hasher: public boost::static_visitor<std::size_t> {
-        template <class T>
-        std::size_t operator()(T const& val) const {
-            using namespace boost;
-            hash<T> hasher;
-            return hasher(val);
-        }
-    };
-    }}
-
-template < BOOST_VARIANT_ENUM_PARAMS(typename T) >
-std::size_t hash_value(variant< BOOST_VARIANT_ENUM_PARAMS(T) > const& val) {
-    std::size_t seed = boost::apply_visitor(detail::variant::variant_hasher(), val);
-    hash_combine(seed, val.which());
-    return seed;
-}
-}
-
-#endif
diff --git a/src/mapnik3x_compatibility.hpp b/src/mapnik3x_compatibility.hpp
new file mode 100644
index 0000000..d2d88ec
--- /dev/null
+++ b/src/mapnik3x_compatibility.hpp
@@ -0,0 +1,34 @@
+#ifndef __MAPNIK_VECTOR_TILE_COMPATIBILITY_H__
+#define __MAPNIK_VECTOR_TILE_COMPATIBILITY_H__
+
+#include <mapnik/version.hpp>
+
+#if MAPNIK_VERSION >= 300000
+    #define MAPNIK_UNIQUE_PTR std::unique_ptr
+    #define MAPNIK_SHARED_INCLUDE <memory>
+    #define MAPNIK_MAKE_SHARED_INCLUDE <memory>
+    #define MAPNIK_MAKE_SHARED std::make_shared
+    #define MAPNIK_SHARED_PTR std::shared_ptr
+    #define MAPNIK_ADD_LAYER add_layer
+    #define MAPNIK_GET std::get
+    #define MAPNIK_GEOM_TYPE mapnik::geometry_type::types
+    #define MAPNIK_POINT mapnik::geometry_type::types::Point
+    #define MAPNIK_POLYGON mapnik::geometry_type::types::Polygon
+    #define MAPNIK_LINESTRING mapnik::geometry_type::types::LineString
+    #define MAPNIK_UNKNOWN mapnik::geometry_type::types::Unknown
+#else
+    #define MAPNIK_UNIQUE_PTR std::auto_ptr
+    #define MAPNIK_SHARED_INCLUDE <boost/shared_ptr.hpp>
+    #define MAPNIK_MAKE_SHARED_INCLUDE <boost/make_shared.hpp>
+    #define MAPNIK_MAKE_SHARED boost::make_shared
+    #define MAPNIK_SHARED_PTR boost::shared_ptr
+    #define MAPNIK_ADD_LAYER addLayer
+    #define MAPNIK_GET boost::get
+    #define MAPNIK_GEOM_TYPE mapnik::eGeomType
+    #define MAPNIK_POINT mapnik::Point
+    #define MAPNIK_POLYGON mapnik::Polygon
+    #define MAPNIK_LINESTRING mapnik::LineString
+    #define MAPNIK_UNKNOWN mapnik::Unknown
+#endif
+
+#endif
diff --git a/src/vector_tile_backend_pbf.hpp b/src/vector_tile_backend_pbf.hpp
index 717c9be..f49d0c4 100644
--- a/src/vector_tile_backend_pbf.hpp
+++ b/src/vector_tile_backend_pbf.hpp
@@ -3,16 +3,18 @@
 
 // mapnik
 #include <mapnik/feature.hpp>
-#include <mapnik/version.hpp>
 #include <mapnik/value_types.hpp>
 
 // vector tile
 #include "vector_tile.pb.h"
+#include "vector_tile_geometry_encoder.hpp"
 
 // boost
 #include <boost/unordered_map.hpp>
 #include <boost/foreach.hpp>
 
+#include "mapnik3x_compatibility.hpp"
+
 namespace mapnik { namespace vector {
 
     struct to_tile_value: public boost::static_visitor<>
@@ -75,6 +77,14 @@ namespace mapnik { namespace vector {
         {
         }
 
+        void add_tile_feature_raster(std::string const& image_buffer)
+        {
+            if (current_feature_)
+            {
+                current_feature_->set_raster(image_buffer);
+            }
+        }
+
         void stop_tile_feature()
         {
             if (current_feature_)
@@ -99,8 +109,8 @@ namespace mapnik { namespace vector {
             feature_kv_iterator end = feature.end();
             for ( ;itr!=end; ++itr)
             {
-                std::string const& name = boost::get<0>(*itr);
-                mapnik::value const& val = boost::get<1>(*itr);
+                std::string const& name = MAPNIK_GET<0>(*itr);
+                mapnik::value const& val = MAPNIK_GET<1>(*itr);
                 if (!val.is_null())
                 {
                     // Insert the key index
@@ -156,90 +166,28 @@ namespace mapnik { namespace vector {
             current_layer_->set_extent(256 * path_multiplier_);
         }
 
-        void stop_tile_layer()
+        inline void stop_tile_layer()
         {
-            // NOTE: we intentionally do not remove layers without features
-            // since the re-rendering logic expects a layer entry no matter what
             //std::cerr << "stop_tile_layer()" << std::endl;
         }
 
         template <typename T>
-        unsigned add_path(T & path, unsigned tolerance, mapnik::eGeomType type)
+        inline unsigned add_path(T & path, unsigned tolerance, MAPNIK_GEOM_TYPE type)
         {
-            unsigned count = 0;
-
             if (current_feature_)
             {
-                path.rewind(0);
-                current_feature_->set_type(mapnik::vector::tile_GeomType(type));
-
-                vertex2d vtx(vertex2d::no_init);
-                int cmd = -1;
-                int cmd_idx = -1;
-                const int cmd_bits = 3;
-                unsigned length = 0;
-
-                // See vector_tile.proto for a description of how vertex command
-                // encoding works.
-                while ((vtx.cmd = path.vertex(&vtx.x, &vtx.y)) != SEG_END)
-                {
-                    if (static_cast<int>(vtx.cmd) != cmd)
-                    {
-                        if (cmd_idx >= 0)
-                        {
-                            // Encode the previous length/command value.
-                            current_feature_->set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
-                        }
-                        cmd = static_cast<int>(vtx.cmd);
-                        length = 0;
-                        cmd_idx = current_feature_->geometry_size();
-                        current_feature_->add_geometry(0); // placeholder
-                    }
-
-                    if (cmd == SEG_MOVETO || cmd == SEG_LINETO)
-                    {
-                        // Compute delta to the previous coordinate.
-                        int32_t cur_x = static_cast<int32_t>(std::floor((vtx.x * path_multiplier_)+.5));
-                        int32_t cur_y = static_cast<int32_t>(std::floor((vtx.y * path_multiplier_)+.5));
-                        int32_t dx = cur_x - x_;
-                        int32_t dy = cur_y - y_;
-
-                        // Omit movements that are no-ops.
-                        unsigned x_floor = static_cast<unsigned>(std::abs(dx));
-                        unsigned y_floor = static_cast<unsigned>(std::abs(dy));
-                        if (x_floor >= tolerance ||
-                            y_floor >= tolerance ||
-                            length == 0)
-                        {
-                            // Manual zigzag encoding.
-                            current_feature_->add_geometry((dx << 1) ^ (dx >> 31));
-                            current_feature_->add_geometry((dy << 1) ^ (dy >> 31));
-                            x_ = cur_x;
-                            y_ = cur_y;
-
-                            length++;
-                        }
-                    }
-                    else if (cmd == SEG_CLOSE) {
-                        length++;
-                    }
-                    else {
-                        throw std::runtime_error("Unknown command type");
-                    }
-
-                    ++count;
-                }
-
-                // Update the last length/command value.
-                if (cmd_idx >= 0)
-                {
-                    current_feature_->set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
-                }
+                return encode_geometry(path,
+                                       static_cast<tile_GeomType>(type),
+                                       *current_feature_,
+                                       x_,
+                                       y_,
+                                       tolerance,
+                                       path_multiplier_);
             }
-            return count;
+            return 0;
         }
     };
 
-    }} // end ns
+}} // end ns
 
 #endif // __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__
diff --git a/src/vector_tile_datasource.hpp b/src/vector_tile_datasource.hpp
index 362e8e9..001d291 100644
--- a/src/vector_tile_datasource.hpp
+++ b/src/vector_tile_datasource.hpp
@@ -14,12 +14,14 @@
 #include <mapnik/version.hpp>
 #include <mapnik/value_types.hpp>
 #include <mapnik/well_known_srs.hpp>
-
+#include <mapnik/version.hpp>
 #include <mapnik/vertex.hpp>
 #include <mapnik/datasource.hpp>
 #include <mapnik/feature.hpp>
 #include <mapnik/feature_factory.hpp>
 #include <mapnik/geom_util.hpp>
+#include <mapnik/image_reader.hpp>
+#include <mapnik/raster.hpp>
 
 #include <memory>
 #include <stdexcept>
@@ -27,24 +29,79 @@
 
 #include <boost/optional.hpp>
 #include <boost/ptr_container/ptr_vector.hpp>
-#include <boost/make_shared.hpp>
-#include <boost/shared_ptr.hpp>
 #include <unicode/unistr.h>
-#include <boost/algorithm/string.hpp>
+
+#include "mapnik3x_compatibility.hpp"
+#include MAPNIK_MAKE_SHARED_INCLUDE
+#include MAPNIK_SHARED_INCLUDE
 
 namespace mapnik { namespace vector {
 
+    void add_attributes(mapnik::feature_ptr feature,
+                        mapnik::vector::tile_feature const& f,
+                        mapnik::vector::tile_layer const& layer,
+                        mapnik::transcoder const& tr)
+    {
+        std::size_t num_keys = static_cast<std::size_t>(layer.keys_size());
+        std::size_t num_values = static_cast<std::size_t>(layer.values_size());
+        for (int m = 0; m < f.tags_size(); m += 2)
+        {
+            std::size_t key_name = f.tags(m);
+            std::size_t key_value = f.tags(m + 1);
+            if (key_name < num_keys
+                && key_value < num_values)
+            {
+                std::string const& name = layer.keys(key_name);
+                if (feature->has_key(name))
+                {
+                    mapnik::vector::tile_value const& value = layer.values(key_value);
+                    if (value.has_string_value())
+                    {
+                        std::string str = value.string_value();
+                        feature->put(name, tr.transcode(str.data(), str.length()));
+                    }
+                    else if (value.has_int_value())
+                    {
+                        feature->put(name, static_cast<mapnik::value_integer>(value.int_value()));
+                    }
+                    else if (value.has_double_value())
+                    {
+                        feature->put(name, static_cast<mapnik::value_double>(value.double_value()));
+                    }
+                    else if (value.has_float_value())
+                    {
+                        feature->put(name, static_cast<mapnik::value_double>(value.float_value()));
+                    }
+                    else if (value.has_bool_value())
+                    {
+                        feature->put(name, static_cast<mapnik::value_bool>(value.bool_value()));
+                    }
+                    else if (value.has_sint_value())
+                    {
+                        feature->put(name, static_cast<mapnik::value_integer>(value.sint_value()));
+                    }
+                    else if (value.has_uint_value())
+                    {
+                        feature->put(name, static_cast<mapnik::value_integer>(value.uint_value()));
+                    }
+                }
+            }
+        }
+    }
+
     template <typename Filter>
     class tile_featureset : public Featureset
     {
     public:
         tile_featureset(Filter const& filter,
+                        mapnik::box2d<double> const& tile_extent,
                         std::set<std::string> const& attribute_names,
                         mapnik::vector::tile_layer const& layer,
                         double tile_x,
                         double tile_y,
                         double scale)
             : filter_(filter),
+              tile_extent_(tile_extent),
               layer_(layer),
               tile_x_(tile_x),
               tile_y_(tile_y),
@@ -52,7 +109,7 @@ namespace mapnik { namespace vector {
               itr_(0),
               end_(layer_.features_size()),
               tr_("utf-8"),
-              ctx_(boost::make_shared<mapnik::context_type>())
+              ctx_(MAPNIK_MAKE_SHARED<mapnik::context_type>())
         {
             std::set<std::string>::const_iterator pos = attribute_names.begin();
             std::set<std::string>::const_iterator end = attribute_names.end();
@@ -77,18 +134,43 @@ namespace mapnik { namespace vector {
             {
                 mapnik::vector::tile_feature const& f = layer_.features(itr_);
                 mapnik::value_integer feature_id = itr_++;
-                // if encoded feature was given an id, respect it
-                // https://github.com/mapbox/mapnik-vector-tile/issues/17
-                // https://github.com/mapbox/mapnik-vector-tile/issues/18
-                if (f.has_id())
+                if (f.has_raster())
                 {
-                    feature_id = f.id();
+                    std::string const& image_buffer = f.raster();
+                    MAPNIK_UNIQUE_PTR<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.data(),image_buffer.size()));
+                    if (reader.get())
+                    {
+                        if (f.has_id())
+                        {
+                            feature_id = f.id();
+                        }
+                        #if MAPNIK_VERSION >= 300000
+                        double filter_factor = 1.0;
+                        #endif
+                        bool premultiplied = false;
+                        mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
+                        mapnik::raster_ptr raster = MAPNIK_MAKE_SHARED<mapnik::raster>(
+                                    tile_extent_,
+                                    reader->width(),
+                                    reader->height(),
+                        #if MAPNIK_VERSION >= 300000
+                                    filter_factor,
+                        #endif
+                                    premultiplied
+                                    );
+                        reader->read(0,0,raster->data_);
+                        feature->set_raster(raster);
+                        add_attributes(feature,f,layer_,tr_);
+                        return feature;
+                    }
+                }
+                if (f.geometry_size() <= 0)
+                {
+                    continue;
                 }
-                mapnik::feature_ptr feature(
-                    mapnik::feature_factory::create(ctx_,feature_id));
-                feature->paths().push_back(new mapnik::geometry_type(
-                        mapnik::eGeomType(f.type())));
-                mapnik::geometry_type * geom = &feature->paths().front();
+                MAPNIK_UNIQUE_PTR<mapnik::geometry_type> geom(
+                    new mapnik::geometry_type(
+                        MAPNIK_GEOM_TYPE(f.type())));
                 int cmd = -1;
                 const int cmd_bits = 3;
                 unsigned length = 0;
@@ -116,11 +198,6 @@ namespace mapnik { namespace vector {
                             y -= (static_cast<double>(dy) / scale_);
                             if (cmd == mapnik::SEG_MOVETO)
                             {
-                                if (!first) {
-                                    feature->paths().push_back(new mapnik::geometry_type(
-                                        mapnik::eGeomType(f.type())));
-                                    geom = &feature->paths().back();
-                                }
                                 first_x = x;
                                 first_y = y;
                             }
@@ -142,7 +219,10 @@ namespace mapnik { namespace vector {
                         }
                         else
                         {
-                            throw std::runtime_error("Unknown command type");
+                            std::stringstream msg;
+                            msg << "Unknown command type (tile_featureset): "
+                                << cmd;
+                            throw std::runtime_error(msg.str());
                         }
                     }
                 }
@@ -150,63 +230,13 @@ namespace mapnik { namespace vector {
                 {
                     continue;
                 }
-
-                // attributes
-                for (int m = 0; m < f.tags_size(); m += 2)
+                if (f.has_id())
                 {
-                    std::size_t key_name = f.tags(m);
-                    std::size_t key_value = f.tags(m + 1);
-
-                    if (key_name < static_cast<std::size_t>(layer_.keys_size())
-                        && key_value < static_cast<std::size_t>(layer_.values_size()))
-                    {
-                        std::string const& name = layer_.keys(key_name);
-                        if (feature->has_key(name))
-                        {
-                            mapnik::vector::tile_value const& value = layer_.values(key_value);
-                            if (value.has_string_value())
-                            {
-                                std::string str = value.string_value();
-                                feature->put(name, tr_.transcode(str.data(), str.length()));
-                            }
-                            else if (value.has_int_value())
-                            {
-                                mapnik::value_integer val = value.int_value();
-                                feature->put(name, val);
-                            }
-                            else if (value.has_double_value())
-                            {
-                                mapnik::value_double val = value.double_value();
-                                feature->put(name, val);
-                            }
-                            else if (value.has_float_value())
-                            {
-                                mapnik::value_double val = value.float_value();
-                                feature->put(name, val);
-                            }
-                            else if (value.has_bool_value())
-                            {
-                                mapnik::value_bool val = value.bool_value();
-                                feature->put(name, val);
-                            }
-                            else if (value.has_sint_value())
-                            {
-                                mapnik::value_integer val = value.sint_value();
-                                feature->put(name, val);
-                            }
-                            else if (value.has_uint_value())
-                            {
-                                mapnik::value_integer val = value.uint_value();
-                                feature->put(name, val);
-                            }
-                            else
-                            {
-                                // Do nothing
-                                //feature->put_new(name, mapnik::value_null());
-                            }
-                        }
-                    }
+                    feature_id = f.id();
                 }
+                mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
+                feature->paths().push_back(geom.release());
+                add_attributes(feature,f,layer_,tr_);
                 return feature;
             }
             return feature_ptr();
@@ -214,6 +244,7 @@ namespace mapnik { namespace vector {
 
     private:
         Filter filter_;
+        mapnik::box2d<double> tile_extent_;
         mapnik::vector::tile_layer const& layer_;
         double tile_x_;
         double tile_y_;
@@ -237,6 +268,7 @@ namespace mapnik { namespace vector {
         featureset_ptr features(query const& q) const;
         featureset_ptr features_at_point(coord2d const& pt, double tol = 0) const;
         void set_envelope(box2d<double> const& bbox);
+        box2d<double> get_tile_extent() const;
         box2d<double> envelope() const;
         boost::optional<geometry_t> get_geometry_type() const;
         layer_descriptor get_descriptor() const;
@@ -286,8 +318,8 @@ namespace mapnik { namespace vector {
     inline featureset_ptr tile_datasource::features(query const& q) const
     {
         mapnik::filter_in_box filter(q.get_bbox());
-        return boost::make_shared<tile_featureset<mapnik::filter_in_box> >
-            (filter, q.property_names(), layer_, tile_x_, tile_y_, scale_);
+        return MAPNIK_MAKE_SHARED<tile_featureset<mapnik::filter_in_box> >
+            (filter, get_tile_extent(), q.property_names(), layer_, tile_x_, tile_y_, scale_);
     }
 
     inline featureset_ptr tile_datasource::features_at_point(coord2d const& pt, double tol) const
@@ -298,8 +330,8 @@ namespace mapnik { namespace vector {
         {
             names.insert(layer_.keys(i));
         }
-        return boost::make_shared<tile_featureset<filter_at_point> >
-            (filter, names, layer_, tile_x_, tile_y_, scale_);
+        return MAPNIK_MAKE_SHARED<tile_featureset<filter_at_point> >
+            (filter, get_tile_extent(), names, layer_, tile_x_, tile_y_, scale_);
     }
 
     inline void tile_datasource::set_envelope(box2d<double> const& bbox)
@@ -308,14 +340,19 @@ namespace mapnik { namespace vector {
         extent_ = bbox;
     }
 
+    inline box2d<double> tile_datasource::get_tile_extent() const
+    {
+        mapnik::vector::spherical_mercator merc(tile_size_);
+        double minx,miny,maxx,maxy;
+        merc.xyz(x_,y_,z_,minx,miny,maxx,maxy);
+        return box2d<double>(minx,miny,maxx,maxy);
+    }
+
     inline box2d<double> tile_datasource::envelope() const
     {
         if (!extent_initialized_)
         {
-            mapnik::vector::spherical_mercator merc(tile_size_);
-            double minx,miny,maxx,maxy;
-            merc.xyz(x_,y_,z_,minx,miny,maxx,maxy);
-            extent_.init(minx,miny,maxx,maxy);
+            extent_ = get_tile_extent();
             extent_initialized_ = true;
         }
         return extent_;
diff --git a/src/vector_tile_geometry_encoder.hpp b/src/vector_tile_geometry_encoder.hpp
new file mode 100644
index 0000000..67bbd32
--- /dev/null
+++ b/src/vector_tile_geometry_encoder.hpp
@@ -0,0 +1,191 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
+
+// vector tile
+#include "vector_tile.pb.h"
+#include <mapnik/vertex.hpp>
+#include <mapnik/version.hpp>
+
+namespace mapnik { namespace vector {
+
+inline void handle_skipped_last(tile_feature & current_feature,
+                                int32_t skipped_index,
+                                int32_t cur_x,
+                                int32_t cur_y,
+                                int32_t & x_,
+                                int32_t & y_)
+{
+    uint32_t last_x = current_feature.geometry(skipped_index - 2);
+    uint32_t last_y = current_feature.geometry(skipped_index - 1);
+    int32_t last_dx = ((last_x >> 1) ^ (-(last_x & 1)));
+    int32_t last_dy = ((last_y >> 1) ^ (-(last_y & 1)));
+    int32_t dx = cur_x - x_ + last_dx;
+    int32_t dy = cur_y - y_ + last_dy;
+    x_ = cur_x;
+    y_ = cur_y;
+    current_feature.set_geometry(skipped_index - 2, ((dx << 1) ^ (dx >> 31)));
+    current_feature.set_geometry(skipped_index - 1, ((dy << 1) ^ (dy >> 31)));
+}
+
+template <typename T>
+unsigned encode_geometry(T & path,
+                         tile_GeomType type,
+                         tile_feature & current_feature,
+                         int32_t & x_,
+                         int32_t & y_,
+                         unsigned tolerance,
+                         unsigned path_multiplier)
+{
+    unsigned count = 0;
+    path.rewind(0);
+    current_feature.set_type(type);
+
+    vertex2d vtx(vertex2d::no_init);
+    int cmd = -1;
+    int prev_cmd = -1;
+    int cmd_idx = -1;
+    const int cmd_bits = 3;
+    unsigned length = 0;
+    bool skipped_last = false;
+    int32_t skipped_index = -1;
+    int32_t cur_x = 0;
+    int32_t cur_y = 0;
+
+    // See vector_tile.proto for a description of how vertex command
+    // encoding works.
+
+    std::vector<vertex2d> output;
+    const std::size_t buffer_size = 8;
+    output.reserve(buffer_size);
+    bool done = false;
+    bool cache = true;
+    while (true)
+    {
+        if (cache)
+        {
+            // read
+            vertex2d v(vertex2d::no_init);
+            while ((v.cmd = path.vertex(&v.x, &v.y)) != SEG_END)
+            {
+#if MAPNIK_VERSION >= 300000
+                output.push_back(std::move(v));
+#else
+                output.push_back(v);
+#endif
+                if (output.size() == buffer_size) break;
+            }
+            cache = false;
+            if (v.cmd == SEG_END)
+            {
+                done = true;
+            }
+        }
+        else
+        {
+            if (done && output.empty()) break;
+            // process
+            vtx = output.front();
+            {
+                if (static_cast<int>(vtx.cmd) != cmd)
+                {
+                    if (cmd_idx >= 0)
+                    {
+                        // Encode the previous length/command value.
+                        current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
+                    }
+                    cmd = static_cast<int>(vtx.cmd);
+                    length = 0;
+                    cmd_idx = current_feature.geometry_size();
+                    current_feature.add_geometry(0); // placeholder added in first pass
+                }
+
+                switch (vtx.cmd)
+                {
+                case SEG_MOVETO:
+                case SEG_LINETO:
+                {
+                    if (cmd == SEG_MOVETO && skipped_last && skipped_index > 1) // at least one vertex + cmd/length
+                    {
+                        // if we skipped previous vertex we just update it to the last one here.
+                        handle_skipped_last(current_feature, skipped_index, cur_x, cur_y,  x_, y_);
+                    }
+
+                    // Compute delta to the previous coordinate.
+                    cur_x = static_cast<int32_t>(std::floor((vtx.x * path_multiplier) + 0.5));
+                    cur_y = static_cast<int32_t>(std::floor((vtx.y * path_multiplier) + 0.5));
+                    int32_t dx = cur_x - x_;
+                    int32_t dy = cur_y - y_;
+                    bool sharp_turn_ahead = false;
+                    if (output.size() > 1)
+                    {
+                        vertex2d const& next_vtx = output[1];
+                        if (next_vtx.cmd == SEG_LINETO)
+                        {
+                            uint32_t next_dx = std::abs(cur_x - static_cast<int32_t>(std::floor((next_vtx.x * path_multiplier) + 0.5)));
+                            uint32_t next_dy = std::abs(cur_y - static_cast<int32_t>(std::floor((next_vtx.y * path_multiplier) + 0.5)));
+                            if ((next_dx == 0 && next_dy >= tolerance) || (next_dy == 0 && next_dx >= tolerance))
+                            {
+                                sharp_turn_ahead = true;
+                            }
+                        }
+                    }
+                    // Keep all move_to commands, but omit other movements that are
+                    // not >= the tolerance threshold and should be considered no-ops.
+                    // NOTE: length == 0 indicates the command has changed and will
+                    // preserve any non duplicate move_to or line_to
+                    if ( length == 0 || sharp_turn_ahead ||
+                         (static_cast<unsigned>(std::abs(dx)) >= tolerance) ||
+                         (static_cast<unsigned>(std::abs(dy)) >= tolerance)
+                        )
+                    {
+                        // Manual zigzag encoding.
+                        current_feature.add_geometry((dx << 1) ^ (dx >> 31));
+                        current_feature.add_geometry((dy << 1) ^ (dy >> 31));
+                        x_ = cur_x;
+                        y_ = cur_y;
+                        skipped_last = false;
+                        ++length;
+                    }
+                    else
+                    {
+                        skipped_last = true;
+                        skipped_index = current_feature.geometry_size();
+                    }
+                    break;
+                }
+
+                case SEG_CLOSE:
+                {
+                    if (prev_cmd != SEG_CLOSE) ++length;
+                    break;
+                }
+                default:
+                    std::stringstream msg;
+                    msg << "Unknown command type (backend_pbf): "
+                        << cmd;
+                    throw std::runtime_error(msg.str());
+                    break;
+                }
+            }
+            ++count;
+            prev_cmd = cmd;
+            output.erase(output.begin());
+            if (output.size() < 2) cache = true;
+        }
+    }
+    if (skipped_last && skipped_index > 1) // at least one vertex + cmd/length
+    {
+        // if we skipped previous vertex we just update it to the last one here.
+        handle_skipped_last(current_feature, skipped_index, cur_x, cur_y, x_, y_);
+    }
+    // Update the last length/command value.
+    if (cmd_idx >= 0)
+    {
+        current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)));
+    }
+    return count;
+}
+
+}} // end ns
+
+#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
diff --git a/src/vector_tile_processor.hpp b/src/vector_tile_processor.hpp
index bdf0d9a..bb1f97e 100644
--- a/src/vector_tile_processor.hpp
+++ b/src/vector_tile_processor.hpp
@@ -18,6 +18,12 @@
 #include <mapnik/box2d.hpp>
 #include <mapnik/version.hpp>
 #include <mapnik/noncopyable.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/raster.hpp>
+#include <mapnik/warp.hpp>
+#include <mapnik/version.hpp>
+#include <mapnik/image_scaling.hpp>
+#include <mapnik/image_compositing.hpp>
 
 // agg
 #ifdef CONV_CLIPPER
@@ -28,6 +34,8 @@
 #endif
 
 #include "agg_conv_clip_polyline.h"
+#include "agg_rendering_buffer.h"
+#include "agg_pixfmt_rgba.h"
 
 #include <boost/foreach.hpp>
 #include <boost/optional.hpp>
@@ -37,6 +45,10 @@
 #include <string>
 #include <stdexcept>
 
+#include "mapnik3x_compatibility.hpp"
+#include MAPNIK_MAKE_SHARED_INCLUDE
+#include MAPNIK_SHARED_INCLUDE
+
 namespace mapnik { namespace vector {
 
 
@@ -61,6 +73,8 @@ namespace mapnik { namespace vector {
         double scale_factor_;
         mapnik::CoordTransform t_;
         unsigned tolerance_;
+        std::string image_format_;
+        scaling_method_e scaling_method_;
         bool painted_;
     public:
         processor(T & backend,
@@ -69,13 +83,18 @@ namespace mapnik { namespace vector {
                   double scale_factor=1.0,
                   unsigned offset_x=0,
                   unsigned offset_y=0,
-                  unsigned tolerance=1)
+                  unsigned tolerance=1,
+                  std::string const& image_format="jpeg",
+                  scaling_method_e scaling_method=SCALING_NEAR
+                  )
             : backend_(backend),
               m_(map),
               m_req_(m_req),
               scale_factor_(scale_factor),
               t_(m_req.width(),m_req.height(),m_req.extent(),offset_x,offset_y),
               tolerance_(tolerance),
+              image_format_(image_format),
+              scaling_method_(scaling_method),
               painted_(false) {}
 
         void apply(double scale_denom=0.0)
@@ -88,7 +107,6 @@ namespace mapnik { namespace vector {
             scale_denom *= scale_factor_;
             BOOST_FOREACH ( mapnik::layer const& lay, m_.layers() )
             {
-                backend_.start_tile_layer(lay.name());
                 if (lay.visible(scale_denom))
                 {
                     apply_to_layer(lay,
@@ -100,7 +118,6 @@ namespace mapnik { namespace vector {
                                    m_req_.extent(),
                                    m_req_.buffer_size());
                 }
-                backend_.stop_tile_layer();
             }
         }
 
@@ -218,27 +235,121 @@ namespace mapnik { namespace vector {
             {
                 return;
             }
-            mapnik::feature_ptr feature;
-            while ((feature = features->next()))
-            {
-                boost::ptr_vector<mapnik::geometry_type> & paths = feature->paths();
-                if (paths.empty()) continue;
-                backend_.start_tile_feature(*feature);
-                BOOST_FOREACH( mapnik::geometry_type & geom, paths)
+            mapnik::feature_ptr feature = features->next();
+            if (feature) {
+                backend_.start_tile_layer(lay.name());
+                raster_ptr const& source = feature->get_raster();
+                if (source)
                 {
-                    mapnik::box2d<double> geom_box = geom.envelope();
-                    if (!geom_box.intersects(buffered_query_ext))
+                    box2d<double> target_ext = box2d<double>(source->ext_);
+                    prj_trans.backward(target_ext, PROJ_ENVELOPE_POINTS);
+                    box2d<double> ext = t_.forward(target_ext);
+                    int start_x = static_cast<int>(std::floor(ext.minx()+.5));
+                    int start_y = static_cast<int>(std::floor(ext.miny()+.5));
+                    int end_x = static_cast<int>(std::floor(ext.maxx()+.5));
+                    int end_y = static_cast<int>(std::floor(ext.maxy()+.5));
+                    int raster_width = end_x - start_x;
+                    int raster_height = end_y - start_y;
+                    if (raster_width > 0 && raster_height > 0)
                     {
+                        #if MAPNIK_VERSION >= 300000
+                        raster target(target_ext, raster_width, raster_height, source->get_filter_factor());
+                        #else
+                        raster target(target_ext, raster_width, raster_height);
+                        #endif
+                        if (!source->premultiplied_alpha_)
+                        {
+                            agg::rendering_buffer buffer(source->data_.getBytes(),
+                                                         source->data_.width(),
+                                                         source->data_.height(),
+                                                         source->data_.width() * 4);
+                            agg::pixfmt_rgba32 pixf(buffer);
+                            pixf.premultiply();
+                        }
+                        if (!prj_trans.equal())
+                        {
+                            double offset_x = ext.minx() - start_x;
+                            double offset_y = ext.miny() - start_y;
+                            #if MAPNIK_VERSION >= 300000
+                            reproject_and_scale_raster(target, *source, prj_trans,
+                                             offset_x, offset_y,
+                                             width,
+                                             scaling_method_);
+                            #else
+                            reproject_and_scale_raster(target, *source, prj_trans,
+                                             offset_x, offset_y,
+                                             width,
+                                             2.0,
+                                             scaling_method_);
+                            #endif
+                        }
+                        else
+                        {
+                            double image_ratio_x = ext.width() / source->data_.width();
+                            double image_ratio_y = ext.height() / source->data_.height();
+                            #if MAPNIK_VERSION >= 300000
+                            scale_image_agg<image_data_32>(target.data_,
+                                                           source->data_,
+                                                           scaling_method_,
+                                                           image_ratio_x,
+                                                           image_ratio_y,
+                                                           0.0,
+                                                           0.0,
+                                                           source->get_filter_factor());
+                            #else
+                            scale_image_agg<image_data_32>(target.data_,
+                                                           source->data_,
+                                                           scaling_method_,
+                                                           image_ratio_x,
+                                                           image_ratio_y,
+                                                           0.0,
+                                                           0.0,
+                                                           2.0);
+                            #endif
+                        }
+                        mapnik::image_data_32 im_tile(width,height);
+                        composite(im_tile, target.data_,
+                                  src_over, 1,
+                                  start_x, start_y, false);
+                        agg::rendering_buffer buffer(im_tile.getBytes(),
+                                                     im_tile.width(),
+                                                     im_tile.height(),
+                                                     im_tile.width() * 4);
+                        agg::pixfmt_rgba32 pixf(buffer);
+                        pixf.demultiply();
+                        backend_.start_tile_feature(*feature);
+                        backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
+                    }
+                    backend_.stop_tile_layer();
+                    return;
+                }
+                // vector pathway
+                while (feature)
+                {
+                    boost::ptr_vector<mapnik::geometry_type> & paths = feature->paths();
+                    if (paths.empty()) {
+                        feature = features->next();
                         continue;
                     }
-                    if (handle_geometry(geom,
-                                        prj_trans,
-                                        buffered_query_ext) > 0)
+                    backend_.start_tile_feature(*feature);
+                    BOOST_FOREACH( mapnik::geometry_type & geom, paths)
                     {
-                        painted_ = true;
+                        mapnik::box2d<double> geom_box = geom.envelope();
+                        if (!geom_box.intersects(buffered_query_ext))
+                        {
+                            continue;
+                        }
+                        if (handle_geometry(geom,
+                                            prj_trans,
+                                            buffered_query_ext) > 0)
+                        {
+                            painted_ = true;
+                        }
                     }
+                    backend_.stop_tile_feature();
+                    feature = features->next();
                 }
-                backend_.stop_tile_feature();
+                backend_.stop_tile_layer();
             }
         }
 
@@ -249,7 +360,7 @@ namespace mapnik { namespace vector {
             unsigned path_count = 0;
             switch (geom.type())
             {
-            case mapnik::Point:
+            case MAPNIK_POINT:
             {
                 if (geom.size() > 0)
                 {
@@ -260,7 +371,7 @@ namespace mapnik { namespace vector {
                 }
                 break;
             }
-            case mapnik::LineString:
+            case MAPNIK_LINESTRING:
             {
                 if (geom.size() > 1)
                 {
@@ -277,7 +388,7 @@ namespace mapnik { namespace vector {
                 }
                 break;
             }
-            case mapnik::Polygon:
+            case MAPNIK_POLYGON:
             {
                 if (geom.size() > 2)
                 {
@@ -310,7 +421,7 @@ namespace mapnik { namespace vector {
                 }
                 break;
             }
-            case mapnik::Unknown:
+            case MAPNIK_UNKNOWN:
             default:
             {
                 throw std::runtime_error("unhandled geometry type");
diff --git a/src/vector_tile_util.hpp b/src/vector_tile_util.hpp
index 25611f7..abc3581 100644
--- a/src/vector_tile_util.hpp
+++ b/src/vector_tile_util.hpp
@@ -87,7 +87,10 @@ namespace mapnik { namespace vector {
                         }
                         else
                         {
-                            throw std::runtime_error("Unknown command type");
+                            std::stringstream msg;
+                            msg << "Unknown command type (is_solid_clipper): "
+                                << cmd;
+                            throw std::runtime_error(msg.str());
                         }
                     }
                 }
@@ -136,11 +139,11 @@ namespace mapnik { namespace vector {
             for (int j = 0; j < layer.features_size(); j++)
             {
                 mapnik::vector::tile_feature const& feature = layer.features(j);
-                mapnik::eGeomType g_type = static_cast<mapnik::eGeomType>(feature.type());
-                mapnik::geometry_type geom(g_type);
                 int cmd = -1;
                 const int cmd_bits = 3;
                 unsigned length = 0;
+                bool first = true;
+                mapnik::box2d<double> box;
                 int32_t x = 0, y = 0;
                 for (int k = 0; k < feature.geometry_size();)
                 {
@@ -149,10 +152,8 @@ namespace mapnik { namespace vector {
                         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++);
@@ -164,21 +165,31 @@ namespace mapnik { namespace vector {
                             if ((x > 0 && x < static_cast<int>(side)) && (y > 0 && y < static_cast<int>(side))) {
                                 return false;
                             }
-                            geom.push_vertex(x, y, static_cast<mapnik::CommandType>(cmd));
+                            if (first)
+                            {
+                                box.init(x,y,x,y);
+                                first = false;
+                            }
+                            else
+                            {
+                                box.expand_to_include(x,y);
+                            }
                         }
                         else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1)))
                         {
-                            geom.push_vertex(0, 0, mapnik::SEG_CLOSE);
+                            // pass
                         }
                         else
                         {
-                            throw std::runtime_error("Unknown command type");
+                            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.
-                mapnik::box2d<double> box = geom.envelope();
                 double geom_area = box.width() * box.height();
                 if (geom_area < (extent_area - 32) )
                 {
diff --git a/test/encoding_util.hpp b/test/encoding_util.hpp
new file mode 100644
index 0000000..ad69bda
--- /dev/null
+++ b/test/encoding_util.hpp
@@ -0,0 +1,86 @@
+#include <mapnik/vertex.hpp>
+#include <mapnik/geometry.hpp>
+#include "vector_tile_geometry_encoder.hpp"
+
+void decode_geometry(mapnik::vector::tile_feature const& f,
+                     mapnik::geometry_type & geom,
+                     double & x,
+                     double & y,
+                     double scale)
+{
+    int cmd = -1;
+    const int cmd_bits = 3;
+    unsigned length = 0;
+    for (int k = 0; k < f.geometry_size();)
+    {
+        if (!length) {
+            unsigned cmd_length = f.geometry(k++);
+            cmd = cmd_length & ((1 << cmd_bits) - 1);
+            length = cmd_length >> cmd_bits;
+        }
+        if (length > 0) {
+            length--;
+            if (cmd == mapnik::SEG_MOVETO || cmd == mapnik::SEG_LINETO)
+            {
+                int32_t dx = f.geometry(k++);
+                int32_t dy = f.geometry(k++);
+                dx = ((dx >> 1) ^ (-(dx & 1)));
+                dy = ((dy >> 1) ^ (-(dy & 1)));
+                x += (static_cast<double>(dx) / scale);
+                y += (static_cast<double>(dy) / scale);
+                geom.push_vertex(x, y, static_cast<mapnik::CommandType>(cmd));
+            }
+            else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1)))
+            {
+                geom.push_vertex(0, 0, mapnik::SEG_CLOSE);
+            }
+            else
+            {
+                std::stringstream msg;
+                msg << "Unknown command type (decode_geometry): "
+                    << cmd;
+                throw std::runtime_error(msg.str());
+            }
+        }
+    }
+}
+
+template <typename T>
+std::string show_path(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";
+    }
+    return s.str();
+}
+
+std::string compare(mapnik::geometry_type const & g,
+                    unsigned tolerance=0,
+                    unsigned path_multiplier=1)
+{
+    using namespace mapnik::vector;
+    // encode
+    tile_feature feature;
+    int32_t x = 0;
+    int32_t y = 0;
+    encode_geometry(g,(tile_GeomType)g.type(),feature,x,y,tolerance,path_multiplier);
+    // decode
+    mapnik::geometry_type g2(MAPNIK_POLYGON);
+    double x0 = 0;
+    double y0 = 0;
+    decode_geometry(feature,g2,x0,y0,path_multiplier);
+    return show_path(g2);
+}
diff --git a/test/geometry_encoding.cpp b/test/geometry_encoding.cpp
new file mode 100644
index 0000000..1533e49
--- /dev/null
+++ b/test/geometry_encoding.cpp
@@ -0,0 +1,338 @@
+// https://github.com/philsquared/Catch/wiki/Supplying-your-own-main()
+// http://www.levelofindirection.com/journal/2013/6/28/catch-10.html
+#define CATCH_CONFIG_RUNNER
+#include "catch.hpp"
+
+#include "mapnik3x_compatibility.hpp"
+#include "encoding_util.hpp"
+
+// https://github.com/mapbox/mapnik-vector-tile/issues/36
+
+TEST_CASE( "test 1", "should round trip without changes" ) {
+    mapnik::geometry_type g(MAPNIK_POLYGON);
+    g.move_to(0,0);
+    g.line_to(1,1);
+    g.line_to(100,100);
+    g.close_path();
+    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( "test 2", "should drop coincident line_to moves" ) {
+    mapnik::geometry_type g(MAPNIK_LINESTRING);
+    g.move_to(0,0);
+    g.line_to(3,3);
+    g.line_to(3,3);
+    g.line_to(3,3);
+    g.line_to(3,3);
+    g.line_to(4,4);
+    std::string expected(
+    "move_to(0,0)\n"
+    "line_to(3,3)\n"
+    "line_to(4,4)\n"
+    );
+    CHECK(compare(g,1) == expected);
+}
+
+TEST_CASE( "test 2b", "should drop vertices" ) {
+    mapnik::geometry_type g(MAPNIK_LINESTRING);
+    g.move_to(0,0);
+    g.line_to(0,0);
+    g.line_to(1,1);
+    std::string expected(
+    "move_to(0,0)\n"
+    "line_to(0,0)\n" // TODO - should we try to drop this?
+    "line_to(1,1)\n"
+    );
+    CHECK(compare(g,1) == expected);
+}
+
+TEST_CASE( "test 3", "should not drop first move_to or last vertex in line" ) {
+    mapnik::geometry_type g(MAPNIK_LINESTRING);
+    g.move_to(0,0);
+    g.line_to(1,1);
+    g.move_to(0,0);
+    g.line_to(1,1);
+    std::string expected(
+    "move_to(0,0)\n"
+    "line_to(1,1)\n"
+    "move_to(0,0)\n"
+    "line_to(1,1)\n"
+    );
+    CHECK(compare(g,1000) == expected);
+}
+
+TEST_CASE( "test 4", "should not drop first move_to or last vertex in polygon" ) {
+    mapnik::geometry_type g(MAPNIK_POLYGON);
+    g.move_to(0,0);
+    g.line_to(1,1);
+    g.move_to(0,0);
+    g.line_to(1,1);
+    g.close_path();
+    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_type g(MAPNIK_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_type g(MAPNIK_LINESTRING);
+    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_type g(MAPNIK_LINESTRING);
+    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_type g(MAPNIK_LINESTRING);
+    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_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_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_type g(MAPNIK_LINESTRING);
+    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_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_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_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_feature feature0;
+    int32_t x = 0;
+    int32_t y = 0;
+    unsigned path_multiplier = 1;
+    unsigned tolerance = 10000;
+    mapnik::geometry_type g0(MAPNIK_POLYGON);
+    g0.move_to(0,0);
+    g0.line_to(-10,-10);
+    g0.line_to(-20,-20);
+    g0.close_path();
+    encode_geometry(g0,(tile_GeomType)g0.type(),feature0,x,y,tolerance,path_multiplier);
+    CHECK(x == -20);
+    CHECK(y == -20);
+    mapnik::geometry_type g1(MAPNIK_POLYGON);
+    g1.move_to(1000,1000);
+    g1.line_to(1010,1010);
+    g1.line_to(1020,1020);
+    g1.close_path();
+    encode_geometry(g1,(tile_GeomType)g1.type(),feature0,x,y,tolerance,path_multiplier);
+    CHECK(x == 1020);
+    CHECK(y == 1020);
+    mapnik::geometry_type g2(MAPNIK_POLYGON);
+    double x0 = 0;
+    double y0 = 0;
+    decode_geometry(feature0,g2,x0,y0,path_multiplier);
+    std::string actual = show_path(g2);
+    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_feature feature0;
+    int32_t x = 0;
+    int32_t y = 0;
+    unsigned path_multiplier = 1;
+    unsigned tolerance = 10;
+    mapnik::geometry_type g(MAPNIK_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();
+    encode_geometry(g,(tile_GeomType)g.type(),feature0,x,y,tolerance,path_multiplier);
+
+    mapnik::geometry_type g2(MAPNIK_POLYGON);
+    double x0 = 0;
+    double y0 = 0;
+    decode_geometry(feature0,g2,x0,y0,path_multiplier);
+    std::string actual = show_path(g2);
+    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);
+}
+
+int main (int argc, char* const argv[])
+{
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+    int result = Catch::Session().run( argc, argv );
+    if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n");
+    google::protobuf::ShutdownProtobufLibrary();
+    return result;
+}
diff --git a/test/raster_style.xml b/test/raster_style.xml
new file mode 100644
index 0000000..36e34e4
--- /dev/null
+++ b/test/raster_style.xml
@@ -0,0 +1,7 @@
+<Map>
+<Style name="style">
+    <Rule>
+        <RasterSymbolizer />
+    </Rule>
+</Style>
+</Map>
\ No newline at end of file
diff --git a/test/raster_tile.cpp b/test/raster_tile.cpp
new file mode 100644
index 0000000..47e3bbb
--- /dev/null
+++ b/test/raster_tile.cpp
@@ -0,0 +1,125 @@
+// https://github.com/philsquared/Catch/wiki/Supplying-your-own-main()
+#define CATCH_CONFIG_RUNNER
+#include "catch.hpp"
+
+// test utils
+#include "test_utils.hpp"
+#include "test-cfg.h"
+
+#include "vector_tile_projection.hpp"
+
+// 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 <mapnik/graphics.hpp>
+#include <mapnik/datasource_cache.hpp>
+
+TEST_CASE( "vector tile output 1", "should create vector tile with one point" ) {
+    mapnik::datasource_cache::instance().register_datasources(MAPNIK_PLUGINDIR);
+    typedef mapnik::vector::backend_pbf backend_type;
+    typedef mapnik::vector::processor<backend_type> renderer_type;
+    typedef mapnik::vector::tile tile_type;
+    unsigned _x=0,_y=0,_z=1;
+    double minx,miny,maxx,maxy;
+    mapnik::vector::spherical_mercator merc(512);
+    merc.xyz(_x,_y,_z,minx,miny,maxx,maxy);
+    mapnik::box2d<double> bbox;
+    bbox.init(minx,miny,maxx,maxy);
+    unsigned tile_size = 512;
+    tile_type tile;
+    backend_type backend(tile,16);
+    mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+    map.set_buffer_size(256);
+    mapnik::layer lyr("layer",map.srs());
+    mapnik::parameters params;
+    params["type"] = "gdal";
+    // created with:
+    // wget http://www.nacis.org/naturalearth/50m/raster/NE2_50m_SR_W.zip
+    // gdalwarp -t_srs EPSG:3857 -ts 1048 1048 -r bilinear NE2_50M_SR_W.tif natural_earth.tif
+    params["file"] = "test/natural_earth.tif";
+    MAPNIK_SHARED_PTR<mapnik::datasource> ds =
+        mapnik::datasource_cache::instance().create(params);
+    lyr.set_datasource(ds);
+    map.MAPNIK_ADD_LAYER(lyr);
+    mapnik::request m_req(tile_size,tile_size,bbox);
+    m_req.set_buffer_size(map.buffer_size());
+    renderer_type ren(backend,map,m_req,1.0,0,0,1,"jpeg",mapnik::SCALING_BILINEAR);
+    ren.apply();
+    CHECK(1 == tile.layers_size());
+    mapnik::vector::tile_layer const& layer = tile.layers(0);
+    CHECK(std::string("layer") == layer.name());
+    CHECK(1 == layer.features_size());
+    mapnik::vector::tile_feature const& f = layer.features(0);
+    CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f.id()));
+    CHECK(0 == f.geometry_size());
+    CHECK(f.has_raster());
+    std::string const& ras_buffer = f.raster();
+    CHECK(!ras_buffer.empty());
+    // debug
+    bool debug = false;
+    if (debug) {
+        std::ofstream file("out.jpeg", std::ios::out|std::ios::trunc|std::ios::binary);
+        file << ras_buffer;
+        file.close();
+    }
+
+    std::size_t expected_image_size = 45660;
+    int 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) {
+        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;
+    CHECK(tile2.ParseFromString(buffer));
+    CHECK(1 == tile2.layers_size());
+    mapnik::vector::tile_layer const& layer2 = tile2.layers(0);
+    CHECK(std::string("layer") == layer2.name());
+    CHECK(1 == layer2.features_size());
+    mapnik::vector::tile_feature const& f2 = layer2.features(0);
+    CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f2.id()));
+    CHECK(0 == f2.geometry_size());
+    CHECK(f2.has_raster());
+    CHECK(!f2.raster().empty());
+    if (!debug) {
+        CHECK(expected_image_size == f2.raster().size());
+    }
+    mapnik::layer lyr2("layer",map2.srs());
+    MAPNIK_SHARED_PTR<mapnik::vector::tile_datasource> ds2 = MAPNIK_MAKE_SHARED<
+                                    mapnik::vector::tile_datasource>(
+                                        layer2,_x,_y,_z,map2.width());
+    ds2->set_envelope(bbox);
+    lyr2.set_datasource(ds2);
+    lyr2.add_style("style");
+    map2.MAPNIK_ADD_LAYER(lyr2);
+    mapnik::load_map(map2,"test/raster_style.xml");
+    map2.zoom_to_box(bbox);
+    mapnik::image_32 im(map2.width(),map2.height());
+    mapnik::agg_renderer<mapnik::image_32> ren2(map2,im);
+    ren2.apply();
+    if (debug) {
+        mapnik::save_to_file(im,"image.png");
+    }
+    unsigned rgba = im.data()(128,128);
+    CHECK(rgba != 0);
+}
+
+int main (int argc, char* const argv[])
+{
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+    int result = Catch::Session().run( argc, argv );
+    if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n");
+    google::protobuf::ShutdownProtobufLibrary();
+    return result;
+}
diff --git a/test/style.xml b/test/style.xml
new file mode 100644
index 0000000..0b403c3
--- /dev/null
+++ b/test/style.xml
@@ -0,0 +1,7 @@
+<Map>
+<Style name="style">
+    <Rule>
+        <MarkersSymbolizer fill="red" />
+    </Rule>
+</Style>
+</Map>
\ No newline at end of file
diff --git a/test/test_utils.hpp b/test/test_utils.hpp
index 55f680a..3e91716 100644
--- a/test/test_utils.hpp
+++ b/test/test_utils.hpp
@@ -1,10 +1,7 @@
 // mapnik
+#include "mapnik3x_compatibility.hpp"
 #include <mapnik/map.hpp>
 #include <mapnik/layer.hpp>
-#include <mapnik/rule.hpp>
-#include <mapnik/feature_type_style.hpp>
-#include <mapnik/rule.hpp>
-#include <mapnik/markers_symbolizer.hpp>
 #include <mapnik/image_util.hpp>
 #include <mapnik/graphics.hpp>
 #include <mapnik/agg_renderer.hpp>
@@ -15,25 +12,25 @@
 #include <mapnik/unicode.hpp>
 #include <mapnik/geometry.hpp>
 #include <mapnik/datasource.hpp>
+#include <mapnik/load_map.hpp>
 #include <mapnik/memory_datasource.hpp>
 
 // boost
-#include <boost/make_shared.hpp>
-#include <boost/shared_ptr.hpp>
+#include MAPNIK_SHARED_INCLUDE
+#include MAPNIK_MAKE_SHARED_INCLUDE
 
 #include <string>
 
-boost::shared_ptr<mapnik::memory_datasource> build_ds() {
-    mapnik::context_ptr ctx = boost::make_shared<mapnik::context_type>();
+MAPNIK_SHARED_PTR<mapnik::memory_datasource> build_ds(double x,double y) {
+    mapnik::context_ptr ctx = MAPNIK_MAKE_SHARED<mapnik::context_type>();
     ctx->push("name");
     mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
     mapnik::transcoder tr("utf-8");
-    UnicodeString ustr = tr.transcode("null island");
-    feature->put("name",ustr);
-    mapnik::geometry_type * pt = new mapnik::geometry_type(mapnik::Point);
-    pt->move_to(0,0);
+    feature->put("name",tr.transcode("null island"));
+    mapnik::geometry_type * pt = new mapnik::geometry_type(MAPNIK_POINT);
+    pt->move_to(x,y);
     feature->add_geometry(pt);
-    boost::shared_ptr<mapnik::memory_datasource> ds = boost::make_shared<mapnik::memory_datasource>();
+    MAPNIK_SHARED_PTR<mapnik::memory_datasource> ds = MAPNIK_MAKE_SHARED<mapnik::memory_datasource>();
     ds->push(feature);
     return ds;
 }
diff --git a/test/vector_tile.cpp b/test/vector_tile.cpp
index 8b4d8a6..4ea681e 100644
--- a/test/vector_tile.cpp
+++ b/test/vector_tile.cpp
@@ -7,21 +7,22 @@
 
 #include "vector_tile_projection.hpp"
 
-const unsigned x=0,y=0,z=0;
+const unsigned _x=0,_y=0,_z=0;
 const unsigned tile_size = 256;
 mapnik::box2d<double> bbox;
 
 // vector output api
 #include "vector_tile_processor.hpp"
 #include "vector_tile_backend_pbf.hpp"
+#include "vector_tile_util.hpp"
+
+// vector input api
+#include "vector_tile_datasource.hpp"
 
 TEST_CASE( "vector tile projection 1", "should support z/x/y to bbox conversion at 0/0/0" ) {
     mapnik::vector::spherical_mercator merc(256);
-    int x = 0;
-    int y = 0;
-    int z = 0;
     double minx,miny,maxx,maxy;
-    merc.xyz(x,y,z,minx,miny,maxx,maxy);
+    merc.xyz(_x,_y,_z,minx,miny,maxx,maxy);
     mapnik::box2d<double> map_extent(minx,miny,maxx,maxy);
     mapnik::box2d<double> e(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
     double epsilon = 0.000001;
@@ -47,7 +48,7 @@ TEST_CASE( "vector tile projection 2", "should support z/x/y to bbox conversion
     CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon);
 }
 
-TEST_CASE( "vector tile output", "should create vector tile with one point" ) {
+TEST_CASE( "vector tile output 1", "should create vector tile with one point" ) {
     typedef mapnik::vector::backend_pbf backend_type;
     typedef mapnik::vector::processor<backend_type> renderer_type;
     typedef mapnik::vector::tile tile_type;
@@ -55,8 +56,8 @@ TEST_CASE( "vector tile output", "should create vector tile with one point" ) {
     backend_type backend(tile,16);
     mapnik::Map map(tile_size,tile_size);
     mapnik::layer lyr("layer");
-    lyr.set_datasource(build_ds());
-    map.addLayer(lyr);
+    lyr.set_datasource(build_ds(0,0));
+    map.MAPNIK_ADD_LAYER(lyr);
     mapnik::request m_req(tile_size,tile_size,bbox);
     renderer_type ren(backend,map,m_req);
     ren.apply();
@@ -76,9 +77,67 @@ TEST_CASE( "vector tile output", "should create vector tile with one point" ) {
     CHECK(52 == buffer.size());
 }
 
-// vector input api
-#include "vector_tile_datasource.hpp"
-#include "vector_tile_backend_pbf.hpp"
+TEST_CASE( "vector tile output 2", "adding empty layers should result in empty tile" ) {
+    typedef mapnik::vector::backend_pbf backend_type;
+    typedef mapnik::vector::processor<backend_type> renderer_type;
+    typedef mapnik::vector::tile tile_type;
+    tile_type tile;
+    backend_type backend(tile,16);
+    mapnik::Map map(tile_size,tile_size);
+    map.MAPNIK_ADD_LAYER(mapnik::layer("layer"));
+    map.zoom_to_box(bbox);
+    mapnik::request m_req(tile_size,tile_size,bbox);
+    renderer_type ren(backend,map,m_req);
+    ren.apply();
+    CHECK(0 == tile.layers_size());
+}
+
+TEST_CASE( "vector tile output 3", "adding layers with geometries outside rendering extent should not add layer" ) {
+    typedef mapnik::vector::backend_pbf backend_type;
+    typedef mapnik::vector::processor<backend_type> renderer_type;
+    typedef mapnik::vector::tile tile_type;
+    tile_type tile;
+    backend_type backend(tile,16);
+    mapnik::Map map(tile_size,tile_size);
+    mapnik::layer lyr("layer");
+    mapnik::context_ptr ctx = MAPNIK_MAKE_SHARED<mapnik::context_type>();
+    mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+    MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_LINESTRING));
+    g->move_to(0,0);
+    g->line_to(1,1);
+    feature->add_geometry(g.release());
+    MAPNIK_SHARED_PTR<mapnik::memory_datasource> ds = MAPNIK_MAKE_SHARED<mapnik::memory_datasource>();
+    ds->push(feature);
+    lyr.set_datasource(ds);
+    map.MAPNIK_ADD_LAYER(lyr);
+    map.zoom_to_box(bbox);
+    mapnik::request m_req(tile_size,tile_size,bbox);
+    renderer_type ren(backend,map,m_req);
+    ren.apply();
+    CHECK(1 == tile.layers_size());
+}
+
+TEST_CASE( "vector tile output 4", "adding layers with degenerate geometries should not add layer" ) {
+    typedef mapnik::vector::backend_pbf backend_type;
+    typedef mapnik::vector::processor<backend_type> renderer_type;
+    typedef mapnik::vector::tile tile_type;
+    tile_type tile;
+    backend_type backend(tile,16);
+    mapnik::Map map(tile_size,tile_size);
+    mapnik::layer lyr("layer");
+    // create a datasource with a feature outside the map
+    MAPNIK_SHARED_PTR<mapnik::memory_datasource> ds = build_ds(bbox.minx()-1,bbox.miny()-1);
+    // but fake the overall envelope to ensure the layer is still processed
+    // and then removed given no intersecting features will be added
+    ds->set_envelope(bbox);
+    lyr.set_datasource(ds);
+    map.MAPNIK_ADD_LAYER(lyr);
+    map.zoom_to_box(bbox);
+    mapnik::request m_req(tile_size,tile_size,bbox);
+    renderer_type ren(backend,map,m_req);
+    ren.apply();
+    CHECK(0 == tile.layers_size());
+}
 
 TEST_CASE( "vector tile input", "should be able to parse message and render point" ) {
     typedef mapnik::vector::backend_pbf backend_type;
@@ -88,8 +147,8 @@ TEST_CASE( "vector tile input", "should be able to parse message and render poin
     backend_type backend(tile,16);
     mapnik::Map map(tile_size,tile_size);
     mapnik::layer lyr("layer");
-    lyr.set_datasource(build_ds());
-    map.addLayer(lyr);
+    lyr.set_datasource(build_ds(0,0));
+    map.MAPNIK_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);
@@ -106,9 +165,9 @@ TEST_CASE( "vector tile input", "should be able to parse message and render poin
     mapnik::vector::tile_layer const& layer2 = tile2.layers(0);
     CHECK(std::string("layer") == layer2.name());
     mapnik::layer lyr2("layer");
-    boost::shared_ptr<mapnik::vector::tile_datasource> ds = boost::make_shared<
+    MAPNIK_SHARED_PTR<mapnik::vector::tile_datasource> ds = MAPNIK_MAKE_SHARED<
                                     mapnik::vector::tile_datasource>(
-                                        layer2,x,y,z,map2.width());
+                                        layer2,_x,_y,_z,map2.width());
     ds->set_envelope(bbox);
     mapnik::layer_descriptor lay_desc = ds->get_descriptor();
     std::vector<std::string> expected_names;
@@ -120,22 +179,14 @@ TEST_CASE( "vector tile input", "should be able to parse message and render poin
     }
     CHECK(names == expected_names);
     lyr2.set_datasource(ds);
-    lyr2.add_style("style");
-    map2.addLayer(lyr2);
-    mapnik::feature_type_style s;
-    mapnik::rule r;
-    mapnik::color red(255,0,0);
-    mapnik::markers_symbolizer sym;
-    sym.set_fill(red);
-    r.append(sym);
-    s.add_rule(r);
-    map2.insert_style("style",s);
+    map2.MAPNIK_ADD_LAYER(lyr2);
+    mapnik::load_map(map2,"test/style.xml");
     map2.zoom_to_box(bbox);
     mapnik::image_32 im(map2.width(),map2.height());
     mapnik::agg_renderer<mapnik::image_32> ren2(map2,im);
     ren2.apply();
     unsigned rgba = im.data()(128,128);
-    CHECK(red.rgba() == rgba);
+    CHECK(0 == rgba);
     //mapnik::save_to_file(im,"test.png");
 }
 
@@ -147,8 +198,8 @@ TEST_CASE( "vector tile datasource", "should filter features outside extent" ) {
     backend_type backend(tile,16);
     mapnik::Map map(tile_size,tile_size);
     mapnik::layer lyr("layer");
-    lyr.set_datasource(build_ds());
-    map.addLayer(lyr);
+    lyr.set_datasource(build_ds(0,0));
+    map.MAPNIK_ADD_LAYER(lyr);
     mapnik::request m_req(tile_size,tile_size,bbox);
     renderer_type ren(backend,map,m_req);
     ren.apply();
@@ -163,7 +214,7 @@ TEST_CASE( "vector tile datasource", "should filter features outside extent" ) {
     CHECK(4096 == f.geometry(1));
     CHECK(4096 == f.geometry(2));
     // now actually start the meat of the test
-    mapnik::vector::tile_datasource ds(layer,x,y,z,tile_size);
+    mapnik::vector::tile_datasource ds(layer,_x,_y,_z,tile_size);
     mapnik::featureset_ptr fs;
 
     // ensure we can query single feature
@@ -211,13 +262,159 @@ TEST_CASE( "vector tile datasource", "should filter features outside extent" ) {
     CHECK(f_ptr->context()->size() == 1);
 }
 
+// NOTE: encoding a multiple lines as one path is technically incorrect
+// because in Mapnik the protocol is to split geometry parts into separate paths
+// however this case should still be supported in error and its an optimization in the
+// case where you know that lines do not need to be labeled in custom ways.
+TEST_CASE( "encoding multi line as one path", "should maintain second move_to command" ) {
+    // Options
+    // here we use a multiplier of 1 to avoid rounding numbers
+    // and stay in integer space for simplity
+    unsigned path_multiplier = 1;
+    // here we use an extreme tolerance to prove tht all vertices are maintained no matter
+    // the tolerance because we never want to drop a move_to or the first line_to
+    unsigned tolerance = 2000000;
+    // now create the testing data
+    mapnik::vector::tile tile;
+    mapnik::vector::backend_pbf backend(tile,path_multiplier);
+    backend.start_tile_layer("layer");
+    mapnik::feature_ptr feature(mapnik::feature_factory::create(MAPNIK_MAKE_SHARED<mapnik::context_type>(),1));
+    backend.start_tile_feature(*feature);
+    MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_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->move_to(1,1);        // takes 3 geoms: command length,x,y
+    g->line_to(2,2);        // new command, so again takes 3 geoms: command length,x,y | total 6
+    backend.add_path(*g, tolerance, g->type());
+    backend.stop_tile_feature();
+    backend.stop_tile_layer();
+    // done encoding single feature/geometry
+    CHECK(1 == tile.layers_size());
+    mapnik::vector::tile_layer const& layer = tile.layers(0);
+    CHECK(1 == layer.features_size());
+    mapnik::vector::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
+}
+
+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
+    mapnik::vector::tile tile;
+    mapnik::vector::backend_pbf backend(tile,path_multiplier);
+    backend.start_tile_layer("layer");
+    mapnik::feature_ptr feature(mapnik::feature_factory::create(MAPNIK_MAKE_SHARED<mapnik::context_type>(),1));
+    backend.start_tile_feature(*feature);
+    MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_LINESTRING));
+    g->move_to(0,0);        // takes 3 geoms: command length,x,y
+    g->line_to(2,2);        // new command, so again takes 3 geoms: command length,x,y | total 6
+    g->line_to(1000,1000);  // repeated line_to, so only takes 2 geoms: x,y | total 8
+    g->line_to(1001,1001);  // should skip given tolerance of 2 | total 8
+    g->line_to(1001,1001);  // should not skip given it is the endpoint, added 2 geoms | total 10
+    backend.add_path(*g, tolerance, g->type());
+    backend.stop_tile_feature();
+    backend.stop_tile_layer();
+    // done encoding single feature/geometry
+    CHECK(1 == tile.layers_size());
+    mapnik::vector::tile_layer const& layer = tile.layers(0);
+    CHECK(1 == layer.features_size());
+    mapnik::vector::tile_feature const& f = layer.features(0);
+    // sequence of 10 geometries given tolerance of 2
+    CHECK(8 == 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(18 == line_value);
+    unsigned line_cmd = line_value & ((1 << 3) - 1);
+    CHECK(2 == line_cmd);
+    unsigned line_length = line_value >> 3;
+    CHECK(2 == 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 = (2 << 1) ^ (2 >> 31);
+    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(1998 == f.geometry(6));
+    CHECK(1998 == f.geometry(7));
+}
+
+// testcase for avoiding error in is_solid_extent of
+// "Unknown command type (is_solid_extent): 0"
+// not yet clear if this test is correct
+// ported from shapefile test in tilelive-bridge (a:should render a (1.0.1))
+TEST_CASE( "encoding single line 2", "should maintain start/end vertex" ) {
+    unsigned path_multiplier = 16;
+    unsigned tolerance = 5;
+    mapnik::vector::tile tile;
+    mapnik::vector::backend_pbf backend(tile,path_multiplier);
+    backend.start_tile_layer("layer");
+    mapnik::feature_ptr feature(mapnik::feature_factory::create(MAPNIK_MAKE_SHARED<mapnik::context_type>(),1));
+    backend.start_tile_feature(*feature);
+    MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_POLYGON));
+    g->move_to(168.267850,-24.576888);
+    g->line_to(167.982618,-24.697145);
+    g->line_to(168.114561,-24.783548);
+    g->line_to(168.267850,-24.576888);
+    g->line_to(168.267850,-24.576888);
+    g->close_path();
+    //g->push_vertex(256.000000,-0.00000, mapnik::SEG_CLOSE);
+    // todo - why does shape_io result in on-zero close path x,y?
+    backend.add_path(*g, tolerance, g->type());
+    backend.stop_tile_feature();
+    backend.stop_tile_layer();
+    std::string key("test");
+    is_solid_extent(tile,key); // should not throw!
+    CHECK(1 == tile.layers_size());
+    mapnik::vector::tile_layer const& layer = tile.layers(0);
+    CHECK(1 == layer.features_size());
+    mapnik::vector::tile_feature const& f = layer.features(0);
+    CHECK(7 == f.geometry_size());
+}
+
 int main (int argc, char* const argv[])
 {
     GOOGLE_PROTOBUF_VERIFY_VERSION;
     // set up bbox
     double minx,miny,maxx,maxy;
     mapnik::vector::spherical_mercator merc(256);
-    merc.xyz(x,y,z,minx,miny,maxx,maxy);
+    merc.xyz(_x,_y,_z,minx,miny,maxx,maxy);
     bbox.init(minx,miny,maxx,maxy);
     int result = Catch::Session().run( argc, argv );
     if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n");

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