[mapnik-vector-tile] 01/07: Imported Upstream version 1.0.1+dfsg
Sebastiaan Couwenberg
sebastic at moszumanska.debian.org
Fri Feb 26 23:10:38 UTC 2016
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 79a8908dbfad5fccac094faa7a31e698aa8a95bc
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date: Fri Feb 26 21:24:56 2016 +0100
Imported Upstream version 1.0.1+dfsg
---
.travis.yml | 2 +-
CHANGELOG.md | 25 +
Makefile | 6 +-
...r.pbf => enf.t5yd5cdi_14_13089_8506.vector.mvt} | Bin
...ctor.pbf => multi_line_13_1310_3166.vector.mvt} | Bin
bench/{notes.md => readme.md} | 55 +-
bench/run-bench.sh | 33 +
bench/vtile-decode.cpp | 38 +-
bench/vtile-encode.cpp | 187 +++
bench/vtile-transform.cpp | 61 +-
bin/vtile-edit.cpp | 134 ++
examples/c++/Makefile | 4 +-
examples/c++/README.md | 2 +-
examples/c++/tileinfo.cpp | 6 +
...6331.vector.pbf.z => 14_2620_6331.vector.mvt.z} | Bin
...716_8015.vector.pbf => 14_8716_8015.vector.mvt} | Bin
gyp/build.gyp | 36 +-
package.json | 4 +-
scripts/build.sh | 4 +-
src/vector_tile_backend_pbf.cpp | 2 -
src/vector_tile_backend_pbf.hpp | 71 -
src/vector_tile_backend_pbf.ipp | 160 --
src/vector_tile_composite.hpp | 105 ++
src/vector_tile_compression.hpp | 58 +-
src/vector_tile_compression.ipp | 33 +-
src/vector_tile_config.hpp | 66 +-
src/vector_tile_datasource.cpp | 2 -
src/vector_tile_datasource.hpp | 52 -
src/vector_tile_datasource.ipp | 328 ----
src/vector_tile_datasource_pbf.hpp | 107 +-
src/vector_tile_datasource_pbf.ipp | 557 +++----
src/vector_tile_featureset_pbf.cpp | 6 +
src/vector_tile_featureset_pbf.hpp | 73 +
src/vector_tile_featureset_pbf.ipp | 325 ++++
src/vector_tile_geometry_clipper.hpp | 479 ++++++
src/vector_tile_geometry_decoder.cpp | 20 +
src/vector_tile_geometry_decoder.hpp | 575 +------
src/vector_tile_geometry_decoder.ipp | 902 +++++++++++
src/vector_tile_geometry_encoder.hpp | 215 ---
src/vector_tile_geometry_encoder_pbf.cpp | 2 +
src/vector_tile_geometry_encoder_pbf.hpp | 67 +
src/vector_tile_geometry_encoder_pbf.ipp | 307 ++++
src/vector_tile_geometry_feature.hpp | 86 ++
src/vector_tile_geometry_intersects.cpp | 2 +
src/vector_tile_geometry_intersects.hpp | 55 +
src/vector_tile_geometry_intersects.ipp | 84 +
src/vector_tile_geometry_simplifier.hpp | 99 ++
src/vector_tile_is_valid.hpp | 304 ++++
src/vector_tile_layer.cpp | 2 +
src/vector_tile_layer.hpp | 374 +++++
src/vector_tile_layer.ipp | 131 ++
src/vector_tile_load_tile.hpp | 135 ++
src/vector_tile_merc_tile.hpp | 82 +
src/vector_tile_processor.cpp | 2 -
src/vector_tile_processor.hpp | 220 ++-
src/vector_tile_processor.ipp | 1618 ++++---------------
src/vector_tile_projection.hpp | 50 +-
src/vector_tile_projection.ipp | 42 +-
src/vector_tile_raster_clipper.cpp | 2 +
src/vector_tile_raster_clipper.hpp | 102 ++
src/vector_tile_raster_clipper.ipp | 523 +++++++
src/vector_tile_strategy.hpp | 121 +-
src/vector_tile_tile.cpp | 2 +
src/vector_tile_tile.hpp | 211 +++
src/vector_tile_tile.ipp | 108 ++
src/vector_tile_util.cpp | 2 -
src/vector_tile_util.hpp | 23 -
src/vector_tile_util.ipp | 256 ----
test/clipper_test.cpp | 72 +-
test/data/0.0.0.vector-b.mvt | Bin 0 -> 2835 bytes
test/data/0.0.0.vector-b.pbf | Bin 2774 -> 0 bytes
test/data/{0.0.0.vector.pbf => 0.0.0.vector.mvt} | Bin
...field.pbf => tile_with_extra_feature_field.mvt} | 0
...h_extra_field.pbf => tile_with_extra_field.mvt} | 0
...fields.pbf => tile_with_extra_layer_fields.mvt} | 0
....pbf => tile_with_invalid_layer_value_type.mvt} | 0
...mtype.pbf => tile_with_unexpected_geomtype.mvt} | 0
test/encoding_util.cpp | 131 --
test/encoding_util.hpp | 25 -
test/fixtures/expected-4.png | Bin 8955 -> 9102 bytes
test/geometry_encoding.cpp | 691 ---------
test/geometry_visual_test.cpp | 184 ++-
test/raster_tile.cpp | 312 ++--
test/system/encode_and_datasource_decode.cpp | 119 ++
test/system/encode_and_decode.cpp | 138 ++
test/system/processor_and_datasource.cpp | 321 ++++
test/system/remove_repeated_point.cpp | 23 +
test/system/round_trip.cpp | 247 +++
test/system/round_trip_fill_type.cpp | 65 +
test/system/round_trip_simplification.cpp | 144 ++
test/test_utils.cpp | 21 +-
test/test_utils.hpp | 2 +-
test/unit/composite/vector.cpp | 15 +
test/unit/compression/compression.cpp | 460 ++++++
test/unit/datasource-pbf/from_layer.cpp | 298 ++++
test/unit/decoding/linestring.cpp | 554 +++++++
test/unit/decoding/point.cpp | 365 +++++
test/unit/decoding/polygon.cpp | 1621 ++++++++++++++++++++
test/unit/decoding/polygon_scaling.cpp | 471 ++++++
test/unit/encoding/linestring_pbf.cpp | 439 ++++++
test/unit/encoding/point_pbf.cpp | 202 +++
test/unit/encoding/polygon_pbf.cpp | 588 +++++++
test/unit/is_valid/feature_is_valid.cpp | 134 ++
test/unit/is_valid/value_is_valid.cpp | 204 +++
test/unit/tile_impl/tile.cpp | 496 ++++++
test/utils/decoding_util.cpp | 16 +
test/utils/decoding_util.hpp | 10 +
test/utils/encoding_util.cpp | 82 +
test/utils/encoding_util.hpp | 16 +
test/utils/geom_to_wkt.cpp | 17 +
test/utils/geom_to_wkt.hpp | 10 +
test/utils/geometry_equal.hpp | 235 +++
test/utils/round_trip.cpp | 89 ++
test/utils/round_trip.hpp | 15 +
test/vector_tile.cpp | 1326 ++--------------
test/vector_tile_pbf.cpp | 371 +++--
test/vector_tile_rasterize.cpp | 100 +-
117 files changed, 14189 insertions(+), 6185 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index ca0a2ce..77823e8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -51,4 +51,4 @@ before_script:
script:
# make sure tileinfo command works
- - ./build/${BUILDTYPE:-Release}/tileinfo examples/data/14_2620_6331.vector.pbf.z
\ No newline at end of file
+ - ./build/${BUILDTYPE:-Release}/tileinfo examples/data/14_2620_6331.vector.mvt.z
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8697358..289e662 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,30 @@
# Changelog
+## 1.0.1
+
+- Updated to protozero v1.3.0
+
+## 1.0.0
+
+Extensive redesign in mapnik-vector-tile to properly support 2.0 of the [Mapbox Vector Tile specification](https://github.com/mapbox/vector-tile-spec/). A large number of changes have occured but a summary of these changes can be described as:
+
+ - Removed `backend_pbf`
+ - Changed `processor` interface
+ - Removed requirement on libprotobuf `Tile` class when using the library.
+ - Created new `tile` and `merc_tile` class
+ - Added different processing logic for v1 and v2 decoding
+ - Solved several small bugs around decoding and encoding
+ - Added many more exceptions around the processing of invalid tiles.
+ - Added `load_tile` and `composite` headers
+ - Organized tests directory and added many more tests
+ - Removed the concept of `is_solid`
+ - Removed the concept of `path_multiplier`
+ - Fixed bugs in `empty` concept.
+ - `tile_size` is now directly related to the layer `extent`
+ - Encoding no longer allows repeated points in lines and polygons
+ - Corrected issues with winding order being reversed in some situations when decoding polygons
+ - Changed the default configuration values for `processor`
+
## 0.14.3
- Fixed compile against latest Mapnik master (variant upgrade)
diff --git a/Makefile b/Makefile
index 9e47cc0..6c436d1 100755
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
MAPNIK_PLUGINDIR := $(shell mapnik-config --input-plugins)
BUILDTYPE ?= Release
-CLIPPER_REVISION=7484da1
-PROTOZERO_REVISION=v1.0.0
+CLIPPER_REVISION=7484da1e7caf3250aa091925518f4fac91c05784
+PROTOZERO_REVISION=v1.3.0
GYP_REVISION=3464008
all: libvtile
@@ -16,7 +16,7 @@ all: libvtile
./deps/clipper:
git clone https://github.com/mapnik/clipper.git -b r496-mapnik ./deps/clipper && cd ./deps/clipper && git checkout $(CLIPPER_REVISION) && ./cpp/fix_members.sh
-build/Makefile: ./deps/gyp ./deps/clipper ./deps/protozero gyp/build.gyp test/*cpp
+build/Makefile: ./deps/gyp ./deps/clipper ./deps/protozero gyp/build.gyp test/*
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/bench/enf.t5yd5cdi_14_13089_8506.vector.pbf b/bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
similarity index 100%
rename from bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
rename to bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
diff --git a/bench/multi_line_13_1310_3166.vector.pbf b/bench/multi_line_13_1310_3166.vector.mvt
similarity index 100%
rename from bench/multi_line_13_1310_3166.vector.pbf
rename to bench/multi_line_13_1310_3166.vector.mvt
diff --git a/bench/notes.md b/bench/readme.md
similarity index 72%
rename from bench/notes.md
rename to bench/readme.md
index 7948c7f..1ca057f 100644
--- a/bench/notes.md
+++ b/bench/readme.md
@@ -1,58 +1,71 @@
+### decode benchmark
+
With reserve
```
-$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.pbf 13 1310 3166
+$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.mvt 13 1310 3166
z:13 x:1310 y:3166 iterations:100
message: zlib compressed
-4026.30ms (cpu 4020.96ms) | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.pbf
+4026.30ms (cpu 4020.96ms) | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.mvt
```
without reserve code
```
-$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.pbf 13 1310 3166
+$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.mvt 13 1310 3166
z:13 x:1310 y:3166 iterations:100
message: zlib compressed
-4296.88ms (cpu 4289.05ms) | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.pbf
+4296.88ms (cpu 4289.05ms) | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.mvt
```
-
-
---------
-
baseline (using mapnik::geometry::envelope + filter.pass)
-$ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.pbf 14 13089 8506 200
+```
+$ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.mvt 14 13089 8506 200
z:14 x:13089 y:8506 iterations:200
-2822.66ms (cpu 2821.10ms) | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
-2070.90ms (cpu 2070.44ms) | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
+2822.66ms (cpu 2821.10ms) | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
+2070.90ms (cpu 2070.44ms) | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
processed 6800 features
+```
-$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.pbf 13 1310 3166 100
+```
+$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.mvt 13 1310 3166 100
z:13 x:1310 y:3166 iterations:100
message: zlib compressed
-4289.26ms (cpu 4275.29ms) | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.pbf
-
-
+4289.26ms (cpu 4275.29ms) | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.mvt
+```
commenting filter.pass/geometry::envelope in datasource_pbf
+```
$ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.pbf 14 13089 8506 200
z:14 x:13089 y:8506 iterations:200
-2305.45ms (cpu 2301.07ms) | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
-2142.56ms (cpu 2140.40ms) | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
+2305.45ms (cpu 2301.07ms) | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
+2142.56ms (cpu 2140.40ms) | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
processed 6800 features
+```
with bbox filter:
-$ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.pbf 14 13089 8506 200
+```
+$ .//build/Release/vtile-decode bench/enf.t5yd5cdi_14_13089_8506.vector.mvt 14 13089 8506 200
z:14 x:13089 y:8506 iterations:200
-2497.01ms (cpu 2493.40ms) | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
-1753.55ms (cpu 1751.10ms) | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.pbf
+2497.01ms (cpu 2493.40ms) | decode as datasource_pbf: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
+1753.55ms (cpu 1751.10ms) | decode as datasource: bench/enf.t5yd5cdi_14_13089_8506.vector.mvt
processed 6600 features
+```
-$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.pbf 13 1310 3166 100
+```
+$ ./build/Release/vtile-decode bench/multi_line_13_1310_3166.vector.mvt 13 1310 3166 100
z:13 x:1310 y:3166 iterations:100
message: zlib compressed
-4090.67ms (cpu 4083.65ms) | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.pbf
+4090.67ms (cpu 4083.65ms) | decode as datasource_pbf: bench/multi_line_13_1310_3166.vector.mvt
+```
+
+### encode benchmark:
+
+```
+$ ./build/Release/vtile-encode ./test/data/linestrings_and_point.geojson 0 0 0
+```
diff --git a/bench/run-bench.sh b/bench/run-bench.sh
new file mode 100755
index 0000000..1887a4a
--- /dev/null
+++ b/bench/run-bench.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+# encode geojson
+if [ "$1" == "encode" ] ; then
+ FILE_PATH=../test/geometry-test-data/input/*
+ COMMAND=../build/Release/vtile-encode
+ for f in $FILE_PATH
+ do
+ echo "${f##*/}"
+ for n in 10000 20000 30000 40000 50000 60000 70000 80000 90000 100000
+ do
+ $COMMAND $f 0 0 0 -i $n
+ done
+ echo " "
+ echo " "
+ done
+#decode pbf
+elif [ "$1" == "decode" ] ; then
+ FILE_PATH=../test/data/**.pbf
+ COMMAND=../build/Release/vtile-decode
+ for f in $FILE_PATH
+ do
+ echo "${f##*/}"
+ for n in 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000
+ do
+ $COMMAND $f 0 0 0 $n
+ done
+ echo " "
+ echo " "
+ done
+else
+ echo "Please specify 'encode' or 'decode'"
+fi
\ No newline at end of file
diff --git a/bench/vtile-decode.cpp b/bench/vtile-decode.cpp
index c56314a..ec11da5 100644
--- a/bench/vtile-decode.cpp
+++ b/bench/vtile-decode.cpp
@@ -1,7 +1,6 @@
#include <mapnik/timer.hpp>
#include <mapnik/util/file_io.hpp>
#include "vector_tile_datasource_pbf.hpp"
-#include "vector_tile_datasource.hpp"
#include "vector_tile_compression.hpp"
#pragma GCC diagnostic push
@@ -21,7 +20,7 @@ int main(int argc, char** argv)
if (argc < 4)
{
- std::clog << "usage: vtile-decode /path/to/tile.vector.pbf z x y [iterations]\n";
+ std::clog << "usage: vtile-decode /path/to/tile.vector.mvt z x y [iterations]\n";
return -1;
}
std::string vtile(argv[1]);
@@ -72,45 +71,24 @@ int main(int argc, char** argv)
while (tile.next(3)) {
++layer_count;
protozero::pbf_reader layer = tile.get_message();
- auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(layer,x,y,z,256);
+ auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(layer,x,y,z);
mapnik::query q(ds->get_tile_extent());
auto fs = ds->features(q);
+ if (!fs) continue;
while (fs->next()) {
++feature_count;
}
}
}
}
-
-
- std::size_t feature_count2 = 0;
- std::size_t layer_count2 = 0;
+
+ if (feature_count == 0)
{
- mapnik::progress_timer __stats__(std::clog, std::string("decode as datasource: ") + vtile);
- vector_tile::Tile tiledata;
- tiledata.ParseFromString(message);
- for (std::size_t i=0;i<iterations;++i)
- {
- for (int j=0;j<tiledata.layers_size(); ++j)
- {
- ++layer_count2;
- auto const& layer = tiledata.layers(j);
- auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource>(layer,x,y,z,256);
- mapnik::query q(ds->get_tile_extent());
- auto fs = ds->features(q);
- while (fs->next()) {
- ++feature_count2;
- }
- }
- }
- }
- if (feature_count!= feature_count2) {
- std::clog << "error: tile datasource impl did not return same # of features " << feature_count << " vs " << feature_count2 << "\n";
- return -1;
- } else if (feature_count == 0) {
std::clog << "error: no features processed\n";
return -1;
- } else {
+ }
+ else
+ {
std::clog << "processed " << feature_count << " features\n";
}
}
diff --git a/bench/vtile-encode.cpp b/bench/vtile-encode.cpp
new file mode 100644
index 0000000..e3b9657
--- /dev/null
+++ b/bench/vtile-encode.cpp
@@ -0,0 +1,187 @@
+#include <mapnik/timer.hpp>
+#include <mapnik/util/file_io.hpp>
+#include "vector_tile_processor.hpp"
+#include "vector_tile_projection.hpp"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+#include <iostream>
+#include <fstream>
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/datasource_cache.hpp>
+#include <mapnik/global.hpp>
+#include <mapnik/well_known_srs.hpp>
+#include <mapnik/json/geometry_parser.hpp>
+#include <mapnik/memory_datasource.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/map.hpp>
+#include <mapnik/layer.hpp>
+#include <mapnik/request.hpp>
+
+#include <protozero/pbf_reader.hpp>
+
+int main(int argc, char** argv)
+{
+ try
+ {
+ if (argc < 4)
+ {
+ std::clog << "usage: vtile-encode /path/to/geometry.geojson z x y [-i iterations] [-l layer_count] [-o output_file] [-p epsg_code]\n";
+ return -1;
+ }
+ std::string geojson_file(argv[1]);
+
+ int z = std::stoi(argv[2]);
+ int x = std::stoi(argv[3]);
+ int y = std::stoi(argv[4]);
+
+ std::size_t iterations = 1000;
+ std::size_t layer_count = 1;
+ std::size_t layer_extent = 4096;
+ std::string output_path = "";
+ std::string layer_projection = "4326";
+ if (argc > 5)
+ {
+ for (int i = 5; i < argc; i++)
+ {
+ std::string flag = argv[i];
+ if (flag == "-i")
+ {
+ iterations = std::stoi(argv[i + 1]);
+ }
+ if (flag == "-l")
+ {
+ layer_count = std::stoi(argv[i + 1]);
+ }
+ if (flag == "-o")
+ {
+ output_path = argv[i + 1];
+ }
+ if (flag == "-p")
+ {
+ layer_projection = argv[i + 1];
+ }
+ if (flag == "-e")
+ {
+ layer_extent = std::stoi(argv[i + 1]);
+ }
+ }
+ }
+
+ std::clog << "z:" << z << " x:" << x << " y:" << y << " iterations:" << iterations << std::endl;
+
+ mapnik::datasource_cache::instance().register_datasources(MAPNIK_PLUGINDIR);
+ mapnik::parameters params_1;
+ params_1["type"] = "geojson";
+ params_1["file"] = geojson_file;
+ params_1["cache_features"] = "false";
+ auto ds_1 = mapnik::datasource_cache::instance().create(params_1);
+
+ // Query
+ mapnik::box2d<double> query_ext(std::numeric_limits<double>::min(),
+ std::numeric_limits<double>::min(),
+ std::numeric_limits<double>::max(),
+ std::numeric_limits<double>::max());
+ mapnik::query q(query_ext);
+ auto json_fs = ds_1->features(q);
+
+ // Create memory datasource from geojson
+ mapnik::parameters params;
+ params["type"] = "memory";
+ auto ds = std::make_shared<mapnik::memory_datasource>(params);
+
+ if (!json_fs)
+ {
+ // No features in geojson so lets try to process it differently as
+ // it might be a partial geojson and we can use from_geojson
+ mapnik::util::file input(geojson_file);
+ if (!input.open())
+ {
+ std::clog << "failed to open " << geojson_file << std::endl;
+ return -1;
+ }
+ mapnik::geometry::geometry<double> geom;
+ std::string json_string(input.data().get(), input.size());
+ if (!mapnik::json::from_geojson(json_string, geom))
+ {
+ std::clog << "failed to parse geojson" << std::endl;
+ return -1;
+ }
+ mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+ feature->set_geometry(std::move(geom));
+ ds->push(feature);
+ }
+ else
+ {
+ mapnik::feature_ptr feature = json_fs->next();
+
+ while (feature)
+ {
+ ds->push(feature);
+ feature = json_fs->next();
+ }
+ }
+
+ // Create tile
+ unsigned tile_size = layer_extent;
+ int buffer_size = 0;
+
+ mapnik::box2d<double> bbox = mapnik::vector_tile_impl::merc_extent(tile_size, x, y, z);
+
+ // Create a fresh map to render into a tile
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ while (layer_count > 0)
+ {
+ std::string layer_name = "layer" + std::to_string(layer_count);
+ std::string layer_proj = "+init=epsg:" + layer_projection;
+ mapnik::layer lyr(layer_name, layer_proj);
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+ --layer_count;
+ }
+ map.zoom_to_box(bbox);
+
+ // Output buffer
+ std::string output_buffer;
+ std::size_t expected_size;
+ {
+ mapnik::vector_tile_impl::tile a_tile(bbox, tile_size, buffer_size);
+ mapnik::vector_tile_impl::processor ren(map);
+ ren.set_simplify_distance(5.0);
+ ren.update_tile(a_tile);
+ a_tile.serialize_to_string(output_buffer);
+ expected_size = a_tile.size();
+ }
+
+ {
+ mapnik::progress_timer __stats__(std::clog, std::string("encode tile: ") + geojson_file);
+ for (std::size_t i = 0; i < iterations; ++i)
+ {
+ mapnik::vector_tile_impl::tile a_tile(bbox, tile_size, buffer_size);
+ mapnik::vector_tile_impl::processor ren(map);
+ ren.set_simplify_distance(5.0);
+ ren.update_tile(a_tile);
+ assert(a_tile.size() == expected_size);
+ }
+ }
+
+ if (output_path != "")
+ {
+ std::ofstream out(output_path);
+ out << output_buffer;
+ }
+ }
+ catch (std::exception const& ex)
+ {
+ std::clog << "error: " << ex.what() << std::endl;
+ return -1;
+ }
+ return 0;
+}
diff --git a/bench/vtile-transform.cpp b/bench/vtile-transform.cpp
index b716cc3..5bc78ba 100644
--- a/bench/vtile-transform.cpp
+++ b/bench/vtile-transform.cpp
@@ -21,15 +21,21 @@
*/
-int main() {
+int main()
+{
mapnik::projection merc("+init=epsg:3857",true);
+ mapnik::projection merc2("+init=epsg:4326",true);
mapnik::proj_transform prj_trans(merc,merc); // no-op
+ mapnik::proj_transform prj_trans2(merc2,merc); // 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);
+ unsigned path_multiplier = 16;
+ mapnik::view_transform tr(tile_size * path_multiplier,
+ tile_size * path_multiplier,
+ z15_extent,0,0);
std::string geojson_file("./test/data/poly.geojson");
mapnik::util::file input(geojson_file);
if (!input.open())
@@ -47,8 +53,9 @@ int main() {
unsigned count = 0;
unsigned count2 = 0;
unsigned count3 = 0;
+ unsigned count4 = 0;
{
- mapnik::vector_tile_impl::vector_tile_strategy vs(tr, 16);
+ mapnik::vector_tile_impl::vector_tile_strategy vs(tr);
mapnik::progress_timer __stats__(std::clog, "boost::geometry::transform");
for (unsigned i=0;i<10000;++i)
{
@@ -58,17 +65,21 @@ int main() {
}
}
{
- mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans,tr, 16);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans,tr);
mapnik::progress_timer __stats__(std::clog, "transform_visitor with reserve with proj no-op");
mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
std::numeric_limits<double>::min(),
std::numeric_limits<double>::max(),
std::numeric_limits<double>::max());
- mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy_proj> transit(vs, clip_extent);
+ mapnik::vector_tile_impl::geom_out_visitor<std::int64_t> out_geom;
+ mapnik::vector_tile_impl::transform_visitor<
+ mapnik::vector_tile_impl::vector_tile_strategy_proj,
+ mapnik::vector_tile_impl::geom_out_visitor<std::int64_t>
+ > transit(vs, clip_extent, out_geom);
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);
+ mapnik::util::apply_visitor(transit,geom);
+ auto const& poly = mapnik::util::get<mapnik::geometry::multi_polygon<std::int64_t>>(out_geom.geom);
count2 += poly.size();
}
if (count != count2)
@@ -78,17 +89,45 @@ int main() {
}
}
{
- mapnik::vector_tile_impl::vector_tile_strategy vs(tr, 16);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans2,tr);
+ mapnik::progress_timer __stats__(std::clog, "transform_visitor with reserve with proj op");
+ mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
+ std::numeric_limits<double>::min(),
+ std::numeric_limits<double>::max(),
+ std::numeric_limits<double>::max());
+ mapnik::vector_tile_impl::geom_out_visitor<std::int64_t> out_geom;
+ mapnik::vector_tile_impl::transform_visitor<
+ mapnik::vector_tile_impl::vector_tile_strategy_proj,
+ mapnik::vector_tile_impl::geom_out_visitor<std::int64_t>
+ > transit(vs, clip_extent, out_geom);
+ for (unsigned i=0;i<10000;++i)
+ {
+ mapnik::util::apply_visitor(transit,geom);
+ auto const& poly = mapnik::util::get<mapnik::geometry::multi_polygon<std::int64_t>>(out_geom.geom);
+ count4 += poly.size();
+ }
+ if (count != count4)
+ {
+ std::clog << "tests did not run as expected!\n";
+ return -1;
+ }
+ }
+ {
+ mapnik::vector_tile_impl::vector_tile_strategy vs(tr);
mapnik::progress_timer __stats__(std::clog, "transform_visitor with reserve with no proj function call overhead");
mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
std::numeric_limits<double>::min(),
std::numeric_limits<double>::max(),
std::numeric_limits<double>::max());
- mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy> transit(vs, clip_extent);
+ mapnik::vector_tile_impl::geom_out_visitor<std::int64_t> out_geom;
+ mapnik::vector_tile_impl::transform_visitor<
+ mapnik::vector_tile_impl::vector_tile_strategy,
+ mapnik::vector_tile_impl::geom_out_visitor<std::int64_t>
+ > transit(vs, clip_extent, out_geom);
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);
+ mapnik::util::apply_visitor(transit,geom);
+ auto const& poly = mapnik::util::get<mapnik::geometry::multi_polygon<std::int64_t>>(out_geom.geom);
count3 += poly.size();
}
if (count != count3)
diff --git a/bin/vtile-edit.cpp b/bin/vtile-edit.cpp
new file mode 100644
index 0000000..7b3b54b
--- /dev/null
+++ b/bin/vtile-edit.cpp
@@ -0,0 +1,134 @@
+#include <mapnik/util/file_io.hpp>
+#include "vector_tile.pb.h"
+#include "vector_tile_compression.hpp"
+
+#include <iostream>
+#include <fstream>
+
+#include <protozero/pbf_reader.hpp>
+
+int main(int argc, char** argv)
+{
+ try
+ {
+ if (argc < 2)
+ {
+ std::clog << "usage: vtile-edit /path/to/tile.vector.mvt [--set-version version]" << std::endl;
+ return -1;
+ }
+ std::string vtile(argv[1]);
+ mapnik::util::file input(vtile);
+ if (!input.open())
+ {
+ std::clog << std::string("failed to open ") + vtile << "\n";
+ return -1;
+ }
+
+ unsigned version = 0;
+ bool clean_empty = false;
+ if (argc > 2)
+ {
+ for (int i = 2; i < argc; i++)
+ {
+ std::string flag = argv[i];
+ if (flag == "--set-version")
+ {
+ version = std::stoi(argv[i + 1]);
+ }
+ if (flag == "--clean-empty")
+ {
+ clean_empty = true;
+ }
+ }
+ }
+
+ std::string message(input.data().get(), input.size());
+
+ bool is_zlib = mapnik::vector_tile_impl::is_zlib_compressed(message);
+ bool is_gzip = mapnik::vector_tile_impl::is_gzip_compressed(message);
+ if (is_zlib || is_gzip)
+ {
+ if (is_zlib)
+ {
+ std::cout << "message: zlib compressed\n";
+ }
+ else if (is_gzip)
+ {
+ std::cout << "message: gzip compressed\n";
+ }
+ std::string uncompressed;
+ mapnik::vector_tile_impl::zlib_decompress(message,uncompressed);
+ message = uncompressed;
+ }
+
+ vector_tile::Tile tile;
+ tile.ParseFromString(message);
+
+ for (int j=0;j<tile.layers_size(); ++j)
+ {
+ auto layer = tile.mutable_layers(j);
+ std::clog << "layer: " << layer->name() << std::endl;
+ if (version != 0)
+ {
+ std::clog << "old version: " << layer->version() << std::endl;
+ if (version != layer->version())
+ {
+ layer->set_version(version);
+ }
+ std::clog << "new version: " << layer->version() << std::endl;
+ }
+ if (clean_empty)
+ {
+ std::clog << "Cleaning empty features" << std::endl;
+ for (int i = 0; i < layer->features_size(); ++i)
+ {
+ auto feature = layer->mutable_features(i);
+ if (feature->geometry_size() == 0 && !feature->has_raster())
+ {
+ layer->mutable_features()->DeleteSubrange(i,1);
+ --i;
+ }
+ }
+ if (layer->features_size() == 0)
+ {
+ tile.mutable_layers()->DeleteSubrange(j,1);
+ --j;
+ }
+ }
+ }
+
+ // Serialize
+ std::string output;
+ tile.SerializeToString(&output);
+
+ // Recompress
+ bool gzip = true;
+ if (is_zlib || is_gzip)
+ {
+ if (is_zlib)
+ {
+ gzip = false;
+ std::cout << "message: zlib compressing\n";
+ }
+ else if (is_gzip)
+ {
+ gzip = true;
+ std::cout << "message: gzip compressing\n";
+ }
+ std::string compressed;
+ mapnik::vector_tile_impl::zlib_compress(output,compressed,gzip);
+ output = compressed;
+ }
+
+ std::ofstream file(vtile);
+ file << output;
+
+ std::clog << "wrote to: " << vtile << std::endl;
+ }
+ catch (std::exception const& ex)
+ {
+ std::clog << "error: " << ex.what() << "\n";
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/c++/Makefile b/examples/c++/Makefile
index 14f21fd..b9a4e9b 100755
--- a/examples/c++/Makefile
+++ b/examples/c++/Makefile
@@ -14,8 +14,8 @@ install: tileinfo
chmod +x /usr/local/bin/tileinfo
test:
- ./tileinfo ../data/14_8716_8015.vector.pbf
- ./tileinfo ../data/14_2620_6331.vector.pbf.z
+ ./tileinfo ../data/14_8716_8015.vector.mvt
+ ./tileinfo ../data/14_2620_6331.vector.mvt.z
clean:
@rm -f ./tileinfo
diff --git a/examples/c++/README.md b/examples/c++/README.md
index 695b826..b415c48 100644
--- a/examples/c++/README.md
+++ b/examples/c++/README.md
@@ -25,4 +25,4 @@ Install these dependencies on OS X:
## Usage
- tileinfo ../data/14_8716_8015.vector.pbf
+ tileinfo ../data/14_8716_8015.vector.mvt
diff --git a/examples/c++/tileinfo.cpp b/examples/c++/tileinfo.cpp
index fb1e532..b4e3959 100644
--- a/examples/c++/tileinfo.cpp
+++ b/examples/c++/tileinfo.cpp
@@ -1,5 +1,11 @@
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
#include "vector_tile_compression.hpp"
+
#include <vector>
#include <iostream>
#include <fstream>
diff --git a/examples/data/14_2620_6331.vector.pbf.z b/examples/data/14_2620_6331.vector.mvt.z
similarity index 100%
rename from examples/data/14_2620_6331.vector.pbf.z
rename to examples/data/14_2620_6331.vector.mvt.z
diff --git a/examples/data/14_8716_8015.vector.pbf b/examples/data/14_8716_8015.vector.mvt
similarity index 100%
rename from examples/data/14_8716_8015.vector.pbf
rename to examples/data/14_8716_8015.vector.mvt
diff --git a/gyp/build.gyp b/gyp/build.gyp
index 1bfccf6..5e579ea 100644
--- a/gyp/build.gyp
+++ b/gyp/build.gyp
@@ -141,7 +141,9 @@
],
"include_dirs": [
"../src",
- '../deps/protozero/include'
+ '../deps/protozero/include',
+ '../test',
+ '../test/utils'
]
},
{
@@ -177,6 +179,38 @@
]
},
{
+ "target_name": "vtile-encode",
+ 'dependencies': [ 'mapnik_vector_tile_impl' ],
+ "type": "executable",
+ "defines": [
+ "<@(common_defines)",
+ "MAPNIK_PLUGINDIR=<(MAPNIK_PLUGINDIR)"
+ ],
+ "sources": [
+ "../bench/vtile-encode.cpp"
+ ],
+ "include_dirs": [
+ "../src",
+ '../deps/protozero/include'
+ ]
+ },
+ {
+ "target_name": "vtile-edit",
+ 'dependencies': [ 'mapnik_vector_tile_impl' ],
+ "type": "executable",
+ "defines": [
+ "<@(common_defines)",
+ "MAPNIK_PLUGINDIR=<(MAPNIK_PLUGINDIR)"
+ ],
+ "sources": [
+ "../bin/vtile-edit.cpp"
+ ],
+ "include_dirs": [
+ "../src",
+ '../deps/protozero/include'
+ ]
+ },
+ {
"target_name": "tileinfo",
'dependencies': [ 'vector_tile' ],
"type": "executable",
diff --git a/package.json b/package.json
index 46244cb..25151c5 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mapnik-vector-tile",
- "version": "0.14.3",
- "description": "Mapnik vector tile API",
+ "version": "1.0.1",
+ "description": "Mapnik Vector Tile API",
"main": "./package.json",
"repository" : {
"type" : "git",
diff --git a/scripts/build.sh b/scripts/build.sh
index 26410ac..c3ba34b 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -8,6 +8,6 @@ fi
echo $PROJ_LIB
ls $PROJ_LIB/
-make -j${JOBS} test BUILDTYPE=${BUILDTYPE:-Release} V=1
+make -j${JOBS:-1} test BUILDTYPE=${BUILDTYPE:-Release} V=1
-set +e +u
\ No newline at end of file
+set +e +u
diff --git a/src/vector_tile_backend_pbf.cpp b/src/vector_tile_backend_pbf.cpp
deleted file mode 100644
index 4404b3b..0000000
--- a/src/vector_tile_backend_pbf.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "vector_tile_backend_pbf.hpp"
-#include "vector_tile_backend_pbf.ipp"
\ No newline at end of file
diff --git a/src/vector_tile_backend_pbf.hpp b/src/vector_tile_backend_pbf.hpp
deleted file mode 100644
index 510a323..0000000
--- a/src/vector_tile_backend_pbf.hpp
+++ /dev/null
@@ -1,71 +0,0 @@
-#ifndef __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__
-#define __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__
-
-// vector tile
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
-
-#include "vector_tile_config.hpp"
-#include "vector_tile_geometry_encoder.hpp"
-#include <mapnik/value.hpp>
-#include <mapnik/geometry.hpp>
-#include <unordered_map>
-
-namespace mapnik
-{
-class feature_impl;
-}
-
-namespace mapnik { namespace vector_tile_impl {
-
- struct backend_pbf
- {
- typedef std::map<std::string, unsigned> keys_container;
- typedef std::unordered_map<mapnik::value, unsigned> values_container;
- private:
- vector_tile::Tile & tile_;
- unsigned path_multiplier_;
- mutable vector_tile::Tile_Layer * current_layer_;
- keys_container keys_;
- values_container values_;
- int32_t x_, y_;
- public:
- mutable vector_tile::Tile_Feature * current_feature_;
- MAPNIK_VECTOR_INLINE explicit backend_pbf(vector_tile::Tile & _tile,
- unsigned path_multiplier);
-
- MAPNIK_VECTOR_INLINE void add_tile_feature_raster(std::string const& image_buffer);
- MAPNIK_VECTOR_INLINE void stop_tile_feature();
- MAPNIK_VECTOR_INLINE void start_tile_feature(mapnik::feature_impl const& feature);
- MAPNIK_VECTOR_INLINE void start_tile_layer(std::string const& name);
- MAPNIK_VECTOR_INLINE unsigned get_path_multiplier()
- {
- return path_multiplier_;
- }
- inline void stop_tile_layer() {}
-
- template <typename T>
- inline bool add_path(T const& path)
- {
- if (current_feature_)
- {
- return encode_geometry(path,
- *current_feature_,
- x_,
- y_);
- }
- // no path was added return false
- return false;
- }
- };
-
-}} // end ns
-
-#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
-#include "vector_tile_backend_pbf.ipp"
-#endif
-
-#endif // __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__
diff --git a/src/vector_tile_backend_pbf.ipp b/src/vector_tile_backend_pbf.ipp
deleted file mode 100644
index 70df527..0000000
--- a/src/vector_tile_backend_pbf.ipp
+++ /dev/null
@@ -1,160 +0,0 @@
-// mapnik
-#include <mapnik/version.hpp>
-#include <mapnik/feature.hpp>
-#include <mapnik/value_types.hpp>
-#include <mapnik/util/variant.hpp>
-
-// vector tile
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
-
-#include "vector_tile_geometry_encoder.hpp"
-
-#include <unordered_map>
-
-namespace mapnik { namespace vector_tile_impl {
-
-struct to_tile_value
-{
-public:
- to_tile_value(vector_tile::Tile_Value * value):
- value_(value) {}
-
- void operator () ( value_integer val ) const
- {
- // TODO: figure out shortest varint encoding.
- value_->set_int_value(val);
- }
-
- void operator () ( mapnik::value_bool val ) const
- {
- value_->set_bool_value(val);
- }
-
- void operator () ( mapnik::value_double val ) const
- {
- // TODO: Figure out how we can encode 32 bit floats in some cases.
- value_->set_double_value(val);
- }
-
- void operator () ( mapnik::value_unicode_string const& val ) const
- {
- std::string str;
- to_utf8(val, str);
- value_->set_string_value(str.data(), str.length());
- }
-
- void operator () ( mapnik::value_null const& /*val*/ ) const
- {
- // do nothing
- }
-private:
- vector_tile::Tile_Value * value_;
-};
-
-backend_pbf::backend_pbf(vector_tile::Tile & _tile,
- unsigned path_multiplier)
- : tile_(_tile),
- path_multiplier_(path_multiplier),
- current_layer_(NULL),
- x_(0),
- y_(0),
- current_feature_(NULL)
-{
-}
-
-void backend_pbf::add_tile_feature_raster(std::string const& image_buffer)
-{
- if (current_feature_)
- {
- current_feature_->set_raster(image_buffer);
- }
-}
-
-void backend_pbf::stop_tile_feature()
-{
- if (current_layer_ && current_feature_)
- {
- if (!current_feature_->has_raster() && current_feature_->geometry_size() == 0 )
- {
- current_layer_->mutable_features()->RemoveLast();
- }
- }
-}
-
-void backend_pbf::start_tile_feature(mapnik::feature_impl const& feature)
-{
- if (current_layer_)
- {
- current_feature_ = current_layer_->add_features();
- x_ = y_ = 0;
-
- // TODO - encode as sint64: (n << 1) ^ ( n >> 63)
- // test current behavior with negative numbers
- current_feature_->set_id(feature.id());
-
- feature_kv_iterator itr = feature.begin();
- feature_kv_iterator end = feature.end();
- for ( ;itr!=end; ++itr)
- {
- std::string const& name = std::get<0>(*itr);
- mapnik::value const& val = std::get<1>(*itr);
- if (!val.is_null())
- {
- // Insert the key index
- keys_container::const_iterator key_itr = keys_.find(name);
- if (key_itr == keys_.end())
- {
- // The key doesn't exist yet in the dictionary.
- current_layer_->add_keys(name.c_str(), name.length());
- size_t index = keys_.size();
- keys_.emplace(name, index);
- current_feature_->add_tags(index);
- }
- else
- {
- current_feature_->add_tags(key_itr->second);
- }
-
- // Insert the value index
- values_container::const_iterator val_itr = values_.find(val);
- if (val_itr == values_.end())
- {
- // The value doesn't exist yet in the dictionary.
- to_tile_value visitor(current_layer_->add_values());
- mapnik::util::apply_visitor(visitor, val);
- size_t index = values_.size();
- values_.emplace(val, index);
- current_feature_->add_tags(index);
- }
- else
- {
- current_feature_->add_tags(val_itr->second);
- }
- }
- }
- }
-}
-
-void backend_pbf::start_tile_layer(std::string const& name)
-{
- // Key/value dictionary is per-layer.
- keys_.clear();
- values_.clear();
-
- current_layer_ = tile_.add_layers();
- current_layer_->set_name(name);
- current_layer_->set_version(1);
-
- // We currently use path_multiplier as a factor to scale the coordinates.
- // Eventually, we should replace this with the extent specifying the
- // bounding box in both dimensions. E.g. an extent of 4096 means that
- // the coordinates encoded in this tile should be visible in the range
- // from 0..4095.
- current_layer_->set_extent(256 * path_multiplier_);
-}
-
-}} // end ns
diff --git a/src/vector_tile_composite.hpp b/src/vector_tile_composite.hpp
new file mode 100644
index 0000000..aeb82b5
--- /dev/null
+++ b/src/vector_tile_composite.hpp
@@ -0,0 +1,105 @@
+#ifndef __MAPNIK_VECTOR_TILE_COMPOSITE_H__
+#define __MAPNIK_VECTOR_TILE_COMPOSITE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_merc_tile.hpp"
+#include "vector_tile_datasource_pbf.hpp"
+#include "vector_tile_processor.hpp"
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/map.hpp>
+#include <mapnik/layer.hpp>
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+void composite(merc_tile & target_vt,
+ std::vector<merc_tile_ptr> const& vtiles,
+ mapnik::Map & map,
+ processor & ren, // processor should be on map object provided
+ double scale_denominator = 0.0,
+ unsigned offset_x = 0,
+ unsigned offset_y = 0,
+ bool reencode = false)
+{
+ if (target_vt.tile_size() <= 0)
+ {
+ throw std::runtime_error("Vector tile size must be great than zero");
+ }
+
+ for (auto const& vt : vtiles)
+ {
+ if (vt->tile_size() <= 0)
+ {
+ throw std::runtime_error("Vector tile size must be great than zero");
+ }
+
+ if (vt->is_empty())
+ {
+ continue;
+ }
+
+ bool reencode_tile = reencode;
+ if (!reencode_tile && !target_vt.same_extent(*vt))
+ {
+ reencode_tile = true;
+ }
+
+ protozero::pbf_reader tile_message(vt->get_reader());
+ // loop through the layers of the tile!
+ while (tile_message.next(Tile_Encoding::LAYERS))
+ {
+ bool reencode_layer = reencode_tile;
+ auto data_pair = tile_message.get_data();
+ protozero::pbf_reader layer_message(data_pair);
+ if (!layer_message.next(Layer_Encoding::NAME))
+ {
+ continue;
+ }
+
+ std::string layer_name = layer_message.get_string();
+
+ if (!reencode_layer)
+ {
+ target_vt.append_layer_buffer(data_pair.first, data_pair.second, layer_name);
+ }
+
+ if (target_vt.has_layer(layer_name))
+ {
+ continue;
+ }
+
+ protozero::pbf_reader layer_pbf(data_pair);
+ auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_pbf,
+ vt->x(),
+ vt->y(),
+ vt->z(),
+ true);
+ mapnik::layer lyr(layer_name,"+init=epsg:3857");
+ ds->set_envelope(vt->get_buffered_extent());
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+ }
+ }
+ if (!map.layers().empty())
+ {
+ ren.update_tile(target_vt,
+ scale_denominator,
+ offset_x,
+ offset_y);
+ }
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_COMPOSITE_H__
diff --git a/src/vector_tile_compression.hpp b/src/vector_tile_compression.hpp
index 65205de..0d65b05 100644
--- a/src/vector_tile_compression.hpp
+++ b/src/vector_tile_compression.hpp
@@ -1,20 +1,43 @@
#ifndef __MAPNIK_VECTOR_TILE_COMPRESSION_H__
#define __MAPNIK_VECTOR_TILE_COMPRESSION_H__
-#include <string>
+// mapnik-vector-tile
#include "vector_tile_config.hpp"
+
+// zlib
#include <zlib.h>
-namespace mapnik { namespace vector_tile_impl {
+// std
+#include <string>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
inline bool is_zlib_compressed(const char * data, std::size_t size)
{
- return size > 2 && static_cast<uint8_t>(data[0]) == 0x78 && static_cast<uint8_t>(data[1]) == 0x9C;
+ return size > 2 &&
+ static_cast<uint8_t>(data[0]) == 0x78 &&
+ (
+ static_cast<uint8_t>(data[1]) == 0x9C ||
+ static_cast<uint8_t>(data[1]) == 0x01 ||
+ static_cast<uint8_t>(data[1]) == 0xDA ||
+ static_cast<uint8_t>(data[1]) == 0x5E
+ );
}
inline bool is_zlib_compressed(std::string const& data)
{
- return data.size() > 2 && static_cast<uint8_t>(data[0]) == 0x78 && static_cast<uint8_t>(data[1]) == 0x9C;
+ return data.size() > 2 &&
+ static_cast<uint8_t>(data[0]) == 0x78 &&
+ (
+ static_cast<uint8_t>(data[1]) == 0x9C ||
+ static_cast<uint8_t>(data[1]) == 0x01 ||
+ static_cast<uint8_t>(data[1]) == 0xDA ||
+ static_cast<uint8_t>(data[1]) == 0x5E
+ );
}
inline bool is_gzip_compressed(const char * data, std::size_t size)
@@ -29,12 +52,29 @@ inline bool is_gzip_compressed(std::string const& data)
// decodes both zlib and gzip
// http://stackoverflow.com/a/1838702/2333354
-MAPNIK_VECTOR_INLINE void zlib_decompress(std::string const& input, std::string & output);
-MAPNIK_VECTOR_INLINE void zlib_compress(std::string const& input, std::string & output, bool gzip=true, int level=Z_DEFAULT_COMPRESSION, int strategy=Z_DEFAULT_STRATEGY);
-MAPNIK_VECTOR_INLINE void zlib_decompress(const char * data, std::size_t size, std::string & output);
-MAPNIK_VECTOR_INLINE void zlib_compress(const char * data, std::size_t size, std::string & output, bool gzip=true, int level=Z_DEFAULT_COMPRESSION, int strategy=Z_DEFAULT_STRATEGY);
+MAPNIK_VECTOR_INLINE void zlib_decompress(std::string const& input,
+ std::string & output);
+
+MAPNIK_VECTOR_INLINE void zlib_compress(std::string const& input,
+ std::string & output,
+ bool gzip=true,
+ int level=Z_DEFAULT_COMPRESSION,
+ int strategy=Z_DEFAULT_STRATEGY);
+
+MAPNIK_VECTOR_INLINE void zlib_decompress(const char * data,
+ std::size_t size,
+ std::string & output);
+
+MAPNIK_VECTOR_INLINE void zlib_compress(const char * data,
+ std::size_t size,
+ std::string & output,
+ bool gzip=true,
+ int level=Z_DEFAULT_COMPRESSION,
+ int strategy=Z_DEFAULT_STRATEGY);
+
+} // end ns vector_tile_impl
-}} // end ns
+} // end ns mapnik
#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
#include "vector_tile_compression.ipp"
diff --git a/src/vector_tile_compression.ipp b/src/vector_tile_compression.ipp
index 75fd369..0e2d31e 100644
--- a/src/vector_tile_compression.ipp
+++ b/src/vector_tile_compression.ipp
@@ -1,7 +1,14 @@
-#include <stdexcept>
+// zlib
#include <zlib.h>
-namespace mapnik { namespace vector_tile_impl {
+// std
+#include <stdexcept>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
// decodes both zlib and gzip
// http://stackoverflow.com/a/1838702/2333354
@@ -22,8 +29,11 @@ void zlib_decompress(const char * data, std::size_t size, std::string & output)
inflate_s.avail_out = 2 * size;
inflate_s.next_out = (Bytef *)(output.data() + length);
int ret = inflate(&inflate_s, Z_FINISH);
- if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) {
- throw std::runtime_error(inflate_s.msg);
+ if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR)
+ {
+ std::string error_msg = inflate_s.msg;
+ inflateEnd(&inflate_s);
+ throw std::runtime_error(error_msg);
}
length += (2 * size - inflate_s.avail_out);
@@ -52,7 +62,7 @@ void zlib_compress(const char * data, std::size_t size, std::string & output, bo
}
if (deflateInit2(&deflate_s, level, Z_DEFLATED, windowsBits, 8, strategy) != Z_OK)
{
- throw std::runtime_error("deflate failed");
+ throw std::runtime_error("deflate init failed");
}
deflate_s.next_in = (Bytef *)data;
deflate_s.avail_in = size;
@@ -62,10 +72,11 @@ void zlib_compress(const char * data, std::size_t size, std::string & output, bo
output.resize(length + increase);
deflate_s.avail_out = increase;
deflate_s.next_out = (Bytef *)(output.data() + length);
- int ret = deflate(&deflate_s, Z_FINISH);
- if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) {
- throw std::runtime_error(deflate_s.msg);
- }
+ // From http://www.zlib.net/zlib_how.html
+ // "deflate() has a return value that can indicate errors, yet we do not check it here.
+ // Why not? Well, it turns out that deflate() can do no wrong here."
+ // Basically only possible error is from deflateInit not working properly
+ deflate(&deflate_s, Z_FINISH);
length += (increase - deflate_s.avail_out);
} while (deflate_s.avail_out == 0);
deflateEnd(&deflate_s);
@@ -77,4 +88,6 @@ void zlib_compress(std::string const& input, std::string & output, bool gzip, in
zlib_compress(input.data(),input.size(),output,gzip,level,strategy);
}
-}}
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_config.hpp b/src/vector_tile_config.hpp
index f1a56c0..2c586c9 100644
--- a/src/vector_tile_config.hpp
+++ b/src/vector_tile_config.hpp
@@ -7,5 +7,69 @@
#define MAPNIK_VECTOR_INLINE
#endif
+#include <protozero/types.hpp>
-#endif // __MAPNIK_VECTOR_TILE_CONFIG_H__
\ No newline at end of file
+#define VT_LEGACY_IMAGE_SIZE 256.0
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+enum Tile_Encoding : protozero::pbf_tag_type
+{
+ LAYERS = 3
+};
+
+enum Layer_Encoding : protozero::pbf_tag_type
+{
+ VERSION = 15,
+ NAME = 1,
+ FEATURES = 2,
+ KEYS = 3,
+ VALUES = 4,
+ EXTENT = 5
+};
+
+enum Feature_Encoding : protozero::pbf_tag_type
+{
+ ID = 1,
+ TAGS = 2,
+ TYPE = 3,
+ GEOMETRY = 4,
+ RASTER = 5
+};
+
+enum Value_Encoding : protozero::pbf_tag_type
+{
+ STRING = 1,
+ FLOAT = 2,
+ DOUBLE = 3,
+ INT = 4,
+ UINT = 5,
+ SINT = 6,
+ BOOL = 7
+};
+
+enum Geometry_Type : std::uint8_t
+{
+ UNKNOWN = 0,
+ POINT = 1,
+ LINESTRING = 2,
+ POLYGON = 3
+};
+
+enum polygon_fill_type : std::uint8_t {
+ even_odd_fill = 0,
+ non_zero_fill,
+ positive_fill,
+ negative_fill,
+ polygon_fill_type_max
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_CONFIG_H__
diff --git a/src/vector_tile_datasource.cpp b/src/vector_tile_datasource.cpp
deleted file mode 100644
index 9782880..0000000
--- a/src/vector_tile_datasource.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "vector_tile_datasource.hpp"
-#include "vector_tile_datasource.ipp"
\ No newline at end of file
diff --git a/src/vector_tile_datasource.hpp b/src/vector_tile_datasource.hpp
deleted file mode 100644
index 60d5d07..0000000
--- a/src/vector_tile_datasource.hpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#ifndef __MAPNIK_VECTOR_TILE_DATASOURCE_H__
-#define __MAPNIK_VECTOR_TILE_DATASOURCE_H__
-
-#include <mapnik/datasource.hpp>
-#include <mapnik/box2d.hpp>
-#include "vector_tile_config.hpp"
-
-namespace vector_tile {
- class Tile_Layer;
-}
-
-namespace mapnik { namespace vector_tile_impl {
-
- class tile_datasource : public datasource
- {
- public:
- tile_datasource(vector_tile::Tile_Layer const& layer,
- unsigned x,
- unsigned y,
- unsigned z,
- unsigned tile_size);
- virtual ~tile_datasource();
- 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;
- private:
- mutable mapnik::layer_descriptor desc_;
- mutable bool attributes_added_;
- vector_tile::Tile_Layer const& 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_;
- };
-
-}} // end ns
-
-#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
-#include "vector_tile_datasource.ipp"
-#endif
-
-#endif // __MAPNIK_VECTOR_TILE_DATASOURCE_H__
diff --git a/src/vector_tile_datasource.ipp b/src/vector_tile_datasource.ipp
deleted file mode 100644
index acd297d..0000000
--- a/src/vector_tile_datasource.ipp
+++ /dev/null
@@ -1,328 +0,0 @@
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
-
-#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 <memory>
-#include <stdexcept>
-#include <string>
-
-#include <boost/optional.hpp>
-#include <unicode/unistr.h>
-
-#if defined(DEBUG)
-#include <mapnik/debug.hpp>
-#endif
-
-namespace mapnik { namespace vector_tile_impl {
-
- void add_attributes(mapnik::feature_ptr feature,
- vector_tile::Tile_Feature const& f,
- vector_tile::Tile_Layer const& layer,
- mapnik::transcoder const& tr)
- {
- std::size_t num_keys = static_cast<std::size_t>(layer.keys_size());
- std::size_t num_values = static_cast<std::size_t>(layer.values_size());
- for (int m = 0; m < f.tags_size(); m += 2)
- {
- std::size_t key_name = f.tags(m);
- std::size_t key_value = f.tags(m + 1);
- if (key_name < num_keys
- && key_value < num_values)
- {
- std::string const& name = layer.keys(key_name);
- if (feature->has_key(name))
- {
- vector_tile::Tile_Value const& value = layer.values(key_value);
- if (value.has_string_value())
- {
- std::string const& str = value.string_value();
- feature->put(name, tr.transcode(str.data(), str.length()));
- }
- else if (value.has_int_value())
- {
- feature->put(name, static_cast<mapnik::value_integer>(value.int_value()));
- }
- else if (value.has_double_value())
- {
- feature->put(name, static_cast<mapnik::value_double>(value.double_value()));
- }
- else if (value.has_float_value())
- {
- feature->put(name, static_cast<mapnik::value_double>(value.float_value()));
- }
- else if (value.has_bool_value())
- {
- feature->put(name, static_cast<mapnik::value_bool>(value.bool_value()));
- }
- else if (value.has_sint_value())
- {
- feature->put(name, static_cast<mapnik::value_integer>(value.sint_value()));
- }
- else if (value.has_uint_value())
- {
- feature->put(name, static_cast<mapnik::value_integer>(value.uint_value()));
- }
- }
- }
- }
- }
-
- template <typename Filter>
- class tile_featureset : public Featureset
- {
- public:
- tile_featureset(Filter const& filter,
- mapnik::box2d<double> const& tile_extent,
- mapnik::box2d<double> const& unbuffered_query,
- std::set<std::string> const& attribute_names,
- vector_tile::Tile_Layer const& layer,
- double tile_x,
- double tile_y,
- double scale)
- : filter_(filter),
- tile_extent_(tile_extent),
- unbuffered_query_(unbuffered_query),
- layer_(layer),
- tile_x_(tile_x),
- tile_y_(tile_y),
- scale_(scale),
- itr_(0),
- end_(layer_.features_size()),
- 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 (int i = 0; i < layer_.keys_size(); ++i)
- {
- if (layer_.keys(i) == *pos)
- {
- ctx_->push(*pos);
- break;
- }
- }
- }
- }
-
- virtual ~tile_featureset() {}
-
- feature_ptr next()
- {
- while (itr_ < end_)
- {
- vector_tile::Tile_Feature const& f = layer_.features(itr_);
- mapnik::value_integer feature_id = itr_++;
- if (f.has_id())
- {
- feature_id = f.id();
- }
- if (f.has_raster())
- {
- std::string const& image_buffer = f.raster();
- 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
- );
- mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
- feature->set_raster(raster);
- add_attributes(feature,f,layer_,tr_);
- return feature;
- }
- }
- }
- }
- if (f.geometry_size() <= 0)
- {
- continue;
- }
- mapnik::vector_tile_impl::Geometry<double> geoms(f,tile_x_, tile_y_, scale_, -1*scale_);
- mapnik::geometry::geometry<double> geom = decode_geometry<double>(geoms, f.type(), filter_.box_);
- if (geom.is<mapnik::geometry::geometry_empty>())
- {
- continue;
- }
- #if defined(DEBUG)
- mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
- if (!filter_.pass(envelope))
- {
- MAPNIK_LOG_ERROR(tile_datasource_pbf) << "tile_datasource: filter:pass should not get here";
- continue;
- }
- #endif
- mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id);
- feature->set_geometry(std::move(geom));
- add_attributes(feature,f,layer_,tr_);
- return feature;
- }
- return feature_ptr();
- }
-
- private:
- Filter filter_;
- mapnik::box2d<double> tile_extent_;
- mapnik::box2d<double> unbuffered_query_;
- vector_tile::Tile_Layer const& layer_;
- double tile_x_;
- double tile_y_;
- double scale_;
- unsigned itr_;
- unsigned end_;
- mapnik::transcoder tr_;
- mapnik::context_ptr ctx_;
- };
-
- // tile_datasource impl
- tile_datasource::tile_datasource(vector_tile::Tile_Layer const& layer,
- unsigned x,
- unsigned y,
- unsigned z,
- unsigned tile_size)
- : datasource(parameters()),
- desc_("in-memory datasource","utf-8"),
- attributes_added_(false),
- layer_(layer),
- x_(x),
- y_(y),
- z_(z),
- tile_size_(tile_size),
- extent_initialized_(false) {
- 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;
- scale_ = (static_cast<double>(layer_.extent()) / tile_size_) * tile_size_/resolution;
- }
-
- tile_datasource::~tile_datasource() {}
-
- datasource::datasource_t tile_datasource::type() const
- {
- return datasource::Vector;
- }
-
- featureset_ptr tile_datasource::features(query const& q) const
- {
- mapnik::filter_in_box filter(q.get_bbox());
- return std::make_shared<tile_featureset<mapnik::filter_in_box> >
- (filter, get_tile_extent(), q.get_unbuffered_bbox(), q.property_names(), layer_, tile_x_, tile_y_, scale_);
- }
-
- featureset_ptr tile_datasource::features_at_point(coord2d const& pt, double tol) const
- {
- mapnik::filter_at_point filter(pt,tol);
- std::set<std::string> names;
- for (int i = 0; i < layer_.keys_size(); ++i)
- {
- names.insert(layer_.keys(i));
- }
- return std::make_shared<tile_featureset<filter_at_point> >
- (filter, get_tile_extent(), get_tile_extent(), names, layer_, tile_x_, tile_y_, scale_);
- }
-
- void tile_datasource::set_envelope(box2d<double> const& bbox)
- {
- extent_initialized_ = true;
- extent_ = bbox;
- }
-
- box2d<double> tile_datasource::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::envelope() const
- {
- if (!extent_initialized_)
- {
- extent_ = get_tile_extent();
- extent_initialized_ = true;
- }
- return extent_;
- }
-
- boost::optional<mapnik::datasource_geometry_t> tile_datasource::get_geometry_type() const
- {
- return mapnik::datasource_geometry_t::Collection;
- }
-
- layer_descriptor tile_datasource::get_descriptor() const
- {
- if (!attributes_added_)
- {
- for (int i = 0; i < layer_.keys_size(); ++i)
- {
- // Object type here because we don't know the precise value until features are unpacked
- desc_.add_descriptor(attribute_descriptor(layer_.keys(i), Object));
- }
- attributes_added_ = true;
- }
- return desc_;
- }
-
- }} // end ns
diff --git a/src/vector_tile_datasource_pbf.hpp b/src/vector_tile_datasource_pbf.hpp
index 89024db..be5b144 100644
--- a/src/vector_tile_datasource_pbf.hpp
+++ b/src/vector_tile_datasource_pbf.hpp
@@ -1,56 +1,69 @@
#ifndef __MAPNIK_VECTOR_TILE_DATASOURCE_PBF_H__
#define __MAPNIK_VECTOR_TILE_DATASOURCE_PBF_H__
-#include <mapnik/datasource.hpp>
+// mapnik
#include <mapnik/box2d.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/util/variant.hpp>
+
+// protozero
#include <protozero/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(protozero::pbf_reader 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_;
- protozero::pbf_reader 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<protozero::pbf_reader> features_;
- std::vector<std::string> layer_keys_;
- layer_pbf_attr_type layer_values_;
- };
-
-}} // end ns
+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(protozero::pbf_reader const& layer,
+ unsigned x,
+ unsigned y,
+ unsigned z,
+ bool use_tile_extent = false);
+ 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_; }
+ std::uint32_t get_layer_extent() { return tile_size_; }
+private:
+ mutable mapnik::layer_descriptor desc_;
+ mutable bool attributes_added_;
+ mutable bool valid_layer_;
+ protozero::pbf_reader layer_;
+ unsigned x_;
+ unsigned y_;
+ unsigned z_;
+ std::uint32_t tile_size_;
+ mutable bool extent_initialized_;
+ mutable mapnik::box2d<double> extent_;
+ double tile_x_;
+ double tile_y_;
+ double scale_;
+ uint32_t version_;
+ datasource::datasource_t type_;
+
+ std::string name_;
+ std::vector<protozero::pbf_reader> features_;
+ std::vector<std::string> layer_keys_;
+ layer_pbf_attr_type layer_values_;
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
#include "vector_tile_datasource_pbf.ipp"
diff --git a/src/vector_tile_datasource_pbf.ipp b/src/vector_tile_datasource_pbf.ipp
index e72d179..124c3db 100644
--- a/src/vector_tile_datasource_pbf.ipp
+++ b/src/vector_tile_datasource_pbf.ipp
@@ -1,421 +1,260 @@
+// mapnik-vector-tile
+#include "vector_tile_featureset_pbf.hpp"
#include "vector_tile_projection.hpp"
-#include "vector_tile_geometry_decoder.hpp"
+#include "vector_tile_config.hpp"
+// mapnik
#include <mapnik/box2d.hpp>
#include <mapnik/coord.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/feature.hpp>
#include <mapnik/feature_layer_desc.hpp>
-#include <mapnik/geometry.hpp>
-#include <mapnik/params.hpp>
+#include <mapnik/geom_util.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>
+// protozero
+#include <protozero/pbf_reader.hpp>
+
+// boost
+#include <boost/optional.hpp>
+
+// std
#include <memory>
#include <stdexcept>
#include <string>
-#include <protozero/pbf_reader.hpp>
+namespace mapnik
+{
-#include <boost/optional.hpp>
-#include <unicode/unistr.h>
+namespace vector_tile_impl
+{
+
+// tile_datasource impl
+tile_datasource_pbf::tile_datasource_pbf(protozero::pbf_reader const& layer,
+ unsigned x,
+ unsigned y,
+ unsigned z,
+ bool use_tile_extent)
+ : datasource(parameters()),
+ desc_("in-memory PBF encoded datasource","utf-8"),
+ attributes_added_(false),
+ valid_layer_(true),
+ layer_(layer),
+ x_(x),
+ y_(y),
+ z_(z),
+ tile_size_(4096), // default for version 1
+ extent_initialized_(false),
+ tile_x_(0.0),
+ tile_y_(0.0),
+ scale_(0.0),
+ version_(1), // Version == 1 is the default because it was not required until v2 to have this field
+ type_(datasource::Vector)
+{
+ 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;
-#if defined(DEBUG)
-#include <mapnik/debug.hpp>
-#endif
+ protozero::pbf_reader val_msg;
-namespace mapnik { namespace vector_tile_impl {
+ // Check that the required fields exist for version 2 of the MVT specification
+ bool has_name = false;
+ bool has_extent = false;
- template <typename Filter>
- class tile_featureset_pbf : public Featureset
+ while (layer_.next())
{
- 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<protozero::pbf_reader> 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),
- num_keys_(layer_keys_.size()),
- num_values_(layer_values_.size()),
- tile_x_(tile_x),
- tile_y_(tile_y),
- scale_(scale),
- itr_(0),
- tr_("utf-8"),
- ctx_(std::make_shared<mapnik::context_type>())
+ switch(layer_.tag())
{
- 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_)
+ case Layer_Encoding::NAME:
+ name_ = layer_.get_string();
+ has_name = true;
+ break;
+ case Layer_Encoding::FEATURES:
{
- if (key == *pos)
+ auto data_pair = layer_.get_data();
+ protozero::pbf_reader check_feature(data_pair);
+ while (check_feature.next(Feature_Encoding::RASTER))
{
- ctx_->push(*pos);
- break;
+ type_ = datasource::Raster;
+ check_feature.skip();
}
+ protozero::pbf_reader f_msg(data_pair);
+ features_.push_back(f_msg);
}
- }
- }
-
- virtual ~tile_featureset_pbf() {}
-
- feature_ptr next()
- {
- while ( itr_ < features_.size() )
- {
- protozero::pbf_reader f = features_.at(itr_);
- // TODO: auto-increment feature id counter here
- mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,itr_);
-
- ++itr_;
- int32_t geometry_type = 0; // vector_tile::Tile_GeomType_UNKNOWN
- while (f.next())
+ break;
+ case Layer_Encoding::KEYS:
+ layer_keys_.push_back(layer_.get_string());
+ break;
+ case Layer_Encoding::VALUES:
+ val_msg = layer_.get_message();
+ while (val_msg.next())
{
- switch(f.tag())
- {
- case 1:
- feature->set_id(f.get_uint64());
+ switch(val_msg.tag()) {
+ case Value_Encoding::STRING:
+ layer_values_.push_back(val_msg.get_string());
break;
- case 2:
- {
- auto tag_iterator = f.get_packed_uint32();
- for (auto _i = tag_iterator.first; _i != tag_iterator.second;)
- {
- std::size_t key_name = *(_i++);
- assert(_i != tag_iterator.second);
- std::size_t key_value = *(_i++);
- if (key_name < num_keys_
- && key_value < num_values_)
- {
- std::string const& name = layer_keys_.at(key_name);
- if (feature->has_key(name))
- {
- pbf_attr_value_type val = layer_values_.at(key_value);
- 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");
- }
- }
- }
- }
- }
+ case Value_Encoding::FLOAT:
+ layer_values_.push_back(val_msg.get_float());
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");
- }
+ case Value_Encoding::DOUBLE:
+ layer_values_.push_back(val_msg.get_double());
break;
- case 5:
- {
- auto image_buffer = f.get_data();
- std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.first, image_buffer.second));
- 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;
- }
- }
- }
- }
+ case Value_Encoding::INT:
+ layer_values_.push_back(val_msg.get_int64());
+ break;
+ case Value_Encoding::UINT:
+ layer_values_.push_back(val_msg.get_uint64());
break;
- case 4:
- {
- auto geom_itr = f.get_packed_uint32();
- mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x_,tile_y_,scale_,-1*scale_);
- mapnik::geometry::geometry<double> geom = decode_geometry<double>(geoms, geometry_type, filter_.box_);
- if (geom.is<mapnik::geometry::geometry_empty>())
- {
- continue;
- }
- #if defined(DEBUG)
- mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
- if (!filter_.pass(envelope))
- {
- MAPNIK_LOG_ERROR(tile_datasource_pbf) << "tile_datasource_pbf: filter:pass should not get here";
- continue;
- }
- #endif
- feature->set_geometry(std::move(geom));
- return feature;
- }
+ case Value_Encoding::SINT:
+ layer_values_.push_back(val_msg.get_sint64());
+ break;
+ case Value_Encoding::BOOL:
+ layer_values_.push_back(val_msg.get_bool());
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");
-
+ throw std::runtime_error("unknown Value type " + std::to_string(layer_.tag()) + " in layer.values");
}
}
- }
- return feature_ptr();
+ break;
+ case Layer_Encoding::EXTENT:
+ tile_size_ = layer_.get_uint32();
+ has_extent = true;
+ break;
+ case Layer_Encoding::VERSION:
+ version_ = layer_.get_uint32();
+ break;
+ default:
+ throw std::runtime_error("unknown field type " + std::to_string(layer_.tag()) + " in layer");
}
-
- private:
- Filter filter_;
- mapnik::box2d<double> tile_extent_;
- mapnik::box2d<double> unbuffered_query_;
- std::vector<protozero::pbf_reader> const& features_;
- std::vector<std::string> const& layer_keys_;
- layer_pbf_attr_type const& layer_values_;
- std::size_t num_keys_;
- std::size_t num_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(protozero::pbf_reader 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)
+ }
+ if (version_ == 2)
{
- 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;
-
- protozero::pbf_reader val_msg;
-
- while (layer_.next())
+ // Check that all required
+ if (!has_name)
{
- 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");
- }
+ throw std::runtime_error("The required name field is missing in a vector tile layer. Tile does not comply with Version 2 of the Mapbox Vector Tile Specification.");
}
- scale_ = (static_cast<double>(layer_extent_) / tile_size_) * tile_size_/resolution;
+ else if (!has_extent)
+ {
+ throw std::runtime_error("The required extent field is missing in the layer " + name_ + ". Tile does not comply with Version 2 of the Mapbox Vector Tile Specification.");
+ }
+ scale_ = static_cast<double>(tile_size_)/resolution;
}
-
- tile_datasource_pbf::~tile_datasource_pbf() {}
-
- datasource::datasource_t tile_datasource_pbf::type() const
+ else if (version_ == 1)
{
- return datasource::Vector;
+ if (!has_name)
+ {
+ throw std::runtime_error("The required name field is missing in a vector tile layer.");
+ }
+ scale_ = static_cast<double>(tile_size_)/resolution;
}
-
- featureset_ptr tile_datasource_pbf::features(query const& q) const
+ else
{
- 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_);
+ valid_layer_ = false;
}
- featureset_ptr tile_datasource_pbf::features_at_point(coord2d const& pt, double tol) const
+ if (features_.empty())
{
- 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_);
+ valid_layer_ = false;
}
-
- void tile_datasource_pbf::set_envelope(box2d<double> const& bbox)
+ if (use_tile_extent)
{
- extent_initialized_ = true;
- extent_ = bbox;
+ params_["vector_layer_extent"] = tile_size_;
}
+}
+
+tile_datasource_pbf::~tile_datasource_pbf() {}
+
+datasource::datasource_t tile_datasource_pbf::type() const
+{
+ return type_;
+}
- box2d<double> tile_datasource_pbf::get_tile_extent() const
+featureset_ptr tile_datasource_pbf::features(query const& q) const
+{
+ if (!valid_layer_)
{
- 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);
+ // From spec:
+ // When a Vector Tile consumer encounters a Vector Tile layer with an unknown version,
+ // it MAY make a best-effort attempt to interpret the layer, or it MAY skip the layer.
+ // In either case it SHOULD continue to process subsequent layers in the Vector Tile.
+
+ // Therefore if version is invalid we will just return pointer
+ return featureset_ptr();
}
+ 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_, version_);
+}
- box2d<double> tile_datasource_pbf::envelope() const
+featureset_ptr tile_datasource_pbf::features_at_point(coord2d const& pt, double tol) const
+{
+ if (!valid_layer_)
{
- if (!extent_initialized_)
- {
- extent_ = get_tile_extent();
- extent_initialized_ = true;
- }
- return extent_;
+ // From spec:
+ // When a Vector Tile consumer encounters a Vector Tile layer with an unknown version,
+ // it MAY make a best-effort attempt to interpret the layer, or it MAY skip the layer.
+ // In either case it SHOULD continue to process subsequent layers in the Vector Tile.
+
+ // Therefore if version is invalid we will just return pointer
+ return featureset_ptr();
+ }
+ 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_, version_);
+}
+
+void tile_datasource_pbf::set_envelope(box2d<double> const& bbox)
+{
+ extent_initialized_ = true;
+ extent_ = bbox;
+}
- boost::optional<mapnik::datasource_geometry_t> tile_datasource_pbf::get_geometry_type() const
+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_)
{
- return mapnik::datasource_geometry_t::Collection;
+ extent_ = get_tile_extent();
+ extent_initialized_ = true;
}
+ return extent_;
+}
- layer_descriptor tile_datasource_pbf::get_descriptor() const
+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_)
{
- if (!attributes_added_)
+ for (auto const& key : layer_keys_)
{
- 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;
+ // Object type here because we don't know the precise value until features are unpacked
+ desc_.add_descriptor(attribute_descriptor(key, Object));
}
- return desc_;
+ attributes_added_ = true;
}
+ return desc_;
+}
+
+} // end ns vector_tile_impl
- }} // end ns
+} // end ns mapnik
diff --git a/src/vector_tile_featureset_pbf.cpp b/src/vector_tile_featureset_pbf.cpp
new file mode 100644
index 0000000..5e3c472
--- /dev/null
+++ b/src/vector_tile_featureset_pbf.cpp
@@ -0,0 +1,6 @@
+#include "vector_tile_featureset_pbf.hpp"
+#include "vector_tile_featureset_pbf.ipp"
+#include <mapnik/geom_util.hpp>
+
+template class mapnik::vector_tile_impl::tile_featureset_pbf<mapnik::filter_in_box>;
+template class mapnik::vector_tile_impl::tile_featureset_pbf<mapnik::filter_at_point>;
diff --git a/src/vector_tile_featureset_pbf.hpp b/src/vector_tile_featureset_pbf.hpp
new file mode 100644
index 0000000..1d6a142
--- /dev/null
+++ b/src/vector_tile_featureset_pbf.hpp
@@ -0,0 +1,73 @@
+#ifndef __MAPNIK_VECTOR_TILE_FEATURESET_PBF_H__
+#define __MAPNIK_VECTOR_TILE_FEATURESET_PBF_H__
+
+// protozero
+#include <protozero/pbf_reader.hpp>
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/feature.hpp>
+#include <mapnik/featureset.hpp>
+#include <mapnik/unicode.hpp>
+#include <mapnik/util/variant.hpp>
+#include <mapnik/view_transform.hpp>
+
+// std
+#include <set>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+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>;
+
+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<protozero::pbf_reader> const& features,
+ double tile_x,
+ double tile_y,
+ double scale,
+ std::vector<std::string> const& layer_keys,
+ layer_pbf_attr_type const& layer_values,
+ unsigned version);
+
+ virtual ~tile_featureset_pbf() {}
+
+ feature_ptr next();
+
+private:
+ Filter filter_;
+ mapnik::box2d<double> tile_extent_;
+ mapnik::box2d<double> unbuffered_query_;
+ std::vector<protozero::pbf_reader> const& features_;
+ std::vector<std::string> const& layer_keys_;
+ layer_pbf_attr_type const& layer_values_;
+ std::size_t num_keys_;
+ std::size_t num_values_;
+ double tile_x_;
+ double tile_y_;
+ double scale_;
+ unsigned itr_;
+ unsigned version_;
+ mapnik::transcoder tr_;
+ mapnik::context_ptr ctx_;
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_featureset_pbf.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_FEATURESET_PBF_H__
diff --git a/src/vector_tile_featureset_pbf.ipp b/src/vector_tile_featureset_pbf.ipp
new file mode 100644
index 0000000..441624a
--- /dev/null
+++ b/src/vector_tile_featureset_pbf.ipp
@@ -0,0 +1,325 @@
+// mapnik-vector-tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/image.hpp>
+#include <mapnik/image_reader.hpp>
+#include <mapnik/raster.hpp>
+#include <mapnik/unicode.hpp>
+#include <mapnik/view_transform.hpp>
+#if defined(DEBUG)
+#include <mapnik/debug.hpp>
+#endif
+
+// protozero
+#include <protozero/pbf_reader.hpp>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+template <typename Filter>
+tile_featureset_pbf<Filter>::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<protozero::pbf_reader> const& features,
+ double tile_x,
+ double tile_y,
+ double scale,
+ std::vector<std::string> const& layer_keys,
+ layer_pbf_attr_type const& layer_values,
+ unsigned version)
+ : filter_(filter),
+ tile_extent_(tile_extent),
+ unbuffered_query_(unbuffered_query),
+ features_(features),
+ layer_keys_(layer_keys),
+ layer_values_(layer_values),
+ num_keys_(layer_keys_.size()),
+ num_values_(layer_values_.size()),
+ tile_x_(tile_x),
+ tile_y_(tile_y),
+ scale_(scale),
+ itr_(0),
+ version_(version),
+ 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;
+ }
+ }
+ }
+}
+
+struct value_visitor
+{
+ mapnik::transcoder & tr_;
+ mapnik::feature_ptr & feature_;
+ std::string const& name_;
+
+ value_visitor(mapnik::transcoder & tr,
+ mapnik::feature_ptr & feature,
+ std::string const& name)
+ : tr_(tr),
+ feature_(feature),
+ name_(name) {}
+
+ void operator() (std::string const& val)
+ {
+ feature_->put(name_, tr_.transcode(val.data(), val.length()));
+ }
+
+ void operator() (bool const& val)
+ {
+ feature_->put(name_, static_cast<mapnik::value_bool>(val));
+ }
+
+ void operator() (int64_t const& val)
+ {
+ feature_->put(name_, static_cast<mapnik::value_integer>(val));
+ }
+
+ void operator() (uint64_t const& val)
+ {
+ feature_->put(name_, static_cast<mapnik::value_integer>(val));
+ }
+
+ void operator() (double const& val)
+ {
+ feature_->put(name_, static_cast<mapnik::value_double>(val));
+ }
+
+ void operator() (float const& val)
+ {
+ feature_->put(name_, static_cast<mapnik::value_double>(val));
+ }
+};
+
+template <typename Filter>
+feature_ptr tile_featureset_pbf<Filter>::next()
+{
+ while ( itr_ < features_.size() )
+ {
+ protozero::pbf_reader f = features_.at(itr_);
+ // TODO: auto-increment feature id counter here
+ mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,itr_);
+
+ ++itr_;
+ int32_t geometry_type = 0; // vector_tile::Tile_GeomType_UNKNOWN
+ bool has_geometry = false;
+ bool has_geometry_type = false;
+ std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+ bool has_raster = false;
+ std::pair<const char*, protozero::pbf_length_type> image_buffer;
+ while (f.next())
+ {
+ switch(f.tag())
+ {
+ case 1:
+ feature->set_id(f.get_uint64());
+ break;
+ case 2:
+ {
+ auto tag_iterator = f.get_packed_uint32();
+ for (auto _i = tag_iterator.first; _i != tag_iterator.second;)
+ {
+ std::size_t key_name = *(_i++);
+ if (_i == tag_iterator.second)
+ {
+ throw std::runtime_error("Vector Tile has a feature with an odd number of tags, therefore the tile is invalid.");
+ }
+ std::size_t key_value = *(_i++);
+ if (key_name < num_keys_
+ && key_value < num_values_)
+ {
+ std::string const& name = layer_keys_.at(key_name);
+ if (feature->has_key(name))
+ {
+ pbf_attr_value_type val = layer_values_.at(key_value);
+ value_visitor vv(tr_, feature, name);
+ mapnik::util::apply_visitor(vv, val);
+ }
+ }
+ else if (version_ == 2)
+ {
+ throw std::runtime_error("Vector Tile has a feature with repeated attributes with an invalid key or value as it does not appear in the layer. This is invalid according to the Mapbox Vector Tile Specification Version 2");
+ }
+ }
+ }
+ break;
+ case 3:
+ has_geometry_type = true;
+ 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("Vector tile has an unknown geometry type " + std::to_string(geometry_type) + " in feature");
+ }
+ break;
+ case 5:
+ if (has_geometry)
+ {
+ throw std::runtime_error("Vector Tile has a feature with a geometry and a raster, it must have only one of them");
+ }
+ if (has_raster)
+ {
+ throw std::runtime_error("Vector Tile has a feature with multiple raster fields, it must have only one of them");
+ }
+ has_raster = true;
+ image_buffer = f.get_data();
+ break;
+ case 4:
+ if (has_raster)
+ {
+ throw std::runtime_error("Vector Tile has a feature with a geometry and a raster, it must have only one of them");
+ }
+ if (has_geometry)
+ {
+ throw std::runtime_error("Vector Tile has a feature with multiple geometry fields, it must have only one of them");
+ }
+ has_geometry = true;
+ geom_itr = f.get_packed_uint32();
+ break;
+ default:
+ throw std::runtime_error("Vector Tile contains unknown field type " + std::to_string(f.tag()) +" in feature");
+
+ }
+ }
+ if (has_raster)
+ {
+ std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.first, image_buffer.second));
+ 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;
+ }
+ else if (has_geometry)
+ {
+ if (!has_geometry_type)
+ {
+ if (version_ == 1)
+ {
+ continue;
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has a feature that does not define the required geometry type.");
+ }
+ }
+ if (version_ != 1)
+ {
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x_,tile_y_,scale_,-1*scale_);
+ mapnik::geometry::geometry<double> geom = decode_geometry(geoms, geometry_type, version_, filter_.box_);
+ if (geom.is<mapnik::geometry::geometry_empty>())
+ {
+ continue;
+ }
+ #if defined(DEBUG)
+ mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
+ if (!filter_.pass(envelope))
+ {
+ MAPNIK_LOG_ERROR(tile_featureset_pbf) << "tile_featureset_pbf: filter:pass should not get here";
+ continue;
+ }
+ #endif
+ feature->set_geometry(std::move(geom));
+ return feature;
+ }
+ else
+ {
+ try
+ {
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x_,tile_y_,scale_,-1*scale_);
+ mapnik::geometry::geometry<double> geom = decode_geometry(geoms, geometry_type, version_, filter_.box_);
+ if (geom.is<mapnik::geometry::geometry_empty>())
+ {
+ continue;
+ }
+ #if defined(DEBUG)
+ mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
+ if (!filter_.pass(envelope))
+ {
+ MAPNIK_LOG_ERROR(tile_featureset_pbf) << "tile_featureset_pbf: filter:pass should not get here";
+ continue;
+ }
+ #endif
+ feature->set_geometry(std::move(geom));
+ }
+ catch (std::exception& e)
+ {
+ // For v1 any invalid geometry errors lets just skip the feature
+ continue;
+ }
+ return feature;
+ }
+ }
+ else if (version_ != 1)
+ {
+ throw std::runtime_error("Vector Tile does not have a geometry or raster");
+ }
+ }
+ return feature_ptr();
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_geometry_clipper.hpp b/src/vector_tile_geometry_clipper.hpp
new file mode 100644
index 0000000..13a8029
--- /dev/null
+++ b/src/vector_tile_geometry_clipper.hpp
@@ -0,0 +1,479 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_CLIPPER_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_CLIPPER_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+#include "vector_tile_processor.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_adapters.hpp>
+
+// angus clipper
+// http://www.angusj.com/delphi/clipper.php
+#include "clipper.hpp"
+
+// boost
+#pragma GCC diagnostic push
+#include <mapnik/warning_ignore.hpp>
+#include <boost/geometry/algorithms/intersection.hpp>
+#include <boost/geometry/algorithms/unique.hpp>
+#pragma GCC diagnostic pop
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+namespace detail
+{
+
+inline ClipperLib::PolyFillType get_angus_fill_type(polygon_fill_type type)
+{
+ switch (type)
+ {
+ case polygon_fill_type_max:
+ case even_odd_fill:
+ return ClipperLib::pftEvenOdd;
+ case non_zero_fill:
+ return ClipperLib::pftNonZero;
+ case positive_fill:
+ return ClipperLib::pftPositive;
+ case negative_fill:
+ return ClipperLib::pftNegative;
+ }
+}
+
+
+inline void process_polynode_branch(ClipperLib::PolyNode* polynode,
+ mapnik::geometry::multi_polygon<std::int64_t> & mp,
+ double area_threshold)
+{
+ mapnik::geometry::polygon<std::int64_t> polygon;
+ polygon.set_exterior_ring(std::move(polynode->Contour));
+ if (polygon.exterior_ring.size() > 2) // Throw out invalid polygons
+ {
+ double outer_area = ClipperLib::Area(polygon.exterior_ring);
+ if (std::abs(outer_area) >= area_threshold)
+ {
+ // The view transform inverts the y axis so this should be positive still despite now
+ // being clockwise for the exterior ring. If it is not lets invert it.
+ if (outer_area < 0)
+ {
+ std::reverse(polygon.exterior_ring.begin(), polygon.exterior_ring.end());
+ }
+
+ // children of exterior ring are always interior rings
+ for (auto * ring : polynode->Childs)
+ {
+ if (ring->Contour.size() < 3)
+ {
+ continue; // Throw out invalid holes
+ }
+ double inner_area = ClipperLib::Area(ring->Contour);
+ if (std::abs(inner_area) < area_threshold)
+ {
+ continue;
+ }
+
+ if (inner_area > 0)
+ {
+ std::reverse(ring->Contour.begin(), ring->Contour.end());
+ }
+ polygon.add_hole(std::move(ring->Contour));
+ }
+ mp.push_back(std::move(polygon));
+ }
+ }
+ for (auto * ring : polynode->Childs)
+ {
+ for (auto * sub_ring : ring->Childs)
+ {
+ process_polynode_branch(sub_ring, mp, area_threshold);
+ }
+ }
+}
+
+} // end ns detail
+
+template <typename NextProcessor>
+class geometry_clipper
+{
+private:
+ NextProcessor & next_;
+ mapnik::box2d<int> const& tile_clipping_extent_;
+ double area_threshold_;
+ bool strictly_simple_;
+ bool multi_polygon_union_;
+ polygon_fill_type fill_type_;
+ bool process_all_rings_;
+public:
+ geometry_clipper(mapnik::box2d<int> const& tile_clipping_extent,
+ double area_threshold,
+ bool strictly_simple,
+ bool multi_polygon_union,
+ polygon_fill_type fill_type,
+ bool process_all_rings,
+ NextProcessor & next) :
+ next_(next),
+ tile_clipping_extent_(tile_clipping_extent),
+ area_threshold_(area_threshold),
+ strictly_simple_(strictly_simple),
+ multi_polygon_union_(multi_polygon_union),
+ fill_type_(fill_type),
+ process_all_rings_(process_all_rings)
+ {
+ }
+
+ void operator() (mapnik::geometry::geometry_empty &)
+ {
+ return;
+ }
+
+ void operator() (mapnik::geometry::point<std::int64_t> & geom)
+ {
+ next_(geom);
+ }
+
+ void operator() (mapnik::geometry::multi_point<std::int64_t> & geom)
+ {
+ // Here we remove repeated points from multi_point
+ auto last = std::unique(geom.begin(), geom.end());
+ geom.erase(last, geom.end());
+ next_(geom);
+ }
+
+ void operator() (mapnik::geometry::geometry_collection<std::int64_t> & geom)
+ {
+ for (auto & g : geom)
+ {
+ mapnik::util::apply_visitor((*this), g);
+ }
+ }
+
+ void operator() (mapnik::geometry::line_string<std::int64_t> & geom)
+ {
+ boost::geometry::unique(geom);
+ if (geom.size() < 2)
+ {
+ return;
+ }
+ //std::deque<mapnik::geometry::line_string<int64_t>> result;
+ mapnik::geometry::multi_line_string<int64_t> result;
+ mapnik::geometry::linear_ring<std::int64_t> clip_box;
+ clip_box.reserve(5);
+ 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())
+ {
+ return;
+ }
+ next_(result);
+ }
+
+ void operator() (mapnik::geometry::multi_line_string<std::int64_t> & geom)
+ {
+ if (geom.empty())
+ {
+ return;
+ }
+
+ mapnik::geometry::linear_ring<std::int64_t> clip_box;
+ clip_box.reserve(5);
+ 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::unique(geom);
+ mapnik::geometry::multi_line_string<int64_t> results;
+ for (auto const& line : geom)
+ {
+ if (line.size() < 2)
+ {
+ continue;
+ }
+ boost::geometry::intersection(clip_box, line, results);
+ }
+ if (results.empty())
+ {
+ return;
+ }
+ next_(results);
+ }
+
+ void operator() (mapnik::geometry::polygon<std::int64_t> & geom)
+ {
+ if ((geom.exterior_ring.size() < 3) && !process_all_rings_)
+ {
+ return;
+ }
+
+ double clean_distance = 1.415;
+
+ // Prepare the clipper object
+ ClipperLib::Clipper clipper;
+
+ if (strictly_simple_)
+ {
+ clipper.StrictlySimple(true);
+ }
+
+ // Start processing on exterior ring
+ // if proces_all_rings is true even if the exterior
+ // ring is invalid we will continue to insert all polygon
+ // rings into the clipper
+ ClipperLib::CleanPolygon(geom.exterior_ring, clean_distance);
+ double outer_area = ClipperLib::Area(geom.exterior_ring);
+ if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
+ {
+ return;
+ }
+
+ // The view transform inverts the y axis so this should be positive still despite now
+ // being clockwise for the exterior ring. If it is not lets invert it.
+ if (outer_area < 0)
+ {
+ std::reverse(geom.exterior_ring.begin(), geom.exterior_ring.end());
+ }
+
+ if (!clipper.AddPath(geom.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
+ {
+ return;
+ }
+
+ for (auto & ring : geom.interior_rings)
+ {
+ if (ring.size() < 3)
+ {
+ continue;
+ }
+ ClipperLib::CleanPolygon(ring, clean_distance);
+ double inner_area = ClipperLib::Area(ring);
+ if (std::abs(inner_area) < area_threshold_)
+ {
+ continue;
+ }
+ // This should be a negative area, the y axis is down, so the ring will be "CCW" rather
+ // then "CW" after the view transform, but if it is not lets reverse it
+ if (inner_area > 0)
+ {
+ std::reverse(ring.begin(), ring.end());
+ }
+ if (!clipper.AddPath(ring, ClipperLib::ptSubject, true))
+ {
+ continue;
+ }
+ }
+
+ // Setup the box for clipping
+ mapnik::geometry::linear_ring<std::int64_t> clip_box;
+ clip_box.reserve(5);
+ 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());
+
+ // Finally add the box we will be using for clipping
+ if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+ {
+ return;
+ }
+
+ ClipperLib::PolyTree polygons;
+ ClipperLib::PolyFillType fill_type = detail::get_angus_fill_type(fill_type_);
+ clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type, ClipperLib::pftEvenOdd);
+ clipper.Clear();
+
+ mapnik::geometry::multi_polygon<std::int64_t> mp;
+
+ for (auto * polynode : polygons.Childs)
+ {
+ detail::process_polynode_branch(polynode, mp, area_threshold_);
+ }
+
+ if (mp.empty())
+ {
+ return;
+ }
+ next_(mp);
+ }
+
+ void operator() (mapnik::geometry::multi_polygon<std::int64_t> & geom)
+ {
+ if (geom.empty())
+ {
+ return;
+ }
+
+ double clean_distance = 1.415;
+ mapnik::geometry::linear_ring<std::int64_t> clip_box;
+ clip_box.reserve(5);
+ 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());
+
+ mapnik::geometry::multi_polygon<std::int64_t> mp;
+
+ ClipperLib::Clipper clipper;
+
+ if (strictly_simple_)
+ {
+ clipper.StrictlySimple(true);
+ }
+
+ ClipperLib::PolyFillType fill_type = detail::get_angus_fill_type(fill_type_);
+
+ if (multi_polygon_union_)
+ {
+ for (auto & poly : geom)
+ {
+ // Below we attempt to skip processing of all interior rings if the exterior
+ // ring fails a variety of sanity checks for size and validity for AddPath
+ // When `process_all_rings_=true` this optimization is disabled. This is needed when
+ // the ring order of input polygons is potentially incorrect and where the
+ // "exterior_ring" might actually be an incorrectly classified exterior ring.
+ if (poly.exterior_ring.size() < 3 && !process_all_rings_)
+ {
+ continue;
+ }
+ ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
+ double outer_area = ClipperLib::Area(poly.exterior_ring);
+ if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
+ {
+ continue;
+ }
+ // The view transform inverts the y axis so this should be positive still despite now
+ // being clockwise for the exterior ring. If it is not lets invert it.
+ if (outer_area < 0)
+ {
+ std::reverse(poly.exterior_ring.begin(), poly.exterior_ring.end());
+ }
+ if (!clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
+ {
+ continue;
+ }
+
+ for (auto & ring : poly.interior_rings)
+ {
+ if (ring.size() < 3)
+ {
+ continue;
+ }
+ ClipperLib::CleanPolygon(ring, clean_distance);
+ double inner_area = ClipperLib::Area(ring);
+ if (std::abs(inner_area) < area_threshold_)
+ {
+ continue;
+ }
+ // This should be a negative area, the y axis is down, so the ring will be "CCW" rather
+ // then "CW" after the view transform, but if it is not lets reverse it
+ if (inner_area > 0)
+ {
+ std::reverse(ring.begin(), ring.end());
+ }
+ clipper.AddPath(ring, ClipperLib::ptSubject, true);
+ }
+ }
+ if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+ {
+ return;
+ }
+ ClipperLib::PolyTree polygons;
+ clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type, ClipperLib::pftEvenOdd);
+ clipper.Clear();
+
+ for (auto * polynode : polygons.Childs)
+ {
+ detail::process_polynode_branch(polynode, mp, area_threshold_);
+ }
+ }
+ else
+ {
+ for (auto & poly : geom)
+ {
+ // Below we attempt to skip processing of all interior rings if the exterior
+ // ring fails a variety of sanity checks for size and validity for AddPath
+ // When `process_all_rings_=true` this optimization is disabled. This is needed when
+ // the ring order of input polygons is potentially incorrect and where the
+ // "exterior_ring" might actually be an incorrectly classified exterior ring.
+ if (poly.exterior_ring.size() < 3 && !process_all_rings_)
+ {
+ continue;
+ }
+ ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
+ double outer_area = ClipperLib::Area(poly.exterior_ring);
+ if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
+ {
+ continue;
+ }
+ // The view transform inverts the y axis so this should be positive still despite now
+ // being clockwise for the exterior ring. If it is not lets invert it.
+ if (outer_area < 0)
+ {
+ std::reverse(poly.exterior_ring.begin(), poly.exterior_ring.end());
+ }
+ if (!clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
+ {
+ continue;
+ }
+ for (auto & ring : poly.interior_rings)
+ {
+ if (ring.size() < 3)
+ {
+ continue;
+ }
+ ClipperLib::CleanPolygon(ring, clean_distance);
+ double inner_area = ClipperLib::Area(ring);
+ if (std::abs(inner_area) < area_threshold_)
+ {
+ continue;
+ }
+ // This should be a negative area, the y axis is down, so the ring will be "CCW" rather
+ // then "CW" after the view transform, but if it is not lets reverse it
+ if (inner_area > 0)
+ {
+ std::reverse(ring.begin(), ring.end());
+ }
+ if (!clipper.AddPath(ring, ClipperLib::ptSubject, true))
+ {
+ continue;
+ }
+ }
+ if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+ {
+ return;
+ }
+ ClipperLib::PolyTree polygons;
+ clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type, ClipperLib::pftEvenOdd);
+ clipper.Clear();
+
+ for (auto * polynode : polygons.Childs)
+ {
+ detail::process_polynode_branch(polynode, mp, area_threshold_);
+ }
+ }
+ }
+
+ if (mp.empty())
+ {
+ return;
+ }
+ next_(mp);
+ }
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_GEOMETRY_CLIPPER_H__
diff --git a/src/vector_tile_geometry_decoder.cpp b/src/vector_tile_geometry_decoder.cpp
new file mode 100644
index 0000000..5bbd2b9
--- /dev/null
+++ b/src/vector_tile_geometry_decoder.cpp
@@ -0,0 +1,20 @@
+#include "vector_tile_geometry_decoder.hpp"
+#include "vector_tile_geometry_decoder.ipp"
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+// Geometry classes
+template class GeometryPBF<double>;
+template class GeometryPBF<std::int64_t>;
+
+// decode geometry
+template mapnik::geometry::geometry<double> decode_geometry(GeometryPBF<double> & paths, int32_t geom_type, unsigned version);
+template mapnik::geometry::geometry<std::int64_t> decode_geometry(GeometryPBF<std::int64_t> & paths, int32_t geom_type, unsigned version);
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_geometry_decoder.hpp b/src/vector_tile_geometry_decoder.hpp
index e934ca4..a7a2254 100644
--- a/src/vector_tile_geometry_decoder.hpp
+++ b/src/vector_tile_geometry_decoder.hpp
@@ -1,64 +1,45 @@
#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
#define __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+//protozero
#include <protozero/pbf_reader.hpp>
+//mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
#include <mapnik/util/is_clockwise.hpp>
-//std
-#include <algorithm>
-
#if defined(DEBUG)
#include <mapnik/debug.hpp>
#endif
-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
-template <typename ValueType>
-class Geometry {
-
-public:
- inline explicit Geometry(vector_tile::Tile_Feature const& f,
- ValueType tile_x, ValueType 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(ValueType & rx, ValueType & ry, std::uint32_t & len);
+//std
+#include <algorithm>
+#include <cmath>
+#include <stdexcept>
-private:
- vector_tile::Tile_Feature const& f_;
- double scale_x_;
- double scale_y_;
- uint32_t k;
- uint32_t geoms_;
- uint8_t cmd;
- uint32_t length;
- ValueType x, y;
- ValueType ox, oy;
-};
+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
-template <typename ValueType>
-class GeometryPBF {
-
+template <typename T>
+class GeometryPBF
+{
public:
- inline explicit GeometryPBF(std::pair< protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > const& geo_iterator,
- ValueType tile_x, ValueType tile_y,
- double scale_x, double scale_y);
+ using value_type = T;
+ using pbf_itr = std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator >;
+
+ explicit GeometryPBF(pbf_itr const& geo_iterator,
+ value_type tile_x,
+ value_type tile_y,
+ double scale_x,
+ double scale_y);
enum command : uint8_t
{
@@ -68,494 +49,50 @@ public:
close = 7
};
- inline command next(ValueType & rx, ValueType & ry, std::uint32_t & len);
-
-private:
- std::pair< protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > geo_iterator_;
- double scale_x_;
- double scale_y_;
- uint8_t cmd;
- std::uint32_t length;
- ValueType x, y;
- ValueType ox, oy;
-};
-
-template <typename ValueType>
-Geometry<ValueType>::Geometry(vector_tile::Tile_Feature const& f,
- ValueType tile_x, ValueType tile_y,
- double scale_x, double scale_y)
- : f_(f),
- scale_x_(scale_x),
- scale_y_(scale_y),
- k(0),
- geoms_(f_.geometry_size()),
- cmd(1),
- length(0),
- x(tile_x), y(tile_y),
- ox(0), oy(0) {}
-
-template <typename ValueType>
-typename Geometry<ValueType>::command Geometry<ValueType>::next(ValueType & rx, ValueType & ry, std::uint32_t & len)
-{
- if (k < geoms_)
+ bool scaling_reversed_orientation() const
{
- if (length == 0)
- {
- uint32_t cmd_length = static_cast<uint32_t>(f_.geometry(k++));
- cmd = cmd_length & 0x7;
- len = length = cmd_length >> 3;
- }
-
- --length;
-
- if (cmd == move_to || cmd == line_to)
- {
- int32_t dx = f_.geometry(k++);
- int32_t dy = f_.geometry(k++);
- dx = ((dx >> 1) ^ (-(dx & 1)));
- dy = ((dy >> 1) ^ (-(dy & 1)));
- x += static_cast<ValueType>(static_cast<double>(dx) / scale_x_);
- y += static_cast<ValueType>(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;
+ return (scale_x_ * scale_y_) < 0;
}
-}
-template <typename ValueType>
-GeometryPBF<ValueType>::GeometryPBF(std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > const& geo_iterator,
- ValueType tile_x, ValueType 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) {}
-
-template <typename ValueType>
-typename GeometryPBF<ValueType>::command GeometryPBF<ValueType>::next(ValueType & rx, ValueType & ry, std::uint32_t & len)
-{
- if (geo_iterator_.first != geo_iterator_.second)
+ uint32_t get_length() const
{
- if (length == 0)
- {
- uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
- cmd = cmd_length & 0x7;
- len = 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<ValueType>(static_cast<double>(dx) / scale_x_);
- y += static_cast<ValueType>(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;
+ return length;
}
-}
-namespace detail {
+ command point_next(value_type & rx, value_type & ry);
+ command line_next(value_type & rx, value_type & ry, bool skip_lineto_zero);
+ command ring_next(value_type & rx, value_type & ry, bool skip_lineto_zero);
-template <typename ValueType, typename T>
-void decode_point(mapnik::geometry::geometry<ValueType> & geom, T & paths, mapnik::box2d<double> const& bbox)
-{
- typename T::command cmd;
- ValueType x1, y1;
- mapnik::geometry::multi_point<ValueType> mp;
- bool first = true;
- std::uint32_t len;
- while ((cmd = paths.next(x1, y1, len)) != T::end)
- {
- // TODO: consider profiling and trying to optimize this further
- // when all points are within the bbox filter then the `mp.reserve` should be
- // perfect, but when some points are thrown out we will allocate more than needed
- // the "all points intersect" case I think is going to be more common/important
- // however worth a future look to see if the "some or few points intersect" can be optimized
- if (first)
- {
- first = false;
- mp.reserve(len);
- }
- if (!bbox.intersects(x1,y1))
- {
- continue;
- }
- mp.emplace_back(x1,y1);
- }
- std::size_t num_points = mp.size();
- #if defined(DEBUG)
- if (num_points > 0 && len != num_points) {
- // BUG: https://github.com/mapbox/mapnik-vector-tile/issues/144
- MAPNIK_LOG_ERROR(decode_point) << "warning: encountered incorrectly encoded multipoint with " << num_points << " points but only " << len << " repeated commands";
- }
- #endif
- if (num_points == 0)
- {
- geom = mapnik::geometry::geometry_empty();
- }
- else if (num_points == 1)
- {
- geom = std::move(mp[0]);
- }
- else if (num_points > 1)
- {
- // return multipoint
- geom = std::move(mp);
- }
-}
-
-template <typename ValueType, typename T>
-void decode_linestring(mapnik::geometry::geometry<ValueType> & geom, T & paths, mapnik::box2d<double> const& bbox)
-{
- typename T::command cmd;
- ValueType x1, y1;
- mapnik::geometry::multi_line_string<ValueType> multi_line;
- multi_line.emplace_back();
- bool first = true;
- bool first_line_to = true;
- std::uint32_t len;
- #if defined(DEBUG)
- std::uint32_t pre_len;
- #endif
- mapnik::box2d<double> part_env;
- while ((cmd = paths.next(x1, y1, len)) != T::end)
- {
- if (cmd == T::move_to)
- {
- if (first)
- {
- first = false;
- }
- else
- {
- #if defined(DEBUG)
- if (multi_line.back().size() > 0 && pre_len != multi_line.back().size())
- {
- MAPNIK_LOG_ERROR(decode_linestring) << "warning: encountered incorrectly encoded line with " << multi_line.back().size() << " points but only " << pre_len << " repeated commands";
- }
- #endif
- first_line_to = true;
- if (!bbox.intersects(part_env))
- {
- // remove last line
- multi_line.pop_back();
- }
- // add fresh line to start adding to
- multi_line.emplace_back();
- }
- part_env.init(x1,y1,x1,y1);
- }
- else if (first_line_to && cmd == T::line_to)
- {
- first_line_to = false;
- multi_line.back().reserve(len+1);
- #if defined(DEBUG)
- pre_len = len+1;
- #endif
- }
- if (!first)
- {
- part_env.expand_to_include(x1,y1);
- }
- multi_line.back().add_coord(x1,y1);
- }
- if (!bbox.intersects(part_env))
- {
- // remove last line
- multi_line.pop_back();
- }
- std::size_t num_lines = multi_line.size();
- if (num_lines == 0)
- {
- geom = mapnik::geometry::geometry_empty();
- }
- else if (num_lines == 1)
- {
- auto itr = std::make_move_iterator(multi_line.begin());
- if (itr->size() > 1)
- {
- geom = std::move(*itr);
- }
- }
- else if (num_lines > 1)
- {
- geom = std::move(multi_line);
- }
-}
-
-template <typename ValueType, typename T>
-void read_rings(std::vector<mapnik::geometry::linear_ring<ValueType> > & rings,
- T & paths, mapnik::box2d<double> const& bbox)
-{
- typename T::command cmd;
- ValueType x1, y1;
- rings.emplace_back();
- ValueType x2, y2;
- bool first = true;
- bool first_line_to = true;
- std::uint32_t len;
+private:
+ std::pair< protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > geo_iterator_;
+ double scale_x_;
+ double scale_y_;
+ value_type x, y;
+ value_type ox, oy;
+ uint32_t length;
+ uint8_t cmd;
#if defined(DEBUG)
- std::uint32_t pre_len;
+public:
+ bool already_had_error;
#endif
- mapnik::box2d<double> part_env;
- while ((cmd = paths.next(x1, y1, len)) != T::end)
- {
- if (cmd == T::move_to)
- {
- x2 = x1;
- y2 = y1;
- if (first)
- {
- first = false;
- }
- else
- {
- #if defined(DEBUG)
- // off by one is expected/okay in rare cases
- if (rings.back().size() > 0 && (rings.back().size() > pre_len || std::fabs(pre_len - rings.back().size()) > 1))
- {
- MAPNIK_LOG_ERROR(read_rings) << "warning: encountered incorrectly encoded ring with " << rings.back().size() << " points but " << pre_len << " repeated commands";
- }
- #endif
- first_line_to = true;
- if (!bbox.intersects(part_env))
- {
- // remove last ring
- rings.pop_back();
- }
- rings.emplace_back();
- }
- part_env.init(x1,y1,x1,y1);
- }
- else if (first_line_to && cmd == T::line_to)
- {
- first_line_to = false;
- rings.back().reserve(len+2);
- #if defined(DEBUG)
- pre_len = len+2;
- #endif
- }
- else if (cmd == T::close)
- {
- auto & ring = rings.back();
- if (ring.size() > 2 && !(ring.back().x == x2 && ring.back().y == y2))
- {
- ring.add_coord(x2,y2);
- }
- continue;
- }
- if (!first)
- {
- part_env.expand_to_include(x1,y1);
- }
- rings.back().add_coord(x1,y1);
- }
- if (!bbox.intersects(part_env))
- {
- // remove last ring
- rings.pop_back();
- }
-}
-
-template <typename ValueType, typename T>
-void decode_polygons(mapnik::geometry::geometry<ValueType> & 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 == 0)
- {
- geom = mapnik::geometry::geometry_empty();
- }
- else if (num_rings == 1)
- {
- if (rings_itr->size() < 4)
- {
- return;
- }
- if (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<ValueType> poly;
- poly.set_exterior_ring(std::move(*rings_itr));
- geom = std::move(poly);
- }
- else
- {
- mapnik::geometry::multi_polygon<ValueType> multi_poly;
- bool first = true;
- bool is_clockwise = true;
- for (; rings_itr != rings_end; ++rings_itr)
- {
- if (rings_itr->size() < 4)
- {
- continue; // skip degenerate rings
- }
- if (first)
- {
- 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)
- {
- // 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 (is_clockwise == 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)
- {
- // 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
- {
- if (is_clockwise)
- {
- // 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 == 1)
- {
- auto itr = std::make_move_iterator(multi_poly.begin());
- geom = mapnik::geometry::polygon<ValueType>(std::move(*itr));
- }
- else
- {
- geom = std::move(multi_poly);
- }
- }
-}
-
-} // ns detail
+};
-template <typename ValueType, typename T>
-inline mapnik::geometry::geometry<ValueType> decode_geometry(T & paths, int32_t geom_type, mapnik::box2d<double> const& bbox)
-{
- mapnik::geometry::geometry<ValueType> geom; // output geometry
- switch (geom_type)
- {
- case vector_tile::Tile_GeomType_POINT:
- {
- detail::decode_point<ValueType, T>(geom, paths, bbox);
- break;
- }
- case vector_tile::Tile_GeomType_LINESTRING:
- {
- detail::decode_linestring<ValueType, T>(geom, paths, bbox);
- break;
- }
- case vector_tile::Tile_GeomType_POLYGON:
- {
- std::vector<mapnik::geometry::linear_ring<ValueType> > rings;
- detail::read_rings<ValueType, T>(rings, paths, bbox);
- if (rings.empty())
- {
- geom = mapnik::geometry::geometry_empty();
- }
- else
- {
- detail::decode_polygons(geom, rings);
- }
- break;
- }
- case vector_tile::Tile_GeomType_UNKNOWN:
- default:
- {
- throw std::runtime_error("unhandled geometry type during decoding");
- break;
- }
- }
- return geom;
-}
+template <typename T>
+MAPNIK_VECTOR_INLINE mapnik::geometry::geometry<typename T::value_type> decode_geometry(T & paths,
+ int32_t geom_type,
+ unsigned version,
+ mapnik::box2d<double> const& bbox);
-// For back compatibility in tests / for cases where performance is not critical
-// TODO: consider removing and always requiring bbox arg
-template <typename ValueType, typename T>
-inline mapnik::geometry::geometry<ValueType> decode_geometry(T & paths, int32_t geom_type)
-{
+template <typename T>
+MAPNIK_VECTOR_INLINE mapnik::geometry::geometry<typename T::value_type> decode_geometry(T & paths, int32_t geom_type, unsigned version);
- mapnik::box2d<double> bbox(std::numeric_limits<double>::lowest(),
- std::numeric_limits<double>::lowest(),
- std::numeric_limits<double>::max(),
- std::numeric_limits<double>::max());
- return decode_geometry<ValueType>(paths,geom_type,bbox);
-}
+} // end ns vector_tile_impl
-}} // end ns
+} // end ns mapnik
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_geometry_decoder.ipp"
+#endif
#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_DECODER_H__
diff --git a/src/vector_tile_geometry_decoder.ipp b/src/vector_tile_geometry_decoder.ipp
new file mode 100644
index 0000000..5a182f8
--- /dev/null
+++ b/src/vector_tile_geometry_decoder.ipp
@@ -0,0 +1,902 @@
+//protozero
+#include <protozero/pbf_reader.hpp>
+
+//mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/util/is_clockwise.hpp>
+#if defined(DEBUG)
+#include <mapnik/debug.hpp>
+#endif
+
+//std
+#include <algorithm>
+#include <cmath>
+#include <stdexcept>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+namespace detail
+{
+
+template <typename value_type>
+inline void move_cursor(value_type & x, value_type & y, std::int32_t dx, std::int32_t dy, double scale_x_, double scale_y_)
+{
+ x += static_cast<value_type>(std::round(static_cast<double>(dx) / scale_x_));
+ y += static_cast<value_type>(std::round(static_cast<double>(dy) / scale_y_));
+}
+
+template <>
+inline void move_cursor<double>(double & x, double & y, std::int32_t dx, std::int32_t dy, double scale_x_, double scale_y_)
+{
+ x += static_cast<double>(dx) / scale_x_;
+ y += static_cast<double>(dy) / scale_y_;
+}
+
+template <typename T>
+void decode_point(mapnik::geometry::geometry<typename T::value_type> & geom,
+ T & paths,
+ mapnik::box2d<double> const& bbox)
+{
+ using value_type = typename T::value_type;
+ typename T::command cmd;
+ value_type x1, y1;
+ mapnik::geometry::multi_point<value_type> mp;
+ #if defined(DEBUG)
+ std::uint32_t previous_len = 0;
+ #endif
+ // Find first moveto inside bbox and then reserve points from size of geometry.
+ while (true)
+ {
+ cmd = paths.point_next(x1, y1);
+ if (cmd == T::end)
+ {
+ geom = std::move(mapnik::geometry::geometry_empty());
+ return;
+ }
+ else if (bbox.intersects(x1,y1))
+ {
+ #if defined(DEBUG)
+ if (previous_len <= paths.get_length() && !paths.already_had_error)
+ {
+ MAPNIK_LOG_WARN(decode_point) << "warning: encountered POINT geometry that might have MOVETO commands repeated that could be fewer commands";
+ paths.already_had_error = true;
+ }
+ previous_len = paths.get_length();
+ #endif
+ mp.reserve(paths.get_length() + 1);
+ mp.emplace_back(x1,y1);
+ break;
+ }
+ }
+ while ((cmd = paths.point_next(x1, y1)) != T::end)
+ {
+ #if defined(DEBUG)
+ if (previous_len <= paths.get_length() && !paths.already_had_error)
+ {
+ MAPNIK_LOG_WARN(decode_point) << "warning: encountered POINT geometry that might have MOVETO commands repeated that could be fewer commands";
+ paths.already_had_error = true;
+ }
+ previous_len = paths.get_length();
+ #endif
+ // TODO: consider profiling and trying to optimize this further
+ // when all points are within the bbox filter then the `mp.reserve` should be
+ // perfect, but when some points are thrown out we will allocate more than needed
+ // the "all points intersect" case I think is going to be more common/important
+ // however worth a future look to see if the "some or few points intersect" can be optimized
+ if (!bbox.intersects(x1,y1))
+ {
+ continue;
+ }
+ mp.emplace_back(x1,y1);
+ }
+ std::size_t num_points = mp.size();
+ if (num_points == 0)
+ {
+ geom = std::move(mapnik::geometry::geometry_empty());
+ }
+ 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<typename T::value_type> & geom,
+ T & paths,
+ mapnik::box2d<double> const& bbox,
+ unsigned version)
+{
+ using value_type = typename T::value_type;
+ typename T::command cmd;
+ value_type x0, y0;
+ value_type x1, y1;
+ mapnik::geometry::multi_line_string<value_type> multi_line;
+ #if defined(DEBUG)
+ std::uint32_t previous_len = 0;
+ #endif
+ mapnik::box2d<double> part_env;
+ cmd = paths.line_next(x0, y0, false);
+ if (cmd == T::end)
+ {
+ geom = std::move(mapnik::geometry::geometry_empty());
+ return;
+ }
+ else if (cmd != T::move_to)
+ {
+ throw std::runtime_error("Vector Tile has LINESTRING type geometry where the first command is not MOVETO.");
+ }
+
+ while (true)
+ {
+ cmd = paths.line_next(x1, y1, true);
+ if (cmd != T::line_to)
+ {
+ if (cmd == T::move_to)
+ {
+ if (version == 1)
+ {
+ // Version 1 of the spec wasn't clearly defined and therefore
+ // we shouldn't be strict on the reading of a tile that has two
+ // moveto commands that are repeated, lets ignore the previous moveto.
+ x0 = x1;
+ y0 = y1;
+ continue;
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has LINESTRING type geometry with repeated MOVETO commands.");
+ }
+ }
+ else //cmd == T::end
+ {
+ if (version == 1)
+ {
+ // Version 1 of the spec wasn't clearly defined and therefore
+ // we shouldn't be strict on the reading of a tile that has only a moveto
+ // command. So lets just ignore this moveto command.
+ break;
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has LINESTRING type geometry with a MOVETO command with no LINETO following.");
+ }
+ }
+ }
+ // add fresh line
+ multi_line.emplace_back();
+ auto & line = multi_line.back();
+ // reserve prior
+ line.reserve(paths.get_length() + 2);
+ // add moveto command position
+ line.add_coord(x0, y0);
+ part_env.init(x0, y0, x0, y0);
+ // add first lineto
+ line.add_coord(x1, y1);
+ part_env.expand_to_include(x1, y1);
+ #if defined(DEBUG)
+ previous_len = paths.get_length();
+ #endif
+ while ((cmd = paths.line_next(x1, y1, true)) == T::line_to)
+ {
+ line.add_coord(x1, y1);
+ part_env.expand_to_include(x1, y1);
+ #if defined(DEBUG)
+ if (previous_len <= paths.get_length() && !paths.already_had_error)
+ {
+ MAPNIK_LOG_WARN(decode_linestring) << "warning: encountered LINESTRING geometry that might have LINETO commands repeated that could be fewer commands";
+ paths.already_had_error = true;
+ }
+ previous_len = paths.get_length();
+ #endif
+ }
+ if (!bbox.intersects(part_env))
+ {
+ // remove last linestring
+ multi_line.pop_back();
+ }
+ if (cmd == T::end)
+ {
+ break;
+ }
+ // else we are gauranteed it is a moveto
+ x0 = x1;
+ y0 = y1;
+ }
+
+ std::size_t num_lines = multi_line.size();
+ if (num_lines == 0)
+ {
+ geom = std::move(mapnik::geometry::geometry_empty());
+ }
+ else if (num_lines == 1)
+ {
+ auto itr = std::make_move_iterator(multi_line.begin());
+ if (itr->size() > 1)
+ {
+ geom = std::move(*itr);
+ }
+ }
+ else if (num_lines > 1)
+ {
+ geom = std::move(multi_line);
+ }
+}
+
+template <typename T>
+void read_rings(std::vector<mapnik::geometry::linear_ring<typename T::value_type> > & rings,
+ T & paths,
+ mapnik::box2d<double> const& bbox,
+ unsigned version)
+{
+ using value_type = typename T::value_type;
+ typename T::command cmd;
+ value_type x0, y0;
+ value_type x1, y1;
+ value_type x2, y2;
+ #if defined(DEBUG)
+ std::uint32_t previous_len;
+ #endif
+ mapnik::box2d<double> part_env;
+ cmd = paths.ring_next(x0, y0, false);
+ if (cmd == T::end)
+ {
+ return;
+ }
+ else if (cmd != T::move_to)
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry where the first command is not MOVETO.");
+ }
+
+ while (true)
+ {
+ cmd = paths.ring_next(x1, y1, true);
+ if (cmd != T::line_to)
+ {
+ if (cmd == T::close && version == 1)
+ {
+ // Version 1 of the specification we were not clear on the command requirements for polygons
+ // lets just to recover from this situation.
+ cmd = paths.ring_next(x0, y0, false);
+ if (cmd == T::end)
+ {
+ break;
+ }
+ else if (cmd == T::move_to)
+ {
+ continue;
+ }
+ else if (cmd == T::close)
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a CLOSE.");
+ }
+ else // cmd == T::line_to
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a LINETO.");
+ }
+ }
+ else // cmd == end || cmd == move_to
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry with a MOVETO command with out at least two LINETOs and CLOSE following.");
+ }
+ }
+ #if defined(DEBUG)
+ previous_len = paths.get_length();
+ #endif
+ cmd = paths.ring_next(x2, y2, true);
+ if (cmd != T::line_to)
+ {
+ if (cmd == T::close && version == 1)
+ {
+ // Version 1 of the specification we were not clear on the command requirements for polygons
+ // lets just to recover from this situation.
+ cmd = paths.ring_next(x0, y0, false);
+ if (cmd == T::end)
+ {
+ break;
+ }
+ else if (cmd == T::move_to)
+ {
+ continue;
+ }
+ else if (cmd == T::close)
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a CLOSE.");
+ }
+ else // cmd == T::line_to
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a LINETO.");
+ }
+ }
+ else // cmd == end || cmd == move_to
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry with a MOVETO command with out at least two LINETOs and CLOSE following.");
+ }
+ }
+ // add new ring to start adding to
+ rings.emplace_back();
+ auto & ring = rings.back();
+ // reserve prior
+ ring.reserve(paths.get_length() + 4);
+ // add moveto command position
+ ring.add_coord(x0, y0);
+ part_env.init(x0, y0, x0, y0);
+ // add first lineto
+ ring.add_coord(x1, y1);
+ part_env.expand_to_include(x1, y1);
+ // add second lineto
+ ring.add_coord(x2, y2);
+ part_env.expand_to_include(x2, y2);
+ #if defined(DEBUG)
+ if (previous_len <= paths.get_length() && !paths.already_had_error)
+ {
+ MAPNIK_LOG_WARN(read_rings) << "warning: encountered POLYGON geometry that might have LINETO commands repeated that could be fewer commands";
+ paths.already_had_error = true;
+ }
+ previous_len = paths.get_length();
+ #endif
+ while ((cmd = paths.ring_next(x1, y1, true)) == T::line_to)
+ {
+ ring.add_coord(x1,y1);
+ part_env.expand_to_include(x1,y1);
+ #if defined(DEBUG)
+ if (previous_len <= paths.get_length() && !paths.already_had_error)
+ {
+ MAPNIK_LOG_WARN(read_rings) << "warning: encountered POLYGON geometry that might have LINETO commands repeated that could be fewer commands";
+ paths.already_had_error = true;
+ }
+ previous_len = paths.get_length();
+ #endif
+ }
+ // Make sure we are now on a close command
+ if (cmd != T::close)
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry with a ring not closed by a CLOSE command.");
+ }
+ if (ring.back().x != x0 || ring.back().y != y0)
+ {
+ // If the previous lineto didn't already close the polygon (WHICH IT SHOULD NOT)
+ // close out the polygon ring.
+ ring.add_coord(x0,y0);
+ }
+
+ if (!bbox.intersects(part_env))
+ {
+ // remove last linestring
+ rings.pop_back();
+ }
+
+ cmd = paths.ring_next(x0, y0, false);
+ if (cmd == T::end)
+ {
+ break;
+ }
+ else if (cmd != T::move_to)
+ {
+ if (cmd == T::close)
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a CLOSE.");
+ }
+ else // cmd == T::line_to
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a LINETO.");
+ }
+ }
+ }
+}
+
+template <typename T1, typename T2>
+void decode_polygons(mapnik::geometry::geometry<T1> & geom,
+ T2 && rings,
+ unsigned version,
+ bool scaling_reversed_orientation)
+{
+ using value_type = T1;
+ 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 == 0)
+ {
+ geom = std::move(mapnik::geometry::geometry_empty());
+ }
+ else if (num_rings == 1)
+ {
+ if (rings_itr->size() < 4)
+ {
+ if (version == 1)
+ {
+ geom = std::move(mapnik::geometry::geometry_empty());
+ return;
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has POLYGON has ring with too few points. It is not valid according to v2 of VT spec.");
+ }
+ }
+
+ // We are going to check if the current ring is clockwise, keeping in mind that
+ // the orientation could have flipped due to scaling.
+ if (mapnik::util::is_clockwise(*rings_itr))
+ {
+ if (scaling_reversed_orientation)
+ {
+ // Because the way we scaled changed the orientation of the ring (most likely due to a flipping of axis)
+ // we now have an exterior ring that clockwise. Internally in mapnik we want this to be counter clockwise
+ // so we must flip this.
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ }
+ else
+ {
+ if (version == 1)
+ {
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has POLYGON with first ring clockwise. It is not valid according to v2 of VT spec.");
+ }
+ }
+ }
+ else if (scaling_reversed_orientation) // ring is counter clockwise
+ {
+ // ring is counter clockwise but scaling reversed it.
+ // this means in the vector tile it is incorrectly orientated.
+ if (version == 1)
+ {
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has POLYGON with first ring clockwise. It is not valid according to v2 of VT spec.");
+ }
+ }
+ // else ring is counter clockwise and scaling didn't reverse orientation.
+
+ // return the single polygon without interior rings
+ mapnik::geometry::polygon<value_type> poly;
+ poly.set_exterior_ring(std::move(*rings_itr));
+ geom = std::move(poly);
+ }
+ else if (version == 1)
+ {
+ // Version 1 didn't specify the winding order, so we are forced
+ // to do a best guess. This means assuming the first ring is an exterior
+ // ring and then basing all winding order off that. For this we can
+ // ignore if the scaling reversed the orientation.
+ mapnik::geometry::multi_polygon<value_type> multi_poly;
+ bool first = true;
+ bool first_is_clockwise = false;
+ for (; rings_itr != rings_end; ++rings_itr)
+ {
+ if (rings_itr->size() < 4)
+ {
+ continue;
+ }
+ if (first)
+ {
+ first_is_clockwise = mapnik::util::is_clockwise(*rings_itr);
+ // first ring always exterior and sets all future winding order
+ multi_poly.emplace_back();
+ if (first_is_clockwise)
+ {
+ // 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_is_clockwise == mapnik::util::is_clockwise(*rings_itr))
+ {
+ // hit a new exterior ring, so start a new polygon
+ multi_poly.emplace_back(); // start new polygon
+ if (first_is_clockwise)
+ {
+ // 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
+ {
+ if (first_is_clockwise)
+ {
+ // 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 == 1)
+ {
+ auto itr = std::make_move_iterator(multi_poly.begin());
+ geom = std::move(*itr);
+ }
+ else
+ {
+ geom = std::move(multi_poly);
+ }
+ }
+ else // if (version == 2)
+ {
+ mapnik::geometry::multi_polygon<value_type> multi_poly;
+ bool first = true;
+ for (; rings_itr != rings_end; ++rings_itr)
+ {
+ if (rings_itr->size() < 4)
+ {
+ throw std::runtime_error("Vector Tile has POLYGON has ring with too few points. It is not valid according to v2 of VT spec.");
+ }
+
+ if (first)
+ {
+ if (mapnik::util::is_clockwise(*rings_itr))
+ {
+ if (scaling_reversed_orientation)
+ {
+ // Need to reverse ring so that it is proper going into mapnik.
+ // as mapnik needs CCW exterior rings
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has POLYGON with first ring clockwise. It is not valid according to v2 of VT spec.");
+ }
+ }
+ else if (scaling_reversed_orientation)
+ {
+ throw std::runtime_error("Vector Tile has POLYGON with first ring clockwise. It is not valid according to v2 of VT spec.");
+ }
+ multi_poly.emplace_back();
+ multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+ first = false;
+ }
+ else if (mapnik::util::is_clockwise(*rings_itr)) // interior ring
+ {
+ if (scaling_reversed_orientation)
+ {
+ // This is an exterior ring but needs to be reversed.
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ multi_poly.emplace_back(); // start new polygon
+ multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+ }
+ else
+ {
+ // this is an interior ring
+ multi_poly.back().add_hole(std::move(*rings_itr));
+ }
+ }
+ else
+ {
+ if (scaling_reversed_orientation)
+ {
+ // This is interior ring and it must be reversed.
+ std::reverse(rings_itr->begin(), rings_itr->end());
+ multi_poly.back().add_hole(std::move(*rings_itr));
+ }
+ else
+ {
+ // hit a new exterior ring, so start a new polygon
+ multi_poly.emplace_back(); // start new polygon
+ multi_poly.back().set_exterior_ring(std::move(*rings_itr));
+ }
+ }
+ }
+
+ auto num_poly = multi_poly.size();
+ if (num_poly == 1)
+ {
+ auto itr = std::make_move_iterator(multi_poly.begin());
+ geom = std::move(*itr);
+ }
+ else
+ {
+ geom = std::move(multi_poly);
+ }
+ }
+}
+
+} // end ns detail
+
+template <typename T>
+GeometryPBF<T>::GeometryPBF(pbf_itr const& geo_iterator,
+ value_type tile_x,
+ value_type tile_y,
+ double scale_x,
+ double scale_y)
+ : geo_iterator_(geo_iterator),
+ scale_x_(scale_x),
+ scale_y_(scale_y),
+ x(tile_x),
+ y(tile_y),
+ ox(0),
+ oy(0),
+ length(0),
+ cmd(move_to)
+{
+ #if defined(DEBUG)
+ already_had_error = false;
+ #endif
+}
+
+template <typename T>
+typename GeometryPBF<T>::command GeometryPBF<T>::point_next(value_type & rx, value_type & ry)
+{
+ if (length == 0)
+ {
+ if (geo_iterator_.first != geo_iterator_.second)
+ {
+ uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
+ cmd = cmd_length & 0x7;
+ length = cmd_length >> 3;
+ if (cmd == move_to)
+ {
+ if (length == 0)
+ {
+ throw std::runtime_error("Vector Tile has POINT geometry with a MOVETO command that has a command count of zero");
+ }
+ }
+ else
+ {
+ if (cmd == line_to)
+ {
+ throw std::runtime_error("Vector Tile has POINT type geometry with a LINETO command.");
+ }
+ else if (cmd == close)
+ {
+ throw std::runtime_error("Vector Tile has POINT type geometry with a CLOSE command.");
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has POINT type geometry with an unknown command.");
+ }
+ }
+ }
+ else
+ {
+ return end;
+ }
+ }
+
+ --length;
+ // It is possible for the next to lines to throw because we can not check the length
+ // of the buffer to ensure that it is long enough.
+ // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+ // "end of buffer exception"
+ // While this error message is not verbose a try catch here would slow down processing.
+ int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+ rx = x;
+ ry = y;
+ return move_to;
+}
+
+template <typename T>
+typename GeometryPBF<T>::command GeometryPBF<T>::line_next(value_type & rx,
+ value_type & ry,
+ bool skip_lineto_zero)
+{
+ if (length == 0)
+ {
+ if (geo_iterator_.first != geo_iterator_.second)
+ {
+ uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
+ cmd = cmd_length & 0x7;
+ length = cmd_length >> 3;
+ if (cmd == move_to)
+ {
+ if (length != 1)
+ {
+ throw std::runtime_error("Vector Tile has LINESTRING with a MOVETO command that is given more then one pair of parameters or not enough parameters are provided");
+ }
+ --length;
+ // It is possible for the next to lines to throw because we can not check the length
+ // of the buffer to ensure that it is long enough.
+ // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+ // "end of buffer exception"
+ // While this error message is not verbose a try catch here would slow down processing.
+ int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+ rx = x;
+ ry = y;
+ return move_to;
+ }
+ else if (cmd == line_to)
+ {
+ if (length == 0)
+ {
+ throw std::runtime_error("Vector Tile has geometry with LINETO command that is not followed by a proper number of parameters");
+ }
+ }
+ else
+ {
+ if (cmd == close)
+ {
+ throw std::runtime_error("Vector Tile has LINESTRING type geometry with a CLOSE command.");
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has LINESTRING type geometry with an unknown command.");
+ }
+ }
+ }
+ else
+ {
+ return end;
+ }
+ }
+
+ --length;
+ // It is possible for the next to lines to throw because we can not check the length
+ // of the buffer to ensure that it is long enough.
+ // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+ // "end of buffer exception"
+ // While this error message is not verbose a try catch here would slow down processing.
+ int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ if (skip_lineto_zero && dx == 0 && dy == 0)
+ {
+ // We are going to skip this vertex as the point doesn't move call line_next again
+ return line_next(rx, ry, true);
+ }
+ detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+ rx = x;
+ ry = y;
+ return line_to;
+}
+
+template <typename T>
+typename GeometryPBF<T>::command GeometryPBF<T>::ring_next(value_type & rx,
+ value_type & ry,
+ bool skip_lineto_zero)
+{
+ if (length == 0)
+ {
+ if (geo_iterator_.first != geo_iterator_.second)
+ {
+ uint32_t cmd_length = static_cast<uint32_t>(*geo_iterator_.first++);
+ cmd = cmd_length & 0x7;
+ length = cmd_length >> 3;
+ if (cmd == move_to)
+ {
+ if (length != 1)
+ {
+ throw std::runtime_error("Vector Tile has POLYGON with a MOVETO command that is given more then one pair of parameters or not enough parameters are provided");
+ }
+ --length;
+ // It is possible for the next to lines to throw because we can not check the length
+ // of the buffer to ensure that it is long enough.
+ // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+ // "end of buffer exception"
+ // While this error message is not verbose a try catch here would slow down processing.
+ int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+ rx = x;
+ ry = y;
+ ox = x;
+ oy = y;
+ return move_to;
+ }
+ else if (cmd == line_to)
+ {
+ if (length == 0)
+ {
+ throw std::runtime_error("Vector Tile has geometry with LINETO command that is not followed by a proper number of parameters");
+ }
+ }
+ else if (cmd == close)
+ {
+ // Just set length in case a close command provides an invalid number here.
+ // While we could throw because V2 of the spec declares it incorrect, this is not
+ // difficult to fix and has no effect on the results.
+ length = 0;
+ rx = ox;
+ ry = oy;
+ return close;
+ }
+ else
+ {
+ throw std::runtime_error("Vector Tile has POLYGON type geometry with an unknown command.");
+ }
+ }
+ else
+ {
+ return end;
+ }
+ }
+
+ --length;
+ // It is possible for the next to lines to throw because we can not check the length
+ // of the buffer to ensure that it is long enough.
+ // If an exception occurs it will likely be a end_of_buffer_exception with the text:
+ // "end of buffer exception"
+ // While this error message is not verbose a try catch here would slow down processing.
+ int32_t dx = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_iterator_.first++));
+ if (skip_lineto_zero && dx == 0 && dy == 0)
+ {
+ // We are going to skip this vertex as the point doesn't move call ring_next again
+ return ring_next(rx, ry, true);
+ }
+ detail::move_cursor(x, y, dx, dy, scale_x_, scale_y_);
+ rx = x;
+ ry = y;
+ return line_to;
+}
+
+template <typename T>
+MAPNIK_VECTOR_INLINE mapnik::geometry::geometry<typename T::value_type> decode_geometry(T & paths,
+ int32_t geom_type,
+ unsigned version,
+ mapnik::box2d<double> const& bbox)
+{
+ using value_type = typename T::value_type;
+ mapnik::geometry::geometry<value_type> geom; // output geometry
+ switch (geom_type)
+ {
+ case Geometry_Type::POINT:
+ {
+ detail::decode_point<T>(geom, paths, bbox);
+ break;
+ }
+ case Geometry_Type::LINESTRING:
+ {
+ detail::decode_linestring<T>(geom, paths, bbox, version);
+ break;
+ }
+ case Geometry_Type::POLYGON:
+ {
+ std::vector<mapnik::geometry::linear_ring<value_type> > rings;
+ detail::read_rings<T>(rings, paths, bbox, version);
+ if (rings.empty())
+ {
+ geom = std::move(mapnik::geometry::geometry_empty());
+ }
+ else
+ {
+ detail::decode_polygons(geom, rings, version, paths.scaling_reversed_orientation());
+ }
+ break;
+ }
+ case Geometry_Type::UNKNOWN:
+ default:
+ {
+ // This was changed to not throw as unknown according to v2 of spec can simply be ignored and doesn't require
+ // it failing the processing
+ geom = std::move(mapnik::geometry::geometry_empty());
+ break;
+ }
+ }
+ return std::move(geom);
+}
+
+template <typename T>
+MAPNIK_VECTOR_INLINE mapnik::geometry::geometry<typename T::value_type> decode_geometry(T & paths, int32_t geom_type, unsigned version)
+{
+ mapnik::box2d<double> bbox(std::numeric_limits<double>::lowest(),
+ std::numeric_limits<double>::lowest(),
+ std::numeric_limits<double>::max(),
+ std::numeric_limits<double>::max());
+ return decode_geometry(paths, geom_type, version, bbox);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_geometry_encoder.hpp b/src/vector_tile_geometry_encoder.hpp
deleted file mode 100644
index ed2c250..0000000
--- a/src/vector_tile_geometry_encoder.hpp
+++ /dev/null
@@ -1,215 +0,0 @@
-#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
-#define __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
-
-// vector tile
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
-
-#include <mapnik/geometry.hpp>
-#include "vector_tile_config.hpp"
-#include <protozero/varint.hpp>
-
-#include <cstdlib>
-#include <cmath>
-#include <sstream>
-#include <iostream>
-
-namespace mapnik { namespace vector_tile_impl {
-
-inline bool encode_geometry(mapnik::geometry::point<std::int64_t> const& pt,
- vector_tile::Tile_Feature & current_feature,
- int32_t & start_x,
- int32_t & start_y)
-{
- current_feature.add_geometry(9); // move_to | (1 << 3)
- int32_t dx = pt.x - start_x;
- int32_t dy = pt.y - start_y;
- // Manual zigzag encoding.
- current_feature.add_geometry(protozero::encode_zigzag32(dx));
- current_feature.add_geometry(protozero::encode_zigzag32(dy));
- start_x = pt.x;
- start_y = pt.y;
- return true;
-}
-
-inline unsigned encode_length(unsigned len)
-{
- return (len << 3u) | 2u;
-}
-
-inline bool encode_geometry(mapnik::geometry::line_string<std::int64_t> const& line,
- vector_tile::Tile_Feature & current_feature,
- int32_t & start_x,
- int32_t & start_y)
-{
- std::size_t line_size = line.size();
- if (line_size <= 0)
- {
- return false;
- }
- unsigned line_to_length = static_cast<unsigned>(line_size) - 1;
-
- enum {
- move_to = 1,
- line_to = 2,
- coords = 3
- } status = move_to;
-
- for (auto const& pt : line)
- {
- if (status == move_to)
- {
- status = line_to;
- current_feature.add_geometry(9); // move_to | (1 << 3)
- }
- else if (status == line_to)
- {
- status = coords;
- current_feature.add_geometry(encode_length(line_to_length));
- }
- int32_t dx = pt.x - start_x;
- int32_t dy = pt.y - start_y;
- // Manual zigzag encoding.
- current_feature.add_geometry(protozero::encode_zigzag32(dx));
- current_feature.add_geometry(protozero::encode_zigzag32(dy));
- start_x = pt.x;
- start_y = pt.y;
- }
- return true;
-}
-
-inline bool encode_geometry(mapnik::geometry::linear_ring<std::int64_t> const& ring,
- vector_tile::Tile_Feature & current_feature,
- int32_t & start_x,
- int32_t & start_y)
-{
- std::size_t ring_size = ring.size();
- if (ring_size < 3)
- {
- return false;
- }
- unsigned line_to_length = static_cast<unsigned>(ring_size) - 1;
- unsigned count = 0;
- enum {
- move_to = 1,
- line_to = 2,
- coords = 3
- } status = move_to;
- bool drop_last = false;
- if (ring.size() > 2 && ring.front() == ring.back())
- {
- drop_last = true;
- line_to_length -= 1;
- if (line_to_length < 2)
- {
- return false;
- }
- }
-
- for (auto const& pt : ring)
- {
- if (status == move_to)
- {
- status = line_to;
- current_feature.add_geometry(9); // move_to | (1 << 3)
- }
- else if (status == line_to)
- {
- status = coords;
- current_feature.add_geometry(encode_length(line_to_length));
- }
- else if (drop_last && count == line_to_length + 1)
- {
- continue;
- }
- int32_t dx = pt.x - start_x;
- int32_t dy = pt.y - start_y;
- // Manual zigzag encoding.
- current_feature.add_geometry(protozero::encode_zigzag32(dx));
- current_feature.add_geometry(protozero::encode_zigzag32(dy));
- start_x = pt.x;
- start_y = pt.y;
- ++count;
- }
- current_feature.add_geometry(15); // close_path
- return true;
-}
-
-inline bool encode_geometry(mapnik::geometry::polygon<std::int64_t> const& poly,
- vector_tile::Tile_Feature & current_feature,
- int32_t & start_x,
- int32_t & start_y)
-{
- if (!encode_geometry(poly.exterior_ring, current_feature, start_x, start_y))
- {
- return false;
- }
- for (auto const& ring : poly.interior_rings)
- {
- encode_geometry(ring, current_feature, start_x, start_y);
- }
- return true;
-}
-
-inline bool encode_geometry(mapnik::geometry::multi_point<std::int64_t> const& geom,
- vector_tile::Tile_Feature & current_feature,
- int32_t & start_x,
- int32_t & start_y)
-{
- std::size_t geom_size = geom.size();
- if (geom_size <= 0)
- {
- return false;
- }
- current_feature.add_geometry(1u | (geom_size << 3)); // move_to | (len << 3)
- for (auto const& pt : geom)
- {
- int32_t dx = pt.x - start_x;
- int32_t dy = pt.y - start_y;
- // Manual zigzag encoding.
- current_feature.add_geometry(protozero::encode_zigzag32(dx));
- current_feature.add_geometry(protozero::encode_zigzag32(dy));
- start_x = pt.x;
- start_y = pt.y;
- }
- return true;
-}
-
-inline bool encode_geometry(mapnik::geometry::multi_line_string<std::int64_t> const& geom,
- vector_tile::Tile_Feature & current_feature,
- int32_t & start_x,
- int32_t & start_y)
-{
- bool success = false;
- for (auto const& poly : geom)
- {
- if (encode_geometry(poly, current_feature, start_x, start_y))
- {
- success = true;
- }
- }
- return success;
-}
-
-inline bool encode_geometry(mapnik::geometry::multi_polygon<std::int64_t> const& geom,
- vector_tile::Tile_Feature & current_feature,
- int32_t & start_x,
- int32_t & start_y)
-{
- bool success = false;
- for (auto const& poly : geom)
- {
- if (encode_geometry(poly, current_feature, start_x, start_y))
- {
- success = true;
- }
- }
- return success;
-}
-
-}} // end ns
-
-#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__
diff --git a/src/vector_tile_geometry_encoder_pbf.cpp b/src/vector_tile_geometry_encoder_pbf.cpp
new file mode 100644
index 0000000..58fd224
--- /dev/null
+++ b/src/vector_tile_geometry_encoder_pbf.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_geometry_encoder_pbf.hpp"
+#include "vector_tile_geometry_encoder_pbf.ipp"
diff --git a/src/vector_tile_geometry_encoder_pbf.hpp b/src/vector_tile_geometry_encoder_pbf.hpp
new file mode 100644
index 0000000..28cbde4
--- /dev/null
+++ b/src/vector_tile_geometry_encoder_pbf.hpp
@@ -0,0 +1,67 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_PBF_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_PBF_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/varint.hpp>
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <cstdlib>
+#include <cmath>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::point<std::int64_t> const& pt,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_point<std::int64_t> const& geom,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::line_string<std::int64_t> const& line,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_line_string<std::int64_t> const& geom,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::polygon<std::int64_t> const& poly,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_polygon<std::int64_t> const& poly,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y);
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::geometry<std::int64_t> const& geom,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y);
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_geometry_encoder_pbf.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_PBF_H__
diff --git a/src/vector_tile_geometry_encoder_pbf.ipp b/src/vector_tile_geometry_encoder_pbf.ipp
new file mode 100644
index 0000000..a499eae
--- /dev/null
+++ b/src/vector_tile_geometry_encoder_pbf.ipp
@@ -0,0 +1,307 @@
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/varint.hpp>
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <cstdlib>
+#include <cmath>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+namespace detail_pbf
+{
+
+template <typename Geometry>
+inline std::size_t repeated_point_count(Geometry const& geom)
+{
+ if (geom.size() < 2)
+ {
+ return 0;
+ }
+ std::size_t count = 0;
+ auto itr = geom.begin();
+
+ for (auto prev_itr = itr++; itr != geom.end(); ++prev_itr, ++itr)
+ {
+ if (itr->x == prev_itr->x && itr->y == prev_itr->y)
+ {
+ count++;
+ }
+ }
+ return count;
+}
+
+inline unsigned encode_length(unsigned len)
+{
+ return (len << 3u) | 2u;
+}
+
+struct encoder_visitor
+{
+ protozero::pbf_writer & feature_;
+ int32_t & x_;
+ int32_t & y_;
+
+ encoder_visitor(protozero::pbf_writer & feature,
+ int32_t & x,
+ int32_t & y)
+ : feature_(feature),
+ x_(x),
+ y_(y) {}
+
+ bool operator() (mapnik::geometry::geometry_empty const&)
+ {
+ return false;
+ }
+
+ bool operator() (mapnik::geometry::geometry_collection<std::int64_t> const&)
+ {
+ throw std::runtime_error("Geometry collections can not be encoded as they may contain different geometry types");
+ }
+
+ template <typename T>
+ bool operator() (T const& geom)
+ {
+ return encode_geometry_pbf(geom, feature_, x_, y_);
+ }
+};
+
+inline bool encode_linestring(mapnik::geometry::line_string<std::int64_t> const& line,
+ protozero::packed_field_uint32 & geometry,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ std::size_t line_size = line.size();
+ line_size -= detail_pbf::repeated_point_count(line);
+ if (line_size < 2)
+ {
+ return false;
+ }
+
+ unsigned line_to_length = static_cast<unsigned>(line_size) - 1;
+
+ auto pt = line.begin();
+ geometry.add_element(9); // move_to | (1 << 3)
+ geometry.add_element(protozero::encode_zigzag32(pt->x - start_x));
+ geometry.add_element(protozero::encode_zigzag32(pt->y - start_y));
+ start_x = pt->x;
+ start_y = pt->y;
+ geometry.add_element(detail_pbf::encode_length(line_to_length));
+ for (++pt; pt != line.end(); ++pt)
+ {
+ int32_t dx = pt->x - start_x;
+ int32_t dy = pt->y - start_y;
+ if (dx == 0 && dy == 0)
+ {
+ continue;
+ }
+ geometry.add_element(protozero::encode_zigzag32(dx));
+ geometry.add_element(protozero::encode_zigzag32(dy));
+ start_x = pt->x;
+ start_y = pt->y;
+ }
+ return true;
+}
+
+inline bool encode_linearring(mapnik::geometry::linear_ring<std::int64_t> const& ring,
+ protozero::packed_field_uint32 & geometry,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ std::size_t ring_size = ring.size();
+ ring_size -= detail_pbf::repeated_point_count(ring);
+ if (ring_size < 3)
+ {
+ return false;
+ }
+ auto last_itr = ring.end();
+ if (ring.front() == ring.back())
+ {
+ --last_itr;
+ --ring_size;
+ if (ring_size < 3)
+ {
+ return false;
+ }
+ }
+
+ unsigned line_to_length = static_cast<unsigned>(ring_size) - 1;
+ auto pt = ring.begin();
+ geometry.add_element(9); // move_to | (1 << 3)
+ geometry.add_element(protozero::encode_zigzag32(pt->x - start_x));
+ geometry.add_element(protozero::encode_zigzag32(pt->y - start_y));
+ start_x = pt->x;
+ start_y = pt->y;
+ geometry.add_element(detail_pbf::encode_length(line_to_length));
+ for (++pt; pt != last_itr; ++pt)
+ {
+ int32_t dx = pt->x - start_x;
+ int32_t dy = pt->y - start_y;
+ if (dx == 0 && dy == 0)
+ {
+ continue;
+ }
+ geometry.add_element(protozero::encode_zigzag32(dx));
+ geometry.add_element(protozero::encode_zigzag32(dy));
+ start_x = pt->x;
+ start_y = pt->y;
+ }
+ geometry.add_element(15); // close_path
+ return true;
+}
+
+inline bool encode_polygon(mapnik::geometry::polygon<std::int64_t> const& poly,
+ protozero::packed_field_uint32 & geometry,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ if (!encode_linearring(poly.exterior_ring, geometry, start_x, start_y))
+ {
+ return false;
+ }
+ for (auto const& ring : poly.interior_rings)
+ {
+ encode_linearring(ring, geometry, start_x, start_y);
+ }
+ return true;
+}
+
+} // end ns detail
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::point<std::int64_t> const& pt,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::POINT);
+ {
+ protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+ geometry.add_element(9);
+ int32_t dx = pt.x - start_x;
+ int32_t dy = pt.y - start_y;
+ // Manual zigzag encoding.
+ geometry.add_element(protozero::encode_zigzag32(dx));
+ geometry.add_element(protozero::encode_zigzag32(dy));
+ start_x = pt.x;
+ start_y = pt.y;
+ }
+ return true;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_point<std::int64_t> const& geom,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ std::size_t geom_size = geom.size();
+ if (geom_size <= 0)
+ {
+ return false;
+ }
+ current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::POINT);
+ {
+ protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+ geometry.add_element(1u | (geom_size << 3)); // move_to | (len << 3)
+ for (auto const& pt : geom)
+ {
+ int32_t dx = pt.x - start_x;
+ int32_t dy = pt.y - start_y;
+ // Manual zigzag encoding.
+ geometry.add_element(protozero::encode_zigzag32(dx));
+ geometry.add_element(protozero::encode_zigzag32(dy));
+ start_x = pt.x;
+ start_y = pt.y;
+ }
+ }
+ return true;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::line_string<std::int64_t> const& line,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ bool success = false;
+ current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::LINESTRING);
+ {
+ protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+ success = detail_pbf::encode_linestring(line, geometry, start_x, start_y);
+ }
+ return success;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_line_string<std::int64_t> const& geom,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ bool success = false;
+ current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::LINESTRING);
+ {
+ protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+ for (auto const& line : geom)
+ {
+ if (detail_pbf::encode_linestring(line, geometry, start_x, start_y))
+ {
+ success = true;
+ }
+ }
+ }
+ return success;
+}
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::polygon<std::int64_t> const& poly,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ bool success = false;
+ current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::POLYGON);
+ {
+ protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+ success = detail_pbf::encode_polygon(poly, geometry, start_x, start_y);
+ }
+ return success;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::multi_polygon<std::int64_t> const& geom,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ bool success = false;
+ current_feature.add_enum(Feature_Encoding::TYPE, Geometry_Type::POLYGON);
+ {
+ protozero::packed_field_uint32 geometry(current_feature, Feature_Encoding::GEOMETRY);
+ for (auto const& poly : geom)
+ {
+ if (detail_pbf::encode_polygon(poly, geometry, start_x, start_y))
+ {
+ success = true;
+ }
+ }
+ }
+ return success;
+}
+
+MAPNIK_VECTOR_INLINE bool encode_geometry_pbf(mapnik::geometry::geometry<std::int64_t> const& geom,
+ protozero::pbf_writer & current_feature,
+ int32_t & start_x,
+ int32_t & start_y)
+{
+ detail_pbf::encoder_visitor ap(current_feature, start_x, start_y);
+ return mapnik::util::apply_visitor(ap, geom);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_geometry_feature.hpp b/src/vector_tile_geometry_feature.hpp
new file mode 100644
index 0000000..250f394
--- /dev/null
+++ b/src/vector_tile_geometry_feature.hpp
@@ -0,0 +1,86 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_FEATURE_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_FEATURE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+#include "vector_tile_layer.hpp"
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/feature.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/util/variant.hpp>
+
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+inline void raster_to_feature(std::string const& buffer,
+ mapnik::feature_impl const& mapnik_feature,
+ layer_builder_pbf & builder)
+{
+ std::vector<std::uint32_t> feature_tags;
+ protozero::pbf_writer layer_writer = builder.add_feature(mapnik_feature, feature_tags);
+ protozero::pbf_writer feature_writer(layer_writer, Layer_Encoding::FEATURES);
+ feature_writer.add_uint64(Feature_Encoding::ID, static_cast<std::uint64_t>(mapnik_feature.id()));
+ feature_writer.add_string(Feature_Encoding::RASTER, buffer);
+ feature_writer.add_packed_uint32(Feature_Encoding::TAGS, feature_tags.begin(), feature_tags.end());
+ builder.make_not_empty();
+}
+
+struct geometry_to_feature_pbf_visitor
+{
+ mapnik::feature_impl const& mapnik_feature_;
+ layer_builder_pbf & builder_;
+
+ geometry_to_feature_pbf_visitor(mapnik::feature_impl const& mapnik_feature,
+ layer_builder_pbf & builder)
+ : mapnik_feature_(mapnik_feature),
+ builder_(builder) {}
+
+ void operator() (mapnik::geometry::geometry_empty const&)
+ {
+ return;
+ }
+
+ template <typename T>
+ void operator() (T const& geom)
+ {
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ bool success = false;
+ std::vector<std::uint32_t> feature_tags;
+ protozero::pbf_writer layer_writer = builder_.add_feature(mapnik_feature_, feature_tags);
+ {
+ protozero::pbf_writer feature_writer(layer_writer, Layer_Encoding::FEATURES);
+ success = encode_geometry_pbf(geom, feature_writer, x, y);
+ if (success)
+ {
+ feature_writer.add_uint64(Feature_Encoding::ID, static_cast<std::uint64_t>(mapnik_feature_.id()));
+ feature_writer.add_packed_uint32(Feature_Encoding::TAGS, feature_tags.begin(), feature_tags.end());
+ builder_.make_not_empty();
+ }
+ }
+ if (!success)
+ {
+ layer_writer.rollback();
+ }
+ }
+
+ void operator() (mapnik::geometry::geometry_collection<std::int64_t> const& collection)
+ {
+ for (auto & g : collection)
+ {
+ mapnik::util::apply_visitor((*this), g);
+ }
+ }
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_GEOMETRY_FEATURE_H__
diff --git a/src/vector_tile_geometry_intersects.cpp b/src/vector_tile_geometry_intersects.cpp
new file mode 100644
index 0000000..2238228
--- /dev/null
+++ b/src/vector_tile_geometry_intersects.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_geometry_intersects.hpp"
+#include "vector_tile_geometry_intersects.ipp"
diff --git a/src/vector_tile_geometry_intersects.hpp b/src/vector_tile_geometry_intersects.hpp
new file mode 100644
index 0000000..1b71109
--- /dev/null
+++ b/src/vector_tile_geometry_intersects.hpp
@@ -0,0 +1,55 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_INTERSECTS_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_INTERSECTS_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/util/variant.hpp>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+struct geometry_intersects
+{
+ MAPNIK_VECTOR_INLINE geometry_intersects(mapnik::box2d<double> const& extent);
+
+ MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::geometry_empty const& geom);
+
+ MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::point<double> const& geom);
+
+ MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::multi_point<double> const& geom);
+
+ MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::geometry_collection<double> const& geom);
+
+ MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::line_string<double> const& geom);
+
+ MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::multi_line_string<double> const& geom);
+
+ MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::polygon<double> const& geom);
+
+ MAPNIK_VECTOR_INLINE bool operator() (mapnik::geometry::multi_polygon<double> const& geom);
+
+ mapnik::geometry::linear_ring<double> extent_;
+};
+
+inline bool intersects(mapnik::box2d<double> const& extent, mapnik::geometry::geometry<double> const& geom)
+{
+ return mapnik::util::apply_visitor(geometry_intersects(extent), geom);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_geometry_intersects.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_GEOMETRY_INTERSECTS_H__
+
diff --git a/src/vector_tile_geometry_intersects.ipp b/src/vector_tile_geometry_intersects.ipp
new file mode 100644
index 0000000..32fbc56
--- /dev/null
+++ b/src/vector_tile_geometry_intersects.ipp
@@ -0,0 +1,84 @@
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_adapters.hpp>
+
+// boost
+#include <boost/geometry.hpp>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+MAPNIK_VECTOR_INLINE geometry_intersects::geometry_intersects(mapnik::box2d<double> const & extent)
+ : extent_()
+{
+ extent_.reserve(5);
+ extent_.emplace_back(extent.minx(),extent.miny());
+ extent_.emplace_back(extent.maxx(),extent.miny());
+ extent_.emplace_back(extent.maxx(),extent.maxy());
+ extent_.emplace_back(extent.minx(),extent.maxy());
+ extent_.emplace_back(extent.minx(),extent.miny());
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::geometry_empty const& )
+{
+ return false;
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::point<double> const& )
+{
+ // The query should gaurantee that a point is within the bounding box!
+ return true;
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::multi_point<double> const& geom)
+{
+ for (auto const& g : geom)
+ {
+ if (boost::geometry::intersects(g, extent_))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::line_string<double> const& geom)
+{
+ return boost::geometry::intersects(geom, extent_);
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::multi_line_string<double> const& geom)
+{
+ return boost::geometry::intersects(geom, extent_);
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::polygon<double> const& geom)
+{
+ return boost::geometry::intersects(geom, extent_);
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::multi_polygon<double> const& geom)
+{
+ return boost::geometry::intersects(geom, extent_);
+}
+
+MAPNIK_VECTOR_INLINE bool geometry_intersects::operator() (mapnik::geometry::geometry_collection<double> const& geom)
+{
+ for (auto const& g : geom)
+ {
+ if (mapnik::util::apply_visitor(*this, g))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
diff --git a/src/vector_tile_geometry_simplifier.hpp b/src/vector_tile_geometry_simplifier.hpp
new file mode 100644
index 0000000..3cc117a
--- /dev/null
+++ b/src/vector_tile_geometry_simplifier.hpp
@@ -0,0 +1,99 @@
+#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_SIMPLIFIER_H__
+#define __MAPNIK_VECTOR_TILE_GEOMETRY_SIMPLIFIER_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_adapters.hpp>
+
+// boost
+#include <boost/geometry/algorithms/simplify.hpp>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+template <typename NextProcessor>
+struct geometry_simplifier
+{
+ geometry_simplifier(unsigned simplify_distance,
+ NextProcessor & next)
+ : next_(next),
+ simplify_distance_(simplify_distance) {}
+
+ void operator() (mapnik::geometry::geometry_empty &)
+ {
+ return;
+ }
+
+ void operator() (mapnik::geometry::point<std::int64_t> & geom)
+ {
+ next_(geom);
+ }
+
+ void operator() (mapnik::geometry::multi_point<std::int64_t> & geom)
+ {
+ next_(geom);
+ }
+
+ void operator() (mapnik::geometry::line_string<std::int64_t> & geom)
+ {
+ mapnik::geometry::line_string<std::int64_t> simplified;
+ boost::geometry::simplify<
+ mapnik::geometry::line_string<std::int64_t>,
+ std::int64_t>
+ (geom, simplified, simplify_distance_);
+ next_(simplified);
+ }
+
+ void operator() (mapnik::geometry::multi_line_string<std::int64_t> & geom)
+ {
+ mapnik::geometry::multi_line_string<std::int64_t> simplified;
+ boost::geometry::simplify<
+ mapnik::geometry::multi_line_string<std::int64_t>,
+ std::int64_t>
+ (geom, simplified, simplify_distance_);
+ next_(simplified);
+ }
+
+ void operator() (mapnik::geometry::polygon<std::int64_t> & geom)
+ {
+ mapnik::geometry::polygon<std::int64_t> simplified;
+ boost::geometry::simplify<
+ mapnik::geometry::polygon<std::int64_t>,
+ std::int64_t>
+ (geom, simplified, simplify_distance_);
+ next_(simplified);
+ }
+
+ void operator() (mapnik::geometry::multi_polygon<std::int64_t> & geom)
+ {
+ mapnik::geometry::multi_polygon<std::int64_t> simplified;
+ boost::geometry::simplify<
+ mapnik::geometry::multi_polygon<std::int64_t>,
+ std::int64_t>
+ (geom, simplified, simplify_distance_);
+ next_(simplified);
+ }
+
+ void operator() (mapnik::geometry::geometry_collection<std::int64_t> & geom)
+ {
+ for (auto & g : geom)
+ {
+ mapnik::util::apply_visitor((*this), g);
+ }
+ }
+
+ NextProcessor & next_;
+ unsigned simplify_distance_;
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_GEOMETRY_SIMPLIFIER_H__
diff --git a/src/vector_tile_is_valid.hpp b/src/vector_tile_is_valid.hpp
new file mode 100644
index 0000000..93dc82b
--- /dev/null
+++ b/src/vector_tile_is_valid.hpp
@@ -0,0 +1,304 @@
+#ifndef __MAPNIK_VECTOR_TILE_IS_VALID_H__
+#define __MAPNIK_VECTOR_TILE_IS_VALID_H__
+
+// mapnik protzero parsing configuration
+#include "vector_tile_config.hpp"
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+
+// std
+#include <sstream>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+enum validity_error : std::uint8_t
+{
+ TILE_REPEATED_LAYER_NAMES = 0,
+ TILE_HAS_UNKNOWN_TAG,
+ LAYER_HAS_NO_NAME,
+ LAYER_HAS_NO_EXTENT,
+ LAYER_HAS_NO_FEATURES,
+ LAYER_HAS_INVALID_VERSION,
+ LAYER_HAS_UNKNOWN_TAG,
+ VALUE_MULTIPLE_VALUES,
+ VALUE_NO_VALUE,
+ VALUE_HAS_UNKNOWN_TAG,
+ FEATURE_IS_EMPTY,
+ FEATURE_MULTIPLE_GEOM,
+ FEATURE_NO_GEOM_TYPE,
+ FEATURE_HAS_INVALID_GEOM_TYPE,
+ FEATURE_HAS_UNKNOWN_TAG,
+ INVALID_PBF_BUFFER
+};
+
+inline std::string validity_error_to_string(validity_error err)
+{
+ switch (err)
+ {
+ case TILE_REPEATED_LAYER_NAMES:
+ return "Vector Tile message has two or more layers with the same name";
+ case TILE_HAS_UNKNOWN_TAG:
+ return "Vector Tile message has an unknown tag";
+ case LAYER_HAS_NO_NAME:
+ return "Vector Tile Layer message has no name";
+ case LAYER_HAS_NO_EXTENT:
+ return "Vector Tile Layer message has no extent";
+ case LAYER_HAS_NO_FEATURES:
+ return "Vector Tile Layer message has no features";
+ case LAYER_HAS_INVALID_VERSION:
+ return "Vector Tile Layer message has an invalid version";
+ case LAYER_HAS_UNKNOWN_TAG:
+ return "Vector Tile Layer message has an unknown tag";
+ case VALUE_MULTIPLE_VALUES:
+ return "Vector Tile Value message contains more then one values";
+ case VALUE_NO_VALUE:
+ return "Vector Tile Value message contains no values";
+ case VALUE_HAS_UNKNOWN_TAG:
+ return "Vector Tile Value message has an unknown tag";
+ case FEATURE_IS_EMPTY:
+ return "Vector Tile Feature message has no geometry";
+ case FEATURE_MULTIPLE_GEOM:
+ return "Vector Tile Feature message has multiple geometries";
+ case FEATURE_NO_GEOM_TYPE:
+ return "Vector Tile Feature message is missing a geometry type";
+ case FEATURE_HAS_INVALID_GEOM_TYPE:
+ return "Vector Tile Feature message has an invalid geometry type";
+ case FEATURE_HAS_UNKNOWN_TAG:
+ return "Vector Tile Feature message has an unknown tag";
+ case INVALID_PBF_BUFFER:
+ return "Buffer is not encoded as a valid PBF";
+ default:
+ return "UNKNOWN ERROR";
+ }
+}
+
+inline void validity_error_to_string(std::set<validity_error> & errors, std::string & out)
+{
+ if (errors.empty())
+ {
+ return;
+ }
+ std::ostringstream err;
+ err << "Vector Tile Validity Errors Found:" << std::endl;
+ for (auto e : errors)
+ {
+ err << " - " << validity_error_to_string(e) << std::endl;
+ }
+ out.append(err.str());
+}
+
+inline void feature_is_valid(protozero::pbf_reader & feature_msg, std::set<validity_error> & errors)
+{
+ bool has_geom = false;
+ bool has_raster = false;
+ bool has_type = false;
+ while (feature_msg.next())
+ {
+ switch (feature_msg.tag())
+ {
+ case Feature_Encoding::ID: // id
+ feature_msg.skip();
+ break;
+ case Feature_Encoding::TAGS: // tags
+ feature_msg.get_packed_uint32();
+ break;
+ case Feature_Encoding::TYPE: // geom type
+ {
+ std::int32_t type = feature_msg.get_enum();
+ if (type <= 0 || type > 3)
+ {
+ errors.insert(FEATURE_HAS_INVALID_GEOM_TYPE);
+ }
+ has_type = true;
+ }
+ break;
+ case Feature_Encoding::GEOMETRY: // geometry
+ if (has_geom || has_raster)
+ {
+ errors.insert(FEATURE_MULTIPLE_GEOM);
+ }
+ has_geom = true;
+ feature_msg.get_packed_uint32();
+ break;
+ case Feature_Encoding::RASTER: // raster
+ if (has_geom || has_raster)
+ {
+ errors.insert(FEATURE_MULTIPLE_GEOM);
+ }
+ has_raster = true;
+ feature_msg.get_data();
+ break;
+ default:
+ errors.insert(FEATURE_HAS_UNKNOWN_TAG);
+ feature_msg.skip();
+ break;
+ }
+ }
+ if (!has_geom && !has_raster)
+ {
+ errors.insert(FEATURE_IS_EMPTY);
+ }
+ if (has_geom && !has_type)
+ {
+ errors.insert(FEATURE_NO_GEOM_TYPE);
+ }
+}
+
+inline void value_is_valid(protozero::pbf_reader & value_msg, std::set<validity_error> & errors)
+{
+ bool has_value = false;
+ while (value_msg.next())
+ {
+ switch (value_msg.tag())
+ {
+ case Value_Encoding::STRING:
+ case Value_Encoding::FLOAT:
+ case Value_Encoding::DOUBLE:
+ case Value_Encoding::INT:
+ case Value_Encoding::UINT:
+ case Value_Encoding::SINT:
+ case Value_Encoding::BOOL:
+ if (has_value)
+ {
+ errors.insert(VALUE_MULTIPLE_VALUES);
+ }
+ has_value = true;
+ value_msg.skip();
+ break;
+ default:
+ errors.insert(VALUE_HAS_UNKNOWN_TAG);
+ value_msg.skip();
+ break;
+ }
+ }
+ if (!has_value)
+ {
+ errors.insert(VALUE_NO_VALUE);
+ }
+}
+
+inline void layer_is_valid(protozero::pbf_reader & layer_msg,
+ std::set<validity_error> & errors,
+ std::string & layer_name,
+ std::uint32_t & version)
+{
+ bool contains_a_feature = false;
+ bool contains_a_name = false;
+ bool contains_an_extent = false;
+ try
+ {
+ while (layer_msg.next())
+ {
+ switch (layer_msg.tag())
+ {
+ case Layer_Encoding::NAME: // name
+ contains_a_name = true;
+ layer_name = layer_msg.get_string();
+ break;
+ case Layer_Encoding::FEATURES:
+ {
+ contains_a_feature = true;
+ protozero::pbf_reader feature_msg = layer_msg.get_message();
+ feature_is_valid(feature_msg, errors);
+ }
+ break;
+ case Layer_Encoding::KEYS: // keys
+ layer_msg.skip();
+ break;
+ case Layer_Encoding::VALUES: // value
+ {
+ protozero::pbf_reader value_msg = layer_msg.get_message();
+ value_is_valid(value_msg, errors);
+ }
+ break;
+ case Layer_Encoding::EXTENT: // extent
+ contains_an_extent = true;
+ layer_msg.skip();
+ break;
+ case Layer_Encoding::VERSION:
+ version = layer_msg.get_uint32();
+ break;
+ default:
+ errors.insert(LAYER_HAS_UNKNOWN_TAG);
+ layer_msg.skip();
+ break;
+ }
+ }
+ }
+ catch (...)
+ {
+ errors.insert(INVALID_PBF_BUFFER);
+ }
+ if (!contains_a_name)
+ {
+ errors.insert(LAYER_HAS_NO_NAME);
+ }
+ if (version > 2 || version == 0)
+ {
+ errors.insert(LAYER_HAS_INVALID_VERSION);
+ }
+ if (!contains_an_extent && version != 1)
+ {
+ errors.insert(LAYER_HAS_NO_EXTENT);
+ }
+ if (!contains_a_feature && version != 1)
+ {
+ errors.insert(LAYER_HAS_NO_FEATURES);
+ }
+}
+
+// Check some common things that could be wrong with a tile
+// does not check all items in line with the v2 spec. For
+// example does does not check validity of geometry. It also does not
+// verify that all feature attributes are valid.
+inline void tile_is_valid(protozero::pbf_reader & tile_msg, std::set<validity_error> & errors)
+{
+ std::set<std::string> layer_names_set;
+ try
+ {
+ while (tile_msg.next())
+ {
+ switch (tile_msg.tag())
+ {
+ case Tile_Encoding::LAYERS:
+ {
+ protozero::pbf_reader layer_msg = tile_msg.get_message();
+ std::string layer_name;
+ std::uint32_t version = 1;
+ layer_is_valid(layer_msg, errors, layer_name, version);
+ auto p = layer_names_set.insert(layer_name);
+ if (!p.second)
+ {
+ errors.insert(TILE_REPEATED_LAYER_NAMES);
+ }
+ }
+ break;
+ default:
+ errors.insert(TILE_HAS_UNKNOWN_TAG);
+ tile_msg.skip();
+ break;
+ }
+ }
+ }
+ catch (...)
+ {
+ errors.insert(INVALID_PBF_BUFFER);
+ }
+}
+
+inline void tile_is_valid(std::string const& tile, std::set<validity_error> & errors)
+{
+ protozero::pbf_reader tile_msg(tile);
+ tile_is_valid(tile_msg, errors);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_IS_VALID_H__
diff --git a/src/vector_tile_layer.cpp b/src/vector_tile_layer.cpp
new file mode 100644
index 0000000..2cb0e9f
--- /dev/null
+++ b/src/vector_tile_layer.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_layer.hpp"
+#include "vector_tile_layer.ipp"
diff --git a/src/vector_tile_layer.hpp b/src/vector_tile_layer.hpp
new file mode 100644
index 0000000..d70663d
--- /dev/null
+++ b/src/vector_tile_layer.hpp
@@ -0,0 +1,374 @@
+#ifndef __MAPNIK_VECTOR_TILE_LAYER_H__
+#define __MAPNIK_VECTOR_TILE_LAYER_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/feature.hpp>
+#include <mapnik/layer.hpp>
+#include <mapnik/map.hpp>
+#include <mapnik/projection.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/scale_denominator.hpp>
+#include <mapnik/value.hpp>
+#include <mapnik/view_transform.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <map>
+#include <unordered_map>
+#include <utility>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+struct layer_builder_pbf
+{
+ typedef std::map<std::string, unsigned> keys_container;
+ typedef std::unordered_map<mapnik::value, unsigned> values_container;
+
+ keys_container keys;
+ values_container values;
+ std::string & layer_buffer;
+ bool empty;
+ bool painted;
+
+ layer_builder_pbf(std::string const & name, std::uint32_t extent, std::string & _layer_buffer)
+ : keys(),
+ values(),
+ layer_buffer(_layer_buffer),
+ empty(true),
+ painted(false)
+ {
+ protozero::pbf_writer layer_writer(layer_buffer);
+ layer_writer.add_uint32(Layer_Encoding::VERSION, 2);
+ layer_writer.add_string(Layer_Encoding::NAME, name);
+ layer_writer.add_uint32(Layer_Encoding::EXTENT, extent);
+ }
+
+ void make_painted()
+ {
+ painted = true;
+ }
+
+ void make_not_empty()
+ {
+ empty = false;
+ }
+
+ MAPNIK_VECTOR_INLINE protozero::pbf_writer add_feature(mapnik::feature_impl const& mapnik_feature,
+ std::vector<std::uint32_t> & feature_tags);
+};
+
+class tile_layer
+{
+private:
+ bool valid_;
+ mapnik::datasource_ptr ds_;
+ mapnik::projection target_proj_;
+ mapnik::projection source_proj_;
+ mapnik::proj_transform prj_trans_;
+ std::string buffer_;
+ std::string name_;
+ std::uint32_t layer_extent_;
+ mapnik::box2d<double> target_buffered_extent_;
+ mapnik::box2d<double> source_buffered_extent_;
+ mapnik::query query_;
+ mapnik::view_transform view_trans_;
+ bool empty_;
+ bool painted_;
+
+public:
+ tile_layer(mapnik::Map const& map,
+ mapnik::layer const& lay,
+ mapnik::box2d<double> const& tile_extent_bbox,
+ std::uint32_t tile_size,
+ std::int32_t buffer_size,
+ double scale_factor,
+ double scale_denom,
+ int offset_x,
+ int offset_y)
+ : valid_(true),
+ ds_(lay.datasource()),
+ target_proj_(map.srs(), true),
+ source_proj_(lay.srs(), true),
+ prj_trans_(target_proj_, source_proj_),
+ buffer_(),
+ name_(lay.name()),
+ layer_extent_(calc_extent(tile_size)),
+ target_buffered_extent_(calc_target_buffered_extent(tile_extent_bbox, buffer_size, lay, map)),
+ source_buffered_extent_(calc_source_buffered_extent()),
+ query_(calc_query(scale_factor, scale_denom, tile_extent_bbox, lay)),
+ view_trans_(layer_extent_, layer_extent_, tile_extent_bbox, offset_x, offset_y),
+ empty_(true),
+ painted_(false)
+ {
+ }
+
+
+ tile_layer(tile_layer && rhs)
+ : valid_(std::move(rhs.valid_)),
+ ds_(std::move(rhs.ds_)),
+ target_proj_(std::move(rhs.target_proj_)),
+ source_proj_(std::move(rhs.source_proj_)),
+ prj_trans_(target_proj_, source_proj_),
+ buffer_(std::move(rhs.buffer_)),
+ name_(std::move(rhs.name_)),
+ layer_extent_(std::move(rhs.layer_extent_)),
+ target_buffered_extent_(std::move(rhs.target_buffered_extent_)),
+ source_buffered_extent_(std::move(rhs.source_buffered_extent_)),
+ query_(std::move(rhs.query_)),
+ view_trans_(std::move(rhs.view_trans_)),
+ empty_(std::move(rhs.empty_)),
+ painted_(std::move(rhs.painted_)) {}
+
+ tile_layer& operator=(tile_layer&&) = default;
+
+ tile_layer(tile_layer const& rhs) = delete;
+ tile_layer& operator=(const tile_layer&) = delete;
+
+ std::uint32_t calc_extent(std::uint32_t layer_extent)
+ {
+ if (!ds_)
+ {
+ valid_ = false;
+ return 4096;
+ }
+ auto ds_extent = ds_->params().template get<mapnik::value_integer>("vector_layer_extent");
+ if (ds_extent)
+ {
+ layer_extent = *ds_extent;
+ }
+ if (layer_extent == 0)
+ {
+ valid_ = false;
+ layer_extent = 4096;
+ }
+ return layer_extent;
+ }
+
+ mapnik::box2d<double> calc_target_buffered_extent(mapnik::box2d<double> const& tile_extent_bbox,
+ std::int32_t buffer_size,
+ mapnik::layer const& lay,
+ mapnik::Map const& map) const
+ {
+ mapnik::box2d<double> ext(tile_extent_bbox);
+ double scale = ext.width() / layer_extent_;
+ 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
+ {
+ if (!ds_ || ds_->type() == datasource::Vector)
+ {
+ buffer_padding *= (*layer_buffer_size) * (static_cast<double>(layer_extent_) / VT_LEGACY_IMAGE_SIZE);
+ }
+ else
+ {
+ buffer_padding *= (*layer_buffer_size) * (static_cast<double>(layer_extent_));
+ }
+ }
+ else
+ {
+ buffer_padding *= buffer_size;
+ }
+ double buffered_width = ext.width() + buffer_padding;
+ double buffered_height = ext.height() + buffer_padding;
+ if (buffered_width < 0.0)
+ {
+ buffered_width = 0.0;
+ }
+ if (buffered_height < 0.0)
+ {
+ buffered_height = 0.0;
+ }
+ ext.width(buffered_width);
+ ext.height(buffered_height);
+
+ boost::optional<box2d<double> > const& maximum_extent = map.maximum_extent();
+ if (maximum_extent)
+ {
+ ext.clip(*maximum_extent);
+ }
+ return ext;
+ }
+
+ mapnik::box2d<double> calc_source_buffered_extent()
+ {
+ mapnik::box2d<double> new_extent(target_buffered_extent_);
+ if (!prj_trans_.forward(new_extent, PROJ_ENVELOPE_POINTS))
+ {
+ // this modifies the layer_ext by clipping to the buffered_ext
+ valid_ = false;
+ }
+ return new_extent;
+ }
+
+ mapnik::query calc_query(double scale_factor,
+ double scale_denom,
+ mapnik::box2d<double> const& tile_extent_bbox,
+ mapnik::layer const& lay)
+ {
+ // Adjust the scale denominator if required
+ if (scale_denom <= 0.0)
+ {
+ double scale = tile_extent_bbox.width() / VT_LEGACY_IMAGE_SIZE;
+ scale_denom = mapnik::scale_denominator(scale, target_proj_.is_geographic());
+ }
+ scale_denom *= scale_factor;
+ if (!lay.visible(scale_denom))
+ {
+ valid_ = false;
+ }
+
+ mapnik::box2d<double> query_extent(lay.envelope()); // source projection
+
+ // first, try intersection of map extent forward projected into layer srs
+ if (source_buffered_extent_.intersects(query_extent))
+ {
+ // this modifies the query_extent by clipping to the buffered_ext
+ query_extent.clip(source_buffered_extent_);
+ }
+ // if no intersection and projections are also equal, early return
+ else if (prj_trans_.equal())
+ {
+ valid_ = false;
+ }
+ // next try intersection of layer extent back projected into map srs
+ else if (prj_trans_.backward(query_extent, PROJ_ENVELOPE_POINTS) && target_buffered_extent_.intersects(query_extent))
+ {
+ query_extent.clip(target_buffered_extent_);
+ // forward project layer extent back into native projection
+ if (!prj_trans_.forward(query_extent, PROJ_ENVELOPE_POINTS))
+ {
+ throw std::runtime_error("vector_tile_processor: layer extent did not repoject back to map projection");
+ }
+ }
+ else
+ {
+ // if no intersection then nothing to do for layer
+ valid_ = false;
+ }
+ double qw = tile_extent_bbox.width() > 0 ? tile_extent_bbox.width() : 1;
+ double qh = tile_extent_bbox.height() > 0 ? tile_extent_bbox.height() : 1;
+ if (!ds_ || ds_->type() == datasource::Vector)
+ {
+ qw = VT_LEGACY_IMAGE_SIZE / qw;
+ qh = VT_LEGACY_IMAGE_SIZE / qh;
+ }
+ else
+ {
+ qw = static_cast<double>(layer_extent_) / qw;
+ qh = static_cast<double>(layer_extent_) / qh;
+ }
+ mapnik::query::resolution_type res(qw, qh);
+ mapnik::query q(query_extent, res, scale_denom, tile_extent_bbox);
+ if (ds_)
+ {
+ mapnik::layer_descriptor lay_desc = ds_->get_descriptor();
+ for (mapnik::attribute_descriptor const& desc : lay_desc.get_descriptors())
+ {
+ q.add_property_name(desc.get_name());
+ }
+ }
+ return q;
+ }
+
+ mapnik::datasource_ptr get_ds() const
+ {
+ return ds_;
+ }
+
+ mapnik::featureset_ptr get_features() const
+ {
+ return ds_->features(query_);
+ }
+
+ mapnik::view_transform const& get_view_transform() const
+ {
+ return view_trans_;
+ }
+
+ mapnik::proj_transform const& get_proj_transform() const
+ {
+ return prj_trans_;
+ }
+
+ mapnik::box2d<double> const& get_source_buffered_extent() const
+ {
+ return source_buffered_extent_;
+ }
+
+ mapnik::box2d<double> const& get_target_buffered_extent() const
+ {
+ return target_buffered_extent_;
+ }
+
+ bool is_valid() const
+ {
+ return valid_;
+ }
+
+ std::uint32_t layer_extent() const
+ {
+ return layer_extent_;
+ }
+
+ std::string const& name() const
+ {
+ return name_;
+ }
+
+ void name(std::string const& val)
+ {
+ name_ = val;
+ }
+
+ bool is_empty() const
+ {
+ return empty_;
+ }
+
+ bool is_painted() const
+ {
+ return painted_;
+ }
+
+ void make_painted()
+ {
+ painted_ = true;
+ }
+
+ std::string const& get_data() const
+ {
+ return buffer_;
+ }
+
+ std::string & get_data()
+ {
+ return buffer_;
+ }
+
+ void build(layer_builder_pbf const& builder)
+ {
+ empty_ = builder.empty;
+ painted_ = builder.painted;
+ }
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_layer.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_LAYER_H__
diff --git a/src/vector_tile_layer.ipp b/src/vector_tile_layer.ipp
new file mode 100644
index 0000000..121450c
--- /dev/null
+++ b/src/vector_tile_layer.ipp
@@ -0,0 +1,131 @@
+// mapnik
+#include <mapnik/feature.hpp>
+#include <mapnik/util/variant.hpp>
+#include <mapnik/value.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <map>
+#include <unordered_map>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+namespace detail
+{
+
+struct to_tile_value_pbf
+{
+public:
+ to_tile_value_pbf(protozero::pbf_writer & value):
+ value_(value) {}
+
+ void operator () ( value_integer val ) const
+ {
+ value_.add_int64(Value_Encoding::INT,val);
+ }
+
+ void operator () ( mapnik::value_bool val ) const
+ {
+ value_.add_bool(Value_Encoding::BOOL,val);
+ }
+
+ void operator () ( mapnik::value_double val ) const
+ {
+ float fval = static_cast<float>(val);
+ if (val == static_cast<double>(fval))
+ {
+ value_.add_float(Value_Encoding::FLOAT, fval);
+ }
+ else
+ {
+ value_.add_double(Value_Encoding::DOUBLE, val);
+ }
+ }
+
+ void operator () ( mapnik::value_unicode_string const& val ) const
+ {
+ std::string str;
+ to_utf8(val, str);
+ value_.add_string(Value_Encoding::STRING, str);
+ }
+
+ void operator () ( mapnik::value_null const& /*val*/ ) const
+ {
+ // do nothing
+ }
+private:
+ protozero::pbf_writer & value_;
+};
+
+} // end ns detail
+
+MAPNIK_VECTOR_INLINE protozero::pbf_writer layer_builder_pbf::add_feature(mapnik::feature_impl const& mapnik_feature,
+ std::vector<std::uint32_t> & feature_tags)
+
+{
+ protozero::pbf_writer layer_writer(layer_buffer);
+ // Feature id should be unique from mapnik so we should comply with
+ // the following wording of the specification:
+ // "the value of the (feature) id SHOULD be unique among the features of the parent layer."
+
+ // note that feature.id is signed int64_t so we are casting.
+
+ // Mapnik features can not have more then one value for
+ // a single key. Therefore, we do not have to check if
+ // key already exists in the feature as we insert each
+ // key value pair into the feature.
+ feature_kv_iterator itr = mapnik_feature.begin();
+ feature_kv_iterator end = mapnik_feature.end();
+ for (; itr!=end; ++itr)
+ {
+ std::string const& name = std::get<0>(*itr);
+ mapnik::value const& val = std::get<1>(*itr);
+ if (!val.is_null())
+ {
+ // Insert the key index
+ keys_container::const_iterator key_itr = keys.find(name);
+ if (key_itr == keys.end())
+ {
+ // The key doesn't exist yet in the dictionary.
+ layer_writer.add_string(Layer_Encoding::KEYS, name);
+ size_t index = keys.size();
+ keys.emplace(name, index);
+ feature_tags.push_back(index);
+ }
+ else
+ {
+ feature_tags.push_back(key_itr->second);
+ }
+
+ // Insert the value index
+ values_container::const_iterator val_itr = values.find(val);
+ if (val_itr == values.end())
+ {
+ // The value doesn't exist yet in the dictionary.
+ {
+ protozero::pbf_writer value_writer(layer_writer, Layer_Encoding::VALUES);
+ detail::to_tile_value_pbf visitor(value_writer);
+ mapnik::util::apply_visitor(visitor, val);
+ }
+ size_t index = values.size();
+ values.emplace(val, index);
+ feature_tags.push_back(index);
+ }
+ else
+ {
+ feature_tags.push_back(val_itr->second);
+ }
+ }
+ }
+ return layer_writer;
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_load_tile.hpp b/src/vector_tile_load_tile.hpp
new file mode 100644
index 0000000..d83b6c1
--- /dev/null
+++ b/src/vector_tile_load_tile.hpp
@@ -0,0 +1,135 @@
+#ifndef __MAPNIK_VECTOR_TILE_LOAD_TILE_H__
+#define __MAPNIK_VECTOR_TILE_LOAD_TILE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+#include "vector_tile_compression.hpp"
+#include "vector_tile_tile.hpp"
+#include "vector_tile_datasource_pbf.hpp"
+#include "vector_tile_is_valid.hpp"
+
+// mapnik
+#include <mapnik/map.hpp>
+#include <mapnik/layer.hpp>
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <set>
+#include <string>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+void merge_from_buffer(merc_tile & t, const char * data, std::size_t size)
+{
+ using ds_ptr = std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf>;
+ protozero::pbf_reader tile_msg(data, size);
+ std::vector<ds_ptr> ds_vec;
+ while (tile_msg.next())
+ {
+ switch (tile_msg.tag())
+ {
+ case Tile_Encoding::LAYERS:
+ {
+ auto layer_data = tile_msg.get_data();
+ protozero::pbf_reader layer_valid_msg(layer_data);
+ std::set<validity_error> errors;
+ std::string layer_name;
+ std::uint32_t version = 1;
+ layer_is_valid(layer_valid_msg, errors, layer_name, version);
+ if (!errors.empty())
+ {
+ std::string layer_errors;
+ validity_error_to_string(errors, layer_errors);
+ throw std::runtime_error(layer_errors);
+ }
+ if (t.has_layer(layer_name))
+ {
+ continue;
+ }
+ if (version == 1)
+ {
+ // v1 tiles will be convereted to v2
+ protozero::pbf_reader layer_msg(layer_data);
+ ds_vec.push_back(std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_msg,
+ t.x(),
+ t.y(),
+ t.z(),
+ true));
+ }
+ else
+ {
+ t.append_layer_buffer(layer_data.first, layer_data.second, layer_name);
+ }
+ }
+ break;
+ default:
+ throw std::runtime_error("Vector Tile Buffer contains invalid tag");
+ break;
+ }
+ }
+ if (ds_vec.empty())
+ {
+ return;
+ }
+ // Convert v1 tiles to v2
+ std::int32_t prev_buffer_size = t.buffer_size();
+ t.buffer_size(4096); // very large buffer so we don't miss any buffered points
+ mapnik::Map map(t.tile_size(), t.tile_size(), "+init=epsg:3857");
+ for (auto ds : ds_vec)
+ {
+ ds->set_envelope(t.get_buffered_extent());
+ mapnik::layer lyr(ds->get_name(), "+init=epsg:3857");
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+ }
+ mapnik::vector_tile_impl::processor ren(map);
+ ren.set_multi_polygon_union(true);
+ ren.set_fill_type(mapnik::vector_tile_impl::even_odd_fill);
+ ren.set_process_all_rings(true);
+ //ren.set_simplify_distance(4.0);
+ ren.update_tile(t);
+ t.buffer_size(prev_buffer_size);
+}
+
+void merge_from_compressed_buffer(merc_tile & t, const char * data, std::size_t size)
+{
+ if (mapnik::vector_tile_impl::is_gzip_compressed(data,size) ||
+ mapnik::vector_tile_impl::is_zlib_compressed(data,size))
+ {
+ std::string decompressed;
+ mapnik::vector_tile_impl::zlib_decompress(data, size, decompressed);
+ return merge_from_buffer(t, decompressed.data(), decompressed.size());
+ }
+ else
+ {
+ return merge_from_buffer(t, data, size);
+ }
+}
+
+void add_image_buffer_as_tile_layer(merc_tile & t, std::string const& layer_name, const char * data, std::size_t size)
+{
+ std::string layer_buffer;
+ protozero::pbf_writer layer_writer(layer_buffer);
+ layer_writer.add_uint32(Layer_Encoding::VERSION, 2);
+ layer_writer.add_string(Layer_Encoding::NAME, layer_name);
+ layer_writer.add_uint32(Layer_Encoding::EXTENT, 4096);
+ {
+ protozero::pbf_writer feature_writer(layer_writer, Layer_Encoding::FEATURES);
+ feature_writer.add_bytes(Feature_Encoding::RASTER, data, size);
+ }
+ t.append_layer_buffer(layer_buffer.data(), layer_buffer.size(), layer_name);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_LOAD_TILE_H__
diff --git a/src/vector_tile_merc_tile.hpp b/src/vector_tile_merc_tile.hpp
new file mode 100644
index 0000000..ffd1a31
--- /dev/null
+++ b/src/vector_tile_merc_tile.hpp
@@ -0,0 +1,82 @@
+#ifndef __MAPNIK_VECTOR_TILE_MERC_TILE_H__
+#define __MAPNIK_VECTOR_TILE_MERC_TILE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_tile.hpp"
+#include "vector_tile_projection.hpp"
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+class merc_tile : public tile
+{
+private:
+ std::uint32_t x_;
+ std::uint32_t y_;
+ std::uint32_t z_;
+public:
+ merc_tile(std::uint32_t x,
+ std::uint32_t y,
+ std::uint32_t z,
+ std::uint32_t tile_size = 4096,
+ std::int32_t buffer_size = 128)
+ : tile(merc_extent(tile_size, x, y, z), tile_size, buffer_size),
+ x_(x),
+ y_(y),
+ z_(z) {}
+
+ merc_tile(merc_tile const& rhs) = default;
+
+ merc_tile(merc_tile && rhs) = default;
+
+ bool same_extent(merc_tile & other)
+ {
+ return x_ == other.x_ &&
+ y_ == other.y_ &&
+ z_ == other.z_;
+ }
+
+ std::uint32_t x() const
+ {
+ return x_;
+ }
+
+ void x(std::uint32_t x)
+ {
+ x_ = x;
+ extent_ = merc_extent(tile_size_, x_, y_, z_);
+ }
+
+ std::uint32_t y() const
+ {
+ return y_;
+ }
+
+ void y(std::uint32_t y)
+ {
+ y_ = y;
+ extent_ = merc_extent(tile_size_, x_, y_, z_);
+ }
+
+ std::uint32_t z() const
+ {
+ return z_;
+ }
+
+ void z(std::uint32_t z)
+ {
+ z_ = z;
+ extent_ = merc_extent(tile_size_, x_, y_, z_);
+ }
+};
+
+typedef std::shared_ptr<merc_tile> merc_tile_ptr;
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#endif // __MAPNIK_VECTOR_TILE_MERC_TILE_H__
diff --git a/src/vector_tile_processor.cpp b/src/vector_tile_processor.cpp
index 4239e31..a72b04d 100644
--- a/src/vector_tile_processor.cpp
+++ b/src/vector_tile_processor.cpp
@@ -1,4 +1,2 @@
#include "vector_tile_processor.hpp"
#include "vector_tile_processor.ipp"
-#include "vector_tile_backend_pbf.hpp"
-template class mapnik::vector_tile_impl::processor<mapnik::vector_tile_impl::backend_pbf>;
diff --git a/src/vector_tile_processor.hpp b/src/vector_tile_processor.hpp
index 21442ca..0868010 100644
--- a/src/vector_tile_processor.hpp
+++ b/src/vector_tile_processor.hpp
@@ -1,28 +1,27 @@
#ifndef __MAPNIK_VECTOR_PROCESSOR_H__
#define __MAPNIK_VECTOR_PROCESSOR_H__
-#include <mapnik/map.hpp>
-#include <mapnik/layer.hpp>
+// mapnik
#include <mapnik/feature.hpp>
-#include <mapnik/util/noncopyable.hpp>
-#include <mapnik/request.hpp>
-#include <mapnik/view_transform.hpp>
#include <mapnik/image_scaling.hpp>
-#include <mapnik/image_compositing.hpp>
-#include <mapnik/geometry.hpp>
-#include "clipper.hpp"
+#include <mapnik/layer.hpp>
+#include <mapnik/map.hpp>
+#include <mapnik/request.hpp>
+#include <mapnik/util/noncopyable.hpp>
+// mapnik-vector-tile
#include "vector_tile_config.hpp"
+#include "vector_tile_tile.hpp"
+#include "vector_tile_merc_tile.hpp"
-namespace mapnik { namespace vector_tile_impl {
+// std
+#include <future>
-enum polygon_fill_type : std::uint8_t {
- even_odd_fill = 0,
- non_zero_fill,
- positive_fill,
- negative_fill,
- polygon_fill_type_max
-};
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
/*
This processor combines concepts from mapnik's
@@ -33,88 +32,171 @@ enum polygon_fill_type : std::uint8_t {
that would normally come from a style's symbolizers
*/
-template <typename T>
class processor : private mapnik::util::noncopyable
{
-public:
- typedef T backend_type;
private:
- backend_type & backend_;
mapnik::Map const& m_;
- mapnik::request const& m_req_;
+ std::string image_format_;
double scale_factor_;
- mapnik::view_transform t_;
double area_threshold_;
- bool strictly_simple_;
- std::string image_format_;
- scaling_method_e scaling_method_;
- bool painted_;
double simplify_distance_;
+ polygon_fill_type fill_type_;
+ scaling_method_e scaling_method_;
+ bool strictly_simple_;
bool multi_polygon_union_;
- ClipperLib::PolyFillType fill_type_;
bool process_all_rings_;
+ std::launch threading_mode_;
+
public:
- MAPNIK_VECTOR_INLINE processor(T & backend,
- mapnik::Map const& map,
- mapnik::request const& m_req,
- double scale_factor=1.0,
- unsigned offset_x=0,
- unsigned offset_y=0,
- double area_threshold=0.1,
- bool strictly_simple=false,
- std::string const& image_format="jpeg",
- scaling_method_e scaling_method=SCALING_NEAR
- );
-
- inline void set_simplify_distance(double dist)
+ processor(mapnik::Map const& map)
+ : m_(map),
+ image_format_("webp"),
+ scale_factor_(1.0),
+ area_threshold_(0.1),
+ simplify_distance_(0.0),
+ fill_type_(positive_fill),
+ scaling_method_(SCALING_BILINEAR),
+ strictly_simple_(true),
+ multi_polygon_union_(false),
+ process_all_rings_(false),
+ threading_mode_(std::launch::deferred) {}
+
+ MAPNIK_VECTOR_INLINE void update_tile(tile & t,
+ double scale_denom = 0.0,
+ int offset_x = 0,
+ int offset_y = 0);
+
+ merc_tile create_tile(std::uint64_t x,
+ std::uint64_t y,
+ std::uint64_t z,
+ std::uint32_t tile_size = 4096,
+ std::int32_t buffer_size = 0,
+ double scale_denom = 0.0,
+ int offset_x = 0,
+ int offset_y = 0)
+ {
+ merc_tile t(x, y, z, tile_size, buffer_size);
+ update_tile(t, scale_denom, offset_x, offset_y);
+ return t;
+ }
+
+ tile create_tile(mapnik::box2d<double> const & extent,
+ std::uint32_t tile_size = 4096,
+ std::int32_t buffer_size = 0,
+ double scale_denom = 0.0,
+ int offset_x = 0,
+ int offset_y = 0)
+ {
+ tile t(extent, tile_size, buffer_size);
+ update_tile(t, scale_denom, offset_x, offset_y);
+ return t;
+ }
+
+ void set_simplify_distance(double dist)
{
simplify_distance_ = dist;
}
+
+ double get_simplify_distance() const
+ {
+ return simplify_distance_;
+ }
+
+ void set_area_threshold(double value)
+ {
+ area_threshold_ = value;
+ }
+
+ double get_area_threshold() const
+ {
+ return area_threshold_;
+ }
+
+ void set_scale_factor(double value)
+ {
+ scale_factor_ = value;
+ }
+
+ double get_scale_factor() const
+ {
+ return scale_factor_;
+ }
- inline void set_process_all_rings(bool value)
+ void set_process_all_rings(bool value)
{
process_all_rings_ = value;
}
+
+ bool get_process_all_rings() const
+ {
+ return process_all_rings_;
+ }
- inline void set_multi_polygon_union(bool value)
+ void set_multi_polygon_union(bool value)
{
multi_polygon_union_ = value;
}
-
- inline double get_simplify_distance() const
+
+ bool get_multi_polygon_union() const
{
- return simplify_distance_;
+ return multi_polygon_union_;
+ }
+
+ void set_strictly_simple(bool value)
+ {
+ strictly_simple_ = value;
+ }
+
+ bool get_multipolygon_union() const
+ {
+ return strictly_simple_;
+ }
+
+ void set_fill_type(polygon_fill_type type)
+ {
+ fill_type_ = type;
+ }
+
+ polygon_fill_type set_fill_type() const
+ {
+ return fill_type_;
}
- inline mapnik::view_transform const& get_transform()
+ void set_scaling_method(scaling_method_e type)
{
- return t_;
+ scaling_method_ = type;
}
- MAPNIK_VECTOR_INLINE void set_fill_type(polygon_fill_type type);
-
- MAPNIK_VECTOR_INLINE void apply(double scale_denom=0.0);
-
- MAPNIK_VECTOR_INLINE bool painted() const;
-
- MAPNIK_VECTOR_INLINE void apply_to_layer(mapnik::layer const& lay,
- mapnik::projection const& proj0,
- double scale,
- double scale_denom,
- unsigned width,
- unsigned height,
- box2d<double> const& extent,
- int buffer_size);
-
- template <typename T2>
- MAPNIK_VECTOR_INLINE bool handle_geometry(T2 const& vs,
- mapnik::feature_impl const& feature,
- mapnik::geometry::geometry<double> const& geom,
- mapnik::box2d<int> const& tile_clipping_extent,
- mapnik::box2d<double> const& target_clipping_extent);
+ scaling_method_e set_scaling_method() const
+ {
+ return scaling_method_;
+ }
+
+ void set_image_format(std::string const& value)
+ {
+ image_format_ = value;
+ }
+
+ std::string const& get_image_format() const
+ {
+ return image_format_;
+ }
+
+ void set_threading_mode(std::launch mode)
+ {
+ threading_mode_ = mode;
+ }
+
+ std::launch set_threading_mode() const
+ {
+ return threading_mode_;
+ }
+
};
-}} // end ns
+} // end ns vector_tile_impl
+
+} // end ns mapnik
#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
#include "vector_tile_processor.ipp"
diff --git a/src/vector_tile_processor.ipp b/src/vector_tile_processor.ipp
index f521d67..c5bdc04 100644
--- a/src/vector_tile_processor.ipp
+++ b/src/vector_tile_processor.ipp
@@ -1,1443 +1,371 @@
-#include <mapnik/map.hpp>
-#include <mapnik/request.hpp>
-#include <mapnik/layer.hpp>
-#include <mapnik/query.hpp>
-#include <mapnik/geometry.hpp>
-#include <mapnik/vertex_adapters.hpp>
-#include <mapnik/feature.hpp>
-#include <mapnik/datasource.hpp>
-#include <mapnik/projection.hpp>
-#include <mapnik/proj_transform.hpp>
-#include <mapnik/scale_denominator.hpp>
-#include <mapnik/attribute_descriptor.hpp>
-#include <mapnik/feature_layer_desc.hpp>
-#include <mapnik/config.hpp>
+// mapnik-vector-tile
+#include "vector_tile_geometry_clipper.hpp"
+#include "vector_tile_geometry_feature.hpp"
+#include "vector_tile_geometry_intersects.hpp"
+#include "vector_tile_geometry_simplifier.hpp"
+#include "vector_tile_raster_clipper.hpp"
+#include "vector_tile_strategy.hpp"
+#include "vector_tile_tile.hpp"
+#include "vector_tile_layer.hpp"
+
+// mapnik
#include <mapnik/box2d.hpp>
-#include <mapnik/version.hpp>
-#include <mapnik/image_util.hpp>
-#include <mapnik/raster.hpp>
-#include <mapnik/warp.hpp>
-#include <mapnik/version.hpp>
+#include <mapnik/datasource.hpp>
+#include <mapnik/feature.hpp>
#include <mapnik/image_scaling.hpp>
-#include <mapnik/image_compositing.hpp>
-#include <mapnik/view_transform.hpp>
-#include <mapnik/util/noncopyable.hpp>
-#include <mapnik/transform_path_adapter.hpp>
-#include <mapnik/geometry_is_empty.hpp>
-#include <mapnik/geometry_adapters.hpp>
+#include <mapnik/layer.hpp>
+#include <mapnik/map.hpp>
#include <mapnik/geometry_transform.hpp>
-// http://www.angusj.com/delphi/clipper.php
-#include "clipper.hpp"
-
-#include "agg_rendering_buffer.h"
-#include "agg_pixfmt_rgba.h"
-#include "agg_pixfmt_gray.h"
-#include "agg_renderer_base.h"
-
+// boost
#include <boost/optional.hpp>
-#include <boost/geometry/algorithms/simplify.hpp>
-#include <boost/geometry/algorithms/intersection.hpp>
-
-#include <iostream>
-#include <string>
-#include <stdexcept>
-
-#include "vector_tile_strategy.hpp"
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
+// std
+#include <future>
-namespace mapnik { namespace vector_tile_impl {
-
-template <typename T>
-struct visitor_raster_processor
+namespace mapnik
{
- typedef T backend_type;
-private:
- mapnik::raster const& source_;
- mapnik::feature_impl const& feature_;
- box2d<double> const& target_ext_;
- box2d<double> const& ext_;
- backend_type & backend_;
- bool & painted_;
- mapnik::proj_transform const& prj_trans_;
- std::string const& image_format_;
- scaling_method_e scaling_method_;
- unsigned width_;
- unsigned height_;
- unsigned raster_width_;
- unsigned raster_height_;
- int start_x_;
- int start_y_;
-public:
- visitor_raster_processor(mapnik::raster const& source,
- mapnik::feature_impl const& feature,
- box2d<double> const& target_ext,
- box2d<double> const& ext,
- backend_type & backend,
- bool & painted,
- mapnik::proj_transform const& prj_trans,
- std::string const& image_format,
- scaling_method_e scaling_method,
- unsigned width,
- unsigned height,
- unsigned raster_width,
- unsigned raster_height,
- int start_x,
- int start_y)
- : source_(source),
- feature_(feature),
- target_ext_(target_ext),
- ext_(ext),
- backend_(backend),
- painted_(painted),
- prj_trans_(prj_trans),
- image_format_(image_format),
- scaling_method_(scaling_method),
- width_(width),
- height_(height),
- raster_width_(raster_width),
- raster_height_(raster_height),
- start_x_(start_x),
- start_y_(start_y) {}
- void operator() (mapnik::image_rgba8 & source_data)
- {
- mapnik::image_rgba8 data(raster_width_, raster_height_, true, true);
- mapnik::raster target(target_ext_, std::move(data), source_.get_filter_factor());
- mapnik::premultiply_alpha(source_data);
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_rgba8>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_rgba32_pre;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_rgba8 im_tile(width_, height_, true, true);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- mapnik::demultiply_alpha(im_tile);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- backend_.stop_tile_feature();
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray8 & source_data)
- {
- mapnik::image_gray8 data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray8>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray8;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray8);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray8s & source_data)
- {
- mapnik::image_gray8s data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray8s>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray8;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray8s);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray16 & source_data)
- {
- mapnik::image_gray16 data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray16>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray16;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray16);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray16s & source_data)
- {
- mapnik::image_gray16s data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray16s>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray16;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray16s);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray32 & source_data)
- {
- mapnik::image_gray32 data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray32>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray32;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray32);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray32s & source_data)
- {
- mapnik::image_gray32s data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray32s>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray32;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray32s);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray32f & source_data)
- {
- mapnik::image_gray32f data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray32f>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray32;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray32f);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray64 & source_data)
- {
- mapnik::image_gray64 data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray64>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray32;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray64);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray64s & source_data)
- {
- mapnik::image_gray64s data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray64s>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray32;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray64s);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (mapnik::image_gray64f & source_data)
- {
- mapnik::image_gray64f data(raster_width_, raster_height_);
- mapnik::raster target(target_ext_, data, source_.get_filter_factor());
- if (!prj_trans_.equal())
- {
- double offset_x = ext_.minx() - start_x_;
- double offset_y = ext_.miny() - start_y_;
- reproject_and_scale_raster(target, source_, prj_trans_,
- offset_x, offset_y,
- width_,
- scaling_method_);
- }
- else
- {
- double image_ratio_x = ext_.width() / source_data.width();
- double image_ratio_y = ext_.height() / source_data.height();
- scale_image_agg(util::get<image_gray64f>(target.data_),
- source_data,
- scaling_method_,
- image_ratio_x,
- image_ratio_y,
- 0.0,
- 0.0,
- source_.get_filter_factor());
- }
-
- using pixfmt_type = agg::pixfmt_gray32;
- using renderer_type = agg::renderer_base<pixfmt_type>;
-
- mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray64f);
- agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
- agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
- pixfmt_type src_pixf(src_buffer);
- pixfmt_type dst_pixf(dst_buffer);
- renderer_type ren(dst_pixf);
- ren.copy_from(src_pixf,0,start_x_, start_y_);
- backend_.start_tile_feature(feature_);
- backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_));
- painted_ = true;
- }
-
- void operator() (image_null &) const
- {
- throw std::runtime_error("Null data passed to visitor");
- }
-
-};
-
-/*
- This processor combines concepts from mapnik's
- feature_style_processor and agg_renderer. It
- differs in that here we only process layers in
- isolation of their styles, and because of this we need
- options for clipping and simplification, for example,
- that would normally come from a style's symbolizers
-*/
-
-template <typename T>
-processor<T>::processor(T & backend,
- mapnik::Map const& map,
- mapnik::request const& m_req,
- double scale_factor,
- unsigned offset_x,
- unsigned offset_y,
- double area_threshold,
- bool strictly_simple,
- std::string const& image_format,
- scaling_method_e scaling_method)
- : backend_(backend),
- m_(map),
- m_req_(m_req),
- scale_factor_(scale_factor),
- t_(m_req.width(),m_req.height(),m_req.extent(),offset_x,offset_y),
- area_threshold_(area_threshold),
- strictly_simple_(strictly_simple),
- image_format_(image_format),
- scaling_method_(scaling_method),
- painted_(false),
- simplify_distance_(0.0),
- multi_polygon_union_(false),
- fill_type_(ClipperLib::pftNonZero),
- process_all_rings_(false) {}
-
-template <typename T>
-void processor<T>::apply(double scale_denom)
+namespace vector_tile_impl
{
- mapnik::projection proj(m_.srs(),true);
- if (scale_denom <= 0.0)
- {
- scale_denom = mapnik::scale_denominator(m_req_.scale(),proj.is_geographic());
- }
- scale_denom *= scale_factor_;
- for (mapnik::layer const& lay : m_.layers())
- {
- if (lay.visible(scale_denom))
- {
- apply_to_layer(lay,
- proj,
- m_req_.scale(),
- scale_denom,
- m_req_.width(),
- m_req_.height(),
- m_req_.extent(),
- m_req_.buffer_size());
- }
- }
-}
-template <typename T>
-bool processor<T>::painted() const
-{
- return painted_;
-}
-
-template <typename T>
-void processor<T>::set_fill_type(polygon_fill_type type)
+namespace detail
{
- switch (type)
- {
- case polygon_fill_type_max:
- case even_odd_fill:
- fill_type_ = ClipperLib::pftEvenOdd;
- break;
- case non_zero_fill:
- fill_type_ = ClipperLib::pftNonZero;
- break;
- case positive_fill:
- fill_type_ = ClipperLib::pftPositive;
- break;
- case negative_fill:
- fill_type_ = ClipperLib::pftNegative;
- break;
- }
-}
-
-template <typename T>
-void processor<T>::apply_to_layer(mapnik::layer const& lay,
- mapnik::projection const& target_proj,
- double scale,
- double scale_denom,
- unsigned width,
- unsigned height,
- box2d<double> const& extent,
- int buffer_size)
+inline void create_geom_layer(tile_layer & layer,
+ double simplify_distance,
+ double area_threshold,
+ polygon_fill_type fill_type,
+ bool strictly_simple,
+ bool multi_polygon_union,
+ bool process_all_rings)
{
- mapnik::datasource_ptr ds = lay.datasource();
- if (!ds) return;
-
- 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
- {
- buffer_padding *= *layer_buffer_size;
- }
- else
- {
- buffer_padding *= buffer_size;
- }
- buffered_query_ext.width(query_ext.width() + buffer_padding);
- buffered_query_ext.height(query_ext.height() + buffer_padding);
+ layer_builder_pbf builder(layer.name(), layer.layer_extent(), layer.get_data());
- // ^^ now `buffered_query_ext` is actually buffered out.
+ // query for the features
+ mapnik::featureset_ptr features = layer.get_features();
- // clip buffered extent by maximum extent, if supplied
- // 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)
+ if (!features)
{
- buffered_query_ext.clip(*maximum_extent);
+ return;
}
- // 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::feature_ptr feature = features->next();
- mapnik::box2d<double> layer_ext = lay.envelope();
- 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))
- {
- // 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
- else if (prj_trans.equal())
- {
- early_return = true;
- }
- // next try intersection of layer extent back projected into map srs
- else if (prj_trans.backward(layer_ext, PROJ_ENVELOPE_POINTS) && buffered_query_ext.intersects(layer_ext))
- {
- layer_ext.clip(buffered_query_ext);
- // forward project layer extent back into native projection
- if (! prj_trans.forward(layer_ext, PROJ_ENVELOPE_POINTS))
- {
- std::cerr << "feature_style_processor: Layer=" << lay.name()
- << " extent=" << layer_ext << " in map projection "
- << " did not reproject properly back to layer projection";
- }
- }
- else
- {
- // if no intersection then nothing to do for layer
- early_return = true;
- }
- if (early_return)
+ if (!feature)
{
return;
}
-
- 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,
- height/qh);
- mapnik::query q(layer_ext,res,scale_denom,extent);
- mapnik::layer_descriptor lay_desc = ds->get_descriptor();
- for (mapnik::attribute_descriptor const& desc : lay_desc.get_descriptors())
+
+ using encoding_process = mapnik::vector_tile_impl::geometry_to_feature_pbf_visitor;
+ using clipping_process = mapnik::vector_tile_impl::geometry_clipper<encoding_process>;
+ if (simplify_distance > 0)
{
- q.add_property_name(desc.get_name());
- }
- mapnik::featureset_ptr features = ds->features(q);
-
- if (!features) return;
-
- mapnik::feature_ptr feature = features->next();
- if (feature)
- {
- backend_.start_tile_layer(lay.name());
- raster_ptr const& source = feature->get_raster();
- if (source)
- {
- box2d<double> target_ext = box2d<double>(source->ext_);
- prj_trans.backward(target_ext, PROJ_ENVELOPE_POINTS);
- box2d<double> ext = t_.forward(target_ext);
- int start_x = static_cast<int>(std::floor(ext.minx()+.5));
- int start_y = 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));
- int raster_width = end_x - start_x;
- int raster_height = end_y - start_y;
- if (raster_width > 0 && raster_height > 0)
- {
- visitor_raster_processor<T> visit(*source,
- *feature,
- target_ext,
- ext,
- backend_,
- painted_,
- prj_trans,
- image_format_,
- scaling_method_,
- width,
- height,
- raster_width,
- raster_height,
- start_x,
- start_y);
- mapnik::util::apply_visitor(visit, source->data_);
- }
- backend_.stop_tile_layer();
- return;
- }
- // vector pathway
- if (prj_trans.equal())
+ using simplifier_process = mapnik::vector_tile_impl::geometry_simplifier<clipping_process>;
+ if (layer.get_proj_transform().equal())
{
- 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());
+ using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy;
+ using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, simplifier_process>;
+ strategy_type vs(layer.get_view_transform());
+ mapnik::box2d<double> const& buffered_extent = layer.get_target_buffered_extent();
+ mapnik::geometry::point<double> p1_min(buffered_extent.minx(), buffered_extent.miny());
+ mapnik::geometry::point<double> p1_max(buffered_extent.maxx(), buffered_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);
+ double minx = std::min(p2_min.x, p2_max.x);
+ double maxx = std::max(p2_min.x, p2_max.x);
+ double miny = std::min(p2_min.y, p2_max.y);
+ double maxy = std::max(p2_min.y, p2_max.y);
+ mapnik::box2d<int> tile_clipping_extent(minx, miny, maxx, maxy);
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,
- target_clipping_extent))
- {
- painted_ = true;
- }
+ encoding_process encoder(*feature, builder);
+ clipping_process clipper(tile_clipping_extent,
+ area_threshold,
+ strictly_simple,
+ multi_polygon_union,
+ fill_type,
+ process_all_rings,
+ encoder);
+ simplifier_process simplifier(simplify_distance, clipper);
+ transform_type transformer(vs, buffered_extent, simplifier);
+ mapnik::util::apply_visitor(transformer, geom);
feature = features->next();
}
}
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());
+ using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy_proj;
+ using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, simplifier_process>;
+ mapnik::vector_tile_impl::vector_tile_strategy vs(layer.get_view_transform());
+ mapnik::box2d<double> const& buffered_extent = layer.get_target_buffered_extent();
+ mapnik::geometry::point<double> p1_min(buffered_extent.minx(), buffered_extent.miny());
+ mapnik::geometry::point<double> p1_max(buffered_extent.maxx(), buffered_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());
- prj_trans.forward(target_clipping_extent, PROJ_ENVELOPE_POINTS);
+ double minx = std::min(p2_min.x, p2_max.x);
+ double maxx = std::max(p2_min.x, p2_max.x);
+ double miny = std::min(p2_min.y, p2_max.y);
+ double maxy = std::max(p2_min.y, p2_max.y);
+ mapnik::box2d<int> tile_clipping_extent(minx, miny, maxx, maxy);
+ strategy_type vs2(layer.get_proj_transform(), layer.get_view_transform());
+ mapnik::box2d<double> const& trans_buffered_extent = layer.get_source_buffered_extent();
while (feature)
{
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,
- target_clipping_extent) > 0)
- {
- painted_ = true;
- }
+ encoding_process encoder(*feature, builder);
+ clipping_process clipper(tile_clipping_extent,
+ area_threshold,
+ strictly_simple,
+ multi_polygon_union,
+ fill_type,
+ process_all_rings,
+ encoder);
+ simplifier_process simplifier(simplify_distance, clipper);
+ transform_type transformer(vs2, trans_buffered_extent, simplifier);
+ mapnik::util::apply_visitor(transformer, geom);
feature = features->next();
}
}
- backend_.stop_tile_layer();
}
-}
-
-inline void process_polynode_branch(ClipperLib::PolyNode* polynode,
- mapnik::geometry::multi_polygon<std::int64_t> & mp,
- double area_threshold)
-{
- mapnik::geometry::polygon<std::int64_t> polygon;
- polygon.set_exterior_ring(std::move(polynode->Contour));
- if (polygon.exterior_ring.size() > 2) // Throw out invalid polygons
+ else
{
- double outer_area = ClipperLib::Area(polygon.exterior_ring);
- if (std::abs(outer_area) >= area_threshold)
+ if (layer.get_proj_transform().equal())
{
- // The view transform inverts the y axis so this should be positive still despite now
- // being clockwise for the exterior ring. If it is not lets invert it.
- if (outer_area > 0)
- {
- std::reverse(polygon.exterior_ring.begin(), polygon.exterior_ring.end());
- }
-
- // children of exterior ring are always interior rings
- for (auto * ring : polynode->Childs)
+ using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy;
+ using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, clipping_process>;
+ strategy_type vs(layer.get_view_transform());
+ mapnik::box2d<double> const& buffered_extent = layer.get_target_buffered_extent();
+ mapnik::geometry::point<double> p1_min(buffered_extent.minx(), buffered_extent.miny());
+ mapnik::geometry::point<double> p1_max(buffered_extent.maxx(), buffered_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);
+ double minx = std::min(p2_min.x, p2_max.x);
+ double maxx = std::max(p2_min.x, p2_max.x);
+ double miny = std::min(p2_min.y, p2_max.y);
+ double maxy = std::max(p2_min.y, p2_max.y);
+ mapnik::box2d<int> tile_clipping_extent(minx, miny, maxx, maxy);
+ while (feature)
{
- if (ring->Contour.size() < 3)
- {
- continue; // Throw out invalid holes
- }
- double inner_area = ClipperLib::Area(ring->Contour);
- if (std::abs(inner_area) < area_threshold)
- {
- continue;
- }
-
- if (inner_area < 0)
- {
- std::reverse(ring->Contour.begin(), ring->Contour.end());
- }
- polygon.add_hole(std::move(ring->Contour));
+ mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
+ encoding_process encoder(*feature, builder);
+ clipping_process clipper(tile_clipping_extent,
+ area_threshold,
+ strictly_simple,
+ multi_polygon_union,
+ fill_type,
+ process_all_rings,
+ encoder);
+ transform_type transformer(vs, buffered_extent, clipper);
+ mapnik::util::apply_visitor(transformer, geom);
+ feature = features->next();
}
- mp.emplace_back(std::move(polygon));
}
- }
- for (auto * ring : polynode->Childs)
- {
- for (auto * sub_ring : ring->Childs)
+ else
{
- process_polynode_branch(sub_ring, mp, area_threshold);
+ using strategy_type = mapnik::vector_tile_impl::vector_tile_strategy_proj;
+ using transform_type = mapnik::vector_tile_impl::transform_visitor<strategy_type, clipping_process>;
+ mapnik::vector_tile_impl::vector_tile_strategy vs(layer.get_view_transform());
+ mapnik::box2d<double> const& buffered_extent = layer.get_target_buffered_extent();
+ mapnik::geometry::point<double> p1_min(buffered_extent.minx(), buffered_extent.miny());
+ mapnik::geometry::point<double> p1_max(buffered_extent.maxx(), buffered_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);
+ double minx = std::min(p2_min.x, p2_max.x);
+ double maxx = std::max(p2_min.x, p2_max.x);
+ double miny = std::min(p2_min.y, p2_max.y);
+ double maxy = std::max(p2_min.y, p2_max.y);
+ mapnik::box2d<int> tile_clipping_extent(minx, miny, maxx, maxy);
+ strategy_type vs2(layer.get_proj_transform(), layer.get_view_transform());
+ mapnik::box2d<double> const& trans_buffered_extent = layer.get_source_buffered_extent();
+ while (feature)
+ {
+ mapnik::geometry::geometry<double> const& geom = feature->get_geometry();
+ encoding_process encoder(*feature, builder);
+ clipping_process clipper(tile_clipping_extent,
+ area_threshold,
+ strictly_simple,
+ multi_polygon_union,
+ fill_type,
+ process_all_rings,
+ encoder);
+ transform_type transformer(vs2, trans_buffered_extent, clipper);
+ mapnik::util::apply_visitor(transformer, geom);
+ feature = features->next();
+ }
}
}
+ layer.build(builder);
+ return;
}
-template <typename T>
-struct encoder_visitor
+inline void create_raster_layer(tile_layer & layer,
+ std::string const& image_format,
+ scaling_method_e scaling_method)
{
- typedef T backend_type;
- encoder_visitor(backend_type & backend,
- mapnik::feature_impl const& feature,
- mapnik::box2d<int> const& tile_clipping_extent,
- double area_threshold,
- bool strictly_simple,
- bool multi_polygon_union,
- ClipperLib::PolyFillType fill_type,
- bool process_all_rings) :
- backend_(backend),
- feature_(feature),
- tile_clipping_extent_(tile_clipping_extent),
- area_threshold_(area_threshold),
- strictly_simple_(strictly_simple),
- multi_polygon_union_(multi_polygon_union),
- fill_type_(fill_type),
- process_all_rings_(process_all_rings) {}
+ layer_builder_pbf builder(layer.name(), layer.layer_extent(), layer.get_data());
- bool operator() (mapnik::geometry::geometry_empty const&)
- {
- return false;
- }
+ // query for the features
+ mapnik::featureset_ptr features = layer.get_features();
- bool operator() (mapnik::geometry::geometry_collection<std::int64_t> & geom)
+ if (!features)
{
- bool painted = false;
- for (auto & g : geom)
- {
- if (mapnik::util::apply_visitor((*this), g))
- {
- painted = true;
- }
- }
- return painted;
+ return;
}
- bool operator() (mapnik::geometry::point<std::int64_t> const& geom)
- {
- backend_.start_tile_feature(feature_);
- backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POINT);
- bool painted = backend_.add_path(geom);
- backend_.stop_tile_feature();
- return painted;
- }
+ mapnik::feature_ptr feature = features->next();
- bool operator() (mapnik::geometry::multi_point<std::int64_t> const& geom)
+ if (!feature)
{
- bool painted = false;
- if (!geom.empty())
- {
- backend_.start_tile_feature(feature_);
- backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POINT);
- painted = backend_.add_path(geom);
- backend_.stop_tile_feature();
- }
- return painted;
+ return;
}
-
- bool operator() (mapnik::geometry::line_string<std::int64_t> & geom)
+
+ mapnik::raster_ptr const& source = feature->get_raster();
+ if (!source)
{
- bool painted = false;
- boost::geometry::unique(geom);
- if (geom.size() < 2)
- {
- // This is false because it means the original data was invalid
- return false;
- }
- std::deque<mapnik::geometry::line_string<int64_t>> result;
- 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());
- boost::geometry::intersection(clip_box,geom,result);
- if (!result.empty())
- {
- // Found some data in tile so painted is now true
- painted = true;
- // Add the data to the tile
- backend_.start_tile_feature(feature_);
- backend_.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
- for (auto const& ls : result)
- {
- backend_.add_path(ls);
- }
- backend_.stop_tile_feature();
- }
- return painted;
+ return;
}
- bool operator() (mapnik::geometry::multi_line_string<std::int64_t> & geom)
+ mapnik::box2d<double> target_ext = box2d<double>(source->ext_);
+
+ layer.get_proj_transform().backward(target_ext, PROJ_ENVELOPE_POINTS);
+ mapnik::box2d<double> ext = layer.get_view_transform().forward(target_ext);
+
+ int start_x = static_cast<int>(std::floor(ext.minx()+.5));
+ int start_y = 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));
+ int raster_width = end_x - start_x;
+ int raster_height = end_y - start_y;
+ if (raster_width > 0 && raster_height > 0)
+ {
+ builder.make_painted();
+ raster_clipper visit(*source,
+ target_ext,
+ ext,
+ layer.get_proj_transform(),
+ image_format,
+ scaling_method,
+ layer.layer_extent(),
+ layer.layer_extent(),
+ raster_width,
+ raster_height,
+ start_x,
+ start_y);
+ std::string buffer = mapnik::util::apply_visitor(visit, source->data_);
+ raster_to_feature(buffer, *feature, builder);
+ }
+ layer.build(builder);
+ return;
+}
+
+} // end ns detail
+
+MAPNIK_VECTOR_INLINE void processor::update_tile(tile & t,
+ double scale_denom,
+ int offset_x,
+ int offset_y)
+{
+ // Futures
+ std::vector<tile_layer> tile_layers;
+
+ for (mapnik::layer const& lay : m_.layers())
{
- bool painted = false;
- 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());
- bool first = true;
- boost::geometry::unique(geom);
- for (auto const& line : geom)
+ if (t.has_layer(lay.name()))
{
- if (line.size() < 2)
- {
- continue;
- }
- // If any line reaches here painted is now true because
- std::deque<mapnik::geometry::line_string<int64_t>> result;
- boost::geometry::intersection(clip_box,line,result);
- if (!result.empty())
- {
- if (first)
- {
- painted = true;
- first = false;
- backend_.start_tile_feature(feature_);
- backend_.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
- }
- for (auto const& ls : result)
- {
- backend_.add_path(ls);
- }
- }
+ continue;
}
- if (!first)
+ tile_layers.emplace_back(m_,
+ lay,
+ t.extent(),
+ t.tile_size(),
+ t.buffer_size(),
+ scale_factor_,
+ scale_denom,
+ offset_x,
+ offset_y);
+ if (!tile_layers.back().is_valid())
{
- backend_.stop_tile_feature();
+ t.add_empty_layer(lay.name());
+ tile_layers.pop_back();
+ continue;
}
- return painted;
}
-
- bool operator() (mapnik::geometry::polygon<std::int64_t> & geom)
+
+ if (threading_mode_ == std::launch::deferred)
{
- bool painted = false;
- if ((geom.exterior_ring.size() < 3) && !process_all_rings_)
+ for (auto & layer_ref : tile_layers)
{
- // Invalid geometry so will be false
- return false;
- }
- // Because of geometry cleaning and other methods
- // we automatically call this tile painted even if there is no intersections.
- painted = true;
- double clean_distance = 1.415;
- 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);
- if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
- {
- return painted;
- }
- // The view transform inverts the y axis so this should be positive still despite now
- // being clockwise for the exterior ring. If it is not lets invert it.
- if (outer_area > 0)
- {
- std::reverse(geom.exterior_ring.begin(), geom.exterior_ring.end());
- }
- ClipperLib::Clipper poly_clipper;
-
- if (strictly_simple_)
- {
- poly_clipper.StrictlySimple(true);
- }
- if (!poly_clipper.AddPath(geom.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
- {
- return painted;
- }
- for (auto & ring : geom.interior_rings)
- {
- if (ring.size() < 3)
+ if (layer_ref.get_ds()->type() == datasource::Vector)
{
- continue;
+ detail::create_geom_layer(layer_ref,
+ simplify_distance_,
+ area_threshold_,
+ fill_type_,
+ strictly_simple_,
+ multi_polygon_union_,
+ process_all_rings_
+ );
}
- ClipperLib::CleanPolygon(ring, clean_distance);
- double inner_area = ClipperLib::Area(ring);
- if (std::abs(inner_area) < area_threshold_)
+ else // Raster
{
- continue;
+ detail::create_raster_layer(layer_ref,
+ image_format_,
+ scaling_method_
+ );
}
- // This should be a negative area, the y axis is down, so the ring will be "CCW" rather
- // then "CW" after the view transform, but if it is not lets reverse it
- if (inner_area < 0)
- {
- std::reverse(ring.begin(), ring.end());
- }
- if (!poly_clipper.AddPath(ring, ClipperLib::ptSubject, true))
- {
- continue;
- }
- }
- if (!poly_clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
- {
- return painted;
- }
- ClipperLib::PolyTree polygons;
- poly_clipper.ReverseSolution(true);
- poly_clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type_, fill_type_);
- poly_clipper.Clear();
-
- mapnik::geometry::multi_polygon<std::int64_t> mp;
-
- for (auto * polynode : polygons.Childs)
- {
- process_polynode_branch(polynode, mp, area_threshold_);
- }
-
- if (mp.empty())
- {
- return painted;
}
-
- backend_.start_tile_feature(feature_);
- backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POLYGON);
-
- for (auto const& poly : mp)
- {
- backend_.add_path(poly);
- }
- backend_.stop_tile_feature();
- return painted;
}
-
- bool operator() (mapnik::geometry::multi_polygon<std::int64_t> & geom)
+ else
{
- bool painted = false;
- //mapnik::box2d<std::int64_t> bbox = mapnik::geometry::envelope(geom);
- if (geom.empty())
- {
- return painted;
- }
- // From this point on due to polygon cleaning etc, we just assume that the tile has some sort
- // of intersection and is painted.
- painted = true;
- double clean_distance = 1.415;
- 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());
- mapnik::geometry::multi_polygon<std::int64_t> mp;
-
- ClipperLib::Clipper clipper;
- if (multi_polygon_union_)
+ std::vector<std::future<void> > future_layers;
+ future_layers.reserve(tile_layers.size());
+
+ for (auto & layer_ref : tile_layers)
{
- for (auto & poly : geom)
+ if (layer_ref.get_ds()->type() == datasource::Vector)
{
- // Below we attempt to skip processing of all interior rings if the exterior
- // ring fails a variety of sanity checks for size and validity for AddPath
- // When `process_all_rings_=true` this optimization is disabled. This is needed when
- // the ring order of input polygons is potentially incorrect and where the
- // "exterior_ring" might actually be an incorrectly classified exterior ring.
- if (poly.exterior_ring.size() < 3 && !process_all_rings_)
- {
- continue;
- }
- ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
- double outer_area = ClipperLib::Area(poly.exterior_ring);
- if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
- {
- continue;
- }
- // The view transform inverts the y axis so this should be positive still despite now
- // being clockwise for the exterior ring. If it is not lets invert it.
- if (outer_area > 0)
- {
- std::reverse(poly.exterior_ring.begin(), poly.exterior_ring.end());
- }
- if (!clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
- {
- continue;
- }
- for (auto & ring : poly.interior_rings)
- {
- if (ring.size() < 3)
- {
- continue;
- }
- ClipperLib::CleanPolygon(ring, clean_distance);
- double inner_area = ClipperLib::Area(ring);
- if (std::abs(inner_area) < area_threshold_)
- {
- continue;
- }
- // This should be a negative area, the y axis is down, so the ring will be "CCW" rather
- // then "CW" after the view transform, but if it is not lets reverse it
- if (inner_area < 0)
- {
- std::reverse(ring.begin(), ring.end());
- }
- clipper.AddPath(ring, ClipperLib::ptSubject, true);
- }
+ future_layers.push_back(std::async(
+ threading_mode_,
+ detail::create_geom_layer,
+ std::ref(layer_ref),
+ simplify_distance_,
+ area_threshold_,
+ fill_type_,
+ strictly_simple_,
+ multi_polygon_union_,
+ process_all_rings_
+ ));
}
- if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
+ else // Raster
{
- return painted;
+ future_layers.push_back(std::async(
+ threading_mode_,
+ detail::create_raster_layer,
+ std::ref(layer_ref),
+ image_format_,
+ scaling_method_
+ ));
}
- ClipperLib::PolyTree polygons;
- if (strictly_simple_)
- {
- clipper.StrictlySimple(true);
- }
- clipper.ReverseSolution(true);
- clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type_, fill_type_);
- clipper.Clear();
-
- for (auto * polynode : polygons.Childs)
- {
- process_polynode_branch(polynode, mp, area_threshold_);
- }
- }
- else
- {
- for (auto & poly : geom)
- {
- // Below we attempt to skip processing of all interior rings if the exterior
- // ring fails a variety of sanity checks for size and validity for AddPath
- // When `process_all_rings_=true` this optimization is disabled. This is needed when
- // the ring order of input polygons is potentially incorrect and where the
- // "exterior_ring" might actually be an incorrectly classified exterior ring.
- if (poly.exterior_ring.size() < 3 && !process_all_rings_)
- {
- continue;
- }
- ClipperLib::CleanPolygon(poly.exterior_ring, clean_distance);
- double outer_area = ClipperLib::Area(poly.exterior_ring);
- if ((std::abs(outer_area) < area_threshold_) && !process_all_rings_)
- {
- continue;
- }
- // The view transform inverts the y axis so this should be positive still despite now
- // being clockwise for the exterior ring. If it is not lets invert it.
- if (outer_area > 0)
- {
- std::reverse(poly.exterior_ring.begin(), poly.exterior_ring.end());
- }
- if (!clipper.AddPath(poly.exterior_ring, ClipperLib::ptSubject, true) && !process_all_rings_)
- {
- continue;
- }
- for (auto & ring : poly.interior_rings)
- {
- if (ring.size() < 3)
- {
- continue;
- }
- ClipperLib::CleanPolygon(ring, clean_distance);
- double inner_area = ClipperLib::Area(ring);
- if (std::abs(inner_area) < area_threshold_)
- {
- continue;
- }
- // This should be a negative area, the y axis is down, so the ring will be "CCW" rather
- // then "CW" after the view transform, but if it is not lets reverse it
- if (inner_area < 0)
- {
- std::reverse(ring.begin(), ring.end());
- }
- if (!clipper.AddPath(ring, ClipperLib::ptSubject, true))
- {
- continue;
- }
- }
- if (!clipper.AddPath( clip_box, ClipperLib::ptClip, true ))
- {
- return painted;
- }
- ClipperLib::PolyTree polygons;
- if (strictly_simple_)
- {
- clipper.StrictlySimple(true);
- }
- clipper.ReverseSolution(true);
- clipper.Execute(ClipperLib::ctIntersection, polygons, fill_type_, fill_type_);
- clipper.Clear();
-
- for (auto * polynode : polygons.Childs)
- {
- process_polynode_branch(polynode, mp, area_threshold_);
- }
- }
- }
-
- if (mp.empty())
- {
- return painted;
- }
-
- backend_.start_tile_feature(feature_);
- backend_.current_feature_->set_type(vector_tile::Tile_GeomType_POLYGON);
-
- for (auto const& poly : mp)
- {
- backend_.add_path(poly);
}
- backend_.stop_tile_feature();
- return painted;
- }
-
- backend_type & backend_;
- mapnik::feature_impl const& feature_;
- mapnik::box2d<int> const& tile_clipping_extent_;
- double area_threshold_;
- bool strictly_simple_;
- bool multi_polygon_union_;
- ClipperLib::PolyFillType fill_type_;
- bool process_all_rings_;
-};
-
-template <typename T>
-struct simplify_visitor
-{
- typedef T backend_type;
- simplify_visitor(double simplify_distance,
- encoder_visitor<backend_type> & encoder) :
- encoder_(encoder),
- simplify_distance_(simplify_distance) {}
-
- bool operator() (mapnik::geometry::point<std::int64_t> const& geom)
- {
- return encoder_(geom);
- }
-
- bool operator() (mapnik::geometry::multi_point<std::int64_t> const& geom)
- {
- return encoder_(geom);
- }
-
- bool operator() (mapnik::geometry::line_string<std::int64_t> const& geom)
- {
- mapnik::geometry::line_string<std::int64_t> simplified;
- boost::geometry::simplify(geom,simplified,simplify_distance_);
- return encoder_(simplified);
- }
-
- bool operator() (mapnik::geometry::multi_line_string<std::int64_t> const& geom)
- {
- mapnik::geometry::multi_line_string<std::int64_t> simplified;
- boost::geometry::simplify(geom,simplified,simplify_distance_);
- return encoder_(simplified);
- }
-
- bool operator() (mapnik::geometry::polygon<std::int64_t> const& geom)
- {
- mapnik::geometry::polygon<std::int64_t> simplified;
- boost::geometry::simplify(geom,simplified,simplify_distance_);
- return encoder_(simplified);
- }
- bool operator() (mapnik::geometry::multi_polygon<std::int64_t> const& geom)
- {
- mapnik::geometry::multi_polygon<std::int64_t> simplified;
- boost::geometry::simplify(geom,simplified,simplify_distance_);
- return encoder_(simplified);
- }
-
- bool operator() (mapnik::geometry::geometry_collection<std::int64_t> const& geom)
- {
- bool painted = false;
- for (auto const& g : geom)
+ for (auto && lay_future : future_layers)
{
- if (mapnik::util::apply_visitor((*this), g))
+ if (!lay_future.valid())
{
- painted = true;
+ throw std::runtime_error("unexpected invalid async return");
}
+ lay_future.get();
}
- return painted;
}
- bool operator() (mapnik::geometry::geometry_empty const&)
+ for (auto & layer_ref : tile_layers)
{
- return false;
- }
-
- encoder_visitor<backend_type> & encoder_;
- unsigned simplify_distance_;
-};
-
-
-template <typename T> template <typename T2>
-bool processor<T>::handle_geometry(T2 const& vs,
- mapnik::feature_impl const& feature,
- mapnik::geometry::geometry<double> const& geom,
- mapnik::box2d<int> const& tile_clipping_extent,
- mapnik::box2d<double> const& target_clipping_extent)
-{
- // 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, target_clipping_extent);
- mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(skipping_transformer,geom);
- encoder_visitor<T> encoder(backend_,
- feature,
- tile_clipping_extent,
- area_threshold_,
- strictly_simple_,
- multi_polygon_union_,
- fill_type_,
- process_all_rings_);
- if (simplify_distance_ > 0)
- {
- simplify_visitor<T> simplifier(simplify_distance_,encoder);
- return mapnik::util::apply_visitor(simplifier,new_geom);
- }
- else
- {
- return mapnik::util::apply_visitor(encoder,new_geom);
+ t.add_layer(layer_ref);
}
}
-}} // end ns
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_projection.hpp b/src/vector_tile_projection.hpp
index 248411e..528fbe8 100644
--- a/src/vector_tile_projection.hpp
+++ b/src/vector_tile_projection.hpp
@@ -1,35 +1,45 @@
#ifndef __MAPNIK_VECTOR_TILE_PROJECTION_H__
#define __MAPNIK_VECTOR_TILE_PROJECTION_H__
-#include <cstdint>
-
+// mapnik-vector-tile
#include "vector_tile_config.hpp"
-namespace mapnik { namespace vector_tile_impl {
+// mapnik
+#include <mapnik/box2d.hpp>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
- class spherical_mercator
- {
- private:
- double tile_size_;
- public:
- MAPNIK_VECTOR_INLINE spherical_mercator(unsigned tile_size);
+class spherical_mercator
+{
+private:
+ double tile_size_;
+public:
+ spherical_mercator(unsigned tile_size)
+ : tile_size_(static_cast<double>(tile_size)) {}
- MAPNIK_VECTOR_INLINE void from_pixels(double shift, double & x, double & y);
+ MAPNIK_VECTOR_INLINE void from_pixels(double shift, double & x, double & y);
- MAPNIK_VECTOR_INLINE void xyz(int x,
- int y,
- int z,
- double & minx,
- double & miny,
- double & maxx,
- double & maxy);
- };
+ MAPNIK_VECTOR_INLINE void xyz(int x,
+ int y,
+ int z,
+ double & minx,
+ double & miny,
+ double & maxx,
+ double & maxy);
+};
-}} // end ns
+MAPNIK_VECTOR_INLINE mapnik::box2d<double> merc_extent(std::uint32_t tile_size, int x, int y, int z);
+
+} // end vector_tile_impl ns
+
+} // end mapnik ns
#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
#include "vector_tile_projection.ipp"
#endif
-
#endif // __MAPNIK_VECTOR_TILE_PROJECTION_H__
diff --git a/src/vector_tile_projection.ipp b/src/vector_tile_projection.ipp
index 1620872..995a195 100644
--- a/src/vector_tile_projection.ipp
+++ b/src/vector_tile_projection.ipp
@@ -1,17 +1,25 @@
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
#include <mapnik/box2d.hpp>
#include <mapnik/well_known_srs.hpp>
+
+// std
#include <cmath>
+#include <cstdint>
#ifndef M_PI
#define M_PI 3.141592653589793238462643
#endif
-namespace mapnik { namespace vector_tile_impl {
+namespace mapnik
+{
-spherical_mercator::spherical_mercator(unsigned tile_size)
- : tile_size_(static_cast<double>(tile_size)) {}
+namespace vector_tile_impl
+{
-void spherical_mercator::from_pixels(double shift, double & x, double & y)
+MAPNIK_VECTOR_INLINE void spherical_mercator::from_pixels(double shift, double & x, double & y)
{
double b = shift/2.0;
x = (x - b)/(shift/360.0);
@@ -19,13 +27,13 @@ void spherical_mercator::from_pixels(double shift, double & x, double & y)
y = R2D * (2.0 * std::atan(std::exp(g)) - M_PI_by2);
}
-void spherical_mercator::xyz(int x,
- int y,
- int z,
- double & minx,
- double & miny,
- double & maxx,
- double & maxy)
+MAPNIK_VECTOR_INLINE void spherical_mercator::xyz(int x,
+ int y,
+ int z,
+ double & minx,
+ double & miny,
+ double & maxx,
+ double & maxy)
{
minx = x * tile_size_;
miny = (y + 1.0) * tile_size_;
@@ -38,4 +46,14 @@ void spherical_mercator::xyz(int x,
lonlat2merc(&maxx,&maxy,1);
}
-}} // end ns
\ No newline at end of file
+MAPNIK_VECTOR_INLINE mapnik::box2d<double> merc_extent(std::uint32_t tile_size, int x, int y, int z)
+{
+ spherical_mercator merc(tile_size);
+ double minx,miny,maxx,maxy;
+ merc.xyz(x, y, z, minx, miny, maxx, maxy);
+ return mapnik::box2d<double>(minx,miny,maxx,maxy);
+}
+
+} // end vector_tile_impl ns
+
+} // end mapnik ns
diff --git a/src/vector_tile_raster_clipper.cpp b/src/vector_tile_raster_clipper.cpp
new file mode 100644
index 0000000..f5f7b07
--- /dev/null
+++ b/src/vector_tile_raster_clipper.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_raster_clipper.hpp"
+#include "vector_tile_raster_clipper.ipp"
diff --git a/src/vector_tile_raster_clipper.hpp b/src/vector_tile_raster_clipper.hpp
new file mode 100644
index 0000000..0a9ec9a
--- /dev/null
+++ b/src/vector_tile_raster_clipper.hpp
@@ -0,0 +1,102 @@
+#ifndef __MAPNIK_VECTOR_TILE_RASTER_CLIPPER_H__
+#define __MAPNIK_VECTOR_TILE_RASTER_CLIPPER_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/image_scaling.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/raster.hpp>
+
+// std
+#include <stdexcept>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+struct raster_clipper
+{
+private:
+ mapnik::raster const& source_;
+ box2d<double> const& target_ext_;
+ box2d<double> const& ext_;
+ mapnik::proj_transform const& prj_trans_;
+ std::string const& image_format_;
+ scaling_method_e scaling_method_;
+ unsigned width_;
+ unsigned height_;
+ unsigned raster_width_;
+ unsigned raster_height_;
+ int start_x_;
+ int start_y_;
+public:
+ raster_clipper(mapnik::raster const& source,
+ box2d<double> const& target_ext,
+ box2d<double> const& ext,
+ mapnik::proj_transform const& prj_trans,
+ std::string const& image_format,
+ scaling_method_e scaling_method,
+ unsigned width,
+ unsigned height,
+ unsigned raster_width,
+ unsigned raster_height,
+ int start_x,
+ int start_y)
+ : source_(source),
+ target_ext_(target_ext),
+ ext_(ext),
+ prj_trans_(prj_trans),
+ image_format_(image_format),
+ scaling_method_(scaling_method),
+ width_(width),
+ height_(height),
+ raster_width_(raster_width),
+ raster_height_(raster_height),
+ start_x_(start_x),
+ start_y_(start_y)
+ {
+ }
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_rgba8 & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray8 & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray8s & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray16 & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray16s & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray32 & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray32s & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray32f & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray64 & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray64s & source_data);
+
+ MAPNIK_VECTOR_INLINE std::string operator() (mapnik::image_gray64f & source_data);
+
+ std::string operator() (image_null &) const
+ {
+ throw std::runtime_error("Null data passed to visitor");
+ }
+
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_raster_clipper.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_RASTER_CLIPPER_H__
diff --git a/src/vector_tile_raster_clipper.ipp b/src/vector_tile_raster_clipper.ipp
new file mode 100644
index 0000000..173cca6
--- /dev/null
+++ b/src/vector_tile_raster_clipper.ipp
@@ -0,0 +1,523 @@
+// mapnik
+#include <mapnik/box2d.hpp>
+#include <mapnik/image.hpp>
+#include <mapnik/image_any.hpp>
+#include <mapnik/image_scaling.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/proj_transform.hpp>
+#include <mapnik/raster.hpp>
+#include <mapnik/warp.hpp>
+
+// agg
+#include "agg_rendering_buffer.h"
+#include "agg_pixfmt_rgba.h"
+#include "agg_pixfmt_gray.h"
+#include "agg_renderer_base.h"
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_rgba8 & source_data)
+{
+ mapnik::image_rgba8 data(raster_width_, raster_height_, true, true);
+ mapnik::raster target(target_ext_, std::move(data), source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ mapnik::premultiply_alpha(source_data);
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::demultiply_alpha(source_data);
+ return mapnik::save_to_string(source_data, image_format_);
+ }
+ else
+ {
+ mapnik::premultiply_alpha(source_data);
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_rgba8>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_rgba32_pre;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_rgba8 im_tile(width_, height_, true, true);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ mapnik::demultiply_alpha(im_tile);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray8 & source_data)
+{
+ mapnik::image_gray8 data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray8>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray8;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray8);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray8s & source_data)
+{
+ mapnik::image_gray8s data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray8s>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray8;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray8s);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray16 & source_data)
+{
+ mapnik::image_gray16 data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray16>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray16;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray16);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray16s & source_data)
+{
+ mapnik::image_gray16s data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray16s>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray16;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray16s);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray32 & source_data)
+{
+ mapnik::image_gray32 data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray32>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray32);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray32s & source_data)
+{
+ mapnik::image_gray32s data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray32s>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray32s);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray32f & source_data)
+{
+ mapnik::image_gray32f data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray32f>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray32f);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray64 & source_data)
+{
+ mapnik::image_gray64 data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray64>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray64);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray64s & source_data)
+{
+ mapnik::image_gray64s data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray64s>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray64s);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+MAPNIK_VECTOR_INLINE std::string raster_clipper::operator() (mapnik::image_gray64f & source_data)
+{
+ mapnik::image_gray64f data(raster_width_, raster_height_);
+ mapnik::raster target(target_ext_, data, source_.get_filter_factor());
+ if (!prj_trans_.equal())
+ {
+ double offset_x = ext_.minx() - start_x_;
+ double offset_y = ext_.miny() - start_y_;
+ reproject_and_scale_raster(target, source_, prj_trans_,
+ offset_x, offset_y,
+ width_,
+ scaling_method_);
+ }
+ else if ((raster_width_ == source_data.width()) && (raster_height_ == source_data.height()))
+ {
+ mapnik::image_any any(source_data);
+ return mapnik::save_to_string(any, image_format_);
+ }
+ else
+ {
+ double image_ratio_x = ext_.width() / source_data.width();
+ double image_ratio_y = ext_.height() / source_data.height();
+ scale_image_agg(util::get<image_gray64f>(target.data_),
+ source_data,
+ scaling_method_,
+ image_ratio_x,
+ image_ratio_y,
+ 0.0,
+ 0.0,
+ source_.get_filter_factor());
+ }
+
+ using pixfmt_type = agg::pixfmt_gray32;
+ using renderer_type = agg::renderer_base<pixfmt_type>;
+
+ mapnik::image_any im_tile(width_, height_, mapnik::image_dtype_gray64f);
+ agg::rendering_buffer dst_buffer(im_tile.bytes(), im_tile.width(), im_tile.height(), im_tile.row_size());
+ agg::rendering_buffer src_buffer(target.data_.bytes(),target.data_.width(), target.data_.height(), target.data_.row_size());
+ pixfmt_type src_pixf(src_buffer);
+ pixfmt_type dst_pixf(dst_buffer);
+ renderer_type ren(dst_pixf);
+ ren.copy_from(src_pixf,0,start_x_, start_y_);
+ return mapnik::save_to_string(im_tile, image_format_);
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_strategy.hpp b/src/vector_tile_strategy.hpp
index a8f535f..2701325 100644
--- a/src/vector_tile_strategy.hpp
+++ b/src/vector_tile_strategy.hpp
@@ -11,11 +11,9 @@
#include "clipper.hpp"
#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wunused-local-typedef"
+#include <mapnik/warning_ignore.hpp>
#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 {
@@ -27,10 +25,8 @@ static constexpr double coord_min = -1 * static_cast<double>(ClipperLib::hiRange
struct vector_tile_strategy
{
- vector_tile_strategy(view_transform const& tr,
- double scaling)
- : tr_(tr),
- scaling_(scaling) {}
+ vector_tile_strategy(view_transform const& tr)
+ : tr_(tr) {}
template <typename P1, typename P2>
inline bool apply(P1 const& p1, P2 & p2) const
@@ -39,8 +35,8 @@ struct vector_tile_strategy
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_);
+ x = std::round(x);
+ y = std::round(y);
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));
@@ -57,18 +53,14 @@ struct vector_tile_strategy
}
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)
+ view_transform const& tr)
: prj_trans_(prj_trans),
- tr_(tr),
- scaling_(scaling),
- not_equal_(!prj_trans_.equal()) {}
+ tr_(tr) {}
template <typename P1, typename P2>
inline bool apply(P1 const& p1, P2 & p2) const
@@ -77,10 +69,10 @@ struct vector_tile_strategy_proj
double x = boost::geometry::get<0>(p1);
double y = boost::geometry::get<1>(p1);
double z = 0.0;
- if (not_equal_ && !prj_trans_.backward(x, y, z)) return false;
+ if (!prj_trans_.backward(x, y, z)) return false;
tr_.forward(&x,&y);
- x = std::round(x * scaling_);
- y = std::round(y * scaling_);
+ x = std::round(x);
+ y = std::round(y);
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));
@@ -98,27 +90,50 @@ struct vector_tile_strategy_proj
proj_transform const& prj_trans_;
view_transform const& tr_;
- double const scaling_;
- bool not_equal_;
+};
+
+template <typename T>
+struct geom_out_visitor
+{
+ mapnik::geometry::geometry<T> geom;
+
+ template <typename T1>
+ void operator() (T1 const& g)
+ {
+ geom = mapnik::geometry::geometry<T>(g);
+ }
};
// TODO - avoid creating degenerate polygons when first/last point of ring is skipped
-template <typename TransformType>
-struct transform_visitor {
+template <typename TransformType, typename NextProcessor>
+struct transform_visitor
+{
+ TransformType const& tr_;
+ NextProcessor & next_;
+ box2d<double> const& target_clipping_extent_;
- transform_visitor(TransformType const& tr, box2d<double> const& target_clipping_extent) :
+ transform_visitor(TransformType const& tr,
+ box2d<double> const& target_clipping_extent,
+ NextProcessor & next) :
tr_(tr),
+ next_(next),
target_clipping_extent_(target_clipping_extent) {}
- inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::point<double> const& geom)
+ inline void operator() (mapnik::geometry::point<double> const& geom)
{
- if (!target_clipping_extent_.intersects(geom.x,geom.y)) return mapnik::geometry::geometry_empty();
+ if (!target_clipping_extent_.intersects(geom.x,geom.y))
+ {
+ return;
+ }
mapnik::geometry::point<std::int64_t> new_geom;
- if (!tr_.apply(geom,new_geom)) return mapnik::geometry::geometry_empty();
- return new_geom;
+ if (!tr_.apply(geom,new_geom))
+ {
+ return;
+ }
+ return next_(new_geom);
}
- inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::multi_point<double> const& geom)
+ inline void operator() (mapnik::geometry::multi_point<double> const& geom)
{
mapnik::geometry::multi_point<std::int64_t> new_geom;
new_geom.reserve(geom.size());
@@ -130,16 +145,19 @@ struct transform_visitor {
new_geom.push_back(std::move(pt2));
}
}
- if (new_geom.empty()) return mapnik::geometry::geometry_empty();
- return new_geom;
+ if (new_geom.empty())
+ {
+ return;
+ }
+ return next_(new_geom);
}
- inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::line_string<double> const& geom)
+ inline void operator() (mapnik::geometry::line_string<double> const& geom)
{
mapnik::box2d<double> geom_bbox = mapnik::geometry::envelope(geom);
if (!target_clipping_extent_.intersects(geom_bbox))
{
- return mapnik::geometry::geometry_empty();
+ return;
}
mapnik::geometry::line_string<std::int64_t> new_geom;
new_geom.reserve(geom.size());
@@ -151,10 +169,10 @@ struct transform_visitor {
new_geom.push_back(std::move(pt2));
}
}
- return new_geom;
+ return next_(new_geom);
}
- inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::multi_line_string<double> const& geom)
+ inline void operator() (mapnik::geometry::multi_line_string<double> const& geom)
{
mapnik::geometry::multi_line_string<std::int64_t> new_geom;
new_geom.reserve(geom.size());
@@ -174,16 +192,19 @@ struct transform_visitor {
}
new_geom.push_back(std::move(new_line));
}
- if (new_geom.empty()) return mapnik::geometry::geometry_empty();
- return new_geom;
+ if (new_geom.empty())
+ {
+ return;
+ }
+ return next_(new_geom);
}
- inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::polygon<double> const& geom)
+ inline void operator() (mapnik::geometry::polygon<double> const& geom)
{
mapnik::box2d<double> ext_bbox = mapnik::geometry::envelope(geom);
if (!target_clipping_extent_.intersects(ext_bbox))
{
- return mapnik::geometry::geometry_empty();
+ return;
}
mapnik::geometry::polygon<std::int64_t> new_geom;
new_geom.exterior_ring.reserve(geom.exterior_ring.size());
@@ -214,10 +235,10 @@ struct transform_visitor {
}
new_geom.interior_rings.push_back(std::move(new_ring));
}
- return new_geom;
+ return next_(new_geom);
}
- inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::multi_polygon<double> const& geom)
+ inline void operator() (mapnik::geometry::multi_polygon<double> const& geom)
{
mapnik::geometry::multi_polygon<std::int64_t> new_geom;
new_geom.reserve(geom.size());
@@ -259,27 +280,25 @@ struct transform_visitor {
}
new_geom.push_back(std::move(new_poly));
}
- if (new_geom.empty()) return mapnik::geometry::geometry_empty();
- return new_geom;
+ if (new_geom.empty())
+ {
+ return;
+ }
+ return next_(new_geom);
}
- inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::geometry_collection<double> const& geom)
+ inline void operator() (mapnik::geometry::geometry_collection<double> const& geom)
{
- mapnik::geometry::geometry_collection<std::int64_t> new_geom;
- new_geom.reserve(geom.size());
for (auto const& g : geom)
{
- new_geom.push_back(mapnik::util::apply_visitor((*this), g));
+ mapnik::util::apply_visitor((*this), g);
}
- return new_geom;
}
- inline mapnik::geometry::geometry<std::int64_t> operator() (mapnik::geometry::geometry_empty const& geom)
+ inline void operator() (mapnik::geometry::geometry_empty const&)
{
- return geom;
+ return;
}
- TransformType const& tr_;
- box2d<double> const& target_clipping_extent_;
};
}
diff --git a/src/vector_tile_tile.cpp b/src/vector_tile_tile.cpp
new file mode 100644
index 0000000..0843827
--- /dev/null
+++ b/src/vector_tile_tile.cpp
@@ -0,0 +1,2 @@
+#include "vector_tile_tile.hpp"
+#include "vector_tile_tile.ipp"
diff --git a/src/vector_tile_tile.hpp b/src/vector_tile_tile.hpp
new file mode 100644
index 0000000..0bbf2e5
--- /dev/null
+++ b/src/vector_tile_tile.hpp
@@ -0,0 +1,211 @@
+#ifndef __MAPNIK_VECTOR_TILE_TILE_H__
+#define __MAPNIK_VECTOR_TILE_TILE_H__
+
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+
+// mapnik
+#include <mapnik/box2d.hpp>
+
+// std
+#include <set>
+#include <string>
+#include <vector>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+// fwd declare
+class tile_layer;
+
+class tile
+{
+protected:
+ std::string buffer_;
+ std::set<std::string> painted_layers_;
+ std::set<std::string> empty_layers_;
+ std::set<std::string> layers_set_;
+ std::vector<std::string> layers_;
+ mapnik::box2d<double> extent_;
+ std::uint32_t tile_size_;
+ std::int32_t buffer_size_;
+
+public:
+ tile(mapnik::box2d<double> const& extent,
+ std::uint32_t tile_size = 4096,
+ std::int32_t buffer_size = 128)
+ : buffer_(),
+ painted_layers_(),
+ empty_layers_(),
+ layers_set_(),
+ layers_(),
+ extent_(extent),
+ tile_size_(tile_size),
+ buffer_size_(buffer_size) {}
+
+ tile(tile const& rhs) = default;
+
+ tile(tile && rhs) = default;
+
+ MAPNIK_VECTOR_INLINE bool add_layer(tile_layer const& layer);
+
+ void add_empty_layer(std::string const& name)
+ {
+ empty_layers_.insert(name);
+ }
+
+ const char * data() const
+ {
+ return buffer_.data();
+ }
+
+ std::size_t size() const
+ {
+ return buffer_.size();
+ }
+
+ std::string const& get_buffer() const
+ {
+ return buffer_;
+ }
+
+ double scale() const
+ {
+ if (tile_size_ > 0)
+ {
+ return extent_.width()/tile_size_;
+ }
+ return extent_.width();
+ }
+
+ box2d<double> get_buffered_extent() const
+ {
+ double extra = 2.0 * scale() * buffer_size_;
+ box2d<double> ext(extent_);
+ double extra_width = extent_.width() + extra;
+ double extra_height = extent_.height() + extra;
+ if (extra_width < 0.0)
+ {
+ extra_width = 0.0;
+ }
+ if (extra_height < 0.0)
+ {
+ extra_height = 0.0;
+ }
+ ext.width(extra_width);
+ ext.height(extra_height);
+ return ext;
+ }
+
+ void append_to_string(std::string & str) const
+ {
+ str.append(buffer_);
+ }
+
+ void serialize_to_string(std::string & str) const
+ {
+ str = buffer_;
+ }
+
+ bool is_painted() const
+ {
+ return !painted_layers_.empty();
+ }
+
+ bool is_empty() const
+ {
+ return layers_.empty();
+ }
+
+ box2d<double> const& extent() const
+ {
+ return extent_;
+ }
+
+ std::uint32_t tile_size() const
+ {
+ return tile_size_;
+ }
+
+ void tile_size(std::uint32_t val)
+ {
+ tile_size_ = val;
+ }
+
+ std::int32_t buffer_size() const
+ {
+ return buffer_size_;
+ }
+
+ void buffer_size(std::int32_t val)
+ {
+ buffer_size_ = val;
+ }
+
+ MAPNIK_VECTOR_INLINE bool append_layer_buffer(const char * data, std::size_t size, std::string const& name);
+
+ std::set<std::string> const& get_painted_layers() const
+ {
+ return painted_layers_;
+ }
+
+ std::set<std::string> const& get_empty_layers() const
+ {
+ return empty_layers_;
+ }
+
+ std::vector<std::string> const& get_layers() const
+ {
+ return layers_;
+ }
+
+ std::set<std::string> const& get_layers_set() const
+ {
+ return layers_set_;
+ }
+
+ bool same_extent(tile const& other) const
+ {
+ return extent_ == other.extent_;
+ }
+
+ void clear()
+ {
+ buffer_.clear();
+ empty_layers_.clear();
+ layers_.clear();
+ layers_set_.clear();
+ painted_layers_.clear();
+ }
+
+ bool has_layer(std::string const& name)
+ {
+ auto itr = layers_set_.find(name);
+ return itr != layers_set_.end();
+ }
+
+ protozero::pbf_reader get_reader() const
+ {
+ return protozero::pbf_reader(buffer_.data(), buffer_.size());
+ }
+
+ MAPNIK_VECTOR_INLINE bool layer_reader(std::string const& name, protozero::pbf_reader & layer_msg) const;
+
+ MAPNIK_VECTOR_INLINE bool layer_reader(std::size_t index, protozero::pbf_reader & layer_msg) const;
+};
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
+
+#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
+#include "vector_tile_tile.ipp"
+#endif
+
+#endif // __MAPNIK_VECTOR_TILE_TILE_H__
diff --git a/src/vector_tile_tile.ipp b/src/vector_tile_tile.ipp
new file mode 100644
index 0000000..d807686
--- /dev/null
+++ b/src/vector_tile_tile.ipp
@@ -0,0 +1,108 @@
+// mapnik-vector-tile
+#include "vector_tile_config.hpp"
+#include "vector_tile_layer.hpp"
+
+//protozero
+#include <protozero/pbf_reader.hpp>
+#include <protozero/pbf_writer.hpp>
+
+// std
+#include <set>
+#include <string>
+
+namespace mapnik
+{
+
+namespace vector_tile_impl
+{
+
+MAPNIK_VECTOR_INLINE bool tile::add_layer(tile_layer const& layer)
+{
+ std::string const& new_name = layer.name();
+ if (layer.is_empty())
+ {
+ empty_layers_.insert(new_name);
+ if (layer.is_painted())
+ {
+ painted_layers_.insert(new_name);
+ }
+ }
+ else
+ {
+ painted_layers_.insert(new_name);
+ auto p = layers_set_.insert(new_name);
+ if (!p.second)
+ {
+ // Layer already in tile
+ return false;
+ }
+ layers_.push_back(new_name);
+ protozero::pbf_writer tile_writer(buffer_);
+ tile_writer.add_message(Tile_Encoding::LAYERS, layer.get_data());
+ auto itr = empty_layers_.find(new_name);
+ if (itr != empty_layers_.end())
+ {
+ empty_layers_.erase(itr);
+ }
+ }
+ return true;
+}
+
+MAPNIK_VECTOR_INLINE bool tile::append_layer_buffer(const char * data, std::size_t size, std::string const& name)
+{
+ painted_layers_.insert(name);
+ auto p = layers_set_.insert(name);
+ if (!p.second)
+ {
+ // Layer already in tile
+ return false;
+ }
+ layers_.push_back(name);
+ protozero::pbf_writer writer(buffer_);
+ writer.add_message(3, data, size);
+ auto itr = empty_layers_.find(name);
+ if (itr != empty_layers_.end())
+ {
+ empty_layers_.erase(itr);
+ }
+ return true;
+}
+
+MAPNIK_VECTOR_INLINE bool tile::layer_reader(std::string const& name, protozero::pbf_reader & layer_msg) const
+{
+ protozero::pbf_reader item(buffer_.data(), buffer_.size());
+ while (item.next(Tile_Encoding::LAYERS))
+ {
+ layer_msg = item.get_message();
+ protozero::pbf_reader lay(layer_msg);
+ while (lay.next(Layer_Encoding::NAME))
+ {
+ if (lay.get_string() == name)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+MAPNIK_VECTOR_INLINE bool tile::layer_reader(std::size_t index, protozero::pbf_reader & layer_msg) const
+{
+ protozero::pbf_reader item(buffer_.data(), buffer_.size());
+ std::size_t idx = 0;
+ while (item.next(Tile_Encoding::LAYERS))
+ {
+ if (idx == index)
+ {
+ layer_msg = item.get_message();
+ return true;
+ }
+ ++idx;
+ item.skip();
+ }
+ return false;
+}
+
+} // end ns vector_tile_impl
+
+} // end ns mapnik
diff --git a/src/vector_tile_util.cpp b/src/vector_tile_util.cpp
deleted file mode 100644
index 6b05777..0000000
--- a/src/vector_tile_util.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "vector_tile_util.hpp"
-#include "vector_tile_util.ipp"
\ No newline at end of file
diff --git a/src/vector_tile_util.hpp b/src/vector_tile_util.hpp
deleted file mode 100644
index fae87dd..0000000
--- a/src/vector_tile_util.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#ifndef __MAPNIK_VECTOR_TILE_UTIL_H__
-#define __MAPNIK_VECTOR_TILE_UTIL_H__
-
-#include <string>
-
-#include "vector_tile_config.hpp"
-
-namespace vector_tile {
- class Tile;
-}
-
-namespace mapnik { namespace vector_tile_impl {
-
- MAPNIK_VECTOR_INLINE bool is_solid_extent(vector_tile::Tile const& tile, std::string & key);
- MAPNIK_VECTOR_INLINE bool is_solid_extent(std::string const& tile, std::string & key);
-
-}}
-
-#if !defined(MAPNIK_VECTOR_TILE_LIBRARY)
-#include "vector_tile_util.ipp"
-#endif
-
-#endif // __MAPNIK_VECTOR_TILE_UTIL_H__
diff --git a/src/vector_tile_util.ipp b/src/vector_tile_util.ipp
deleted file mode 100644
index ac747ac..0000000
--- a/src/vector_tile_util.ipp
+++ /dev/null
@@ -1,256 +0,0 @@
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#pragma GCC diagnostic ignored "-Wsign-conversion"
-#include "vector_tile.pb.h"
-#pragma GCC diagnostic pop
-
-#include <mapnik/vertex.hpp>
-#include <mapnik/box2d.hpp>
-#include <mapnik/geometry.hpp>
-
-#include "vector_tile_geometry_decoder.hpp"
-
-#include <protozero/pbf_reader.hpp>
-
-#include <stdexcept>
-#include <string>
-#include <sstream>
-
-namespace mapnik { namespace vector_tile_impl {
-
- // ported from http://stackoverflow.com/a/1968345/2333354
- bool line_intersects(int p0_x, int p0_y, int p1_x, int p1_y,
- int p2_x, int p2_y, int p3_x, int p3_y)
- {
- float s1_x = p1_x - p0_x;
- float s1_y = p1_y - p0_y;
- float s2_x = p3_x - p2_x;
- float s2_y = p3_y - p2_y;
- float a = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y));
- float b = (-s2_x * s1_y + s1_x * s2_y);
- if (b == 0 ) return false;
- float s = a / b;
- float c = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x));
- float d = (-s2_x * s1_y + s1_x * s2_y);
- if (d == 0 ) return false;
- float t = c / d;
- if (s >= 0 && s <= 1 && t >= 0 && t <= 1) return true;
- return false;
- }
-
- bool line_intersects_box(int p0_x, int p0_y, int p1_x, int p1_y, mapnik::box2d<int> box)
- {
- // possible early return for degenerate line
- if (p0_x == p1_x && p0_y == p1_y) return false;
-
- // check intersections with 4 sides of box
- /*
- 0,1 ------ 2,1
- | |
- | |
- 0,3 ------ 2,3
- */
- // bottom side: 0,3,2,3
- if (line_intersects(p0_x,p0_y,p1_x,p1_y,
- box[0],box[3],box[2],box[3])) {
- return true;
- }
- // right side: 2,3,2,1
- else if (line_intersects(p0_x,p0_y,p1_x,p1_y,
- box[2],box[3],box[2],box[1])) {
- return true;
- }
- // top side: 0,1,2,1
- else if (line_intersects(p0_x,p0_y,p1_x,p1_y,
- box[0],box[1],box[2],box[1])) {
- return true;
- }
- // left side: 0,1,0,3
- else if (line_intersects(p0_x,p0_y,p1_x,p1_y,
- box[0],box[1],box[0],box[3])) {
- return true;
- }
- return false;
- }
-
- bool is_solid_extent(vector_tile::Tile const& tile, std::string & key)
- {
- for (int i = 0; i < tile.layers_size(); i++)
- {
- vector_tile::Tile_Layer const& layer = tile.layers(i);
- unsigned extent = layer.extent();
- unsigned side = extent - 1;
- // TODO: need to account for buffer here
- // NOTE: insetting box by 2 pixels is needed to account for
- // rounding issues (at least on right and bottom)
- mapnik::box2d<int> container(2, 2, extent-2, extent-2);
- double extent_area = side * side;
- for (int j = 0; j < layer.features_size(); j++)
- {
- vector_tile::Tile_Feature const& feature = layer.features(j);
- int cmd = -1;
- const int cmd_bits = 3;
- unsigned length = 0;
- bool first = true;
- mapnik::box2d<int> box;
- int32_t x1 = 0;
- int32_t y1 = 0;
- int32_t x0 = 0;
- int32_t y0 = 0;
- for (int k = 0; k < feature.geometry_size();)
- {
- if (!length) {
- unsigned cmd_length = feature.geometry(k++);
- cmd = cmd_length & ((1 << cmd_bits) - 1);
- length = cmd_length >> cmd_bits;
- }
- if (length > 0) {
- length--;
- if (cmd == mapnik::SEG_MOVETO || cmd == mapnik::SEG_LINETO)
- {
- int32_t dx = feature.geometry(k++);
- int32_t dy = feature.geometry(k++);
- dx = ((dx >> 1) ^ (-(dx & 1)));
- dy = ((dy >> 1) ^ (-(dy & 1)));
- x1 += dx;
- y1 += dy;
- if ((x1 > 0 && x1 < static_cast<int>(side)) && (y1 > 0 && y1 < static_cast<int>(side))) {
- // We can abort early if this feature has a vertex that is
- // inside the bbox.
- return false;
- } else if (!first && line_intersects_box(x0,y0,x1,y1,container)) {
- // or if the last line segment intersects with the expected bounding rectangle
- return false;
- }
- x0 = x1;
- y0 = y1;
- if (first)
- {
- box.init(x1,y1,x1,y1);
- first = false;
- }
- else
- {
- box.expand_to_include(x1,y1);
- }
- }
- else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1)))
- {
- // pass
- }
- else
- {
- std::stringstream msg;
- msg << "Unknown command type (is_solid_extent): "
- << cmd;
- throw std::runtime_error(msg.str());
- }
- }
- }
- // Once we have only one clipped result polygon, we can compare the
- // areas and return early if they don't match.
- int geom_area = box.width() * box.height();
- if (geom_area < (extent_area - 32) )
- {
- return false;
- }
- if (i == 0) {
- key = layer.name();
- } else if (i > 0) {
- key += std::string("-") + layer.name();
- }
- }
- }
-
- // It's either empty or doesn't have features that have vertices that are
- // not on the border of the bbox.
- return true;
- }
-
- bool is_solid_extent(std::string const& tile, std::string & key)
- {
- protozero::pbf_reader item(tile);
- unsigned i = 0;
- while (item.next(3)) {
- protozero::pbf_reader layer_msg = item.get_message();
- unsigned extent = 0;
- std::string name;
- std::vector<protozero::pbf_reader> feature_collection;
- while (layer_msg.next())
- {
- switch(layer_msg.tag())
- {
- case 1:
- name = layer_msg.get_string();
- break;
- case 2:
- feature_collection.push_back(layer_msg.get_message());
- break;
- case 5:
- extent = layer_msg.get_uint32();
- break;
- default:
- layer_msg.skip();
- break;
- }
- }
- unsigned side = extent - 1;
- mapnik::box2d<int> container(2, 2, extent-2, extent-2);
- double extent_area = side * side;
- for (auto & features : feature_collection)
- {
- while (features.next(4)) {
- mapnik::vector_tile_impl::GeometryPBF<double> paths(features.get_packed_uint32(), 0, 0, 1, 1);
- mapnik::vector_tile_impl::GeometryPBF<double>::command cmd;
- double x0, y0, x1, y1;
- mapnik::box2d<int> box;
- bool first = true;
- std::uint32_t len;
- while ((cmd = paths.next(x1, y1, len)) != mapnik::vector_tile_impl::GeometryPBF<double>::end)
- {
- if (cmd == mapnik::vector_tile_impl::GeometryPBF<double>::move_to || cmd == mapnik::vector_tile_impl::GeometryPBF<double>::line_to)
- {
- if ((x1 > 0 && x1 < static_cast<int>(side)) && (y1 > 0 && y1 < static_cast<int>(side)))
- {
- // We can abort early if this feature has a vertex that is
- // inside the bbox.
- return false;
- }
- else if (!first && line_intersects_box(x0,y0,x1,y1,container))
- {
- // or if the last line segment intersects with the expected bounding rectangle
- return false;
- }
- x0 = x1;
- y0 = y1;
- if (first)
- {
- box.init(x1,y1,x1,y1);
- first = false;
- }
- else
- {
- box.expand_to_include(x1,y1);
- }
- }
- }
- // Once we have only one clipped result polygon, we can compare the
- // areas and return early if they don't match.
- int geom_area = box.width() * box.height();
- if (geom_area < (extent_area - 32) )
- {
- return false;
- }
- if (i == 0) {
- key = name;
- } else if (i > 0) {
- key += std::string("-") + name;
- }
- }
- }
- ++i;
- }
- return true;
- }
-
-}} // end ns
diff --git a/test/clipper_test.cpp b/test/clipper_test.cpp
index 4eb1c25..691dfdc 100644
--- a/test/clipper_test.cpp
+++ b/test/clipper_test.cpp
@@ -2,7 +2,6 @@
#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"
@@ -11,17 +10,22 @@
#include "catch.hpp"
#include "clipper.hpp"
-TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
+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;
+ unsigned tile_size = 4096;
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);
+ mapnik::view_transform tr(tile_size,
+ tile_size,
+ z15_extent,
+ 0,
+ 0);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr);
// 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,8 +45,12 @@ TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
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_proj vs(prj_trans, tr, path_multiplier);
+ mapnik::view_transform tr(std::numeric_limits<int>::max(),
+ std::numeric_limits<int>::max(),
+ z15_extent,
+ 0,
+ 0);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr);
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);
@@ -56,10 +64,19 @@ TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
REQUIRE( (-pt.y < ClipperLib::hiRange) );
}
}
+ /*
+ * This test was originally something that would require an
+ * exception for situations where scaling would cause an out of range
+ * situation, however, scaling was removed and the view transform
+ * is the limited to an int32 in range currently within mapnik.
{
// expected to trigger values above hirange
- double path_multiplier = 1000000000000.0;
- mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr, path_multiplier);
+ mapnik::view_transform tr(std::numeric_limits<int>::max(),
+ std::numeric_limits<int>::max(),
+ z15_extent,
+ 0,
+ 0);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr);
CHECK_THROWS( mapnik::geometry::transform<std::int64_t>(g, vs) );
mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
std::numeric_limits<double>::min(),
@@ -79,26 +96,36 @@ TEST_CASE( "vector_tile_strategy", "should not overflow" ) {
REQUIRE( (-pt.y < ClipperLib::hiRange) );
}
}
+ */
}
-TEST_CASE( "vector_tile_strategy2", "invalid mercator coord in interior ring" ) {
+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;
+ unsigned tile_size = 4096;
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);
+ mapnik::view_transform tr(tile_size,
+ tile_size,
+ z15_extent,
+ 0,
+ 0);
+ mapnik::vector_tile_impl::vector_tile_strategy_proj vs(prj_trans, tr);
CHECK_THROWS( mapnik::geometry::transform<std::int64_t>(geom, vs) );
mapnik::box2d<double> clip_extent(std::numeric_limits<double>::min(),
std::numeric_limits<double>::min(),
std::numeric_limits<double>::max(),
std::numeric_limits<double>::max());
- mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy_proj> skipping_transformer(vs, clip_extent);
- mapnik::geometry::geometry<std::int64_t> new_geom = mapnik::util::apply_visitor(skipping_transformer,geom);
+ mapnik::vector_tile_impl::geom_out_visitor<int64_t> out_geom;
+ mapnik::vector_tile_impl::transform_visitor<mapnik::vector_tile_impl::vector_tile_strategy_proj,
+ mapnik::vector_tile_impl::geom_out_visitor<int64_t> >
+ skipping_transformer(vs, clip_extent, out_geom);
+ mapnik::util::apply_visitor(skipping_transformer,geom);
+ mapnik::geometry::geometry<std::int64_t> new_geom = out_geom.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)
@@ -124,7 +151,8 @@ TEST_CASE( "vector_tile_strategy2", "invalid mercator coord in interior ring" )
}
}
-TEST_CASE( "clipper IntPoint", "should accept 64bit values" ) {
+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();
@@ -149,7 +177,8 @@ TEST_CASE( "clipper IntPoint", "should accept 64bit values" ) {
CHECK( (pt != pt3) );
}
-TEST_CASE( "clipper AddPath 1", "should not throw within range coords" ) {
+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
@@ -166,7 +195,8 @@ TEST_CASE( "clipper AddPath 1", "should not throw within range coords" ) {
CHECK( clipper.AddPath(clip_box,ClipperLib::ptClip,true) );
}
-TEST_CASE( "clipper AddPath 2", "should throw on out of range coords" ) {
+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;
@@ -189,8 +219,8 @@ TEST_CASE( "clipper AddPath 2", "should throw on out of range coords" ) {
}
}
-TEST_CASE( "clipper polytree error" ) {
-
+TEST_CASE("clipper polytree error")
+{
// http://sourceforge.net/p/polyclipping/bugs/132/
ClipperLib::Clipper clipper;
ClipperLib::Paths polygons;
@@ -241,6 +271,4 @@ TEST_CASE( "clipper polytree error" ) {
REQUIRE(solution.Childs[0]->Childs.size() == 1);
REQUIRE(solution.Childs[0]->Childs[0]->Childs.size() == 1);
REQUIRE(solution.Childs[0]->Childs[0]->Childs[0]->Childs.size() == 0);
-
}
-
diff --git a/test/data/0.0.0.vector-b.mvt b/test/data/0.0.0.vector-b.mvt
new file mode 100644
index 0000000..295bf58
Binary files /dev/null and b/test/data/0.0.0.vector-b.mvt differ
diff --git a/test/data/0.0.0.vector-b.pbf b/test/data/0.0.0.vector-b.pbf
deleted file mode 100644
index a26263f..0000000
Binary files a/test/data/0.0.0.vector-b.pbf and /dev/null differ
diff --git a/test/data/0.0.0.vector.pbf b/test/data/0.0.0.vector.mvt
similarity index 100%
rename from test/data/0.0.0.vector.pbf
rename to test/data/0.0.0.vector.mvt
diff --git a/test/data/tile_with_extra_feature_field.pbf b/test/data/tile_with_extra_feature_field.mvt
similarity index 100%
rename from test/data/tile_with_extra_feature_field.pbf
rename to test/data/tile_with_extra_feature_field.mvt
diff --git a/test/data/tile_with_extra_field.pbf b/test/data/tile_with_extra_field.mvt
similarity index 100%
rename from test/data/tile_with_extra_field.pbf
rename to test/data/tile_with_extra_field.mvt
diff --git a/test/data/tile_with_extra_layer_fields.pbf b/test/data/tile_with_extra_layer_fields.mvt
similarity index 100%
rename from test/data/tile_with_extra_layer_fields.pbf
rename to test/data/tile_with_extra_layer_fields.mvt
diff --git a/test/data/tile_with_invalid_layer_value_type.pbf b/test/data/tile_with_invalid_layer_value_type.mvt
similarity index 100%
rename from test/data/tile_with_invalid_layer_value_type.pbf
rename to test/data/tile_with_invalid_layer_value_type.mvt
diff --git a/test/data/tile_with_unexpected_geomtype.pbf b/test/data/tile_with_unexpected_geomtype.mvt
similarity index 100%
rename from test/data/tile_with_unexpected_geomtype.pbf
rename to test/data/tile_with_unexpected_geomtype.mvt
diff --git a/test/encoding_util.cpp b/test/encoding_util.cpp
deleted file mode 100644
index 7454f6f..0000000
--- a/test/encoding_util.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-#include <mapnik/vertex.hpp>
-#include <mapnik/geometry.hpp>
-#include <mapnik/geometry_adapters.hpp>
-#include <mapnik/vertex_processor.hpp>
-#include "vector_tile_geometry_decoder.hpp"
-#include "vector_tile_geometry_encoder.hpp"
-
-namespace {
-
-using namespace mapnik::geometry;
-
-struct print
-{
- void operator() (geometry_empty const&) const
- {
- std::cerr << "EMPTY" << std::endl;
- }
- template <typename T>
- void operator() (geometry_collection<T> const&) const
- {
- std::cerr << "COLLECTION" << std::endl;
- }
- template <typename T>
- void operator() (T const& geom) const
- {
- std::cerr << boost::geometry::wkt(geom) << std::endl;
- }
-};
-
-}
-
-struct show_path
-{
- std::string & str_;
- show_path(std::string & out) :
- str_(out) {}
-
- template <typename T>
- void operator()(T & path)
- {
- unsigned cmd = -1;
- double x = 0;
- double y = 0;
- std::ostringstream s;
- path.rewind(0);
- while ((cmd = path.vertex(&x, &y)) != mapnik::SEG_END)
- {
- switch (cmd)
- {
- case mapnik::SEG_MOVETO: s << "move_to("; break;
- case mapnik::SEG_LINETO: s << "line_to("; break;
- case mapnik::SEG_CLOSE: s << "close_path("; break;
- default: std::clog << "unhandled cmd " << cmd << "\n"; break;
- }
- s << x << "," << y << ")\n";
- }
- str_ += s.str();
- }
-};
-
-struct encoder_visitor
-{
- vector_tile::Tile_Feature & feature_;
- int32_t x_;
- int32_t y_;
- encoder_visitor(vector_tile::Tile_Feature & feature) :
- feature_(feature),
- x_(0),
- y_(0) { }
-
- void operator() (geometry_empty const&)
- {
- }
-
- void operator()(mapnik::geometry::geometry_collection<std::int64_t> const& path)
- {
- for (auto const& p : path)
- {
- mapnik::util::apply_visitor((*this), p);
- }
- }
-
- template <typename T>
- void operator()(T const& geom)
- {
- mapnik::vector_tile_impl::encode_geometry(geom,feature_,x_,y_);
- }
-};
-
-vector_tile::Tile_Feature geometry_to_feature(mapnik::geometry::geometry<std::int64_t> const& g)
-{
- vector_tile::Tile_Feature feature;
- if (g.template is<mapnik::geometry::point<std::int64_t>>() || g.template is<mapnik::geometry::multi_point<std::int64_t>>())
- {
- feature.set_type(vector_tile::Tile_GeomType_POINT);
- }
- else if (g.template is<mapnik::geometry::line_string<std::int64_t>>() || g.template is<mapnik::geometry::multi_line_string<std::int64_t>>())
- {
- feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
- }
- else if (g.template is<mapnik::geometry::polygon<std::int64_t>>() || g.template is<mapnik::geometry::multi_polygon<std::int64_t>>())
- {
- feature.set_type(vector_tile::Tile_GeomType_POLYGON);
- }
- else
- {
- throw std::runtime_error("could not detect valid geometry type");
- }
- encoder_visitor ap(feature);
- mapnik::util::apply_visitor(ap,g);
- return feature;
-}
-
-template <typename T>
-std::string decode_to_path_string(mapnik::geometry::geometry<T> const& g)
-{
- //mapnik::util::apply_visitor(print(), g2);
- using decode_path_type = mapnik::geometry::vertex_processor<show_path>;
- std::string out;
- show_path sp(out);
- mapnik::util::apply_visitor(decode_path_type(sp), g);
- return out;
-}
-
-std::string compare(mapnik::geometry::geometry<std::int64_t> const& g)
-{
- vector_tile::Tile_Feature feature = geometry_to_feature(g);
- mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
- auto g2 = mapnik::vector_tile_impl::decode_geometry<double>(geoms,feature.type());
- return decode_to_path_string(g2);
-}
diff --git a/test/encoding_util.hpp b/test/encoding_util.hpp
deleted file mode 100644
index 169ba49..0000000
--- a/test/encoding_util.hpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#include <mapnik/vertex.hpp>
-#include <mapnik/geometry.hpp>
-#include <mapnik/geometry_adapters.hpp>
-#include <mapnik/vertex_processor.hpp>
-#include "vector_tile_geometry_decoder.hpp"
-#include "vector_tile_geometry_encoder.hpp"
-
-namespace {
-
-using namespace mapnik::geometry;
-
-struct print;
-
-}
-
-struct show_path;
-
-struct encoder_visitor;
-
-vector_tile::Tile_Feature geometry_to_feature(mapnik::geometry::geometry<std::int64_t> const& g);
-
-template <typename T>
-std::string decode_to_path_string(mapnik::geometry::geometry<T> const& g);
-
-std::string compare(mapnik::geometry::geometry<std::int64_t> const& g);
diff --git a/test/fixtures/expected-4.png b/test/fixtures/expected-4.png
index 4db812f..89a5508 100644
Binary files a/test/fixtures/expected-4.png and b/test/fixtures/expected-4.png differ
diff --git a/test/geometry_encoding.cpp b/test/geometry_encoding.cpp
deleted file mode 100644
index bcc8313..0000000
--- a/test/geometry_encoding.cpp
+++ /dev/null
@@ -1,691 +0,0 @@
-#include "catch.hpp"
-
-#include "encoding_util.hpp"
-#include <mapnik/geometry_is_valid.hpp>
-#include <mapnik/geometry_is_simple.hpp>
-#include <mapnik/geometry_correct.hpp>
-#include <mapnik/geometry_envelope.hpp>
-#include <mapnik/util/geometry_to_wkt.hpp>
-
-//#include <mapnik/geometry_unique.hpp>
-
-/*
-
-low level encoding and decoding that skips clipping
-
-*/
-
-TEST_CASE( "point", "should round trip without changes" ) {
- mapnik::geometry::point<std::int64_t> g(0,0);
- std::string expected(
- "move_to(0,0)\n"
- );
- CHECK(compare(g) == expected);
-}
-
-TEST_CASE( "multi_point", "should round trip without changes" ) {
- mapnik::geometry::multi_point<std::int64_t> g;
- g.add_coord(0,0);
- g.add_coord(1,1);
- g.add_coord(2,2);
- std::string expected(
- "move_to(0,0)\n"
- "move_to(1,1)\n"
- "move_to(2,2)\n"
- );
- CHECK(compare(g) == expected);
-}
-
-TEST_CASE( "line_string", "should round trip without changes" ) {
- mapnik::geometry::line_string<std::int64_t> g;
- g.add_coord(0,0);
- g.add_coord(1,1);
- g.add_coord(100,100);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(1,1)\n"
- "line_to(100,100)\n"
- );
- CHECK(compare(g) == expected);
-}
-
-TEST_CASE( "multi_line_string", "should round trip without changes" ) {
- mapnik::geometry::multi_line_string<std::int64_t> g;
- {
- mapnik::geometry::line_string<std::int64_t> line;
- line.add_coord(0,0);
- line.add_coord(1,1);
- line.add_coord(100,100);
- g.emplace_back(std::move(line));
- }
- {
- mapnik::geometry::line_string<std::int64_t> line;
- line.add_coord(-10,-10);
- line.add_coord(-20,-20);
- line.add_coord(-100,-100);
- g.emplace_back(std::move(line));
- }
- std::string expected(
- "move_to(0,0)\n"
- "line_to(1,1)\n"
- "line_to(100,100)\n"
- "move_to(-10,-10)\n"
- "line_to(-20,-20)\n"
- "line_to(-100,-100)\n"
- );
- CHECK(compare(g) == expected);
-}
-
-/*TEST_CASE( "degenerate line_string", "should be culled" ) {
- mapnik::geometry::line_string<std::int64_t> line;
- line.add_coord(10,10);
-
- std::string wkt0;
- CHECK( mapnik::util::to_wkt(wkt0,line) );
- // wkt writer copes with busted line_string
- std::string expected_wkt0("LINESTRING(10 10)");
- CHECK( wkt0 == expected_wkt0);
-
- vector_tile::Tile_Feature feature = geometry_to_feature(line);
- CHECK( feature.geometry_size() == 0 );
- auto geom = mapnik::vector_tile_impl::decode_geometry<double>(feature,0.0,0.0,1.0,1.0);
- CHECK( geom.is<mapnik::geometry::geometry_empty>() );
-}*/
-
-/*
-TEST_CASE( "multi_line_string with degenerate first part", "should be culled" ) {
- mapnik::geometry::multi_line_string<std::int64_t> g;
- mapnik::geometry::line_string<std::int64_t> l1;
- l1.add_coord(0,0);
- g.push_back(std::move(l1));
- mapnik::geometry::line_string<std::int64_t> l2;
- l2.add_coord(2,2);
- l2.add_coord(3,3);
- g.push_back(std::move(l2));
-
- std::string wkt0;
- CHECK( mapnik::util::to_wkt(wkt0,g) );
- // wkt writer copes with busted line_string
- std::string expected_wkt0("MULTILINESTRING((0 0),(2 2,3 3))");
- CHECK( wkt0 == expected_wkt0);
-
- vector_tile::Tile_Feature feature = geometry_to_feature(g);
- CHECK( feature.geometry_size() == 6 );
- auto geom = mapnik::vector_tile_impl::decode_geometry<double>(feature,0.0,0.0,1.0,1.0);
-
- wkt0.clear();
- CHECK( mapnik::util::to_wkt(wkt0,geom) );
- CHECK( wkt0 == "LINESTRING(2 2,3 3)");
- CHECK( geom.is<mapnik::geometry::line_string<std::int64_t> >() );
-}*/
-
-/*TEST_CASE( "multi_line_string with degenerate second part", "should be culled" ) {
- mapnik::geometry::multi_line_string<std::int64_t> g;
- {
- mapnik::geometry::line_string<std::int64_t> line;
- line.add_coord(0,0);
- line.add_coord(1,1);
- line.add_coord(100,100);
- g.emplace_back(std::move(line));
- }
- {
- mapnik::geometry::line_string<std::int64_t> line;
- line.add_coord(-10,-10);
- g.emplace_back(std::move(line));
- }
-
- std::string wkt0;
- CHECK( mapnik::util::to_wkt(wkt0,g) );
- // wkt writer copes with busted line_string
- std::string expected_wkt0("MULTILINESTRING((0 0,1 1,100 100),(-10 -10))");
- CHECK( wkt0 == expected_wkt0);
-
- vector_tile::Tile_Feature feature = geometry_to_feature(g);
- CHECK( feature.type() == vector_tile::Tile_GeomType_LINESTRING );
- CHECK( feature.geometry_size() == 8 );
- auto geom = mapnik::vector_tile_impl::decode_geometry<double>(feature,0.0,0.0,1.0,1.0);
-
- wkt0.clear();
- CHECK( mapnik::util::to_wkt(wkt0,geom) );
- CHECK( wkt0 == "LINESTRING(0 0,1 1,100 100)");
- CHECK( geom.is<mapnik::geometry::line_string<std::int64_t> >() );
-}
-*/
-TEST_CASE( "polygon", "should round trip without changes" ) {
- mapnik::geometry::polygon<std::int64_t> g;
- g.exterior_ring.add_coord(0,0);
- g.exterior_ring.add_coord(1,1);
- g.exterior_ring.add_coord(100,100);
- g.exterior_ring.add_coord(0,0);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(1,1)\n"
- "line_to(100,100)\n"
- "close_path(0,0)\n"
- );
- CHECK(compare(g) == expected);
-}
-
-TEST_CASE( "polygon with degenerate exterior ring ", "should be culled" ) {
- mapnik::geometry::polygon<std::int64_t> p0;
- // invalid exterior ring
- p0.exterior_ring.add_coord(0,0);
- p0.exterior_ring.add_coord(0,10);
-
- std::string wkt0;
- CHECK( mapnik::util::to_wkt(wkt0,mapnik::geometry::geometry<std::int64_t>(p0)) );
- // wkt writer copes with busted polygon
- std::string expected_wkt0("POLYGON((0 0,0 10))");
- CHECK( wkt0 == expected_wkt0);
-
- vector_tile::Tile_Feature feature = geometry_to_feature(p0);
- // since first ring is degenerate the whole polygon should be culled
- mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
- auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(geoms, feature.type());
- CHECK( p1.is<mapnik::geometry::geometry_empty>() );
-}
-
-/*TEST_CASE( "polygon with degenerate exterior ring will drop valid interior ring", "should be culled" ) {
- mapnik::geometry::polygon<std::int64_t> p0;
- // invalid exterior ring
- p0.exterior_ring.add_coord(0,0);
- p0.exterior_ring.add_coord(0,10);
- // valid interior ring
- 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,p0) );
- // wkt writer copes with busted polygon
- std::string expected_wkt0("POLYGON((0 0,0 10),(-7 7,-3 7,-3 3,-7 3,-7 7))");
- CHECK( wkt0 == expected_wkt0);
-
- vector_tile::Tile_Feature feature = geometry_to_feature(p0);
- // since first ring is degenerate the whole polygon should be culled
- mapnik::vector_tile_impl::Geometry geoms(feature,0.0,0.0,1.0,1.0);
- auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(geoms, feature.type());
- CHECK( p1.is<mapnik::geometry::geometry_empty>() );
-}*/
-
-TEST_CASE( "polygon with valid exterior ring but degenerate interior ring", "should be culled" ) {
- mapnik::geometry::polygon<std::int64_t> p0;
- p0.exterior_ring.add_coord(0,0);
- p0.exterior_ring.add_coord(0,10);
- p0.exterior_ring.add_coord(-10,10);
- p0.exterior_ring.add_coord(-10,0);
- p0.exterior_ring.add_coord(0,0);
- // invalid interior ring
- mapnik::geometry::linear_ring<std::int64_t> hole;
- hole.add_coord(-7,7);
- hole.add_coord(-3,7);
- p0.add_hole(std::move(hole));
-
- std::string wkt0;
- CHECK( mapnik::util::to_wkt(wkt0,mapnik::geometry::geometry<std::int64_t>(p0)) );
- // wkt writer copes with busted polygon
- std::string expected_wkt0("POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7))");
- CHECK( wkt0 == expected_wkt0);
-
- vector_tile::Tile_Feature feature = geometry_to_feature(p0);
- mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
- auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(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
- auto const& holes = poly.interior_rings;
- CHECK( holes.empty() == true );
-}
-
-TEST_CASE( "polygon with valid exterior ring but one degenerate interior ring of two", "should be culled" ) {
- mapnik::geometry::polygon<std::int64_t> p0;
- p0.exterior_ring.add_coord(0,0);
- p0.exterior_ring.add_coord(0,10);
- p0.exterior_ring.add_coord(-10,10);
- p0.exterior_ring.add_coord(-10,0);
- p0.exterior_ring.add_coord(0,0);
- // invalid interior ring
- {
- mapnik::geometry::linear_ring<std::int64_t> hole;
- hole.add_coord(-7,7);
- hole.add_coord(-3,7);
- p0.add_hole(std::move(hole));
- }
- // valid interior ring
- {
- mapnik::geometry::linear_ring<std::int64_t> hole_in_hole;
- hole_in_hole.add_coord(-6,4);
- hole_in_hole.add_coord(-6,6);
- hole_in_hole.add_coord(-4,6);
- hole_in_hole.add_coord(-4,4);
- hole_in_hole.add_coord(-6,4);
- p0.add_hole(std::move(hole_in_hole));
- }
-
- std::string wkt0;
- CHECK( mapnik::util::to_wkt(wkt0,mapnik::geometry::geometry<std::int64_t>(p0)) );
- // wkt writer copes with busted polygon
- std::string expected_wkt0("POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7),(-6 4,-6 6,-4 6,-4 4,-6 4))");
- CHECK( wkt0 == expected_wkt0);
-
- vector_tile::Tile_Feature feature = geometry_to_feature(p0);
- mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
- auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(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
- auto const& holes = poly.interior_rings;
- // the second one is kept: somewhat dubious since it is actually a hole in a hole
- // but this is probably the best we can do
- 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(p0);
- mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
- auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(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
- mapnik::geometry::polygon<std::int64_t> p0;
- p0.exterior_ring.add_coord(0,0);
- p0.exterior_ring.add_coord(0,10);
- p0.exterior_ring.add_coord(-10,10);
- p0.exterior_ring.add_coord(-10,0);
- p0.exterior_ring.add_coord(0,0);
- 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));
-
- mapnik::box2d<double> extent = mapnik::geometry::envelope(p0);
-
- std::string wkt0;
- CHECK( mapnik::util::to_wkt(wkt0,mapnik::geometry::geometry<std::int64_t>(p0) ) );
- std::string expected_wkt0("POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7))");
- CHECK( wkt0 == expected_wkt0);
- // ensure correcting geometry has no effect
- // as a way of confirming the original was correct
- mapnik::geometry::correct(p0);
- wkt0.clear();
- CHECK( mapnik::util::to_wkt(wkt0,mapnik::geometry::geometry<std::int64_t>(p0)) );
- CHECK( wkt0 == expected_wkt0);
-
- vector_tile::Tile_Feature feature = geometry_to_feature(p0);
- mapnik::vector_tile_impl::Geometry<double> geoms(feature,0.0,0.0,1.0,1.0);
- auto p1 = mapnik::vector_tile_impl::decode_geometry<double>(geoms, feature.type());
- CHECK( p1.is<mapnik::geometry::polygon<double> >() );
- CHECK( extent == mapnik::geometry::envelope(p1) );
-
- wkt0.clear();
- CHECK( mapnik::util::to_wkt(wkt0,p1) );
- CHECK( wkt0 == expected_wkt0);
-
-}
-
-// We no longer drop coincidental points in the encoder it should be
-// done prior to reaching encoder.
-/*TEST_CASE( "test 2", "should drop coincident line_to commands" ) {
- mapnik::geometry::line_string<std::int64_t> g;
- g.add_coord(0,0);
- g.add_coord(3,3);
- g.add_coord(3,3);
- g.add_coord(3,3);
- g.add_coord(3,3);
- g.add_coord(4,4);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(3,3)\n"
- "line_to(4,4)\n"
- );
- CHECK( compare(g) == expected);
-}*/
-
-/*
-TEST_CASE( "test 2b", "should drop vertices" ) {
- mapnik::geometry::line_string<std::int64_t> g;
- g.add_coord(0,0);
- g.add_coord(0,0);
- g.add_coord(1,1);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(0,0)\n" // TODO - should we try to drop this?
- "line_to(1,1)\n"
- );
- CHECK(compare(g) == expected);
-}*/
-
-TEST_CASE( "test 3", "should not drop first move_to or last vertex in line" ) {
- mapnik::geometry::multi_line_string<std::int64_t> g;
- mapnik::geometry::line_string<std::int64_t> l1;
- l1.add_coord(0,0);
- l1.add_coord(1,1);
- g.push_back(std::move(l1));
- mapnik::geometry::line_string<std::int64_t> l2;
- l2.add_coord(2,2);
- l2.add_coord(3,3);
- g.push_back(std::move(l2));
-
- std::string expected(
- "move_to(0,0)\n"
- "line_to(1,1)\n"
- "move_to(2,2)\n"
- "line_to(3,3)\n"
- );
- CHECK(compare(g) == expected);
-}
-
-/*
-
-TEST_CASE( "test 4", "should not drop first move_to or last vertex in polygon" ) {
- mapnik::geometry::multi_polygon g;
- mapnik::geometry::polygon p0;
- p0.exterior_ring.add_coord(0,0);
- p0.exterior_ring.add_coord(1,0);
- g.push_back(std::move(p0));
-
- mapnik::geometry::polygon p1;
- p1.exterior_ring.add_coord(1,1);
- p1.exterior_ring.add_coord(0,1);
- p1.exterior_ring.add_coord(1,1);
- g.push_back(std::move(p1));
-
- std::string expected(
- "move_to(0,0)\n"
- "line_to(1,1)\n"
- "move_to(0,0)\n"
- "line_to(1,1)\n"
- "close_path(0,0)\n"
- );
- CHECK(compare(g,1000) == expected);
-}
-
-
-
-TEST_CASE( "test 5", "can drop duplicate move_to" ) {
-
- mapnik::geometry::path p(mapnik::path::LineString);
- g.move_to(0,0);
- g.move_to(1,1); // skipped
- g.line_to(4,4); // skipped
- g.line_to(5,5);
-
- std::string expected(
- "move_to(0,0)\n" // TODO - should we keep move_to(1,1) instead?
- "line_to(5,5)\n"
- );
- CHECK(compare(g,2) == expected);
-}
-
-
-TEST_CASE( "test 5b", "can drop duplicate move_to" ) {
- mapnik::geometry::line_string g;
- g.move_to(0,0);
- g.move_to(1,1);
- g.line_to(2,2);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(2,2)\n"
- );
- CHECK(compare(g,3) == expected);
-}
-
-TEST_CASE( "test 5c", "can drop duplicate move_to but not second" ) {
- mapnik::geometry::line_string g;
- g.move_to(0,0);
- g.move_to(1,1);
- g.line_to(2,2);
- g.move_to(3,3);
- g.line_to(4,4);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(2,2)\n"
- "move_to(3,3)\n"
- "line_to(4,4)\n"
- );
- CHECK(compare(g,3) == expected);
-}
-
-TEST_CASE( "test 6", "should not drop last line_to if repeated" ) {
- mapnik::geometry::line_string g;
- g.move_to(0,0);
- g.line_to(2,2);
- g.line_to(1000,1000); // skipped
- g.line_to(1001,1001); // skipped
- g.line_to(1001,1001);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(2,2)\n"
- "line_to(1001,1001)\n"
- );
- CHECK(compare(g,2) == expected);
-}
-
-TEST_CASE( "test 7", "ensure proper handling of skipping + close commands" ) {
- mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
- g.move_to(0,0);
- g.line_to(2,2);
- g.close_path();
- g.move_to(5,5);
- g.line_to(10,10); // skipped
- g.line_to(21,21);
- g.close_path();
- std::string expected(
- "move_to(0,0)\n"
- "line_to(2,2)\n"
- "close_path(0,0)\n"
- "move_to(5,5)\n"
- "line_to(21,21)\n"
- "close_path(0,0)\n"
- );
- CHECK(compare(g,100) == expected);
-}
-
-TEST_CASE( "test 8", "should drop repeated close commands" ) {
- mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
- g.move_to(0,0);
- g.line_to(2,2);
- g.close_path();
- g.close_path();
- g.close_path();
- std::string expected(
- "move_to(0,0)\n"
- "line_to(2,2)\n"
- "close_path(0,0)\n"
- );
- CHECK(compare(g,100) == expected);
-}
-
-TEST_CASE( "test 9a", "should not drop last vertex" ) {
- mapnik::geometry::line_string g;
- g.move_to(0,0);
- g.line_to(9,0); // skipped
- g.line_to(0,10);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(0,10)\n"
- );
- CHECK(compare(g,11) == expected);
-}
-
-TEST_CASE( "test 9b", "should not drop last vertex" ) {
- mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
- g.move_to(0,0);
- g.line_to(10,0); // skipped
- g.line_to(0,10);
- g.close_path();
- std::string expected(
- "move_to(0,0)\n"
- "line_to(0,10)\n"
- "close_path(0,0)\n"
- );
- CHECK(compare(g,11) == expected);
-}
-
-TEST_CASE( "test 9c", "should not drop last vertex" ) {
- mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
- g.move_to(0,0);
- g.line_to(0,10);
- g.close_path();
- std::string expected(
- "move_to(0,0)\n"
- "line_to(0,10)\n"
- "close_path(0,0)\n"
- );
- CHECK(compare(g,11) == expected);
-}
-
-TEST_CASE( "test 10", "should skip repeated close and coincident line_to commands" ) {
- mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
- g.move_to(0,0);
- g.line_to(10,10);
- g.line_to(10,10); // skipped
- g.line_to(20,20);
- g.line_to(20,20); // skipped, but added back and replaces previous
- g.close_path();
- g.close_path(); // skipped
- g.close_path(); // skipped
- g.close_path(); // skipped
- g.move_to(0,0);
- g.line_to(10,10);
- g.line_to(20,20);
- g.close_path();
- g.close_path(); // skipped
- std::string expected(
- "move_to(0,0)\n"
- "line_to(10,10)\n"
- "line_to(20,20)\n"
- "close_path(0,0)\n"
- "move_to(0,0)\n"
- "line_to(10,10)\n"
- "line_to(20,20)\n"
- "close_path(0,0)\n"
- );
- CHECK(compare(g,1) == expected);
-}
-
-TEST_CASE( "test 11", "should correctly encode multiple paths" ) {
- using namespace mapnik::vector_tile_impl;
- vector_tile::Tile_Feature feature0;
- int32_t x = 0;
- int32_t y = 0;
- unsigned path_multiplier = 1;
- unsigned tolerance = 10000;
- mapnik::geometry_type g0(mapnik::geometry_type::types::Polygon);
- g0.move_to(0,0);
- g0.line_to(-10,-10);
- g0.line_to(-20,-20);
- g0.close_path();
- mapnik::vertex_adapter va(g0);
- encode_geometry(va,(vector_tile::Tile_GeomType)g0.type(),feature0,x,y,tolerance,path_multiplier);
- CHECK(x == -20);
- CHECK(y == -20);
- mapnik::geometry_type g1(mapnik::geometry_type::types::Polygon);
- g1.move_to(1000,1000);
- g1.line_to(1010,1010);
- g1.line_to(1020,1020);
- g1.close_path();
- mapnik::vertex_adapter va1(g1);
- encode_geometry(va1,(vector_tile::Tile_GeomType)g1.type(),feature0,x,y,tolerance,path_multiplier);
- CHECK(x == 1020);
- CHECK(y == 1020);
- mapnik::geometry_type g2(mapnik::geometry_type::types::Polygon);
- double x0 = 0;
- double y0 = 0;
- decode_geometry<double>(feature0,g2,x0,y0,path_multiplier);
- mapnik::vertex_adapter va2(g2);
- std::string actual = show_path(va2);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(-20,-20)\n"
- "close_path(0,0)\n"
- "move_to(1000,1000)\n"
- "line_to(1020,1020)\n"
- "close_path(0,0)\n"
- );
- CHECK(actual == expected);
-}
-
-TEST_CASE( "test 12", "should correctly encode multiple paths" ) {
- using namespace mapnik::vector_tile_impl;
- vector_tile::Tile_Feature feature0;
- int32_t x = 0;
- int32_t y = 0;
- unsigned path_multiplier = 1;
- unsigned tolerance = 10;
- mapnik::geometry_type g(mapnik::geometry_type::types::Polygon);
- g.move_to(0,0);
- g.line_to(100,0);
- g.line_to(100,100);
- g.line_to(0,100);
- g.line_to(0,5);
- g.line_to(0,0);
- g.close_path();
- g.move_to(20,20);
- g.line_to(20,60);
- g.line_to(60,60);
- g.line_to(60,20);
- g.line_to(25,20);
- g.line_to(20,20);
- g.close_path();
- mapnik::vertex_adapter va(g);
- encode_geometry(va,(vector_tile::Tile_GeomType)g.type(),feature0,x,y,tolerance,path_multiplier);
-
- mapnik::geometry_type g2(mapnik::geometry_type::types::Polygon);
- double x0 = 0;
- double y0 = 0;
- decode_geometry<double>(feature0,g2,x0,y0,path_multiplier);
- mapnik::vertex_adapter va2(g2);
- std::string actual = show_path(va2);
- std::string expected(
- "move_to(0,0)\n"
- "line_to(100,0)\n"
- "line_to(100,100)\n"
- "line_to(0,100)\n"
- "line_to(0,0)\n"
- "close_path(0,0)\n"
- "move_to(20,20)\n"
- "line_to(20,60)\n"
- "line_to(60,60)\n"
- "line_to(60,20)\n"
- "line_to(20,20)\n"
- "close_path(0,0)\n"
- );
- CHECK(actual == expected);
-}
-*/
diff --git a/test/geometry_visual_test.cpp b/test/geometry_visual_test.cpp
index 1692c40..16698ea 100644
--- a/test/geometry_visual_test.cpp
+++ b/test/geometry_visual_test.cpp
@@ -1,32 +1,37 @@
-#include <iostream>
-#include <fstream>
-#include <sstream>
-#include <cmath>
-#include <mapnik/util/fs.hpp>
-#include <mapnik/projection.hpp>
-#include <mapnik/geometry_transform.hpp>
-#include <mapnik/util/geometry_to_geojson.hpp>
-#include <mapnik/geometry_correct.hpp>
-#include <mapnik/util/file_io.hpp>
-#include <mapnik/save_map.hpp>
-#include <mapnik/geometry_reprojection.hpp>
-#include <mapnik/geometry_strategy.hpp>
-#include <mapnik/proj_strategy.hpp>
-#include <mapnik/view_strategy.hpp>
-#include <mapnik/geometry_is_valid.hpp>
-#include <mapnik/geometry_is_simple.hpp>
-
+// catch
#include "catch.hpp"
-#include "encoding_util.hpp"
-#include "test_utils.hpp"
+
+// mapnik-vector-tile
#include "vector_tile_strategy.hpp"
#include "vector_tile_processor.hpp"
-#include "vector_tile_backend_pbf.hpp"
-#include "vector_tile_projection.hpp"
#include "vector_tile_geometry_decoder.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_simple.hpp>
+#include <mapnik/geometry_is_valid.hpp>
+#include <mapnik/json/geometry_parser.hpp>
+#include <mapnik/save_map.hpp>
+#include <mapnik/util/file_io.hpp>
+#include <mapnik/util/fs.hpp>
+#include <mapnik/util/geometry_to_geojson.hpp>
+
+// boost
#include <boost/filesystem/operations.hpp>
-void clip_geometry(std::string const& file,
+// test utils
+#include "test_utils.hpp"
+#include "geometry_equal.hpp"
+
+// std
+#include <cmath>
+#include <fstream>
+#include <sstream>
+
+// protozero
+#include <protozero/pbf_reader.hpp>
+
+void clip_geometry(mapnik::Map const& map,
+ std::string const& file,
mapnik::box2d<double> const& bbox,
int simplify_distance,
bool strictly_simple,
@@ -34,74 +39,86 @@ void clip_geometry(std::string const& file,
bool mpu,
bool process_all)
{
- typedef mapnik::vector_tile_impl::backend_pbf backend_type;
- typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
- typedef vector_tile::Tile tile_type;
+ unsigned tile_size = 4096;
+ int buffer_size = 0;
std::string geojson_string;
- std::shared_ptr<mapnik::memory_datasource> ds = testing::build_geojson_ds(file);
- tile_type tile;
- backend_type backend(tile,1000);
-
- mapnik::Map map(256,256,"+init=epsg:4326");
- mapnik::layer lyr("layer","+init=epsg:4326");
- lyr.set_datasource(ds);
- map.add_layer(lyr);
- map.zoom_to_box(bbox);
- mapnik::request m_req(map.width(),map.height(),map.get_current_extent());
- renderer_type ren(
- backend,
- map,
- m_req,
- 1.0,
- 0,
- 0,
- 0.1,
- strictly_simple
- );
+
+ mapnik::vector_tile_impl::processor ren(map);
+ // TODO - test these booleans https://github.com/mapbox/mapnik-vector-tile/issues/165
+ ren.set_strictly_simple(strictly_simple);
ren.set_simplify_distance(simplify_distance);
ren.set_fill_type(fill_type);
- // TODO - test these booleans https://github.com/mapbox/mapnik-vector-tile/issues/165
ren.set_process_all_rings(process_all);
ren.set_multi_polygon_union(mpu);
- ren.apply();
+
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(bbox, tile_size, buffer_size);
+ mapnik::geometry::geometry<double> geom4326_pbf;
+
std::string buffer;
- tile.SerializeToString(&buffer);
- if (buffer.size() > 0 && tile.layers_size() > 0 && tile.layers_size() != false)
+ out_tile.serialize_to_string(buffer);
+ if (!buffer.empty())
{
- vector_tile::Tile_Layer const& layer = tile.layers(0);
- if (layer.features_size() != false)
+ protozero::pbf_reader tile_reader(buffer);
+ if(!tile_reader.next(mapnik::vector_tile_impl::Tile_Encoding::LAYERS))
{
- vector_tile::Tile_Feature const& f = layer.features(0);
- mapnik::vector_tile_impl::Geometry<double> geoms(f,0.0,0.0,32.0,32.0);
- mapnik::geometry::geometry<double> geom = mapnik::vector_tile_impl::decode_geometry<double>(geoms,f.type());
- mapnik::geometry::scale_strategy ss(1.0/32.0);
- mapnik::view_transform vt(256, 256, bbox);
- mapnik::unview_strategy uvs(vt);
- using sg_type = strategy_group_first<mapnik::geometry::scale_strategy, mapnik::unview_strategy >;
- sg_type sg(ss, uvs);
- mapnik::geometry::geometry<double> geom4326 = transform<double>(geom, sg);
+ throw std::runtime_error("tile buffer contains no layer!");
+ }
+ protozero::pbf_reader layer_reader = tile_reader.get_message();
+ if (layer_reader.next(mapnik::vector_tile_impl::Layer_Encoding::FEATURES))
+ {
+ protozero::pbf_reader feature_reader = layer_reader.get_message();
+ int32_t geometry_type = mapnik::vector_tile_impl::Geometry_Type::UNKNOWN;
+ std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+ while (feature_reader.next())
+ {
+ if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::GEOMETRY)
+ {
+ geom_itr = feature_reader.get_packed_uint32();
+ }
+ else if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::TYPE)
+ {
+ geometry_type = feature_reader.get_enum();
+ }
+ else
+ {
+ feature_reader.skip();
+ }
+ }
+ double sx = static_cast<double>(tile_size) / bbox.width();
+ double sy = static_cast<double>(tile_size) / bbox.height();
+ double i_x = bbox.minx();
+ double i_y = bbox.maxy();
+
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms2_pbf(geom_itr, i_x, i_y, 1.0 * sx, -1.0 * sy);
+ geom4326_pbf = mapnik::vector_tile_impl::decode_geometry(geoms2_pbf, geometry_type, 2);
+ //mapnik::vector_tile_impl::GeometryPBF<double> geoms2_pbf(geom_itr, 0.0, 0.0, 1.0, 1.0);
+ //geom4326_pbf = std::move(mapnik::vector_tile_impl::decode_geometry(geoms2_pbf, geometry_type, 2));
+
std::string reason;
std::string is_valid = "false";
std::string is_simple = "false";
- if (mapnik::geometry::is_valid(geom4326, reason))
+ if (mapnik::geometry::is_valid(geom4326_pbf, reason))
+ {
is_valid = "true";
- if (mapnik::geometry::is_simple(geom4326))
+ }
+ if (mapnik::geometry::is_simple(geom4326_pbf))
+ {
is_simple = "true";
-
+ }
unsigned int n_err = 0;
- mapnik::util::to_geojson(geojson_string,geom4326);
+ mapnik::util::to_geojson(geojson_string,geom4326_pbf);
geojson_string = geojson_string.substr(0, geojson_string.size()-1);
geojson_string += ",\"properties\":{\"is_valid\":"+is_valid+", \"is_simple\":"+is_simple+", \"message\":\""+reason+"\"}}";
}
else
{
- geojson_string = "{\"type\": \"Feature\", \"coordinates\":null, \"properties\":{\"message\":\"Tile layer had no features\"}}";
+ geojson_string = "{\"type\": \"Point\", \"coordinates\":[], \"properties\":{\"message\":\"Tile layer had no features\"}}";
}
}
else
{
- geojson_string = "{\"type\": \"Feature\", \"coordinates\":null, \"properties\":{\"message\":\"Tile had no layers\"}}";
+ geojson_string = "{\"type\": \"Point\", \"coordinates\":[], \"properties\":{\"message\":\"Tile had no layers\"}}";
}
std::string fixture_name = mapnik::util::basename(file);
@@ -137,11 +154,15 @@ void clip_geometry(std::string const& file,
mapnik::util::file input(file_path);
if (!input.open())
{
- throw std::runtime_error("failed to open geojson");
+ throw std::runtime_error("failed to open test geojson");
}
- mapnik::geometry::geometry<double> geom;
std::string expected_string(input.data().get(), input.size());
+ mapnik::geometry::geometry<double> geom_expected;
CHECK(expected_string == geojson_string);
+ if (mapnik::json::from_geojson(expected_string, geom_expected))
+ {
+ assert_g_equal(geom_expected, geom4326_pbf);
+ }
}
}
@@ -223,9 +244,8 @@ mapnik::box2d<double> zoomed_out(mapnik::box2d<double> const& bbox)
return new_bbox;
}
-TEST_CASE( "geometries", "should reproject, clip, and simplify")
+TEST_CASE("geometries visual tests")
{
-
std::vector<std::string> geometries = mapnik::util::list_directory("./test/geometry-test-data/input");
std::vector<std::string> benchmarks = mapnik::util::list_directory("./test/geometry-test-data/benchmark");
if (std::getenv("BENCHMARK") != nullptr)
@@ -234,8 +254,12 @@ TEST_CASE( "geometries", "should reproject, clip, and simplify")
}
for (std::string const& file: geometries)
{
- std::shared_ptr<mapnik::memory_datasource> ds = testing::build_geojson_ds(file);
+ mapnik::datasource_ptr ds = testing::build_geojson_fs_ds(file);
mapnik::box2d<double> bbox = ds->envelope();
+ mapnik::Map map(256, 256, "+init=epsg:4326");
+ mapnik::layer lyr("layer","+init=epsg:4326");
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
for (int simplification_distance : std::vector<int>({0, 4, 8}))
{
for (bool strictly_simple : std::vector<bool>({false, true}))
@@ -245,19 +269,19 @@ TEST_CASE( "geometries", "should reproject, clip, and simplify")
types.emplace_back(mapnik::vector_tile_impl::non_zero_fill);
types.emplace_back(mapnik::vector_tile_impl::positive_fill);
types.emplace_back(mapnik::vector_tile_impl::negative_fill);
- for (auto type : types)
+ for (auto const& type : types)
{
for (bool mpu : std::vector<bool>({false, true}))
{
for (bool process_all : std::vector<bool>({false, true}))
{
- clip_geometry(file, bbox, simplification_distance, strictly_simple, type, mpu, process_all);
- clip_geometry(file, middle_fifty(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
- clip_geometry(file, top_left(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
- clip_geometry(file, top_right(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
- clip_geometry(file, bottom_left(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
- clip_geometry(file, bottom_right(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
- clip_geometry(file, zoomed_out(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+ clip_geometry(map, file, bbox, simplification_distance, strictly_simple, type, mpu, process_all);
+ clip_geometry(map, file, middle_fifty(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+ clip_geometry(map, file, top_left(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+ clip_geometry(map, file, top_right(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+ clip_geometry(map, file, bottom_left(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+ clip_geometry(map, file, bottom_right(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
+ clip_geometry(map, file, zoomed_out(bbox), simplification_distance, strictly_simple, type, mpu, process_all);
}
}
}
diff --git a/test/raster_tile.cpp b/test/raster_tile.cpp
index 3afb8d6..466773b 100644
--- a/test/raster_tile.cpp
+++ b/test/raster_tile.cpp
@@ -6,10 +6,9 @@
// vector output api
#include "vector_tile_processor.hpp"
-#include "vector_tile_backend_pbf.hpp"
-#include "vector_tile_util.hpp"
-#include "vector_tile_datasource.hpp"
+#include "vector_tile_datasource_pbf.hpp"
+// mapnik
#include <mapnik/util/fs.hpp>
#include <mapnik/datasource_cache.hpp>
#include <mapnik/agg_renderer.hpp>
@@ -19,24 +18,28 @@
#include <mapnik/feature_factory.hpp>
#include <mapnik/raster.hpp>
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
#include <sstream>
#include <fstream>
-TEST_CASE( "raster tile output 1", "should create raster tile with one raster layer" ) {
- 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;
- unsigned _x=0,_y=0,_z=1;
- double minx,miny,maxx,maxy;
- mapnik::vector_tile_impl::spherical_mercator merc(512);
- merc.xyz(_x,_y,_z,minx,miny,maxx,maxy);
- mapnik::box2d<double> bbox;
- bbox.init(minx,miny,maxx,maxy);
+TEST_CASE("raster tile output 1")
+{
+ // this test should create raster tile with one raster layer
+ unsigned _x = 0;
+ unsigned _y = 0;
+ unsigned _z = 1;
unsigned tile_size = 512;
- tile_type tile;
- backend_type backend(tile,16);
+ int buffer_size = 256;
+
+ // setup map object
mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
- map.set_buffer_size(256);
+ map.set_buffer_size(buffer_size);
mapnik::layer lyr("layer",map.srs());
mapnik::parameters params;
params["type"] = "gdal";
@@ -44,17 +47,21 @@ TEST_CASE( "raster tile output 1", "should create raster tile with one raster la
// wget http://www.nacis.org/naturalearth/50m/raster/NE2_50m_SR_W.zip
// gdalwarp -t_srs EPSG:3857 -ts 1048 1048 -r bilinear NE2_50M_SR_W.tif natural_earth.tif
params["file"] = "test/data/natural_earth.tif";
- std::shared_ptr<mapnik::datasource> ds =
- mapnik::datasource_cache::instance().create(params);
+ std::shared_ptr<mapnik::datasource> ds = mapnik::datasource_cache::instance().create(params);
lyr.set_datasource(ds);
map.add_layer(lyr);
- mapnik::request m_req(tile_size,tile_size,bbox);
- m_req.set_buffer_size(map.buffer_size());
- renderer_type ren(backend,map,m_req,1.0,0,0,1,false,"jpeg",mapnik::SCALING_BILINEAR);
- ren.apply();
- std::string key("");
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
- CHECK("" == key);
+
+ // Create the processor
+ mapnik::vector_tile_impl::processor ren(map);
+ ren.set_image_format("jpeg");
+ ren.set_scaling_method(mapnik::SCALING_BILINEAR);
+
+ // Request the tile
+ mapnik::vector_tile_impl::merc_tile out_tile = ren.create_tile(_x, _y, _z, tile_size, buffer_size);
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
+
+ // Test that tile is correct
CHECK(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
@@ -68,45 +75,49 @@ TEST_CASE( "raster tile output 1", "should create raster tile with one raster la
// debug
bool debug = false;
- if (!mapnik::util::exists("test/fixtures/expected-2.jpeg")) {
+ if (!mapnik::util::exists("test/fixtures/expected-2.jpeg"))
+ {
std::ofstream file("test/fixtures/expected-2.jpeg", std::ios::out|std::ios::trunc|std::ios::binary);
- if (!file) {
+ if (!file)
+ {
throw std::runtime_error("could not write image");
}
file << ras_buffer;
file.close();
}
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(ras_buffer.data(),ras_buffer.size()));
- if (!reader.get()) {
+ if (!reader.get())
+ {
throw std::runtime_error("could not open image bytes");
}
mapnik::image_rgba8 im_data(reader->width(),reader->height());
reader->read(0,0,im_data);
unsigned diff = testing::compare_images(im_data,"test/fixtures/expected-2.jpeg");
CHECK(0 == diff);
- if (diff > 0) {
+ if (diff > 0)
+ {
mapnik::save_to_file(im_data,"test/fixtures/actual-2.jpeg","jpeg");
}
std::size_t expected_image_size = 45660;
- int expected_vtile_size = expected_image_size + 26;
- if (!debug) {
+ std::size_t expected_vtile_size = expected_image_size + 26;
+ if (!debug)
+ {
CHECK(expected_image_size == ras_buffer.size());
CHECK(expected_vtile_size == tile.ByteSize());
}
std::string buffer;
CHECK(tile.SerializeToString(&buffer));
- if (!debug) {
+ if (!debug)
+ {
CHECK(expected_vtile_size == buffer.size());
}
// now read back and render image
mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
- map2.set_buffer_size(256);
- tile_type tile2;
+ map2.set_buffer_size(buffer_size);
+
+ vector_tile::Tile tile2;
CHECK(tile2.ParseFromString(buffer));
- std::string key2("");
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key2));
- CHECK("" == key2);
CHECK(1 == tile2.layers_size());
vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
CHECK(std::string("layer") == layer2.name());
@@ -116,62 +127,71 @@ TEST_CASE( "raster tile output 1", "should create raster tile with one raster la
CHECK(0 == f2.geometry_size());
CHECK(f2.has_raster());
CHECK(!f2.raster().empty());
- if (!debug) {
+ if (!debug)
+ {
CHECK(expected_image_size == f2.raster().size());
}
mapnik::layer lyr2("layer",map2.srs());
- std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds2 = std::make_shared<
- mapnik::vector_tile_impl::tile_datasource>(
- layer2,_x,_y,_z,map2.width());
+ protozero::pbf_reader layer_reader;
+ out_tile.layer_reader(0, layer_reader);
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_reader,_x,_y,_z);
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);
+ map2.zoom_all();
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-2.png")) {
+ if (!mapnik::util::exists("test/fixtures/expected-2.png"))
+ {
mapnik::save_to_file(im,"test/fixtures/expected-2.png","png32");
}
diff = testing::compare_images(im,"test/fixtures/expected-2.png");
CHECK(0 == diff);
- if (diff > 0) {
+ if (diff > 0)
+ {
mapnik::save_to_file(im_data,"test/fixtures/actual-2.png","png32");
}
}
-TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
- typedef vector_tile::Tile tile_type;
- tile_type tile;
+TEST_CASE("raster tile output 2")
+{
+ // the test is to check if you can overzoom a raster after encoding it into a vector tile
+ unsigned tile_size = 256;
+ int buffer_size = 1024;
+ mapnik::vector_tile_impl::merc_tile out_tile(0, 0, 0, tile_size, buffer_size);
{
- 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";
+ mapnik::box2d<double> const& bbox = out_tile.extent();
std::ostringstream s;
s << std::fixed << std::setprecision(16)
<< bbox.minx() << ',' << bbox.miny() << ','
<< bbox.maxx() << ',' << bbox.maxy();
+
+ // build map
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ map.set_buffer_size(buffer_size);
+ mapnik::layer lyr("layer",map.srs());
+ mapnik::parameters params;
+ params["type"] = "gdal";
params["extent"] = s.str();
params["file"] = "test/data/256x256.png";
- std::shared_ptr<mapnik::datasource> ds =
- mapnik::datasource_cache::instance().create(params);
+ 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,false,"jpeg",mapnik::SCALING_BILINEAR);
- ren.apply();
+
+ // build processor
+ mapnik::vector_tile_impl::processor ren(map);
+ ren.set_image_format("jpeg");
+ ren.set_scaling_method(mapnik::SCALING_BILINEAR);
+
+ // Update the tile
+ ren.update_tile(out_tile);
}
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
// Done creating test data, now test created tile
CHECK(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
@@ -183,9 +203,11 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
CHECK(f.has_raster());
std::string const& ras_buffer = f.raster();
CHECK(!ras_buffer.empty());
+
// debug
bool debug = false;
- if (debug) {
+ if (debug)
+ {
std::ofstream file("out2.png", std::ios::out|std::ios::trunc|std::ios::binary);
file << ras_buffer;
file.close();
@@ -193,17 +215,19 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
// confirm tile looks correct as encoded
std::size_t expected_image_size = 1654;
- int expected_vtile_size = expected_image_size + 23;
- if (!debug) {
+ std::size_t expected_vtile_size = expected_image_size + 23;
+ if (!debug)
+ {
CHECK(expected_image_size == ras_buffer.size());
CHECK(expected_vtile_size == tile.ByteSize());
}
std::string buffer;
- CHECK(tile.SerializeToString(&buffer));
- if (!debug) {
+ out_tile.serialize_to_string(buffer);
+ if (!debug)
+ {
CHECK(expected_vtile_size == buffer.size());
}
- tile_type tile2;
+ vector_tile::Tile tile2;
CHECK(tile2.ParseFromString(buffer));
CHECK(1 == tile2.layers_size());
vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
@@ -214,7 +238,8 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
CHECK(0 == f2.geometry_size());
CHECK(f2.has_raster());
CHECK(!f2.raster().empty());
- if (!debug) {
+ if (!debug)
+ {
CHECK(expected_image_size == f2.raster().size());
}
@@ -228,9 +253,11 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
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> ds2 = std::make_shared<
- mapnik::vector_tile_impl::tile_datasource>(
- layer2,0,0,0,256);
+ protozero::pbf_reader layer_reader;
+ out_tile.layer_reader(0, layer_reader);
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_reader,0,0,0);
lyr2.set_datasource(ds2);
lyr2.add_style("style");
map2.add_layer(lyr2);
@@ -239,21 +266,53 @@ TEST_CASE( "raster tile output 2", "should be able to overzoom raster" ) {
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")) {
+ 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) {
+ if (diff > 0)
+ {
mapnik::save_to_file(im,"test/fixtures/actual-3.png","png32");
}
}
-TEST_CASE( "raster tile output 3", "should be able to round trip image with alpha" ) {
- mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
-
+TEST_CASE("raster tile output 3 -- should be able to round trip image with alpha")
+{
// create a vtile from scratch with a raster
+ unsigned tile_size = 256;
+ mapnik::vector_tile_impl::merc_tile out_tile(0, 0, 0, tile_size);
+ mapnik::box2d<double> const& bbox = out_tile.extent();
+
+ {
+ // build map
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
+ mapnik::parameters params;
+ params["type"] = "raster";
+ params["lox"] = bbox.minx();
+ params["loy"] = bbox.miny();
+ params["hix"] = bbox.maxx();
+ params["hiy"] = bbox.maxy();
+ params["file"] = "test/fixtures/alpha-white-2.png";
+ std::shared_ptr<mapnik::datasource> ds = mapnik::datasource_cache::instance().create(params);
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+
+ // build processor
+ mapnik::vector_tile_impl::processor ren(map);
+ ren.set_image_format("png32");
+ ren.set_scaling_method(mapnik::SCALING_NEAR);
+
+ // Update the tile
+ ren.update_tile(out_tile);
+ }
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
+ // Done creating test data, now test created tile
+/*
vector_tile::Tile tile;
std::string format("png32");
{
@@ -271,6 +330,7 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
backend.stop_tile_feature();
backend.stop_tile_layer();
}
+*/
{
REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
@@ -279,16 +339,18 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
REQUIRE(f.has_raster());
std::string const& ras_buffer = f.raster();
REQUIRE(!ras_buffer.empty());
- std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(ras_buffer.data(),ras_buffer.size()));
- if (!reader.get()) {
+ std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(ras_buffer.data(), ras_buffer.size()));
+ if (!reader.get())
+ {
throw std::runtime_error("could not open image bytes");
}
- mapnik::image_rgba8 im_data(reader->width(),reader->height());
- reader->read(0,0,im_data);
- unsigned diff = testing::compare_images(im_data,"./test/fixtures/alpha-white-2.png",0,true);
+ mapnik::image_rgba8 im_data(reader->width(), reader->height());
+ reader->read(0, 0, im_data);
+ unsigned diff = testing::compare_images(im_data, "./test/fixtures/alpha-white-2.png", 0, true);
CHECK(0 == diff);
- if (diff > 0) {
- mapnik::save_to_file(im_data,"test/fixtures/actual-4.png",format);
+ if (diff > 0)
+ {
+ mapnik::save_to_file(im_data, "test/fixtures/actual-4.png", "png32");
}
}
@@ -296,11 +358,12 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
// Now actually re-render to trigger the raster being passed through the processor
// and confirm raster still looks correct
{
+ protozero::pbf_reader layer_reader;
+ out_tile.layer_reader(0, layer_reader);
// create datasource wrapping raster
- unsigned tile_size = 256;
- std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds = std::make_shared<
- mapnik::vector_tile_impl::tile_datasource>(
- tile.layers(0),0,0,0,tile_size);
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_reader,0,0,0);
// before rendering let's query the raster directly to ensure
// the datasource returns it correctly.
mapnik::query q(bbox);
@@ -309,10 +372,11 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
mapnik::raster_ptr const& source = feat->get_raster();
CHECK(source->data_.is<mapnik::image_rgba8>());
mapnik::image_rgba8 const& source_data = mapnik::util::get<mapnik::image_rgba8>(source->data_);
- unsigned diff = testing::compare_images(source_data,"./test/fixtures/alpha-white-2.png",0,true);
+ unsigned diff = testing::compare_images(source_data, "./test/fixtures/alpha-white-2.png", 0, true);
CHECK(0 == diff);
- if (diff > 0) {
- mapnik::save_to_file(source_data,"test/fixtures/actual-5.png",format);
+ if (diff > 0)
+ {
+ mapnik::save_to_file(source_data, "test/fixtures/actual-5.png", "png32");
}
// okay, now we'll re-render another vector tile from original
@@ -324,52 +388,50 @@ TEST_CASE( "raster tile output 3", "should be able to round trip image with alph
map.add_layer(lyr);
map.zoom_to_box(bbox);
- typedef mapnik::vector_tile_impl::backend_pbf backend_type;
- vector_tile::Tile round_tipped_tile;
- backend_type backend(round_tipped_tile,16);
- typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
- mapnik::request m_req(tile_size,tile_size,bbox);
- renderer_type ren(backend,
- map,
- m_req,
- 1.0,
- 0,
- 0,
- 1,
- false,
- format,
- mapnik::SCALING_BILINEAR);
- ren.apply();
- REQUIRE(1 == round_tipped_tile.layers_size());
- vector_tile::Tile_Layer const& layer = round_tipped_tile.layers(0);
+ // build processor
+ mapnik::vector_tile_impl::processor ren(map);
+ ren.set_image_format("png32");
+ //ren.set_scaling_method(mapnik::SCALING_NEAR);
+
+ // Update the tile
+ mapnik::vector_tile_impl::tile new_tile = ren.create_tile(0, 0, 0, tile_size); //, buffer_size);
+
+ vector_tile::Tile round_tripped_tile;
+ round_tripped_tile.ParseFromString(new_tile.get_buffer());
+
+ REQUIRE(1 == round_tripped_tile.layers_size());
+ vector_tile::Tile_Layer const& layer = round_tripped_tile.layers(0);
REQUIRE(1 == layer.features_size());
vector_tile::Tile_Feature const& f = layer.features(0);
REQUIRE(f.has_raster());
std::string const& ras_buffer = f.raster();
REQUIRE(!ras_buffer.empty());
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(ras_buffer.data(),ras_buffer.size()));
- if (!reader.get()) {
+ if (!reader.get())
+ {
throw std::runtime_error("could not open image bytes");
}
- mapnik::image_rgba8 im_data(reader->width(),reader->height());
- reader->read(0,0,im_data);
+ mapnik::image_rgba8 im_data(reader->width(), reader->height());
+ reader->read(0, 0, im_data);
- if (!mapnik::util::exists("test/fixtures/expected-4.png")) {
- mapnik::save_to_file(im_data,"test/fixtures/expected-4.png","png32");
+ if (!mapnik::util::exists("test/fixtures/expected-4.png"))
+ {
+ mapnik::save_to_file(im_data, "test/fixtures/expected-4.png", "png32");
}
- diff = testing::compare_images(im_data,"test/fixtures/expected-4.png",0,true);
+ diff = testing::compare_images(im_data, "test/fixtures/expected-4.png", 0, true);
CHECK(0 == diff);
- if (diff > 0) {
- mapnik::save_to_file(im_data,"test/fixtures/actual-7.png",format);
+ if (diff > 0)
+ {
+ mapnik::save_to_file(im_data, "test/fixtures/actual-7.png", "png32");
}
- // visually identical but scaling causes some pixels shifts compared to original
- diff = testing::compare_images(im_data,"./test/fixtures/alpha-white-2.png",0,true);
- CHECK(13853 == diff);
- if (diff > 0) {
- mapnik::save_to_file(im_data,"test/fixtures/actual-7.png",format);
+ diff = testing::compare_images(im_data, "./test/fixtures/alpha-white-2.png", 0, true);
+ CHECK(0 == diff);
+ if (diff > 0)
+ {
+ mapnik::save_to_file(im_data, "test/fixtures/actual-7.png", "png32");
}
}
diff --git a/test/system/encode_and_datasource_decode.cpp b/test/system/encode_and_datasource_decode.cpp
new file mode 100644
index 0000000..a9ef5e6
--- /dev/null
+++ b/test/system/encode_and_datasource_decode.cpp
@@ -0,0 +1,119 @@
+#include "catch.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_strategy.hpp>
+#include <mapnik/geometry_transform.hpp>
+
+// mapnik-vector-tile
+#include "vector_tile_geometry_encoder_pbf.hpp"
+#include "vector_tile_datasource_pbf.hpp"
+
+// vector tile
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+TEST_CASE("encoding multi line string and check output datasource")
+{
+ mapnik::geometry::multi_line_string<std::int64_t> geom;
+ {
+ mapnik::geometry::line_string<std::int64_t> ring;
+ ring.add_coord(0,0);
+ ring.add_coord(2,2);
+ geom.emplace_back(std::move(ring));
+ }
+ {
+ mapnik::geometry::line_string<std::int64_t> ring;
+ ring.add_coord(1,1);
+ ring.add_coord(2,2);
+ geom.emplace_back(std::move(ring));
+ }
+
+ vector_tile::Tile tile;
+ vector_tile::Tile_Layer * t_layer = tile.add_layers();
+ t_layer->set_name("layer");
+ t_layer->set_version(2);
+ t_layer->set_extent(4096);
+ vector_tile::Tile_Feature * t_feature = t_layer->add_features();
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ CHECK(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+ t_feature->ParseFromString(feature_str);
+
+ REQUIRE(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);
+ protozero::pbf_reader pbf_tile(buffer);
+ pbf_tile.next();
+ protozero::pbf_reader layer2 = pbf_tile.get_message();
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ 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> >());
+}
+
+TEST_CASE("encoding and decoding with datasource simple polygon")
+{
+ mapnik::geometry::polygon<double> geom;
+ {
+ mapnik::geometry::linear_ring<double> ring;
+ ring.add_coord(168.267850,-24.576888);
+ ring.add_coord(167.982618,-24.697145);
+ ring.add_coord(168.114561,-24.783548);
+ ring.add_coord(168.267850,-24.576888);
+ ring.add_coord(168.267850,-24.576888);
+ geom.set_exterior_ring(std::move(ring));
+ }
+
+ unsigned path_multiplier = 16;
+ mapnik::geometry::scale_rounding_strategy scale_strat(path_multiplier);
+ mapnik::geometry::geometry<std::int64_t> geom2 = mapnik::geometry::transform<std::int64_t>(geom, scale_strat);
+
+ // encode geometry
+ vector_tile::Tile tile;
+ vector_tile::Tile_Layer * t_layer = tile.add_layers();
+ vector_tile::Tile_Feature * t_feature = t_layer->add_features();
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ CHECK(mapnik::vector_tile_impl::encode_geometry_pbf(geom2, feature_writer, x, y));
+ t_feature->ParseFromString(feature_str);
+
+ // test results
+ REQUIRE(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(11 == f.geometry_size());
+}
diff --git a/test/system/encode_and_decode.cpp b/test/system/encode_and_decode.cpp
new file mode 100644
index 0000000..6ec7b32
--- /dev/null
+++ b/test/system/encode_and_decode.cpp
@@ -0,0 +1,138 @@
+#include "catch.hpp"
+
+// test utils
+#include "encoding_util.hpp"
+
+//
+// Point Round Trip
+//
+
+TEST_CASE("encode and decode point")
+{
+ mapnik::geometry::point<std::int64_t> g(0,0);
+ std::string expected(
+ "move_to(0,0)\n"
+ );
+
+ SECTION("protozero decoder VT Spec v1")
+ {
+ CHECK(compare_pbf(g,1) == expected);
+ }
+
+ SECTION("protozero decoder VT Spec v2")
+ {
+ CHECK(compare_pbf(g,2) == expected);
+ }
+}
+
+TEST_CASE( "encode and decode multipoint" )
+{
+ mapnik::geometry::multi_point<std::int64_t> g;
+ g.add_coord(0,0);
+ g.add_coord(1,1);
+ g.add_coord(2,2);
+ std::string expected(
+ "move_to(0,0)\n"
+ "move_to(1,1)\n"
+ "move_to(2,2)\n"
+ );
+
+ SECTION("protozero decoder VT Spec v1")
+ {
+ CHECK(compare_pbf(g,1) == expected);
+ }
+
+ SECTION("protozero decoder VT Spec v2")
+ {
+ CHECK(compare_pbf(g,2) == expected);
+ }
+}
+
+//
+// Linestring Round Trip
+//
+
+TEST_CASE( "encode and decode linestring" )
+{
+ mapnik::geometry::line_string<std::int64_t> g;
+ g.add_coord(0,0);
+ g.add_coord(1,1);
+ g.add_coord(100,100);
+ std::string expected(
+ "move_to(0,0)\n"
+ "line_to(1,1)\n"
+ "line_to(100,100)\n"
+ );
+
+ SECTION("protozero decoder VT Spec v1")
+ {
+ CHECK(compare_pbf(g,1) == expected);
+ }
+
+ SECTION("protozero decoder VT Spec v2")
+ {
+ CHECK(compare_pbf(g,2) == expected);
+ }
+}
+
+TEST_CASE( "encode and decode multi_line_string" )
+{
+ mapnik::geometry::multi_line_string<std::int64_t> g;
+ {
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(0,0);
+ line.add_coord(1,1);
+ line.add_coord(100,100);
+ g.emplace_back(std::move(line));
+ }
+ {
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(-10,-10);
+ line.add_coord(-20,-20);
+ line.add_coord(-100,-100);
+ g.emplace_back(std::move(line));
+ }
+ std::string expected(
+ "move_to(0,0)\n"
+ "line_to(1,1)\n"
+ "line_to(100,100)\n"
+ "move_to(-10,-10)\n"
+ "line_to(-20,-20)\n"
+ "line_to(-100,-100)\n"
+ );
+
+ SECTION("protozero decoder VT Spec v1")
+ {
+ CHECK(compare_pbf(g,1) == expected);
+ }
+
+ SECTION("protozero decoder VT Spec v2")
+ {
+ CHECK(compare_pbf(g,2) == expected);
+ }
+}
+
+TEST_CASE( "encode and decode polygon" )
+{
+ mapnik::geometry::polygon<std::int64_t> g;
+ g.exterior_ring.add_coord(0,0);
+ g.exterior_ring.add_coord(100,0);
+ g.exterior_ring.add_coord(100,100);
+ g.exterior_ring.add_coord(0,0);
+ std::string expected(
+ "move_to(0,0)\n"
+ "line_to(100,0)\n"
+ "line_to(100,100)\n"
+ "close_path(0,0)\n"
+ );
+
+ SECTION("protozero decoder VT Spec v1")
+ {
+ CHECK(compare_pbf(g,1) == expected);
+ }
+
+ SECTION("protozero decoder VT Spec v2")
+ {
+ CHECK(compare_pbf(g,2) == expected);
+ }
+}
diff --git a/test/system/processor_and_datasource.cpp b/test/system/processor_and_datasource.cpp
new file mode 100644
index 0000000..5748075
--- /dev/null
+++ b/test/system/processor_and_datasource.cpp
@@ -0,0 +1,321 @@
+#include "catch.hpp"
+
+// mapnik-vector-tile
+#include "vector_tile_datasource_pbf.hpp"
+#include "vector_tile_processor.hpp"
+
+// mapnik
+#include <mapnik/agg_renderer.hpp>
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/load_map.hpp>
+#include <mapnik/image_util.hpp>
+#include <mapnik/util/fs.hpp>
+
+// test utils
+#include "test_utils.hpp"
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// boost
+#include <boost/optional/optional_io.hpp>
+
+// std
+#include <set>
+
+TEST_CASE("vector tile output -- simple two points")
+{
+ // This test should create vector tile with two points
+
+ // Build Map
+ mapnik::Map map(256, 256, "+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
+ lyr.set_datasource(testing::build_ds(0,0,true));
+ map.add_layer(lyr);
+
+ // Create processor
+ mapnik::vector_tile_impl::processor ren(map);
+
+ // Request Tile
+ mapnik::vector_tile_impl::merc_tile out_tile = ren.create_tile(0,0,0);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+
+ // Now check that the tile is correct.
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
+ REQUIRE(1 == tile.layers_size());
+ vector_tile::Tile_Layer const& layer = tile.layers(0);
+ CHECK(std::string("layer") == layer.name());
+ REQUIRE(2 == 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()));
+ REQUIRE(3 == f.geometry_size());
+ CHECK(9 == f.geometry(0));
+ CHECK(4096 == f.geometry(1));
+ CHECK(4096 == f.geometry(2));
+ CHECK(190 == tile.ByteSize());
+ std::string buffer;
+ CHECK(tile.SerializeToString(&buffer));
+ CHECK(190 == buffer.size());
+}
+
+TEST_CASE("vector tile output -- empty tile")
+{
+ // test adding empty layers should result in empty tile
+ mapnik::Map map(256,256,"+init=epsg:3857");
+ map.add_layer(mapnik::layer("layer",map.srs()));
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+ CHECK(out_tile.is_painted() == false);
+ CHECK(out_tile.is_empty() == true);
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
+ CHECK(0 == tile.layers_size());
+ std::string buffer;
+ out_tile.serialize_to_string(buffer);
+ CHECK(buffer.empty());
+}
+
+TEST_CASE("vector tile output -- layers outside extent")
+{
+ // adding layers with geometries outside rendering extent should not add layer
+ unsigned tile_size = 4096;
+ // Create map
+ mapnik::Map map(256,256,"+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
+ mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+ mapnik::geometry::line_string<double> g;
+ g.add_coord(-10,-10);
+ g.add_coord(-11,-11);
+ feature->set_geometry(std::move(g));
+ mapnik::parameters params;
+ params["type"] = "memory";
+ std::shared_ptr<mapnik::memory_datasource> ds = std::make_shared<mapnik::memory_datasource>(params);
+ ds->push(feature);
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+
+ mapnik::box2d<double> custom_bbox(0,0,10,10);
+
+ // Build processor and create tile
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(custom_bbox, tile_size);
+ CHECK(out_tile.is_painted() == false);
+ CHECK(out_tile.is_empty() == true);
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
+ CHECK(0 == tile.layers_size());
+ std::string buffer;
+ out_tile.serialize_to_string(buffer);
+ CHECK(buffer.empty());
+}
+
+TEST_CASE("vector tile output is empty -- degenerate geometries")
+{
+ // adding layers with degenerate geometries should not add layer
+ unsigned tile_size = 4096;
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::Map map(256,256,"+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
+ // create a datasource with a feature outside the map
+ std::shared_ptr<mapnik::memory_datasource> ds = testing::build_ds(bbox.minx()-1,bbox.miny()-1);
+ // 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(bbox);
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+
+ // Build processor and create tile
+ mapnik::vector_tile_impl::processor ren(map);
+
+ // Request Tile
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(bbox, tile_size);
+
+ // Check output
+ CHECK(out_tile.is_painted() == false);
+ CHECK(out_tile.is_empty() == true);
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
+ CHECK(0 == tile.layers_size());
+ std::string buffer;
+ out_tile.serialize_to_string(buffer);
+ CHECK(buffer.empty());
+}
+
+TEST_CASE("vector tile render simple point")
+{
+ // should be able to parse message and render point
+ unsigned tile_size = 4096;
+ mapnik::Map map(256, 256, "+init=epsg:3857");
+ mapnik::layer lyr("layer", map.srs());
+ lyr.set_datasource(testing::build_ds(0,0));
+ map.add_layer(lyr);
+
+ // create processor
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+ // serialize to message
+ std::string buffer;
+ out_tile.serialize_to_string(buffer);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+ CHECK(147 == buffer.size());
+
+ // create a new vector tile from the buffer
+ vector_tile::Tile tile2;
+ CHECK(tile2.ParseFromString(buffer));
+
+ // Validate the new tile.
+ 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());
+
+ // Create another map
+ mapnik::Map map2(256, 256, "+init=epsg:3857");
+ mapnik::layer lyr2("layer",map.srs());
+
+ // Create datasource from tile.
+ protozero::pbf_reader layer_reader;
+ out_tile.layer_reader(0, layer_reader);
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_reader,0,0,0);
+ CHECK( ds->type() == mapnik::datasource::Vector );
+ CHECK( ds->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
+
+ // Check that all the names are in the list
+ mapnik::layer_descriptor lay_desc = ds->get_descriptor();
+ std::set<std::string> expected_names;
+ expected_names.insert("bool");
+ expected_names.insert("boolf");
+ expected_names.insert("double");
+ expected_names.insert("float");
+ expected_names.insert("int");
+ expected_names.insert("name");
+ expected_names.insert("uint");
+ std::size_t desc_count = 0;
+ for (auto const& desc : lay_desc.get_descriptors())
+ {
+ ++desc_count;
+ CHECK(expected_names.count(desc.get_name()) == 1);
+ }
+ CHECK(desc_count == expected_names.size());
+
+ // Add datasource to layer and map
+ lyr2.set_datasource(ds);
+ lyr2.add_style("style");
+ map2.add_layer(lyr2);
+ // Load map style
+ mapnik::load_map(map2,"test/data/style.xml");
+ map2.zoom_all();
+ 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("vector tile datasource -- should filter features outside extent")
+{
+ mapnik::Map map(256,256,"+init=epsg:3857");
+ mapnik::layer lyr("layer",map.srs());
+ lyr.set_datasource(testing::build_ds(0,0));
+ map.add_layer(lyr);
+
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+
+ // serialize to message
+ std::string buffer;
+ out_tile.serialize_to_string(buffer);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+
+ // check that vector tile contains proper information
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
+ 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());
+ 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));
+
+ // now actually start the meat of the test
+ // create a datasource from the vector tile
+ protozero::pbf_reader layer_reader;
+ out_tile.layer_reader(0, layer_reader);
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(layer_reader,0,0,0);
+
+ // ensure we can query single feature
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+ mapnik::featureset_ptr fs;
+ fs = ds.features(mapnik::query(bbox));
+ mapnik::feature_ptr feat = fs->next();
+ // Check that feature is not empty.
+ CHECK(feat != mapnik::feature_ptr());
+ CHECK(feat->size() == 0);
+ // Check that this was the only feature so next should be empty
+ CHECK(fs->next() == mapnik::feature_ptr());
+
+ // Now query for another feature.
+ 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 that feature is not empty
+ CHECK(feat != mapnik::feature_ptr());
+ CHECK(feat->size() == 1);
+ CHECK(feat->get("name") == mapnik::value_unicode_string("null island"));
+ CHECK(fs->next() == mapnik::feature_ptr());
+
+ // 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);
+}
diff --git a/test/system/remove_repeated_point.cpp b/test/system/remove_repeated_point.cpp
new file mode 100644
index 0000000..bf77324
--- /dev/null
+++ b/test/system/remove_repeated_point.cpp
@@ -0,0 +1,23 @@
+#include "catch.hpp"
+
+// test-utils
+#include "round_trip.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_empty.hpp>
+
+TEST_CASE("vector tile multi_point encoding with repeated points should be removed")
+{
+ mapnik::geometry::multi_point<double> geom;
+ geom.emplace_back(0,0);
+ geom.emplace_back(0,0);
+ geom.emplace_back(1,1);
+ geom.emplace_back(1,1);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "MULTIPOINT(128 -128,128.711 -126.578)" );
+ CHECK( new_geom.is<mapnik::geometry::multi_point<double> >() );
+}
diff --git a/test/system/round_trip.cpp b/test/system/round_trip.cpp
new file mode 100644
index 0000000..8009979
--- /dev/null
+++ b/test/system/round_trip.cpp
@@ -0,0 +1,247 @@
+#include "catch.hpp"
+
+// test-utils
+#include "round_trip.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_empty.hpp>
+
+TEST_CASE("vector tile round trip point encoding")
+{
+ mapnik::geometry::point<double> geom(0,0);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( test_utils::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")
+{
+ 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 = test_utils::round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( test_utils::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")
+{
+ 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 = test_utils::round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POINT(128 -128)" );
+ CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE("vector tile multi_point encoding of single point")
+{
+ mapnik::geometry::multi_point<double> geom;
+ geom.emplace_back(0,0);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POINT(128 -128)" );
+ CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE("vector tile multi_point encoding of actual multi_point")
+{
+ mapnik::geometry::multi_point<double> geom;
+ geom.emplace_back(0,0);
+ geom.emplace_back(1,1);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "MULTIPOINT(128 -128,128.711 -126.578)" );
+ CHECK( new_geom.is<mapnik::geometry::multi_point<double> >() );
+}
+
+TEST_CASE("vector tile line_string encoding")
+{
+ mapnik::geometry::line_string<double> geom;
+ geom.add_coord(0,0);
+ geom.add_coord(100,100);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "LINESTRING(128 -128,192 0)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
+}
+
+TEST_CASE("vector tile multi_line_string encoding of single line_string")
+{
+ mapnik::geometry::multi_line_string<double> geom;
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(0,0);
+ line.add_coord(100,100);
+ geom.emplace_back(std::move(line));
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "LINESTRING(128 -128,192 0)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
+}
+
+TEST_CASE("vector tile multi_line_string encoding of actual multi_line_string")
+{
+ mapnik::geometry::multi_line_string<double> geom;
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(0,0);
+ line.add_coord(100,100);
+ geom.emplace_back(std::move(line));
+ mapnik::geometry::line_string<double> line2;
+ line2.add_coord(-10,-0);
+ line2.add_coord(-100,-100);
+ geom.emplace_back(std::move(line2));
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "MULTILINESTRING((128 -128,192 0),(120.889 -128,63.289 -256))" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::multi_line_string<double> >() );
+}
+
+TEST_CASE("vector tile polygon encoding")
+{
+ mapnik::geometry::polygon<double> geom;
+ geom.exterior_ring.add_coord(0,0);
+ geom.exterior_ring.add_coord(0,10);
+ geom.exterior_ring.add_coord(-10,10);
+ geom.exterior_ring.add_coord(-10,0);
+ geom.exterior_ring.add_coord(0,0);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128))");
+}
+
+TEST_CASE("vector tile multi_polygon encoding of single polygon")
+{
+ mapnik::geometry::polygon<double> poly;
+ poly.exterior_ring.add_coord(0,0);
+ poly.exterior_ring.add_coord(0,10);
+ poly.exterior_ring.add_coord(-10,10);
+ poly.exterior_ring.add_coord(-10,0);
+ poly.exterior_ring.add_coord(0,0);
+ mapnik::geometry::multi_polygon<double> geom;
+ geom.emplace_back(std::move(poly));
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128))");
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE("vector tile multi_polygon with multipolygon union")
+{
+ mapnik::geometry::polygon<double> poly;
+ poly.exterior_ring.add_coord(0,0);
+ poly.exterior_ring.add_coord(0,10);
+ poly.exterior_ring.add_coord(-10,10);
+ poly.exterior_ring.add_coord(-10,0);
+ poly.exterior_ring.add_coord(0,0);
+ mapnik::geometry::polygon<double> poly2;
+ poly2.exterior_ring.add_coord(0,0);
+ poly2.exterior_ring.add_coord(0,10);
+ poly2.exterior_ring.add_coord(-10,10);
+ poly2.exterior_ring.add_coord(-10,0);
+ poly2.exterior_ring.add_coord(0,0);
+ mapnik::geometry::multi_polygon<double> geom;
+ geom.emplace_back(std::move(poly));
+ geom.emplace_back(std::move(poly2));
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom, 0, mapnik::vector_tile_impl::non_zero_fill, true);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128))");
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE("vector tile multi_polygon with out multipolygon union")
+{
+ mapnik::geometry::polygon<double> poly;
+ poly.exterior_ring.add_coord(0,0);
+ poly.exterior_ring.add_coord(0,10);
+ poly.exterior_ring.add_coord(-10,10);
+ poly.exterior_ring.add_coord(-10,0);
+ poly.exterior_ring.add_coord(0,0);
+ mapnik::geometry::polygon<double> poly2;
+ poly2.exterior_ring.add_coord(0,0);
+ poly2.exterior_ring.add_coord(0,10);
+ poly2.exterior_ring.add_coord(-10,10);
+ poly2.exterior_ring.add_coord(-10,0);
+ poly2.exterior_ring.add_coord(0,0);
+ mapnik::geometry::multi_polygon<double> geom;
+ geom.emplace_back(std::move(poly));
+ geom.emplace_back(std::move(poly2));
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom, 0, mapnik::vector_tile_impl::non_zero_fill, false);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "MULTIPOLYGON(((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128)),((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128)))");
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
+}
+
+TEST_CASE("vector tile multi_polygon encoding of actual multi_polygon")
+{
+ mapnik::geometry::multi_polygon<double> geom;
+ mapnik::geometry::polygon<double> poly;
+ poly.exterior_ring.add_coord(0,0);
+ poly.exterior_ring.add_coord(0,10);
+ poly.exterior_ring.add_coord(-10,10);
+ poly.exterior_ring.add_coord(-10,0);
+ poly.exterior_ring.add_coord(0,0);
+ geom.emplace_back(std::move(poly));
+ mapnik::geometry::polygon<double> poly2;
+ poly2.exterior_ring.add_coord(11,11);
+ poly2.exterior_ring.add_coord(11,21);
+ poly2.exterior_ring.add_coord(1,21);
+ poly2.exterior_ring.add_coord(1,11);
+ poly2.exterior_ring.add_coord(11,11);
+ geom.emplace_back(std::move(poly2));
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
+}
+
+TEST_CASE("vector tile multi_polygon encoding overlapping multipolygons")
+{
+ mapnik::geometry::multi_polygon<double> geom;
+ mapnik::geometry::polygon<double> poly;
+ poly.exterior_ring.add_coord(0,0);
+ poly.exterior_ring.add_coord(0,10);
+ poly.exterior_ring.add_coord(-10,10);
+ poly.exterior_ring.add_coord(-10,0);
+ poly.exterior_ring.add_coord(0,0);
+ geom.emplace_back(std::move(poly));
+ mapnik::geometry::polygon<double> poly2;
+ poly2.exterior_ring.add_coord(-5,5);
+ poly2.exterior_ring.add_coord(-5,15);
+ poly2.exterior_ring.add_coord(-15,15);
+ poly2.exterior_ring.add_coord(-15,5);
+ poly2.exterior_ring.add_coord(-5,5);
+ geom.emplace_back(std::move(poly2));
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom);
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
+}
diff --git a/test/system/round_trip_fill_type.cpp b/test/system/round_trip_fill_type.cpp
new file mode 100644
index 0000000..2d2e6f0
--- /dev/null
+++ b/test/system/round_trip_fill_type.cpp
@@ -0,0 +1,65 @@
+#include "catch.hpp"
+
+// test-utils
+#include "round_trip.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_empty.hpp>
+
+TEST_CASE("vector tile polygon even odd fill")
+{
+ using namespace mapnik::geometry;
+ polygon<double> poly;
+ {
+ linear_ring<double> ring;
+ ring.add_coord(0,0);
+ ring.add_coord(-10,0);
+ ring.add_coord(-10,10);
+ ring.add_coord(0,10);
+ ring.add_coord(0,0);
+ poly.set_exterior_ring(std::move(ring));
+ linear_ring<double> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-7,3);
+ hole.add_coord(-3,3);
+ hole.add_coord(-3,7);
+ hole.add_coord(-7,7);
+ poly.add_hole(std::move(hole));
+ }
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(poly,0,mapnik::vector_tile_impl::even_odd_fill);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128),(123.022 -118.044,125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -118.044))");
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE("vector tile polygon non zero fill")
+{
+ using namespace mapnik::geometry;
+ polygon<double> poly;
+ {
+ linear_ring<double> ring;
+ ring.add_coord(0,0);
+ ring.add_coord(-10,0);
+ ring.add_coord(-10,10);
+ ring.add_coord(0,10);
+ ring.add_coord(0,0);
+ poly.set_exterior_ring(std::move(ring));
+ linear_ring<double> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-7,3);
+ hole.add_coord(-3,3);
+ hole.add_coord(-3,7);
+ hole.add_coord(-7,7);
+ poly.add_hole(std::move(hole));
+ }
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(poly,0,mapnik::vector_tile_impl::non_zero_fill);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128),(123.022 -118.044,125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -118.044))");
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
diff --git a/test/system/round_trip_simplification.cpp b/test/system/round_trip_simplification.cpp
new file mode 100644
index 0000000..356ac03
--- /dev/null
+++ b/test/system/round_trip_simplification.cpp
@@ -0,0 +1,144 @@
+#include "catch.hpp"
+
+// test-utils
+#include "round_trip.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry_is_empty.hpp>
+
+TEST_CASE("vector tile point correctly passed through simplification code path")
+{
+ mapnik::geometry::point<double> geom(-122,48);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom,500);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POINT(41.244 -59.733)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::point<double> >() );
+}
+
+TEST_CASE("vector tile mulit_point correctly passed through simplification code path")
+{
+ mapnik::geometry::multi_point<double> geom;
+ geom.emplace_back(-122,48);
+ geom.emplace_back(-123,49);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom,500);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "MULTIPOINT(41.244 -59.733,40.533 -58.311)" );
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ CHECK( new_geom.is<mapnik::geometry::multi_point<double> >() );
+}
+
+TEST_CASE("vector tile line_string is simplified")
+{
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(0,0);
+ line.add_coord(1,1);
+ line.add_coord(2,2);
+ line.add_coord(100,100);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(line,500);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ 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);
+ CHECK( line2.size() == 2 );
+}
+
+TEST_CASE("vector tile multi_line_string is simplified")
+{
+ mapnik::geometry::multi_line_string<double> geom;
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(0,0);
+ line.add_coord(1,1);
+ line.add_coord(2,2);
+ line.add_coord(100,100);
+ geom.emplace_back(std::move(line));
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom,500);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ 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);
+ CHECK( line2.size() == 2 );
+}
+
+TEST_CASE("vector tile polygon is simplified")
+{
+ using namespace mapnik::geometry;
+ polygon<double> poly;
+ {
+ linear_ring<double> ring;
+ ring.add_coord(0,0);
+ ring.add_coord(-10,0);
+ ring.add_coord(-10,10);
+ ring.add_coord(0,10);
+ ring.add_coord(0,0);
+ poly.set_exterior_ring(std::move(ring));
+ linear_ring<double> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-7,3);
+ hole.add_coord(-3,3);
+ hole.add_coord(-3,7);
+ hole.add_coord(-7,7);
+ poly.add_hole(std::move(hole));
+ }
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(poly,500);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128),(123.022 -118.044,125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -118.044))");
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE("vector tile mulit_polygon is simplified")
+{
+ using namespace mapnik::geometry;
+ polygon<double> poly;
+ {
+ linear_ring<double> ring;
+ ring.add_coord(0,0);
+ ring.add_coord(-10,0);
+ ring.add_coord(-10,10);
+ ring.add_coord(0,10);
+ ring.add_coord(0,0);
+ poly.set_exterior_ring(std::move(ring));
+ linear_ring<double> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-7,3);
+ hole.add_coord(-3,3);
+ hole.add_coord(-3,7);
+ hole.add_coord(-7,7);
+ poly.add_hole(std::move(hole));
+ }
+ multi_polygon<double> mp;
+ mp.push_back(poly);
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(mp,500);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ CHECK( wkt == "POLYGON((128 -128,128 -113.778,120.889 -113.778,120.889 -128,128 -128),(123.022 -118.044,125.867 -118.044,125.867 -123.733,123.022 -123.733,123.022 -118.044))");
+ CHECK( !mapnik::geometry::is_empty(new_geom) );
+ REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
+}
+
+TEST_CASE("vector tile line_string is simplified when outside bounds", "should create vector tile with data" ) {
+ mapnik::geometry::multi_line_string<double> geom;
+ mapnik::geometry::line_string<double> line;
+ line.add_coord(-10000,0);
+ line.add_coord(-10000.1,0);
+ line.add_coord(100000,0);
+ geom.emplace_back(std::move(line));
+ mapnik::geometry::geometry<double> new_geom = test_utils::round_trip(geom,100);
+ std::string wkt;
+ CHECK( test_utils::to_wkt(wkt, new_geom) );
+ // yep this test is weird - more of a fuzz than anything
+ CHECK( wkt == "LINESTRING(0 -128,256 -128)" );
+ 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);
+ CHECK( line2.size() == 2 );
+}
diff --git a/test/test_utils.cpp b/test/test_utils.cpp
index 68a8d42..f56ae15 100644
--- a/test/test_utils.cpp
+++ b/test/test_utils.cpp
@@ -1,3 +1,5 @@
+#include "test_utils.hpp"
+
// mapnik
#include <mapnik/map.hpp>
#include <mapnik/layer.hpp>
@@ -20,9 +22,11 @@
#include <string>
#include <memory>
-namespace testing {
+namespace testing
+{
-std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool second) {
+std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool second)
+{
mapnik::parameters params;
params["type"] = "memory";
std::shared_ptr<mapnik::memory_datasource> ds = std::make_shared<mapnik::memory_datasource>(params);
@@ -41,7 +45,8 @@ std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool seco
feature->put_new("boolf",false);
feature->set_geometry(mapnik::geometry::point<double>(x,y));
ds->push(feature);
- if (second) {
+ if (second)
+ {
ctx->push("name2");
mapnik::feature_ptr feature2(mapnik::feature_factory::create(ctx,1));
feature2->put("name",tr.transcode("null island"));
@@ -52,7 +57,8 @@ std::shared_ptr<mapnik::memory_datasource> build_ds(double x,double y, bool seco
return ds;
}
-std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& geojson_file) {
+std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& geojson_file)
+{
mapnik::util::file input(geojson_file);
if (!input.open())
{
@@ -76,7 +82,8 @@ std::shared_ptr<mapnik::memory_datasource> build_geojson_ds(std::string const& g
return ds;
}
-std::shared_ptr<mapnik::datasource> build_geojson_fs_ds(std::string const& geojson_file) {
+mapnik::datasource_ptr build_geojson_fs_ds(std::string const& geojson_file)
+{
mapnik::parameters params;
params["type"] = "geojson";
params["file"] = geojson_file;
@@ -84,7 +91,8 @@ std::shared_ptr<mapnik::datasource> build_geojson_fs_ds(std::string const& geojs
return mapnik::datasource_cache::instance().create(params);
}
-mapnik::geometry::geometry<double> read_geojson(std::string const& geojson_file) {
+mapnik::geometry::geometry<double> read_geojson(std::string const& geojson_file)
+{
mapnik::util::file input(geojson_file);
if (!input.open())
{
@@ -121,5 +129,4 @@ unsigned compare_images(mapnik::image_rgba8 const& src1,
return mapnik::compare(src1,src2,threshold,alpha);
}
-
} // end ns
diff --git a/test/test_utils.hpp b/test/test_utils.hpp
index 96d9fbc..4da046d 100644
--- a/test/test_utils.hpp
+++ b/test/test_utils.hpp
@@ -12,7 +12,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);
-std::shared_ptr<mapnik::memory_datasource> build_geojson_fs_ds(std::string const& geojson_file);
+mapnik::datasource_ptr build_geojson_fs_ds(std::string const& geojson_file);
unsigned compare_images(std::string const& src_fn,
std::string const& dest_fn,
diff --git a/test/unit/composite/vector.cpp b/test/unit/composite/vector.cpp
new file mode 100644
index 0000000..2ffadd6
--- /dev/null
+++ b/test/unit/composite/vector.cpp
@@ -0,0 +1,15 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_composite.hpp"
+
+//
+// Unit tests for vt compositing
+//
+
+TEST_CASE("composite")
+{
+ CHECK(true);
+}
+
+
diff --git a/test/unit/compression/compression.cpp b/test/unit/compression/compression.cpp
new file mode 100644
index 0000000..444bcaf
--- /dev/null
+++ b/test/unit/compression/compression.cpp
@@ -0,0 +1,460 @@
+#include "catch.hpp"
+
+// mapnik-vector-tile
+#include "vector_tile_compression.hpp"
+
+TEST_CASE("invalid decompression")
+{
+ std::string data("this is a string that should be compressed data");
+ // data is not compressed but we will try to decompress it
+ std::string output;
+ CHECK_THROWS(mapnik::vector_tile_impl::zlib_decompress(data, output));
+}
+
+TEST_CASE("round trip compression - zlib")
+{
+ std::string data("this is a sentence that will be compressed into something");
+ CHECK(!mapnik::vector_tile_impl::is_zlib_compressed(data));
+
+ int strategy;
+
+ SECTION("strategy - invalid compression")
+ {
+ strategy = 99;
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ CHECK_THROWS(mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy));
+ }
+
+ SECTION("compression level - invalid")
+ {
+ strategy = Z_DEFAULT_STRATEGY;
+ int level = 99;
+ std::string compressed_data;
+ CHECK_THROWS(mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy));
+ }
+
+ SECTION("strategy - default")
+ {
+ strategy = Z_DEFAULT_STRATEGY;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+
+ SECTION("strategy - filtered")
+ {
+ strategy = Z_FILTERED;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+
+ SECTION("strategy - huffman only")
+ {
+ strategy = Z_HUFFMAN_ONLY;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+
+ SECTION("strategy - rle")
+ {
+ strategy = Z_RLE;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+
+ SECTION("strategy - fixed")
+ {
+ strategy = Z_FIXED;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, false, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_zlib_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+}
+
+TEST_CASE("round trip compression - gzip")
+{
+ std::string data("this is a sentence that will be compressed into something");
+ CHECK(!mapnik::vector_tile_impl::is_gzip_compressed(data));
+
+ int strategy;
+
+ SECTION("strategy - invalid compression")
+ {
+ strategy = 99;
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ CHECK_THROWS(mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy));
+ }
+
+ SECTION("compression level - invalid")
+ {
+ strategy = Z_DEFAULT_STRATEGY;
+ int level = 99;
+ std::string compressed_data;
+ CHECK_THROWS(mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy));
+ }
+
+ SECTION("strategy - default")
+ {
+ strategy = Z_DEFAULT_STRATEGY;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+
+ SECTION("strategy - filtered")
+ {
+ strategy = Z_FILTERED;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+
+ SECTION("strategy - huffman only")
+ {
+ strategy = Z_HUFFMAN_ONLY;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+
+ SECTION("strategy - rle")
+ {
+ strategy = Z_RLE;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+
+ SECTION("strategy - fixed")
+ {
+ strategy = Z_FIXED;
+
+ SECTION("no compression")
+ {
+ int level = Z_NO_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("default compression level")
+ {
+ int level = Z_DEFAULT_COMPRESSION;
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+
+ SECTION("compression level -- min to max")
+ {
+ for (int level = Z_BEST_SPEED; level <= Z_BEST_COMPRESSION; ++level)
+ {
+ std::string compressed_data;
+ mapnik::vector_tile_impl::zlib_compress(data, compressed_data, true, level, strategy);
+ CHECK(mapnik::vector_tile_impl::is_gzip_compressed(compressed_data));
+ std::string new_data;
+ mapnik::vector_tile_impl::zlib_decompress(compressed_data, new_data);
+ CHECK(data == new_data);
+ }
+ }
+ }
+}
diff --git a/test/unit/datasource-pbf/from_layer.cpp b/test/unit/datasource-pbf/from_layer.cpp
new file mode 100644
index 0000000..557b134
--- /dev/null
+++ b/test/unit/datasource-pbf/from_layer.cpp
@@ -0,0 +1,298 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_datasource_pbf.hpp"
+#include "vector_tile_projection.hpp"
+
+// mapnik
+#include <mapnik/util/geometry_to_wkt.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// Boost
+#include <boost/optional.hpp>
+
+
+TEST_CASE( "cannot create datasource from layer pbf without name" )
+{
+ std::string buffer;
+ vector_tile::Tile_Layer layer;
+
+ SECTION("VT Spec v1")
+ {
+ layer.set_version(1);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ try
+ {
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+ FAIL( "expected exception" );
+ }
+ catch(std::exception const& ex)
+ {
+ CHECK(std::string(ex.what()) == "The required name field is missing in a vector tile layer.");
+ }
+ }
+
+ SECTION("VT Spec v2")
+ {
+ layer.set_version(2);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ try
+ {
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+ FAIL( "expected exception" );
+ }
+ catch(std::exception const& ex)
+ {
+ CHECK(std::string(ex.what()) == "The required name field is missing in a vector tile layer. Tile does not comply with Version 2 of the Mapbox Vector Tile Specification.");
+ }
+ }
+}
+
+TEST_CASE( "can create datasource from layer pbf with name but without extent" )
+{
+ std::string buffer;
+ vector_tile::Tile_Layer layer;
+ layer.set_name("test_name");
+
+ SECTION("VT Spec v1")
+ {
+ layer.set_version(1);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ CHECK(ds.get_name() == "test_name");
+ }
+
+ SECTION("VT Spec v2")
+ {
+ layer.set_version(2);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ try
+ {
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+ FAIL( "expected exception" );
+ }
+ catch(std::exception const& ex)
+ {
+ CHECK(std::string(ex.what()) == "The required extent field is missing in the layer test_name. Tile does not comply with Version 2 of the Mapbox Vector Tile Specification.");
+ }
+ }
+}
+
+TEST_CASE( "can create datasource from layer pbf with name and extent" )
+{
+ std::string buffer;
+ vector_tile::Tile_Layer layer;
+ layer.set_name("test_name");
+ layer.set_extent(4096);
+
+ SECTION("VT Spec v1")
+ {
+ layer.set_version(1);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ CHECK(ds.get_name() == "test_name");
+ }
+
+ SECTION("VT Spec v2")
+ {
+ layer.set_version(2);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ CHECK(ds.get_name() == "test_name");
+ }
+}
+
+TEST_CASE( "extent of a tile effects the scale of features" )
+{
+ std::string buffer;
+ vector_tile::Tile_Layer layer;
+ layer.set_name("test_name");
+
+ // Add feature to layer
+ vector_tile::Tile_Feature * new_feature = layer.add_features();
+ new_feature->set_type(vector_tile::Tile_GeomType_POINT);
+ // MoveTo(5,5)
+ new_feature->add_geometry(9); // move_to | (1 << 3)
+ new_feature->add_geometry(protozero::encode_zigzag32(5));
+ new_feature->add_geometry(protozero::encode_zigzag32(5));
+
+ SECTION("default for v1 is 4096")
+ {
+ layer.set_version(1);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ mapnik::query q(ds.get_tile_extent());
+ mapnik::featureset_ptr featureset = ds.features(q);
+ REQUIRE(featureset);
+ mapnik::feature_ptr feature = featureset->next();
+
+ std::string wkt0;
+ mapnik::util::to_wkt(wkt0, feature->get_geometry());
+ CHECK(wkt0 == "POINT(-19988588.6446867 19988588.6446867)");
+ }
+
+ SECTION("geometry coordinates change if layer has different extent")
+ {
+ layer.set_extent(2048);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ mapnik::query q(ds.get_tile_extent());
+ mapnik::featureset_ptr featureset = ds.features(q);
+ REQUIRE(featureset);
+ mapnik::feature_ptr feature = featureset->next();
+
+ std::string wkt0;
+ mapnik::util::to_wkt(wkt0, feature->get_geometry());
+ CHECK(wkt0 == "POINT(-19939668.9465842 19939668.9465842)");
+ }
+}
+
+TEST_CASE( "datasource of empty layer pbf returns a null featureset pointer" )
+{
+ // From the spec: A layer SHOULD contain at least one feature.
+ // Unknown behavior when that is not the case. Current behavior is to
+ // return a null featureset.
+ std::string buffer;
+ vector_tile::Tile_Layer layer;
+ layer.set_name("test_name");
+ layer.set_extent(4096);
+
+ SECTION("VT Spec v1")
+ {
+ layer.set_version(1);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ mapnik::query q(ds.get_tile_extent());
+ mapnik::featureset_ptr featureset = ds.features(q);
+
+ CHECK(!featureset);
+ }
+
+ SECTION("VT Spec v2")
+ {
+ layer.set_version(2);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ mapnik::query q(ds.get_tile_extent());
+ mapnik::featureset_ptr featureset = ds.features(q);
+
+ CHECK(!featureset);
+ }
+}
+
+TEST_CASE( "datasource of pbf with unkown version returns a null featureset pointer" )
+{
+ // From spec:
+ // When a Vector Tile consumer encounters a Vector Tile layer with an unknown
+ // version, it MAY make a best-effort attempt to interpret the layer, or it MAY
+ // skip the layer. In either case it SHOULD continue to process subsequent layers
+ // in the Vector Tile.
+ std::string buffer;
+ vector_tile::Tile_Layer layer;
+ layer.set_name("test_name");
+ layer.set_extent(4096);
+
+ layer.set_version(3);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ mapnik::query q(ds.get_tile_extent());
+ mapnik::featureset_ptr featureset = ds.features(q);
+
+ CHECK(!featureset);
+}
+
+TEST_CASE( "datasource of empty layer pbf returns a null featureset pointer for features_at_point query" )
+{
+ // From the spec: A layer SHOULD contain at least one feature.
+ // Unknown behavior when that is not the case. Current behavior is to
+ // return a null featureset.
+ std::string buffer;
+ vector_tile::Tile_Layer layer;
+ layer.set_name("test_name");
+ layer.set_extent(4096);
+
+ SECTION("VT Spec v1")
+ {
+ layer.set_version(1);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ mapnik::featureset_ptr featureset = ds.features_at_point(mapnik::coord2d(0.0,0.0),0.0001);
+
+ CHECK(!featureset);
+ }
+
+ SECTION("VT Spec v2")
+ {
+ layer.set_version(2);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ mapnik::featureset_ptr featureset = ds.features_at_point(mapnik::coord2d(0.0,0.0),0.0001);
+
+ CHECK(!featureset);
+ }
+}
+
+TEST_CASE( "datasource of pbf with unknown version returns a null featureset pointer" )
+{
+ // From spec:
+ // When a Vector Tile consumer encounters a Vector Tile layer with an unknown
+ // version, it MAY make a best-effort attempt to interpret the layer, or it MAY
+ // skip the layer. In either case it SHOULD continue to process subsequent layers
+ // in the Vector Tile.
+ std::string buffer;
+ vector_tile::Tile_Layer layer;
+ layer.set_name("test_name");
+ layer.set_extent(4096);
+
+ layer.set_version(3);
+ layer.SerializePartialToString(&buffer);
+ protozero::pbf_reader pbf_layer(buffer);
+
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(pbf_layer,0,0,0);
+
+ mapnik::featureset_ptr featureset = ds.features_at_point(mapnik::coord2d(0.0,0.0),0.0001);
+
+ CHECK(!featureset);
+}
diff --git a/test/unit/decoding/linestring.cpp b/test/unit/decoding/linestring.cpp
new file mode 100644
index 0000000..275c092
--- /dev/null
+++ b/test/unit/decoding/linestring.cpp
@@ -0,0 +1,554 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// test utils
+#include "decoding_util.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+//
+// Unit tests for geometry decoding of linestrings
+//
+
+TEST_CASE("decode simple linestring")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(10,10)
+ feature.add_geometry(protozero::encode_zigzag32(8));
+ feature.add_geometry(protozero::encode_zigzag32(8));
+ // LineTo(0,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(1 1,2 2,10 10,0 10)");
+ CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(1 1,2 2,10 10,0 10)");
+ CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+ }
+}
+
+TEST_CASE("decode degenerate line_string only moveto")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2));
+ }
+}
+
+TEST_CASE("decode degenerate line_string lineto(0,0)")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(1,1)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2));
+ }
+}
+
+TEST_CASE("decode line_string with first lineto command having delta zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(1,1),LineTo(2,2)
+ feature.add_geometry((2 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(1 1,2 2)");
+ CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(1 1,2 2)");
+ CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+ }
+}
+
+TEST_CASE("decode line_string with second lineto command having delta zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2),LineTo(2,2)
+ feature.add_geometry((2 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(1 1,2 2)");
+ CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(1 1,2 2)");
+ CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+ }
+}
+
+TEST_CASE("decode line_string with third lineto command having delta zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2),LineTo(3,3),LineTo(3,3)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(1 1,2 2,3 3)");
+ CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(1 1,2 2,3 3)");
+ CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+ }
+}
+
+TEST_CASE("decode degenerate linestring with close command at end")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // Close Path
+ feature.add_geometry(15); // close_path
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode degenerate linestring with close command first")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // Close Path
+ feature.add_geometry(15); // close_path
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode degenerate linestring with moveto command count greater then 1")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry((2 << 3u) | 1u); // command count 2
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // MoveTo(2,2)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(3,3)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode degenerate linestring with moveto command count of zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry((0 << 3u) | 1u); // command count 0
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(3,3)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode degenerate linestring with lineto command count of zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo command count 0
+ feature.add_geometry((0 << 3u) | 2u);
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode degenerate linestring that starts with unknown command")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ feature.add_geometry((1 << 3u) | 5u); // invalid command
+ // MoveTo(1,1)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode degenerate linestring that ends with unknown command")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry((1 << 3u) | 5u); // invalid command
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode degenerate linestring that begins with lineto")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // LineTo(1,1)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // MoveTo(2,2)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(3,3)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode degenerate linestring that begins with lineto delta zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // LineTo(0,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // MoveTo(1,1)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode degenerate linestring that begins with close")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // Close
+ feature.add_geometry(15);
+ // MoveTo(1,1)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
+
+TEST_CASE("decode linestring that begins with two moveto commands")
+{
+ // This should work with v1 but throw with v2
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo(1,1)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // MoveTo(2,2)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(3,3)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "LINESTRING(2 2,3 3)");
+ CHECK( geom.is<mapnik::geometry::line_string<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_LINESTRING, 2));
+ }
+}
diff --git a/test/unit/decoding/point.cpp b/test/unit/decoding/point.cpp
new file mode 100644
index 0000000..0345dc3
--- /dev/null
+++ b/test/unit/decoding/point.cpp
@@ -0,0 +1,365 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// test utils
+#include "decoding_util.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// std
+#include <limits>
+
+//
+// Unit tests for geometry decoding of points
+//
+
+TEST_CASE("decode simple point")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ // MoveTo(5,5)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(5));
+ feature.add_geometry(protozero::encode_zigzag32(5));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(5 5)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(5 5)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+}
+
+TEST_CASE("decode simple negative point")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ // MoveTo(-5,-5)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(-5));
+ feature.add_geometry(protozero::encode_zigzag32(-5));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(-5 -5)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(-5 -5)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+}
+
+TEST_CASE("point with delta of max int32")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ feature.add_geometry(9); // move_to | (1 << 3)
+ std::int64_t max_32t = std::numeric_limits<int32_t>::max();
+ feature.add_geometry(protozero::encode_zigzag32(max_32t));
+ feature.add_geometry(protozero::encode_zigzag32(max_32t));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(2147483647 2147483647)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(2147483647 2147483647)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+}
+
+TEST_CASE("point with delta of min int32")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ feature.add_geometry(9); // move_to | (1 << 3)
+ std::int64_t min_32t = std::numeric_limits<int32_t>::min();
+ feature.add_geometry(protozero::encode_zigzag32(min_32t));
+ feature.add_geometry(protozero::encode_zigzag32(min_32t));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(-2147483648 -2147483648)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(-2147483648 -2147483648)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+}
+
+TEST_CASE("point with delta of min int32 + 1")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ feature.add_geometry(9); // move_to | (1 << 3)
+ std::int64_t min_32t = std::numeric_limits<int32_t>::min() + 1;
+ feature.add_geometry(protozero::encode_zigzag32(min_32t));
+ feature.add_geometry(protozero::encode_zigzag32(min_32t));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(-2147483647 -2147483647)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POINT(-2147483647 -2147483647)");
+ CHECK( geom.is<mapnik::geometry::point<double> >() );
+ }
+}
+
+TEST_CASE("degenerate point with close command")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // Close Path
+ feature.add_geometry(15); // close_path
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+ }
+}
+
+TEST_CASE("degenerate point with lineto command")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(1,1)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+ }
+}
+
+TEST_CASE("degenerate point with moveto with out enough parameters")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+ }
+}
+
+TEST_CASE("degenerate point with moveto with out enough parameters - case 2")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ // MoveTo(1,1)
+ feature.add_geometry((2 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+ }
+}
+
+TEST_CASE("degenerate point with moveto with command count of zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ // MoveTo(1,1)
+ feature.add_geometry((0 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+ }
+}
+
+TEST_CASE("degenerate point with invalid command")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POINT);
+ // MoveTo(1,1)
+ feature.add_geometry((1 << 3u) | 5u); // 5 isn't valid
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2));
+ }
+}
+
+TEST_CASE("multipoint with three movetos with command count 1")
+{
+ // While this is not the proper way to encode two movetwos we want to make sure
+ // that it still works properly in the decoder.
+ vector_tile::Tile_Feature feature;
+ //feature.set_type(vector_tile::Tile_GeomType_POINT);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // MoveTo(2,2)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // MoveTo(3,3)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "MULTIPOINT(1 1,2 2,3 3)");
+ CHECK( geom.is<mapnik::geometry::multi_point<double> >() );
+ }
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POINT, 1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "MULTIPOINT(1 1,2 2,3 3)");
+ CHECK( geom.is<mapnik::geometry::multi_point<double> >() );
+ }
+}
diff --git a/test/unit/decoding/polygon.cpp b/test/unit/decoding/polygon.cpp
new file mode 100644
index 0000000..d04e67b
--- /dev/null
+++ b/test/unit/decoding/polygon.cpp
@@ -0,0 +1,1621 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// test utils
+#include "decoding_util.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+//
+// Unit tests for geometry decoding of polygons
+//
+
+TEST_CASE("decode simple polygon")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon - int64 decode")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<std::int64_t> geoms = feature_to_pbf_geometry<std::int64_t>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon with hole")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // This ring is counter clockwise
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+ // This ring is clockwise!
+ // Cursor is still at -10,0
+ // MoveTo(-7,7)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(3));
+ feature.add_geometry(protozero::encode_zigzag32(7));
+ // LineTo(-3,7)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-3,3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ // LineTo(-7,3)
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode simple multipolygon")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // This ring is counter clockwise
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+ // This ring is counter clockwise -- so it is not a hole but a new polygon
+ // Cursor is still at -10,0
+ // MoveTo(-7,7)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(3));
+ feature.add_geometry(protozero::encode_zigzag32(7));
+ // LineTo(-7,3)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ // LineTo(-3,3)
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-3,7)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0)),((-7 7,-7 3,-3 3,-3 7,-7 7)))");
+ CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0)),((-7 7,-7 3,-3 3,-3 7,-7 7)))");
+ CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode polygon with hole where winding orders are reversed.")
+{
+ // This should work with v1 parser
+ // but fail with the v2 parsers.
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // This ring is clockwise
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(0,10)
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+ // This ring is counter clockwise -- so it is not a hole but a new polygon
+ // Cursor is still at 0,10
+ // MoveTo(-7,7)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(-7));
+ feature.add_geometry(protozero::encode_zigzag32(-3));
+ // LineTo(-7,3)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ // LineTo(-3,3)
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-3,7)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2));
+ }
+}
+
+TEST_CASE("decode simple multi polygon with hole")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // This ring is counter clockwise
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+ // This ring is clockwise!
+ // Cursor is still at -10,0
+ // MoveTo(-7,7)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(3));
+ feature.add_geometry(protozero::encode_zigzag32(7));
+ // LineTo(-3,7)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-3,3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ // LineTo(-7,3)
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+ // This ring is counter clockwise
+ // Cursor is still at -7,3
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(7));
+ feature.add_geometry(protozero::encode_zigzag32(-3));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+ // This ring is clockwise!
+ // Cursor is still at -10,0
+ // MoveTo(-7,7)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(3));
+ feature.add_geometry(protozero::encode_zigzag32(7));
+ // LineTo(-3,7)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-3,3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ // LineTo(-7,3)
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)),((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)))");
+ CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "MULTIPOLYGON(((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)),((0 0,0 10,-10 10,-10 0,0 0),(-7 7,-3 7,-3 3,-7 3,-7 7)))");
+ CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode multi polygon with holes - first ring invalid")
+{
+ // One of the problems with the v1 logic is that because we are
+ // not sure about the correct winding order if we run into an exterior ring
+ // that is invalid in the first ring it could reverse all the rest of the winding orders.
+ // Within v2 this should simply throw.
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // This ring is bad exterior ring. For v1, it will make the next interior ring as the first
+ // exterior ring!
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+ // This ring is clockwise!
+ // Cursor is still at -10,0
+ // MoveTo(-7,7)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(3));
+ feature.add_geometry(protozero::encode_zigzag32(7));
+ // LineTo(-3,7)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-3,3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ // LineTo(-7,3)
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+ // This ring is counter clockwise
+ // Cursor is still at -7,3
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(7));
+ feature.add_geometry(protozero::encode_zigzag32(-3));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+ // This ring is clockwise!
+ // Cursor is still at -10,0
+ // MoveTo(-7,7)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(3));
+ feature.add_geometry(protozero::encode_zigzag32(7));
+ // LineTo(-3,7)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-3,3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ // LineTo(-7,3)
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "MULTIPOLYGON(((-7 7,-7 3,-3 3,-3 7,-7 7),(0 0,-10 0,-10 10,0 10,0 0)),((-7 7,-7 3,-3 3,-3 7,-7 7)))");
+ CHECK( geom.is<mapnik::geometry::multi_polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2));
+ }
+}
+
+TEST_CASE("decode simple polygon -- incorrect exterior winding order")
+{
+ // winding order is clockwise
+ // v1 will reverse the order
+ // v2 will throw as invalid
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(10,10)
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 10,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto and close")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto and close followed by close")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // Close
+ feature.add_geometry(15);
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto and close followed by lineto")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // Close
+ feature.add_geometry(15);
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto and close followed by lineto -- delta zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // Close
+ feature.add_geometry(15);
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - moveto and close followed by real polygon")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // Close
+ feature.add_geometry(15);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(-1));
+ feature.add_geometry(protozero::encode_zigzag32(-1));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto and lineto")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(10,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - moveto and lineto followed by real polygon")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(-2));
+ feature.add_geometry(protozero::encode_zigzag32(-2));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+
+TEST_CASE("decode polygon - only moveto lineto and close")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(10,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, and close followed by close")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(10,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, and close followed by lineto")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(10,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+ // LineTo(11,1)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, and close followed by lineto -- delta zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(10,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+ // LineTo(10,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - moveto lineto and close followed by real polygon")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(1,1)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(2,2)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // Close
+ feature.add_geometry(15);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(-2));
+ feature.add_geometry(protozero::encode_zigzag32(-2));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 10,-10 10,-10 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - both delta zero one command")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,0)
+ feature.add_geometry((2 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - both delta zero two commands")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - first delta zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(1,1)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - second delta zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(1,1)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // LineTo(1,1)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ CHECK( geom.is<mapnik::geometry::geometry_empty>() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - lineto two commands")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(10,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(10,10)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode polygon - only moveto, lineto, lineto and close - lineto one command")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(10,0)
+ feature.add_geometry((2 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode polygon -- moveto command count zero")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry((0 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- moveto command count two")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry((2 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- lineto command count 0")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo no commands
+ feature.add_geometry((0 << 3u) | 2u);
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- lineto command count 2 when it should be 3")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((2 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- lineto command count 4 when it should be 3")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((4 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- close is first command")
+{
+ // It is important that in this test the lineto has dx and dy of 0.
+ // This checks that the skip dx,dy 0 on lineto doesn't prevent
+ // a series of bad commands from throwing.
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // Close
+ feature.add_geometry(15);
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- extra close command")
+{
+ // It is important that in this test the lineto has dx and dy of 0.
+ // This checks that the skip dx,dy 0 on lineto doesn't prevent
+ // a series of bad commands from throwing.
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- lineto is first command -- delta zero")
+{
+ // It is important that in this test the lineto has dx and dy of 0.
+ // This checks that the skip dx,dy 0 on lineto doesn't prevent
+ // a series of bad commands from throwing.
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // LineTo(0,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- lineto is first command")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // LineTo(1,1)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- lineto is last command -- delta zero")
+{
+ // It is important that in this test the lineto has dx and dy of 0.
+ // This checks that the skip dx,dy 0 on lineto doesn't prevent
+ // a series of bad commands from throwing.
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+ // LineTo(0,0)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- lineto is last command")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+ // LineTo(1,1)
+ feature.add_geometry((1 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(1));
+ feature.add_geometry(protozero::encode_zigzag32(1));
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
+
+TEST_CASE("decode polygon -- has invalid command first")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ feature.add_geometry((1 << 3u) | 5u); // Invalid command
+ // MoveTo(0,0)
+ feature.add_geometry((1 << 3u) | 1u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string);
+
+ SECTION("VT Spec v1")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 1));
+ }
+
+ SECTION("VT Spec v2")
+ {
+ CHECK_THROWS(mapnik::vector_tile_impl::decode_geometry(geoms, vector_tile::Tile_GeomType_POLYGON, 2));
+ }
+}
diff --git a/test/unit/decoding/polygon_scaling.cpp b/test/unit/decoding/polygon_scaling.cpp
new file mode 100644
index 0000000..33f10d2
--- /dev/null
+++ b/test/unit/decoding/polygon_scaling.cpp
@@ -0,0 +1,471 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+// test utils
+#include "decoding_util.hpp"
+#include "geom_to_wkt.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+//
+// Unit tests for geometry decoding of polygons with scaling
+//
+
+TEST_CASE("decode simple polygon - scale 2")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 2.0, 2.0);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 5,-5 5,-5 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 5,-5 5,-5 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon - scale 2 - int64 decode")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<std::int64_t> geoms = feature_to_pbf_geometry<std::int64_t>(feature_string, 2.0, 2.0);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 5,-5 5,-5 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 5,-5 5,-5 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon - scale 3.214")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 3.214, 3.214);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 3.11138767890479,-3.11138767890479 3.11138767890479,-3.11138767890479 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 3.11138767890479,-3.11138767890479 3.11138767890479,-3.11138767890479 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon - scale 3.214 - int64 decode")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<std::int64_t> geoms = feature_to_pbf_geometry<std::int64_t>(feature_string, 3.214, 3.214);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 3,-3 3,-3 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 3,-3 3,-3 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon - scale 0.46")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 0.46, 0.46);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 21.7391304347826,-21.7391304347826 21.7391304347826,-21.7391304347826 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 21.7391304347826,-21.7391304347826 21.7391304347826,-21.7391304347826 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon - scale 0.46 - int64 decode")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<std::int64_t> geoms = feature_to_pbf_geometry<std::int64_t>(feature_string, 0.46, 0.46);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 22,-22 22,-22 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 22,-22 22,-22 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<std::int64_t> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon - inverted y axis")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 1.0, -1.0);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,-10 0,-10 -10,0 -10,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,-10 0,-10 -10,0 -10,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon - inverted x axis")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, -1.0, 1.0);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 10,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,10 0,10 10,0 10,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon - inverted x axis and y axis")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, -1.0, -1.0);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 -10,10 -10,10 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,0 -10,10 -10,10 0,0 0))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
+
+TEST_CASE("decode simple polygon with hole - invert y axis")
+{
+ vector_tile::Tile_Feature feature;
+ feature.set_type(vector_tile::Tile_GeomType_POLYGON);
+ // This ring is counter clockwise
+ // MoveTo(0,0)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(0,10)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(10));
+ // LineTo(-10,10)
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-10,0)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-10));
+ // Close
+ feature.add_geometry(15);
+ // This ring is clockwise!
+ // Cursor is still at -10,0
+ // MoveTo(-7,7)
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(3));
+ feature.add_geometry(protozero::encode_zigzag32(7));
+ // LineTo(-3,7)
+ feature.add_geometry((3 << 3u) | 2u);
+ feature.add_geometry(protozero::encode_zigzag32(4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // LineTo(-3,3)
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ // LineTo(-7,3)
+ feature.add_geometry(protozero::encode_zigzag32(-4));
+ feature.add_geometry(protozero::encode_zigzag32(0));
+ // Close
+ feature.add_geometry(15);
+
+ std::string feature_string = feature.SerializeAsString();
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms = feature_to_pbf_geometry<double>(feature_string, 1.0, -1.0);
+
+ SECTION("VT Spec v1")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),1);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,-10 0,-10 -10,0 -10,0 0),(-7 -7,-7 -3,-3 -3,-3 -7,-7 -7))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+
+ SECTION("VT Spec v2")
+ {
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, feature.type(),2);
+ std::string wkt0;
+ CHECK( test_utils::to_wkt(wkt0,geom) );
+ CHECK( wkt0 == "POLYGON((0 0,-10 0,-10 -10,0 -10,0 0),(-7 -7,-7 -3,-3 -3,-3 -7,-7 -7))");
+ CHECK( geom.is<mapnik::geometry::polygon<double> >() );
+ }
+}
diff --git a/test/unit/encoding/linestring_pbf.cpp b/test/unit/encoding/linestring_pbf.cpp
new file mode 100644
index 0000000..7f4d7e6
--- /dev/null
+++ b/test/unit/encoding/linestring_pbf.cpp
@@ -0,0 +1,439 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// std
+#include <limits>
+
+//
+// Unit tests for geometry encoding of linestrings
+//
+
+TEST_CASE("encode pbf simple line_string")
+{
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(10,10);
+ line.add_coord(20,20);
+ line.add_coord(30,30);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Therefore 2 commands + 6 parameters = 8
+ REQUIRE(feature.geometry_size() == 8);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 20);
+ CHECK(feature.geometry(2) == 20);
+ // LineTo(20,20)
+ CHECK(feature.geometry(3) == ((2 << 3) | 2u));
+ CHECK(feature.geometry(4) == 20);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(30,30)
+ CHECK(feature.geometry(6) == 20);
+ CHECK(feature.geometry(7) == 20);
+}
+
+TEST_CASE("encode pbf simple line_string -- geometry type")
+{
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(10,10);
+ line.add_coord(20,20);
+ line.add_coord(30,30);
+ mapnik::geometry::geometry<std::int64_t> geom(line);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Therefore 2 commands + 6 parameters = 8
+ REQUIRE(feature.geometry_size() == 8);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 20);
+ CHECK(feature.geometry(2) == 20);
+ // LineTo(20,20)
+ CHECK(feature.geometry(3) == ((2 << 3) | 2u));
+ CHECK(feature.geometry(4) == 20);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(30,30)
+ CHECK(feature.geometry(6) == 20);
+ CHECK(feature.geometry(7) == 20);
+}
+
+TEST_CASE("encode pbf overlapping line_string")
+{
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(10,10);
+ line.add_coord(20,20);
+ line.add_coord(10,10);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Therefore 2 commands + 6 parameters = 8
+ REQUIRE(feature.geometry_size() == 8);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 20);
+ CHECK(feature.geometry(2) == 20);
+ // LineTo(20,20)
+ CHECK(feature.geometry(3) == ((2 << 3) | 2u));
+ CHECK(feature.geometry(4) == 20);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(10,10)
+ CHECK(feature.geometry(6) == 19);
+ CHECK(feature.geometry(7) == 19);
+}
+
+TEST_CASE("encode pbf line_string with repeated points")
+{
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(10,10);
+ line.add_coord(10,10);
+ line.add_coord(10,10);
+ line.add_coord(20,20);
+ line.add_coord(20,20);
+ line.add_coord(20,20);
+ line.add_coord(30,30);
+ line.add_coord(30,30);
+ line.add_coord(30,30);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+
+ // All of the repeated points should be removed resulting in the following:
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Therefore 2 commands + 6 parameters = 8
+ REQUIRE(feature.geometry_size() == 8);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 20);
+ CHECK(feature.geometry(2) == 20);
+ // LineTo(20,20)
+ CHECK(feature.geometry(3) == ((2 << 3) | 2u));
+ CHECK(feature.geometry(4) == 20);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(30,30)
+ CHECK(feature.geometry(6) == 20);
+ CHECK(feature.geometry(7) == 20);
+}
+
+TEST_CASE("encode pbf degenerate line_string")
+{
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(10,10);
+
+ // since the line is degenerate the whole line should be culled during encoding
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.has_type());
+ CHECK(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+ CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encode pbf degenerate line_string all repeated points")
+{
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(10,10);
+ line.add_coord(10,10);
+ line.add_coord(10,10);
+ line.add_coord(10,10);
+
+ // since the line is degenerate the whole line should be culled during encoding
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ CHECK(feature.has_type());
+ CHECK(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+ CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encode pbf incredibly large segments")
+{
+ // This is a test case added that is known to completely break the logic
+ // within the encoder.
+ std::int64_t val = std::numeric_limits<std::int64_t>::max();
+ mapnik::geometry::line_string<std::int64_t> line;
+ line.add_coord(0,0);
+ line.add_coord(val,val);
+ line.add_coord(0,0);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(line, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Therefore 2 commands + 6 parameters = 8
+ REQUIRE(feature.geometry_size() == 8);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(0,0)
+ CHECK(feature.geometry(3) == ((2 << 3) | 2u));
+ CHECK(feature.geometry(4) == 1);
+ CHECK(feature.geometry(5) == 1);
+ // LineTo(1,1)
+ CHECK(feature.geometry(6) == 2);
+ CHECK(feature.geometry(7) == 2);
+}
+
+TEST_CASE("encode pbf simple multi_line_string")
+{
+ mapnik::geometry::multi_line_string<std::int64_t> g;
+ mapnik::geometry::line_string<std::int64_t> l1;
+ l1.add_coord(0,0);
+ l1.add_coord(1,1);
+ l1.add_coord(2,2);
+ g.push_back(std::move(l1));
+ mapnik::geometry::line_string<std::int64_t> l2;
+ l2.add_coord(5,5);
+ l2.add_coord(0,0);
+ g.push_back(std::move(l2));
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(g, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger
+ // Therefore 4 commands + 10 parameters = 14
+ REQUIRE(feature.geometry_size() == 14);
+ // MoveTo(0,0)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(1,1)
+ CHECK(feature.geometry(3) == ((2 << 3) | 2u));
+ CHECK(feature.geometry(4) == 2);
+ CHECK(feature.geometry(5) == 2);
+ // LineTo(2,2)
+ CHECK(feature.geometry(6) == 2);
+ CHECK(feature.geometry(7) == 2);
+ // MoveTo(5,5)
+ CHECK(feature.geometry(8) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(9) == 6);
+ CHECK(feature.geometry(10) == 6);
+ // LineTo(0,0)
+ CHECK(feature.geometry(11) == ((1 << 3) | 2u));
+ CHECK(feature.geometry(12) == 9);
+ CHECK(feature.geometry(13) == 9);
+}
+
+TEST_CASE("encode pbf simple multi_line_string -- geometry type")
+{
+ mapnik::geometry::multi_line_string<std::int64_t> g;
+ mapnik::geometry::line_string<std::int64_t> l1;
+ l1.add_coord(0,0);
+ l1.add_coord(1,1);
+ l1.add_coord(2,2);
+ g.push_back(std::move(l1));
+ mapnik::geometry::line_string<std::int64_t> l2;
+ l2.add_coord(5,5);
+ l2.add_coord(0,0);
+ g.push_back(std::move(l2));
+ mapnik::geometry::geometry<std::int64_t> geom(g);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger
+ // Therefore 4 commands + 10 parameters = 14
+ REQUIRE(feature.geometry_size() == 14);
+ // MoveTo(0,0)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(1,1)
+ CHECK(feature.geometry(3) == ((2 << 3) | 2u));
+ CHECK(feature.geometry(4) == 2);
+ CHECK(feature.geometry(5) == 2);
+ // LineTo(2,2)
+ CHECK(feature.geometry(6) == 2);
+ CHECK(feature.geometry(7) == 2);
+ // MoveTo(5,5)
+ CHECK(feature.geometry(8) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(9) == 6);
+ CHECK(feature.geometry(10) == 6);
+ // LineTo(0,0)
+ CHECK(feature.geometry(11) == ((1 << 3) | 2u));
+ CHECK(feature.geometry(12) == 9);
+ CHECK(feature.geometry(13) == 9);
+}
+
+TEST_CASE("encode pbf multi_line_string with repeated points")
+{
+ mapnik::geometry::multi_line_string<std::int64_t> g;
+ mapnik::geometry::line_string<std::int64_t> l1;
+ l1.add_coord(0,0);
+ l1.add_coord(0,0);
+ l1.add_coord(0,0);
+ l1.add_coord(1,1);
+ l1.add_coord(1,1);
+ l1.add_coord(1,1);
+ l1.add_coord(2,2);
+ l1.add_coord(2,2);
+ l1.add_coord(2,2);
+ g.push_back(std::move(l1));
+ mapnik::geometry::line_string<std::int64_t> l2;
+ l2.add_coord(5,5);
+ l2.add_coord(5,5);
+ l2.add_coord(5,5);
+ l2.add_coord(0,0);
+ l2.add_coord(0,0);
+ l2.add_coord(0,0);
+ g.push_back(std::move(l2));
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(g, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+
+ // Repeated commands should be removed points should be as follows:
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger
+ // Therefore 4 commands + 10 parameters = 14
+ REQUIRE(feature.geometry_size() == 14);
+ // MoveTo(0,0)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(1,1)
+ CHECK(feature.geometry(3) == ((2 << 3) | 2u));
+ CHECK(feature.geometry(4) == 2);
+ CHECK(feature.geometry(5) == 2);
+ // LineTo(2,2)
+ CHECK(feature.geometry(6) == 2);
+ CHECK(feature.geometry(7) == 2);
+ // MoveTo(5,5)
+ CHECK(feature.geometry(8) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(9) == 6);
+ CHECK(feature.geometry(10) == 6);
+ // LineTo(0,0)
+ CHECK(feature.geometry(11) == ((1 << 3) | 2u));
+ CHECK(feature.geometry(12) == 9);
+ CHECK(feature.geometry(13) == 9);
+}
+
+TEST_CASE("encode pbf multi_line_string with two degenerate linestrings")
+{
+ mapnik::geometry::multi_line_string<std::int64_t> g;
+ mapnik::geometry::line_string<std::int64_t> l1;
+ l1.add_coord(0,0);
+ g.push_back(std::move(l1));
+ mapnik::geometry::line_string<std::int64_t> l2;
+ l2.add_coord(5,0);
+ l2.add_coord(5,0);
+ l2.add_coord(5,0);
+ g.push_back(std::move(l2));
+ mapnik::geometry::line_string<std::int64_t> l3;
+ l3.add_coord(5,5);
+ l3.add_coord(0,0);
+ g.push_back(std::move(l3));
+
+ // Should remove first line string as it does not have enough points
+ // and second linestring should be removed because it only has repeated
+ // points and therefore is too small
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(g, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_LINESTRING);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger
+ // Therefore 2 commands + 4 parameters = 6
+ REQUIRE(feature.geometry_size() == 6);
+ // MoveTo(5,5)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 10);
+ CHECK(feature.geometry(2) == 10);
+ // LineTo(0,0)
+ CHECK(feature.geometry(3) == ((1 << 3) | 2u));
+ CHECK(feature.geometry(4) == 9);
+ CHECK(feature.geometry(5) == 9);
+}
diff --git a/test/unit/encoding/point_pbf.cpp b/test/unit/encoding/point_pbf.cpp
new file mode 100644
index 0000000..0781be0
--- /dev/null
+++ b/test/unit/encoding/point_pbf.cpp
@@ -0,0 +1,202 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+// std
+#include <limits>
+
+//
+// Unit tests for geometry encoding of points
+//
+
+TEST_CASE("encode pbf simple point")
+{
+ mapnik::geometry::point<std::int64_t> point(10,10);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(point, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // Therefore 1 commands + 2 parameters = 3
+ REQUIRE(feature.geometry_size() == 3);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 20);
+ CHECK(feature.geometry(2) == 20);
+}
+
+TEST_CASE("encode pbf simple point -- geometry type")
+{
+ mapnik::geometry::point<std::int64_t> point(10,10);
+ mapnik::geometry::geometry<std::int64_t> geom(point);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // Therefore 1 commands + 2 parameters = 3
+ REQUIRE(feature.geometry_size() == 3);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 20);
+ CHECK(feature.geometry(2) == 20);
+}
+
+TEST_CASE("encode pbf simple negative point")
+{
+ mapnik::geometry::point<std::int64_t> point(-10,-10);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(point, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // Therefore 1 commands + 2 parameters = 3
+ REQUIRE( feature.geometry_size() == 3);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 19);
+ CHECK(feature.geometry(2) == 19);
+}
+
+TEST_CASE("encode pbf simple multi point -- geometry type")
+{
+ mapnik::geometry::multi_point<std::int64_t> mp;
+ mp.add_coord(10,10);
+ mp.add_coord(20,20);
+ mp.add_coord(30,30);
+ mapnik::geometry::geometry<std::int64_t> geom(mp);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+
+ // MoveTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Therefore 1 commands + 6 parameters = 7
+ REQUIRE( feature.geometry_size() == 7);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((3 << 3) | 1u)); // 25
+ CHECK(feature.geometry(1) == 20);
+ CHECK(feature.geometry(2) == 20);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(3) == 20);
+ CHECK(feature.geometry(4) == 20);
+ // MoveTo(20,20)
+ CHECK(feature.geometry(5) == 20);
+ CHECK(feature.geometry(6) == 20);
+}
+
+TEST_CASE("encode pbf simple multi point")
+{
+ mapnik::geometry::multi_point<std::int64_t> mp;
+ mp.add_coord(10,10);
+ mp.add_coord(20,20);
+ mp.add_coord(30,30);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+
+ // MoveTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Therefore 1 commands + 6 parameters = 7
+ REQUIRE( feature.geometry_size() == 7);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((3 << 3) | 1u)); // 25
+ CHECK(feature.geometry(1) == 20);
+ CHECK(feature.geometry(2) == 20);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(3) == 20);
+ CHECK(feature.geometry(4) == 20);
+ // MoveTo(20,20)
+ CHECK(feature.geometry(5) == 20);
+ CHECK(feature.geometry(6) == 20);
+}
+
+TEST_CASE("encode pbf multi point with repeated points")
+{
+ mapnik::geometry::multi_point<std::int64_t> mp;
+ mp.add_coord(10,10);
+ mp.add_coord(10,10);
+ mp.add_coord(20,20);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POINT);
+
+ // MoveTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Therefore 1 commands + 6 parameters = 7
+ REQUIRE( feature.geometry_size() == 7);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(0) == ((3 << 3) | 1u)); // 25
+ CHECK(feature.geometry(1) == 20);
+ CHECK(feature.geometry(2) == 20);
+ // MoveTo(10,10)
+ CHECK(feature.geometry(3) == 0);
+ CHECK(feature.geometry(4) == 0);
+ // MoveTo(20,20)
+ CHECK(feature.geometry(5) == 20);
+ CHECK(feature.geometry(6) == 20);
+}
+
+TEST_CASE("encode pbf empty multi point geometry")
+{
+ mapnik::geometry::multi_point<std::int64_t> mp;
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(!feature.has_type());
+
+ REQUIRE(feature.geometry_size() == 0);
+}
+
diff --git a/test/unit/encoding/polygon_pbf.cpp b/test/unit/encoding/polygon_pbf.cpp
new file mode 100644
index 0000000..511f961
--- /dev/null
+++ b/test/unit/encoding/polygon_pbf.cpp
@@ -0,0 +1,588 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+//
+// Unit tests for geometry encoding pbf of polygons
+//
+
+TEST_CASE("encoding pbf simple polygon")
+{
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // 3 commands + 8 Params = 11
+ REQUIRE(feature.geometry_size() == 11);
+ // MoveTo(0,0)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(0,10)
+ CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(4) == 0);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(-10,10)
+ CHECK(feature.geometry(6) == 19);
+ CHECK(feature.geometry(7) == 0);
+ // LineTo(-10,0)
+ CHECK(feature.geometry(8) == 0);
+ CHECK(feature.geometry(9) == 19);
+ // Close
+ CHECK(feature.geometry(10) == 15);
+}
+
+TEST_CASE("encoding pbf simple polygon -- geometry")
+{
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ mapnik::geometry::geometry<std::int64_t> geom(p0);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // 3 commands + 8 Params = 11
+ REQUIRE(feature.geometry_size() == 11);
+ // MoveTo(0,0)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(0,10)
+ CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(4) == 0);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(-10,10)
+ CHECK(feature.geometry(6) == 19);
+ CHECK(feature.geometry(7) == 0);
+ // LineTo(-10,0)
+ CHECK(feature.geometry(8) == 0);
+ CHECK(feature.geometry(9) == 19);
+ // Close
+ CHECK(feature.geometry(10) == 15);
+}
+
+TEST_CASE("encoding pbf simple polygon with hole")
+{
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ 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::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // 6 commands + 16 Params = 22
+ REQUIRE(feature.geometry_size() == 22);
+ // MoveTo(0,0)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(0,10)
+ CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(4) == 0);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(-10,10)
+ CHECK(feature.geometry(6) == 19);
+ CHECK(feature.geometry(7) == 0);
+ // LineTo(-10,0)
+ CHECK(feature.geometry(8) == 0);
+ CHECK(feature.geometry(9) == 19);
+ // Close
+ CHECK(feature.geometry(10) == 15);
+ // Remember the cursor didn't move after the close
+ // so it is at -10,0
+ // MoveTo(-7,7)
+ CHECK(feature.geometry(11) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(12) == 6);
+ CHECK(feature.geometry(13) == 14);
+ // LineTo(-3,7)
+ CHECK(feature.geometry(14) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(15) == 8);
+ CHECK(feature.geometry(16) == 0);
+ // LineTo(-3,3)
+ CHECK(feature.geometry(17) == 0);
+ CHECK(feature.geometry(18) == 7);
+ // LineTo(-7,3)
+ CHECK(feature.geometry(19) == 7);
+ CHECK(feature.geometry(20) == 0);
+ // Close
+ CHECK(feature.geometry(21) == 15);
+}
+
+TEST_CASE("encoding pbf empty polygon")
+{
+ mapnik::geometry::polygon<std::int64_t> p;
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(p, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.has_type());
+ CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+ REQUIRE(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf multi polygons with holes")
+{
+ mapnik::geometry::multi_polygon<std::int64_t> mp;
+ {
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ 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));
+ mp.push_back(p0);
+ }
+ // yeah so its the same polygon -- haters gonna hate.
+ {
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ 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));
+ mp.push_back(p0);
+ }
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // 12 commands + 32 Params = 44
+ REQUIRE(feature.geometry_size() == 44);
+ // MoveTo(0,0)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(0,10)
+ CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(4) == 0);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(-10,10)
+ CHECK(feature.geometry(6) == 19);
+ CHECK(feature.geometry(7) == 0);
+ // LineTo(-10,0)
+ CHECK(feature.geometry(8) == 0);
+ CHECK(feature.geometry(9) == 19);
+ // Close
+ CHECK(feature.geometry(10) == 15);
+ // Remember the cursor didn't move after the close
+ // so it is at -10,0
+ // MoveTo(-7,7)
+ CHECK(feature.geometry(11) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(12) == 6);
+ CHECK(feature.geometry(13) == 14);
+ // LineTo(-3,7)
+ CHECK(feature.geometry(14) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(15) == 8);
+ CHECK(feature.geometry(16) == 0);
+ // LineTo(-3,3)
+ CHECK(feature.geometry(17) == 0);
+ CHECK(feature.geometry(18) == 7);
+ // LineTo(-7,3)
+ CHECK(feature.geometry(19) == 7);
+ CHECK(feature.geometry(20) == 0);
+ // Close
+ CHECK(feature.geometry(21) == 15);
+ // Remember the cursor didn't move after the close
+ // so it is at -7,3
+ // MoveTo(0,0)
+ CHECK(feature.geometry(22) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(23) == 14);
+ CHECK(feature.geometry(24) == 5);
+ // LineTo(0,10)
+ CHECK(feature.geometry(25) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(26) == 0);
+ CHECK(feature.geometry(27) == 20);
+ // LineTo(-10,10)
+ CHECK(feature.geometry(28) == 19);
+ CHECK(feature.geometry(29) == 0);
+ // LineTo(-10,0)
+ CHECK(feature.geometry(30) == 0);
+ CHECK(feature.geometry(31) == 19);
+ // Close
+ CHECK(feature.geometry(32) == 15);
+ // Remember the cursor didn't move after the close
+ // so it is at -10,0
+ // MoveTo(-7,7)
+ CHECK(feature.geometry(33) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(34) == 6);
+ CHECK(feature.geometry(35) == 14);
+ // LineTo(-3,7)
+ CHECK(feature.geometry(36) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(37) == 8);
+ CHECK(feature.geometry(38) == 0);
+ // LineTo(-3,3)
+ CHECK(feature.geometry(39) == 0);
+ CHECK(feature.geometry(40) == 7);
+ // LineTo(-7,3)
+ CHECK(feature.geometry(41) == 7);
+ CHECK(feature.geometry(42) == 0);
+ // Close
+ CHECK(feature.geometry(43) == 15);
+}
+
+TEST_CASE("encoding pbf multi polygons with holes -- geometry type")
+{
+ mapnik::geometry::multi_polygon<std::int64_t> mp;
+ {
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ 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));
+ mp.push_back(p0);
+ }
+ // yeah so its the same polygon -- haters gonna hate.
+ {
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ 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));
+ mp.push_back(p0);
+ }
+ mapnik::geometry::geometry<std::int64_t> geom(mp);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // 12 commands + 32 Params = 44
+ REQUIRE(feature.geometry_size() == 44);
+ // MoveTo(0,0)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(0,10)
+ CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(4) == 0);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(-10,10)
+ CHECK(feature.geometry(6) == 19);
+ CHECK(feature.geometry(7) == 0);
+ // LineTo(-10,0)
+ CHECK(feature.geometry(8) == 0);
+ CHECK(feature.geometry(9) == 19);
+ // Close
+ CHECK(feature.geometry(10) == 15);
+ // Remember the cursor didn't move after the close
+ // so it is at -10,0
+ // MoveTo(-7,7)
+ CHECK(feature.geometry(11) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(12) == 6);
+ CHECK(feature.geometry(13) == 14);
+ // LineTo(-3,7)
+ CHECK(feature.geometry(14) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(15) == 8);
+ CHECK(feature.geometry(16) == 0);
+ // LineTo(-3,3)
+ CHECK(feature.geometry(17) == 0);
+ CHECK(feature.geometry(18) == 7);
+ // LineTo(-7,3)
+ CHECK(feature.geometry(19) == 7);
+ CHECK(feature.geometry(20) == 0);
+ // Close
+ CHECK(feature.geometry(21) == 15);
+ // Remember the cursor didn't move after the close
+ // so it is at -7,3
+ // MoveTo(0,0)
+ CHECK(feature.geometry(22) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(23) == 14);
+ CHECK(feature.geometry(24) == 5);
+ // LineTo(0,10)
+ CHECK(feature.geometry(25) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(26) == 0);
+ CHECK(feature.geometry(27) == 20);
+ // LineTo(-10,10)
+ CHECK(feature.geometry(28) == 19);
+ CHECK(feature.geometry(29) == 0);
+ // LineTo(-10,0)
+ CHECK(feature.geometry(30) == 0);
+ CHECK(feature.geometry(31) == 19);
+ // Close
+ CHECK(feature.geometry(32) == 15);
+ // Remember the cursor didn't move after the close
+ // so it is at -10,0
+ // MoveTo(-7,7)
+ CHECK(feature.geometry(33) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(34) == 6);
+ CHECK(feature.geometry(35) == 14);
+ // LineTo(-3,7)
+ CHECK(feature.geometry(36) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(37) == 8);
+ CHECK(feature.geometry(38) == 0);
+ // LineTo(-3,3)
+ CHECK(feature.geometry(39) == 0);
+ CHECK(feature.geometry(40) == 7);
+ // LineTo(-7,3)
+ CHECK(feature.geometry(41) == 7);
+ CHECK(feature.geometry(42) == 0);
+ // Close
+ CHECK(feature.geometry(43) == 15);
+}
+
+TEST_CASE("encoding pbf empty multi polygon")
+{
+ mapnik::geometry::multi_polygon<std::int64_t> mp;
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(mp, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.has_type());
+ CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+ REQUIRE(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf polygon with degenerate exterior ring full of repeated points")
+{
+ mapnik::geometry::polygon<std::int64_t> p0;
+ // invalid exterior ring
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(0,10);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.has_type());
+ CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+ CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf polygon with degenerate exterior ring")
+{
+ mapnik::geometry::polygon<std::int64_t> p0;
+ // invalid exterior ring
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.has_type());
+ CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+ CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf polygon with degenerate exterior ring and interior ring")
+{
+ mapnik::geometry::polygon<std::int64_t> p0;
+ // invalid exterior ring
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ // invalid interior ring -- is counter clockwise
+ 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));
+
+ // encoder should cull the exterior invalid ring, which triggers
+ // the entire polygon to be culled.
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE_FALSE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.has_type());
+ CHECK(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+ CHECK(feature.geometry_size() == 0);
+}
+
+TEST_CASE("encoding pbf polygon with valid exterior ring but degenerate interior ring")
+{
+ mapnik::geometry::polygon<std::int64_t> p0;
+ p0.exterior_ring.add_coord(0,0);
+ p0.exterior_ring.add_coord(0,10);
+ p0.exterior_ring.add_coord(-10,10);
+ p0.exterior_ring.add_coord(-10,0);
+ p0.exterior_ring.add_coord(0,0);
+ // invalid interior ring
+ mapnik::geometry::linear_ring<std::int64_t> hole;
+ hole.add_coord(-7,7);
+ hole.add_coord(-3,7);
+ p0.add_hole(std::move(hole));
+
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ vector_tile::Tile_Feature feature;
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(p0, feature_writer, x, y));
+ feature.ParseFromString(feature_str);
+ REQUIRE(feature.type() == vector_tile::Tile_GeomType_POLYGON);
+
+ // MoveTo, ParameterInteger, ParameterInteger
+ // LineTo, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger, ParameterInteger
+ // Close
+ // 3 commands + 8 Params = 11
+ REQUIRE(feature.geometry_size() == 11);
+ // MoveTo(0,0)
+ CHECK(feature.geometry(0) == ((1 << 3) | 1u)); // 9
+ CHECK(feature.geometry(1) == 0);
+ CHECK(feature.geometry(2) == 0);
+ // LineTo(0,10)
+ CHECK(feature.geometry(3) == ((3 << 3) | 2u));
+ CHECK(feature.geometry(4) == 0);
+ CHECK(feature.geometry(5) == 20);
+ // LineTo(-10,10)
+ CHECK(feature.geometry(6) == 19);
+ CHECK(feature.geometry(7) == 0);
+ // LineTo(-10,0)
+ CHECK(feature.geometry(8) == 0);
+ CHECK(feature.geometry(9) == 19);
+ // Close
+ CHECK(feature.geometry(10) == 15);
+}
+
diff --git a/test/unit/is_valid/feature_is_valid.cpp b/test/unit/is_valid/feature_is_valid.cpp
new file mode 100644
index 0000000..b3e9ce6
--- /dev/null
+++ b/test/unit/is_valid/feature_is_valid.cpp
@@ -0,0 +1,134 @@
+#include "catch.hpp"
+
+// mvt
+#include "vector_tile_is_valid.hpp"
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+#include "vector_tile.pb.h"
+
+typedef std::set<mapnik::vector_tile_impl::validity_error> error_set_T;
+
+TEST_CASE( "invalid empty feature" )
+{
+ std::string buffer;
+ vector_tile::Tile_Feature feature;
+ error_set_T errs;
+
+ feature.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_feature(buffer);
+
+ feature_is_valid(pbf_feature, errs);
+
+ CHECK(errs.empty() == false);
+ CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_IS_EMPTY) == 1);
+}
+
+TEST_CASE( "invalid geometry without type" )
+{
+ std::string buffer;
+ vector_tile::Tile_Feature feature;
+ error_set_T errs;
+
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(5));
+ feature.add_geometry(protozero::encode_zigzag32(5));
+
+ feature.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_feature(buffer);
+
+ feature_is_valid(pbf_feature, errs);
+
+ CHECK(errs.empty() == false);
+ CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_NO_GEOM_TYPE) == 1);
+}
+
+TEST_CASE( "valid raster feature" )
+{
+ std::string buffer;
+ vector_tile::Tile_Feature feature;
+ error_set_T errs;
+
+ feature.set_raster("raster-blaster");
+
+ feature.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_feature(buffer);
+
+ feature_is_valid(pbf_feature, errs);
+
+ CHECK(errs.empty() == true);
+}
+
+TEST_CASE( "valid geometry feature" )
+{
+ std::string buffer;
+ vector_tile::Tile_Feature feature;
+ error_set_T errs;
+
+ feature.set_type(vector_tile::Tile_GeomType::Tile_GeomType_POINT);
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(5));
+ feature.add_geometry(protozero::encode_zigzag32(5));
+
+ feature.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_feature(buffer);
+
+ feature_is_valid(pbf_feature, errs);
+
+ CHECK(errs.empty() == true);
+}
+
+TEST_CASE( "geometry feature with invalid type" )
+{
+ std::string buffer;
+ error_set_T errs;
+
+ protozero::pbf_writer pbf_message(buffer);
+ pbf_message.add_uint32(3, 4);
+
+ protozero::pbf_reader pbf_feature(buffer);
+
+ feature_is_valid(pbf_feature, errs);
+
+ CHECK(errs.empty() == false);
+ CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_HAS_INVALID_GEOM_TYPE) == 1);
+}
+
+TEST_CASE( "invalid feature with geometry and raster" )
+{
+ std::string buffer;
+ vector_tile::Tile_Feature feature;
+ error_set_T errs;
+
+ feature.set_type(vector_tile::Tile_GeomType::Tile_GeomType_POINT);
+ feature.add_geometry(9); // move_to | (1 << 3)
+ feature.add_geometry(protozero::encode_zigzag32(5));
+ feature.add_geometry(protozero::encode_zigzag32(5));
+
+ feature.set_raster("raster-blaster");
+
+ feature.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_feature(buffer);
+
+ feature_is_valid(pbf_feature, errs);
+
+ CHECK(errs.empty() == false);
+ CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_MULTIPLE_GEOM) == 1);
+}
+
+TEST_CASE( "invalid unknown tag in feature" )
+{
+ std::string buffer;
+ error_set_T errs;
+
+ protozero::pbf_writer pbf_message(buffer);
+ pbf_message.add_string(8, "unknown field");
+
+ protozero::pbf_reader pbf_value(buffer);
+
+ feature_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == false);
+ CHECK(errs.count(mapnik::vector_tile_impl::validity_error::FEATURE_HAS_UNKNOWN_TAG) == 1);
+}
diff --git a/test/unit/is_valid/value_is_valid.cpp b/test/unit/is_valid/value_is_valid.cpp
new file mode 100644
index 0000000..02d4467
--- /dev/null
+++ b/test/unit/is_valid/value_is_valid.cpp
@@ -0,0 +1,204 @@
+#include "catch.hpp"
+
+// mvt
+#include "vector_tile_is_valid.hpp"
+
+// protozero
+#include <protozero/pbf_writer.hpp>
+
+#include "vector_tile.pb.h"
+
+
+typedef std::set<mapnik::vector_tile_impl::validity_error> error_set_T;
+
+TEST_CASE( "invalid empty value" )
+{
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == false);
+ CHECK(errs.count(mapnik::vector_tile_impl::validity_error::VALUE_NO_VALUE) == 1);
+}
+
+TEST_CASE( "valid values" )
+{
+ SECTION("valid string")
+ {
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.set_string_value("just another mapnik monday");
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == true);
+ }
+
+ SECTION("valid float")
+ {
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.set_float_value(2015.0);
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == true);
+ }
+
+ SECTION("valid double")
+ {
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.set_double_value(2016.0);
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == true);
+ }
+
+ SECTION("valid int")
+ {
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.set_int_value(2017);
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == true);
+ }
+
+ SECTION("valid uint")
+ {
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.set_uint_value(2017);
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == true);
+ }
+
+ SECTION("valid sint")
+ {
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.set_sint_value(2017);
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == true);
+ }
+
+ SECTION("valid bool")
+ {
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.set_bool_value(false);
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == true);
+ }
+}
+
+TEST_CASE( "invalid multiple values" )
+{
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.set_bool_value(false);
+ value.set_string_value("false");
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == false);
+ CHECK(errs.count(mapnik::vector_tile_impl::validity_error::VALUE_MULTIPLE_VALUES) == 1);
+}
+
+TEST_CASE( "invalid unknown values" )
+{
+ std::string buffer;
+ vector_tile::Tile_Value value;
+ error_set_T errs;
+
+ value.set_bool_value(false);
+ value.set_string_value("false");
+
+ value.SerializeToString(&buffer);
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == false);
+ CHECK(errs.count(mapnik::vector_tile_impl::validity_error::VALUE_MULTIPLE_VALUES) == 1);
+}
+
+TEST_CASE( "invalid garbage value throws" )
+{
+ std::string buffer = "garbage";
+ error_set_T errs;
+
+ protozero::pbf_reader pbf_value(buffer);
+
+ CHECK_THROWS(value_is_valid(pbf_value, errs));
+}
+
+TEST_CASE( "invalid unknown tag" )
+{
+ std::string buffer;
+ error_set_T errs;
+
+ protozero::pbf_writer pbf_message(buffer);
+ pbf_message.add_string(8, "unknown field");
+
+ protozero::pbf_reader pbf_value(buffer);
+
+ value_is_valid(pbf_value, errs);
+
+ CHECK(errs.empty() == false);
+ CHECK(errs.count(mapnik::vector_tile_impl::validity_error::VALUE_HAS_UNKNOWN_TAG) == 1);
+}
diff --git a/test/unit/tile_impl/tile.cpp b/test/unit/tile_impl/tile.cpp
new file mode 100644
index 0000000..41e6b92
--- /dev/null
+++ b/test/unit/tile_impl/tile.cpp
@@ -0,0 +1,496 @@
+#include "catch.hpp"
+#include <memory>
+
+// mapnik vector tile tile class
+#include "vector_tile_tile.hpp"
+#include "vector_tile_layer.hpp"
+
+// mapnik
+#include <mapnik/feature.hpp>
+#include <mapnik/util/file_io.hpp>
+#include <mapnik/json/feature_parser.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+TEST_CASE("Vector tile base class")
+{
+ mapnik::box2d<double> global_extent(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
+
+ SECTION("default constructed")
+ {
+ mapnik::vector_tile_impl::tile default_tile(global_extent);
+
+ CHECK(default_tile.size() == 0);
+ CHECK(default_tile.data()[0] == '\0');
+ CHECK(std::abs(default_tile.scale() - 9783.9396205024) < 0.00001);
+
+ std::string str;
+ default_tile.serialize_to_string(str);
+ CHECK(str == "");
+ CHECK(default_tile.is_painted() == false);
+ CHECK(default_tile.is_empty() == true);
+
+ mapnik::box2d<double> global_buffered_extent(-21289852.6142133139073849,-21289852.6142133139073849,21289852.6142133139073849,21289852.6142133139073849);
+ CHECK(default_tile.extent() == global_extent);
+ CHECK(default_tile.get_buffered_extent() == global_buffered_extent);
+ CHECK(default_tile.tile_size() == 4096);
+
+ CHECK(default_tile.get_painted_layers().empty() == true);
+ CHECK(default_tile.get_empty_layers().empty() == true);
+ CHECK(default_tile.get_layers().empty() == true);
+ CHECK(default_tile.get_layers_set().empty() == true);
+
+ CHECK(default_tile.has_layer("anything") == false);
+
+ vector_tile::Tile t;
+ t.ParseFromString(default_tile.get_buffer());
+ CHECK(t.layers_size() == 0);
+ }
+
+ SECTION("construction with zero tile_size")
+ {
+ mapnik::vector_tile_impl::tile zero_size_tile(global_extent, 0, 0);
+
+ CHECK(zero_size_tile.tile_size() == 0);
+ CHECK(std::abs(zero_size_tile.scale() - 40075016.6855780035) < 0.00001);
+ CHECK(zero_size_tile.extent() == global_extent);
+ CHECK(zero_size_tile.get_buffered_extent() == global_extent);
+ }
+
+ SECTION("construction with negative tile_size")
+ {
+ mapnik::vector_tile_impl::tile negative_size_tile(global_extent, -1, 0);
+
+ CHECK(negative_size_tile.tile_size() == 4294967295);
+ CHECK(std::abs(negative_size_tile.scale() - 0.0093306919) < 0.0000001);
+ CHECK(negative_size_tile.get_buffered_extent() == global_extent);
+ }
+
+ SECTION("construction with positive buffer size")
+ {
+ mapnik::vector_tile_impl::tile positive_buffer_tile(global_extent, 4096, 10);
+
+ mapnik::box2d<double> buffered_extent(-20135347.7389940246939659,-20135347.7389940246939659,20135347.7389940246939659,20135347.7389940246939659);
+ CHECK(positive_buffer_tile.get_buffered_extent() == buffered_extent);
+ CHECK(positive_buffer_tile.buffer_size() == 10);
+ }
+
+ SECTION("construction with very negative buffer size")
+ {
+ mapnik::vector_tile_impl::tile negative_buffer_tile(global_extent, 4096, -4000);
+ mapnik::box2d<double> buffered_extent(0.0, 0.0, 0.0, 0.0);
+ CHECK(negative_buffer_tile.get_buffered_extent() == buffered_extent);
+ CHECK(negative_buffer_tile.buffer_size() == -4000);
+ }
+
+ SECTION("add bogus layer buffer")
+ {
+ mapnik::vector_tile_impl::tile tile(global_extent);
+ std::string bogus_layer_buffer = "blahblah";
+
+ tile.append_layer_buffer(bogus_layer_buffer.data(), bogus_layer_buffer.length(), "bogus");
+
+ const std::set<std::string> expected_set{"bogus"};
+ const std::set<std::string> empty_set;
+ const std::vector<std::string> expected_vec{"bogus"};
+
+ CHECK(tile.get_painted_layers() == expected_set);
+ CHECK(tile.get_empty_layers() == empty_set);
+ CHECK(tile.get_layers() == expected_vec);
+ CHECK(tile.get_layers_set() == expected_set);
+ CHECK(tile.has_layer("bogus") == true);
+ CHECK(tile.is_painted() == true);
+ CHECK(tile.is_empty() == false);
+
+ CHECK(tile.size() == 10);
+
+ std::string str;
+ tile.serialize_to_string(str);
+ CHECK(str == "\32\10blahblah");
+
+ std::string buffer(tile.data());
+
+ // Check the buffer itself
+ protozero::pbf_reader read_back(buffer);
+ CHECK(read_back.next(3) == true);
+ std::string blah_blah = read_back.get_string();
+ CHECK(blah_blah == "blahblah");
+
+ // Check the provided reader
+ protozero::pbf_reader tile_reader = tile.get_reader();
+ CHECK(tile_reader.next(3) == true);
+ blah_blah = tile_reader.get_string();
+ CHECK(blah_blah == "blahblah");
+
+ protozero::pbf_reader layer_reader;
+ CHECK_THROWS_AS(tile.layer_reader("bogus", layer_reader), protozero::end_of_buffer_exception);
+
+ protozero::pbf_reader layer_reader_by_index;
+ bool status = tile.layer_reader(0, layer_reader_by_index);
+
+ CHECK(status == true);
+ CHECK_THROWS_AS(layer_reader_by_index.next(1), protozero::end_of_buffer_exception);
+
+ vector_tile::Tile bogus_tile;
+ bogus_tile.ParseFromString(tile.get_buffer());
+ CHECK(bogus_tile.layers_size() == 1);
+ vector_tile::Tile_Layer bogus_layer = bogus_tile.layers(0);
+ CHECK(bogus_layer.version() == 1);
+ CHECK(bogus_layer.name() == "");
+ CHECK(bogus_layer.features_size() == 0);
+ CHECK(bogus_layer.keys_size() == 0);
+ CHECK(bogus_layer.values_size() == 0);
+ CHECK(bogus_layer.extent() == 4096);
+ }
+
+ SECTION("Add valid layer with layer buffer")
+ {
+ mapnik::vector_tile_impl::tile tile(global_extent);
+
+ // Create layer
+ vector_tile::Tile_Layer layer;
+ layer.set_version(2);
+ layer.set_name("valid");
+
+ std::string layer_buffer;
+ layer.SerializePartialToString(&layer_buffer);
+ tile.append_layer_buffer(layer_buffer.data(), layer_buffer.length(), "valid");
+
+ const std::set<std::string> expected_set{"valid"};
+ const std::set<std::string> empty_set;
+ const std::vector<std::string> expected_vec{"valid"};
+
+ CHECK(tile.get_painted_layers() == expected_set);
+ CHECK(tile.get_empty_layers() == empty_set);
+ CHECK(tile.get_layers() == expected_vec);
+ CHECK(tile.get_layers_set() == expected_set);
+ CHECK(tile.has_layer("valid") == true);
+ CHECK(tile.is_painted() == true);
+ CHECK(tile.is_empty() == false);
+
+ protozero::pbf_reader layer_reader_by_name;
+ bool status_by_name = tile.layer_reader("valid", layer_reader_by_name);
+ CHECK(status_by_name == true);
+ CHECK(layer_reader_by_name.next(1) == true);
+ CHECK(layer_reader_by_name.get_string() == "valid");
+
+ protozero::pbf_reader layer_reader_by_index;
+ bool status_by_index = tile.layer_reader(0, layer_reader_by_index);
+ CHECK(status_by_index == true);
+ CHECK(layer_reader_by_index.next(1) == true);
+ CHECK(layer_reader_by_index.get_string() == "valid");
+
+ vector_tile::Tile parsed_tile;
+ parsed_tile.ParseFromString(tile.get_buffer());
+ CHECK(parsed_tile.layers_size() == 1);
+ vector_tile::Tile_Layer parsed_layer = parsed_tile.layers(0);
+ CHECK(parsed_layer.version() == 2);
+ CHECK(parsed_layer.name() == "valid");
+ }
+/*
+ SECTION("Add valid empty layer with tile_layer")
+ {
+ mapnik::vector_tile_impl::tile tile(global_extent);
+
+ // Create layer
+ mapnik::vector_tile_impl::tile_layer layer;
+ mapnik::vector_tile_impl::layer_builder_pbf builder("empty", 4096, layer.get_data());
+ layer.build(builder);
+
+ CHECK(layer.is_empty() == true);
+ CHECK(layer.name() == "empty");
+
+ tile.add_layer(layer);
+
+ const std::set<std::string> empty_set{"empty"};
+
+ CHECK(tile.get_empty_layers().size() == 1);
+ CHECK(tile.is_painted() == false);
+ CHECK(tile.is_empty() == true);
+ }
+
+ SECTION("Add valid layer with tile_layer")
+ {
+ mapnik::vector_tile_impl::tile tile(global_extent);
+
+ mapnik::vector_tile_impl::tile_layer layer;
+ layer.name("valid");
+ // Create layer builder and add feature
+ mapnik::vector_tile_impl::layer_builder_pbf builder("valid", 4096, layer.get_data());
+ protozero::pbf_writer feature_writer = builder.get_feature_writer();
+
+ // Get geojson file string
+ mapnik::util::file input("./test/data/linestrings_and_point.geojson");
+ std::string geojson(input.data().get(), input.size());
+ auto context = std::make_shared<mapnik::context_type>();
+ auto mapnik_feature = std::make_shared<mapnik::feature_impl>(context, 0);
+ mapnik::json::from_geojson(geojson, *mapnik_feature);
+
+ builder.add_feature(feature_writer, *mapnik_feature);
+
+ // Create layer
+ layer.build(builder);
+
+ // Check properties of layer
+ CHECK(layer.is_empty() == false);
+ CHECK(layer.name() == "valid");
+
+ // Add layer to tile
+ tile.add_layer(layer);
+
+ const std::set<std::string> expected_set{"valid"};
+ const std::set<std::string> empty_set;
+ const std::vector<std::string> expected_vec{"valid"};
+
+ CHECK(tile.get_painted_layers() == expected_set);
+ CHECK(tile.get_empty_layers() == empty_set);
+ CHECK(tile.get_layers() == expected_vec);
+ CHECK(tile.get_layers_set() == expected_set);
+ CHECK(tile.has_layer("valid") == true);
+ CHECK(tile.is_painted() == true);
+ CHECK(tile.is_empty() == false);
+
+ protozero::pbf_reader layer_reader_by_name;
+ bool status_by_name = tile.layer_reader("valid", layer_reader_by_name);
+ CHECK(status_by_name == true);
+ CHECK(layer_reader_by_name.next(1) == true);
+ CHECK(layer_reader_by_name.get_string() == "valid");
+
+ vector_tile::Tile parsed_tile;
+ parsed_tile.ParseFromString(tile.get_buffer());
+ CHECK(parsed_tile.layers_size() == 1);
+ vector_tile::Tile_Layer parsed_layer = parsed_tile.layers(0);
+ CHECK(parsed_layer.version() == 2);
+ CHECK(parsed_layer.name() == "valid");
+ }
+
+ SECTION("cannot add the same layer with add_layer")
+ {
+ mapnik::vector_tile_impl::tile tile(global_extent);
+
+ // Add empty layer to tile
+ tile.add_empty_layer("valid");
+ std::set<std::string> empty_set{"valid"};
+ CHECK(tile.get_empty_layers() == empty_set);
+
+ // Create layer builder and add feature
+ mapnik::vector_tile_impl::tile_layer layer;
+ layer.name("valid");
+ mapnik::vector_tile_impl::layer_builder_pbf builder("valid", 4096, layer.get_data());
+ protozero::pbf_writer feature_writer = builder.get_feature_writer();
+
+ // Get geojson file string
+ mapnik::util::file input("./test/data/linestrings_and_point.geojson");
+ std::string geojson(input.data().get(), input.size());
+ auto context = std::make_shared<mapnik::context_type>();
+ auto mapnik_feature = std::make_shared<mapnik::feature_impl>(context, 0);
+ mapnik::json::from_geojson(geojson, *mapnik_feature);
+
+ builder.add_feature(feature_writer, *mapnik_feature);
+
+ // Create layer
+ layer.build(builder);
+
+ // Add layer to tile
+ tile.add_layer(layer);
+ empty_set.clear();
+ CHECK(tile.get_empty_layers() == empty_set);
+ }
+
+ SECTION("add_layer takes layer out of empty layers")
+ {
+ mapnik::vector_tile_impl::tile tile(global_extent);
+
+ // Create layer builder and add feature
+ mapnik::vector_tile_impl::tile_layer layer;
+ layer.name("valid");
+ mapnik::vector_tile_impl::layer_builder_pbf builder("valid", 4096, layer.get_data());
+ protozero::pbf_writer feature_writer = builder.get_feature_writer();
+
+ // Get geojson file string
+ mapnik::util::file input("./test/data/linestrings_and_point.geojson");
+ std::string geojson(input.data().get(), input.size());
+ auto context = std::make_shared<mapnik::context_type>();
+ auto mapnik_feature = std::make_shared<mapnik::feature_impl>(context, 0);
+ mapnik::json::from_geojson(geojson, *mapnik_feature);
+
+ builder.add_feature(feature_writer, *mapnik_feature);
+
+ // Create layer
+ layer.build(builder);
+
+ // Check properties of layer
+ CHECK(layer.is_empty() == false);
+ CHECK(layer.name() == "valid");
+
+ // Add layer to tile
+ bool status1 = tile.add_layer(layer);
+ CHECK(status1 == true);
+
+ bool status2 = tile.add_layer(layer);
+ CHECK(status2 == false);
+ }
+*/
+ SECTION("layer_reader by name works by name in buffer")
+ {
+ // Note - if the names of the layer are different
+ // between the layer in the buffer and in the
+ // tile object, `has_layer` will use the one
+ // in the tile object, but `layer_reader` will
+ // use the name in the buffer
+ mapnik::vector_tile_impl::tile tile(global_extent);
+
+ // Create layer
+ vector_tile::Tile_Layer layer;
+ layer.set_version(2);
+ layer.set_name("buffer name");
+
+ // Add layer to tile
+ std::string layer_buffer;
+ layer.SerializePartialToString(&layer_buffer);
+ tile.append_layer_buffer(layer_buffer.data(), layer_buffer.length(), "layer name");
+
+ const std::set<std::string> expected_set{"layer name"};
+ const std::vector<std::string> expected_vec{"layer name"};
+
+ // Confirm the use of "layer name" in these methods
+ CHECK(tile.get_painted_layers() == expected_set);
+ CHECK(tile.get_layers() == expected_vec);
+ CHECK(tile.get_layers_set() == expected_set);
+ CHECK(tile.has_layer("layer name") == true);
+ CHECK(tile.has_layer("buffer name") == false);
+
+ // Confirm the use of "buffer name" in this method
+ protozero::pbf_reader layer_reader_by_buffer_name;
+ bool status_by_buffer_name = tile.layer_reader("buffer name", layer_reader_by_buffer_name);
+ CHECK(status_by_buffer_name == true);
+ CHECK(layer_reader_by_buffer_name.next(1) == true);
+ CHECK(layer_reader_by_buffer_name.get_string() == "buffer name");
+
+ protozero::pbf_reader layer_reader_by_name;
+ bool status_by_layer_name = tile.layer_reader("layer name", layer_reader_by_name);
+ CHECK(status_by_layer_name == false);
+ }
+
+ SECTION("layer ordering is deterministic")
+ {
+ // Newly added layers from buffers are added to the end of
+ // the tile, and are read from the tile in the same order
+ // as they are added
+ mapnik::vector_tile_impl::tile tile(global_extent);
+
+ // Create layers
+ vector_tile::Tile_Layer layer1, layer2;
+ layer1.set_version(2);
+ layer1.set_name("layer1");
+
+ layer2.set_version(2);
+ layer2.set_name("layer2");
+
+ std::string layer1_buffer, layer2_buffer;
+ layer1.SerializePartialToString(&layer1_buffer);
+ tile.append_layer_buffer(layer1_buffer.data(), layer1_buffer.length(), "layer1");
+
+ layer2.SerializePartialToString(&layer2_buffer);
+ tile.append_layer_buffer(layer2_buffer.data(), layer2_buffer.length(), "layer2");
+
+ const std::vector<std::string> expected_vec{"layer1", "layer2"};
+
+ // Both of the layers are here, in order
+ CHECK(tile.get_layers() == expected_vec);
+ CHECK(tile.has_layer("layer1") == true);
+ CHECK(tile.has_layer("layer2") == true);
+
+ // layer_reader reads them in the same order
+ protozero::pbf_reader layer_reader1, layer_reader2;
+ bool status1 = tile.layer_reader(0, layer_reader1);
+ CHECK(status1 == true);
+ CHECK(layer_reader1.next(1) == true);
+ CHECK(layer_reader1.get_string() == "layer1");
+
+ bool status2 = tile.layer_reader(1, layer_reader2);
+ CHECK(status2 == true);
+ CHECK(layer_reader2.next(1) == true);
+ CHECK(layer_reader2.get_string() == "layer2");
+ }
+
+ SECTION("cannot add same layer buffer twice")
+ {
+ // Newly added layers from buffers are added to the end of
+ // the tile, and are read from the tile in the same order
+ // as they are added
+ mapnik::vector_tile_impl::tile tile(global_extent);
+
+ // Create layers
+ vector_tile::Tile_Layer layer1, layer2;
+ layer1.set_version(2);
+ layer1.set_name("layer");
+
+ layer2.set_version(2);
+ layer2.set_name("layer");
+
+ std::string layer1_buffer, layer2_buffer;
+ layer1.SerializePartialToString(&layer1_buffer);
+ bool status1 = tile.append_layer_buffer(layer1_buffer.data(), layer1_buffer.length(), "layer");
+ CHECK(status1 == true);
+
+ layer2.SerializePartialToString(&layer2_buffer);
+ bool status2 = tile.append_layer_buffer(layer2_buffer.data(), layer2_buffer.length(), "layer");
+ CHECK(status2 == false);
+ }
+
+ SECTION("index out of bounds for layer_reader method")
+ {
+ mapnik::vector_tile_impl::tile tile(global_extent);
+
+ // Read a layer from an empty tile
+ protozero::pbf_reader layer_reader;
+ bool status = tile.layer_reader(0, layer_reader);
+ CHECK(status == false);
+ CHECK(layer_reader.next(1) == false);
+ }
+
+ SECTION("adding a valid layer takes name out of empty layers")
+ {
+ mapnik::vector_tile_impl::tile tile(global_extent);
+ tile.add_empty_layer("layer");
+
+ const std::set<std::string> expected_set{"layer"};
+
+ CHECK(tile.get_empty_layers() == expected_set);
+ CHECK(tile.has_layer("layer") == false);
+ CHECK(tile.is_painted() == false);
+ CHECK(tile.is_empty() == true);
+
+ // Create layers
+ vector_tile::Tile_Layer layer;
+ layer.set_version(2);
+ layer.set_name("layer");
+
+ std::string layer_buffer;
+ layer.SerializePartialToString(&layer_buffer);
+ tile.append_layer_buffer(layer_buffer.data(), layer_buffer.length(), "layer");
+
+ const std::set<std::string> empty_set;
+
+ CHECK(tile.get_empty_layers() == empty_set);
+ CHECK(tile.get_painted_layers() == expected_set);
+ CHECK(tile.has_layer("layer") == true);
+ CHECK(tile.is_painted() == true);
+ CHECK(tile.is_empty() == false);
+ }
+
+ SECTION("has same extent works correctly")
+ {
+ mapnik::vector_tile_impl::tile tile1(global_extent);
+ mapnik::vector_tile_impl::tile tile2(global_extent);
+
+ CHECK(tile1.same_extent(tile2) == true);
+ CHECK(tile2.same_extent(tile1) == true);
+ }
+}
diff --git a/test/utils/decoding_util.cpp b/test/utils/decoding_util.cpp
new file mode 100644
index 0000000..27cd3b3
--- /dev/null
+++ b/test/utils/decoding_util.cpp
@@ -0,0 +1,16 @@
+// test utils
+#include "decoding_util.hpp"
+
+// mapnik-vector-tile
+#include "vector_tile_geometry_decoder.hpp"
+
+template <typename T>
+mapnik::vector_tile_impl::GeometryPBF<T> feature_to_pbf_geometry(std::string const& feature_string, double scale_x, double scale_y)
+{
+ protozero::pbf_reader feature_pbf(feature_string);
+ feature_pbf.next(4);
+ return mapnik::vector_tile_impl::GeometryPBF<T>(feature_pbf.get_packed_uint32(),0.0,0.0,scale_x,scale_y);
+}
+
+template mapnik::vector_tile_impl::GeometryPBF<double> feature_to_pbf_geometry<double>(std::string const& feature_string, double scale_x, double scale_y);
+template mapnik::vector_tile_impl::GeometryPBF<std::int64_t> feature_to_pbf_geometry<std::int64_t>(std::string const& feature_string, double scale_x, double scale_y);
diff --git a/test/utils/decoding_util.hpp b/test/utils/decoding_util.hpp
new file mode 100644
index 0000000..d8becfa
--- /dev/null
+++ b/test/utils/decoding_util.hpp
@@ -0,0 +1,10 @@
+#ifndef __MAPNIK_VECTOR_TILE_TEST_DECODING_UTIL_H__
+#define __MAPNIK_VECTOR_TILE_TEST_DECODING_UTIL_H__
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+
+template <typename T>
+mapnik::vector_tile_impl::GeometryPBF<T> feature_to_pbf_geometry(std::string const& feature_string, double scale_x = 1.0, double scale_y = 1.0);
+
+#endif // __MAPNIK_VECTOR_TILE_TEST_DECODING_UTIL_H__
diff --git a/test/utils/encoding_util.cpp b/test/utils/encoding_util.cpp
new file mode 100644
index 0000000..6f28b87
--- /dev/null
+++ b/test/utils/encoding_util.cpp
@@ -0,0 +1,82 @@
+#include "catch.hpp"
+
+// mapnik vector tile
+#include "vector_tile_geometry_decoder.hpp"
+#include "vector_tile_geometry_encoder_pbf.hpp"
+
+// mapnik
+#include <mapnik/vertex.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_adapters.hpp>
+#include <mapnik/vertex_processor.hpp>
+
+// test utils
+#include "encoding_util.hpp"
+#include "decoding_util.hpp"
+
+using namespace mapnik::geometry;
+
+struct show_path
+{
+ std::string & str_;
+ show_path(std::string & out) :
+ str_(out) {}
+
+ template <typename T>
+ void operator()(T & path)
+ {
+ unsigned cmd = -1;
+ double x = 0;
+ double y = 0;
+ std::ostringstream s;
+ path.rewind(0);
+ while ((cmd = path.vertex(&x, &y)) != mapnik::SEG_END)
+ {
+ switch (cmd)
+ {
+ case mapnik::SEG_MOVETO: s << "move_to("; break;
+ case mapnik::SEG_LINETO: s << "line_to("; break;
+ case mapnik::SEG_CLOSE: s << "close_path("; break;
+ default: std::clog << "unhandled cmd " << cmd << "\n"; break;
+ }
+ s << x << "," << y << ")\n";
+ }
+ str_ += s.str();
+ }
+};
+
+template <typename T>
+std::string decode_to_path_string(mapnik::geometry::geometry<T> const& g)
+{
+ using decode_path_type = mapnik::geometry::vertex_processor<show_path>;
+ std::string out;
+ show_path sp(out);
+ mapnik::util::apply_visitor(decode_path_type(sp), g);
+ return out;
+}
+
+std::string compare_pbf(mapnik::geometry::geometry<std::int64_t> const& g, unsigned version)
+{
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ REQUIRE(mapnik::vector_tile_impl::encode_geometry_pbf(g, feature_writer, x, y));
+ protozero::pbf_reader feature_reader(feature_str);
+ int32_t geometry_type = mapnik::vector_tile_impl::Geometry_Type::UNKNOWN;
+ std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+ while (feature_reader.next())
+ {
+ if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::GEOMETRY)
+ {
+ geom_itr = feature_reader.get_packed_uint32();
+ }
+ else if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::TYPE)
+ {
+ geometry_type = feature_reader.get_enum();
+ }
+ }
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, 0.0, 0.0, 1.0, 1.0);
+ auto g2 = mapnik::vector_tile_impl::decode_geometry(geoms, geometry_type, version);
+ return decode_to_path_string(g2);
+}
diff --git a/test/utils/encoding_util.hpp b/test/utils/encoding_util.hpp
new file mode 100644
index 0000000..e3dc831
--- /dev/null
+++ b/test/utils/encoding_util.hpp
@@ -0,0 +1,16 @@
+#ifndef __MAPNIK_VECTOR_TILE_TEST_ENCODING_UTIL_H__
+#define __MAPNIK_VECTOR_TILE_TEST_ENCODING_UTIL_H__
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+std::string compare_pbf(mapnik::geometry::geometry<std::int64_t> const& g, unsigned version);
+
+#endif // __MAPNIK_VECTOR_TILE_TEST_ENCODING_UTIL_H__
diff --git a/test/utils/geom_to_wkt.cpp b/test/utils/geom_to_wkt.cpp
new file mode 100644
index 0000000..16e2e01
--- /dev/null
+++ b/test/utils/geom_to_wkt.cpp
@@ -0,0 +1,17 @@
+// mapnik
+#include <mapnik/util/geometry_to_wkt.hpp>
+
+namespace test_utils
+{
+
+bool to_wkt(std::string & wkt, mapnik::geometry::geometry<double> const& geom)
+{
+ return mapnik::util::to_wkt(wkt, geom);
+}
+
+bool to_wkt(std::string & wkt, mapnik::geometry::geometry<std::int64_t> const& geom)
+{
+ return mapnik::util::to_wkt(wkt, geom);
+}
+
+} // end ns test_utils
diff --git a/test/utils/geom_to_wkt.hpp b/test/utils/geom_to_wkt.hpp
new file mode 100644
index 0000000..236d9f3
--- /dev/null
+++ b/test/utils/geom_to_wkt.hpp
@@ -0,0 +1,10 @@
+// mapnik
+#include <mapnik/geometry.hpp>
+
+namespace test_utils
+{
+
+bool to_wkt(std::string & wkt, mapnik::geometry::geometry<double> const& geom);
+bool to_wkt(std::string & wkt, mapnik::geometry::geometry<std::int64_t> const& geom);
+
+} // end ns test_utils
diff --git a/test/utils/geometry_equal.hpp b/test/utils/geometry_equal.hpp
new file mode 100644
index 0000000..fa53446
--- /dev/null
+++ b/test/utils/geometry_equal.hpp
@@ -0,0 +1,235 @@
+#include "catch.hpp"
+
+// boost
+#include <type_traits>
+#include <iterator>
+
+#pragma GCC diagnostic push
+#include <mapnik/warning_ignore.hpp>
+#include <boost/tuple/tuple.hpp>
+#include <boost/iterator/zip_iterator.hpp>
+#include <boost/range/iterator_range.hpp>
+#pragma GCC diagnostic pop
+
+// helper namespace to ensure correct functionality
+namespace aux{
+namespace adl{
+using std::begin;
+using std::end;
+
+template<class T>
+auto do_begin(T& v) -> decltype(begin(v));
+template<class T>
+auto do_end(T& v) -> decltype(end(v));
+} // adl::
+
+template<class... Its>
+using zipper_it = boost::zip_iterator<boost::tuple<Its...>>;
+
+template<class T>
+T const& as_const(T const& v){ return v; }
+} // aux::
+
+template<class... Conts>
+auto zip_begin(Conts&... conts)
+ -> aux::zipper_it<decltype(aux::adl::do_begin(conts))...>
+{
+ using std::begin;
+ return {boost::make_tuple(begin(conts)...)};
+}
+
+template<class... Conts>
+auto zip_end(Conts&... conts)
+ -> aux::zipper_it<decltype(aux::adl::do_end(conts))...>
+{
+ using std::end;
+ return {boost::make_tuple(end(conts)...)};
+}
+
+template<class... Conts>
+auto zip_range(Conts&... conts)
+ -> boost::iterator_range<decltype(zip_begin(conts...))>
+{
+ return {zip_begin(conts...), zip_end(conts...)};
+}
+
+// for const access
+template<class... Conts>
+auto zip_cbegin(Conts&... conts)
+ -> decltype(zip_begin(aux::as_const(conts)...))
+{
+ using std::begin;
+ return zip_begin(aux::as_const(conts)...);
+}
+
+template<class... Conts>
+auto zip_cend(Conts&... conts)
+ -> decltype(zip_end(aux::as_const(conts)...))
+{
+ using std::end;
+ return zip_end(aux::as_const(conts)...);
+}
+
+template<class... Conts>
+auto zip_crange(Conts&... conts)
+ -> decltype(zip_range(aux::as_const(conts)...))
+{
+ return zip_range(aux::as_const(conts)...);
+}
+
+// mapnik
+#include <mapnik/geometry.hpp>
+#include <mapnik/util/variant.hpp>
+
+#include <string>
+#include <cstdlib>
+#include <cxxabi.h>
+
+template<typename T>
+std::string type_name()
+{
+ int status;
+ std::string tname = typeid(T).name();
+ char *demangled_name = abi::__cxa_demangle(tname.c_str(), NULL, NULL, &status);
+ if(status == 0) {
+ tname = demangled_name;
+ std::free(demangled_name);
+ }
+ return tname;
+}
+
+using namespace mapnik::geometry;
+
+template <typename T>
+void assert_g_equal(geometry<T> const& g1, geometry<T> const& g2);
+
+struct geometry_equal_visitor
+{
+ template <typename T1, typename T2>
+ void operator() (T1 const&, T2 const&)
+ {
+ // comparing two different types!
+ INFO(type_name<T1>());
+ INFO(type_name<T2>());
+ REQUIRE(false);
+ }
+
+ void operator() (geometry_empty const&, geometry_empty const&)
+ {
+ REQUIRE(true);
+ }
+
+ template <typename T>
+ void operator() (point<T> const& p1, point<T> const& p2)
+ {
+ REQUIRE(p1.x == Approx(p2.x));
+ REQUIRE(p1.y == Approx(p2.y));
+ }
+
+ template <typename T>
+ void operator() (line_string<T> const& ls1, line_string<T> const& ls2)
+ {
+ if (ls1.size() != ls2.size())
+ {
+ REQUIRE(false);
+ }
+
+ for(auto const& p : zip_crange(ls1, ls2))
+ {
+ REQUIRE(p.template get<0>().x == Approx(p.template get<1>().x));
+ REQUIRE(p.template get<0>().y == Approx(p.template get<1>().y));
+ }
+ }
+
+ template <typename T>
+ void operator() (polygon<T> const& p1, polygon<T> const& p2)
+ {
+ (*this)(static_cast<line_string<T> const&>(p1.exterior_ring), static_cast<line_string<T> const&>(p2.exterior_ring));
+
+ if (p1.interior_rings.size() != p2.interior_rings.size())
+ {
+ REQUIRE(false);
+ }
+
+ for (auto const& p : zip_crange(p1.interior_rings, p2.interior_rings))
+ {
+ (*this)(static_cast<line_string<T> const&>(p.template get<0>()),static_cast<line_string<T> const&>(p.template get<1>()));
+ }
+ }
+
+ template <typename T>
+ void operator() (multi_point<T> const& mp1, multi_point<T> const& mp2)
+ {
+ (*this)(static_cast<line_string<T> const&>(mp1), static_cast<line_string<T> const&>(mp2));
+ }
+
+ template <typename T>
+ void operator() (multi_line_string<T> const& mls1, multi_line_string<T> const& mls2)
+ {
+ if (mls1.size() != mls2.size())
+ {
+ REQUIRE(false);
+ }
+
+ for (auto const& ls : zip_crange(mls1, mls2))
+ {
+ (*this)(ls.template get<0>(),ls.template get<1>());
+ }
+ }
+
+ template <typename T>
+ void operator() (multi_polygon<T> const& mpoly1, multi_polygon<T> const& mpoly2)
+ {
+ if (mpoly1.size() != mpoly2.size())
+ {
+ REQUIRE(false);
+ }
+
+ for (auto const& poly : zip_crange(mpoly1, mpoly2))
+ {
+ (*this)(poly.template get<0>(),poly.template get<1>());
+ }
+ }
+
+ template <typename T>
+ void operator() (mapnik::util::recursive_wrapper<geometry_collection<T> > const& c1_, mapnik::util::recursive_wrapper<geometry_collection<T> > const& c2_)
+ {
+ geometry_collection<T> const& c1 = static_cast<geometry_collection<T> const&>(c1_);
+ geometry_collection<T> const& c2 = static_cast<geometry_collection<T> const&>(c2_);
+ if (c1.size() != c2.size())
+ {
+ REQUIRE(false);
+ }
+
+ for (auto const& g : zip_crange(c1, c2))
+ {
+ assert_g_equal(g.template get<0>(),g.template get<1>());
+ }
+ }
+
+ template <typename T>
+ void operator() (geometry_collection<T> const& c1, geometry_collection<T> const& c2)
+ {
+ if (c1.size() != c2.size())
+ {
+ REQUIRE(false);
+ }
+
+ for (auto const& g : zip_crange(c1, c2))
+ {
+ assert_g_equal(g.template get<0>(),g.template get<1>());
+ }
+ }
+};
+
+template <typename T>
+void assert_g_equal(geometry<T> const& g1, geometry<T> const& g2)
+{
+ return mapnik::util::apply_visitor(geometry_equal_visitor(), g1, g2);
+}
+
+template <typename T>
+void assert_g_equal(T const& g1, T const& g2)
+{
+ return geometry_equal_visitor()(g1,g2);
+}
diff --git a/test/utils/round_trip.cpp b/test/utils/round_trip.cpp
new file mode 100644
index 0000000..420a2b2
--- /dev/null
+++ b/test/utils/round_trip.cpp
@@ -0,0 +1,89 @@
+// test utils
+#include "round_trip.hpp"
+
+// mapnik-vector-tile
+#include "vector_tile_processor.hpp"
+#include "vector_tile_strategy.hpp"
+#include "vector_tile_geometry_decoder.hpp"
+
+// mapnik
+#include <mapnik/feature_factory.hpp>
+#include <mapnik/memory_datasource.hpp>
+
+// std
+#include <exception>
+
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+namespace test_utils
+{
+
+mapnik::geometry::geometry<double> round_trip(mapnik::geometry::geometry<double> const& geom,
+ double simplify_distance,
+ mapnik::vector_tile_impl::polygon_fill_type fill_type,
+ bool mpu)
+{
+ unsigned tile_size = 256 * 1000;
+ // Create map note its not 3857 -- round trip as 4326
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:4326");
+ // create layer
+ mapnik::layer lyr("layer",map.srs());
+ // create feature with geometry
+ mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
+ mapnik::geometry::geometry<double> g(geom);
+ feature->set_geometry(std::move(g));
+ mapnik::parameters params;
+ params["type"] = "memory";
+ std::shared_ptr<mapnik::memory_datasource> ds = std::make_shared<mapnik::memory_datasource>(params);
+ ds->push(feature);
+ lyr.set_datasource(ds);
+ map.add_layer(lyr);
+
+ // Build request
+ mapnik::box2d<double> bbox(-180,-90,180,90);
+
+ // Build processor and create tile
+ mapnik::vector_tile_impl::processor ren(map);
+ ren.set_simplify_distance(simplify_distance);
+ ren.set_fill_type(fill_type);
+ ren.set_multi_polygon_union(mpu);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(bbox, tile_size);
+
+ if (out_tile.get_layers().size() != 1)
+ {
+ std::stringstream s;
+ s << "expected 1 layer in `round_trip` found " << out_tile.get_layers().size();
+ throw std::runtime_error(s.str());
+ }
+
+ protozero::pbf_reader layer_reader;
+ out_tile.layer_reader(0, layer_reader);
+ if (!layer_reader.next(mapnik::vector_tile_impl::Layer_Encoding::FEATURES))
+ {
+ throw std::runtime_error("Expected at least one feature in layer");
+ }
+ protozero::pbf_reader feature_reader = layer_reader.get_message();
+ int32_t geometry_type = mapnik::vector_tile_impl::Geometry_Type::UNKNOWN;
+ std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+ while (feature_reader.next())
+ {
+ if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::GEOMETRY)
+ {
+ geom_itr = feature_reader.get_packed_uint32();
+ }
+ else if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::TYPE)
+ {
+ geometry_type = feature_reader.get_enum();
+ }
+ }
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, 0, 0, 1000, -1000);
+ return mapnik::vector_tile_impl::decode_geometry(geoms, geometry_type, 2);
+}
+
+} // end ns
diff --git a/test/utils/round_trip.hpp b/test/utils/round_trip.hpp
new file mode 100644
index 0000000..361116a
--- /dev/null
+++ b/test/utils/round_trip.hpp
@@ -0,0 +1,15 @@
+// mapnik-vector-tile
+#include "vector_tile_processor.hpp"
+
+// mapnik
+#include <mapnik/geometry.hpp>
+
+namespace test_utils
+{
+
+mapnik::geometry::geometry<double> round_trip(mapnik::geometry::geometry<double> const& geom,
+ double simplify_distance=0.0,
+ mapnik::vector_tile_impl::polygon_fill_type fill_type = mapnik::vector_tile_impl::non_zero_fill,
+ bool mpu = false);
+
+} // end ns
diff --git a/test/vector_tile.cpp b/test/vector_tile.cpp
index bb07901..1f3ee0d 100644
--- a/test/vector_tile.cpp
+++ b/test/vector_tile.cpp
@@ -2,1112 +2,54 @@
// test utils
#include "test_utils.hpp"
-#include <mapnik/memory_datasource.hpp>
-#include <mapnik/util/fs.hpp>
+
+// mapnik
#include <mapnik/agg_renderer.hpp>
+#include <mapnik/datasource_cache.hpp>
#include <mapnik/feature_factory.hpp>
-#include <mapnik/load_map.hpp>
+#include <mapnik/geometry.hpp>
+#include <mapnik/geometry_is_empty.hpp>
+#include <mapnik/geometry_reprojection.hpp>
#include <mapnik/image_util.hpp>
-#include <mapnik/vertex_adapters.hpp>
+#include <mapnik/load_map.hpp>
+#include <mapnik/memory_datasource.hpp>
#include <mapnik/projection.hpp>
#include <mapnik/proj_transform.hpp>
-#include <mapnik/geometry_is_empty.hpp>
+#include <mapnik/util/fs.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 <mapnik/vertex_adapters.hpp>
+// boost
#include <boost/optional/optional_io.hpp>
-// vector output api
-#include "vector_tile_compression.hpp"
+// mapnik-vector-tile
#include "vector_tile_processor.hpp"
-#include "vector_tile_strategy.hpp"
-#include "vector_tile_backend_pbf.hpp"
-#include "vector_tile_util.hpp"
-#include "vector_tile_projection.hpp"
#include "vector_tile_geometry_decoder.hpp"
+#include "vector_tile_datasource_pbf.hpp"
-// vector input api
-#include "vector_tile_datasource.hpp"
-#include "protozero/pbf_writer.hpp"
-
-/*
-TEST_CASE( "vector tile negative id", "hmm" ) {
- vector_tile::Tile tile;
- vector_tile::Tile_Layer * layer = tile.add_layers();
- vector_tile::Tile_Feature * feat = layer->add_features();
- feat->set_id(-1);
- std::clog << feat->id() << "\n";
- //CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon);
-}
-*/
-
-TEST_CASE( "vector tile compression", "should be able to round trip gzip and zlib data" ) {
- std::string data("amazing data");
- CHECK(!mapnik::vector_tile_impl::is_zlib_compressed(data));
- CHECK(!mapnik::vector_tile_impl::is_gzip_compressed(data));
- std::string zlibbed;
- mapnik::vector_tile_impl::zlib_compress(data,zlibbed,false);
- // failing - why?
- //CHECK(mapnik::vector_tile_impl::is_zlib_compressed(zlibbed));
- CHECK(!mapnik::vector_tile_impl::is_gzip_compressed(zlibbed));
-
- std::string unzlibbed;
- mapnik::vector_tile_impl::zlib_decompress(zlibbed,unzlibbed);
- CHECK(data == unzlibbed);
-
- std::string gzipped;
- mapnik::vector_tile_impl::zlib_compress(data,gzipped,true);
- CHECK(!mapnik::vector_tile_impl::is_zlib_compressed(gzipped));
- CHECK(mapnik::vector_tile_impl::is_gzip_compressed(gzipped));
-
- std::string ungzipped;
- mapnik::vector_tile_impl::zlib_decompress(gzipped,ungzipped);
- CHECK(data == ungzipped);
-}
-
-TEST_CASE( "vector tile output 1", "should create vector tile with two points" ) {
- typedef mapnik::vector_tile_impl::backend_pbf backend_type;
- typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
- 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,true));
- map.add_layer(lyr);
- mapnik::request m_req(tile_size,tile_size,bbox);
- renderer_type ren(backend,map,m_req);
- ren.apply();
- CHECK( ren.painted() == true );
- std::string key("");
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
- CHECK("" == key);
- REQUIRE(1 == tile.layers_size());
- vector_tile::Tile_Layer const& layer = tile.layers(0);
- CHECK(std::string("layer") == layer.name());
- REQUIRE(2 == 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()));
- REQUIRE(3 == f.geometry_size());
- CHECK(9 == f.geometry(0));
- CHECK(4096 == f.geometry(1));
- CHECK(4096 == f.geometry(2));
- CHECK(194 == tile.ByteSize());
- std::string buffer;
- CHECK(tile.SerializeToString(&buffer));
- CHECK(194 == buffer.size());
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
-}
-
-TEST_CASE( "vector tile output 2", "adding empty layers should result in empty tile" ) {
- 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");
- map.add_layer(mapnik::layer("layer",map.srs()));
- 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() == false );
- std::string key("");
- CHECK(true == mapnik::vector_tile_impl::is_solid_extent(tile,key));
- CHECK("" == key);
- CHECK(0 == tile.layers_size());
- std::string buffer;
- CHECK(tile.SerializeToString(&buffer));
- CHECK(true == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
-}
-
-TEST_CASE( "vector tile output 3", "adding layers with geometries outside rendering extent should not add layer" ) {
- 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());
- mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
- mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
- mapnik::geometry::line_string<double> g;
- g.add_coord(-10,-10);
- g.add_coord(-11,-11);
- feature->set_geometry(std::move(g));
- mapnik::parameters params;
- params["type"] = "memory";
- std::shared_ptr<mapnik::memory_datasource> ds = std::make_shared<mapnik::memory_datasource>(params);
- ds->push(feature);
- lyr.set_datasource(ds);
- map.add_layer(lyr);
- mapnik::box2d<double> custom_bbox(0,0,10,10);
- map.zoom_to_box(custom_bbox);
- mapnik::request m_req(tile_size,tile_size,custom_bbox);
- renderer_type ren(backend,map,m_req);
- ren.apply();
- std::string key("");
- CHECK(true == mapnik::vector_tile_impl::is_solid_extent(tile,key));
- CHECK("" == key);
- CHECK(0 == tile.layers_size());
- std::string buffer;
- CHECK(tile.SerializeToString(&buffer));
- CHECK(true == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
-}
-
-TEST_CASE( "vector tile output 4", "adding layers with degenerate geometries should not add layer" ) {
- 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());
- // create a datasource with a feature outside the map
- std::shared_ptr<mapnik::memory_datasource> ds = testing::build_ds(bbox.minx()-1,bbox.miny()-1);
- // 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(bbox);
- 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();
- std::string key("");
- CHECK(true == mapnik::vector_tile_impl::is_solid_extent(tile,key));
- CHECK("" == key);
- CHECK(0 == tile.layers_size());
- std::string buffer;
- CHECK(tile.SerializeToString(&buffer));
- CHECK(true == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
-}
-
-TEST_CASE( "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(false == mapnik::vector_tile_impl::is_solid_extent(buffer,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());
- std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds = std::make_shared<
- mapnik::vector_tile_impl::tile_datasource>(
- layer2,0,0,0,map2.width());
- ds->set_envelope(bbox);
- CHECK( ds->type() == mapnik::datasource::Vector );
- CHECK( ds->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
- mapnik::layer_descriptor lay_desc = ds->get_descriptor();
- std::vector<std::string> expected_names;
- expected_names.push_back("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( "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);
- std::string buffer;
- CHECK(tile.SerializeToString(&buffer));
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
- 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());
- 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));
- // now actually start the meat of the test
- mapnik::vector_tile_impl::tile_datasource ds(layer,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);
-}
-
-TEST_CASE( "backend does not crash on misusage", "adding feature before layer" ) {
- vector_tile::Tile tile;
- mapnik::vector_tile_impl::backend_pbf backend(tile,1);
- mapnik::feature_ptr feature(mapnik::feature_factory::create(std::make_shared<mapnik::context_type>(),1));
- backend.start_tile_feature(*feature);
-}
-
-TEST_CASE( "backend does not crash on misusage 2", "adding path before feature" ) {
- vector_tile::Tile tile;
- mapnik::vector_tile_impl::backend_pbf backend(tile,1);
- mapnik::geometry::point<std::int64_t> geom;
- CHECK(0 == backend.add_path(geom) );
-}
-
-// 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( "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);
- std::string buffer;
- CHECK(tile.SerializeToString(&buffer));
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
- REQUIRE(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;
-
- mapnik::vector_tile_impl::tile_datasource ds(layer,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> >());
-}
-
-
-TEST_CASE( "encoding single line 1", "should maintain start/end vertex" ) {
- // Options
- // here we use a multiplier of 1 to avoid rounding numbers
- // this works because we are staying in integer space for this test
- unsigned path_multiplier = 1;
- // here we use a tolerance of 2. Along with a multiplier of 1 this
- // says to discard any verticies that are not at least >= 2 different
- // in both the x and y from the previous vertex
- // unsigned tolerance = 2;
- // now create the testing data
- 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::LineString));
- 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->line_to(1000,1000); // repeated line_to, so only takes 2 geoms: x,y | total 8
- g->line_to(1001,1001); // should skip given tolerance of 2 | total 8
- g->line_to(1001,1001); // should not skip given it is the endpoint, added 2 geoms | total 10
- */
- mapnik::geometry::line_string<std::int64_t> geom;
- geom.add_coord(0,0);
- geom.add_coord(2,2);
- geom.add_coord(1000,1000);
- geom.add_coord(1001,1001);
- geom.add_coord(1001,1001);
- backend.current_feature_->set_type(vector_tile::Tile_GeomType_LINESTRING);
- backend.add_path(geom);
- 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);
- std::string buffer;
- CHECK(tile.SerializeToString(&buffer));
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
- REQUIRE(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);
- // sequence of 10 geometries given tolerance of 2
- CHECK(12 == f.geometry_size());
- // first geometry is 9, which packs both the command and how many verticies are encoded with that same command
- // It is 9 because it is a move_to (which is an enum of 1) and there is one command (length == 1)
- unsigned move_value = f.geometry(0);
- // (1 << 3) | (1 & ((1 << 3) -1)) == 9
- // (length << 3) | (MOVE_TO & ((1 << 3) -1))
- CHECK(9 == move_value);
- unsigned move_cmd = move_value & ((1 << 3) - 1);
- CHECK(1 == move_cmd);
- unsigned move_length = move_value >> 3;
- CHECK(1 == move_length);
- // 2nd and 3rd are the x,y of the one move_to command
- CHECK(0 == f.geometry(1));
- CHECK(0 == f.geometry(2));
- // 4th is the line_to (which is an enum of 2) and a number indicating how many line_to commands are repeated
- // in this case there should be 2 because two were skipped
- unsigned line_value = f.geometry(3);
- // (2 << 3) | (2 & ((1 << 3) -1)) == 18
- CHECK(34 == line_value);
- unsigned line_cmd = line_value & ((1 << 3) - 1);
- CHECK(2 == line_cmd);
- unsigned line_length = line_value >> 3;
- CHECK(4 == line_length);
- // 5th and 6th are the x,y of the first line_to command
- // due zigzag encoding the 2,2 should be 4,4
- // delta encoding has no impact since the previous coordinate was 0,0
- unsigned four = protozero::encode_zigzag32(2u);
- CHECK(4 == four);
- CHECK(4 == f.geometry(4));
- CHECK(4 == f.geometry(5));
- // 7th and 8th are x,y of the second line_to command
- // due to delta encoding 1001-2 becomes 999
- // zigzag encoded 999 becomes 1998 == (999 << 1) ^ (999 >> 31)
- CHECK(1996 == f.geometry(6));
- CHECK(1996 == f.geometry(7));
-}
-
-// testcase for avoiding error in mapnik::vector_tile_impl::is_solid_extent of
-// "Unknown command type (is_solid_extent): 0"
-// not yet clear if this test is correct
-// 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;
- 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);
- mapnik::geometry::polygon<double> geom;
- {
- mapnik::geometry::linear_ring<double> ring;
- ring.add_coord(168.267850,-24.576888);
- ring.add_coord(167.982618,-24.697145);
- ring.add_coord(168.114561,-24.783548);
- ring.add_coord(168.267850,-24.576888);
- ring.add_coord(168.267850,-24.576888);
- geom.set_exterior_ring(std::move(ring));
- }
- mapnik::geometry::scale_rounding_strategy scale_strat(backend.get_path_multiplier());
- mapnik::geometry::geometry<std::int64_t> geom2 = mapnik::geometry::transform<std::int64_t>(geom, scale_strat);
- std::string foo;
- mapnik::util::to_wkt(foo, geom2);
- INFO(foo);
- backend.current_feature_->set_type(vector_tile::Tile_GeomType_POLYGON);
- REQUIRE( geom2.is<mapnik::geometry::polygon<std::int64_t> >() );
- auto const& poly = mapnik::util::get<mapnik::geometry::polygon<std::int64_t>>(geom2);
- backend.add_path(poly);
- backend.stop_tile_feature();
- backend.stop_tile_layer();
- std::string key("");
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile,key));
- CHECK("" == key);
- std::string buffer;
- CHECK(tile.SerializeToString(&buffer));
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
- REQUIRE(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(11 == f.geometry_size());
-}
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
-mapnik::geometry::geometry<double> round_trip(mapnik::geometry::geometry<double> const& geom,
- double simplify_distance=0.0,
- mapnik::vector_tile_impl::polygon_fill_type fill_type = mapnik::vector_tile_impl::non_zero_fill,
- bool mpu = false)
+TEST_CASE("vector tile from simplified geojson")
{
- 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;
- unsigned path_multiplier = 1000;
- backend_type backend(tile,path_multiplier);
- unsigned tile_size = 256;
- mapnik::box2d<double> bbox(-180,-90,180,90);
- mapnik::Map map(tile_size,tile_size,"+init=epsg:4326");
- mapnik::request m_req(tile_size,tile_size,bbox);
- renderer_type ren(backend,map,m_req,1,0,0,0);
- // instead of calling apply, let's cheat and test `handle_geometry` directly by adding features
- backend.start_tile_layer("layer");
- mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
- mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
- mapnik::projection wgs84("+init=epsg:4326",true);
- mapnik::projection merc("+init=epsg:4326",true);
- mapnik::proj_transform prj_trans(merc,wgs84);
- ren.set_simplify_distance(simplify_distance);
- ren.set_fill_type(fill_type);
- ren.set_multi_polygon_union(mpu);
- mapnik::vector_tile_impl::vector_tile_strategy_proj vs2(prj_trans,ren.get_transform(),backend.get_path_multiplier());
- mapnik::vector_tile_impl::vector_tile_strategy vs(ren.get_transform(),backend.get_path_multiplier());
- mapnik::geometry::point<double> p1_min(bbox.minx(), bbox.miny());
- 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, bbox);
- backend.stop_tile_layer();
- if (tile.layers_size() != 1)
- {
- std::stringstream s;
- s << "expected 1 layer in `round_trip` found " << tile.layers_size();
- throw std::runtime_error(s.str());
- }
- vector_tile::Tile_Layer const& layer = tile.layers(0);
- if (layer.features_size() != 1)
- {
- std::stringstream s;
- s << "expected 1 feature in `round_trip` found " << layer.features_size();
- throw std::runtime_error(s.str());
- }
- vector_tile::Tile_Feature const& f = layer.features(0);
- double scale = (double)path_multiplier;
-
- mapnik::vector_tile_impl::Geometry<double> geoms(f,0,0,scale,-1*scale);
- return mapnik::vector_tile_impl::decode_geometry<double>(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> >() );
-}
-
-TEST_CASE( "vector tile multi_point encoding of single point", "should create vector tile with data" ) {
- mapnik::geometry::multi_point<double> geom;
- geom.emplace_back(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 multi_point encoding of actual multi_point", "should create vector tile with data" ) {
- mapnik::geometry::multi_point<double> geom;
- geom.emplace_back(0,0);
- geom.emplace_back(1,1);
- 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 == "MULTIPOINT(128 -128,128.711 -126.578)" );
- CHECK( new_geom.is<mapnik::geometry::multi_point<double> >() );
-}
-
-TEST_CASE( "vector tile line_string encoding", "should create vector tile with data" ) {
- mapnik::geometry::line_string<double> geom;
- geom.add_coord(0,0);
- geom.add_coord(100,100);
- 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 0)" );
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
-}
-
-TEST_CASE( "vector tile multi_line_string encoding of single line_string", "should create vector tile with data" ) {
- mapnik::geometry::multi_line_string<double> geom;
- mapnik::geometry::line_string<double> line;
- line.add_coord(0,0);
- line.add_coord(100,100);
- geom.emplace_back(std::move(line));
- 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 0)" );
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::line_string<double> >() );
-}
-
-TEST_CASE( "vector tile multi_line_string encoding of actual multi_line_string", "should create vector tile with data" ) {
- mapnik::geometry::multi_line_string<double> geom;
- mapnik::geometry::line_string<double> line;
- line.add_coord(0,0);
- line.add_coord(100,100);
- geom.emplace_back(std::move(line));
- mapnik::geometry::line_string<double> line2;
- line2.add_coord(-10,-0);
- line2.add_coord(-100,-100);
- geom.emplace_back(std::move(line2));
- 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 0),(120.889 -128,63.289 -256))" );
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::multi_line_string<double> >() );
-}
-
-
-TEST_CASE( "vector tile polygon encoding", "should create vector tile with data" ) {
- mapnik::geometry::polygon<double> geom;
- geom.exterior_ring.add_coord(0,0);
- geom.exterior_ring.add_coord(0,10);
- geom.exterior_ring.add_coord(-10,10);
- geom.exterior_ring.add_coord(-10,0);
- geom.exterior_ring.add_coord(0,0);
- mapnik::geometry::geometry<double> new_geom = round_trip(geom);
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778))" );
-}
-
-TEST_CASE( "vector tile multi_polygon encoding of single polygon", "should create vector tile with data" ) {
- mapnik::geometry::polygon<double> poly;
- poly.exterior_ring.add_coord(0,0);
- poly.exterior_ring.add_coord(0,10);
- poly.exterior_ring.add_coord(-10,10);
- poly.exterior_ring.add_coord(-10,0);
- poly.exterior_ring.add_coord(0,0);
- mapnik::geometry::multi_polygon<double> geom;
- geom.emplace_back(std::move(poly));
- mapnik::geometry::geometry<double> new_geom = round_trip(geom);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778))" );
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-TEST_CASE( "vector tile multi_polygon with multipolygon union", "should create vector tile with data" ) {
- mapnik::geometry::polygon<double> poly;
- poly.exterior_ring.add_coord(0,0);
- poly.exterior_ring.add_coord(0,10);
- poly.exterior_ring.add_coord(-10,10);
- poly.exterior_ring.add_coord(-10,0);
- poly.exterior_ring.add_coord(0,0);
- mapnik::geometry::polygon<double> poly2;
- poly2.exterior_ring.add_coord(0,0);
- poly2.exterior_ring.add_coord(0,10);
- poly2.exterior_ring.add_coord(-10,10);
- poly2.exterior_ring.add_coord(-10,0);
- poly2.exterior_ring.add_coord(0,0);
- mapnik::geometry::multi_polygon<double> geom;
- geom.emplace_back(std::move(poly));
- geom.emplace_back(std::move(poly2));
- mapnik::geometry::geometry<double> new_geom = round_trip(geom, 0, mapnik::vector_tile_impl::non_zero_fill, true);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778))" );
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-TEST_CASE( "vector tile multi_polygon with out multipolygon union", "should create vector tile with data" ) {
- mapnik::geometry::polygon<double> poly;
- poly.exterior_ring.add_coord(0,0);
- poly.exterior_ring.add_coord(0,10);
- poly.exterior_ring.add_coord(-10,10);
- poly.exterior_ring.add_coord(-10,0);
- poly.exterior_ring.add_coord(0,0);
- mapnik::geometry::polygon<double> poly2;
- poly2.exterior_ring.add_coord(0,0);
- poly2.exterior_ring.add_coord(0,10);
- poly2.exterior_ring.add_coord(-10,10);
- poly2.exterior_ring.add_coord(-10,0);
- poly2.exterior_ring.add_coord(0,0);
- mapnik::geometry::multi_polygon<double> geom;
- geom.emplace_back(std::move(poly));
- geom.emplace_back(std::move(poly2));
- mapnik::geometry::geometry<double> new_geom = round_trip(geom, 0, mapnik::vector_tile_impl::non_zero_fill, false);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "MULTIPOLYGON(((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778)),((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778)))" );
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
-}
-
-TEST_CASE( "vector tile multi_polygon encoding of actual multi_polygon", "should create vector tile with data a multi polygon" ) {
- mapnik::geometry::multi_polygon<double> geom;
- mapnik::geometry::polygon<double> poly;
- poly.exterior_ring.add_coord(0,0);
- poly.exterior_ring.add_coord(0,10);
- poly.exterior_ring.add_coord(-10,10);
- poly.exterior_ring.add_coord(-10,0);
- poly.exterior_ring.add_coord(0,0);
- /*
- // This is an interior ring that touches nothing.
- poly.interior_rings.emplace_back();
- poly.interior_rings.back().add_coord(-1,1);
- poly.interior_rings.back().add_coord(-1,2);
- poly.interior_rings.back().add_coord(-2,2);
- poly.interior_rings.back().add_coord(-2,1);
- poly.interior_rings.back().add_coord(-1,1);
- // This is an interior ring that touches exterior edge.
- poly.interior_rings.emplace_back();
- poly.interior_rings.back().add_coord(-10,7);
- poly.interior_rings.back().add_coord(-10,5);
- poly.interior_rings.back().add_coord(-8,5);
- poly.interior_rings.back().add_coord(-8,7);
- poly.interior_rings.back().add_coord(-10,7);
- */
- geom.emplace_back(std::move(poly));
- mapnik::geometry::polygon<double> poly2;
- poly2.exterior_ring.add_coord(11,11);
- poly2.exterior_ring.add_coord(11,21);
- poly2.exterior_ring.add_coord(1,21);
- poly2.exterior_ring.add_coord(1,11);
- poly2.exterior_ring.add_coord(11,11);
- geom.emplace_back(std::move(poly2));
- mapnik::geometry::geometry<double> new_geom = round_trip(geom);
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
-}
-
-TEST_CASE( "vector tile multi_polygon encoding overlapping multipolygons", "should create vector tile with data a multi polygon" ) {
- mapnik::geometry::multi_polygon<double> geom;
- mapnik::geometry::polygon<double> poly;
- poly.exterior_ring.add_coord(0,0);
- poly.exterior_ring.add_coord(0,10);
- poly.exterior_ring.add_coord(-10,10);
- poly.exterior_ring.add_coord(-10,0);
- poly.exterior_ring.add_coord(0,0);
- /*
- // This is an interior ring that touches nothing.
- poly.interior_rings.emplace_back();
- poly.interior_rings.back().add_coord(-1,1);
- poly.interior_rings.back().add_coord(-1,2);
- poly.interior_rings.back().add_coord(-2,2);
- poly.interior_rings.back().add_coord(-2,1);
- poly.interior_rings.back().add_coord(-1,1);
- // This is an interior ring that touches exterior edge.
- poly.interior_rings.emplace_back();
- poly.interior_rings.back().add_coord(-10,7);
- poly.interior_rings.back().add_coord(-10,5);
- poly.interior_rings.back().add_coord(-8,5);
- poly.interior_rings.back().add_coord(-8,7);
- poly.interior_rings.back().add_coord(-10,7);
- */
- geom.emplace_back(std::move(poly));
- mapnik::geometry::polygon<double> poly2;
- poly2.exterior_ring.add_coord(-5,5);
- poly2.exterior_ring.add_coord(-5,15);
- poly2.exterior_ring.add_coord(-15,15);
- poly2.exterior_ring.add_coord(-15,5);
- poly2.exterior_ring.add_coord(-5,5);
- geom.emplace_back(std::move(poly2));
- mapnik::geometry::geometry<double> new_geom = round_trip(geom);
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::multi_polygon<double> >() );
-}
-
-
-// simplification
-
-TEST_CASE( "vector tile point correctly passed through simplification code path", "should create vector tile with data" ) {
- mapnik::geometry::point<double> geom(-122,48);
- mapnik::geometry::geometry<double> new_geom = round_trip(geom,500);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "POINT(41.244 -59.733)" );
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::point<double> >() );
-}
-
-TEST_CASE( "vector tile mulit_point correctly passed through simplification code path", "should create vector tile with data" ) {
- mapnik::geometry::multi_point<double> geom;
- geom.emplace_back(-122,48);
- geom.emplace_back(-123,49);
- mapnik::geometry::geometry<double> new_geom = round_trip(geom,500);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "MULTIPOINT(41.244 -59.733,40.533 -58.311)" );
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::multi_point<double> >() );
-}
-
-TEST_CASE( "vector tile line_string is simplified", "should create vector tile with data" ) {
- mapnik::geometry::line_string<double> line;
- line.add_coord(0,0);
- line.add_coord(1,1);
- line.add_coord(2,2);
- line.add_coord(100,100);
- 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 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);
- CHECK( line2.size() == 2 );
-}
-
-TEST_CASE( "vector tile multi_line_string is simplified", "should create vector tile with data" ) {
- mapnik::geometry::multi_line_string<double> geom;
- mapnik::geometry::line_string<double> line;
- line.add_coord(0,0);
- line.add_coord(1,1);
- line.add_coord(2,2);
- line.add_coord(100,100);
- geom.emplace_back(std::move(line));
- 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 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);
- CHECK( line2.size() == 2 );
-}
-
-TEST_CASE( "vector tile polygon even odd fill", "should create vector tile with data" ) {
- using namespace mapnik::geometry;
- polygon<double> poly;
- {
- linear_ring<double> ring;
- ring.add_coord(0,0);
- ring.add_coord(-10,0);
- ring.add_coord(-10,10);
- ring.add_coord(0,10);
- ring.add_coord(0,0);
- poly.set_exterior_ring(std::move(ring));
- linear_ring<double> hole;
- hole.add_coord(-7,7);
- hole.add_coord(-7,3);
- hole.add_coord(-3,3);
- hole.add_coord(-3,7);
- hole.add_coord(-7,7);
- poly.add_hole(std::move(hole));
- }
- mapnik::geometry::geometry<double> new_geom = round_trip(poly,0,mapnik::vector_tile_impl::even_odd_fill);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044,125.867 -123.733))");
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-TEST_CASE( "vector tile polygon non zero fill", "should create vector tile with data" ) {
- using namespace mapnik::geometry;
- polygon<double> poly;
- {
- linear_ring<double> ring;
- ring.add_coord(0,0);
- ring.add_coord(-10,0);
- ring.add_coord(-10,10);
- ring.add_coord(0,10);
- ring.add_coord(0,0);
- poly.set_exterior_ring(std::move(ring));
- linear_ring<double> hole;
- hole.add_coord(-7,7);
- hole.add_coord(-7,3);
- hole.add_coord(-3,3);
- hole.add_coord(-3,7);
- hole.add_coord(-7,7);
- poly.add_hole(std::move(hole));
- }
- mapnik::geometry::geometry<double> new_geom = round_trip(poly,0,mapnik::vector_tile_impl::non_zero_fill);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044,125.867 -123.733))");
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-TEST_CASE( "vector tile polygon is simplified", "should create vector tile with data" ) {
- using namespace mapnik::geometry;
- polygon<double> poly;
- {
- linear_ring<double> ring;
- ring.add_coord(0,0);
- ring.add_coord(-10,0);
- ring.add_coord(-10,10);
- ring.add_coord(0,10);
- ring.add_coord(0,0);
- poly.set_exterior_ring(std::move(ring));
- linear_ring<double> hole;
- hole.add_coord(-7,7);
- hole.add_coord(-7,3);
- hole.add_coord(-3,3);
- hole.add_coord(-3,7);
- hole.add_coord(-7,7);
- poly.add_hole(std::move(hole));
- }
- mapnik::geometry::geometry<double> new_geom = round_trip(poly,500);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044,125.867 -123.733))");
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-TEST_CASE( "vector tile mulit_polygon is simplified", "should create vector tile with data" ) {
- using namespace mapnik::geometry;
- polygon<double> poly;
- {
- linear_ring<double> ring;
- ring.add_coord(0,0);
- ring.add_coord(-10,0);
- ring.add_coord(-10,10);
- ring.add_coord(0,10);
- ring.add_coord(0,0);
- poly.set_exterior_ring(std::move(ring));
- linear_ring<double> hole;
- hole.add_coord(-7,7);
- hole.add_coord(-7,3);
- hole.add_coord(-3,3);
- hole.add_coord(-3,7);
- hole.add_coord(-7,7);
- poly.add_hole(std::move(hole));
- }
- multi_polygon<double> mp;
- mp.push_back(poly);
- mapnik::geometry::geometry<double> new_geom = round_trip(mp,500);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- CHECK( wkt == "POLYGON((120.889 -113.778,120.889 -128,128 -128,128 -113.778,120.889 -113.778),(125.867 -123.733,123.022 -123.733,123.022 -118.044,125.867 -118.044,125.867 -123.733))" );
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- REQUIRE( new_geom.is<mapnik::geometry::polygon<double> >() );
-}
-
-TEST_CASE( "vector tile line_string is simplified when outside bounds", "should create vector tile with data" ) {
- mapnik::geometry::multi_line_string<double> geom;
- mapnik::geometry::line_string<double> line;
- line.add_coord(-10000,0);
- line.add_coord(-10000.1,0);
- line.add_coord(100000,0);
- geom.emplace_back(std::move(line));
- mapnik::geometry::geometry<double> new_geom = round_trip(geom,100);
- std::string wkt;
- CHECK( mapnik::util::to_wkt(wkt, new_geom) );
- // yep this test is weird - more of a fuzz than anything
- CHECK( wkt == "LINESTRING(0 -128,256 -128)" );
- 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);
- CHECK( line2.size() == 2 );
-}
-
-TEST_CASE( "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");
+ unsigned tile_size = 256 * 1000;
+ mapnik::Map map(256,256,"+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 );
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
@@ -1119,9 +61,26 @@ TEST_CASE( "vector tile from simplified geojson", "should create vector tile wit
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;
- mapnik::vector_tile_impl::Geometry<double> geoms(f,tile_x, tile_y,scale,-1*scale);
- auto geom = mapnik::vector_tile_impl::decode_geometry<double>(geoms,f.type());
+ double scale = static_cast<double>(layer.extent())/resolution;
+ protozero::pbf_reader layer_reader;
+ out_tile.layer_reader(0, layer_reader);
+ REQUIRE(layer_reader.next(mapnik::vector_tile_impl::Layer_Encoding::FEATURES));
+ protozero::pbf_reader feature_reader = layer_reader.get_message();
+ int32_t geometry_type = mapnik::vector_tile_impl::Geometry_Type::UNKNOWN;
+ std::pair<protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator> geom_itr;
+ while (feature_reader.next())
+ {
+ if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::GEOMETRY)
+ {
+ geom_itr = feature_reader.get_packed_uint32();
+ }
+ else if (feature_reader.tag() == mapnik::vector_tile_impl::Feature_Encoding::TYPE)
+ {
+ geometry_type = feature_reader.get_enum();
+ }
+ }
+ mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x, tile_y, scale, -1*scale);
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, geometry_type, 2);
unsigned int n_err = 0;
mapnik::projection wgs84("+init=epsg:4326",true);
@@ -1131,107 +90,14 @@ 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\":\"Polygon\",\"coordinates\":[[[160.40671875,11.3976701817587],[160.396875,11.3935345987524],[160.39828125,11.4018057045896],[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.29421875,11.3507967223837],[160.2928125,11.3480392200086],[160.28859375,11.34 [...]
+ //std::clog << geojson_string << std::endl;
+ CHECK( geojson_string == "{\"type\":\"Polygon\",\"coordinates\":[[[160.36171875,11.2997786224589],[160.3828125,11.3011576095711],[160.408125,11.3039155638972],[160.408125,11.2997786224589],[160.425,11.3094313929343],[160.41234375,11.3411453475587],[160.3996875,11.3301148056307],[160.40953125,11.3700984927314],[160.39265625,11.3618264654176],[160.396875,11.3797488877286],[160.4053125,11.3893989555911],[160.40953125,11.3866418267411],[160.419375,11.4004272036667],[160.41515625,11.40594 [...]
}
-mapnik::geometry::geometry<double> round_trip2(mapnik::geometry::geometry<double> const& geom,
- double simplify_distance=0.0)
+TEST_CASE("vector tile transform -- should not throw on coords outside merc range")
{
- 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,160);
- unsigned tile_size = 256;
- mapnik::projection wgs84("+init=epsg:4326",true);
- mapnik::projection merc("+init=epsg:3857",true);
- mapnik::proj_transform prj_trans(merc,wgs84);
- mapnik::box2d<double> bbox(0,0,11.25,11.178401873711785);
- prj_trans.backward(bbox);
- mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
- mapnik::request m_req(tile_size,tile_size,bbox);
- renderer_type ren(backend,map,m_req,1,0,0,0);
- // instead of calling apply, let's cheat and test `handle_geometry` directly by adding features
- backend.start_tile_layer("layer");
- mapnik::context_ptr ctx = std::make_shared<mapnik::context_type>();
- mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1));
- ren.set_simplify_distance(simplify_distance);
- double simp2 = ren.get_simplify_distance();
- if (simp2 != simplify_distance)
- {
- throw std::runtime_error("simplify_distance setter did not work");
- }
- 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, bbox);
- backend.stop_tile_layer();
- if (tile.layers_size() != 1)
- {
- throw std::runtime_error("expected 1 layer in `round_trip`");
- }
- vector_tile::Tile_Layer const& layer = tile.layers(0);
- if (layer.features_size() != 1)
- {
- throw std::runtime_error("expected 1 feature in `round_trip`");
- }
- vector_tile::Tile_Feature const& f = layer.features(0);
- unsigned z = 5;
- unsigned x = 16;
- unsigned y = 15;
- 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;
- mapnik::vector_tile_impl::Geometry<double> geoms(f,tile_x, tile_y,scale,-1*scale);
- return mapnik::vector_tile_impl::decode_geometry<double>(geoms,f.type());
-}
-
-TEST_CASE( "vector tile line_string is verify direction", "should line string with proper directions" ) {
- mapnik::geometry::line_string<double> line;
- line.add_coord(-20,2);
- line.add_coord(2,2);
- line.add_coord(2,-20);
- line.add_coord(8,-20);
- line.add_coord(8,2);
- line.add_coord(60,2);
- line.add_coord(60,8);
- line.add_coord(8,8);
- line.add_coord(8,60);
- line.add_coord(2,60);
- line.add_coord(2,8);
- line.add_coord(-20,8);
-
- mapnik::geometry::geometry<double> new_geom = round_trip2(line);
- CHECK( !mapnik::geometry::is_empty(new_geom) );
- CHECK( new_geom.is<mapnik::geometry::multi_line_string<double> >() );
- mapnik::projection wgs84("+init=epsg:4326",true);
- mapnik::projection merc("+init=epsg:3857",true);
- mapnik::proj_transform prj_trans(merc,wgs84);
- mapnik::proj_strategy proj_strat(prj_trans);
- 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,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() == 4 );
-}
-
-TEST_CASE( "vector tile transform", "should not throw on coords outside merc range" ) {
- 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");
+ unsigned tile_size = 256 * 64;
+ mapnik::Map map(256,256,"+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","+init=epsg:4269");
@@ -1242,35 +108,37 @@ TEST_CASE( "vector tile transform", "should not throw on coords outside merc ran
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/issues/116
- ren.apply();
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
// serialize to message
std::string buffer;
CHECK(tile.SerializeToString(&buffer));
CHECK(70 == buffer.size());
// now create new objects
- mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
- tile_type tile2;
+ mapnik::Map map2(256,256,"+init=epsg:3857");
+ vector_tile::Tile tile2;
CHECK(tile2.ParseFromString(buffer));
- std::string key("");
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
- CHECK("" == key);
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,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());
+ CHECK(1 == layer2.features(0).id());
+ CHECK(3 == layer2.features(0).type());
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);
+
+ protozero::pbf_reader layer_reader;
+ out_tile.layer_reader(0, layer_reader);
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_reader,0,0,0);
+ //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();
@@ -1287,29 +155,27 @@ TEST_CASE( "vector tile transform", "should not throw on coords outside merc ran
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);
+ map2.zoom_all();
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-1.png")) {
+ if (!mapnik::util::exists("test/fixtures/transform-expected-1.png"))
+ {
mapnik::save_to_file(im,"test/fixtures/transform-expected-1.png","png32");
}
unsigned diff = testing::compare_images(im,"test/fixtures/transform-expected-1.png");
CHECK(0 == diff);
- if (diff > 0) {
+ if (diff > 0)
+ {
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;
+TEST_CASE("vector tile transform2 -- should not throw reprojected data from local NZ projection")
+{
+ unsigned tile_size = 256 * 64;
mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
- mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ mapnik::Map map(256,256,"+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");
@@ -1320,34 +186,34 @@ TEST_CASE( "vector tile transform2", "should not throw reprojected data from loc
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();
+
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
// 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;
+ mapnik::Map map2(256,256,"+init=epsg:3857");
+ vector_tile::Tile tile2;
CHECK(tile2.ParseFromString(buffer));
- std::string key("");
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
- CHECK("" == key);
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,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());
+ protozero::pbf_reader layer_reader;
+ out_tile.layer_reader(0, layer_reader);
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_reader,0,0,0);
ds2->set_envelope(bbox);
CHECK( ds2->type() == mapnik::datasource::Vector );
CHECK( ds2->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
diff --git a/test/vector_tile_pbf.cpp b/test/vector_tile_pbf.cpp
index 6db96c2..6150bad 100644
--- a/test/vector_tile_pbf.cpp
+++ b/test/vector_tile_pbf.cpp
@@ -2,6 +2,8 @@
// test utils
#include "test_utils.hpp"
+
+// mapnik
#include <mapnik/memory_datasource.hpp>
#include <mapnik/util/fs.hpp>
#include <mapnik/agg_renderer.hpp>
@@ -13,7 +15,6 @@
#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>
@@ -21,55 +22,53 @@
#include <mapnik/geometry.hpp>
#include <mapnik/datasource_cache.hpp>
+// boost
#include <boost/optional/optional_io.hpp>
-// vector output api
+// mapnik-vector-tile
#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_geometry_encoder_pbf.hpp"
#include "vector_tile_datasource_pbf.hpp"
+//protozero
#include "protozero/pbf_reader.hpp"
+//std
#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");
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+TEST_CASE("pbf vector tile input")
+{
+ unsigned tile_size = 4096;
+ mapnik::Map map(256,256,"+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();
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
// serialize to message
std::string buffer;
CHECK(tile.SerializeToString(&buffer));
- CHECK(151 == buffer.size());
+ CHECK(147 == buffer.size());
// now create new objects
- mapnik::Map map2(tile_size,tile_size,"+init=epsg:3857");
- tile_type tile2;
+ mapnik::Map map2(256,256,"+init=epsg:3857");
+ vector_tile::Tile tile2;
CHECK(tile2.ParseFromString(buffer));
- std::string key("");
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
- CHECK("" == key);
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
CHECK(1 == tile2.layers_size());
vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
CHECK(std::string("layer") == layer2.name());
@@ -83,69 +82,70 @@ TEST_CASE( "pbf vector tile input", "should be able to parse message and render
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());
+ layer3,0,0,0);
CHECK(ds->get_name() == "layer");
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
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;
+ std::set<std::string> expected_names;
+ expected_names.insert("bool");
+ expected_names.insert("boolf");
+ expected_names.insert("double");
+ expected_names.insert("float");
+ expected_names.insert("int");
+ expected_names.insert("name");
+ expected_names.insert("uint");
+ std::size_t desc_count = 0;
for (auto const& desc : lay_desc.get_descriptors())
{
- names.push_back(desc.get_name());
+ ++desc_count;
+ CHECK(expected_names.count(desc.get_name()) == 1);
}
-
- CHECK(names == expected_names);
+ CHECK(desc_count == expected_names.size());
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);
+ map2.zoom_all();
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")) {
+ 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) {
+ 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);
+TEST_CASE("pbf vector tile datasource")
+{
+ unsigned tile_size = 4096;
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);
+
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+
+ // serialize to message
std::string buffer;
- tile.SerializeToString(&buffer);
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
+ out_tile.serialize_to_string(buffer);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+
+ // check that vector tile contains proper information
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
+
REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
@@ -162,10 +162,11 @@ TEST_CASE( "pbf vector tile datasource", "should filter features outside extent"
protozero::pbf_reader 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::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
mapnik::featureset_ptr fs;
// ensure we can query single feature
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
fs = ds.features(mapnik::query(bbox));
mapnik::feature_ptr feat = fs->next();
CHECK(feat != mapnik::feature_ptr());
@@ -177,7 +178,7 @@ TEST_CASE( "pbf vector tile datasource", "should filter features outside extent"
feat = fs->next();
CHECK(feat != mapnik::feature_ptr());
CHECK(feat->size() == 1);
-// CHECK(feat->get("name") == "null island");
+ CHECK(feat->get("name") == mapnik::value_unicode_string("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)));
@@ -210,61 +211,37 @@ TEST_CASE( "pbf vector tile datasource", "should filter features outside extent"
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);
+TEST_CASE("pbf encoding multi line")
+{
mapnik::geometry::multi_line_string<std::int64_t> geom;
{
- mapnik::geometry::linear_ring<std::int64_t> ring;
+ mapnik::geometry::line_string<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;
+ mapnik::geometry::line_string<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);
+ vector_tile::Tile tile;
+ vector_tile::Tile_Layer * t_layer = tile.add_layers();
+ t_layer->set_name("layer");
+ t_layer->set_version(2);
+ t_layer->set_extent(4096);
+ vector_tile::Tile_Feature * t_feature = t_layer->add_features();
+ std::int32_t x = 0;
+ std::int32_t y = 0;
+
+ std::string feature_str;
+ protozero::pbf_writer feature_writer(feature_str);
+ CHECK(mapnik::vector_tile_impl::encode_geometry_pbf(geom, feature_writer, x, y));
+ t_feature->ParseFromString(feature_str);
+
std::string buffer;
tile.SerializeToString(&buffer);
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
REQUIRE(1 == layer.features_size());
@@ -290,8 +267,9 @@ TEST_CASE( "pbf encoding multi line as one path", "should maintain second move_t
pbf_tile.next();
protozero::pbf_reader layer2 = pbf_tile.get_message();
-
- mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+ unsigned tile_size = 4096;
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
fs = ds.features(mapnik::query(bbox));
f_ptr = fs->next();
CHECK(f_ptr != mapnik::feature_ptr());
@@ -319,35 +297,32 @@ TEST_CASE( "pbf decoding empty buffer", "should throw exception" ) {
TEST_CASE( "pbf decoding garbage buffer", "should throw exception" ) {
std::string buffer("daufyglwi3h7fseuhfas8w3h,dksufasdf");
protozero::pbf_reader pbf_tile(buffer);
- pbf_tile.next();
+ REQUIRE_THROWS_AS(pbf_tile.next(), protozero::unknown_pbf_wire_type_exception);
protozero::pbf_reader 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;
+TEST_CASE("pbf decoding some truncated buffers")
+{
+ unsigned tile_size = 4096;
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);
- std::string buffer;
- tile.SerializeToString(&buffer);
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer,key));
- CHECK("" == key);
+
+ // Create processor
+ mapnik::vector_tile_impl::processor ren(map);
+
+ // Request Tile
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+
+ // Now check that the tile is correct.
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
@@ -359,47 +334,48 @@ TEST_CASE( "pbf decoding some truncated buffers", "should throw exception" ) {
CHECK(4096 == f.geometry(1));
CHECK(4096 == f.geometry(2));
-
+ std::string buffer;
+ out_tile.serialize_to_string(buffer);
// 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.
for (std::size_t i=1; i< buffer.size(); ++i)
{
- CHECK_THROWS({
+ CHECK_THROWS(
+ {
protozero::pbf_reader pbf_tile(buffer.c_str(), i);
pbf_tile.next();
protozero::pbf_reader layer2 = pbf_tile.get_message();
- mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
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()) {
+ 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);
+TEST_CASE("pbf vector tile from simplified geojson")
+{
+ unsigned tile_size = 256 * 1000;
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 );
+ mapnik::vector_tile_impl::processor ren(map);
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0,tile_size);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
@@ -420,14 +396,16 @@ TEST_CASE( "pbf vector tile from simplified geojson", "should create vector tile
protozero::pbf_reader 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(2)) {
+ while (!found && pbf_layer.next(2))
+ {
// But there will be only one in our tile, so we'll break out of loop once we find it
protozero::pbf_reader pbf_feature = pbf_layer.get_message();
- while (!found && pbf_feature.next(4)) {
+ while (!found && pbf_feature.next(4))
+ {
found = true;
std::pair< protozero::pbf_reader::const_uint32_iterator, protozero::pbf_reader::const_uint32_iterator > geom_itr = pbf_feature.get_packed_uint32();
mapnik::vector_tile_impl::GeometryPBF<double> geoms(geom_itr, tile_x,tile_y,scale,-1*scale);
- auto geom = mapnik::vector_tile_impl::decode_geometry<double>(geoms, f.type());
+ auto geom = mapnik::vector_tile_impl::decode_geometry(geoms, f.type(),2);
unsigned int n_err = 0;
mapnik::projection wgs84("+init=epsg:4326",true);
mapnik::projection merc("+init=epsg:3857",true);
@@ -436,45 +414,48 @@ TEST_CASE( "pbf vector tile from simplified geojson", "should create vector tile
CHECK( n_err == 0 );
std::string geojson_string;
CHECK( mapnik::util::to_geojson(geojson_string,projected_geom) );
- CHECK( geojson_string == "{\"type\":\"Polygon\",\"coordinates\":[[[160.40671875,11.3976701817587],[160.396875,11.3935345987524],[160.39828125,11.4018057045896],[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.29421875,11.3507967223837],[160.2928125,11.3480392200086],[160.28859375 [...]
+ //std::clog << geojson_string << std::endl;
+ CHECK( geojson_string == "{\"type\":\"Polygon\",\"coordinates\":[[[160.36171875,11.2997786224589],[160.3828125,11.3011576095711],[160.408125,11.3039155638972],[160.408125,11.2997786224589],[160.425,11.3094313929343],[160.41234375,11.3411453475587],[160.3996875,11.3301148056307],[160.40953125,11.3700984927314],[160.39265625,11.3618264654176],[160.396875,11.3797488877286],[160.4053125,11.3893989555911],[160.40953125,11.3866418267411],[160.419375,11.4004272036667],[160.41515625,11 [...]
break;
}
}
REQUIRE( found );
}
-
-TEST_CASE( "pbf raster tile output", "should be able to overzoom raster" ) {
- typedef vector_tile::Tile tile_type;
- tile_type tile;
+TEST_CASE("pbf raster tile output -- should be able to overzoom raster")
+{
+ unsigned tile_size = 4096;
+ int buffer_size = 1024;
+ mapnik::vector_tile_impl::merc_tile out_tile(0, 0, 0, tile_size, buffer_size);
{
- 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";
+ mapnik::box2d<double> const& bbox = out_tile.extent();
std::ostringstream s;
s << std::fixed << std::setprecision(16)
<< bbox.minx() << ',' << bbox.miny() << ','
<< bbox.maxx() << ',' << bbox.maxy();
+
+ // build map
+ mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
+ map.set_buffer_size(buffer_size);
+ mapnik::layer lyr("layer",map.srs());
+ mapnik::parameters params;
+ params["type"] = "gdal";
params["extent"] = s.str();
params["file"] = "test/data/256x256.png";
- std::shared_ptr<mapnik::datasource> ds =
- mapnik::datasource_cache::instance().create(params);
+ 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,false,"jpeg",mapnik::SCALING_BILINEAR);
- ren.apply();
+
+ // build processor
+ mapnik::vector_tile_impl::processor ren(map);
+ ren.set_image_format("jpeg");
+ ren.set_scaling_method(mapnik::SCALING_BILINEAR);
+
+ // Update the tile
+ ren.update_tile(out_tile);
}
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
// Done creating test data, now test created tile
REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
@@ -507,7 +488,7 @@ TEST_CASE( "pbf raster tile output", "should be able to overzoom raster" ) {
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);
+ layer2,0,0,0);
lyr2.set_datasource(ds2);
lyr2.add_style("style");
map2.add_layer(lyr2);
@@ -516,36 +497,38 @@ TEST_CASE( "pbf raster tile output", "should be able to overzoom raster" ) {
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")) {
+ 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) {
+ 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")
+TEST_CASE("Check that we throw on various valid-but-we-don't-handle PBF encoded files")
{
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) {
-
+ 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;
+ unsigned tile_size = 4096;
protozero::pbf_reader pbf_tile(buffer);
pbf_tile.next();
protozero::pbf_reader layer2 = pbf_tile.get_message();
- mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0,tile_size);
+ mapnik::vector_tile_impl::tile_datasource_pbf ds(layer2,0,0,0);
mapnik::featureset_ptr fs;
mapnik::feature_ptr f_ptr;
fs = ds.features(mapnik::query(bbox));
@@ -558,24 +541,23 @@ TEST_CASE("Check that we throw on various valid-but-we-don't-handle PBF encoded
}
-TEST_CASE( "pbf vector tile from linestring 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);
+TEST_CASE("pbf vector tile from linestring geojson")
+{
+ unsigned tile_size = 4096;
mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
mapnik::layer lyr("layer","+init=epsg:4326");
auto ds = testing::build_geojson_fs_ds("./test/data/linestrings_and_point.geojson");
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 );
+ mapnik::vector_tile_impl::processor ren(map);
+ // Request Tile
+ mapnik::vector_tile_impl::tile out_tile = ren.create_tile(0,0,0);
+ CHECK(out_tile.is_painted() == true);
+ CHECK(out_tile.is_empty() == false);
+
+ // Now check that the tile is correct.
+ vector_tile::Tile tile;
+ tile.ParseFromString(out_tile.get_buffer());
REQUIRE(1 == tile.layers_size());
vector_tile::Tile_Layer const& layer = tile.layers(0);
CHECK(std::string("layer") == layer.name());
@@ -587,8 +569,10 @@ TEST_CASE( "pbf vector tile from linestring geojson", "should create vector tile
protozero::pbf_reader layer3 = pbf_tile.get_message();
std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds2 = std::make_shared<
mapnik::vector_tile_impl::tile_datasource_pbf>(
- layer3,0,0,0,256);
+ layer3,0,0,0);
CHECK(ds2->get_name() == "layer");
+
+ mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
mapnik::query q(bbox);
// note: https://github.com/mapbox/mapnik-vector-tile/issues/132 does not occur
// if we uncomment these lines
@@ -616,5 +600,4 @@ TEST_CASE( "pbf vector tile from linestring geojson", "should create vector tile
// only three features
f_ptr = fs->next();
CHECK(f_ptr == mapnik::feature_ptr());
-
}
diff --git a/test/vector_tile_rasterize.cpp b/test/vector_tile_rasterize.cpp
index 6954017..61dfcfb 100644
--- a/test/vector_tile_rasterize.cpp
+++ b/test/vector_tile_rasterize.cpp
@@ -13,21 +13,28 @@
#include "vector_tile_compression.hpp"
#include "vector_tile_processor.hpp"
#include "vector_tile_strategy.hpp"
-#include "vector_tile_backend_pbf.hpp"
-#include "vector_tile_util.hpp"
#include "vector_tile_projection.hpp"
#include "vector_tile_geometry_decoder.hpp"
-#include "vector_tile_datasource.hpp"
#include "vector_tile_datasource_pbf.hpp"
#include "protozero/pbf_reader.hpp"
+// boost
#include <boost/optional/optional_io.hpp>
+// std
#include <fstream>
-TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
+// libprotobuf
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#include "vector_tile.pb.h"
+#pragma GCC diagnostic pop
+
+TEST_CASE("vector tile rasterize -- should try to decode windfail tile")
+{
// open vtile
- std::ifstream stream("./test/data/0.0.0.vector.pbf",std::ios_base::in|std::ios_base::binary);
+ std::ifstream stream("./test/data/0.0.0.vector.mvt",std::ios_base::in|std::ios_base::binary);
REQUIRE(stream.is_open());
std::string buffer(std::istreambuf_iterator<char>(stream.rdbuf()),(std::istreambuf_iterator<char>()));
REQUIRE(buffer.size() == 3812);
@@ -37,31 +44,27 @@ TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
mapnik::vector_tile_impl::zlib_decompress(buffer,uncompressed);
REQUIRE(uncompressed.size() == 4934);
- typedef vector_tile::Tile tile_type;
- tile_type tile;
- unsigned tile_size = 256;
mapnik::box2d<double> bbox(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789);
// first we render the raw tile directly to an image
{
- mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
- tile_type tile2;
+ mapnik::Map map(256,256,"+init=epsg:3857");
+ vector_tile::Tile tile2;
CHECK(tile2.ParseFromString(uncompressed));
- std::string key("");
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
- CHECK("" == key);
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(uncompressed,key));
- CHECK("" == key);
CHECK(1 == tile2.layers_size());
vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
CHECK(std::string("water") == layer2.name());
+ CHECK(1 == layer2.version());
CHECK(23 == layer2.features_size());
+ protozero::pbf_reader tile_reader(uncompressed);
+ REQUIRE(tile_reader.next(mapnik::vector_tile_impl::Tile_Encoding::LAYERS));
+ protozero::pbf_reader layer_reader = tile_reader.get_message();
mapnik::layer lyr2("water",map.srs());
- std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds = std::make_shared<
- mapnik::vector_tile_impl::tile_datasource>(
- layer2,0,0,0,map.width());
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_reader,0,0,0);
ds->set_envelope(bbox);
CHECK( ds->type() == mapnik::datasource::Vector );
CHECK( ds->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
@@ -83,7 +86,8 @@ TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
mapnik::image_rgba8 im(map.width(),map.height());
mapnik::agg_renderer<mapnik::image_rgba8> ren(map,im);
ren.apply();
- if (!mapnik::util::exists("test/fixtures/rasterize-expected-1.png")) {
+ if (!mapnik::util::exists("test/fixtures/rasterize-expected-1.png"))
+ {
mapnik::save_to_file(im,"test/fixtures/rasterize-expected-1.png","png32");
}
}
@@ -91,61 +95,57 @@ TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
// set up to "re-render" it
// the goal here is to trigger the geometries to pass through
// the decoder and encoder again
+ mapnik::vector_tile_impl::tile out_tile(bbox);
+
{
- typedef mapnik::vector_tile_impl::backend_pbf backend_type;
- typedef mapnik::vector_tile_impl::processor<backend_type> renderer_type;
- backend_type backend(tile,16);
std::string merc_srs("+init=epsg:3857");
- mapnik::Map map(tile_size,tile_size,merc_srs);
- map.zoom_to_box(bbox);
- mapnik::request m_req(map.width(),map.height(),map.get_current_extent());
- protozero::pbf_reader message(uncompressed.data(),uncompressed.size());
- while (message.next(3)) {
+ mapnik::Map map(256, 256, merc_srs);
+ protozero::pbf_reader message(uncompressed.data(), uncompressed.size());
+ while (message.next(3))
+ {
protozero::pbf_reader layer_msg = message.get_message();
auto ds = std::make_shared<mapnik::vector_tile_impl::tile_datasource_pbf>(
layer_msg,
0,
0,
- 0,
- tile_size);
+ 0);
mapnik::layer lyr(ds->get_name(),merc_srs);
- ds->set_envelope(m_req.get_buffered_extent());
+ ds->set_envelope(out_tile.get_buffered_extent());
lyr.set_datasource(ds);
map.add_layer(lyr);
}
- renderer_type ren(backend,map,m_req);
+ mapnik::vector_tile_impl::processor ren(map);
ren.set_process_all_rings(true);
- ren.apply();
+ ren.set_fill_type(mapnik::vector_tile_impl::non_zero_fill);
+ ren.update_tile(out_tile);
}
// now `tile` should contain all the data
std::string buffer2;
- CHECK(tile.SerializeToString(&buffer2));
- CHECK(2774 == buffer2.size());
+ out_tile.serialize_to_string(buffer2);
+ CHECK(2835 == buffer2.size());
- std::ofstream stream_out("./test/data/0.0.0.vector-b.pbf",std::ios_base::out|std::ios_base::binary);
+ std::ofstream stream_out("./test/data/0.0.0.vector-b.mvt",std::ios_base::out|std::ios_base::binary);
stream_out << buffer2;
stream_out.close();
// let's now render this to a image and make sure it looks right
{
- mapnik::Map map(tile_size,tile_size,"+init=epsg:3857");
- tile_type tile2;
+ mapnik::Map map(256,256,"+init=epsg:3857");
+ vector_tile::Tile tile2;
CHECK(tile2.ParseFromString(buffer2));
- std::string key("");
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(tile2,key));
- CHECK("" == key);
- CHECK(false == mapnik::vector_tile_impl::is_solid_extent(buffer2,key));
- CHECK("" == key);
-
CHECK(1 == tile2.layers_size());
vector_tile::Tile_Layer const& layer2 = tile2.layers(0);
CHECK(std::string("water") == layer2.name());
CHECK(5 == layer2.features_size());
+ CHECK(2 == layer2.version());
mapnik::layer lyr2("water",map.srs());
- std::shared_ptr<mapnik::vector_tile_impl::tile_datasource> ds = std::make_shared<
- mapnik::vector_tile_impl::tile_datasource>(
- layer2,0,0,0,map.width());
+ protozero::pbf_reader tile_reader(buffer2);
+ REQUIRE(tile_reader.next(mapnik::vector_tile_impl::Tile_Encoding::LAYERS));
+ protozero::pbf_reader layer_reader = tile_reader.get_message();
+ std::shared_ptr<mapnik::vector_tile_impl::tile_datasource_pbf> ds = std::make_shared<
+ mapnik::vector_tile_impl::tile_datasource_pbf>(
+ layer_reader,0,0,0);
ds->set_envelope(bbox);
CHECK( ds->type() == mapnik::datasource::Vector );
CHECK( ds->get_geometry_type() == mapnik::datasource_geometry_t::Collection );
@@ -168,12 +168,12 @@ TEST_CASE( "vector tile rasterize", "should try to decode windfail tile" ) {
mapnik::agg_renderer<mapnik::image_rgba8> ren(map,im);
ren.apply();
unsigned diff = testing::compare_images(im,"test/fixtures/rasterize-expected-1.png");
- // should be almost equal (49 is good enough since re-rendering filters a few small degenerates)
- CHECK(49 == diff);
- if (diff > 49) {
+ // should be almost equal (50 is good enough since re-rendering filters a few small degenerates)
+ CHECK(50 == diff);
+ if (diff > 50)
+ {
mapnik::save_to_file(im,"test/fixtures/rasterize-actual-1.png","png32");
}
}
-
}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mapnik-vector-tile.git
More information about the Pkg-grass-devel
mailing list