[mapnik-vector-tile] 04/15: Imported Upstream version 0.8.4+dfsg
Sebastiaan Couwenberg
sebastic at moszumanska.debian.org
Sat Sep 12 11:06:50 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 137d9fdb40429d6803f93e86ef937f707cf97b1a
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date: Fri Sep 11 22:55:15 2015 +0200
Imported Upstream version 0.8.4+dfsg
---
CHANGELOG.md | 8 +
bench/vtile-transform.cpp | 25 ++-
package.json | 2 +-
src/vector_tile_backend_pbf.hpp | 11 +-
src/vector_tile_geometry_decoder.hpp | 374 +++++++++++++++++------------------
src/vector_tile_processor.hpp | 12 +-
src/vector_tile_processor.ipp | 191 ++++++++++--------
src/vector_tile_strategy.hpp | 55 +++++-
test/clipper_test.cpp | 59 +++++-
test/data/NZ_Coastline_NZMG.dbf | Bin 0 -> 143 bytes
test/data/NZ_Coastline_NZMG.prj | 1 +
test/data/NZ_Coastline_NZMG.qpj | 1 +
test/data/NZ_Coastline_NZMG.shp | Bin 0 -> 1268 bytes
test/data/NZ_Coastline_NZMG.shx | Bin 0 -> 116 bytes
test/data/invalid-interior-ring.json | 95 +++++++++
test/geometry_encoding.cpp | 122 +++---------
test/test_utils.cpp | 16 ++
test/test_utils.hpp | 1 +
test/vector_tile.cpp | 102 +++++++++-
test/vector_tile_pbf.cpp | 20 +-
20 files changed, 692 insertions(+), 403 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad096c4..d38b756 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,15 @@
# Changelog
+## 0.8.4
+
+ - Started to skip coordinates that are out of range (#121)
+ - Fix clipping box used when reprojecting data on the fly (#128)
+ - Fixed decoding of degenerate polygons - we need to gracefully support these as they
+ are commonly in the wild based on that AGG clipper used in v0.7.1 and earlier (#123)
+
## 0.8.3
+ - Started to skip coordinates that cannot be reprojected (#117)
- Minor optimization in attribute encoding by using `emplace` instead of `insert`
- Now depends on `pbf_writer.hpp` for zigzag implementation (no change in behavior)
- Minor code cleanup to avoid unnecessary compiler warnings
diff --git a/bench/vtile-transform.cpp b/bench/vtile-transform.cpp
index 01cc25f..772a90d 100644
--- a/bench/vtile-transform.cpp
+++ b/bench/vtile-transform.cpp
@@ -46,8 +46,9 @@ int main() {
unsigned count = 0;
unsigned count2 = 0;
+ unsigned count3 = 0;
{
- mapnik::vector_tile_impl::vector_tile_strategy vs(prj_trans, tr, 16);
+ mapnik::vector_tile_impl::vector_tile_strategy vs(tr, 16);
mapnik::progress_timer __stats__(std::clog, "boost::geometry::transform");
for (unsigned i=0;i<10000;++i)
{
@@ -57,9 +58,9 @@ int main() {
}
}
{
- mapnik::vector_tile_impl::vector_tile_strategy vs(prj_trans, tr, 16);
- mapnik::progress_timer __stats__(std::clog, "transform_visitor with reserve");
- mapnik::vector_tile_impl::transform_visitor transit(vs);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans,tr, 16);
+ mapnik::progress_timer __stats__(std::clog, "transform_visitor with reserve with proj no-op");
+ mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy_proj> transit(vs);
for (unsigned i=0;i<10000;++i)
{
mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(transit,geom);
@@ -72,5 +73,21 @@ int main() {
return -1;
}
}
+ {
+ mapnik::vector_tile_impl::vector_tile_strategy vs(tr, 16);
+ mapnik::progress_timer __stats__(std::clog, "transform_visitor with reserve with no proj function call overhead");
+ mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy> transit(vs);
+ for (unsigned i=0;i<10000;++i)
+ {
+ mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(transit,geom);
+ auto const& poly = mapnik::util::get<mapnik::geometry::multi_polygon<std::int64_t>>(new_geom);
+ count3 += poly.size();
+ }
+ if (count != count3)
+ {
+ std::clog << "tests did not run as expected!\n";
+ return -1;
+ }
+ }
return 0;
}
\ No newline at end of file
diff --git a/package.json b/package.json
index 311f840..7dfbbd3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mapnik-vector-tile",
- "version": "0.8.3",
+ "version": "0.8.4",
"description": "Mapnik vector tile API",
"main": "./package.json",
"repository" : {
diff --git a/src/vector_tile_backend_pbf.hpp b/src/vector_tile_backend_pbf.hpp
index 3198e19..0090663 100644
--- a/src/vector_tile_backend_pbf.hpp
+++ b/src/vector_tile_backend_pbf.hpp
@@ -12,12 +12,11 @@
#include "vector_tile_geometry_encoder.hpp"
#include <mapnik/value.hpp>
#include <mapnik/geometry.hpp>
+#include <unordered_map>
-// boost
-#include <boost/unordered_map.hpp>
-
-namespace mapnik {
- class feature_impl;
+namespace mapnik
+{
+class feature_impl;
}
namespace mapnik { namespace vector_tile_impl {
@@ -25,7 +24,7 @@ namespace mapnik { namespace vector_tile_impl {
struct backend_pbf
{
typedef std::map<std::string, unsigned> keys_container;
- typedef boost::unordered_map<mapnik::value, unsigned> values_container;
+ typedef std::unordered_map<mapnik::value, unsigned> values_container;
private:
vector_tile::Tile & tile_;
unsigned path_multiplier_;
diff --git a/src/vector_tile_geometry_decoder.hpp b/src/vector_tile_geometry_decoder.hpp
index 1a5d9da..c3abdf8 100644
--- a/src/vector_tile_geometry_decoder.hpp
+++ b/src/vector_tile_geometry_decoder.hpp
@@ -10,7 +10,6 @@
#include "pbf_reader.hpp"
#include <mapnik/util/is_clockwise.hpp>
-
//std
#include <algorithm>
@@ -55,12 +54,13 @@ public:
double tile_x, double tile_y,
double scale_x, double scale_y);
- enum command : uint8_t {
+ enum command : uint8_t
+ {
end = 0,
- move_to = 1,
- line_to = 2,
- close = 7
- };
+ move_to = 1,
+ line_to = 2,
+ close = 7
+ };
inline command next(double& rx, double& ry);
@@ -87,9 +87,12 @@ Geometry::Geometry(vector_tile::Tile_Feature const& f,
x(tile_x), y(tile_y),
ox(0), oy(0) {}
-Geometry::command Geometry::next(double& rx, double& ry) {
- if (k < geoms_) {
- if (length == 0) {
+Geometry::command Geometry::next(double& rx, double& ry)
+{
+ if (k < geoms_)
+ {
+ if (length == 0)
+ {
uint32_t cmd_length = static_cast<uint32_t>(f_.geometry(k++));
cmd = cmd_length & 0x7;
length = cmd_length >> 3;
@@ -97,7 +100,8 @@ Geometry::command Geometry::next(double& rx, double& ry) {
--length;
- if (cmd == move_to || cmd == line_to) {
+ if (cmd == move_to || cmd == line_to)
+ {
int32_t dx = f_.geometry(k++);
int32_t dy = f_.geometry(k++);
dx = ((dx >> 1) ^ (-(dx & 1)));
@@ -113,11 +117,15 @@ Geometry::command Geometry::next(double& rx, double& ry) {
} else {
return line_to;
}
- } else if (cmd == close) {
+ }
+ else if (cmd == close)
+ {
rx = ox;
ry = oy;
return close;
- } else {
+ }
+ else
+ {
fprintf(stderr, "unknown command: %d\n", cmd);
return end;
}
@@ -137,9 +145,12 @@ GeometryPBF::GeometryPBF(std::pair<mapbox::util::pbf::const_uint32_iterator, map
x(tile_x), y(tile_y),
ox(0), oy(0) {}
-GeometryPBF::command GeometryPBF::next(double& rx, double& ry) {
- if (geo_iterator_.first != geo_iterator_.second) {
- if (length == 0) {
+GeometryPBF::command GeometryPBF::next(double& rx, double& ry)
+{
+ if (geo_iterator_.first != geo_iterator_.second)
+ {
+ if (length == 0)
+ {
uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
cmd = cmd_length & 0x7;
length = cmd_length >> 3;
@@ -147,7 +158,8 @@ GeometryPBF::command GeometryPBF::next(double& rx, double& ry) {
--length;
- if (cmd == move_to || cmd == line_to) {
+ if (cmd == move_to || cmd == line_to)
+ {
int32_t dx = *geo_iterator_.first++;
int32_t dy = *geo_iterator_.first++;
dx = ((dx >> 1) ^ (-(dx & 1)));
@@ -156,244 +168,224 @@ GeometryPBF::command GeometryPBF::next(double& rx, double& ry) {
y += (static_cast<double>(dy) / scale_y_);
rx = x;
ry = y;
- if (cmd == move_to) {
+ if (cmd == move_to)
+ {
ox = x;
oy = y;
return move_to;
- } else {
+ }
+ else
+ {
return line_to;
}
- } else if (cmd == close) {
+ }
+ else if (cmd == close)
+ {
rx = ox;
ry = oy;
return close;
- } else {
+ }
+ else
+ {
fprintf(stderr, "unknown command: %d\n", cmd);
return end;
}
- } else {
+ }
+ else
+ {
return end;
}
}
+namespace detail {
+
template <typename T>
-inline mapnik::geometry::geometry<double> decode_geometry(T & geoms, int32_t geom_type,
- bool treat_all_rings_as_exterior=false)
+void decode_point(mapnik::geometry::geometry<double> & geom, T & paths)
{
typename T::command cmd;
double x1, y1;
- mapnik::geometry::geometry<double> geom; // output geometry
-
- switch (geom_type)
+ mapnik::geometry::multi_point<double> mp;
+ while ((cmd = paths.next(x1, y1)) != T::end)
{
- case vector_tile::Tile_GeomType_POINT:
+ mp.emplace_back(mapnik::geometry::point<double>(x1,y1));
+ }
+ std::size_t num_points = mp.size();
+ if (num_points == 1)
{
- mapnik::geometry::multi_point<double> mp;
- while ((cmd = geoms.next(x1, y1)) != T::end)
- {
- mp.emplace_back(mapnik::geometry::point<double>(x1,y1));
- }
- std::size_t num_points = mp.size();
- if (num_points == 1)
- {
- // return the single point
- geom = std::move(mp[0]);
- return geom;
- }
- else if (num_points > 1)
+ geom = std::move(mp[0]);
+ }
+ else if (num_points > 1)
+ {
+ // return multipoint
+ geom = std::move(mp);
+ }
+}
+
+template <typename T>
+void decode_linestring(mapnik::geometry::geometry<double> & geom, T & paths)
+{
+ typename T::command cmd;
+ double x1, y1;
+ mapnik::geometry::multi_line_string<double> multi_line;
+ multi_line.emplace_back();
+ bool first = true;
+ while ((cmd = paths.next(x1, y1)) != T::end)
+ {
+ if (cmd == T::move_to)
{
- // return multipoint
- geom = std::move(mp);
- return geom;
+ if (first) first = false;
+ else multi_line.emplace_back();
}
- break;
+ multi_line.back().add_coord(x1,y1);
}
- case vector_tile::Tile_GeomType_LINESTRING:
+ std::size_t num_lines = multi_line.size();
+ if (num_lines == 1)
{
- mapnik::geometry::multi_line_string<double> multi_line;
- multi_line.emplace_back();
- bool first = true;
- while ((cmd = geoms.next(x1, y1)) != T::end)
+ auto itr = std::make_move_iterator(multi_line.begin());
+ if (itr->size() > 1)
{
- if (cmd == T::move_to)
- {
- if (first)
- {
- first = false;
- }
- else
- {
- multi_line.emplace_back();
- }
- }
- multi_line.back().add_coord(x1,y1);
+ geom = std::move(*itr);
}
- if (multi_line.empty())
+ }
+ else if (num_lines > 1)
+ {
+ geom = std::move(multi_line);
+ }
+}
+
+template <typename T>
+std::vector<mapnik::geometry::linear_ring<double>> read_rings(T & paths)
+{
+ typename T::command cmd;
+ double x1, y1;
+ std::vector<mapnik::geometry::linear_ring<double>> rings;
+ rings.emplace_back();
+ double x2,y2;
+ bool first = true;
+ while ((cmd = paths.next(x1, y1)) != T::end)
+ {
+ if (cmd == T::move_to)
{
- return geom;
+ x2 = x1;
+ y2 = y1;
+ if (first) first = false;
+ else rings.emplace_back();
}
- std::size_t num_lines = multi_line.size();
- if (num_lines == 1)
+ else if (cmd == T::close)
{
- // return the single line
- auto itr = std::make_move_iterator(multi_line.begin());
- if (itr->size() > 1)
+ auto & ring = rings.back();
+ if (ring.size() > 2 && !(ring.back().x == x2 && ring.back().y == y2))
{
- geom = std::move(*itr);
+ ring.add_coord(x2,y2);
}
- return geom;
+ continue;
}
- else if (num_lines > 1)
+ rings.back().add_coord(x1,y1);
+ }
+ return rings;
+}
+
+template <typename T>
+void decode_polygons(mapnik::geometry::geometry<double> & geom, T && rings)
+{
+ auto rings_itr = std::make_move_iterator(rings.begin());
+ auto rings_end = std::make_move_iterator(rings.end());
+ std::size_t num_rings = rings.size();
+ if (num_rings == 1)
+ {
+ if (rings_itr->size() < 4) return;
+ if (mapnik::util::is_clockwise(*rings_itr))
{
- // return multiline
- geom = std::move(multi_line);
- return geom;
+ // Its clockwise, so lets reverse it.
+ std::reverse(rings_itr->begin(), rings_itr->end());
}
- break;
+ // return the single polygon without interior rings
+ mapnik::geometry::polygon<double> poly;
+ poly.set_exterior_ring(std::move(*rings_itr));
+ geom = std::move(poly);
}
- case vector_tile::Tile_GeomType_POLYGON:
+ else
{
- std::vector<mapnik::geometry::linear_ring<double>> rings;
- rings.emplace_back();
- double x2,y2;
+ mapnik::geometry::multi_polygon<double> multi_poly;
bool first = true;
- while ((cmd = geoms.next(x1, y1)) != T::end)
+ bool is_clockwise = true;
+ for (; rings_itr != rings_end; ++rings_itr)
{
- if (cmd == T::move_to)
+ if (rings_itr->size() < 4) continue; // skip degenerate rings
+ if (first)
{
- x2 = x1;
- y2 = y1;
- if (first)
- {
- first = false;
- }
- else
+ is_clockwise = mapnik::util::is_clockwise(*rings_itr);
+ // first ring always exterior and sets all future winding order
+ multi_poly.emplace_back();
+ if (is_clockwise)
{
- rings.emplace_back();
+ // Going into mapnik we want the outer ring to be CCW
+ std::reverse(rings_itr->begin(), rings_itr->end());
}
+ multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+ first = false;
}
- else if (cmd == T::close)
- {
- rings.back().add_coord(x2,y2);
- continue;
- }
- rings.back().add_coord(x1,y1);
- }
-
- auto rings_itr = std::make_move_iterator(rings.begin());
- auto rings_end = std::make_move_iterator(rings.end());
- std::size_t num_rings = rings.size();
- if (num_rings == 1)
- {
- if (rings_itr->size() < 4)
- {
- return geom;
- }
- if (mapnik::util::is_clockwise(*rings_itr))
+ else if (is_clockwise == mapnik::util::is_clockwise(*rings_itr))
{
- // Its clockwise, so lets reverse it.
- std::reverse(rings_itr->begin(), rings_itr->end());
- }
- // return the single polygon without interior rings
- mapnik::geometry::polygon<double> poly;
- poly.set_exterior_ring(std::move(*rings_itr));
- geom = std::move(poly);
- return geom;
- }
-
- // Multiple rings represent either:
- // 1) a polygon with interior ring(s)
- // 2) a multipolygon with polygons with no interior ring(s)
- // 3) a multipolygon with polygons with interior ring(s)
- mapnik::geometry::multi_polygon<double> multi_poly;
- first = true;
- // back compatibility mode to previous Mapnik (pre new geometry)
- // which pushed all rings into single path
- if (treat_all_rings_as_exterior)
- {
- for (; rings_itr != rings_end; ++rings_itr)
- {
- bool degenerate_ring = (rings_itr->size() < 4);
- if (degenerate_ring) continue;
- multi_poly.emplace_back();
- if (mapnik::util::is_clockwise(*rings_itr))
+ // hit a new exterior ring, so start a new polygon
+ multi_poly.emplace_back(); // start new polygon
+ if (is_clockwise)
{
- // Its clockwise, so lets reverse it.
+ // Going into mapnik we want the outer ring to be CCW,
+ // since first winding order was CW, we need to reverse
+ // these rings.
std::reverse(rings_itr->begin(), rings_itr->end());
}
multi_poly.back().set_exterior_ring(std::move(*rings_itr));
}
- }
- else
- {
- bool exterior_was_degenerate = false;
- bool first_winding_order = true;
- for (; rings_itr != rings_end; ++rings_itr)
+ else
{
- bool degenerate_ring = (rings_itr->size() < 4);
- if (first)
- {
- if (degenerate_ring)
- {
- exterior_was_degenerate = true;
- continue;
- }
- first_winding_order = mapnik::util::is_clockwise(*rings_itr);
- // first ring always exterior and sets all future winding order
- multi_poly.emplace_back();
- if (first_winding_order)
- {
- // Going into mapnik we want the outer ring to be CCW
- std::reverse(rings_itr->begin(), rings_itr->end());
- }
- multi_poly.back().set_exterior_ring(std::move(*rings_itr));
- first = false;
- }
- else if (first_winding_order == mapnik::util::is_clockwise(*rings_itr))
- {
- if (degenerate_ring) continue;
- // hit a new exterior ring, so start a new polygon
- multi_poly.emplace_back(); // start new polygon
- if (first_winding_order)
- {
- // Going into mapnik we want the outer ring to be CCW,
- // since first winding order was CW, we need to reverse
- // these rings.
- std::reverse(rings_itr->begin(), rings_itr->end());
- }
- multi_poly.back().set_exterior_ring(std::move(*rings_itr));
- exterior_was_degenerate = false;
- }
- else
+ if (is_clockwise)
{
- if (exterior_was_degenerate || degenerate_ring) continue;
- if (first_winding_order)
- {
- // Going into mapnik we want the inner ring to be CW,
- // since first winding order of the outer ring CW, we
- // need to reverse these rings as they are CCW.
- std::reverse(rings_itr->begin(), rings_itr->end());
- }
- multi_poly.back().add_hole(std::move(*rings_itr));
+ // Going into mapnik we want the inner ring to be CW,
+ // since first winding order of the outer ring CW, we
+ // need to reverse these rings as they are CCW.
+ std::reverse(rings_itr->begin(), rings_itr->end());
}
+ multi_poly.back().add_hole(std::move(*rings_itr));
}
}
+
auto num_poly = multi_poly.size();
- if (num_poly == 0)
- {
- return geom;
- }
- else if (num_poly == 1)
+ if (num_poly == 1)
{
auto itr = std::make_move_iterator(multi_poly.begin());
geom = mapnik::geometry::polygon<double>(std::move(*itr));
- return geom;
}
else
{
geom = std::move(multi_poly);
- return geom;
}
+ }
+}
+
+} // ns detail
+
+template <typename T>
+inline mapnik::geometry::geometry<double> decode_geometry(T & paths, int32_t geom_type)
+{
+ mapnik::geometry::geometry<double> geom; // output geometry
+ switch (geom_type)
+ {
+ case vector_tile::Tile_GeomType_POINT:
+ {
+ detail::decode_point(geom, paths);
+ break;
+ }
+ case vector_tile::Tile_GeomType_LINESTRING:
+ {
+ detail::decode_linestring(geom, paths);
+ break;
+ }
+ case vector_tile::Tile_GeomType_POLYGON:
+ {
+ auto rings = detail::read_rings(paths);
+ detail::decode_polygons(geom, rings);
break;
}
case vector_tile::Tile_GeomType_UNKNOWN:
diff --git a/src/vector_tile_processor.hpp b/src/vector_tile_processor.hpp
index 3cd41ea..2bf2808 100644
--- a/src/vector_tile_processor.hpp
+++ b/src/vector_tile_processor.hpp
@@ -63,6 +63,11 @@ public:
return simplify_distance_;
}
+ inline mapnik::view_transform const& get_transform()
+ {
+ return t_;
+ }
+
MAPNIK_VECTOR_INLINE void apply(double scale_denom=0.0);
MAPNIK_VECTOR_INLINE bool painted() const;
@@ -76,10 +81,11 @@ public:
box2d<double> const& extent,
int buffer_size);
- MAPNIK_VECTOR_INLINE unsigned handle_geometry(mapnik::feature_impl const& feature,
+ template <typename T2>
+ MAPNIK_VECTOR_INLINE unsigned handle_geometry(T2 const& vs,
+ mapnik::feature_impl const& feature,
mapnik::geometry::geometry<double> const& geom,
- mapnik::proj_transform const& prj_trans,
- mapnik::box2d<double> const& buffered_query_ext);
+ mapnik::box2d<int> const& tile_clipping_extent);
};
}} // end ns
diff --git a/src/vector_tile_processor.ipp b/src/vector_tile_processor.ipp
index 31ea8bf..72da363 100644
--- a/src/vector_tile_processor.ipp
+++ b/src/vector_tile_processor.ipp
@@ -640,7 +640,7 @@ bool processor<T>::painted() const
template <typename T>
void processor<T>::apply_to_layer(mapnik::layer const& lay,
- mapnik::projection const& proj0,
+ mapnik::projection const& target_proj,
double scale,
double scale_denom,
unsigned width,
@@ -651,10 +651,21 @@ void processor<T>::apply_to_layer(mapnik::layer const& lay,
mapnik::datasource_ptr ds = lay.datasource();
if (!ds) return;
- mapnik::projection proj1(lay.srs(),true);
- mapnik::proj_transform prj_trans(proj0,proj1);
- box2d<double> query_ext = extent; // unbuffered
- box2d<double> buffered_query_ext(query_ext); // buffered
+ mapnik::projection source_proj(lay.srs(),true);
+
+ // set up a transform from target to source
+ // target == final map (aka tile) projection, usually epsg:3857
+ // source == projection of the data being queried
+ mapnik::proj_transform prj_trans(target_proj,source_proj);
+
+ // working version of unbuffered extent
+ box2d<double> query_ext(extent);
+
+ // working version of buffered extent
+ box2d<double> buffered_query_ext(query_ext);
+
+ // transform the user-driven buffer size into the right
+ // size buffer into the target projection
double buffer_padding = 2.0 * scale;
boost::optional<int> layer_buffer_size = lay.buffer_size();
if (layer_buffer_size) // if layer overrides buffer size, use this value to compute buffered extent
@@ -667,19 +678,29 @@ void processor<T>::apply_to_layer(mapnik::layer const& lay,
}
buffered_query_ext.width(query_ext.width() + buffer_padding);
buffered_query_ext.height(query_ext.height() + buffer_padding);
+
+ // ^^ now `buffered_query_ext` is actually buffered out.
+
// clip buffered extent by maximum extent, if supplied
- boost::optional<box2d<double> > const& maximum_extent = m_.maximum_extent();
+ // Note: Carto.js used to set this by default but no longer does after:
+ // https://github.com/mapbox/carto/pull/342
+ boost::optional<box2d<double>> const& maximum_extent = m_.maximum_extent();
if (maximum_extent)
{
buffered_query_ext.clip(*maximum_extent);
}
+
+ // buffered_query_ext is transformed below
+ // into the coordinate system of the source data
+ // so grab a pristine copy of it to use later
+ box2d<double> target_clipping_extent(buffered_query_ext);
+
mapnik::box2d<double> layer_ext = lay.envelope();
- bool fw_success = false;
bool early_return = false;
// first, try intersection of map extent forward projected into layer srs
if (prj_trans.forward(buffered_query_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext))
{
- fw_success = true;
+ // this modifies the layer_ext by clipping to the buffered_query_ext
layer_ext.clip(buffered_query_ext);
}
// if no intersection and projections are also equal, early return
@@ -709,28 +730,6 @@ void processor<T>::apply_to_layer(mapnik::layer const& lay,
return;
}
- // if we've got this far, now prepare the unbuffered extent
- // which is used as a bbox for clipping geometries
- if (maximum_extent)
- {
- query_ext.clip(*maximum_extent);
- }
- mapnik::box2d<double> layer_ext2 = lay.envelope();
- if (fw_success)
- {
- if (prj_trans.forward(query_ext, PROJ_ENVELOPE_POINTS))
- {
- layer_ext2.clip(query_ext);
- }
- }
- else
- {
- if (prj_trans.backward(layer_ext2, PROJ_ENVELOPE_POINTS))
- {
- layer_ext2.clip(query_ext);
- prj_trans.forward(layer_ext2, PROJ_ENVELOPE_POINTS);
- }
- }
double qw = query_ext.width()>0 ? query_ext.width() : 1;
double qh = query_ext.height()>0 ? query_ext.height() : 1;
mapnik::query::resolution_type res(width/qw,
@@ -784,22 +783,58 @@ void processor<T>::apply_to_layer(mapnik::layer const& lay,
return;
}
// vector pathway
- while (feature)
+ if (prj_trans.equal())
{
- mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
- if (mapnik::geometry::is_empty(geom))
+ mapnik::vector_tile_impl::vector_tile_strategy vs(t_,backend_.get_path_multiplier());
+ mapnik::geometry::point<double> p1_min(target_clipping_extent.minx(), target_clipping_extent.miny());
+ mapnik::geometry::point<double> p1_max(target_clipping_extent.maxx(), target_clipping_extent.maxy());
+ mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
+ mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
+ box2d<int> tile_clipping_extent(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
+ while (feature)
{
+ mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
+ if (mapnik::geometry::is_empty(geom))
+ {
+ feature = features->next();
+ continue;
+ }
+ if (handle_geometry(vs,
+ *feature,
+ geom,
+ tile_clipping_extent) > 0)
+ {
+ painted_ = true;
+ }
feature = features->next();
- continue;
}
- if (handle_geometry(*feature,
- geom,
- prj_trans,
- buffered_query_ext) > 0)
+ }
+ else
+ {
+ mapnik::vector_tile_impl::vector_tile_strategy vs(t_,backend_.get_path_multiplier());
+ mapnik::geometry::point<double> p1_min(target_clipping_extent.minx(), target_clipping_extent.miny());
+ mapnik::geometry::point<double> p1_max(target_clipping_extent.maxx(), target_clipping_extent.maxy());
+ mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
+ mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
+ box2d<int> tile_clipping_extent(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs2(prj_trans,t_,backend_.get_path_multiplier());
+ while (feature)
{
- painted_ = true;
+ mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
+ if (mapnik::geometry::is_empty(geom))
+ {
+ feature = features->next();
+ continue;
+ }
+ if (handle_geometry(vs2,
+ *feature,
+ geom,
+ tile_clipping_extent) > 0)
+ {
+ painted_ = true;
+ }
+ feature = features->next();
}
- feature = features->next();
}
backend_.stop_tile_layer();
}
@@ -852,11 +887,11 @@ struct encoder_visitor {
typedef T backend_type;
encoder_visitor(backend_type & backend,
mapnik::feature_impl const& feature,
- mapnik::box2d<int> const& buffered_query_ext,
+ mapnik::box2d<int> const& tile_clipping_extent,
double area_threshold) :
backend_(backend),
feature_(feature),
- buffered_query_ext_(buffered_query_ext),
+ tile_clipping_extent_(tile_clipping_extent),
area_threshold_(area_threshold) {}
unsigned operator() (mapnik::geometry::geometry_empty const&)
@@ -877,7 +912,7 @@ struct encoder_visitor {
unsigned operator() (mapnik::geometry::point<std::int64_t> const& geom)
{
unsigned path_count = 0;
- if (buffered_query_ext_.intersects(geom.x,geom.y))
+ if (tile_clipping_extent_.intersects(geom.x,geom.y))
{
backend_.start_tile_feature(feature_);
backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POINT);
@@ -893,7 +928,7 @@ struct encoder_visitor {
bool first = true;
for (auto const& pt : geom)
{
- if (buffered_query_ext_.intersects(pt.x,pt.y))
+ if (tile_clipping_extent_.intersects(pt.x,pt.y))
{
if (first)
{
@@ -917,11 +952,11 @@ struct encoder_visitor {
if (geom.size() < 2) return 0;
std::deque<mapnik::geometry::line_string<int64_t>> result;
mapnik::geometry::linear_ring<std::int64_t> clip_box;
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
- clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.miny());
- clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.maxy());
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.maxy());
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
boost::geometry::intersection(clip_box,geom,result);
if (!result.empty())
{
@@ -940,11 +975,11 @@ struct encoder_visitor {
{
unsigned path_count = 0;
mapnik::geometry::linear_ring<std::int64_t> clip_box;
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
- clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.miny());
- clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.maxy());
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.maxy());
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
bool first = true;
for (auto const& line : geom)
{
@@ -980,12 +1015,12 @@ struct encoder_visitor {
unsigned path_count = 0;
if (geom.exterior_ring.size() < 3) return 0;
double clean_distance = 1.415;
- mapnik::geometry::line_string<std::int64_t> clip_box;
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
- clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.miny());
- clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.maxy());
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.maxy());
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ mapnik::geometry::linear_ring<std::int64_t> clip_box;
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
ClipperLib::Clipper clipper;
ClipperLib::CleanPolygon(geom.exterior_ring, clean_distance);
double outer_area = ClipperLib::Area(geom.exterior_ring);
@@ -1070,12 +1105,12 @@ struct encoder_visitor {
if (geom.empty()) return 0;
double clean_distance = 1.415;
- mapnik::geometry::line_string<std::int64_t> clip_box;
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
- clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.miny());
- clip_box.emplace_back(buffered_query_ext_.maxx(),buffered_query_ext_.maxy());
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.maxy());
- clip_box.emplace_back(buffered_query_ext_.minx(),buffered_query_ext_.miny());
+ mapnik::geometry::linear_ring<std::int64_t> clip_box;
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.miny());
+ clip_box.emplace_back(tile_clipping_extent_.maxx(),tile_clipping_extent_.maxy());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.maxy());
+ clip_box.emplace_back(tile_clipping_extent_.minx(),tile_clipping_extent_.miny());
ClipperLib::Clipper clipper;
for (auto & poly : geom)
{
@@ -1160,7 +1195,7 @@ struct encoder_visitor {
backend_type & backend_;
mapnik::feature_impl const& feature_;
- mapnik::box2d<int> const& buffered_query_ext_;
+ mapnik::box2d<int> const& tile_clipping_extent_;
double area_threshold_;
};
@@ -1230,21 +1265,19 @@ struct simplify_visitor {
};
-template <typename T>
-unsigned processor<T>::handle_geometry(mapnik::feature_impl const& feature,
+template <typename T> template <typename T2>
+unsigned processor<T>::handle_geometry(T2 const& vs,
+ mapnik::feature_impl const& feature,
mapnik::geometry::geometry<double> const& geom,
- mapnik::proj_transform const& prj_trans,
- mapnik::box2d<double> const& buffered_query_ext)
+ mapnik::box2d<int> const& tile_clipping_extent)
{
- vector_tile_strategy vs(prj_trans, t_, backend_.get_path_multiplier());
- mapnik::geometry::point<double> p1_min(buffered_query_ext.minx(), buffered_query_ext.miny());
- mapnik::geometry::point<double> p1_max(buffered_query_ext.maxx(), buffered_query_ext.maxy());
- mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
- mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
- box2d<int> bbox(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
- mapnik::vector_tile_impl::transform_visitor skipping_transformer(vs);
+ // TODO
+ // - no need to create a new skipping_transformer per geometry
+ // - write a non-skipping / zero copy transformer to be used when no projection is needed
+ using vector_tile_strategy_type = T2;
+ mapnik::vector_tile_impl::transform_visitor<vector_tile_strategy_type> skipping_transformer(vs);
mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(skipping_transformer,geom);
- encoder_visitor<T> encoder(backend_,feature,bbox, area_threshold_);
+ encoder_visitor<T> encoder(backend_, feature, tile_clipping_extent, area_threshold_);
if (simplify_distance_ > 0)
{
simplify_visitor<T> simplifier(simplify_distance_,encoder);
diff --git a/src/vector_tile_strategy.hpp b/src/vector_tile_strategy.hpp
index aa80caa..27a83dc 100644
--- a/src/vector_tile_strategy.hpp
+++ b/src/vector_tile_strategy.hpp
@@ -7,6 +7,8 @@
#include <mapnik/proj_transform.hpp>
#include <mapnik/view_transform.hpp>
+#include "clipper.hpp"
+
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-local-typedef"
@@ -20,9 +22,47 @@ namespace mapnik {
namespace vector_tile_impl {
+static constexpr double coord_max = static_cast<double>(ClipperLib::hiRange);
+static constexpr double coord_min = -1 * static_cast<double>(ClipperLib::hiRange);
+
struct vector_tile_strategy
{
- vector_tile_strategy(proj_transform const& prj_trans,
+ vector_tile_strategy(view_transform const& tr,
+ double scaling)
+ : tr_(tr),
+ scaling_(scaling) {}
+
+ template <typename P1, typename P2>
+ inline bool apply(P1 const& p1, P2 & p2) const
+ {
+ using p2_type = typename boost::geometry::coordinate_type<P2>::type;
+ double x = boost::geometry::get<0>(p1);
+ double y = boost::geometry::get<1>(p1);
+ tr_.forward(&x,&y);
+ x = std::round(x * scaling_);
+ y = std::round(y * scaling_);
+ if (x <= coord_min || x >= coord_max ||
+ y <= coord_min || y >= coord_max) return false;
+ boost::geometry::set<0>(p2, static_cast<p2_type>(x));
+ boost::geometry::set<1>(p2, static_cast<p2_type>(y));
+ return true;
+ }
+
+ template <typename P1, typename P2>
+ inline P2 execute(P1 const& p1, bool & status) const
+ {
+ P2 p2;
+ status = apply(p1, p2);
+ return p2;
+ }
+
+ view_transform const& tr_;
+ double const scaling_;
+};
+
+struct vector_tile_strategy_proj
+{
+ vector_tile_strategy_proj(proj_transform const& prj_trans,
view_transform const& tr,
double scaling)
: prj_trans_(prj_trans),
@@ -39,8 +79,12 @@ struct vector_tile_strategy
double z = 0.0;
if (not_equal_ && !prj_trans_.backward(x, y, z)) return false;
tr_.forward(&x,&y);
- boost::geometry::set<0>(p2, static_cast<p2_type>(std::round(x * scaling_)));
- boost::geometry::set<1>(p2, static_cast<p2_type>(std::round(y * scaling_)));
+ x = std::round(x * scaling_);
+ y = std::round(y * scaling_);
+ if (x <= coord_min || x >= coord_max ||
+ y <= coord_min || y >= coord_max) return false;
+ boost::geometry::set<0>(p2, static_cast<p2_type>(x));
+ boost::geometry::set<1>(p2, static_cast<p2_type>(y));
return true;
}
@@ -59,9 +103,10 @@ struct vector_tile_strategy
};
// TODO - avoid creating degenerate polygons when first/last point of ring is skipped
+template <typename TransformType>
struct transform_visitor {
- transform_visitor(vector_tile_strategy const& tr) :
+ transform_visitor(TransformType const& tr) :
tr_(tr) {}
inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::point<double> const& geom)
@@ -201,7 +246,7 @@ struct transform_visitor {
{
return geom;
}
- vector_tile_strategy const& tr_;
+ TransformType const& tr_;
};
}
diff --git a/test/clipper_test.cpp b/test/clipper_test.cpp
index e3ae16a..fcbdd0c 100644
--- a/test/clipper_test.cpp
+++ b/test/clipper_test.cpp
@@ -7,6 +7,7 @@
#include "vector_tile_strategy.hpp"
#include "vector_tile_projection.hpp"
+#include "test_utils.hpp"
#include "catch.hpp"
#include "clipper.hpp"
@@ -20,7 +21,7 @@ TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
mapnik::box2d<double> z15_extent(minx,miny,maxx,maxy);
mapnik::view_transform tr(tile_size,tile_size,z15_extent,0,0);
{
- mapnik::vector_tile_impl::vector_tile_strategy vs(prj_trans, tr, 16);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr, 16);
// even an invalid point is not expected to result in values beyond hirange
mapnik::geometry::point<double> g(-20037508.342789*2.0,-20037508.342789*2.0);
mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::geometry::transform<std::int64_t>(g, vs);
@@ -41,7 +42,7 @@ TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
{
// absurdly large but still should not result in values beyond hirange
double path_multiplier = 100000000000.0;
- mapnik::vector_tile_impl::vector_tile_strategy vs(prj_trans, tr, path_multiplier);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr, path_multiplier);
mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::geometry::transform<std::int64_t>(g, vs);
REQUIRE( new_geom.is<mapnik::geometry::polygon<std::int64_t>>() );
auto const& poly = mapnik::util::get<mapnik::geometry::polygon<std::int64_t>>(new_geom);
@@ -58,19 +59,59 @@ TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
{
// expected to trigger values above hirange
double path_multiplier = 1000000000000.0;
- mapnik::vector_tile_impl::vector_tile_strategy vs(prj_trans, tr, path_multiplier);
- mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::geometry::transform<std::int64_t>(g, vs);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr, path_multiplier);
+ CHECK_THROWS( mapnik::geometry::transform<std::int64_t>(g, vs) );
+ mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy_proj> skipping_transformer(vs);
+ mapnik::geometry::geometry<std::int64_t> new_geom = skipping_transformer(g);
REQUIRE( new_geom.is<mapnik::geometry::polygon<std::int64_t>>() );
auto const& poly = mapnik::util::get<mapnik::geometry::polygon<std::int64_t>>(new_geom);
for (auto const& pt : poly.exterior_ring)
{
INFO( pt.x )
INFO( ClipperLib::hiRange )
- REQUIRE(( (pt.x > ClipperLib::hiRange) ||
- (pt.y > ClipperLib::hiRange) ||
- (-pt.x < ClipperLib::hiRange) ||
- (-pt.y < ClipperLib::hiRange)
- ));
+ REQUIRE( (pt.x < ClipperLib::hiRange) );
+ REQUIRE( (pt.y < ClipperLib::hiRange) );
+ REQUIRE( (-pt.x < ClipperLib::hiRange) );
+ REQUIRE( (-pt.y < ClipperLib::hiRange) );
+ }
+ }
+}
+
+TEST_CASE( "vector_tile_strategy2", "invalid mercator coord in interior ring" ) {
+ mapnik::geometry::geometry<double> geom = testing::read_geojson("./test/data/invalid-interior-ring.json");
+ mapnik::projection longlat("+init=epsg:4326",true);
+ mapnik::proj_transform prj_trans(longlat,longlat); // no-op
+ unsigned tile_size = 256;
+ mapnik::vector_tile_impl::spherical_mercator merc_tiler(tile_size);
+ double minx,miny,maxx,maxy;
+ merc_tiler.xyz(9664,20435,15,minx,miny,maxx,maxy);
+ mapnik::box2d<double> z15_extent(minx,miny,maxx,maxy);
+ mapnik::view_transform tr(tile_size,tile_size,z15_extent,0,0);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr, 16);
+ CHECK_THROWS( mapnik::geometry::transform<std::int64_t>(geom, vs) );
+ mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy_proj> skipping_transformer(vs);
+ mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(skipping_transformer,geom);
+ REQUIRE( new_geom.is<mapnik::geometry::polygon<std::int64_t>>() );
+ auto const& poly = mapnik::util::get<mapnik::geometry::polygon<std::int64_t>>(new_geom);
+ for (auto const& pt : poly.exterior_ring)
+ {
+ INFO( pt.x )
+ INFO( ClipperLib::hiRange )
+ REQUIRE( (pt.x < ClipperLib::hiRange) );
+ REQUIRE( (pt.y < ClipperLib::hiRange) );
+ REQUIRE( (-pt.x < ClipperLib::hiRange) );
+ REQUIRE( (-pt.y < ClipperLib::hiRange) );
+ }
+ for (auto const& ring : poly.interior_rings)
+ {
+ for (auto const& pt : ring)
+ {
+ INFO( pt.x )
+ INFO( ClipperLib::hiRange )
+ REQUIRE( (pt.x < ClipperLib::hiRange) );
+ REQUIRE( (pt.y < ClipperLib::hiRange) );
+ REQUIRE( (-pt.x < ClipperLib::hiRange) );
+ REQUIRE( (-pt.y < ClipperLib::hiRange) );
}
}
}
diff --git a/test/data/NZ_Coastline_NZMG.dbf b/test/data/NZ_Coastline_NZMG.dbf
new file mode 100644
index 0000000..1c9e979
Binary files /dev/null and b/test/data/NZ_Coastline_NZMG.dbf differ
diff --git a/test/data/NZ_Coastline_NZMG.prj b/test/data/NZ_Coastline_NZMG.prj
new file mode 100644
index 0000000..6a4197f
--- /dev/null
+++ b/test/data/NZ_Coastline_NZMG.prj
@@ -0,0 +1 @@
+PROJCS["NZGD49_New_Zealand_Map_Grid",GEOGCS["GCS_New_Zealand_1949",DATUM["D_New_Zealand_1949",SPHEROID["International_1924",6378388,297]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["New_Zealand_Map_Grid"],PARAMETER["latitude_of_origin",-41],PARAMETER["Longitude_Of_Origin",173],PARAMETER["false_easting",2510000],PARAMETER["false_northing",6023150],UNIT["Meter",1]]
\ No newline at end of file
diff --git a/test/data/NZ_Coastline_NZMG.qpj b/test/data/NZ_Coastline_NZMG.qpj
new file mode 100644
index 0000000..62479e3
--- /dev/null
+++ b/test/data/NZ_Coastline_NZMG.qpj
@@ -0,0 +1 @@
+PROJCS["NZGD49 / New Zealand Map Grid",GEOGCS["NZGD49",DATUM["New_Zealand_Geodetic_Datum_1949",SPHEROID["International 1924",6378388,297,AUTHORITY["EPSG","7022"]],TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993],AUTHORITY["EPSG","6272"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4272"]],PROJECTION["New_Zealand_Map_Grid"],PARAMETER["latitude_of_origin",-41],PARAMETER["central_meridian",173],PARAMETER["fal [...]
diff --git a/test/data/NZ_Coastline_NZMG.shp b/test/data/NZ_Coastline_NZMG.shp
new file mode 100644
index 0000000..872179f
Binary files /dev/null and b/test/data/NZ_Coastline_NZMG.shp differ
diff --git a/test/data/NZ_Coastline_NZMG.shx b/test/data/NZ_Coastline_NZMG.shx
new file mode 100644
index 0000000..ec190af
Binary files /dev/null and b/test/data/NZ_Coastline_NZMG.shx differ
diff --git a/test/data/invalid-interior-ring.json b/test/data/invalid-interior-ring.json
new file mode 100644
index 0000000..99c75e4
--- /dev/null
+++ b/test/data/invalid-interior-ring.json
@@ -0,0 +1,95 @@
+{
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -17.2265625,
+ 63.074865690586634
+ ],
+ [
+ -40.78125,
+ 45.583289756006316
+ ],
+ [
+ -47.109375,
+ 20.3034175184893
+ ],
+ [
+ -42.5390625,
+ 2.811371193331128
+ ],
+ [
+ -15.468749999999998,
+ 2.811371193331128
+ ],
+ [
+ 10.8984375,
+ 16.29905101458183
+ ],
+ [
+ 17.578125,
+ 38.272688535980976
+ ],
+ [
+ 16.5234375,
+ 56.36525013685606
+ ],
+ [
+ 1.0546875,
+ 65.5129625532949
+ ],
+ [
+ -17.2265625,
+ 63.074865690586634
+ ]
+ ],
+ [
+ [
+ -10.8984375,
+ 54.77534585936447
+ ],
+ [
+ -23.5546875,
+ 32.24997445586331
+ ],
+ [
+ -10.8984375,
+ 18.646245142670608
+ ],
+ [
+ 4.21875,
+ 35.17380831799959
+ ],
+ [
+ 4.21875,
+ 46.800059446787316
+ ],
+ [
+ -10.8984375,
+ 54.77534585936447
+ ]
+ ],
+ [
+ [
+ -10.1953125,
+ 44.33956524809713
+ ],
+ [
+ -12.65625,
+ 0.0
+ ],
+ [
+ 29256264171659283709034496,
+ 32.84267363195431
+ ],
+ [
+ -3.515625,
+ 42.032974332441405
+ ],
+ [
+ -10.1953125,
+ 44.33956524809713
+ ]
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/test/geometry_encoding.cpp b/test/geometry_encoding.cpp
index 69a94eb..4520f0e 100644
--- a/test/geometry_encoding.cpp
+++ b/test/geometry_encoding.cpp
@@ -284,6 +284,38 @@ TEST_CASE( "polygon with valid exterior ring but one degenerate interior ring of
CHECK( holes.size() == 1 );
}
+TEST_CASE( "Polygon with de-generate ring(s)", "should skip invalid ring(s)" )
+{
+ // create invalid (exterior) polygon
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(10,10);
+ p0.exterior_ring.add_coord(10,10);
+ p0.exterior_ring.add_coord(10,10);
+ p0.exterior_ring.add_coord(10,10);
+ mapnik::geometry::linear_ring<std::int64_t> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-3,7);
+ hole.add_coord(-3,3);
+ hole.add_coord(-7,3);
+ hole.add_coord(-7,7);
+ p0.add_hole(std::move(hole));
+
+ std::string wkt0;
+ CHECK( mapnik::util::to_wkt(wkt0,mapnik::geometry::geometry<std::int64_t>(p0) ) );
+ std::string expected_wkt0("POLYGON((10 10,10 10,10 10,10 10),(-7 7,-3 7,-3 3,-7 3,-7 7))");
+ std::string expected_wkt1("POLYGON((-7 7,-7 3,-3 3,-3 7,-7 7))");
+ CHECK( wkt0 == expected_wkt0);
+
+ vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
+ mapnik::vector_tile_impl::Geometry geoms(feature,0.0,0.0,1.0,1.0);
+ auto p1 = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type());
+ CHECK( p1.is<mapnik::geometry::polygon<double> >() );
+ wkt0.clear();
+ CHECK( mapnik::util::to_wkt(wkt0,p1) );
+ CHECK( wkt0 == expected_wkt1);
+}
+
+
TEST_CASE( "(multi)polygon with hole", "should round trip without changes" ) {
// NOTE: this polygon should have correct winding order:
// CCW for exterior, CW for interior
@@ -324,96 +356,6 @@ TEST_CASE( "(multi)polygon with hole", "should round trip without changes" ) {
CHECK( mapnik::util::to_wkt(wkt0,p1) );
CHECK( wkt0 == expected_wkt0);
- // now test back compatibility mode where we decode all rings into exterior rings
- // for polygons rings that were encoded correctly in vtiles (CCW exterior, CW interior)
- // then this should be unneeded, but for rings with incorrect order then this style of
- // decoding should allow them still to be queried correctly using the current mapnik hit_test algos
- // Note, we need a new Geometry here, the old object can't be rewound.
- mapnik::vector_tile_impl::Geometry geoms2(feature,0.0,0.0,1.0,1.0);
- auto _p1 = mapnik::vector_tile_impl::decode_geometry(geoms2, feature.type(), true);
- wkt0.clear();
- CHECK( mapnik::util::to_wkt(wkt0,_p1) );
- CHECK( _p1.is<mapnik::geometry::multi_polygon<double> >() );
- std::string expected_wkt2("MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0)),((-7 7,-7 3,-3 3,-3 7,-7 7)))");
- CHECK( wkt0 == expected_wkt2 );
- mapnik::geometry::correct(_p1);
- wkt0.clear();
- CHECK( mapnik::util::to_wkt(wkt0,_p1) );
- CHECK( wkt0 == expected_wkt2 );
-
- std::string expected_p0(
- "move_to(0,0)\n"
- "line_to(0,10)\n"
- "line_to(-10,10)\n"
- "line_to(-10,0)\n"
- "close_path(0,0)\n"
- "move_to(-7,7)\n"
- "line_to(-3,7)\n"
- "line_to(-3,3)\n"
- "line_to(-7,3)\n"
- "close_path(0,0)\n"
- );
-
- CHECK(decode_to_path_string(p1) == expected_p0);
-
- std::string expected_p1(
- "move_to(0,0)\n"
- "line_to(0,10)\n"
- "line_to(-10,10)\n"
- "line_to(-10,0)\n"
- "close_path(0,0)\n"
- "move_to(-7,7)\n"
- "line_to(-7,3)\n"
- "line_to(-3,3)\n"
- "line_to(-3,7)\n"
- "close_path(0,0)\n"
- );
-
- CHECK(decode_to_path_string(_p1) == expected_p1);
-
- // make into multi_polygon
- mapnik::geometry::multi_polygon<std::int64_t> multi_poly;
- multi_poly.push_back(std::move(p0));
- mapnik::geometry::polygon<std::int64_t> p2;
- p2.exterior_ring.add_coord(-6,4);
- p2.exterior_ring.add_coord(-4,4);
- p2.exterior_ring.add_coord(-4,6);
- p2.exterior_ring.add_coord(-6,6);
- p2.exterior_ring.add_coord(-6,4);
- multi_poly.push_back(std::move(p2));
-
- mapnik::box2d<double> multi_extent = mapnik::geometry::envelope(multi_poly);
-
- vector_tile::Tile_Feature feature1 = geometry_to_feature<std::int64_t>(multi_poly);
- mapnik::vector_tile_impl::Geometry geoms1(feature1,0.0,0.0,1.0,1.0);
- auto mp = mapnik::vector_tile_impl::decode_geometry(geoms1, feature1.type());
- CHECK( mp.is<mapnik::geometry::multi_polygon<double> >() );
-
- CHECK( multi_extent == mapnik::geometry::envelope(mp) );
-
- wkt0.clear();
- CHECK( mapnik::util::to_wkt(wkt0,mp) );
- std::string expected_wkt3("MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)),((-6 4,-4 4,-4 6,-6 6,-6 4)))");
- CHECK( wkt0 == expected_wkt3);
- // ensure correcting geometry has no effect
- // as a way of confirming the original was correct
- mapnik::geometry::correct(mp);
- wkt0.clear();
- CHECK( mapnik::util::to_wkt(wkt0,mp) );
- CHECK( wkt0 == expected_wkt3);
-
- std::string expected_multi = expected_p0 += std::string(
- "move_to(-6,4)\n"
- "line_to(-4,4)\n"
- "line_to(-4,6)\n"
- "line_to(-6,6)\n"
- "close_path(0,0)\n"
- );
-
- CHECK(decode_to_path_string(mp) == expected_multi);
- CHECK(mapnik::geometry::is_valid(mp));
- CHECK(mapnik::geometry::is_simple(mp));
-
}
// We no longer drop coincidental points in the encoder it should be
diff --git a/test/test_utils.cpp b/test/test_utils.cpp
index 02c2012..b26b5ae 100644
--- a/test/test_utils.cpp
+++ b/test/test_utils.cpp
@@ -75,6 +75,22 @@ std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& g
return ds;
}
+mapnik::geometry::geometry<double> read_geojson(std::string const& geojson_file) {
+ mapnik::util::file input(geojson_file);
+ if (!input.open())
+ {
+ throw std::runtime_error("failed to open geojson");
+ }
+ mapnik::geometry::geometry<double> geom;
+ std::string json_string(input.data().get(), input.size());
+ if (!mapnik::json::from_geojson(json_string, geom))
+ {
+ throw std::runtime_error("failed to parse geojson");
+ }
+ mapnik::geometry::correct(geom);
+ return geom;
+}
+
unsigned compare_images(mapnik::image_rgba8 const& src1,
std::string const& filepath,
int threshold,
diff --git a/test/test_utils.hpp b/test/test_utils.hpp
index 25a4b17..fe51943 100644
--- a/test/test_utils.hpp
+++ b/test/test_utils.hpp
@@ -10,6 +10,7 @@
namespace testing {
std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool second=false);
+mapnik::geometry::geometry<double> read_geojson(std::string const& geojson_file);
std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& geojson_file);
unsigned compare_images(std::string const& src_fn,
diff --git a/test/vector_tile.cpp b/test/vector_tile.cpp
index 00ee133..d5a349a 100644
--- a/test/vector_tile.cpp
+++ b/test/vector_tile.cpp
@@ -26,6 +26,7 @@
// 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"
@@ -606,7 +607,14 @@ 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.handle_geometry(*feature,geom,prj_trans,bbox);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs2(prj_trans,ren.get_transform(),backend.get_path_multiplier());
+ mapnik::vector_tile_impl::vector_tile_strategy vs(ren.get_transform(),backend.get_path_multiplier());
+ mapnik::geometry::point<double> p1_min(bbox.minx(), bbox.miny());
+ mapnik::geometry::point<double> p1_max(bbox.maxx(), bbox.maxy());
+ mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
+ mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
+ mapnik::box2d<int> clipping_extent(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
+ ren.handle_geometry(vs2,*feature,geom,clipping_extent);
backend.stop_tile_layer();
if (tile.layers_size() != 1)
{
@@ -956,7 +964,7 @@ TEST_CASE( "vector tile from simplified geojson", "should create vector tile wit
REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
- CHECK(1 == layer.features_size());
+ REQUIRE(1 == layer.features_size());
vector_tile::Tile_Feature const& f = layer.features(0);
unsigned z = 0;
unsigned x = 0;
@@ -1006,7 +1014,14 @@ mapnik::geometry::geometry<double> round_trip2(mapnik::geometry::geometry<double
{
throw std::runtime_error("simplify_distance setter did not work");
}
- ren.handle_geometry(*feature,geom,prj_trans,bbox);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs2(prj_trans,ren.get_transform(),backend.get_path_multiplier());
+ mapnik::vector_tile_impl::vector_tile_strategy vs(ren.get_transform(),backend.get_path_multiplier());
+ mapnik::geometry::point<double> p1_min(bbox.minx(), bbox.miny());
+ mapnik::geometry::point<double> p1_max(bbox.maxx(), bbox.maxy());
+ mapnik::geometry::point<std::int64_t> p2_min = mapnik::geometry::transform<std::int64_t>(p1_min, vs);
+ mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, vs);
+ mapnik::box2d<int> clipping_extent(p2_min.x, p2_min.y, p2_max.x, p2_max.y);
+ ren.handle_geometry(vs2,*feature,geom,clipping_extent);
backend.stop_tile_layer();
if (tile.layers_size() != 1)
{
@@ -1054,11 +1069,11 @@ TEST_CASE( "vector tile line_string is verify direction", "should line string wi
mapnik::geometry::geometry<double> xgeom = mapnik::geometry::transform<double>(new_geom, proj_strat);
std::string wkt;
mapnik::util::to_wkt(wkt, xgeom);
- CHECK( wkt == "MULTILINESTRING((0 1.99992945603165,2.00006103515625 1.99992945603165,2.00006103515625 0),(7.99996948242188 0,7.99996948242188 1.99992945603165,59.9999084472656 1.99992945603165,59.9999084472656 7.99994115658818,7.99996948242188 7.99994115658818,7.99996948242188 59.9999474398107,2.00006103515625 59.9999474398107,2.00006103515625 7.99994115658818,0.0000000000000005 7.99994115658818))" );
+ CHECK( wkt == "MULTILINESTRING((0 1.99992945603165,2.00006103515625 1.99992945603165,2.00006103515625 0),(7.99996948242188 0,7.99996948242188 1.99992945603165,11.25 1.99992945603165),(11.25 7.99994115658818,7.99996948242188 7.99994115658818,7.99996948242188 11.1784018737118),(2.00006103515625 11.1784018737118,2.00006103515625 7.99994115658818,0.0000000000000005 7.99994115658818))" );
REQUIRE( !mapnik::geometry::is_empty(xgeom) );
REQUIRE( new_geom.is<mapnik::geometry::multi_line_string<double> >() );
auto const& line2 = mapnik::util::get<mapnik::geometry::multi_line_string<double> >(new_geom);
- CHECK( line2.size() == 2 );
+ CHECK( line2.size() == 4 );
}
TEST_CASE( "vector tile transform", "should not throw on coords outside merc range" ) {
@@ -1136,3 +1151,80 @@ TEST_CASE( "vector tile transform", "should not throw on coords outside merc ran
mapnik::save_to_file(im,"test/fixtures/transform-actual-1.png","png32");
}
}
+
+TEST_CASE( "vector tile transform2", "should not throw reprojected data from local NZ projection" ) {
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ typedef vector_tile::Tile tile_type;
+ tile_type tile;
+ backend_type backend(tile,64);
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ // Note: 4269 is key. 4326 will trigger custom mapnik reprojection code
+ // that does not hit proj4 and clamps values
+ mapnik::layer lyr("layer","+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=intl +units=m +no_defs");
+ mapnik::parameters params;
+ params["type"] = "shape";
+ params["file"] = "./test/data/NZ_Coastline_NZMG.shp";
+ std::shared_ptr<mapnik::datasource> ds =
+ mapnik::datasource_cache::instance().create(params);
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+ map.zoom_to_box(bbox);
+ mapnik::request m_req(tile_size,tile_size,bbox);
+ renderer_type ren(backend,map,m_req);
+ // should no longer throw after https://github.com/mapbox/mapnik-vector-tile/pull/128
+ ren.apply();
+
+ // serialize to message
+ std::string buffer;
+ CHECK(tile.SerializeToString(&buffer));
+ CHECK(231 == buffer.size());
+ // now create new objects
+ mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
+ tile_type tile2;
+ CHECK(tile2.ParseFromString(buffer));
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
+ CHECK("" == key);
+ CHECK(1 == tile2.layers_size());
+ vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
+ CHECK(std::string("layer") == layer2.name());
+ CHECK(2 == layer2.features_size());
+
+ mapnik::layer lyr2("layer",map.srs());
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds2 = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource>(
+ layer2,0,0,0,map2.width());
+ ds2->set_envelope(bbox);
+ CHECK( ds2->type() == mapnik::datasource::Vector );
+ CHECK( ds2->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
+ mapnik::layer_descriptor lay_desc = ds2->get_descriptor();
+ std::vector<std::string> expected_names;
+ expected_names.push_back("featurecla");
+ expected_names.push_back("scalerank");
+ 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(ds2);
+ lyr2.add_style("style");
+ map2.add_layer(lyr2);
+ mapnik::load_map(map2,"test/data/polygon-style.xml");
+ //std::clog << mapnik::save_map_to_string(map2) << "\n";
+ map2.zoom_to_box(bbox);
+ mapnik::image_rgba8 im(map2.width(),map2.height());
+ mapnik::agg_renderer<mapnik::image_rgba8> ren2(map2,im);
+ ren2.apply();
+ if (!mapnik::util::exists("test/fixtures/transform-expected-2.png")) {
+ mapnik::save_to_file(im,"test/fixtures/transform-expected-2.png","png32");
+ }
+ unsigned diff = testing::compare_images(im,"test/fixtures/transform-expected-2.png");
+ CHECK(0 == diff);
+ if (diff > 0) {
+ mapnik::save_to_file(im,"test/fixtures/transform-actual-2.png","png32");
+ }
+}
\ No newline at end of file
diff --git a/test/vector_tile_pbf.cpp b/test/vector_tile_pbf.cpp
index 47e8bf0..fb0e3c0 100644
--- a/test/vector_tile_pbf.cpp
+++ b/test/vector_tile_pbf.cpp
@@ -144,10 +144,10 @@ TEST_CASE( "pbf vector tile datasource", "should filter features outside extent"
std::string key("");
CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
CHECK("" == key);
- CHECK(1 == tile.layers_size());
+ REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
- CHECK(1 == layer.features_size());
+ REQUIRE(1 == layer.features_size());
vector_tile::Tile_Feature const& f = layer.features(0);
CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f.id()));
CHECK(3 == f.geometry_size());
@@ -261,9 +261,9 @@ TEST_CASE( "pbf encoding multi line as one path", "should maintain second move_t
std::string key("");
CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
CHECK("" == key);
- CHECK(1 == tile.layers_size());
+ REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
- CHECK(1 == layer.features_size());
+ REQUIRE(1 == layer.features_size());
vector_tile::Tile_Feature const& f = layer.features(0);
CHECK(12 == f.geometry_size());
CHECK(9 == f.geometry(0)); // 1 move_to
@@ -343,10 +343,10 @@ TEST_CASE( "pbf decoding some truncated buffers", "should throw exception" ) {
std::string key("");
CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
CHECK("" == key);
- CHECK(1 == tile.layers_size());
+ REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
- CHECK(1 == layer.features_size());
+ REQUIRE(1 == layer.features_size());
vector_tile::Tile_Feature const& f = layer.features(0);
CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f.id()));
CHECK(3 == f.geometry_size());
@@ -397,10 +397,10 @@ TEST_CASE( "pbf vector tile from simplified geojson", "should create vector tile
renderer_type ren(backend,map,m_req);
ren.apply();
CHECK( ren.painted() == true );
- CHECK(1 == tile.layers_size());
+ REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
- CHECK(1 == layer.features_size());
+ REQUIRE(1 == layer.features_size());
vector_tile::Tile_Feature const& f = layer.features(0);
unsigned z = 0;
unsigned x = 0;
@@ -478,10 +478,10 @@ TEST_CASE( "pbf raster tile output", "should be able to overzoom raster" ) {
ren.apply();
}
// Done creating test data, now test created tile
- CHECK(1 == tile.layers_size());
+ REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
- CHECK(1 == layer.features_size());
+ REQUIRE(1 == layer.features_size());
vector_tile::Tile_Feature const& f = layer.features(0);
CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f.id()));
CHECK(0 == f.geometry_size());
--
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