[mapnik-vector-tile] 01/05: Imported Upstream version 0.14.0+dfsg
Sebastiaan Couwenberg
sebastic at moszumanska.debian.org
Sat Oct 31 09:50:08 UTC 2015
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 <sebastic at 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
More information about the Pkg-grass-devel
mailing list