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

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

commit 598ff118aa6753d6c83ebd2cb2d3e6b270a6abac
Author: Bas Couwenberg <sebas...@xs4all.nl>
Date:   Sat Oct 31 10:35:44 2015 +0100

    Imported Upstream version 0.14.0+dfsg
---
 .gitmodules                                   |   3 +
 CHANGELOG.md                                  |   7 +
 Makefile                                      |   5 +-
 package.json                                  |   2 +-
 src/vector_tile_geometry_decoder.hpp          |  10 +-
 src/vector_tile_processor.hpp                 |  23 +++
 src/vector_tile_processor.ipp                 | 247 ++++++++++++++++++------
 test/data/0.0.0.vector-b.pbf                  | Bin 0 -> 2774 bytes
 test/data/0.0.0.vector.pbf                    | Bin 0 -> 3812 bytes
 test/{encoding_util.hpp => encoding_util.cpp} |   0
 test/encoding_util.hpp                        | 118 +-----------
 test/fixtures/rasterize-expected-1.png        | Bin 0 -> 12645 bytes
 test/geometry_visual_test.cpp                 | 267 ++++++++++++++++++++++++++
 test/test_main.cpp                            |   2 +-
 test/vector_tile.cpp                          | 176 ++++++++++++++---
 test/vector_tile_projection.cpp               | 117 +++++++++++
 test/vector_tile_rasterize.cpp                | 179 +++++++++++++++++
 17 files changed, 952 insertions(+), 204 deletions(-)

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..5f8023a
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "test/geometry-test-data"]
+       path = test/geometry-test-data
+       url = https://github.com/mapnik/geometry-test-data.git
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8a1eba..b22d75e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## 0.14.0
+
+ - Added the ability for the processor to ignore invalid rings and simply 
process them, this is exposed via the option `process_all_rings`.
+ - Exposed the ability for different fill types to be used
+ - Added the ability for multipolygons to be union or not selectively, exposed 
as option `multipoly_polyon_union`
+ - Added new test suite for geometries
+
 ## 0.13.0
 
  - Updated the geometry decoder so that it now supports a variety of geometry 
formats with the ability to return mapnik 
diff --git a/Makefile b/Makefile
index e49af36..9e47cc0 100755
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,10 @@ build/Makefile: ./deps/gyp ./deps/clipper ./deps/protozero 
gyp/build.gyp test/*c
 libvtile: build/Makefile Makefile
        @$(MAKE) -C build/ BUILDTYPE=$(BUILDTYPE) V=$(V)
 
-test: libvtile
+test/geometry-test-data:
+       git submodule update --init
+
+test: libvtile test/geometry-test-data
        ./build/$(BUILDTYPE)/tests
 
 testpack:
diff --git a/package.json b/package.json
index df39880..41ec507 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
     "name": "mapnik-vector-tile",
-    "version": "0.13.0",
+    "version": "0.14.0",
     "description": "Mapnik vector tile API",
     "main": "./package.json",
     "repository"   :  {
diff --git a/src/vector_tile_geometry_decoder.hpp 
b/src/vector_tile_geometry_decoder.hpp
index 09d2c77..e934ca4 100644
--- a/src/vector_tile_geometry_decoder.hpp
+++ b/src/vector_tile_geometry_decoder.hpp
@@ -423,7 +423,10 @@ void decode_polygons(mapnik::geometry::geometry<ValueType> 
& geom, T && rings)
     }
     else if (num_rings == 1)
     {
-        if (rings_itr->size() < 4) return;
+        if (rings_itr->size() < 4)
+        {
+            return;
+        }
         if (mapnik::util::is_clockwise(*rings_itr))
         {
             // Its clockwise, so lets reverse it.
@@ -441,7 +444,10 @@ void decode_polygons(mapnik::geometry::geometry<ValueType> 
& geom, T && rings)
         bool is_clockwise = true;
         for (; rings_itr != rings_end; ++rings_itr)
         {
-            if (rings_itr->size() < 4) continue; // skip degenerate rings
+            if (rings_itr->size() < 4)
+            {
+                continue; // skip degenerate rings
+            }
             if (first)
             {
                 is_clockwise = mapnik::util::is_clockwise(*rings_itr);
diff --git a/src/vector_tile_processor.hpp b/src/vector_tile_processor.hpp
index 70fe9e0..21442ca 100644
--- a/src/vector_tile_processor.hpp
+++ b/src/vector_tile_processor.hpp
@@ -10,11 +10,19 @@
 #include <mapnik/image_scaling.hpp>
 #include <mapnik/image_compositing.hpp>
 #include <mapnik/geometry.hpp>
+#include "clipper.hpp"
 
 #include "vector_tile_config.hpp"
 
 namespace mapnik { namespace vector_tile_impl {
 
+enum polygon_fill_type : std::uint8_t {
+    even_odd_fill = 0, 
+    non_zero_fill, 
+    positive_fill, 
+    negative_fill,
+    polygon_fill_type_max
+};
 
 /*
   This processor combines concepts from mapnik's
@@ -42,6 +50,9 @@ private:
     scaling_method_e scaling_method_;
     bool painted_;
     double simplify_distance_;
+    bool multi_polygon_union_;
+    ClipperLib::PolyFillType fill_type_;
+    bool process_all_rings_;
 public:
     MAPNIK_VECTOR_INLINE processor(T & backend,
               mapnik::Map const& map,
@@ -60,6 +71,16 @@ public:
         simplify_distance_ = dist;
     }
 
+    inline void set_process_all_rings(bool value)
+    {
+        process_all_rings_ = value;
+    }
+
+    inline void set_multi_polygon_union(bool value)
+    {
+        multi_polygon_union_ = value;
+    }
+
     inline double get_simplify_distance() const
     {
         return simplify_distance_;
@@ -69,6 +90,8 @@ public:
     {
         return t_;
     }
+    
+    MAPNIK_VECTOR_INLINE void set_fill_type(polygon_fill_type type);
 
     MAPNIK_VECTOR_INLINE void apply(double scale_denom=0.0);
 
diff --git a/src/vector_tile_processor.ipp b/src/vector_tile_processor.ipp
index 0d23eeb..ab0bc60 100644
--- a/src/vector_tile_processor.ipp
+++ b/src/vector_tile_processor.ipp
@@ -605,7 +605,10 @@ processor<T>::processor(T & backend,
       image_format_(image_format),
       scaling_method_(scaling_method),
       painted_(false),
-      simplify_distance_(0.0) {}
+      simplify_distance_(0.0),
+      multi_polygon_union_(false),
+      fill_type_(ClipperLib::pftNonZero),
+      process_all_rings_(false) {}
 
 template <typename T>
 void processor<T>::apply(double scale_denom)
@@ -637,6 +640,27 @@ bool processor<T>::painted() const
 {
     return painted_;
 }
+   
+template <typename T> 
+void processor<T>::set_fill_type(polygon_fill_type type)
+{
+    switch (type) 
+    {
+    case polygon_fill_type_max:
+    case even_odd_fill:
+        fill_type_ = ClipperLib::pftEvenOdd;
+        break; 
+    case non_zero_fill: 
+        fill_type_ = ClipperLib::pftNonZero;
+        break;
+    case positive_fill:
+        fill_type_ = ClipperLib::pftPositive;
+        break; 
+    case negative_fill:
+        fill_type_ = ClipperLib::pftNegative;
+        break;
+    }
+}
 
 
 template <typename T>
@@ -865,9 +889,15 @@ inline void process_polynode_branch(ClipperLib::PolyNode* 
polynode,
             // children of exterior ring are always interior rings
             for (auto * ring : polynode->Childs)
             {
-                if (ring->Contour.size() < 3) continue; // Throw out invalid 
holes
+                if (ring->Contour.size() < 3)
+                {
+                    continue; // Throw out invalid holes
+                }
                 double inner_area = ClipperLib::Area(ring->Contour);
-                if (std::abs(inner_area) < area_threshold) continue;
+                if (std::abs(inner_area) < area_threshold)
+                {
+                    continue;
+                }
                 
                 if (inner_area < 0)
                 {
@@ -888,18 +918,25 @@ inline void process_polynode_branch(ClipperLib::PolyNode* 
polynode,
 }
 
 template <typename T>
-struct encoder_visitor {
+struct encoder_visitor 
+{
     typedef T backend_type;
     encoder_visitor(backend_type & backend,
                     mapnik::feature_impl const& feature,
                     mapnik::box2d<int> const& tile_clipping_extent,
                     double area_threshold,
-                    bool strictly_simple) :
+                    bool strictly_simple,
+                    bool multi_polygon_union,
+                    ClipperLib::PolyFillType fill_type,
+                    bool process_all_rings) :
       backend_(backend),
       feature_(feature),
       tile_clipping_extent_(tile_clipping_extent),
       area_threshold_(area_threshold),
-      strictly_simple_(strictly_simple) {}
+      strictly_simple_(strictly_simple),
+      multi_polygon_union_(multi_polygon_union),
+      fill_type_(fill_type),
+      process_all_rings_(process_all_rings) {}
 
     bool operator() (mapnik::geometry::geometry_empty const&)
     {
@@ -1019,7 +1056,7 @@ struct encoder_visitor {
     bool operator() (mapnik::geometry::polygon<std::int64_t> & geom)
     {
         bool painted = false;
-        if (geom.exterior_ring.size() < 3)
+        if ((geom.exterior_ring.size() < 3) && !process_all_rings_)
         {
             // Invalid geometry so will be false
             return false;
@@ -1037,7 +1074,7 @@ struct encoder_visitor {
         ClipperLib::Clipper clipper;
         ClipperLib::CleanPolygon(geom.exterior_ring, clean_distance);
         double outer_area = ClipperLib::Area(geom.exterior_ring);
-        if (std::abs(outer_area) < area_threshold_)
+        if ((std::abs(outer_area) < area_threshold_)  && !process_all_rings_)
         {
             return painted;
         }
@@ -1053,7 +1090,7 @@ struct encoder_visitor {
         {
             poly_clipper.StrictlySimple(true);
         }
-        if (!poly_clipper.AddPath(geom.exterior_ring, ClipperLib::ptSubject, 
true))
+        if (!poly_clipper.AddPath(geom.exterior_ring, ClipperLib::ptSubject, 
true) && !process_all_rings_)
         {
             return painted;
         }
@@ -1065,7 +1102,10 @@ struct encoder_visitor {
             }
             ClipperLib::CleanPolygon(ring, clean_distance);
             double inner_area = ClipperLib::Area(ring);
-            if (std::abs(inner_area) < area_threshold_) continue;
+            if (std::abs(inner_area) < area_threshold_)
+            {
+                continue;
+            }
             // This should be a negative area, the y axis is down, so the ring 
will be "CCW" rather
             // then "CW" after the view transform, but if it is not lets 
reverse it
             if (inner_area < 0)
@@ -1083,7 +1123,7 @@ struct encoder_visitor {
         }
         ClipperLib::PolyTree polygons;
         poly_clipper.ReverseSolution(true);
-        poly_clipper.Execute(ClipperLib::ctIntersection, polygons, 
ClipperLib::pftNonZero);
+        poly_clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type_, 
fill_type_);
         poly_clipper.Clear();
         
         mapnik::geometry::multi_polygon<std::int64_t> mp;
@@ -1127,64 +1167,148 @@ struct encoder_visitor {
         
clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
         
clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
         
clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+        mapnik::geometry::multi_polygon<std::int64_t> mp;
+        
         ClipperLib::Clipper clipper;
-        if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
-        {
-            return painted;
-        }
-        for (auto & poly : geom)
+        if (multi_polygon_union_)
         {
-            if (poly.exterior_ring.size() < 3)
+            for (auto & poly : geom)
             {
-                continue;
+                // Below we attempt to skip processing of all interior rings 
if the exterior
+                // ring fails a variety of sanity checks for size and validity 
for AddPath
+                // When `process_all_rings_=true` this optimization is 
disabled. This is needed when
+                // the ring order of input polygons is potentially incorrect 
and where the
+                // "exterior_ring" might actually be an incorrectly classified 
exterior ring.
+                if (poly.exterior_ring.size() < 3 && !process_all_rings_)
+                {
+                    continue;
+                }
+                ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
+                double outer_area = ClipperLib::Area(poly.exterior_ring);
+                if ((std::abs(outer_area) < area_threshold_) && 
!process_all_rings_)
+                {
+                    continue;
+                }
+                // The view transform inverts the y axis so this should be 
positive still despite now
+                // being clockwise for the exterior ring. If it is not lets 
invert it.
+                if (outer_area > 0)
+                {
+                    std::reverse(poly.exterior_ring.begin(), 
poly.exterior_ring.end());
+                }
+                if (!clipper.AddPath(poly.exterior_ring, 
ClipperLib::ptSubject, true) && !process_all_rings_)
+                {
+                    continue;
+                }
+                for (auto & ring : poly.interior_rings)
+                {
+                    if (ring.size() < 3)
+                    {
+                        continue;
+                    }
+                    ClipperLib::CleanPolygon(ring, clean_distance);
+                    double inner_area = ClipperLib::Area(ring);
+                    if (std::abs(inner_area) < area_threshold_)
+                    {
+                        continue;
+                    }
+                    // This should be a negative area, the y axis is down, so 
the ring will be "CCW" rather
+                    // then "CW" after the view transform, but if it is not 
lets reverse it
+                    if (inner_area < 0)
+                    {
+                        std::reverse(ring.begin(), ring.end());
+                    }
+                    clipper.AddPath(ring, ClipperLib::ptSubject, true);
+                }
             }
-            ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
-            double outer_area = ClipperLib::Area(poly.exterior_ring);
-            if (std::abs(outer_area) < area_threshold_) continue;
-            // The view transform inverts the y axis so this should be 
positive still despite now
-            // being clockwise for the exterior ring. If it is not lets invert 
it.
-            if (outer_area > 0)
-            {   
-                std::reverse(poly.exterior_ring.begin(), 
poly.exterior_ring.end());
+            if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+            {
+                return painted;
             }
-            if (!clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, 
true))
+            ClipperLib::PolyTree polygons;
+            if (strictly_simple_) 
             {
-                continue;
+                clipper.StrictlySimple(true);
             }
-            for (auto & ring : poly.interior_rings)
+            clipper.ReverseSolution(true);
+            clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type_, 
fill_type_);
+            clipper.Clear();
+            
+            for (auto * polynode : polygons.Childs)
             {
-                if (ring.size() < 3) continue;
-                ClipperLib::CleanPolygon(ring, clean_distance);
-                double inner_area = ClipperLib::Area(ring);
-                if (std::abs(inner_area) < area_threshold_) continue;
-                // This should be a negative area, the y axis is down, so the 
ring will be "CCW" rather
-                // then "CW" after the view transform, but if it is not lets 
reverse it
-                if (inner_area < 0)
+                process_polynode_branch(polynode, mp, area_threshold_); 
+            }
+        }
+        else
+        {
+            for (auto & poly : geom)
+            {
+                // Below we attempt to skip processing of all interior rings 
if the exterior
+                // ring fails a variety of sanity checks for size and validity 
for AddPath
+                // When `process_all_rings_=true` this optimization is 
disabled. This is needed when
+                // the ring order of input polygons is potentially incorrect 
and where the
+                // "exterior_ring" might actually be an incorrectly classified 
exterior ring.
+                if (poly.exterior_ring.size() < 3 && !process_all_rings_)
                 {
-                    std::reverse(ring.begin(), ring.end());
+                    continue;
                 }
-                if (!clipper.AddPath(ring, ClipperLib::ptSubject, true))
+                ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
+                double outer_area = ClipperLib::Area(poly.exterior_ring);
+                if ((std::abs(outer_area) < area_threshold_) && 
!process_all_rings_)
                 {
                     continue;
                 }
+                // The view transform inverts the y axis so this should be 
positive still despite now
+                // being clockwise for the exterior ring. If it is not lets 
invert it.
+                if (outer_area > 0)
+                {
+                    std::reverse(poly.exterior_ring.begin(), 
poly.exterior_ring.end());
+                }
+                if (!clipper.AddPath(poly.exterior_ring, 
ClipperLib::ptSubject, true) && !process_all_rings_)
+                {
+                    continue;
+                }
+                for (auto & ring : poly.interior_rings)
+                {
+                    if (ring.size() < 3)
+                    {
+                        continue;
+                    }
+                    ClipperLib::CleanPolygon(ring, clean_distance);
+                    double inner_area = ClipperLib::Area(ring);
+                    if (std::abs(inner_area) < area_threshold_)
+                    {
+                        continue;
+                    }
+                    // This should be a negative area, the y axis is down, so 
the ring will be "CCW" rather
+                    // then "CW" after the view transform, but if it is not 
lets reverse it
+                    if (inner_area < 0)
+                    {
+                        std::reverse(ring.begin(), ring.end());
+                    }
+                    if (!clipper.AddPath(ring, ClipperLib::ptSubject, true))
+                    {
+                        continue;
+                    }
+                }
+                if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+                {
+                    return painted;
+                }
+                ClipperLib::PolyTree polygons;
+                if (strictly_simple_) 
+                {
+                    clipper.StrictlySimple(true);
+                }
+                clipper.ReverseSolution(true);
+                clipper.Execute(ClipperLib::ctIntersection, polygons, 
fill_type_, fill_type_);
+                clipper.Clear();
+                
+                for (auto * polynode : polygons.Childs)
+                {
+                    process_polynode_branch(polynode, mp, area_threshold_); 
+                }
             }
         }
-        
-        ClipperLib::PolyTree polygons;
-        if (strictly_simple_) 
-        {
-            clipper.StrictlySimple(true);
-        }
-        clipper.ReverseSolution(true);
-        clipper.Execute(ClipperLib::ctIntersection, polygons, 
ClipperLib::pftNonZero);
-        clipper.Clear();
-        
-        mapnik::geometry::multi_polygon<std::int64_t> mp;
-        
-        for (auto * polynode : polygons.Childs)
-        {
-            process_polynode_branch(polynode, mp, area_threshold_); 
-        }
 
         if (mp.empty())
         {
@@ -1207,10 +1331,14 @@ struct encoder_visitor {
     mapnik::box2d<int> const& tile_clipping_extent_;
     double area_threshold_;
     bool strictly_simple_;
+    bool multi_polygon_union_;
+    ClipperLib::PolyFillType fill_type_;
+    bool process_all_rings_;
 };
 
 template <typename T>
-struct simplify_visitor {
+struct simplify_visitor 
+{
     typedef T backend_type;
     simplify_visitor(double simplify_distance,
                      encoder_visitor<backend_type> & encoder) :
@@ -1291,7 +1419,14 @@ bool processor<T>::handle_geometry(T2 const& vs,
     using vector_tile_strategy_type = T2;
     mapnik::vector_tile_impl::transform_visitor<vector_tile_strategy_type> 
skipping_transformer(vs, target_clipping_extent);
     mapnik::geometry::geometry<std::int64_t> new_geom = 
mapnik::util::apply_visitor(skipping_transformer,geom);
-    encoder_visitor<T> encoder(backend_, feature, tile_clipping_extent, 
area_threshold_, strictly_simple_);
+    encoder_visitor<T> encoder(backend_, 
+                               feature, 
+                               tile_clipping_extent, 
+                               area_threshold_, 
+                               strictly_simple_, 
+                               multi_polygon_union_, 
+                               fill_type_,
+                               process_all_rings_);
     if (simplify_distance_ > 0)
     {
         simplify_visitor<T> simplifier(simplify_distance_,encoder);
diff --git a/test/data/0.0.0.vector-b.pbf b/test/data/0.0.0.vector-b.pbf
new file mode 100644
index 0000000..a26263f
Binary files /dev/null and b/test/data/0.0.0.vector-b.pbf differ
diff --git a/test/data/0.0.0.vector.pbf b/test/data/0.0.0.vector.pbf
new file mode 100644
index 0000000..f447706
Binary files /dev/null and b/test/data/0.0.0.vector.pbf differ
diff --git a/test/encoding_util.hpp b/test/encoding_util.cpp
similarity index 100%
copy from test/encoding_util.hpp
copy to test/encoding_util.cpp
diff --git a/test/encoding_util.hpp b/test/encoding_util.hpp
index 7454f6f..169ba49 100644
--- a/test/encoding_util.hpp
+++ b/test/encoding_util.hpp
@@ -9,123 +9,17 @@ namespace {
 
 using namespace mapnik::geometry;
 
-struct print
-{
-    void operator() (geometry_empty const&) const
-    {
-        std::cerr << "EMPTY" << std::endl;
-    }
-    template <typename T>
-    void operator() (geometry_collection<T> const&) const
-    {
-        std::cerr << "COLLECTION" << std::endl;
-    }
-    template <typename T>
-    void operator() (T const& geom) const
-    {
-        std::cerr << boost::geometry::wkt(geom) << std::endl;
-    }
-};
+struct print;
 
 }
 
-struct show_path
-{
-    std::string & str_;
-    show_path(std::string & out) :
-      str_(out) {}
+struct show_path;
 
-    template <typename T>
-    void operator()(T & path)
-    {
-        unsigned cmd = -1;
-        double x = 0;
-        double y = 0;
-        std::ostringstream s;
-        path.rewind(0);
-        while ((cmd = path.vertex(&x, &y)) != mapnik::SEG_END)
-        {
-            switch (cmd)
-            {
-                case mapnik::SEG_MOVETO: s << "move_to("; break;
-                case mapnik::SEG_LINETO: s << "line_to("; break;
-                case mapnik::SEG_CLOSE: s << "close_path("; break;
-                default: std::clog << "unhandled cmd " << cmd << "\n"; break;
-            }
-            s << x << "," << y << ")\n";
-        }
-        str_ += s.str();
-    }
-};
+struct encoder_visitor;
 
-struct encoder_visitor
-{
-    vector_tile::Tile_Feature & feature_;
-    int32_t x_;
-    int32_t y_;
-    encoder_visitor(vector_tile::Tile_Feature & feature) :
-      feature_(feature),
-      x_(0),
-      y_(0) { }
-
-    void operator() (geometry_empty const&)
-    {
-    }
-
-    void operator()(mapnik::geometry::geometry_collection<std::int64_t> const& 
path)
-    {
-        for (auto const& p : path)
-        {
-            mapnik::util::apply_visitor((*this), p);
-        }
-    }
-
-    template <typename T>
-    void operator()(T const& geom)
-    {
-        mapnik::vector_tile_impl::encode_geometry(geom,feature_,x_,y_);
-    }
-};
-
-vector_tile::Tile_Feature 
geometry_to_feature(mapnik::geometry::geometry<std::int64_t> const& g)
-{
-    vector_tile::Tile_Feature feature;
-    if (g.template is<mapnik::geometry::point<std::int64_t>>() || g.template 
is<mapnik::geometry::multi_point<std::int64_t>>())
-    {
-        feature.set_type(vector_tile::Tile_GeomType_POINT);
-    }
-    else if (g.template is<mapnik::geometry::line_string<std::int64_t>>() || 
g.template is<mapnik::geometry::multi_line_string<std::int64_t>>())
-    {
-        feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
-    }
-    else if (g.template is<mapnik::geometry::polygon<std::int64_t>>() || 
g.template is<mapnik::geometry::multi_polygon<std::int64_t>>())
-    {
-        feature.set_type(vector_tile::Tile_GeomType_POLYGON);
-    }
-    else
-    {
-        throw std::runtime_error("could not detect valid geometry type");
-    }
-    encoder_visitor ap(feature);
-    mapnik::util::apply_visitor(ap,g);
-    return feature;
-}
+vector_tile::Tile_Feature 
geometry_to_feature(mapnik::geometry::geometry<std::int64_t> const& g);
 
 template <typename T>
-std::string decode_to_path_string(mapnik::geometry::geometry<T> const& g)
-{
-    //mapnik::util::apply_visitor(print(), g2);
-    using decode_path_type = mapnik::geometry::vertex_processor<show_path>;
-    std::string out;
-    show_path sp(out);
-    mapnik::util::apply_visitor(decode_path_type(sp), g);
-    return out;
-}
+std::string decode_to_path_string(mapnik::geometry::geometry<T> const& g);
 
-std::string compare(mapnik::geometry::geometry<std::int64_t> const& g)
-{
-    vector_tile::Tile_Feature feature = geometry_to_feature(g);
-    mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
-    auto g2 = 
mapnik::vector_tile_impl::decode_geometry<double>(geoms,feature.type());
-    return decode_to_path_string(g2);
-}
+std::string compare(mapnik::geometry::geometry<std::int64_t> const& g);
diff --git a/test/fixtures/rasterize-expected-1.png 
b/test/fixtures/rasterize-expected-1.png
new file mode 100644
index 0000000..2308d2f
Binary files /dev/null and b/test/fixtures/rasterize-expected-1.png differ
diff --git a/test/geometry_visual_test.cpp b/test/geometry_visual_test.cpp
new file mode 100644
index 0000000..1692c40
--- /dev/null
+++ b/test/geometry_visual_test.cpp
@@ -0,0 +1,267 @@
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <cmath>
+#include <mapnik/util/fs.hpp>
+#include <mapnik/projection.hpp>
+#include <mapnik/geometry_transform.hpp>
+#include <mapnik/util/geometry_to_geojson.hpp>
+#include <mapnik/geometry_correct.hpp>
+#include <mapnik/util/file_io.hpp>
+#include <mapnik/save_map.hpp>
+#include <mapnik/geometry_reprojection.hpp>
+#include <mapnik/geometry_strategy.hpp>
+#include <mapnik/proj_strategy.hpp>
+#include <mapnik/view_strategy.hpp>
+#include <mapnik/geometry_is_valid.hpp>
+#include <mapnik/geometry_is_simple.hpp>
+
+#include "catch.hpp"
+#include "encoding_util.hpp"
+#include "test_utils.hpp"
+#include "vector_tile_strategy.hpp"
+#include "vector_tile_processor.hpp"
+#include "vector_tile_backend_pbf.hpp"
+#include "vector_tile_projection.hpp"
+#include "vector_tile_geometry_decoder.hpp"
+#include <boost/filesystem/operations.hpp>
+
+void clip_geometry(std::string const& file,
+                   mapnik::box2d<double> const& bbox,
+                   int simplify_distance,
+                   bool strictly_simple,
+                   mapnik::vector_tile_impl::polygon_fill_type fill_type,
+                   bool mpu,
+                   bool process_all)
+{
+    typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+    typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+    typedef vector_tile::Tile tile_type;
+    std::string geojson_string;
+    std::shared_ptr<mapnik::memory_datasource> ds = 
testing::build_geojson_ds(file);
+    tile_type tile;
+    backend_type backend(tile,1000);
+
+    mapnik::Map map(256,256,"+init=epsg:4326");
+    mapnik::layer lyr("layer","+init=epsg:4326");
+    lyr.set_datasource(ds);
+    map.add_layer(lyr);
+    map.zoom_to_box(bbox);
+    mapnik::request m_req(map.width(),map.height(),map.get_current_extent());
+    renderer_type ren(
+        backend,
+        map,
+        m_req,
+        1.0,
+        0,
+        0,
+        0.1,
+        strictly_simple
+    );
+    ren.set_simplify_distance(simplify_distance);
+    ren.set_fill_type(fill_type);
+    // TODO - test these booleans 
https://github.com/mapbox/mapnik-vector-tile/issues/165
+    ren.set_process_all_rings(process_all);
+    ren.set_multi_polygon_union(mpu);
+    ren.apply();
+    std::string buffer;
+    tile.SerializeToString(&buffer);
+    if (buffer.size() > 0 && tile.layers_size() > 0 && tile.layers_size() != 
false)
+    {
+        vector_tile::Tile_Layer const& layer = tile.layers(0);
+        if (layer.features_size() != false)
+        {
+            vector_tile::Tile_Feature const& f = layer.features(0);
+            mapnik::vector_tile_impl::Geometry<double> 
geoms(f,0.0,0.0,32.0,32.0);
+            mapnik::geometry::geometry<double> geom = 
mapnik::vector_tile_impl::decode_geometry<double>(geoms,f.type());
+            mapnik::geometry::scale_strategy ss(1.0/32.0);
+            mapnik::view_transform vt(256, 256, bbox);
+            mapnik::unview_strategy uvs(vt);
+            using sg_type = 
strategy_group_first<mapnik::geometry::scale_strategy, mapnik::unview_strategy 
>;
+            sg_type sg(ss, uvs);
+            mapnik::geometry::geometry<double> geom4326 = 
transform<double>(geom, sg);
+            std::string reason;
+            std::string is_valid = "false";
+            std::string is_simple = "false";
+            if (mapnik::geometry::is_valid(geom4326, reason))
+                is_valid = "true";
+            if (mapnik::geometry::is_simple(geom4326))
+                is_simple = "true";
+
+            unsigned int n_err = 0;
+            mapnik::util::to_geojson(geojson_string,geom4326);
+
+            geojson_string = geojson_string.substr(0, geojson_string.size()-1);
+            geojson_string += ",\"properties\":{\"is_valid\":"+is_valid+", 
\"is_simple\":"+is_simple+", \"message\":\""+reason+"\"}}";
+        }
+        else
+        {
+            geojson_string = "{\"type\": \"Feature\", \"coordinates\":null, 
\"properties\":{\"message\":\"Tile layer had no features\"}}";
+        }
+    }
+    else
+    {
+        geojson_string = "{\"type\": \"Feature\", \"coordinates\":null, 
\"properties\":{\"message\":\"Tile had no layers\"}}";
+    }
+
+    std::string fixture_name = mapnik::util::basename(file);
+    fixture_name = fixture_name.substr(0, fixture_name.size()-5);
+    if (!mapnik::util::exists("./test/geometry-test-data/output"))
+    {
+        
boost::filesystem::create_directory(("./test/geometry-test-data/output"));
+    }
+    if 
(!mapnik::util::exists("./test/geometry-test-data/output/"+fixture_name))
+    {
+        
boost::filesystem::create_directory(("./test/geometry-test-data/output/"+fixture_name));
+    }
+
+    std::stringstream file_stream;
+    file_stream << "./test/geometry-test-data/output/" << fixture_name << "/"
+        << bbox.minx() << ","
+        << bbox.miny() << ","
+        << bbox.maxx()<< ","
+        << bbox.maxy() << ","
+        << "simplify_distance=" << simplify_distance << ","
+        << "strictly_simple=" << strictly_simple << ","
+        << "fill_type=" << fill_type << ","
+        << "mpu=" << mpu << ","
+        << "par=" << process_all << ".geojson";
+    std::string file_path = file_stream.str();
+    if (!mapnik::util::exists(file_path) || (std::getenv("UPDATE") != nullptr))
+    {
+        std::ofstream out(file_path);
+        out << geojson_string;
+    }
+    else
+    {
+        mapnik::util::file input(file_path);
+        if (!input.open())
+        {
+            throw std::runtime_error("failed to open geojson");
+        }
+        mapnik::geometry::geometry<double> geom;
+        std::string expected_string(input.data().get(), input.size());
+        CHECK(expected_string == geojson_string);
+    }
+}
+
+mapnik::box2d<double> middle_fifty(mapnik::box2d<double> const& bbox)
+{
+    double width = std::fabs(bbox.maxx() - bbox.minx());
+    double height = std::fabs(bbox.maxy() - bbox.miny());
+    mapnik::box2d<double> new_bbox(
+        bbox.minx() + width*0.25,
+        bbox.miny() + height*0.25,
+        bbox.maxx() - width*0.25,
+        bbox.maxy() - height*0.25
+    );
+    return new_bbox;
+}
+
+mapnik::box2d<double> top_left(mapnik::box2d<double> const& bbox)
+{
+    double width = std::fabs(bbox.maxx() - bbox.minx());
+    double height = std::fabs(bbox.maxy() - bbox.miny());
+    mapnik::box2d<double> new_bbox(
+        bbox.minx(),
+        bbox.miny(),
+        bbox.maxx() - width*0.5,
+        bbox.maxy() - height*0.5
+    );
+    return new_bbox;
+}
+
+mapnik::box2d<double> top_right(mapnik::box2d<double> const& bbox)
+{
+    double width = std::fabs(bbox.maxx() - bbox.minx());
+    double height = std::fabs(bbox.maxy() - bbox.miny());
+    mapnik::box2d<double> new_bbox(
+        bbox.minx() + width*0.5,
+        bbox.miny(),
+        bbox.maxx(),
+        bbox.maxy() - height*0.5
+    );
+    return new_bbox;
+}
+
+mapnik::box2d<double> bottom_left(mapnik::box2d<double> const& bbox)
+{
+    double width = std::fabs(bbox.maxx() - bbox.minx());
+    double height = std::fabs(bbox.maxy() - bbox.miny());
+    mapnik::box2d<double> new_bbox(
+        bbox.minx(),
+        bbox.miny() + height*0.5,
+        bbox.maxx() + width*0.5,
+        bbox.maxy()
+    );
+    return new_bbox;
+}
+
+mapnik::box2d<double> bottom_right(mapnik::box2d<double> const& bbox)
+{
+    double width = std::fabs(bbox.maxx() - bbox.minx());
+    double height = std::fabs(bbox.maxy() - bbox.miny());
+    mapnik::box2d<double> new_bbox(
+        bbox.minx() + width*0.5,
+        bbox.miny() + height*0.5,
+        bbox.maxx(),
+        bbox.maxy()
+    );
+    return new_bbox;
+}
+
+mapnik::box2d<double> zoomed_out(mapnik::box2d<double> const& bbox)
+{
+    double width = std::fabs(bbox.maxx() - bbox.minx());
+    double height = std::fabs(bbox.maxy() - bbox.miny());
+    mapnik::box2d<double> new_bbox(
+        bbox.minx() - width,
+        bbox.miny() - height,
+        bbox.maxx() + width,
+        bbox.maxy() + height
+    );
+    return new_bbox;
+}
+
+TEST_CASE( "geometries", "should reproject, clip, and simplify")
+{
+
+    std::vector<std::string> geometries = 
mapnik::util::list_directory("./test/geometry-test-data/input");
+    std::vector<std::string> benchmarks = 
mapnik::util::list_directory("./test/geometry-test-data/benchmark");
+    if (std::getenv("BENCHMARK") != nullptr)
+    {
+        geometries.insert(geometries.end(), benchmarks.begin(), 
benchmarks.end());
+    }
+    for (std::string const& file: geometries)
+    {
+        std::shared_ptr<mapnik::memory_datasource> ds = 
testing::build_geojson_ds(file);
+        mapnik::box2d<double> bbox = ds->envelope();
+        for (int simplification_distance : std::vector<int>({0, 4, 8}))
+        {
+            for (bool strictly_simple : std::vector<bool>({false, true}))
+            {
+                std::vector<mapnik::vector_tile_impl::polygon_fill_type> types;
+                types.emplace_back(mapnik::vector_tile_impl::even_odd_fill); 
+                types.emplace_back(mapnik::vector_tile_impl::non_zero_fill);
+                types.emplace_back(mapnik::vector_tile_impl::positive_fill); 
+                types.emplace_back(mapnik::vector_tile_impl::negative_fill);
+                for (auto type : types)
+                {
+                    for (bool mpu : std::vector<bool>({false, true}))
+                    {
+                        for (bool process_all : std::vector<bool>({false, 
true}))
+                        {
+                            clip_geometry(file, bbox, simplification_distance, 
strictly_simple, type, mpu, process_all);
+                            clip_geometry(file, middle_fifty(bbox), 
simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(file, top_left(bbox), 
simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(file, top_right(bbox), 
simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(file, bottom_left(bbox), 
simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(file, bottom_right(bbox), 
simplification_distance, strictly_simple, type, mpu, process_all);
+                            clip_geometry(file, zoomed_out(bbox), 
simplification_distance, strictly_simple, type, mpu, process_all);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/test/test_main.cpp b/test/test_main.cpp
index c49e5e8..2930e12 100644
--- a/test/test_main.cpp
+++ b/test/test_main.cpp
@@ -1,4 +1,4 @@
-// https://github.com/philsquared/Catch/wiki/Supplying-your-own-main()
+// https://github.com/philsquared/Catch/blob/master/docs/own-main.md
 #define CATCH_CONFIG_RUNNER
 #include "catch.hpp"
 
diff --git a/test/vector_tile.cpp b/test/vector_tile.cpp
index cedb613..bb07901 100644
--- a/test/vector_tile.cpp
+++ b/test/vector_tile.cpp
@@ -71,35 +71,6 @@ TEST_CASE( "vector tile compression", "should be able to 
round trip gzip and zli
     CHECK(data == ungzipped);
 }
 
-TEST_CASE( "vector tile projection 1", "should support z/x/y to bbox 
conversion at 0/0/0" ) {
-    mapnik::vector_tile_impl::spherical_mercator merc(256);
-    double minx,miny,maxx,maxy;
-    merc.xyz(0,0,0,minx,miny,maxx,maxy);
-    mapnik::box2d<double> map_extent(minx,miny,maxx,maxy);
-    mapnik::box2d<double> 
e(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
-    double epsilon = 0.000001;
-    CHECK(std::fabs(map_extent.minx() - e.minx()) < epsilon);
-    CHECK(std::fabs(map_extent.miny() - e.miny()) < epsilon);
-    CHECK(std::fabs(map_extent.maxx() - e.maxx()) < epsilon);
-    CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon);
-}
-
-TEST_CASE( "vector tile projection 2", "should support z/x/y to bbox 
conversion up to z33" ) {
-    mapnik::vector_tile_impl::spherical_mercator merc(256);
-    int x = 2145960701;
-    int y = 1428172928;
-    int z = 32;
-    double 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(-14210.1492817168364127,6711666.7204630710184574,-14210.1399510249066225,6711666.7297937674447894);
-    double epsilon = 0.00000001;
-    CHECK(std::fabs(map_extent.minx() - e.minx()) < epsilon);
-    CHECK(std::fabs(map_extent.miny() - e.miny()) < epsilon);
-    CHECK(std::fabs(map_extent.maxx() - e.maxx()) < epsilon);
-    CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon);
-}
-
 TEST_CASE( "vector tile output 1", "should create vector tile with two points" 
) {
     typedef mapnik::vector_tile_impl::backend_pbf backend_type;
     typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
@@ -619,7 +590,9 @@ TEST_CASE( "encoding single line 2", "should maintain 
start/end vertex" ) {
 }
 
 mapnik::geometry::geometry<double> 
round_trip(mapnik::geometry::geometry<double> const& geom,
-                                      double simplify_distance=0.0)
+                                      double simplify_distance=0.0,
+                                      
mapnik::vector_tile_impl::polygon_fill_type fill_type = 
mapnik::vector_tile_impl::non_zero_fill,
+                                      bool mpu = false)
 {
     typedef mapnik::vector_tile_impl::backend_pbf backend_type;
     typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
@@ -640,6 +613,8 @@ mapnik::geometry::geometry<double> 
round_trip(mapnik::geometry::geometry<double>
     mapnik::projection merc("+init=epsg:4326",true);
     mapnik::proj_transform prj_trans(merc,wgs84);
     ren.set_simplify_distance(simplify_distance);
+    ren.set_fill_type(fill_type);
+    ren.set_multi_polygon_union(mpu);
     mapnik::vector_tile_impl::vector_tile_strategy_proj 
vs2(prj_trans,ren.get_transform(),backend.get_path_multiplier());
     mapnik::vector_tile_impl::vector_tile_strategy 
vs(ren.get_transform(),backend.get_path_multiplier());
     mapnik::geometry::point<double> p1_min(bbox.minx(), bbox.miny());
@@ -788,7 +763,6 @@ TEST_CASE( "vector tile polygon encoding", "should create 
vector tile with data"
     CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 
-113.778,120.889 -113.778))" );
 }
 
-
 TEST_CASE( "vector tile multi_polygon encoding of single polygon", "should 
create vector tile with data" ) {
     mapnik::geometry::polygon<double> poly;
     poly.exterior_ring.add_coord(0,0);
@@ -806,6 +780,54 @@ TEST_CASE( "vector tile multi_polygon encoding of single 
polygon", "should creat
     CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
 }
 
+TEST_CASE( "vector tile multi_polygon with multipolygon union", "should create 
vector tile with data" ) {
+    mapnik::geometry::polygon<double> poly;
+    poly.exterior_ring.add_coord(0,0);
+    poly.exterior_ring.add_coord(0,10);
+    poly.exterior_ring.add_coord(-10,10);
+    poly.exterior_ring.add_coord(-10,0);
+    poly.exterior_ring.add_coord(0,0);
+    mapnik::geometry::polygon<double> poly2;
+    poly2.exterior_ring.add_coord(0,0);
+    poly2.exterior_ring.add_coord(0,10);
+    poly2.exterior_ring.add_coord(-10,10);
+    poly2.exterior_ring.add_coord(-10,0);
+    poly2.exterior_ring.add_coord(0,0);
+    mapnik::geometry::multi_polygon<double> geom;
+    geom.emplace_back(std::move(poly));
+    geom.emplace_back(std::move(poly2));
+    mapnik::geometry::geometry<double> new_geom = round_trip(geom, 0, 
mapnik::vector_tile_impl::non_zero_fill, true);
+    std::string wkt;
+    CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 
-113.778,120.889 -113.778))" );
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE( "vector tile multi_polygon with out multipolygon union", "should 
create vector tile with data" ) {
+    mapnik::geometry::polygon<double> poly;
+    poly.exterior_ring.add_coord(0,0);
+    poly.exterior_ring.add_coord(0,10);
+    poly.exterior_ring.add_coord(-10,10);
+    poly.exterior_ring.add_coord(-10,0);
+    poly.exterior_ring.add_coord(0,0);
+    mapnik::geometry::polygon<double> poly2;
+    poly2.exterior_ring.add_coord(0,0);
+    poly2.exterior_ring.add_coord(0,10);
+    poly2.exterior_ring.add_coord(-10,10);
+    poly2.exterior_ring.add_coord(-10,0);
+    poly2.exterior_ring.add_coord(0,0);
+    mapnik::geometry::multi_polygon<double> geom;
+    geom.emplace_back(std::move(poly));
+    geom.emplace_back(std::move(poly2));
+    mapnik::geometry::geometry<double> new_geom = round_trip(geom, 0, 
mapnik::vector_tile_impl::non_zero_fill, false);
+    std::string wkt;
+    CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "MULTIPOLYGON(((120.889 -113.778,120.889 -128,128 -128,128 
-113.778,120.889 -113.778)),((120.889 -113.778,120.889 -128,128 -128,128 
-113.778,120.889 -113.778)))" );
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
+}
+
 TEST_CASE( "vector tile multi_polygon encoding of actual multi_polygon", 
"should create vector tile with data a multi polygon" ) {
     mapnik::geometry::multi_polygon<double> geom;
     mapnik::geometry::polygon<double> poly;
@@ -843,6 +865,44 @@ TEST_CASE( "vector tile multi_polygon encoding of actual 
multi_polygon", "should
     CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
 }
 
+TEST_CASE( "vector tile multi_polygon encoding overlapping multipolygons", 
"should create vector tile with data a multi polygon" ) {
+    mapnik::geometry::multi_polygon<double> geom;
+    mapnik::geometry::polygon<double> poly;
+    poly.exterior_ring.add_coord(0,0);
+    poly.exterior_ring.add_coord(0,10);
+    poly.exterior_ring.add_coord(-10,10);
+    poly.exterior_ring.add_coord(-10,0);
+    poly.exterior_ring.add_coord(0,0);
+    /*
+    // This is an interior ring that touches nothing.
+    poly.interior_rings.emplace_back();
+    poly.interior_rings.back().add_coord(-1,1);
+    poly.interior_rings.back().add_coord(-1,2);
+    poly.interior_rings.back().add_coord(-2,2);
+    poly.interior_rings.back().add_coord(-2,1);
+    poly.interior_rings.back().add_coord(-1,1);
+    // This is an interior ring that touches exterior edge.
+    poly.interior_rings.emplace_back();
+    poly.interior_rings.back().add_coord(-10,7);
+    poly.interior_rings.back().add_coord(-10,5);
+    poly.interior_rings.back().add_coord(-8,5);
+    poly.interior_rings.back().add_coord(-8,7);
+    poly.interior_rings.back().add_coord(-10,7);
+    */
+    geom.emplace_back(std::move(poly));
+    mapnik::geometry::polygon<double> poly2;
+    poly2.exterior_ring.add_coord(-5,5);
+    poly2.exterior_ring.add_coord(-5,15);
+    poly2.exterior_ring.add_coord(-15,15);
+    poly2.exterior_ring.add_coord(-15,5);
+    poly2.exterior_ring.add_coord(-5,5);
+    geom.emplace_back(std::move(poly2));
+    mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
+}
+
+
 // simplification
 
 TEST_CASE( "vector tile point correctly passed through simplification code 
path", "should create vector tile with data" ) {
@@ -901,6 +961,60 @@ TEST_CASE( "vector tile multi_line_string is simplified", 
"should create vector
     CHECK( line2.size() == 2 );
 }
 
+TEST_CASE( "vector tile polygon even odd fill", "should create vector tile 
with data" ) {
+    using namespace mapnik::geometry;
+    polygon<double> poly;
+    {
+        linear_ring<double> ring;
+        ring.add_coord(0,0);
+        ring.add_coord(-10,0);
+        ring.add_coord(-10,10);
+        ring.add_coord(0,10);
+        ring.add_coord(0,0);
+        poly.set_exterior_ring(std::move(ring));
+        linear_ring<double> hole;
+        hole.add_coord(-7,7);
+        hole.add_coord(-7,3);
+        hole.add_coord(-3,3);
+        hole.add_coord(-3,7);
+        hole.add_coord(-7,7);
+        poly.add_hole(std::move(hole));
+    }
+    mapnik::geometry::geometry<double> new_geom = 
round_trip(poly,0,mapnik::vector_tile_impl::even_odd_fill);
+    std::string wkt;
+    CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 
-113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 
-118.044,125.867 -118.044,125.867 -123.733))");
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE( "vector tile polygon non zero fill", "should create vector tile 
with data" ) {
+    using namespace mapnik::geometry;
+    polygon<double> poly;
+    {
+        linear_ring<double> ring;
+        ring.add_coord(0,0);
+        ring.add_coord(-10,0);
+        ring.add_coord(-10,10);
+        ring.add_coord(0,10);
+        ring.add_coord(0,0);
+        poly.set_exterior_ring(std::move(ring));
+        linear_ring<double> hole;
+        hole.add_coord(-7,7);
+        hole.add_coord(-7,3);
+        hole.add_coord(-3,3);
+        hole.add_coord(-3,7);
+        hole.add_coord(-7,7);
+        poly.add_hole(std::move(hole));
+    }
+    mapnik::geometry::geometry<double> new_geom = 
round_trip(poly,0,mapnik::vector_tile_impl::non_zero_fill);
+    std::string wkt;
+    CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+    CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 
-113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 
-118.044,125.867 -118.044,125.867 -123.733))");
+    CHECK( !mapnik::geometry::is_empty(new_geom) );
+    REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
 TEST_CASE( "vector tile polygon is simplified", "should create vector tile 
with data" ) {
     using namespace mapnik::geometry;
     polygon<double> poly;
diff --git a/test/vector_tile_projection.cpp b/test/vector_tile_projection.cpp
new file mode 100644
index 0000000..3fcbcf4
--- /dev/null
+++ b/test/vector_tile_projection.cpp
@@ -0,0 +1,117 @@
+#include "catch.hpp"
+
+#include <mapnik/projection.hpp>
+#include <mapnik/box2d.hpp>
+#include <mapnik/well_known_srs.hpp>
+#include <mapnik/proj_transform.hpp>
+#include "vector_tile_projection.hpp"
+
+std::vector<double> pointToTile(double lon, double lat, unsigned z)
+{
+    double s = std::sin(lat * M_PI / 180.0);
+    double z2 = std::pow(2.0,z);
+    double x = z2 * (lon / 360.0 + 0.5);
+    double y = z2 * (0.5 - 0.25 * std::log((1 + s) / (1 - s)) / M_PI) - 1;
+    return { x, y };
+}
+
+int getBboxZoom(std::vector<double> const& bbox)
+{
+    int MAX_ZOOM = 32;
+    for (int z = 0; z < MAX_ZOOM; z++) {
+        int mask = (1 << (32 - (z + 1)));
+        if (((static_cast<int>(bbox[0]) & mask) != (static_cast<int>(bbox[2]) 
& mask)) ||
+            ((static_cast<int>(bbox[1]) & mask) != (static_cast<int>(bbox[3]) 
& mask))) {
+            return z;
+        }
+    }
+
+    return MAX_ZOOM;
+}
+
+std::vector<unsigned> bboxToXYZ(mapnik::box2d<double> const& bboxCoords)
+{
+    double minx = bboxCoords.minx();
+    double miny = bboxCoords.miny();
+    double maxx = bboxCoords.maxx();
+    double maxy = bboxCoords.maxy();
+    mapnik::merc2lonlat(&minx,&miny,1);
+    mapnik::merc2lonlat(&maxx,&maxy,1);
+
+    std::vector<double> ubbox = {
+        minx,
+        miny,
+        maxx,
+        maxy
+    };
+    unsigned z = getBboxZoom(ubbox);
+    if (z == 0) return {0, 0, 0};
+    minx = pointToTile(minx, miny, 32)[0];
+    miny = pointToTile(minx, miny, 32)[1];
+    unsigned x = static_cast<unsigned>(minx);
+    unsigned y = static_cast<unsigned>(miny);
+    return {x, y, z};
+}
+
+TEST_CASE( "vector tile projection 1", "should support z/x/y to bbox 
conversion at 0/0/0" ) {
+    mapnik::vector_tile_impl::spherical_mercator merc(256);
+    double minx,miny,maxx,maxy;
+    merc.xyz(0,0,0,minx,miny,maxx,maxy);
+    mapnik::box2d<double> map_extent(minx,miny,maxx,maxy);
+    mapnik::box2d<double> 
e(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+    double epsilon = 0.000001;
+    CHECK(std::fabs(map_extent.minx() - e.minx()) < epsilon);
+    CHECK(std::fabs(map_extent.miny() - e.miny()) < epsilon);
+    CHECK(std::fabs(map_extent.maxx() - e.maxx()) < epsilon);
+    CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon);
+    auto xyz = bboxToXYZ(map_extent);
+    /*
+    CHECK(xyz[0] == 0);
+    CHECK(xyz[1] == 0);
+    CHECK(xyz[2] == 0);
+    */
+}
+
+TEST_CASE( "vector tile projection 2", "should support z/x/y to bbox 
conversion up to z33" ) {
+    mapnik::vector_tile_impl::spherical_mercator merc(256);
+    int x = 2145960701;
+    int y = 1428172928;
+    int z = 32;
+    double 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(-14210.1492817168364127,6711666.7204630710184574,-14210.1399510249066225,6711666.7297937674447894);
+    double epsilon = 0.00000001;
+    CHECK(std::fabs(map_extent.minx() - e.minx()) < epsilon);
+    CHECK(std::fabs(map_extent.miny() - e.miny()) < epsilon);
+    CHECK(std::fabs(map_extent.maxx() - e.maxx()) < epsilon);
+    CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon);
+    auto xyz = bboxToXYZ(map_extent);
+    /*
+    CHECK(xyz[0] == x);
+    CHECK(xyz[1] == y);
+    CHECK(xyz[2] == z);
+    */
+}
+
+TEST_CASE( "vector tile projection 3", "should support z/x/y to bbox 
conversion for z3" ) {
+    mapnik::vector_tile_impl::spherical_mercator merc(256);
+    int x = 3;
+    int y = 3;
+    int z = 3;
+    double 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(-5009377.085697311,0.0,0.0,5009377.085697311);
+    double epsilon = 0.00000001;
+    CHECK(std::fabs(map_extent.minx() - e.minx()) < epsilon);
+    CHECK(std::fabs(map_extent.miny() - e.miny()) < epsilon);
+    CHECK(std::fabs(map_extent.maxx() - e.maxx()) < epsilon);
+    CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon);
+    auto xyz = bboxToXYZ(map_extent);
+    /*
+    CHECK(xyz[0] == x);
+    CHECK(xyz[1] == y);
+    CHECK(xyz[2] == z);
+    */
+}
diff --git a/test/vector_tile_rasterize.cpp b/test/vector_tile_rasterize.cpp
new file mode 100644
index 0000000..6954017
--- /dev/null
+++ b/test/vector_tile_rasterize.cpp
@@ -0,0 +1,179 @@
+#include "catch.hpp"
+
+// test utils
+#include "test_utils.hpp"
+#include <mapnik/util/fs.hpp>
+#include <mapnik/agg_renderer.hpp>
+#include <mapnik/load_map.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/datasource_cache.hpp>
+
+// vector output api
+#include "vector_tile_compression.hpp"
+#include "vector_tile_processor.hpp"
+#include "vector_tile_strategy.hpp"
+#include "vector_tile_backend_pbf.hpp"
+#include "vector_tile_util.hpp"
+#include "vector_tile_projection.hpp"
+#include "vector_tile_geometry_decoder.hpp"
+#include "vector_tile_datasource.hpp"
+#include "vector_tile_datasource_pbf.hpp"
+#include "protozero/pbf_reader.hpp"
+
+#include <boost/optional/optional_io.hpp>
+
+#include <fstream>
+
+TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
+    // open vtile
+    std::ifstream 
stream("./test/data/0.0.0.vector.pbf",std::ios_base::in|std::ios_base::binary);
+    REQUIRE(stream.is_open());
+    std::string 
buffer(std::istreambuf_iterator<char>(stream.rdbuf()),(std::istreambuf_iterator<char>()));
+    REQUIRE(buffer.size() == 3812);
+
+    // uncompress gzip data
+    std::string uncompressed;
+    mapnik::vector_tile_impl::zlib_decompress(buffer,uncompressed);
+    REQUIRE(uncompressed.size() == 4934);
+
+    typedef vector_tile::Tile tile_type;
+    tile_type tile;
+    unsigned tile_size = 256;
+    mapnik::box2d<double> 
bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+
+    // first we render the raw tile directly to an image
+    {
+        mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+        tile_type tile2;
+        CHECK(tile2.ParseFromString(uncompressed));
+        std::string key("");
+        CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
+        CHECK("" == key);
+        CHECK(false == 
mapnik::vector_tile_impl::is_solid_extent(uncompressed,key));
+        CHECK("" == key);
+
+        CHECK(1 == tile2.layers_size());
+        vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
+        CHECK(std::string("water") == layer2.name());
+        CHECK(23 == layer2.features_size());
+
+        mapnik::layer lyr2("water",map.srs());
+        std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds = 
std::make_shared<
+                                        
mapnik::vector_tile_impl::tile_datasource>(
+                                            layer2,0,0,0,map.width());
+        ds->set_envelope(bbox);
+        CHECK( ds->type() == mapnik::datasource::Vector );
+        CHECK( ds->get_geometry_type() == 
mapnik::datasource_geometry_t::Collection );
+        mapnik::layer_descriptor lay_desc = ds->get_descriptor();
+        std::vector<std::string> expected_names;
+        expected_names.push_back("osm_id");
+        std::vector<std::string> names;
+        for (auto const& desc : lay_desc.get_descriptors())
+        {
+            names.push_back(desc.get_name());
+        }
+        CHECK(names == expected_names);
+        lyr2.set_datasource(ds);
+        lyr2.add_style("style");
+        map.add_layer(lyr2);
+        mapnik::load_map(map,"test/data/polygon-style.xml");
+        //std::clog << mapnik::save_map_to_string(map) << "\n";
+        map.zoom_to_box(bbox);
+        mapnik::image_rgba8 im(map.width(),map.height());
+        mapnik::agg_renderer<mapnik::image_rgba8> ren(map,im);
+        ren.apply();
+        if (!mapnik::util::exists("test/fixtures/rasterize-expected-1.png")) {
+            
mapnik::save_to_file(im,"test/fixtures/rasterize-expected-1.png","png32");
+        }
+    }
+
+    // set up to "re-render" it
+    // the goal here is to trigger the geometries to pass through
+    // the decoder and encoder again
+    {
+        typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+        typedef mapnik::vector_tile_impl::processor<backend_type> 
renderer_type;
+        backend_type backend(tile,16);
+        std::string merc_srs("+init=epsg:3857");
+        mapnik::Map map(tile_size,tile_size,merc_srs);
+        map.zoom_to_box(bbox);
+        mapnik::request 
m_req(map.width(),map.height(),map.get_current_extent());
+        protozero::pbf_reader message(uncompressed.data(),uncompressed.size());
+        while (message.next(3)) {
+            protozero::pbf_reader layer_msg = message.get_message();
+            auto ds = 
std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(
+                        layer_msg,
+                        0,
+                        0,
+                        0,
+                        tile_size);
+            mapnik::layer lyr(ds->get_name(),merc_srs);
+            ds->set_envelope(m_req.get_buffered_extent());
+            lyr.set_datasource(ds);
+            map.add_layer(lyr);
+        }
+        renderer_type ren(backend,map,m_req);
+        ren.set_process_all_rings(true);
+        ren.apply();
+    }
+    // now `tile` should contain all the data
+    std::string buffer2;
+    CHECK(tile.SerializeToString(&buffer2));
+    CHECK(2774 == buffer2.size());
+
+    std::ofstream 
stream_out("./test/data/0.0.0.vector-b.pbf",std::ios_base::out|std::ios_base::binary);
+    stream_out << buffer2;
+    stream_out.close();
+
+    // let's now render this to a image and make sure it looks right
+    {
+        mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+        tile_type tile2;
+        CHECK(tile2.ParseFromString(buffer2));
+        std::string key("");
+        CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
+        CHECK("" == key);
+        CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer2,key));
+        CHECK("" == key);
+
+        CHECK(1 == tile2.layers_size());
+        vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
+        CHECK(std::string("water") == layer2.name());
+        CHECK(5 == layer2.features_size());
+
+        mapnik::layer lyr2("water",map.srs());
+        std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds = 
std::make_shared<
+                                        
mapnik::vector_tile_impl::tile_datasource>(
+                                            layer2,0,0,0,map.width());
+        ds->set_envelope(bbox);
+        CHECK( ds->type() == mapnik::datasource::Vector );
+        CHECK( ds->get_geometry_type() == 
mapnik::datasource_geometry_t::Collection );
+        mapnik::layer_descriptor lay_desc = ds->get_descriptor();
+        std::vector<std::string> expected_names;
+        expected_names.push_back("osm_id");
+        std::vector<std::string> names;
+        for (auto const& desc : lay_desc.get_descriptors())
+        {
+            names.push_back(desc.get_name());
+        }
+        CHECK(names == expected_names);
+        lyr2.set_datasource(ds);
+        lyr2.add_style("style");
+        map.add_layer(lyr2);
+        mapnik::load_map(map,"test/data/polygon-style.xml");
+        //std::clog << mapnik::save_map_to_string(map) << "\n";
+        map.zoom_to_box(bbox);
+        mapnik::image_rgba8 im(map.width(),map.height());
+        mapnik::agg_renderer<mapnik::image_rgba8> ren(map,im);
+        ren.apply();
+        unsigned diff = 
testing::compare_images(im,"test/fixtures/rasterize-expected-1.png");
+        // should be almost equal (49 is good enough since re-rendering 
filters a few small degenerates)
+        CHECK(49 == diff);
+        if (diff > 49) {
+            
mapnik::save_to_file(im,"test/fixtures/rasterize-actual-1.png","png32");
+        }
+    }
+
+}
+

-- 
Alioth's /usr/local/bin/git-commit-notice on 
/srv/git.debian.org/git/pkg-grass/mapnik-vector-tile.git

_______________________________________________
Pkg-grass-devel mailing list
Pkg-grass-devel@lists.alioth.debian.org
http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel

Reply via email to