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