[mapnik-vector-tile] 01/15: Imported Upstream version 0.8.1+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 49f807b4efdb6975697456d92e0a30be2ee7e1bf
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date: Fri Sep 11 22:54:58 2015 +0200
Imported Upstream version 0.8.1+dfsg
---
CHANGELOG.md | 10 +
Makefile | 14 +-
README.md | 2 +-
bootstrap.sh | 2 +-
examples/c++/tileinfo.cpp | 3 +-
gyp/build.gyp | 36 +-
package.json | 2 +-
src/vector_tile_backend_pbf.ipp | 2 +
src/vector_tile_datasource.ipp | 3 +-
src/vector_tile_datasource_pbf.cpp | 2 +
src/vector_tile_datasource_pbf.hpp | 59 +++
src/vector_tile_datasource_pbf.ipp | 413 +++++++++++++++++
src/vector_tile_geometry_decoder.hpp | 105 ++++-
src/vector_tile_geometry_encoder.hpp | 12 +-
src/vector_tile_processor.ipp | 63 +--
src/vector_tile_strategy.hpp | 88 ++++
test/clipper_test.cpp | 143 ++++++
test/data/natural_earth.tif | Bin 3299605 -> 0 bytes
test/data/tile_with_extra_feature_field.pbf | 2 +
test/data/tile_with_extra_field.pbf | 2 +
test/data/tile_with_extra_layer_fields.pbf | 2 +
test/data/tile_with_invalid_layer_value_type.pbf | 2 +
test/data/tile_with_unexpected_geomtype.pbf | 3 +
test/encoding_util.hpp | 3 +-
test/geometry_encoding.cpp | 22 +-
test/test_main.cpp | 9 +-
test/test_utils.cpp | 16 +-
test/vector_tile.cpp | 84 ++--
test/vector_tile_pbf.cpp | 557 +++++++++++++++++++++++
29 files changed, 1543 insertions(+), 118 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72c1c12..da1f497 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
# Changelog
+## 0.8.1
+
+ - Added `tile_datasource_pbf` - It should be used in places where you need to plug
+ in a `mapnik::datasource` to read from a binary encoded .pbf buffer. (@danpat #114)
+ - Updated bundled clipper to https://github.com/mapnik/clipper/commit/bfad32ec4b41783497d076c2ec44c7cbf4ebe56b
+ - Clipper is now patched to avoid abort on out of range coordinates (#111)
+ - Fixed handling of geometry collections (#106)
+ - Added mapnik vector tile strategy for transform
+ - Updated test cases
+
## 0.8.0
- Now using `boost::geometry` to clip lines and `ClipperLib` to clip polygons
diff --git a/Makefile b/Makefile
index 71b405d..a518b5a 100755
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,22 @@
MAPNIK_PLUGINDIR := $(shell mapnik-config --input-plugins)
BUILDTYPE ?= Release
+CLIPPER_REVISION=bfad32e
+PBF_REVISION=1df6453
+GYP_REVISION=3464008
+
all: libvtile
./deps/gyp:
- git clone https://chromium.googlesource.com/external/gyp.git ./deps/gyp && cd ./deps/gyp && git checkout 3464008
+ git clone https://chromium.googlesource.com/external/gyp.git ./deps/gyp && cd ./deps/gyp && git checkout $(GYP_REVISION)
+
+./deps/pbf:
+ git clone https://github.com/mapbox/pbf.hpp.git ./deps/pbf && cd ./deps/pbf && git checkout $(PBF_REVISION)
+
+./deps/clipper:
+ git clone https://github.com/mapnik/clipper.git -b r493-mapnik ./deps/clipper && cd ./deps/clipper && git checkout $(CLIPPER_REVISION) && ./cpp/fix_members.sh
-build/Makefile: ./deps/gyp gyp/build.gyp test/*cpp
+build/Makefile: ./deps/gyp ./deps/clipper ./deps/pbf gyp/build.gyp test/*cpp
deps/gyp/gyp gyp/build.gyp --depth=. -DMAPNIK_PLUGINDIR=\"$(MAPNIK_PLUGINDIR)\" -Goutput_dir=. --generator-output=./build -f make
libvtile: build/Makefile Makefile
diff --git a/README.md b/README.md
index f47e297..5a1c2b2 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ Provides C++ headers that support rendering geodata into vector tiles and render
## Depends
- - mapnik-vector-tile 0.7.x depends on Mapnik v3.0.x (until 3.0.0 is released this means latest mapnik HEAD)
+ - mapnik-vector-tile >=0.7.x depends on Mapnik v3.0.x (until 3.0.0 is released this means latest mapnik HEAD)
- mapnik-vector-tile 0.6.x and previous work with Mapnik v2.2.x or v2.3.x
- You will need `libmapnik` and `mapnik-config` available
- Protobuf: `libprotobuf` and `protoc`
diff --git a/bootstrap.sh b/bootstrap.sh
index d98f431..6e47b2f 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -22,7 +22,7 @@ function install() {
}
function install_mason_deps() {
- install mapnik latest
+ install mapnik 3.0.0-rc3
install protobuf 2.6.1
install freetype 2.5.5
install harfbuzz 0.9.40
diff --git a/examples/c++/tileinfo.cpp b/examples/c++/tileinfo.cpp
index 397ffbb..7a9ddca 100644
--- a/examples/c++/tileinfo.cpp
+++ b/examples/c++/tileinfo.cpp
@@ -104,7 +104,6 @@ int main(int argc, char** argv)
{
vector_tile::Tile_Feature const & f = layer.features(j);
total_repeated += f.geometry_size();
- eGeomType g_type = static_cast<eGeomType>(f.type());
int cmd = -1;
const int cmd_bits = 3;
unsigned length = 0;
@@ -117,7 +116,6 @@ int main(int argc, char** argv)
length = cmd_length >> cmd_bits;
if (length <= 0) num_empty++;
num_commands++;
- g_length = 0;
}
if (length > 0) {
length--;
@@ -138,6 +136,7 @@ int main(int argc, char** argv)
else if (cmd == (SEG_CLOSE & ((1 << cmd_bits) - 1)))
{
if (g_length <= 2) degenerate++;
+ g_length = 0;
num_close++;
}
else
diff --git a/gyp/build.gyp b/gyp/build.gyp
index 3c145d3..42a4b71 100644
--- a/gyp/build.gyp
+++ b/gyp/build.gyp
@@ -3,7 +3,14 @@
"common.gypi"
],
'variables': {
- 'MAPNIK_PLUGINDIR%': ''
+ 'MAPNIK_PLUGINDIR%': '',
+ 'common_defines' : [
+ 'MAPNIK_VECTOR_TILE_LIBRARY=1',
+ 'CLIPPER_INTPOINT_IMPL=mapnik::geometry::point<cInt>',
+ 'CLIPPER_PATH_IMPL=mapnik::geometry::line_string<cInt>',
+ 'CLIPPER_PATHS_IMPL=mapnik::geometry::multi_line_string<cInt>',
+ 'CLIPPER_IMPL_INCLUDE=<mapnik/geometry.hpp>'
+ ]
},
"targets": [
{
@@ -34,7 +41,8 @@
"<(SHARED_INTERMEDIATE_DIR)/vector_tile.pb.cc"
],
'include_dirs': [
- '<(SHARED_INTERMEDIATE_DIR)/'
+ '<(SHARED_INTERMEDIATE_DIR)/',
+ '../deps/pbf'
],
'cflags_cc' : [
'-D_THREAD_SAFE',
@@ -48,7 +56,8 @@
},
'direct_dependent_settings': {
'include_dirs': [
- '<(SHARED_INTERMEDIATE_DIR)/'
+ '<(SHARED_INTERMEDIATE_DIR)/',
+ '../deps/pbf'
],
'libraries':[
'-lprotobuf-lite'
@@ -69,14 +78,19 @@
'hard_dependency': 1,
"type": "static_library",
"sources": [
- "<!@(find ../src/ -name '*.cpp')"
+ "<!@(find ../src/ -name '*.cpp')",
+ "../deps/clipper/cpp/clipper.cpp"
],
'defines' : [
- 'MAPNIK_VECTOR_TILE_LIBRARY=1'
+ "<@(common_defines)"
],
'cflags_cc' : [
'<!@(mapnik-config --cflags)'
],
+ 'include_dirs': [
+ '../deps/pbf',
+ '../deps/clipper/cpp'
+ ],
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS':[
'<!@(mapnik-config --cflags)'
@@ -84,10 +98,12 @@
},
'direct_dependent_settings': {
'include_dirs': [
- '<(SHARED_INTERMEDIATE_DIR)/'
+ '<(SHARED_INTERMEDIATE_DIR)/',
+ '../deps/pbf',
+ '../deps/clipper/cpp'
],
'defines' : [
- 'MAPNIK_VECTOR_TILE_LIBRARY=1'
+ "<@(common_defines)"
],
'cflags_cc' : [
'<!@(mapnik-config --cflags)'
@@ -113,13 +129,15 @@
'dependencies': [ 'mapnik_vector_tile_impl' ],
"type": "executable",
"defines": [
+ "<@(common_defines)",
"MAPNIK_PLUGINDIR=<(MAPNIK_PLUGINDIR)"
],
"sources": [
"<!@(find ../test/ -name '*.cpp')"
],
"include_dirs": [
- "../src"
+ "../src",
+ '../deps/pbf'
]
},
{
@@ -150,4 +168,4 @@
}
]
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index d7c65ca..515480b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mapnik-vector-tile",
- "version": "0.8.0",
+ "version": "0.8.1",
"description": "Mapnik vector tile API",
"main": "./package.json",
"repository" : {
diff --git a/src/vector_tile_backend_pbf.ipp b/src/vector_tile_backend_pbf.ipp
index dbc07a0..8b1792d 100644
--- a/src/vector_tile_backend_pbf.ipp
+++ b/src/vector_tile_backend_pbf.ipp
@@ -59,6 +59,8 @@ backend_pbf::backend_pbf(vector_tile::Tile & _tile,
: tile_(_tile),
path_multiplier_(path_multiplier),
current_layer_(NULL),
+ x_(0),
+ y_(0),
current_feature_(NULL)
{
}
diff --git a/src/vector_tile_datasource.ipp b/src/vector_tile_datasource.ipp
index 1176b71..3ee0dc4 100644
--- a/src/vector_tile_datasource.ipp
+++ b/src/vector_tile_datasource.ipp
@@ -190,7 +190,8 @@ namespace mapnik { namespace vector_tile_impl {
{
continue;
}
- mapnik::geometry::geometry<double> geom = decode_geometry(f,tile_x_,tile_y_,scale_,-1*scale_);
+ mapnik::vector_tile_impl::Geometry geoms(f,tile_x_, tile_y_, scale_, -1*scale_);
+ mapnik::geometry::geometry<double> geom = decode_geometry(geoms, f.type());
if (geom.is<mapnik::geometry::geometry_empty>())
{
continue;
diff --git a/src/vector_tile_datasource_pbf.cpp b/src/vector_tile_datasource_pbf.cpp
new file mode 100644
index 0000000..9a13992
--- /dev/null
+++ b/src/vector_tile_datasource_pbf.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_datasource_pbf.hpp"
+#include "vector_tile_datasource_pbf.ipp"
diff --git a/src/vector_tile_datasource_pbf.hpp b/src/vector_tile_datasource_pbf.hpp
new file mode 100644
index 0000000..5e778df
--- /dev/null
+++ b/src/vector_tile_datasource_pbf.hpp
@@ -0,0 +1,59 @@
+#ifndef __MAPNIK_VECTOR_TILE_DATASOURCE_PBF_H__
+#define __MAPNIK_VECTOR_TILE_DATASOURCE_PBF_H__
+
+#include <mapnik/datasource.hpp>
+#include <mapnik/box2d.hpp>
+#include "pbf_reader.hpp"
+
+namespace mapnik { namespace vector_tile_impl {
+
+ // TODO: consider using mapnik::value here instead
+ using pbf_attr_value_type = mapnik::util::variant<std::string, float, double, int64_t, uint64_t, bool>;
+ using layer_pbf_attr_type = std::vector<pbf_attr_value_type>;
+
+ class tile_datasource_pbf : public datasource
+ {
+ public:
+ tile_datasource_pbf(mapbox::util::pbf const& layer,
+ unsigned x,
+ unsigned y,
+ unsigned z,
+ unsigned tile_size);
+ virtual ~tile_datasource_pbf();
+ datasource::datasource_t type() const;
+ featureset_ptr features(query const& q) const;
+ featureset_ptr features_at_point(coord2d const& pt, double tol = 0) const;
+ void set_envelope(box2d<double> const& bbox);
+ box2d<double> get_tile_extent() const;
+ box2d<double> envelope() const;
+ boost::optional<datasource_geometry_t> get_geometry_type() const;
+ layer_descriptor get_descriptor() const;
+ std::string const& get_name() { return name_; }
+ private:
+ mutable mapnik::layer_descriptor desc_;
+ mutable bool attributes_added_;
+ mapbox::util::pbf layer_;
+ unsigned x_;
+ unsigned y_;
+ unsigned z_;
+ unsigned tile_size_;
+ mutable bool extent_initialized_;
+ mutable mapnik::box2d<double> extent_;
+ double tile_x_;
+ double tile_y_;
+ double scale_;
+ uint32_t layer_extent_;
+
+ std::string name_;
+ std::vector<mapbox::util::pbf> features_;
+ std::vector<std::string> layer_keys_;
+ layer_pbf_attr_type layer_values_;
+ };
+
+}} // end ns
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_datasource_pbf.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_DATASOURCE_PBF_H__
diff --git a/src/vector_tile_datasource_pbf.ipp b/src/vector_tile_datasource_pbf.ipp
new file mode 100644
index 0000000..8976b4b
--- /dev/null
+++ b/src/vector_tile_datasource_pbf.ipp
@@ -0,0 +1,413 @@
+#include "vector_tile_projection.hpp"
+#include "vector_tile_geometry_decoder.hpp"
+
+#include <mapnik/box2d.hpp>
+#include <mapnik/coord.hpp>
+#include <mapnik/feature_layer_desc.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/params.hpp>
+#include <mapnik/query.hpp>
+#include <mapnik/unicode.hpp>
+#include <mapnik/version.hpp>
+#include <mapnik/value_types.hpp>
+#include <mapnik/well_known_srs.hpp>
+#include <mapnik/version.hpp>
+#include <mapnik/vertex.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/feature.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/geom_util.hpp>
+#include <mapnik/image.hpp>
+#include <mapnik/image_reader.hpp>
+#include <mapnik/raster.hpp>
+#include <mapnik/view_transform.hpp>
+#include <mapnik/util/variant.hpp>
+
+#include <memory>
+#include <stdexcept>
+#include <string>
+
+#include <boost/optional.hpp>
+#include <unicode/unistr.h>
+
+namespace mapnik { namespace vector_tile_impl {
+
+ template <typename Filter>
+ class tile_featureset_pbf : public Featureset
+ {
+ public:
+ tile_featureset_pbf(Filter const& filter,
+ mapnik::box2d<double> const& tile_extent,
+ mapnik::box2d<double> const& unbuffered_query,
+ std::set<std::string> const& attribute_names,
+ std::vector<mapbox::util::pbf> const& features,
+ double tile_x,
+ double tile_y,
+ double scale,
+ std::vector<std::string> const& layer_keys,
+ layer_pbf_attr_type const& layer_values)
+ : filter_(filter),
+ tile_extent_(tile_extent),
+ unbuffered_query_(unbuffered_query),
+ features_(features),
+ layer_keys_(layer_keys),
+ layer_values_(layer_values),
+ tile_x_(tile_x),
+ tile_y_(tile_y),
+ scale_(scale),
+ itr_(0),
+ tr_("utf-8"),
+ ctx_(std::make_shared<mapnik::context_type>())
+ {
+ std::set<std::string>::const_iterator pos = attribute_names.begin();
+ std::set<std::string>::const_iterator end = attribute_names.end();
+ for ( ;pos !=end; ++pos)
+ {
+ for (auto const& key : layer_keys_)
+ {
+ if (key == *pos)
+ {
+ ctx_->push(*pos);
+ break;
+ }
+ }
+ }
+ }
+
+ virtual ~tile_featureset_pbf() {}
+
+ feature_ptr next()
+ {
+ while ( itr_ < features_.size() )
+ {
+ mapbox::util::pbf f = features_.at(itr_);
+ // TODO: auto-increment feature id counter here
+ mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,itr_);
+ pbf_attr_value_type val;
+
+ ++itr_;
+ int tagcount=0;
+ uint32_t key_idx, val_idx;
+ int32_t geometry_type = 0; // vector_tile::Tile_GeomType_UNKNOWN
+ while (f.next())
+ {
+ switch(f.tag())
+ {
+ case 1:
+ feature->set_id(f.get_uint64());
+ break;
+ case 2:
+ {
+ auto tag_iterator = f.packed_uint32();
+
+ for (auto _i = tag_iterator.first; _i != tag_iterator.second; ++_i)
+ {
+ if (tagcount % 2 == 0)
+ {
+ key_idx = *_i;
+ ++tagcount;
+ }
+ else
+ {
+ val_idx = *_i;
+ val = layer_values_.at(val_idx);
+ std::string name = layer_keys_.at(key_idx);
+ if (feature->has_key(name))
+ {
+ if (val.is<std::string>())
+ {
+ feature->put(name, tr_.transcode(val.get<std::string>().data(), val.get<std::string>().length()));
+ }
+ else if (val.is<bool>())
+ {
+ feature->put(name, static_cast<mapnik::value_bool>(val.get<bool>()));
+ }
+ else if (val.is<int64_t>())
+ {
+ feature->put(name, static_cast<mapnik::value_integer>(val.get<int64_t>()));
+ }
+ else if (val.is<uint64_t>())
+ {
+ feature->put(name, static_cast<mapnik::value_integer>(val.get<uint64_t>()));
+ }
+ else if (val.is<double>())
+ {
+ feature->put(name, static_cast<mapnik::value_double>(val.get<double>()));
+ }
+ else if (val.is<float>())
+ {
+ feature->put(name, static_cast<mapnik::value_double>(val.get<float>()));
+ } else {
+ throw std::runtime_error("unknown attribute type while reading feature");
+ }
+ ++tagcount;
+ }
+ }
+ }
+ }
+ break;
+ case 3:
+ geometry_type = f.get_enum();
+ switch (geometry_type)
+ {
+ case 1: //vector_tile::Tile_GeomType_POINT
+ case 2: // vector_tile::Tile_GeomType_LINESTRING
+ case 3: // vector_tile::Tile_GeomType_POLYGON
+ break;
+ default: // vector_tile::Tile_GeomType_UNKNOWN or any other value
+ throw std::runtime_error("unknown geometry type " + std::to_string(geometry_type) + " in feature");
+ }
+ break;
+ case 5:
+ {
+ std::string const& image_buffer = f.get_bytes();
+ std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.data(),image_buffer.size()));
+ if (reader.get())
+ {
+ int image_width = reader->width();
+ int image_height = reader->height();
+ if (image_width > 0 && image_height > 0)
+ {
+ mapnik::view_transform t(image_width, image_height, tile_extent_, 0, 0);
+ box2d<double> intersect = tile_extent_.intersect(unbuffered_query_);
+ box2d<double> ext = t.forward(intersect);
+ if (ext.width() > 0.5 && ext.height() > 0.5 )
+ {
+ // select minimum raster containing whole ext
+ int x_off = static_cast<int>(std::floor(ext.minx() +.5));
+ int y_off = static_cast<int>(std::floor(ext.miny() +.5));
+ int end_x = static_cast<int>(std::floor(ext.maxx() +.5));
+ int end_y = static_cast<int>(std::floor(ext.maxy() +.5));
+
+ // clip to available data
+ if (x_off < 0)
+ x_off = 0;
+ if (y_off < 0)
+ y_off = 0;
+ if (end_x > image_width)
+ end_x = image_width;
+ if (end_y > image_height)
+ end_y = image_height;
+ int width = end_x - x_off;
+ int height = end_y - y_off;
+ box2d<double> feature_raster_extent(x_off,
+ y_off,
+ x_off + width,
+ y_off + height);
+ intersect = t.backward(feature_raster_extent);
+ double filter_factor = 1.0;
+ mapnik::image_any data = reader->read(x_off, y_off, width, height);
+ mapnik::raster_ptr raster = std::make_shared<mapnik::raster>(intersect,
+ data,
+ filter_factor
+ );
+ feature->set_raster(raster);
+ return feature;
+ }
+ }
+ }
+ }
+ break;
+ case 4:
+ {
+ auto geom_itr = f.packed_uint32();
+ mapnik::vector_tile_impl::GeometryPBF geoms(geom_itr, tile_x_,tile_y_,scale_,-1*scale_);
+ mapnik::geometry::geometry<double> geom = decode_geometry(geoms, geometry_type);
+ if (geom.is<mapnik::geometry::geometry_empty>())
+ {
+ continue;
+ }
+ mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
+ if (!filter_.pass(envelope))
+ {
+ continue;
+ }
+ feature->set_geometry(std::move(geom));
+ return feature;
+ }
+ break;
+ default:
+ // NOTE: The vector_tile.proto file technically allows for extension fields
+ // of values 16 to max here. Technically, we should just skip() those
+ // fields.
+ // However, if we're fed a corrupt file (or random data), we don't
+ // want to just blindly follow the bytes, so we have made the decision
+ // to abort cleanly, rather than doing GIGO.
+ throw std::runtime_error("unknown field type " + std::to_string(f.tag()) +" in feature");
+
+ }
+ }
+ }
+ return feature_ptr();
+ }
+
+ private:
+ Filter filter_;
+ mapnik::box2d<double> tile_extent_;
+ mapnik::box2d<double> unbuffered_query_;
+ std::vector<mapbox::util::pbf> const& features_;
+ std::vector<std::string> const& layer_keys_;
+ layer_pbf_attr_type const& layer_values_;
+
+ double tile_x_;
+ double tile_y_;
+ double scale_;
+ unsigned itr_;
+ mapnik::transcoder tr_;
+ mapnik::context_ptr ctx_;
+
+ };
+
+ // tile_datasource impl
+ tile_datasource_pbf::tile_datasource_pbf(mapbox::util::pbf const& layer,
+ unsigned x,
+ unsigned y,
+ unsigned z,
+ unsigned tile_size)
+ : datasource(parameters()),
+ desc_("in-memory PBF encoded datasource","utf-8"),
+ attributes_added_(false),
+ layer_(layer),
+ x_(x),
+ y_(y),
+ z_(z),
+ tile_size_(tile_size),
+ extent_initialized_(false),
+ tile_x_(0.0),
+ tile_y_(0.0),
+ scale_(0.0),
+ layer_extent_(0)
+ {
+ double resolution = mapnik::EARTH_CIRCUMFERENCE/(1 << z_);
+ tile_x_ = -0.5 * mapnik::EARTH_CIRCUMFERENCE + x_ * resolution;
+ tile_y_ = 0.5 * mapnik::EARTH_CIRCUMFERENCE - y_ * resolution;
+
+ mapbox::util::pbf val_msg;
+
+ while (layer_.next())
+ {
+ switch(layer_.tag())
+ {
+ case 1:
+ name_ = layer_.get_string();
+ break;
+ case 2:
+ features_.push_back(layer_.get_message());
+ break;
+ case 3:
+ layer_keys_.push_back(layer_.get_string());
+ break;
+ case 4:
+ val_msg = layer_.get_message();
+ while (val_msg.next())
+ {
+ switch(val_msg.tag()) {
+ case 1:
+ layer_values_.push_back(val_msg.get_string());
+ break;
+ case 2:
+ layer_values_.push_back(val_msg.get_float());
+ break;
+ case 3:
+ layer_values_.push_back(val_msg.get_double());
+ break;
+ case 4:
+ layer_values_.push_back(val_msg.get_int64());
+ break;
+ case 5:
+ layer_values_.push_back(val_msg.get_uint64());
+ break;
+ case 6:
+ layer_values_.push_back(val_msg.get_sint64());
+ break;
+ case 7:
+ layer_values_.push_back(val_msg.get_bool());
+ break;
+ default:
+ throw std::runtime_error("unknown Value type " + std::to_string(layer_.tag()) + " in layer.values");
+ }
+ }
+ break;
+ case 5:
+ layer_extent_ = layer_.get_uint32();
+ break;
+ case 15:
+ layer_.skip();
+ break;
+ default:
+ throw std::runtime_error("unknown field type " + std::to_string(layer_.tag()) + " in layer");
+ }
+ }
+ scale_ = (static_cast<double>(layer_extent_) / tile_size_) * tile_size_/resolution;
+ }
+
+ tile_datasource_pbf::~tile_datasource_pbf() {}
+
+ datasource::datasource_t tile_datasource_pbf::type() const
+ {
+ return datasource::Vector;
+ }
+
+ featureset_ptr tile_datasource_pbf::features(query const& q) const
+ {
+ mapnik::filter_in_box filter(q.get_bbox());
+ return std::make_shared<tile_featureset_pbf<mapnik::filter_in_box> >
+ (filter, get_tile_extent(), q.get_unbuffered_bbox(), q.property_names(), features_, tile_x_, tile_y_, scale_, layer_keys_, layer_values_);
+ }
+
+ featureset_ptr tile_datasource_pbf::features_at_point(coord2d const& pt, double tol) const
+ {
+ mapnik::filter_at_point filter(pt,tol);
+ std::set<std::string> names;
+ for (auto const& key : layer_keys_)
+ {
+ names.insert(key);
+ }
+ return std::make_shared<tile_featureset_pbf<filter_at_point> >
+ (filter, get_tile_extent(), get_tile_extent(), names, features_, tile_x_, tile_y_, scale_, layer_keys_, layer_values_);
+ }
+
+ void tile_datasource_pbf::set_envelope(box2d<double> const& bbox)
+ {
+ extent_initialized_ = true;
+ extent_ = bbox;
+ }
+
+ box2d<double> tile_datasource_pbf::get_tile_extent() const
+ {
+ spherical_mercator merc(tile_size_);
+ double minx,miny,maxx,maxy;
+ merc.xyz(x_,y_,z_,minx,miny,maxx,maxy);
+ return box2d<double>(minx,miny,maxx,maxy);
+ }
+
+ box2d<double> tile_datasource_pbf::envelope() const
+ {
+ if (!extent_initialized_)
+ {
+ extent_ = get_tile_extent();
+ extent_initialized_ = true;
+ }
+ return extent_;
+ }
+
+ boost::optional<mapnik::datasource_geometry_t> tile_datasource_pbf::get_geometry_type() const
+ {
+ return mapnik::datasource_geometry_t::Collection;
+ }
+
+ layer_descriptor tile_datasource_pbf::get_descriptor() const
+ {
+ if (!attributes_added_)
+ {
+ for (auto const& key : layer_keys_)
+ {
+ // Object type here because we don't know the precise value until features are unpacked
+ desc_.add_descriptor(attribute_descriptor(key, Object));
+ }
+ attributes_added_ = true;
+ }
+ return desc_;
+ }
+
+ }} // end ns
diff --git a/src/vector_tile_geometry_decoder.hpp b/src/vector_tile_geometry_decoder.hpp
index 938d2e4..c0f1510 100644
--- a/src/vector_tile_geometry_decoder.hpp
+++ b/src/vector_tile_geometry_decoder.hpp
@@ -2,6 +2,7 @@
#define __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
#include "vector_tile.pb.h"
+#include "pbf_reader.hpp"
#include <mapnik/util/is_clockwise.hpp>
@@ -10,6 +11,8 @@
namespace mapnik { namespace vector_tile_impl {
+// NOTE: this object is for one-time use. Once you've progressed to the end
+// by calling next(), to re-iterate, you must construct a new object
class Geometry {
public:
@@ -38,6 +41,34 @@ private:
double ox, oy;
};
+// NOTE: this object is for one-time use. Once you've progressed to the end
+// by calling next(), to re-iterate, you must construct a new object
+class GeometryPBF {
+
+public:
+ inline explicit GeometryPBF(std::pair< mapbox::util::pbf::const_uint32_iterator, mapbox::util::pbf::const_uint32_iterator > const& geo_iterator,
+ double tile_x, double tile_y,
+ double scale_x, double scale_y);
+
+ enum command : uint8_t {
+ end = 0,
+ move_to = 1,
+ line_to = 2,
+ close = 7
+ };
+
+ inline command next(double& rx, double& ry);
+
+private:
+ std::pair< mapbox::util::pbf::const_uint32_iterator, mapbox::util::pbf::const_uint32_iterator > geo_iterator_;
+ double scale_x_;
+ double scale_y_;
+ uint8_t cmd;
+ uint32_t length;
+ double x, y;
+ double ox, oy;
+};
+
Geometry::Geometry(vector_tile::Tile_Feature const& f,
double tile_x, double tile_y,
double scale_x, double scale_y)
@@ -90,22 +121,70 @@ Geometry::command Geometry::next(double& rx, double& ry) {
}
}
-inline mapnik::geometry::geometry<double> decode_geometry(vector_tile::Tile_Feature const& f,
- double tile_x, double tile_y,
- double scale_x, double scale_y,
+GeometryPBF::GeometryPBF(std::pair<mapbox::util::pbf::const_uint32_iterator, mapbox::util::pbf::const_uint32_iterator > const& geo_iterator,
+ double tile_x, double tile_y,
+ double scale_x, double scale_y)
+ : geo_iterator_(geo_iterator),
+ scale_x_(scale_x),
+ scale_y_(scale_y),
+ cmd(1),
+ length(0),
+ 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) {
+ uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
+ cmd = cmd_length & 0x7;
+ length = cmd_length >> 3;
+ }
+
+ --length;
+
+ if (cmd == move_to || cmd == line_to) {
+ int32_t dx = *geo_iterator_.first++;
+ int32_t dy = *geo_iterator_.first++;
+ dx = ((dx >> 1) ^ (-(dx & 1)));
+ dy = ((dy >> 1) ^ (-(dy & 1)));
+ x += (static_cast<double>(dx) / scale_x_);
+ y += (static_cast<double>(dy) / scale_y_);
+ rx = x;
+ ry = y;
+ if (cmd == move_to) {
+ ox = x;
+ oy = y;
+ return move_to;
+ } else {
+ return line_to;
+ }
+ } else if (cmd == close) {
+ rx = ox;
+ ry = oy;
+ return close;
+ } else {
+ fprintf(stderr, "unknown command: %d\n", cmd);
+ return end;
+ }
+ } else {
+ return end;
+ }
+}
+
+template <typename T>
+inline mapnik::geometry::geometry<double> decode_geometry(T & geoms, int32_t geom_type,
bool treat_all_rings_as_exterior=false)
{
- Geometry::command cmd;
- Geometry geoms(f,tile_x,tile_y,scale_x,scale_y);
+ typename T::command cmd;
double x1, y1;
mapnik::geometry::geometry<double> geom; // output geometry
- switch (f.type())
+ switch (geom_type)
{
case vector_tile::Tile_GeomType_POINT:
{
mapnik::geometry::multi_point<double> mp;
- while ((cmd = geoms.next(x1, y1)) != Geometry::end)
+ while ((cmd = geoms.next(x1, y1)) != T::end)
{
mp.emplace_back(mapnik::geometry::point<double>(x1,y1));
}
@@ -129,9 +208,9 @@ inline mapnik::geometry::geometry<double> decode_geometry(vector_tile::Tile_Feat
mapnik::geometry::multi_line_string<double> multi_line;
multi_line.emplace_back();
bool first = true;
- while ((cmd = geoms.next(x1, y1)) != Geometry::end)
+ while ((cmd = geoms.next(x1, y1)) != T::end)
{
- if (cmd == Geometry::move_to)
+ if (cmd == T::move_to)
{
if (first)
{
@@ -173,9 +252,9 @@ inline mapnik::geometry::geometry<double> decode_geometry(vector_tile::Tile_Feat
rings.emplace_back();
double x2,y2;
bool first = true;
- while ((cmd = geoms.next(x1, y1)) != Geometry::end)
+ while ((cmd = geoms.next(x1, y1)) != T::end)
{
- if (cmd == Geometry::move_to)
+ if (cmd == T::move_to)
{
x2 = x1;
y2 = y1;
@@ -188,7 +267,7 @@ inline mapnik::geometry::geometry<double> decode_geometry(vector_tile::Tile_Feat
rings.emplace_back();
}
}
- else if (cmd == Geometry::close)
+ else if (cmd == T::close)
{
rings.back().add_coord(x2,y2);
continue;
@@ -302,7 +381,7 @@ inline mapnik::geometry::geometry<double> decode_geometry(vector_tile::Tile_Feat
else if (num_poly == 1)
{
auto itr = std::make_move_iterator(multi_poly.begin());
- geom = std::move(mapnik::geometry::polygon<double>(std::move(*itr)));
+ geom = mapnik::geometry::polygon<double>(std::move(*itr));
return geom;
}
else
diff --git a/src/vector_tile_geometry_encoder.hpp b/src/vector_tile_geometry_encoder.hpp
index f14f543..dd95f8e 100644
--- a/src/vector_tile_geometry_encoder.hpp
+++ b/src/vector_tile_geometry_encoder.hpp
@@ -21,8 +21,8 @@ inline unsigned encode_geometry(mapnik::geometry::point<std::int64_t> const& pt,
int32_t dx = pt.x - start_x;
int32_t dy = pt.y - start_y;
// Manual zigzag encoding.
- current_feature.add_geometry((dx << 1) ^ (dx >> 31));
- current_feature.add_geometry((dy << 1) ^ (dy >> 31));
+ current_feature.add_geometry((static_cast<unsigned>(dx) << 1) ^ (dx >> 31));
+ current_feature.add_geometry((static_cast<unsigned>(dy) << 1) ^ (dy >> 31));
start_x = pt.x;
start_y = pt.y;
return 1;
@@ -57,8 +57,8 @@ inline unsigned encode_geometry(mapnik::geometry::line_string<std::int64_t> cons
int32_t dx = pt.x - start_x;
int32_t dy = pt.y - start_y;
// Manual zigzag encoding.
- current_feature.add_geometry((dx << 1) ^ (dx >> 31));
- current_feature.add_geometry((dy << 1) ^ (dy >> 31));
+ current_feature.add_geometry((static_cast<unsigned>(dx) << 1) ^ (dx >> 31));
+ current_feature.add_geometry((static_cast<unsigned>(dy) << 1) ^ (dy >> 31));
start_x = pt.x;
start_y = pt.y;
}
@@ -109,8 +109,8 @@ inline unsigned encode_geometry(mapnik::geometry::linear_ring<std::int64_t> cons
int32_t dx = pt.x - start_x;
int32_t dy = pt.y - start_y;
// Manual zigzag encoding.
- current_feature.add_geometry((dx << 1) ^ (dx >> 31));
- current_feature.add_geometry((dy << 1) ^ (dy >> 31));
+ current_feature.add_geometry((static_cast<unsigned>(dx) << 1) ^ (dx >> 31));
+ current_feature.add_geometry((static_cast<unsigned>(dy) << 1) ^ (dy >> 31));
start_x = pt.x;
start_y = pt.y;
++count;
diff --git a/src/vector_tile_processor.ipp b/src/vector_tile_processor.ipp
index 6d639a3..8851fd3 100644
--- a/src/vector_tile_processor.ipp
+++ b/src/vector_tile_processor.ipp
@@ -8,7 +8,6 @@
#include <mapnik/datasource.hpp>
#include <mapnik/projection.hpp>
#include <mapnik/proj_transform.hpp>
-#include <mapnik/proj_strategy.hpp>
#include <mapnik/scale_denominator.hpp>
#include <mapnik/attribute_descriptor.hpp>
#include <mapnik/feature_layer_desc.hpp>
@@ -22,13 +21,11 @@
#include <mapnik/image_scaling.hpp>
#include <mapnik/image_compositing.hpp>
#include <mapnik/view_transform.hpp>
-#include <mapnik/view_strategy.hpp>
#include <mapnik/util/noncopyable.hpp>
#include <mapnik/transform_path_adapter.hpp>
#include <mapnik/geometry_is_empty.hpp>
#include <mapnik/geometry_envelope.hpp>
#include <mapnik/geometry_adapters.hpp>
-#include <mapnik/geometry_strategy.hpp>
#include <mapnik/geometry_transform.hpp>
// agg
@@ -50,6 +47,7 @@
#include <string>
#include <stdexcept>
+#include "vector_tile_strategy.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
@@ -796,29 +794,12 @@ void processor<T>::apply_to_layer(mapnik::layer const& lay,
feature = features->next();
continue;
}
- if (geom.is<mapnik::geometry::geometry_collection<double> >())
+ if (handle_geometry(*feature,
+ geom,
+ prj_trans,
+ buffered_query_ext) > 0)
{
- auto const& collection = mapnik::util::get<mapnik::geometry::geometry_collection<double> >(geom);
- for (auto const& part : collection)
- {
- if (handle_geometry(*feature,
- part,
- prj_trans,
- buffered_query_ext) > 0)
- {
- painted_ = true;
- }
- }
- }
- else
- {
- if (handle_geometry(*feature,
- geom,
- prj_trans,
- buffered_query_ext) > 0)
- {
- painted_ = true;
- }
+ painted_ = true;
}
feature = features->next();
}
@@ -885,10 +866,14 @@ struct encoder_visitor {
return 0;
}
- unsigned operator() (mapnik::geometry::geometry_collection<std::int64_t> const& geom)
+ unsigned operator() (mapnik::geometry::geometry_collection<std::int64_t> & geom)
{
- //throw std::runtime_error("geometry_collections not supported in encoder_visitor");
- return 0;
+ unsigned count = 0;
+ for (auto & g : geom)
+ {
+ count += mapnik::util::apply_visitor((*this), g);
+ }
+ return count;
}
unsigned operator() (mapnik::geometry::point<std::int64_t> const& geom)
@@ -1229,8 +1214,12 @@ struct simplify_visitor {
unsigned operator() (mapnik::geometry::geometry_collection<std::int64_t> const& geom)
{
- //throw std::runtime_error("geometry_collection not supported in simplify_visitor");
- return 0;
+ unsigned count = 0;
+ for (auto const& g : geom)
+ {
+ count += mapnik::util::apply_visitor((*this), g);
+ }
+ return count;
}
unsigned operator() (mapnik::geometry::geometry_empty const& geom)
@@ -1249,19 +1238,13 @@ unsigned processor<T>::handle_geometry(mapnik::feature_impl const& feature,
mapnik::proj_transform const& prj_trans,
mapnik::box2d<double> const& buffered_query_ext)
{
- mapnik::proj_backward_strategy proj_strat(prj_trans);
- mapnik::view_strategy view_strat(t_);
- mapnik::geometry::scale_strategy scale_strat(backend_.get_path_multiplier(), 0.5);
- using sg_type = mapnik::geometry::strategy_group<mapnik::proj_backward_strategy,
- mapnik::view_strategy,
- mapnik::geometry::scale_strategy >;
- sg_type sg(proj_strat, view_strat, scale_strat);
+ 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, sg);
- mapnik::geometry::point<std::int64_t> p2_max = mapnik::geometry::transform<std::int64_t>(p1_max, sg);
+ 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::geometry::geometry<std::int64_t> new_geom = mapnik::geometry::transform<std::int64_t>(geom, sg);
+ mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::geometry::transform<std::int64_t>(geom, vs);
encoder_visitor<T> encoder(backend_,feature,bbox, area_threshold_);
if (simplify_distance_ > 0)
{
diff --git a/src/vector_tile_strategy.hpp b/src/vector_tile_strategy.hpp
new file mode 100644
index 0000000..1f8c045
--- /dev/null
+++ b/src/vector_tile_strategy.hpp
@@ -0,0 +1,88 @@
+/*****************************************************************************
+ *
+ * This file is part of Mapnik (c++ mapping toolkit)
+ *
+ * Copyright (C) 2014 Artem Pavlenko
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ *****************************************************************************/
+
+#ifndef MAPNIK_VECTOR_TILE_STRATEGY_HPP
+#define MAPNIK_VECTOR_TILE_STRATEGY_HPP
+
+// mapnik
+#include <mapnik/config.hpp>
+#include <mapnik/util/noncopyable.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/view_transform.hpp>
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wunused-local-typedef"
+#include <boost/geometry/core/coordinate_type.hpp>
+#include <boost/geometry/core/access.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+#pragma GCC diagnostic pop
+
+
+namespace mapnik {
+
+namespace vector_tile_impl {
+
+struct vector_tile_strategy
+{
+ vector_tile_strategy(proj_transform const& prj_trans,
+ view_transform const& tr,
+ double scaling)
+ : prj_trans_(prj_trans),
+ 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);
+ double z = 0.0;
+ if (!prj_trans_.backward(x, y, z)) return false;
+ tr_.forward(&x,&y);
+ x = x * scaling_;
+ y = y * scaling_;
+ x = std::round(x);
+ y = std::round(y);
+ 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;
+ }
+
+ proj_transform const& prj_trans_;
+ view_transform const& tr_;
+ double const scaling_;
+};
+
+}
+}
+
+#endif // MAPNIK_VECTOR_TILE_STRATEGY_HPP
diff --git a/test/clipper_test.cpp b/test/clipper_test.cpp
new file mode 100644
index 0000000..d06b2bc
--- /dev/null
+++ b/test/clipper_test.cpp
@@ -0,0 +1,143 @@
+#include <limits>
+#include <iostream>
+#include <mapnik/projection.hpp>
+#include <mapnik/geometry_transform.hpp>
+//#include <mapnik/util/geometry_to_geojson.hpp>
+
+#include "vector_tile_strategy.hpp"
+#include "vector_tile_projection.hpp"
+
+#include "catch.hpp"
+#include "clipper.hpp"
+
+TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
+ mapnik::projection merc("+init=epsg:3857",true);
+ mapnik::proj_transform prj_trans(merc,merc); // 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 vs(prj_trans, tr, 16);
+ // even an invalid point is not expected to result in values beyond hirange
+ mapnik::geometry::point<std::int64_t> g(-20037508.342789*2,-20037508.342789*2);
+ mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::geometry::transform<std::int64_t>(g, vs);
+ REQUIRE( new_geom.is<mapnik::geometry::point<std::int64_t>>() );
+ auto const& pt = mapnik::util::get<mapnik::geometry::point<std::int64_t>>(new_geom);
+ REQUIRE( (pt.x < ClipperLib::hiRange) );
+ REQUIRE( (pt.y < ClipperLib::hiRange) );
+ REQUIRE( (-pt.x < ClipperLib::hiRange) );
+ REQUIRE( (-pt.y < ClipperLib::hiRange) );
+ }
+ merc_tiler.xyz(0,0,0,minx,miny,maxx,maxy);
+ mapnik::geometry::polygon<std::int64_t> g;
+ g.exterior_ring.add_coord(minx,miny);
+ g.exterior_ring.add_coord(maxx,miny);
+ g.exterior_ring.add_coord(maxx,maxy);
+ g.exterior_ring.add_coord(minx,maxy);
+ g.exterior_ring.add_coord(minx,miny);
+ {
+ // 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::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);
+ 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) );
+ }
+ }
+ {
+ // 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);
+ 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)
+ ));
+ }
+ }
+
+}
+
+TEST_CASE( "clipper IntPoint", "should accept 64bit values" ) {
+ std::int64_t x = 4611686018427387903;
+ std::int64_t y = 4611686018427387903;
+ auto x0 = std::numeric_limits<std::int64_t>::max();
+ auto y0 = std::numeric_limits<std::int64_t>::max();
+ REQUIRE( x == ClipperLib::hiRange );
+ REQUIRE( y == ClipperLib::hiRange );
+ REQUIRE( (x0/2) == ClipperLib::hiRange );
+ REQUIRE( (y0/2) == ClipperLib::hiRange );
+ auto pt = ClipperLib::IntPoint(x,y);
+ CHECK( pt.x == x );
+ CHECK( pt.y == y );
+ auto pt2 = ClipperLib::IntPoint(x,y);
+ CHECK( (pt == pt2) );
+ CHECK( !(pt != pt2) );
+ // this is invalid when passed to RangeTest but should
+ // still be able to be created
+ auto pt3 = ClipperLib::IntPoint(x0,y0);
+ REQUIRE( pt3.x == x0 );
+ REQUIRE( pt3.y == y0 );
+ REQUIRE( (pt3.x/2) == ClipperLib::hiRange );
+ REQUIRE( (pt3.y/2) == ClipperLib::hiRange );
+ CHECK( (pt != pt3) );
+}
+
+TEST_CASE( "clipper AddPath 1", "should not throw within range coords" ) {
+ ClipperLib::Clipper clipper;
+ ClipperLib::Path clip_box; // actually mapnik::geometry::line_string<std::int64_t>
+ // values that should just barely work since they are one below the
+ // threshold
+ auto x0 = (std::numeric_limits<std::int64_t>::min()/2)+1;
+ auto y0 = (std::numeric_limits<std::int64_t>::min()/2)+1;
+ auto x1 = (std::numeric_limits<std::int64_t>::max()/2);
+ auto y1 = (std::numeric_limits<std::int64_t>::max()/2);
+ clip_box.emplace_back(x0,y0);
+ clip_box.emplace_back(x1,y0);
+ clip_box.emplace_back(x1,y1);
+ clip_box.emplace_back(x0,y1);
+ clip_box.emplace_back(x0,y0);
+ CHECK( clipper.AddPath(clip_box,ClipperLib::ptClip,true) );
+}
+
+TEST_CASE( "clipper AddPath 2", "should throw on out of range coords" ) {
+ ClipperLib::Clipper clipper;
+ ClipperLib::Path clip_box; // actually mapnik::geometry::line_string<std::int64_t>
+ auto x0 = std::numeric_limits<std::int64_t>::min()+1;
+ auto y0 = std::numeric_limits<std::int64_t>::min()+1;
+ auto x1 = std::numeric_limits<std::int64_t>::max()-1;
+ auto y1 = std::numeric_limits<std::int64_t>::max()-1;
+ clip_box.emplace_back(x0,y0);
+ clip_box.emplace_back(x1,y0);
+ clip_box.emplace_back(x1,y1);
+ clip_box.emplace_back(x0,y1);
+ clip_box.emplace_back(x0,y0);
+ try
+ {
+ clipper.AddPath(clip_box,ClipperLib::ptClip,true);
+ FAIL( "expected exception" );
+ }
+ catch(std::exception const& ex)
+ {
+ REQUIRE(std::string(ex.what()) == "Coordinate outside allowed range: -9223372036854775807 -9223372036854775807 9223372036854775807 9223372036854775807");
+ }
+}
+
diff --git a/test/data/natural_earth.tif b/test/data/natural_earth.tif
deleted file mode 100644
index 43500b0..0000000
Binary files a/test/data/natural_earth.tif and /dev/null differ
diff --git a/test/data/tile_with_extra_feature_field.pbf b/test/data/tile_with_extra_feature_field.pbf
new file mode 100644
index 0000000..484e656
--- /dev/null
+++ b/test/data/tile_with_extra_feature_field.pbf
@@ -0,0 +1,2 @@
+
+this is the name0c(��x
\ No newline at end of file
diff --git a/test/data/tile_with_extra_field.pbf b/test/data/tile_with_extra_field.pbf
new file mode 100644
index 0000000..0c08869
--- /dev/null
+++ b/test/data/tile_with_extra_field.pbf
@@ -0,0 +1,2 @@
+
+this is the name(��x c
\ No newline at end of file
diff --git a/test/data/tile_with_extra_layer_fields.pbf b/test/data/tile_with_extra_layer_fields.pbf
new file mode 100644
index 0000000..8dc5537
--- /dev/null
+++ b/test/data/tile_with_extra_layer_fields.pbf
@@ -0,0 +1,2 @@
+
+this is the name(��0cx�c
\ No newline at end of file
diff --git a/test/data/tile_with_invalid_layer_value_type.pbf b/test/data/tile_with_invalid_layer_value_type.pbf
new file mode 100644
index 0000000..8c8b9ef
--- /dev/null
+++ b/test/data/tile_with_invalid_layer_value_type.pbf
@@ -0,0 +1,2 @@
+
+this is the name"@c(��x
\ No newline at end of file
diff --git a/test/data/tile_with_unexpected_geomtype.pbf b/test/data/tile_with_unexpected_geomtype.pbf
new file mode 100644
index 0000000..f718cd2
--- /dev/null
+++ b/test/data/tile_with_unexpected_geomtype.pbf
@@ -0,0 +1,3 @@
+
+this is the name(��x"
+this is the name"cccc(��x
\ No newline at end of file
diff --git a/test/encoding_util.hpp b/test/encoding_util.hpp
index cf10bde..5568c48 100644
--- a/test/encoding_util.hpp
+++ b/test/encoding_util.hpp
@@ -150,6 +150,7 @@ template <typename T>
std::string compare(mapnik::geometry::geometry<T> const& g)
{
vector_tile::Tile_Feature feature = geometry_to_feature(g);
- auto g2 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ mapnik::vector_tile_impl::Geometry geoms(feature,0.0,0.0,1.0,1.0);
+ auto g2 = mapnik::vector_tile_impl::decode_geometry(geoms,feature.type());
return decode_to_path_string(g2);
}
diff --git a/test/geometry_encoding.cpp b/test/geometry_encoding.cpp
index 31a8805..b599580 100644
--- a/test/geometry_encoding.cpp
+++ b/test/geometry_encoding.cpp
@@ -180,7 +180,8 @@ TEST_CASE( "polygon with degenerate exterior ring ", "should be culled" ) {
vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
// since first ring is degenerate the whole polygon should be culled
- auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ 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::geometry_empty>() );
}
@@ -206,7 +207,8 @@ TEST_CASE( "polygon with degenerate exterior ring ", "should be culled" ) {
vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
// since first ring is degenerate the whole polygon should be culled
- auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ 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::geometry_empty>() );
}*/
@@ -230,7 +232,8 @@ TEST_CASE( "polygon with valid exterior ring but degenerate interior ring", "sho
CHECK( wkt0 == expected_wkt0);
vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
- auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ 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> >() );
auto const& poly = mapnik::util::get<mapnik::geometry::polygon<double> >(p1);
// since interior ring is degenerate it should have been culled when decoded
@@ -270,7 +273,8 @@ TEST_CASE( "polygon with valid exterior ring but one degenerate interior ring of
CHECK( wkt0 == expected_wkt0);
vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
- auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ 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> >() );
auto const& poly = mapnik::util::get<mapnik::geometry::polygon<double> >(p1);
// since first interior ring is degenerate it should have been culled when decoded
@@ -311,7 +315,8 @@ TEST_CASE( "(multi)polygon with hole", "should round trip without changes" ) {
CHECK( wkt0 == expected_wkt0);
vector_tile::Tile_Feature feature = geometry_to_feature<std::int64_t>(p0);
- auto p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0);
+ 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> >() );
CHECK( extent == mapnik::geometry::envelope(p1) );
@@ -323,7 +328,9 @@ TEST_CASE( "(multi)polygon with hole", "should round trip without changes" ) {
// 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
- auto _p1 = mapnik::vector_tile_impl::decode_geometry(feature,0.0,0.0,1.0,1.0,true);
+ // 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> >() );
@@ -378,7 +385,8 @@ TEST_CASE( "(multi)polygon with hole", "should round trip without changes" ) {
mapnik::box2d<double> multi_extent = mapnik::geometry::envelope(multi_poly);
vector_tile::Tile_Feature feature1 = geometry_to_feature<std::int64_t>(multi_poly);
- auto mp = mapnik::vector_tile_impl::decode_geometry(feature1,0.0,0.0,1.0,1.0);
+ 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) );
diff --git a/test/test_main.cpp b/test/test_main.cpp
index 8d5a3a8..e12617c 100644
--- a/test/test_main.cpp
+++ b/test/test_main.cpp
@@ -6,7 +6,14 @@
int main (int argc, char* const argv[])
{
- GOOGLE_PROTOBUF_VERIFY_VERSION;
+ try
+ {
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ }
+ catch (std::exception const& ex) {
+ std::clog << ex.what() << "\n";
+ return -1;
+ }
int result = Catch::Session().run( argc, argv );
if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n");
google::protobuf::ShutdownProtobufLibrary();
diff --git a/test/test_utils.cpp b/test/test_utils.cpp
index 7378d5e..b156662 100644
--- a/test/test_utils.cpp
+++ b/test/test_utils.cpp
@@ -30,6 +30,14 @@ std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool seco
mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
mapnik::transcoder tr("utf-8");
feature->put("name",tr.transcode("null island"));
+ // NOTE: all types below that are not part of mapnik::value
+ // are likely getting converted, e.g. float -> double
+ feature->put_new("int",static_cast<int64_t>(-73));
+ feature->put_new("uint",static_cast<uint64_t>(37));
+ feature->put_new("float",static_cast<float>(99.2));
+ feature->put_new("double",static_cast<double>(83.4));
+ feature->put_new("bool",true);
+ feature->put_new("boolf",false);
feature->set_geometry(mapnik::geometry::point<double>(x,y));
ds->push(feature);
if (second) {
@@ -46,13 +54,17 @@ std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool seco
std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& geojson_file) {
mapnik::util::file input(geojson_file);
- auto json = input.data();
+ if (!input.open())
+ {
+ throw std::runtime_error("failed to open geojson");
+ }
mapnik::geometry::geometry<double> geom;
- std::string json_string(json.get());
+ 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);
mapnik::parameters params;
params["type"] = "memory";
std::shared_ptr<mapnik::memory_datasource> ds = std::make_shared<mapnik::memory_datasource>(params);
diff --git a/test/vector_tile.cpp b/test/vector_tile.cpp
index 04ecb1c..3a15986 100644
--- a/test/vector_tile.cpp
+++ b/test/vector_tile.cpp
@@ -126,10 +126,10 @@ TEST_CASE( "vector tile output 1", "should create vector tile with two points" )
CHECK(9 == f.geometry(0));
CHECK(4096 == f.geometry(1));
CHECK(4096 == f.geometry(2));
- CHECK(95 == tile.ByteSize());
+ CHECK(194 == tile.ByteSize());
std::string buffer;
CHECK(tile.SerializeToString(&buffer));
- CHECK(95 == buffer.size());
+ CHECK(194 == buffer.size());
}
TEST_CASE( "vector tile output 2", "adding empty layers should result in empty tile" ) {
@@ -232,7 +232,7 @@ TEST_CASE( "vector tile input", "should be able to parse message and render poin
// serialize to message
std::string buffer;
CHECK(tile.SerializeToString(&buffer));
- CHECK(52 == buffer.size());
+ CHECK(151 == buffer.size());
// now create new objects
mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
tile_type tile2;
@@ -254,7 +254,13 @@ TEST_CASE( "vector tile input", "should be able to parse message and render poin
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("bool");
+ expected_names.push_back("boolf");
+ expected_names.push_back("double");
+ expected_names.push_back("float");
+ expected_names.push_back("int");
expected_names.push_back("name");
+ expected_names.push_back("uint");
std::vector<std::string> names;
for (auto const& desc : lay_desc.get_descriptors())
{
@@ -540,25 +546,11 @@ TEST_CASE( "encoding single line 1", "should maintain start/end vertex" ) {
// ported from shapefile test in tilelive-bridge (a:should render a (1.0.1))
TEST_CASE( "encoding single line 2", "should maintain start/end vertex" ) {
unsigned path_multiplier = 16;
- //unsigned tolerance = 5;
vector_tile::Tile tile;
mapnik::vector_tile_impl::backend_pbf backend(tile,path_multiplier);
backend.start_tile_layer("layer");
mapnik::feature_ptr feature(mapnik::feature_factory::create(std::make_shared<mapnik::context_type>(),1));
backend.start_tile_feature(*feature);
- /*
- std::unique_ptr<mapnik::geometry_type> g(new mapnik::geometry_type(mapnik::geometry_type::types::Polygon));
- g->move_to(168.267850,-24.576888);
- g->line_to(167.982618,-24.697145);
- g->line_to(168.114561,-24.783548);
- g->line_to(168.267850,-24.576888);
- g->line_to(168.267850,-24.576888);
- g->close_path();
- //g->push_vertex(256.000000,-0.00000, mapnik::SEG_CLOSE);
- // todo - why does shape_io result in on-zero close path x,y?
- mapnik::vertex_adapter va(*g);
- backend.add_path(va, tolerance, g->type());
- */
mapnik::geometry::polygon<double> geom;
{
mapnik::geometry::linear_ring<double> ring;
@@ -569,7 +561,7 @@ TEST_CASE( "encoding single line 2", "should maintain start/end vertex" ) {
ring.add_coord(168.267850,-24.576888);
geom.set_exterior_ring(std::move(ring));
}
- mapnik::geometry::scale_strategy scale_strat(backend.get_path_multiplier(), 0.5);
+ mapnik::geometry::scale_rounding_strategy scale_strat(backend.get_path_multiplier());
mapnik::geometry::polygon<std::int64_t> poly = mapnik::geometry::transform<std::int64_t>(geom, scale_strat);
std::string foo;
mapnik::util::to_wkt(foo, poly);
@@ -627,13 +619,44 @@ mapnik::geometry::geometry<double> round_trip(mapnik::geometry::geometry<double>
}
vector_tile::Tile_Feature const& f = layer.features(0);
double scale = (double)path_multiplier;
- return mapnik::vector_tile_impl::decode_geometry(f,0,0,scale,-1*scale);
+
+ mapnik::vector_tile_impl::Geometry geoms(f,0,0,scale,-1*scale);
+ return mapnik::vector_tile_impl::decode_geometry(geoms, f.type());
}
TEST_CASE( "vector tile point encoding", "should create vector tile with data" ) {
mapnik::geometry::point<double> geom(0,0);
mapnik::geometry::geometry<double> new_geom = round_trip(geom);
CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POINT(128 -128)" );
+ CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE( "vector tile geometry collection encoding", "should create vector tile with data" ) {
+ mapnik::geometry::point<double> geom_p(0,0);
+ mapnik::geometry::geometry_collection<double> geom;
+ geom.push_back(geom_p);
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POINT(128 -128)" );
+ CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE( "vector tile geometry collection encoding x2", "should create vector tile with data" ) {
+ mapnik::geometry::point<double> geom_p(0,0);
+ mapnik::geometry::geometry_collection<double> geom_t;
+ geom_t.push_back(geom_p);
+ mapnik::geometry::geometry_collection<double> geom;
+ geom.push_back(std::move(geom_t));
+ mapnik::geometry::geometry<double> new_geom = round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( mapnik::util::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POINT(128 -128)" );
CHECK( new_geom.is<mapnik::geometry::point<double> >() );
}
@@ -667,7 +690,7 @@ TEST_CASE( "vector tile line_string encoding", "should create vector tile with d
mapnik::geometry::geometry<double> new_geom = round_trip(geom);
std::string wkt;
CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "LINESTRING(128 -128,192.001 0)" );
+ CHECK( wkt == "LINESTRING(128 -128,192 0)" );
CHECK( !mapnik::geometry::is_empty(new_geom) );
CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
}
@@ -681,7 +704,7 @@ TEST_CASE( "vector tile multi_line_string encoding of single line_string", "shou
mapnik::geometry::geometry<double> new_geom = round_trip(geom);
std::string wkt;
CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "LINESTRING(128 -128,192.001 0)" );
+ CHECK( wkt == "LINESTRING(128 -128,192 0)" );
CHECK( !mapnik::geometry::is_empty(new_geom) );
CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
}
@@ -699,7 +722,7 @@ TEST_CASE( "vector tile multi_line_string encoding of actual multi_line_string",
mapnik::geometry::geometry<double> new_geom = round_trip(geom);
std::string wkt;
CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "MULTILINESTRING((128 -128,192.001 0),(120.889 -128,63.288 -256))" );
+ CHECK( wkt == "MULTILINESTRING((128 -128,192 0),(120.889 -128,63.288 -256))" );
CHECK( !mapnik::geometry::is_empty(new_geom) );
CHECK( new_geom.is<mapnik::geometry::multi_line_string<double> >() );
}
@@ -808,7 +831,7 @@ TEST_CASE( "vector tile line_string is simplified", "should create vector tile w
mapnik::geometry::geometry<double> new_geom = round_trip(line,500);
std::string wkt;
CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "LINESTRING(128 -128,192.001 0)" );
+ CHECK( wkt == "LINESTRING(128 -128,192 0)" );
CHECK( !mapnik::geometry::is_empty(new_geom) );
CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
auto const& line2 = mapnik::util::get<mapnik::geometry::line_string<double> >(new_geom);
@@ -826,7 +849,7 @@ TEST_CASE( "vector tile multi_line_string is simplified", "should create vector
mapnik::geometry::geometry<double> new_geom = round_trip(geom,500);
std::string wkt;
CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "LINESTRING(128 -128,192.001 0)" );
+ CHECK( wkt == "LINESTRING(128 -128,192 0)" );
CHECK( !mapnik::geometry::is_empty(new_geom) );
CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
auto const& line2 = mapnik::util::get<mapnik::geometry::line_string<double> >(new_geom);
@@ -917,10 +940,7 @@ TEST_CASE( "vector tile from simplified geojson", "should create vector tile wit
mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
mapnik::layer lyr("layer","+init=epsg:4326");
- // create a datasource with a feature outside the map
std::shared_ptr<mapnik::memory_datasource> ds = testing::build_geojson_ds("./test/data/poly.geojson");
- // but fake the overall envelope to ensure the layer is still processed
- // and then removed given no intersecting features will be added
ds->set_envelope(mapnik::box2d<double>(160.147311,11.047284,160.662858,11.423830));
lyr.set_datasource(ds);
map.add_layer(lyr);
@@ -941,7 +961,8 @@ TEST_CASE( "vector tile from simplified geojson", "should create vector tile wit
double tile_x = -0.5 * mapnik::EARTH_CIRCUMFERENCE + x * resolution;
double tile_y = 0.5 * mapnik::EARTH_CIRCUMFERENCE - y * resolution;
double scale = (static_cast<double>(layer.extent()) / tile_size) * tile_size/resolution;
- auto geom = mapnik::vector_tile_impl::decode_geometry(f,tile_x,tile_y,scale,-1*scale);
+ mapnik::vector_tile_impl::Geometry geoms(f,tile_x, tile_y,scale,-1*scale);
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms,f.type());
unsigned int n_err = 0;
mapnik::projection wgs84("+init=epsg:4326",true);
@@ -951,7 +972,7 @@ TEST_CASE( "vector tile from simplified geojson", "should create vector tile wit
CHECK( n_err == 0 );
std::string geojson_string;
CHECK( mapnik::util::to_geojson(geojson_string,projected_geom) );
- CHECK( geojson_string == "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[160.42640625,11.4238608092025],[160.41375,11.404562686369],[160.3996875,11.3949131331061],[160.3996875,11.3990486960562],[160.39265625,11.4031841988239],[160.3940625,11.3976701817588],[160.38703125,11.3838846711709],[160.39265625,11.3825060833676],[160.39125,11.3618264654176],[160.3378125,11.3397665531013],[160.3434375,11.3604477708622],[160.26609375,11.3094313929343],[160.28296875,11.3011576095711],[160.29,11.2 [...]
+ CHECK( geojson_string == "{\"type\":\"Polygon\",\"coordinates\":[[[160.42359375,11.422482415387],[160.40671875,11.3976701817587],[160.396875,11.3935345987523],[160.39828125,11.4018057045895],[160.39265625,11.4004272036667],[160.38984375,11.3811274888866],[160.3940625,11.3838846711709],[160.3771875,11.3521754635814],[160.33921875,11.3590690696413],[160.35046875,11.3645838345287],[160.3575,11.3645838345287],[160.3575,11.3756130442004],[160.28859375,11.3480392200085],[160.295625,11.3287 [...]
}
mapnik::geometry::geometry<double> round_trip2(mapnik::geometry::geometry<double> const& geom,
@@ -1000,7 +1021,8 @@ mapnik::geometry::geometry<double> round_trip2(mapnik::geometry::geometry<double
double tile_x = -0.5 * mapnik::EARTH_CIRCUMFERENCE + x * resolution;
double tile_y = 0.5 * mapnik::EARTH_CIRCUMFERENCE - y * resolution;
double scale = (static_cast<double>(layer.extent()) / tile_size) * tile_size/resolution;
- return mapnik::vector_tile_impl::decode_geometry(f,tile_x,tile_y,scale,-1*scale);
+ mapnik::vector_tile_impl::Geometry geoms(f,tile_x, tile_y,scale,-1*scale);
+ return mapnik::vector_tile_impl::decode_geometry(geoms,f.type());
}
TEST_CASE( "vector tile line_string is verify direction", "should line string with proper directions" ) {
@@ -1028,7 +1050,7 @@ 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.9998101102059,2.00006103515625 59.9998101102059,2.00006103515625 7.99994115658817,0.0000000000000005 7.99994115658817))" );
+ 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))" );
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);
diff --git a/test/vector_tile_pbf.cpp b/test/vector_tile_pbf.cpp
new file mode 100644
index 0000000..25482e5
--- /dev/null
+++ b/test/vector_tile_pbf.cpp
@@ -0,0 +1,557 @@
+#include "catch.hpp"
+
+// test utils
+#include "test_utils.hpp"
+#include <mapnik/memory_datasource.hpp>
+#include <mapnik/util/fs.hpp>
+#include <mapnik/agg_renderer.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/load_map.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/vertex_adapters.hpp>
+#include <mapnik/projection.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/geometry_is_empty.hpp>
+#include <mapnik/util/geometry_to_geojson.hpp>
+#include <mapnik/util/geometry_to_wkt.hpp>
+#include <mapnik/geometry_reprojection.hpp>
+#include <mapnik/geometry_transform.hpp>
+#include <mapnik/geometry_strategy.hpp>
+#include <mapnik/proj_strategy.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/datasource_cache.hpp>
+
+#include <boost/optional/optional_io.hpp>
+
+// vector output api
+#include "vector_tile_compression.hpp"
+#include "vector_tile_processor.hpp"
+#include "vector_tile_backend_pbf.hpp"
+#include "vector_tile_util.hpp"
+#include "vector_tile_projection.hpp"
+#include "vector_tile_geometry_decoder.hpp"
+
+// vector input api
+#include "vector_tile_datasource.hpp"
+#include "vector_tile_datasource_pbf.hpp"
+#include "pbf_reader.hpp"
+
+#include <string>
+#include <fstream>
+#include <streambuf>
+
+TEST_CASE( "pbf vector tile input", "should be able to parse message and render point" ) {
+ 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,16);
+ 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");
+ mapnik::layer lyr("layer",map.srs());
+ lyr.set_datasource(testing::build_ds(0,0));
+ 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);
+ ren.apply();
+ // serialize to message
+ std::string buffer;
+ CHECK(tile.SerializeToString(&buffer));
+ CHECK(151 == 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(1 == layer2.features_size());
+
+ mapnik::layer lyr2("layer",map.srs());
+
+ mapbox::util::pbf pbf_tile(buffer.c_str(), buffer.size());
+ pbf_tile.next();
+ mapbox::util::pbf layer3 = pbf_tile.get_message();
+
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer3,0,0,0,map2.width());
+ CHECK(ds->get_name() == "layer");
+ 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("bool");
+ expected_names.push_back("boolf");
+ expected_names.push_back("double");
+ expected_names.push_back("float");
+ expected_names.push_back("int");
+ expected_names.push_back("name");
+ expected_names.push_back("uint");
+ 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");
+ map2.add_layer(lyr2);
+ mapnik::load_map(map2,"test/data/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/expected-1.png")) {
+ mapnik::save_to_file(im,"test/fixtures/expected-1.png","png32");
+ }
+ unsigned diff = testing::compare_images(im,"test/fixtures/expected-1.png");
+ CHECK(0 == diff);
+ if (diff > 0) {
+ mapnik::save_to_file(im,"test/fixtures/actual-1.png","png32");
+ }
+}
+
+
+TEST_CASE( "pbf vector tile datasource", "should filter features outside extent" ) {
+ 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,16);
+ 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");
+ mapnik::layer lyr("layer",map.srs());
+ lyr.set_datasource(testing::build_ds(0,0));
+ map.add_layer(lyr);
+ mapnik::request m_req(tile_size,tile_size,bbox);
+ renderer_type ren(backend,map,m_req);
+ ren.apply();
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
+ CHECK(1 == tile.layers_size());
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ CHECK(std::string("layer") == layer.name());
+ CHECK(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());
+ CHECK(9 == f.geometry(0));
+ CHECK(4096 == f.geometry(1));
+ CHECK(4096 == f.geometry(2));
+
+ std::string buffer;
+ tile.SerializeToString(&buffer);
+ mapbox::util::pbf pbf_tile(buffer.c_str(), buffer.size());
+ pbf_tile.next();
+ mapbox::util::pbf layer2 = pbf_tile.get_message();
+
+ // now actually start the meat of the test
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+ mapnik::featureset_ptr fs;
+
+ // ensure we can query single feature
+ fs = ds.features(mapnik::query(bbox));
+ mapnik::feature_ptr feat = fs->next();
+ CHECK(feat != mapnik::feature_ptr());
+ CHECK(feat->size() == 0);
+ CHECK(fs->next() == mapnik::feature_ptr());
+ mapnik::query qq = mapnik::query(mapnik::box2d<double>(-1,-1,1,1));
+ qq.add_property_name("name");
+ fs = ds.features(qq);
+ feat = fs->next();
+ CHECK(feat != mapnik::feature_ptr());
+ CHECK(feat->size() == 1);
+// CHECK(feat->get("name") == "null island");
+
+ // now check that datasource api throws out feature which is outside extent
+ fs = ds.features(mapnik::query(mapnik::box2d<double>(-10,-10,-10,-10)));
+ CHECK(fs->next() == mapnik::feature_ptr());
+
+ // ensure same behavior for feature_at_point
+ fs = ds.features_at_point(mapnik::coord2d(0.0,0.0),0.0001);
+ CHECK(fs->next() != mapnik::feature_ptr());
+
+ fs = ds.features_at_point(mapnik::coord2d(1.0,1.0),1.0001);
+ CHECK(fs->next() != mapnik::feature_ptr());
+
+ fs = ds.features_at_point(mapnik::coord2d(-10,-10),0);
+ CHECK(fs->next() == mapnik::feature_ptr());
+
+ // finally, make sure attributes are also filtered
+ mapnik::feature_ptr f_ptr;
+ fs = ds.features(mapnik::query(bbox));
+ f_ptr = fs->next();
+ CHECK(f_ptr != mapnik::feature_ptr());
+ // no attributes
+ CHECK(f_ptr->context()->size() == 0);
+
+ mapnik::query q(bbox);
+ q.add_property_name("name");
+ fs = ds.features(q);
+ f_ptr = fs->next();
+ CHECK(f_ptr != mapnik::feature_ptr());
+ // one attribute
+ CHECK(f_ptr->context()->size() == 1);
+}
+
+// NOTE: encoding multiple lines as one path is technically incorrect
+// because in Mapnik the protocol is to split geometry parts into separate paths.
+// However this case should still be supported because keeping a single flat array is an
+// important optimization in the case that lines do not need to be labeled in custom ways
+// or represented as GeoJSON
+TEST_CASE( "pbf encoding multi line as one path", "should maintain second move_to command" ) {
+ // Options
+ // here we use a multiplier of 1 to avoid rounding numbers
+ // and stay in integer space for simplity
+ unsigned path_multiplier = 1;
+ // here we use an extreme tolerance to prove that all vertices are maintained no matter
+ // the tolerance because we never want to drop a move_to or the first line_to
+ //unsigned tolerance = 2000000;
+ // now create the testing data
+ vector_tile::Tile tile;
+ unsigned tile_size = 256;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::vector_tile_impl::backend_pbf backend(tile,path_multiplier);
+ backend.start_tile_layer("layer");
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(std::make_shared<mapnik::context_type>(),1));
+ backend.start_tile_feature(*feature);
+ mapnik::geometry::multi_line_string<std::int64_t> geom;
+ {
+ mapnik::geometry::linear_ring<std::int64_t> ring;
+ ring.add_coord(0,0);
+ ring.add_coord(2,2);
+ geom.emplace_back(std::move(ring));
+ }
+ {
+ mapnik::geometry::linear_ring<std::int64_t> ring;
+ ring.add_coord(1,1);
+ ring.add_coord(2,2);
+ geom.emplace_back(std::move(ring));
+ }
+ /*
+ g->move_to(0,0); // takes 3 geoms: command length,x,y
+ g->line_to(2,2); // new command, so again takes 3 geoms: command length,x,y | total 6
+ g->move_to(1,1); // takes 3 geoms: command length,x,y
+ g->line_to(2,2); // new command, so again takes 3 geoms: command length,x,y | total 6
+ */
+ backend.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
+ for (auto const& line : geom)
+ {
+ backend.add_path(line);
+ }
+ backend.stop_tile_feature();
+ backend.stop_tile_layer();
+ // done encoding single feature/geometry
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
+ CHECK(1 == tile.layers_size());
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ CHECK(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
+ CHECK(0 == f.geometry(1)); // x:0
+ CHECK(0 == f.geometry(2)); // y:0
+ CHECK(10 == f.geometry(3)); // 1 line_to
+ CHECK(4 == f.geometry(4)); // x:2
+ CHECK(4 == f.geometry(5)); // y:2
+ CHECK(9 == f.geometry(6)); // 1 move_to
+ CHECK(1 == f.geometry(7)); // x:1
+ CHECK(1 == f.geometry(8)); // y:1
+ CHECK(10 == f.geometry(9)); // 1 line_to
+ CHECK(2 == f.geometry(10)); // x:2
+ CHECK(2 == f.geometry(11)); // y:2
+
+ mapnik::featureset_ptr fs;
+ mapnik::feature_ptr f_ptr;
+
+
+ std::string buffer;
+ tile.SerializeToString(&buffer);
+ mapbox::util::pbf pbf_tile(buffer.c_str(), buffer.size());
+ pbf_tile.next();
+ mapbox::util::pbf layer2 = pbf_tile.get_message();
+
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+ fs = ds.features(mapnik::query(bbox));
+ f_ptr = fs->next();
+ CHECK(f_ptr != mapnik::feature_ptr());
+ // no attributes
+ CHECK(f_ptr->context()->size() == 0);
+
+ CHECK(f_ptr->get_geometry().is<mapnik::geometry::multi_line_string<double> >());
+}
+
+
+// NOTE: encoding multiple lines as one path is technically incorrect
+// because in Mapnik the protocol is to split geometry parts into separate paths.
+// However this case should still be supported because keeping a single flat array is an
+// important optimization in the case that lines do not need to be labeled in custom ways
+// or represented as GeoJSON
+
+TEST_CASE( "pbf decoding empty buffer", "should throw exception" ) {
+ std::string buffer;
+ mapbox::util::pbf pbf_tile(buffer.c_str(), buffer.size());
+ pbf_tile.next();
+ mapbox::util::pbf layer2;
+ REQUIRE_THROWS(layer2 = pbf_tile.get_message());
+}
+
+TEST_CASE( "pbf decoding garbage buffer", "should throw exception" ) {
+ std::string buffer("daufyglwi3h7fseuhfas8w3h,dksufasdf");
+ mapbox::util::pbf pbf_tile(buffer.c_str(), buffer.size());
+ pbf_tile.next();
+ mapbox::util::pbf layer2;
+ REQUIRE_THROWS(layer2 = pbf_tile.get_message());
+}
+
+
+TEST_CASE( "pbf decoding some truncated buffers", "should throw exception" ) {
+
+ 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,16);
+ 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");
+ mapnik::layer lyr("layer",map.srs());
+ lyr.set_datasource(testing::build_ds(0,0));
+ map.add_layer(lyr);
+ mapnik::request m_req(tile_size,tile_size,bbox);
+ renderer_type ren(backend,map,m_req);
+ ren.apply();
+ std::string key("");
+ CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
+ CHECK("" == key);
+ CHECK(1 == tile.layers_size());
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ CHECK(std::string("layer") == layer.name());
+ CHECK(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());
+ CHECK(9 == f.geometry(0));
+ CHECK(4096 == f.geometry(1));
+ CHECK(4096 == f.geometry(2));
+
+
+ // We will test truncating the generated protobuf at every increment.
+ // Most cases should fail, except for the lucky bites where we chop
+ // it off at a point that would be valid anyway.
+ std::string buffer;
+ tile.SerializeToString(&buffer);
+ for (int i=1; i< buffer.size(); i++)
+ {
+ CHECK_THROWS({
+ mapbox::util::pbf pbf_tile(buffer.c_str(), i);
+ pbf_tile.next();
+ mapbox::util::pbf layer2 = pbf_tile.get_message();
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+ mapnik::featureset_ptr fs;
+ mapnik::feature_ptr f_ptr;
+ fs = ds.features(mapnik::query(bbox));
+ f_ptr = fs->next();
+ while (f_ptr != mapnik::feature_ptr()) {
+ f_ptr = fs->next();
+ }
+ });
+ }
+}
+
+TEST_CASE( "pbf vector tile from simplified geojson", "should create vector tile with data" ) {
+ 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,1000);
+ 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");
+ mapnik::layer lyr("layer","+init=epsg:4326");
+ std::shared_ptr<mapnik::memory_datasource> ds = testing::build_geojson_ds("./test/data/poly.geojson");
+ ds->set_envelope(mapnik::box2d<double>(160.147311,11.047284,160.662858,11.423830));
+ 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);
+ ren.apply();
+ CHECK( ren.painted() == true );
+ CHECK(1 == tile.layers_size());
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ CHECK(std::string("layer") == layer.name());
+ CHECK(1 == layer.features_size());
+ vector_tile::Tile_Feature const& f = layer.features(0);
+ unsigned z = 0;
+ unsigned x = 0;
+ unsigned y = 0;
+ double resolution = mapnik::EARTH_CIRCUMFERENCE/(1 << z);
+ double tile_x = -0.5 * mapnik::EARTH_CIRCUMFERENCE + x * resolution;
+ double tile_y = 0.5 * mapnik::EARTH_CIRCUMFERENCE - y * resolution;
+ double scale = (static_cast<double>(layer.extent()) / tile_size) * tile_size/resolution;
+
+ std::string buffer;
+ tile.SerializeToString(&buffer);
+ mapbox::util::pbf pbf_tile(buffer.c_str(), buffer.size());
+ pbf_tile.next();
+ mapbox::util::pbf pbf_layer = pbf_tile.get_message();
+ // Need to loop because they could be encoded in any order
+ bool found = false;
+ while (!found && pbf_layer.next()) {
+ // But there will be only one in our tile, so we'll break out of loop once we find it
+ if (pbf_layer.tag() == 2) {
+ mapbox::util::pbf pbf_feature = pbf_layer.get_message();
+ while (!found && pbf_feature.next()) {
+ if (pbf_feature.tag() == 4) {
+ found = true;
+ std::pair< mapbox::util::pbf::const_uint32_iterator, mapbox::util::pbf::const_uint32_iterator > geom_itr = pbf_feature.packed_uint32();
+ mapnik::vector_tile_impl::GeometryPBF geoms(geom_itr, tile_x,tile_y,scale,-1*scale);
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, f.type());
+
+ unsigned int n_err = 0;
+ mapnik::projection wgs84("+init=epsg:4326",true);
+ mapnik::projection merc("+init=epsg:3857",true);
+ mapnik::proj_transform prj_trans(merc,wgs84);
+ mapnik::geometry::geometry<double> projected_geom = mapnik::geometry::reproject_copy(geom,prj_trans,n_err);
+ CHECK( n_err == 0 );
+ std::string geojson_string;
+ CHECK( mapnik::util::to_geojson(geojson_string,projected_geom) );
+ CHECK( geojson_string == "{\"type\":\"Polygon\",\"coordinates\":[[[160.42359375,11.422482415387],[160.40671875,11.3976701817587],[160.396875,11.3935345987523],[160.39828125,11.4018057045895],[160.39265625,11.4004272036667],[160.38984375,11.3811274888866],[160.3940625,11.3838846711709],[160.3771875,11.3521754635814],[160.33921875,11.3590690696413],[160.35046875,11.3645838345287],[160.3575,11.3645838345287],[160.3575,11.3756130442004],[160.28859375,11.3480392200085],[160. [...]
+ break;
+ }
+ }
+ }
+ }
+ REQUIRE( found );
+}
+
+
+TEST_CASE( "pbf raster tile output", "should be able to overzoom raster" ) {
+ mapnik::datasource_cache::instance().register_datasources(MAPNIK_PLUGINDIR);
+ typedef vector_tile::Tile tile_type;
+ tile_type tile;
+ {
+ typedef mapnik::vector_tile_impl::backend_pbf backend_type;
+ typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
+ double minx,miny,maxx,maxy;
+ mapnik::vector_tile_impl::spherical_mercator merc(256);
+ merc.xyz(0,0,0,minx,miny,maxx,maxy);
+ mapnik::box2d<double> bbox(minx,miny,maxx,maxy);
+ backend_type backend(tile,16);
+ mapnik::Map map(256,256,"+init=epsg:3857");
+ map.set_buffer_size(1024);
+ mapnik::layer lyr("layer",map.srs());
+ mapnik::parameters params;
+ params["type"] = "gdal";
+ std::ostringstream s;
+ s << std::fixed << std::setprecision(16)
+ << bbox.minx() << ',' << bbox.miny() << ','
+ << bbox.maxx() << ',' << bbox.maxy();
+ params["extent"] = s.str();
+ params["file"] = "test/data/256x256.png";
+ std::shared_ptr<mapnik::datasource> ds =
+ mapnik::datasource_cache::instance().create(params);
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+ mapnik::request m_req(256,256,bbox);
+ m_req.set_buffer_size(map.buffer_size());
+ renderer_type ren(backend,map,m_req,1.0,0,0,1,"jpeg",mapnik::SCALING_BILINEAR);
+ ren.apply();
+ }
+ // Done creating test data, now test created tile
+ CHECK(1 == tile.layers_size());
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ CHECK(std::string("layer") == layer.name());
+ CHECK(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());
+ CHECK(f.has_raster());
+ std::string const& ras_buffer = f.raster();
+ CHECK(!ras_buffer.empty());
+
+ // confirm tile looks correct as encoded
+ std::string buffer;
+ CHECK(tile.SerializeToString(&buffer));
+
+ mapbox::util::pbf pbf_tile(buffer.c_str(), buffer.size());
+ pbf_tile.next();
+ mapbox::util::pbf layer2 = pbf_tile.get_message();
+
+ // now read back and render image at larger size
+ // and zoomed in
+ double minx,miny,maxx,maxy;
+ mapnik::vector_tile_impl::spherical_mercator merc(256);
+ // 2/0/1.png
+ merc.xyz(0,1,2,minx,miny,maxx,maxy);
+ mapnik::box2d<double> bbox(minx,miny,maxx,maxy);
+ mapnik::Map map2(256,256,"+init=epsg:3857");
+ map2.set_buffer_size(1024);
+ mapnik::layer lyr2("layer",map2.srs());
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer2,0,0,0,256);
+ lyr2.set_datasource(ds2);
+ lyr2.add_style("style");
+ map2.add_layer(lyr2);
+ mapnik::load_map(map2,"test/data/raster_style.xml");
+ 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/expected-3.png")) {
+ mapnik::save_to_file(im,"test/fixtures/expected-3.png","png32");
+ }
+ unsigned diff = testing::compare_images(im,"test/fixtures/expected-3.png");
+ CHECK(0 == diff);
+ if (diff > 0) {
+ mapnik::save_to_file(im,"test/fixtures/actual-3.png","png32");
+ }
+}
+
+TEST_CASE("Check that we throw on various valid-but-we-don't-handle PBF encoded files","Should be throwing exceptions")
+{
+ std::vector<std::string> filenames = {"test/data/tile_with_extra_feature_field.pbf",
+ "test/data/tile_with_extra_layer_fields.pbf",
+ "test/data/tile_with_invalid_layer_value_type.pbf",
+ "test/data/tile_with_unexpected_geomtype.pbf"};
+
+ for (auto const& f : filenames) {
+
+ CHECK_THROWS({
+ std::ifstream t(f);
+ std::string buffer((std::istreambuf_iterator<char>(t)),
+ std::istreambuf_iterator<char>());
+
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ unsigned tile_size = 256;
+ mapbox::util::pbf pbf_tile(buffer.c_str(), buffer.size());
+ pbf_tile.next();
+ mapbox::util::pbf layer2 = pbf_tile.get_message();
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+ mapnik::featureset_ptr fs;
+ mapnik::feature_ptr f_ptr;
+ fs = ds.features(mapnik::query(bbox));
+ f_ptr = fs->next();
+ while (f_ptr != mapnik::feature_ptr()) {
+ f_ptr = fs->next();
+ }
+ });
+ }
+
+}
--
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