This is an automated email from the git hooks/post-receive script. kapouer pushed a commit to branch master in repository mapnik-vector-tile.
commit 16e0df8b187dd21d40d3326ac3828252b4298cb7 Author: Jérémy Lal <kapo...@melix.org> Date: Wed Aug 20 19:03:09 2014 +0200 Imported Upstream version 0.5.1+dfsg --- .gitignore | 5 +- .travis.yml | 16 +- CHANGELOG.md | 43 +++++ Makefile | 34 +++- README.md | 46 +---- examples/c++/Makefile | 11 +- examples/c++/README.md | 28 +++ examples/c++/tileinfo.cpp | 100 ++++++++++- package.json | 5 +- proto/vector_tile.proto | 3 + src/hash_variant.hpp | 36 ---- src/mapnik3x_compatibility.hpp | 34 ++++ src/vector_tile_backend_pbf.hpp | 100 +++-------- src/vector_tile_datasource.hpp | 205 ++++++++++++--------- src/vector_tile_geometry_encoder.hpp | 191 ++++++++++++++++++++ src/vector_tile_processor.hpp | 153 +++++++++++++--- src/vector_tile_util.hpp | 29 ++- test/encoding_util.hpp | 86 +++++++++ test/geometry_encoding.cpp | 338 +++++++++++++++++++++++++++++++++++ test/raster_style.xml | 7 + test/raster_tile.cpp | 125 +++++++++++++ test/style.xml | 7 + test/test_utils.hpp | 23 ++- test/vector_tile.cpp | 257 ++++++++++++++++++++++---- 24 files changed, 1549 insertions(+), 333 deletions(-) diff --git a/.gitignore b/.gitignore index f0fe95c..19f5f78 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ src/vector_tile.pb.cc src/vector_tile.pb.h python/vector_tile_pb2.py test/run-test +test/run-geom-test +test/run-raster-test *pyc archive -TODO.md \ No newline at end of file +TODO.md +examples/c++/tileinfo \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index a422bfc..04883b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,22 +7,28 @@ compiler: before_install: - sudo apt-add-repository --yes ppa:mapnik/v2.2.0 - sudo apt-add-repository --yes ppa:mapnik/nightly-2.3 - - sudo apt-get update -qq + #- sudo apt-add-repository --yes ppa:mapnik/nightly-trunk + - sudo apt-get update -y install: - - sudo apt-get -qq install libprotobuf7 libprotobuf-dev protobuf-compiler + - sudo apt-get -y install libprotobuf7 libprotobuf-dev protobuf-compiler g++ gcc + #- sudo apt-get -y install gcc-4.8 g++-4.8 before_script: - - sudo apt-get -qq install libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0* + - sudo apt-get -y install libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0* - make clean - make test - sudo apt-get purge libmapnik=2.2.0* mapnik-utils=2.2.0* libmapnik-dev=2.2.0* script: - - sudo apt-get -qq install libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* + - sudo apt-get -y install libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* mapnik-input-plugin*=2.3.0* - make clean - make test - - sudo apt-get purge libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* + #- sudo apt-get purge libmapnik=2.3.0* mapnik-utils=2.3.0* libmapnik-dev=2.3.0* mapnik-input-plugin*=2.3.0* + #- sudo apt-get -qq install libmapnik=3.0.0* mapnik-utils=3.0.0* libmapnik-dev=3.0.0* mapnik-input-plugin*=3.0.0* + #- if [ "${CXX}" = 'g++' ]; then export CXX="g++-4.8" && export CC="gcc-4.8"; fi; + #- make clean + #- make test notifications: irc: diff --git a/CHANGELOG.md b/CHANGELOG.md index 60593b4..c22ad80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,50 @@ # Changlog +## 0.5.1 + + - Minor build fixes + +## 0.5.0 + + - Experimental support for encoding images in vector tile features. + - Fixed potential hang if trying to render a feature without geometries + +## 0.4.2 + + - Additional optimizations and fixes to geometry encoding (#38 - avoid dropping vertex that forms horizontal or vertical right angle) + +## 0.4.1 + + - Added initial support for Mapnik 3.x + +## 0.4.0 + + - Refactored geometry encoder with fixes to drop duplicated/no-op verticies and/or + close commands + - `npm install` no longer runs `protoc` - the responsibility for this is now up to `node-mapnik` + - Optimized is_solid check + +## 0.3.7 + + - Add back protoc running to avoid unintended node-mapnik breakages with older versions. + +## 0.3.6 + + - Avoided 'Unknown command type (is_solid_extent): 0' + +## 0.3.5 + + - `npm install` no longer runs `protoc` - the responsibility for this is now up to `node-mapnik` + - Improved tile encoding: empty layers are no longer added + - All move_to commands and the last vertex in lines is no longer thrown out even with high `tolerance` + - Rolled back the change from v0.3.4 - multipart geometries are now again not decoded correctly, but this + needs to stay this way for performance reasons at the cost of correct marker/labeling placement on each + geometry part - long term solutions tracked at mapnik/mapnik#2151. Re-enabled to v0.3.4 behavior by setting + `-DEXPLODE_PARTS` in `CXXFLAGS`. + ## 0.3.4 + - Fixed tile_datasource geometry decoding such that it polygons are closed (for hit_test results) - Fixed tile_datasource geometry decoding such that it respects multipart geometries (#19) ## 0.3.3 diff --git a/Makefile b/Makefile index 33179c3..2b2203f 100755 --- a/Makefile +++ b/Makefile @@ -2,12 +2,17 @@ PROTOBUF_CXXFLAGS=$(shell pkg-config protobuf --cflags) PROTOBUF_LDFLAGS=$(shell pkg-config protobuf --libs-only-L) -lprotobuf-lite MAPNIK_CXXFLAGS=$(shell mapnik-config --cflags) -Wsign-compare MAPNIK_LDFLAGS=$(shell mapnik-config --libs --ldflags --dep-libs) -CXXFLAGS := $(CXXFLAGS) # inherit from env -LDFLAGS := $(LDFLAGS) # inherit from env +COMMON_FLAGS = -Wall -Wshadow -pedantic -Wno-c++11-long-long -Wno-c++11-extensions +#-Wsign-compare -Wsign-conversion -Wunused-parameter +# inherit from env +CXX := $(CXX) +CXXFLAGS := $(CXXFLAGS) +LDFLAGS := $(LDFLAGS) +MAPNIK_PLUGINDIR := $(shell mapnik-config --input-plugins) all: mapnik-vector-tile -mapnik-vector-tile: src/vector_tile.pb.cc +mapnik-vector-tile: src/vector_tile.pb.cc Makefile src/vector_tile.pb.cc: proto/vector_tile.proto protoc -Iproto/ --cpp_out=./src proto/vector_tile.proto @@ -17,11 +22,26 @@ python/vector_tile_pb2.py: proto/vector_tile.proto python: python/vector_tile_pb2.py -test/run-test: src/vector_tile.pb.cc test/vector_tile.cpp test/test_utils.hpp src/* - $(CXX) -o ./test/run-test test/vector_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field +test/run-test: Makefile src/vector_tile.pb.cc test/vector_tile.cpp test/test_utils.hpp src/* + @$(CXX) -o ./test/run-test test/vector_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field -test: test/run-test src/vector_tile.pb.cc +test/test-cfg.h: + echo "#define MAPNIK_PLUGINDIR \"$(MAPNIK_PLUGINDIR)\"" > test/test-cfg.h + +test: test/test-cfg.h test/run-test test/run-geom-test ./test/run-raster-test src/vector_tile.pb.cc test/catch.hpp ./test/run-test + ./test/run-geom-test + ./test/run-raster-test + +./test/run-raster-test: Makefile src/vector_tile.pb.cc test/raster_tile.cpp test/encoding_util.hpp test/catch.hpp + @$(CXX) -o ./test/run-raster-test test/raster_tile.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field + ./test/run-raster-test + +./test/run-geom-test: Makefile src/vector_tile.pb.cc test/geometry_encoding.cpp test/encoding_util.hpp src/vector_tile_geometry_encoder.hpp test/catch.hpp + @$(CXX) -o ./test/run-geom-test test/geometry_encoding.cpp src/vector_tile.pb.cc -I./src $(CXXFLAGS) $(MAPNIK_CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(COMMON_FLAGS) $(MAPNIK_LDFLAGS) $(PROTOBUF_LDFLAGS) $(LDFLAGS) -Wno-unused-private-field + +geom: test/run-geom-test src/vector_tile.pb.cc + ./test/run-geom-test python-test: python/vector_tile_pb2.py python ./test/python/test.py @@ -30,6 +50,8 @@ clean: @rm -f ./src/vector_tile.pb.cc @rm -f ./src/vector_tile.pb.h @rm -f ./test/run-test + @rm -f ./test/run-geom-test + @rm -f ./test/run-raster-test @rm -f ./python/vector_tile_pb2.py @rm -f ./python/*pyc diff --git a/README.md b/README.md index 445fbf5..f2f9142 100644 --- a/README.md +++ b/README.md @@ -2,55 +2,21 @@ [![Build Status](https://secure.travis-ci.org/mapbox/mapnik-vector-tile.png)](http://travis-ci.org/mapbox/mapnik-vector-tile) -A high performance library for working with vector tiles with Mapnik. +A high performance library for working with vector tiles. Provides C++ headers that support rendering geodata into vector tiles and rendering vector tiles into images. -Many efforts at vector tiles use [GeoJSON](http://www.geojson.org/) or other -text based formats that are easy to parse in the browser. - -This approach is different: tiles are encoded as protobuf messages and -storage is tightly designed around the [Mapnik rendering engine](http://mapnik.org). -In this case Mapnik is the client rather than the browser. This provides a geodata -format that is a drop-in replacement for datasources like postgis and shapefiles -without compromising on speed and can be styled using existing design tools -like [TileMill](http://tilemill.com). +## Depends + - Mapnik > = v2.2.x: `libmapnik` and `mapnik-config` + - Protobuf: `libprotobuf` and `protoc` ## Implementation details -Vector tiles in this code represent a direct serialization of Mapnik layers -optimized for space efficient storage and fast deserialization directly back -into Mapnik objects. For those familiar with the Mapnik API vector tiles -here can be considered a named array of `mapnik::featureset_ptr` whose geometries -have been pre-tiled. - -A vector tile can consist of one or more ordered layers identified by name -and containing one or more features. - -Features contain attributes and geometries: either point, linestring, or polygon. -Features expect single geometries so multipolygons or multilinestrings are represented -as multiple features, each storing a single geometry part. - -Geometries are stored as an x,y,command stream (where `command` is a rendering command -like move_to or line_to). Geometries are clipped, reprojected into the map srs, -converted to screen coordinates, and [delta](http://en.wikipedia.org/wiki/Delta_encoding) -and [zigzag](https://developers.google.com/protocol-buffers/docs/encoding#types) encoded. - -Feature attributes are encoded as key:value pairs which are dictionary encoded -at the layer level for compact storage of any repeated keys or values. Values use variant -type encoding supporting both unicode strings, boolean values, and various integer and -floating point types. - -Vector tiles are serialized as protobuf messages which are then zlib compressed. - -The assumed projection is Spherical Mercator (`epsg:3857`). - -## Requires +Vector tiles in this code represent a direct serialization of Mapnik layers optimized for space efficient storage and fast deserialization. For those familiar with the Mapnik API vector tiles here can be considered a named array of `mapnik::featureset_ptr` whose geometries have been pre-tiled. -- Mapnik v2.2.0: `libmapnik` and `mapnik-config` -- Protobuf: `libprotobuf` and `protoc` +For more details see [vector-tile-spec](https://github.com/mapbox/vector-tile-spec). ### Ubuntu Dependencies Installation diff --git a/examples/c++/Makefile b/examples/c++/Makefile index d2870c9..14f21fd 100755 --- a/examples/c++/Makefile +++ b/examples/c++/Makefile @@ -6,16 +6,17 @@ LDFLAGS := $(LDFLAGS) # inherit from env all: tileinfo tileinfo: tileinfo.cpp ../../src/vector_tile.pb.cc - $(CXX) tileinfo.cpp ../../src/vector_tile.pb.cc -o tileinfo -lprotobuf-lite -lz + $(CXX) $(CXXFLAGS) $(PROTOBUF_CXXFLAGS) $(LDFLAGS) $(PROTOBUF_LDFLAGS) tileinfo.cpp ../../src/vector_tile.pb.cc -o tileinfo -lprotobuf-lite -lz + +install: tileinfo + mkdir -p /usr/local/bin + cp ./tileinfo /usr/local/bin/tileinfo + chmod +x /usr/local/bin/tileinfo test: ./tileinfo ../data/14_8716_8015.vector.pbf ./tileinfo ../data/14_2620_6331.vector.pbf.z -install: - cp ./tileinfo /usr/local/bin/tileinfo - chmod +x /usr/local/bin/tileinfo - clean: @rm -f ./tileinfo diff --git a/examples/c++/README.md b/examples/c++/README.md new file mode 100644 index 0000000..695b826 --- /dev/null +++ b/examples/c++/README.md @@ -0,0 +1,28 @@ + +# tileinfo + +A commandline tool to dump details about what layers, features, and geometries exist in a vector tile. + +Vector tiles can be either zlib deflated (compressed) or not. + + +## Depends + + - C++ compiler + - libprotobuf + +Install these dependencies on Ubuntu: + + apt-get install pkg-config libprotobuf7 libprotobuf-dev protobuf-compiler build-essential g++ + +Install these dependencies on OS X: + + brew install pkg-config protobuf + +## Installation + + make + +## Usage + + tileinfo ../data/14_8716_8015.vector.pbf diff --git a/examples/c++/tileinfo.cpp b/examples/c++/tileinfo.cpp index 65b90c2..222d6ee 100644 --- a/examples/c++/tileinfo.cpp +++ b/examples/c++/tileinfo.cpp @@ -4,6 +4,21 @@ #include <iostream> #include <fstream> #include <stdexcept> +#include <sstream> + +enum CommandType { + SEG_END = 0, + SEG_MOVETO = 1, + SEG_LINETO = 2, + SEG_CLOSE = (0x40 | 0x0f) +}; + +enum eGeomType { + Unknown = 0, + Point = 1, + LineString = 2, + Polygon = 3 +}; int main(int argc, char** argv) { @@ -67,6 +82,70 @@ int main(int argc, char** argv) std::cout << " features: " << layer.features_size() << "\n"; std::cout << " keys: " << layer.keys_size() << "\n"; std::cout << " values: " << layer.values_size() << "\n"; + unsigned total_repeated = 0; + unsigned num_commands = 0; + unsigned num_move_to = 0; + unsigned num_line_to = 0; + unsigned num_close = 0; + unsigned num_empty = 0; + unsigned degenerate = 0; + for (unsigned j=0;j<layer.features_size();++j) + { + mapnik::vector::tile_feature const & f = layer.features(j); + total_repeated += f.geometry_size(); + eGeomType g_type = static_cast<eGeomType>(f.type()); + int cmd = -1; + const int cmd_bits = 3; + unsigned length = 0; + unsigned g_length = 0; + for (int k = 0; k < f.geometry_size();) + { + if (!length) { + unsigned cmd_length = f.geometry(k++); + cmd = cmd_length & ((1 << cmd_bits) - 1); + length = cmd_length >> cmd_bits; + if (length <= 0) num_empty++; + num_commands++; + g_length = 0; + } + if (length > 0) { + length--; + if (cmd == SEG_MOVETO || cmd == SEG_LINETO) + { + f.geometry(k++); + f.geometry(k++); + g_length++; + if (cmd == SEG_MOVETO) + { + num_move_to++; + } + else if (cmd == SEG_LINETO) + { + num_line_to++; + } + } + else if (cmd == (SEG_CLOSE & ((1 << cmd_bits) - 1))) + { + if (g_length <= 2) degenerate++; + num_close++; + } + else + { + std::stringstream s; + s << "Unknown command type: " << cmd; + throw std::runtime_error(s.str()); + } + } + } + } + std::cout << " geometry summary:\n"; + std::cout << " total: " << total_repeated << "\n"; + std::cout << " commands: " << num_commands << "\n"; + std::cout << " move_to: " << num_move_to << "\n"; + std::cout << " line_to: " << num_line_to << "\n"; + std::cout << " close: " << num_close << "\n"; + std::cout << " degenerate polygons: " << degenerate << "\n"; + std::cout << " empty geoms: " << num_empty << "\n"; } } else { for (unsigned i=0;i<tile.layers_size();++i) @@ -79,7 +158,10 @@ int main(int argc, char** argv) for (unsigned i=0;i<layer.keys_size();++i) { std::string const& key = layer.keys(i); - std::cout << key << ","; + std::cout << key; + if (i<layer.keys_size()-1) { + std::cout << ","; + } } std::cout << "\n"; std::cout << " values: "; @@ -87,7 +169,7 @@ int main(int argc, char** argv) { mapnik::vector::tile_value const & value = layer.values(i); if (value.has_string_value()) { - std::cout << "'" << value.string_value(); + std::cout << value.string_value(); } else if (value.has_int_value()) { std::cout << value.int_value(); } else if (value.has_double_value()) { @@ -111,7 +193,19 @@ int main(int argc, char** argv) for (unsigned i=0;i<layer.features_size();++i) { mapnik::vector::tile_feature const & feat = layer.features(i); - std::cout << " feature: " << feat.id() << " " << feat.type() << "\n"; + std::cout << " feature: " << feat.id() << "\n"; + std::cout << " type: "; + unsigned feat_type = feat.type(); + if (feat_type == 0) { + std::cout << "Unknown"; + } else if (feat_type == 1) { + std::cout << "Point"; + } else if (feat_type == 2) { + std::cout << "LineString"; + } else if (feat_type == 3) { + std::cout << "Polygon"; + } + std::cout << "\n"; std::cout << " tags: "; for (unsigned j=0;j<feat.tags_size();++j) { diff --git a/package.json b/package.json index 41602ee..82f8ffe 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,10 @@ { "name": "mapnik-vector-tile", - "version": "0.3.4", + "version": "0.5.1", "description": "Mapnik vector tile API", "main": "./package.json", "repository" : { "type" : "git", "url" : "git://github.com/mapbox/mapnik-vector-tile.git" - }, - "scripts": { - "install" : "protoc -Iproto/ --cpp_out=./src/ ./proto/vector_tile.proto" } } diff --git a/proto/vector_tile.proto b/proto/vector_tile.proto index 110f37c..d17e1f1 100644 --- a/proto/vector_tile.proto +++ b/proto/vector_tile.proto @@ -60,6 +60,9 @@ message tile { // also encoded as deltas to the previous position. The original // position is (0,0) repeated uint32 geometry = 4 [ packed = true ]; + + optional bytes raster = 5; + } message layer { diff --git a/src/hash_variant.hpp b/src/hash_variant.hpp deleted file mode 100644 index 57af0da..0000000 --- a/src/hash_variant.hpp +++ /dev/null @@ -1,36 +0,0 @@ -// TODO: Remove this file once the minimum Boost version is bumped to 1.50 - -#ifndef BOOST_HASH_VARIANT_FUNCTION_HPP -#define BOOST_HASH_VARIANT_FUNCTION_HPP - -#if defined(_MSC_VER) && (_MSC_VER >= 1020) -# pragma once -#endif - -#include <boost/variant/variant_fwd.hpp> -#include <boost/variant/static_visitor.hpp> -#include <boost/variant/apply_visitor.hpp> -#include <boost/functional/hash_fwd.hpp> - -namespace boost { - -namespace detail { namespace variant { - struct variant_hasher: public boost::static_visitor<std::size_t> { - template <class T> - std::size_t operator()(T const& val) const { - using namespace boost; - hash<T> hasher; - return hasher(val); - } - }; - }} - -template < BOOST_VARIANT_ENUM_PARAMS(typename T) > -std::size_t hash_value(variant< BOOST_VARIANT_ENUM_PARAMS(T) > const& val) { - std::size_t seed = boost::apply_visitor(detail::variant::variant_hasher(), val); - hash_combine(seed, val.which()); - return seed; -} -} - -#endif diff --git a/src/mapnik3x_compatibility.hpp b/src/mapnik3x_compatibility.hpp new file mode 100644 index 0000000..d2d88ec --- /dev/null +++ b/src/mapnik3x_compatibility.hpp @@ -0,0 +1,34 @@ +#ifndef __MAPNIK_VECTOR_TILE_COMPATIBILITY_H__ +#define __MAPNIK_VECTOR_TILE_COMPATIBILITY_H__ + +#include <mapnik/version.hpp> + +#if MAPNIK_VERSION >= 300000 + #define MAPNIK_UNIQUE_PTR std::unique_ptr + #define MAPNIK_SHARED_INCLUDE <memory> + #define MAPNIK_MAKE_SHARED_INCLUDE <memory> + #define MAPNIK_MAKE_SHARED std::make_shared + #define MAPNIK_SHARED_PTR std::shared_ptr + #define MAPNIK_ADD_LAYER add_layer + #define MAPNIK_GET std::get + #define MAPNIK_GEOM_TYPE mapnik::geometry_type::types + #define MAPNIK_POINT mapnik::geometry_type::types::Point + #define MAPNIK_POLYGON mapnik::geometry_type::types::Polygon + #define MAPNIK_LINESTRING mapnik::geometry_type::types::LineString + #define MAPNIK_UNKNOWN mapnik::geometry_type::types::Unknown +#else + #define MAPNIK_UNIQUE_PTR std::auto_ptr + #define MAPNIK_SHARED_INCLUDE <boost/shared_ptr.hpp> + #define MAPNIK_MAKE_SHARED_INCLUDE <boost/make_shared.hpp> + #define MAPNIK_MAKE_SHARED boost::make_shared + #define MAPNIK_SHARED_PTR boost::shared_ptr + #define MAPNIK_ADD_LAYER addLayer + #define MAPNIK_GET boost::get + #define MAPNIK_GEOM_TYPE mapnik::eGeomType + #define MAPNIK_POINT mapnik::Point + #define MAPNIK_POLYGON mapnik::Polygon + #define MAPNIK_LINESTRING mapnik::LineString + #define MAPNIK_UNKNOWN mapnik::Unknown +#endif + +#endif diff --git a/src/vector_tile_backend_pbf.hpp b/src/vector_tile_backend_pbf.hpp index 717c9be..f49d0c4 100644 --- a/src/vector_tile_backend_pbf.hpp +++ b/src/vector_tile_backend_pbf.hpp @@ -3,16 +3,18 @@ // mapnik #include <mapnik/feature.hpp> -#include <mapnik/version.hpp> #include <mapnik/value_types.hpp> // vector tile #include "vector_tile.pb.h" +#include "vector_tile_geometry_encoder.hpp" // boost #include <boost/unordered_map.hpp> #include <boost/foreach.hpp> +#include "mapnik3x_compatibility.hpp" + namespace mapnik { namespace vector { struct to_tile_value: public boost::static_visitor<> @@ -75,6 +77,14 @@ namespace mapnik { namespace vector { { } + void add_tile_feature_raster(std::string const& image_buffer) + { + if (current_feature_) + { + current_feature_->set_raster(image_buffer); + } + } + void stop_tile_feature() { if (current_feature_) @@ -99,8 +109,8 @@ namespace mapnik { namespace vector { feature_kv_iterator end = feature.end(); for ( ;itr!=end; ++itr) { - std::string const& name = boost::get<0>(*itr); - mapnik::value const& val = boost::get<1>(*itr); + std::string const& name = MAPNIK_GET<0>(*itr); + mapnik::value const& val = MAPNIK_GET<1>(*itr); if (!val.is_null()) { // Insert the key index @@ -156,90 +166,28 @@ namespace mapnik { namespace vector { current_layer_->set_extent(256 * path_multiplier_); } - void stop_tile_layer() + inline void stop_tile_layer() { - // NOTE: we intentionally do not remove layers without features - // since the re-rendering logic expects a layer entry no matter what //std::cerr << "stop_tile_layer()" << std::endl; } template <typename T> - unsigned add_path(T & path, unsigned tolerance, mapnik::eGeomType type) + inline unsigned add_path(T & path, unsigned tolerance, MAPNIK_GEOM_TYPE type) { - unsigned count = 0; - if (current_feature_) { - path.rewind(0); - current_feature_->set_type(mapnik::vector::tile_GeomType(type)); - - vertex2d vtx(vertex2d::no_init); - int cmd = -1; - int cmd_idx = -1; - const int cmd_bits = 3; - unsigned length = 0; - - // See vector_tile.proto for a description of how vertex command - // encoding works. - while ((vtx.cmd = path.vertex(&vtx.x, &vtx.y)) != SEG_END) - { - if (static_cast<int>(vtx.cmd) != cmd) - { - if (cmd_idx >= 0) - { - // Encode the previous length/command value. - current_feature_->set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1))); - } - cmd = static_cast<int>(vtx.cmd); - length = 0; - cmd_idx = current_feature_->geometry_size(); - current_feature_->add_geometry(0); // placeholder - } - - if (cmd == SEG_MOVETO || cmd == SEG_LINETO) - { - // Compute delta to the previous coordinate. - int32_t cur_x = static_cast<int32_t>(std::floor((vtx.x * path_multiplier_)+.5)); - int32_t cur_y = static_cast<int32_t>(std::floor((vtx.y * path_multiplier_)+.5)); - int32_t dx = cur_x - x_; - int32_t dy = cur_y - y_; - - // Omit movements that are no-ops. - unsigned x_floor = static_cast<unsigned>(std::abs(dx)); - unsigned y_floor = static_cast<unsigned>(std::abs(dy)); - if (x_floor >= tolerance || - y_floor >= tolerance || - length == 0) - { - // Manual zigzag encoding. - current_feature_->add_geometry((dx << 1) ^ (dx >> 31)); - current_feature_->add_geometry((dy << 1) ^ (dy >> 31)); - x_ = cur_x; - y_ = cur_y; - - length++; - } - } - else if (cmd == SEG_CLOSE) { - length++; - } - else { - throw std::runtime_error("Unknown command type"); - } - - ++count; - } - - // Update the last length/command value. - if (cmd_idx >= 0) - { - current_feature_->set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1))); - } + return encode_geometry(path, + static_cast<tile_GeomType>(type), + *current_feature_, + x_, + y_, + tolerance, + path_multiplier_); } - return count; + return 0; } }; - }} // end ns +}} // end ns #endif // __MAPNIK_VECTOR_TILE_BACKEND_PBF_H__ diff --git a/src/vector_tile_datasource.hpp b/src/vector_tile_datasource.hpp index 362e8e9..001d291 100644 --- a/src/vector_tile_datasource.hpp +++ b/src/vector_tile_datasource.hpp @@ -14,12 +14,14 @@ #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_reader.hpp> +#include <mapnik/raster.hpp> #include <memory> #include <stdexcept> @@ -27,24 +29,79 @@ #include <boost/optional.hpp> #include <boost/ptr_container/ptr_vector.hpp> -#include <boost/make_shared.hpp> -#include <boost/shared_ptr.hpp> #include <unicode/unistr.h> -#include <boost/algorithm/string.hpp> + +#include "mapnik3x_compatibility.hpp" +#include MAPNIK_MAKE_SHARED_INCLUDE +#include MAPNIK_SHARED_INCLUDE namespace mapnik { namespace vector { + void add_attributes(mapnik::feature_ptr feature, + mapnik::vector::tile_feature const& f, + mapnik::vector::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)) + { + mapnik::vector::tile_value const& value = layer.values(key_value); + if (value.has_string_value()) + { + std::string 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, std::set<std::string> const& attribute_names, mapnik::vector::tile_layer const& layer, double tile_x, double tile_y, double scale) : filter_(filter), + tile_extent_(tile_extent), layer_(layer), tile_x_(tile_x), tile_y_(tile_y), @@ -52,7 +109,7 @@ namespace mapnik { namespace vector { itr_(0), end_(layer_.features_size()), tr_("utf-8"), - ctx_(boost::make_shared<mapnik::context_type>()) + ctx_(MAPNIK_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(); @@ -77,18 +134,43 @@ namespace mapnik { namespace vector { { mapnik::vector::tile_feature const& f = layer_.features(itr_); mapnik::value_integer feature_id = itr_++; - // if encoded feature was given an id, respect it - // https://github.com/mapbox/mapnik-vector-tile/issues/17 - // https://github.com/mapbox/mapnik-vector-tile/issues/18 - if (f.has_id()) + if (f.has_raster()) { - feature_id = f.id(); + std::string const& image_buffer = f.raster(); + MAPNIK_UNIQUE_PTR<mapnik::image_reader> reader(mapnik::get_image_reader(image_buffer.data(),image_buffer.size())); + if (reader.get()) + { + if (f.has_id()) + { + feature_id = f.id(); + } + #if MAPNIK_VERSION >= 300000 + double filter_factor = 1.0; + #endif + bool premultiplied = false; + mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id); + mapnik::raster_ptr raster = MAPNIK_MAKE_SHARED<mapnik::raster>( + tile_extent_, + reader->width(), + reader->height(), + #if MAPNIK_VERSION >= 300000 + filter_factor, + #endif + premultiplied + ); + reader->read(0,0,raster->data_); + feature->set_raster(raster); + add_attributes(feature,f,layer_,tr_); + return feature; + } + } + if (f.geometry_size() <= 0) + { + continue; } - mapnik::feature_ptr feature( - mapnik::feature_factory::create(ctx_,feature_id)); - feature->paths().push_back(new mapnik::geometry_type( - mapnik::eGeomType(f.type()))); - mapnik::geometry_type * geom = &feature->paths().front(); + MAPNIK_UNIQUE_PTR<mapnik::geometry_type> geom( + new mapnik::geometry_type( + MAPNIK_GEOM_TYPE(f.type()))); int cmd = -1; const int cmd_bits = 3; unsigned length = 0; @@ -116,11 +198,6 @@ namespace mapnik { namespace vector { y -= (static_cast<double>(dy) / scale_); if (cmd == mapnik::SEG_MOVETO) { - if (!first) { - feature->paths().push_back(new mapnik::geometry_type( - mapnik::eGeomType(f.type()))); - geom = &feature->paths().back(); - } first_x = x; first_y = y; } @@ -142,7 +219,10 @@ namespace mapnik { namespace vector { } else { - throw std::runtime_error("Unknown command type"); + std::stringstream msg; + msg << "Unknown command type (tile_featureset): " + << cmd; + throw std::runtime_error(msg.str()); } } } @@ -150,63 +230,13 @@ namespace mapnik { namespace vector { { continue; } - - // attributes - for (int m = 0; m < f.tags_size(); m += 2) + if (f.has_id()) { - std::size_t key_name = f.tags(m); - std::size_t key_value = f.tags(m + 1); - - if (key_name < static_cast<std::size_t>(layer_.keys_size()) - && key_value < static_cast<std::size_t>(layer_.values_size())) - { - std::string const& name = layer_.keys(key_name); - if (feature->has_key(name)) - { - mapnik::vector::tile_value const& value = layer_.values(key_value); - if (value.has_string_value()) - { - std::string str = value.string_value(); - feature->put(name, tr_.transcode(str.data(), str.length())); - } - else if (value.has_int_value()) - { - mapnik::value_integer val = value.int_value(); - feature->put(name, val); - } - else if (value.has_double_value()) - { - mapnik::value_double val = value.double_value(); - feature->put(name, val); - } - else if (value.has_float_value()) - { - mapnik::value_double val = value.float_value(); - feature->put(name, val); - } - else if (value.has_bool_value()) - { - mapnik::value_bool val = value.bool_value(); - feature->put(name, val); - } - else if (value.has_sint_value()) - { - mapnik::value_integer val = value.sint_value(); - feature->put(name, val); - } - else if (value.has_uint_value()) - { - mapnik::value_integer val = value.uint_value(); - feature->put(name, val); - } - else - { - // Do nothing - //feature->put_new(name, mapnik::value_null()); - } - } - } + feature_id = f.id(); } + mapnik::feature_ptr feature = mapnik::feature_factory::create(ctx_,feature_id); + feature->paths().push_back(geom.release()); + add_attributes(feature,f,layer_,tr_); return feature; } return feature_ptr(); @@ -214,6 +244,7 @@ namespace mapnik { namespace vector { private: Filter filter_; + mapnik::box2d<double> tile_extent_; mapnik::vector::tile_layer const& layer_; double tile_x_; double tile_y_; @@ -237,6 +268,7 @@ namespace mapnik { namespace vector { 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<geometry_t> get_geometry_type() const; layer_descriptor get_descriptor() const; @@ -286,8 +318,8 @@ namespace mapnik { namespace vector { inline featureset_ptr tile_datasource::features(query const& q) const { mapnik::filter_in_box filter(q.get_bbox()); - return boost::make_shared<tile_featureset<mapnik::filter_in_box> > - (filter, q.property_names(), layer_, tile_x_, tile_y_, scale_); + return MAPNIK_MAKE_SHARED<tile_featureset<mapnik::filter_in_box> > + (filter, get_tile_extent(), q.property_names(), layer_, tile_x_, tile_y_, scale_); } inline featureset_ptr tile_datasource::features_at_point(coord2d const& pt, double tol) const @@ -298,8 +330,8 @@ namespace mapnik { namespace vector { { names.insert(layer_.keys(i)); } - return boost::make_shared<tile_featureset<filter_at_point> > - (filter, names, layer_, tile_x_, tile_y_, scale_); + return MAPNIK_MAKE_SHARED<tile_featureset<filter_at_point> > + (filter, get_tile_extent(), names, layer_, tile_x_, tile_y_, scale_); } inline void tile_datasource::set_envelope(box2d<double> const& bbox) @@ -308,14 +340,19 @@ namespace mapnik { namespace vector { extent_ = bbox; } + inline box2d<double> tile_datasource::get_tile_extent() const + { + mapnik::vector::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); + } + inline box2d<double> tile_datasource::envelope() const { if (!extent_initialized_) { - mapnik::vector::spherical_mercator merc(tile_size_); - double minx,miny,maxx,maxy; - merc.xyz(x_,y_,z_,minx,miny,maxx,maxy); - extent_.init(minx,miny,maxx,maxy); + extent_ = get_tile_extent(); extent_initialized_ = true; } return extent_; diff --git a/src/vector_tile_geometry_encoder.hpp b/src/vector_tile_geometry_encoder.hpp new file mode 100644 index 0000000..67bbd32 --- /dev/null +++ b/src/vector_tile_geometry_encoder.hpp @@ -0,0 +1,191 @@ +#ifndef __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__ +#define __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__ + +// vector tile +#include "vector_tile.pb.h" +#include <mapnik/vertex.hpp> +#include <mapnik/version.hpp> + +namespace mapnik { namespace vector { + +inline void handle_skipped_last(tile_feature & current_feature, + int32_t skipped_index, + int32_t cur_x, + int32_t cur_y, + int32_t & x_, + int32_t & y_) +{ + uint32_t last_x = current_feature.geometry(skipped_index - 2); + uint32_t last_y = current_feature.geometry(skipped_index - 1); + int32_t last_dx = ((last_x >> 1) ^ (-(last_x & 1))); + int32_t last_dy = ((last_y >> 1) ^ (-(last_y & 1))); + int32_t dx = cur_x - x_ + last_dx; + int32_t dy = cur_y - y_ + last_dy; + x_ = cur_x; + y_ = cur_y; + current_feature.set_geometry(skipped_index - 2, ((dx << 1) ^ (dx >> 31))); + current_feature.set_geometry(skipped_index - 1, ((dy << 1) ^ (dy >> 31))); +} + +template <typename T> +unsigned encode_geometry(T & path, + tile_GeomType type, + tile_feature & current_feature, + int32_t & x_, + int32_t & y_, + unsigned tolerance, + unsigned path_multiplier) +{ + unsigned count = 0; + path.rewind(0); + current_feature.set_type(type); + + vertex2d vtx(vertex2d::no_init); + int cmd = -1; + int prev_cmd = -1; + int cmd_idx = -1; + const int cmd_bits = 3; + unsigned length = 0; + bool skipped_last = false; + int32_t skipped_index = -1; + int32_t cur_x = 0; + int32_t cur_y = 0; + + // See vector_tile.proto for a description of how vertex command + // encoding works. + + std::vector<vertex2d> output; + const std::size_t buffer_size = 8; + output.reserve(buffer_size); + bool done = false; + bool cache = true; + while (true) + { + if (cache) + { + // read + vertex2d v(vertex2d::no_init); + while ((v.cmd = path.vertex(&v.x, &v.y)) != SEG_END) + { +#if MAPNIK_VERSION >= 300000 + output.push_back(std::move(v)); +#else + output.push_back(v); +#endif + if (output.size() == buffer_size) break; + } + cache = false; + if (v.cmd == SEG_END) + { + done = true; + } + } + else + { + if (done && output.empty()) break; + // process + vtx = output.front(); + { + if (static_cast<int>(vtx.cmd) != cmd) + { + if (cmd_idx >= 0) + { + // Encode the previous length/command value. + current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1))); + } + cmd = static_cast<int>(vtx.cmd); + length = 0; + cmd_idx = current_feature.geometry_size(); + current_feature.add_geometry(0); // placeholder added in first pass + } + + switch (vtx.cmd) + { + case SEG_MOVETO: + case SEG_LINETO: + { + if (cmd == SEG_MOVETO && skipped_last && skipped_index > 1) // at least one vertex + cmd/length + { + // if we skipped previous vertex we just update it to the last one here. + handle_skipped_last(current_feature, skipped_index, cur_x, cur_y, x_, y_); + } + + // Compute delta to the previous coordinate. + cur_x = static_cast<int32_t>(std::floor((vtx.x * path_multiplier) + 0.5)); + cur_y = static_cast<int32_t>(std::floor((vtx.y * path_multiplier) + 0.5)); + int32_t dx = cur_x - x_; + int32_t dy = cur_y - y_; + bool sharp_turn_ahead = false; + if (output.size() > 1) + { + vertex2d const& next_vtx = output[1]; + if (next_vtx.cmd == SEG_LINETO) + { + uint32_t next_dx = std::abs(cur_x - static_cast<int32_t>(std::floor((next_vtx.x * path_multiplier) + 0.5))); + uint32_t next_dy = std::abs(cur_y - static_cast<int32_t>(std::floor((next_vtx.y * path_multiplier) + 0.5))); + if ((next_dx == 0 && next_dy >= tolerance) || (next_dy == 0 && next_dx >= tolerance)) + { + sharp_turn_ahead = true; + } + } + } + // Keep all move_to commands, but omit other movements that are + // not >= the tolerance threshold and should be considered no-ops. + // NOTE: length == 0 indicates the command has changed and will + // preserve any non duplicate move_to or line_to + if ( length == 0 || sharp_turn_ahead || + (static_cast<unsigned>(std::abs(dx)) >= tolerance) || + (static_cast<unsigned>(std::abs(dy)) >= tolerance) + ) + { + // Manual zigzag encoding. + current_feature.add_geometry((dx << 1) ^ (dx >> 31)); + current_feature.add_geometry((dy << 1) ^ (dy >> 31)); + x_ = cur_x; + y_ = cur_y; + skipped_last = false; + ++length; + } + else + { + skipped_last = true; + skipped_index = current_feature.geometry_size(); + } + break; + } + + case SEG_CLOSE: + { + if (prev_cmd != SEG_CLOSE) ++length; + break; + } + default: + std::stringstream msg; + msg << "Unknown command type (backend_pbf): " + << cmd; + throw std::runtime_error(msg.str()); + break; + } + } + ++count; + prev_cmd = cmd; + output.erase(output.begin()); + if (output.size() < 2) cache = true; + } + } + if (skipped_last && skipped_index > 1) // at least one vertex + cmd/length + { + // if we skipped previous vertex we just update it to the last one here. + handle_skipped_last(current_feature, skipped_index, cur_x, cur_y, x_, y_); + } + // Update the last length/command value. + if (cmd_idx >= 0) + { + current_feature.set_geometry(cmd_idx, (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1))); + } + return count; +} + +}} // end ns + +#endif // __MAPNIK_VECTOR_TILE_GEOMETRY_ENCODER_H__ diff --git a/src/vector_tile_processor.hpp b/src/vector_tile_processor.hpp index bdf0d9a..bb1f97e 100644 --- a/src/vector_tile_processor.hpp +++ b/src/vector_tile_processor.hpp @@ -18,6 +18,12 @@ #include <mapnik/box2d.hpp> #include <mapnik/version.hpp> #include <mapnik/noncopyable.hpp> +#include <mapnik/image_util.hpp> +#include <mapnik/raster.hpp> +#include <mapnik/warp.hpp> +#include <mapnik/version.hpp> +#include <mapnik/image_scaling.hpp> +#include <mapnik/image_compositing.hpp> // agg #ifdef CONV_CLIPPER @@ -28,6 +34,8 @@ #endif #include "agg_conv_clip_polyline.h" +#include "agg_rendering_buffer.h" +#include "agg_pixfmt_rgba.h" #include <boost/foreach.hpp> #include <boost/optional.hpp> @@ -37,6 +45,10 @@ #include <string> #include <stdexcept> +#include "mapnik3x_compatibility.hpp" +#include MAPNIK_MAKE_SHARED_INCLUDE +#include MAPNIK_SHARED_INCLUDE + namespace mapnik { namespace vector { @@ -61,6 +73,8 @@ namespace mapnik { namespace vector { double scale_factor_; mapnik::CoordTransform t_; unsigned tolerance_; + std::string image_format_; + scaling_method_e scaling_method_; bool painted_; public: processor(T & backend, @@ -69,13 +83,18 @@ namespace mapnik { namespace vector { double scale_factor=1.0, unsigned offset_x=0, unsigned offset_y=0, - unsigned tolerance=1) + unsigned tolerance=1, + std::string const& image_format="jpeg", + scaling_method_e scaling_method=SCALING_NEAR + ) : 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), tolerance_(tolerance), + image_format_(image_format), + scaling_method_(scaling_method), painted_(false) {} void apply(double scale_denom=0.0) @@ -88,7 +107,6 @@ namespace mapnik { namespace vector { scale_denom *= scale_factor_; BOOST_FOREACH ( mapnik::layer const& lay, m_.layers() ) { - backend_.start_tile_layer(lay.name()); if (lay.visible(scale_denom)) { apply_to_layer(lay, @@ -100,7 +118,6 @@ namespace mapnik { namespace vector { m_req_.extent(), m_req_.buffer_size()); } - backend_.stop_tile_layer(); } } @@ -218,27 +235,121 @@ namespace mapnik { namespace vector { { return; } - mapnik::feature_ptr feature; - while ((feature = features->next())) - { - boost::ptr_vector<mapnik::geometry_type> & paths = feature->paths(); - if (paths.empty()) continue; - backend_.start_tile_feature(*feature); - BOOST_FOREACH( mapnik::geometry_type & geom, paths) + mapnik::feature_ptr feature = features->next(); + if (feature) { + backend_.start_tile_layer(lay.name()); + raster_ptr const& source = feature->get_raster(); + if (source) { - mapnik::box2d<double> geom_box = geom.envelope(); - if (!geom_box.intersects(buffered_query_ext)) + 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) { + #if MAPNIK_VERSION >= 300000 + raster target(target_ext, raster_width, raster_height, source->get_filter_factor()); + #else + raster target(target_ext, raster_width, raster_height); + #endif + if (!source->premultiplied_alpha_) + { + agg::rendering_buffer buffer(source->data_.getBytes(), + source->data_.width(), + source->data_.height(), + source->data_.width() * 4); + agg::pixfmt_rgba32 pixf(buffer); + pixf.premultiply(); + } + if (!prj_trans.equal()) + { + double offset_x = ext.minx() - start_x; + double offset_y = ext.miny() - start_y; + #if MAPNIK_VERSION >= 300000 + reproject_and_scale_raster(target, *source, prj_trans, + offset_x, offset_y, + width, + scaling_method_); + #else + reproject_and_scale_raster(target, *source, prj_trans, + offset_x, offset_y, + width, + 2.0, + scaling_method_); + #endif + } + else + { + double image_ratio_x = ext.width() / source->data_.width(); + double image_ratio_y = ext.height() / source->data_.height(); + #if MAPNIK_VERSION >= 300000 + scale_image_agg<image_data_32>(target.data_, + source->data_, + scaling_method_, + image_ratio_x, + image_ratio_y, + 0.0, + 0.0, + source->get_filter_factor()); + #else + scale_image_agg<image_data_32>(target.data_, + source->data_, + scaling_method_, + image_ratio_x, + image_ratio_y, + 0.0, + 0.0, + 2.0); + #endif + } + mapnik::image_data_32 im_tile(width,height); + composite(im_tile, target.data_, + src_over, 1, + start_x, start_y, false); + agg::rendering_buffer buffer(im_tile.getBytes(), + im_tile.width(), + im_tile.height(), + im_tile.width() * 4); + agg::pixfmt_rgba32 pixf(buffer); + pixf.demultiply(); + backend_.start_tile_feature(*feature); + backend_.add_tile_feature_raster(mapnik::save_to_string(im_tile,image_format_)); + } + backend_.stop_tile_layer(); + return; + } + // vector pathway + while (feature) + { + boost::ptr_vector<mapnik::geometry_type> & paths = feature->paths(); + if (paths.empty()) { + feature = features->next(); continue; } - if (handle_geometry(geom, - prj_trans, - buffered_query_ext) > 0) + backend_.start_tile_feature(*feature); + BOOST_FOREACH( mapnik::geometry_type & geom, paths) { - painted_ = true; + mapnik::box2d<double> geom_box = geom.envelope(); + if (!geom_box.intersects(buffered_query_ext)) + { + continue; + } + if (handle_geometry(geom, + prj_trans, + buffered_query_ext) > 0) + { + painted_ = true; + } } + backend_.stop_tile_feature(); + feature = features->next(); } - backend_.stop_tile_feature(); + backend_.stop_tile_layer(); } } @@ -249,7 +360,7 @@ namespace mapnik { namespace vector { unsigned path_count = 0; switch (geom.type()) { - case mapnik::Point: + case MAPNIK_POINT: { if (geom.size() > 0) { @@ -260,7 +371,7 @@ namespace mapnik { namespace vector { } break; } - case mapnik::LineString: + case MAPNIK_LINESTRING: { if (geom.size() > 1) { @@ -277,7 +388,7 @@ namespace mapnik { namespace vector { } break; } - case mapnik::Polygon: + case MAPNIK_POLYGON: { if (geom.size() > 2) { @@ -310,7 +421,7 @@ namespace mapnik { namespace vector { } break; } - case mapnik::Unknown: + case MAPNIK_UNKNOWN: default: { throw std::runtime_error("unhandled geometry type"); diff --git a/src/vector_tile_util.hpp b/src/vector_tile_util.hpp index 25611f7..abc3581 100644 --- a/src/vector_tile_util.hpp +++ b/src/vector_tile_util.hpp @@ -87,7 +87,10 @@ namespace mapnik { namespace vector { } else { - throw std::runtime_error("Unknown command type"); + std::stringstream msg; + msg << "Unknown command type (is_solid_clipper): " + << cmd; + throw std::runtime_error(msg.str()); } } } @@ -136,11 +139,11 @@ namespace mapnik { namespace vector { for (int j = 0; j < layer.features_size(); j++) { mapnik::vector::tile_feature const& feature = layer.features(j); - mapnik::eGeomType g_type = static_cast<mapnik::eGeomType>(feature.type()); - mapnik::geometry_type geom(g_type); int cmd = -1; const int cmd_bits = 3; unsigned length = 0; + bool first = true; + mapnik::box2d<double> box; int32_t x = 0, y = 0; for (int k = 0; k < feature.geometry_size();) { @@ -149,10 +152,8 @@ namespace mapnik { namespace vector { 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++); @@ -164,21 +165,31 @@ namespace mapnik { namespace vector { if ((x > 0 && x < static_cast<int>(side)) && (y > 0 && y < static_cast<int>(side))) { return false; } - geom.push_vertex(x, y, static_cast<mapnik::CommandType>(cmd)); + if (first) + { + box.init(x,y,x,y); + first = false; + } + else + { + box.expand_to_include(x,y); + } } else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1))) { - geom.push_vertex(0, 0, mapnik::SEG_CLOSE); + // pass } else { - throw std::runtime_error("Unknown command type"); + 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. - mapnik::box2d<double> box = geom.envelope(); double geom_area = box.width() * box.height(); if (geom_area < (extent_area - 32) ) { diff --git a/test/encoding_util.hpp b/test/encoding_util.hpp new file mode 100644 index 0000000..ad69bda --- /dev/null +++ b/test/encoding_util.hpp @@ -0,0 +1,86 @@ +#include <mapnik/vertex.hpp> +#include <mapnik/geometry.hpp> +#include "vector_tile_geometry_encoder.hpp" + +void decode_geometry(mapnik::vector::tile_feature const& f, + mapnik::geometry_type & geom, + double & x, + double & y, + double scale) +{ + int cmd = -1; + const int cmd_bits = 3; + unsigned length = 0; + for (int k = 0; k < f.geometry_size();) + { + if (!length) { + unsigned cmd_length = f.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 = f.geometry(k++); + int32_t dy = f.geometry(k++); + dx = ((dx >> 1) ^ (-(dx & 1))); + dy = ((dy >> 1) ^ (-(dy & 1))); + x += (static_cast<double>(dx) / scale); + y += (static_cast<double>(dy) / scale); + geom.push_vertex(x, y, static_cast<mapnik::CommandType>(cmd)); + } + else if (cmd == (mapnik::SEG_CLOSE & ((1 << cmd_bits) - 1))) + { + geom.push_vertex(0, 0, mapnik::SEG_CLOSE); + } + else + { + std::stringstream msg; + msg << "Unknown command type (decode_geometry): " + << cmd; + throw std::runtime_error(msg.str()); + } + } + } +} + +template <typename T> +std::string show_path(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"; + } + return s.str(); +} + +std::string compare(mapnik::geometry_type const & g, + unsigned tolerance=0, + unsigned path_multiplier=1) +{ + using namespace mapnik::vector; + // encode + tile_feature feature; + int32_t x = 0; + int32_t y = 0; + encode_geometry(g,(tile_GeomType)g.type(),feature,x,y,tolerance,path_multiplier); + // decode + mapnik::geometry_type g2(MAPNIK_POLYGON); + double x0 = 0; + double y0 = 0; + decode_geometry(feature,g2,x0,y0,path_multiplier); + return show_path(g2); +} diff --git a/test/geometry_encoding.cpp b/test/geometry_encoding.cpp new file mode 100644 index 0000000..1533e49 --- /dev/null +++ b/test/geometry_encoding.cpp @@ -0,0 +1,338 @@ +// https://github.com/philsquared/Catch/wiki/Supplying-your-own-main() +// http://www.levelofindirection.com/journal/2013/6/28/catch-10.html +#define CATCH_CONFIG_RUNNER +#include "catch.hpp" + +#include "mapnik3x_compatibility.hpp" +#include "encoding_util.hpp" + +// https://github.com/mapbox/mapnik-vector-tile/issues/36 + +TEST_CASE( "test 1", "should round trip without changes" ) { + mapnik::geometry_type g(MAPNIK_POLYGON); + g.move_to(0,0); + g.line_to(1,1); + g.line_to(100,100); + g.close_path(); + 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( "test 2", "should drop coincident line_to moves" ) { + mapnik::geometry_type g(MAPNIK_LINESTRING); + g.move_to(0,0); + g.line_to(3,3); + g.line_to(3,3); + g.line_to(3,3); + g.line_to(3,3); + g.line_to(4,4); + std::string expected( + "move_to(0,0)\n" + "line_to(3,3)\n" + "line_to(4,4)\n" + ); + CHECK(compare(g,1) == expected); +} + +TEST_CASE( "test 2b", "should drop vertices" ) { + mapnik::geometry_type g(MAPNIK_LINESTRING); + g.move_to(0,0); + g.line_to(0,0); + g.line_to(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,1) == expected); +} + +TEST_CASE( "test 3", "should not drop first move_to or last vertex in line" ) { + mapnik::geometry_type g(MAPNIK_LINESTRING); + g.move_to(0,0); + g.line_to(1,1); + g.move_to(0,0); + g.line_to(1,1); + std::string expected( + "move_to(0,0)\n" + "line_to(1,1)\n" + "move_to(0,0)\n" + "line_to(1,1)\n" + ); + CHECK(compare(g,1000) == expected); +} + +TEST_CASE( "test 4", "should not drop first move_to or last vertex in polygon" ) { + mapnik::geometry_type g(MAPNIK_POLYGON); + g.move_to(0,0); + g.line_to(1,1); + g.move_to(0,0); + g.line_to(1,1); + g.close_path(); + 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_type g(MAPNIK_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_type g(MAPNIK_LINESTRING); + 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_type g(MAPNIK_LINESTRING); + 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_type g(MAPNIK_LINESTRING); + 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_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_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_type g(MAPNIK_LINESTRING); + 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_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_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_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_feature feature0; + int32_t x = 0; + int32_t y = 0; + unsigned path_multiplier = 1; + unsigned tolerance = 10000; + mapnik::geometry_type g0(MAPNIK_POLYGON); + g0.move_to(0,0); + g0.line_to(-10,-10); + g0.line_to(-20,-20); + g0.close_path(); + encode_geometry(g0,(tile_GeomType)g0.type(),feature0,x,y,tolerance,path_multiplier); + CHECK(x == -20); + CHECK(y == -20); + mapnik::geometry_type g1(MAPNIK_POLYGON); + g1.move_to(1000,1000); + g1.line_to(1010,1010); + g1.line_to(1020,1020); + g1.close_path(); + encode_geometry(g1,(tile_GeomType)g1.type(),feature0,x,y,tolerance,path_multiplier); + CHECK(x == 1020); + CHECK(y == 1020); + mapnik::geometry_type g2(MAPNIK_POLYGON); + double x0 = 0; + double y0 = 0; + decode_geometry(feature0,g2,x0,y0,path_multiplier); + std::string actual = show_path(g2); + 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_feature feature0; + int32_t x = 0; + int32_t y = 0; + unsigned path_multiplier = 1; + unsigned tolerance = 10; + mapnik::geometry_type g(MAPNIK_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(); + encode_geometry(g,(tile_GeomType)g.type(),feature0,x,y,tolerance,path_multiplier); + + mapnik::geometry_type g2(MAPNIK_POLYGON); + double x0 = 0; + double y0 = 0; + decode_geometry(feature0,g2,x0,y0,path_multiplier); + std::string actual = show_path(g2); + 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); +} + +int main (int argc, char* const argv[]) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + int result = Catch::Session().run( argc, argv ); + if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n"); + google::protobuf::ShutdownProtobufLibrary(); + return result; +} diff --git a/test/raster_style.xml b/test/raster_style.xml new file mode 100644 index 0000000..36e34e4 --- /dev/null +++ b/test/raster_style.xml @@ -0,0 +1,7 @@ +<Map> +<Style name="style"> + <Rule> + <RasterSymbolizer /> + </Rule> +</Style> +</Map> \ No newline at end of file diff --git a/test/raster_tile.cpp b/test/raster_tile.cpp new file mode 100644 index 0000000..47e3bbb --- /dev/null +++ b/test/raster_tile.cpp @@ -0,0 +1,125 @@ +// https://github.com/philsquared/Catch/wiki/Supplying-your-own-main() +#define CATCH_CONFIG_RUNNER +#include "catch.hpp" + +// test utils +#include "test_utils.hpp" +#include "test-cfg.h" + +#include "vector_tile_projection.hpp" + +// 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 <mapnik/graphics.hpp> +#include <mapnik/datasource_cache.hpp> + +TEST_CASE( "vector tile output 1", "should create vector tile with one point" ) { + mapnik::datasource_cache::instance().register_datasources(MAPNIK_PLUGINDIR); + typedef mapnik::vector::backend_pbf backend_type; + typedef mapnik::vector::processor<backend_type> renderer_type; + typedef mapnik::vector::tile tile_type; + unsigned _x=0,_y=0,_z=1; + double minx,miny,maxx,maxy; + mapnik::vector::spherical_mercator merc(512); + merc.xyz(_x,_y,_z,minx,miny,maxx,maxy); + mapnik::box2d<double> bbox; + bbox.init(minx,miny,maxx,maxy); + unsigned tile_size = 512; + tile_type tile; + backend_type backend(tile,16); + mapnik::Map map(tile_size,tile_size,"+init=epsg:3857"); + map.set_buffer_size(256); + mapnik::layer lyr("layer",map.srs()); + mapnik::parameters params; + params["type"] = "gdal"; + // created with: + // 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/natural_earth.tif"; + MAPNIK_SHARED_PTR<mapnik::datasource> ds = + mapnik::datasource_cache::instance().create(params); + lyr.set_datasource(ds); + map.MAPNIK_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,"jpeg",mapnik::SCALING_BILINEAR); + ren.apply(); + CHECK(1 == tile.layers_size()); + mapnik::vector::tile_layer const& layer = tile.layers(0); + CHECK(std::string("layer") == layer.name()); + CHECK(1 == layer.features_size()); + mapnik::vector::tile_feature const& f = layer.features(0); + CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f.id())); + CHECK(0 == f.geometry_size()); + CHECK(f.has_raster()); + std::string const& ras_buffer = f.raster(); + CHECK(!ras_buffer.empty()); + // debug + bool debug = false; + if (debug) { + std::ofstream file("out.jpeg", std::ios::out|std::ios::trunc|std::ios::binary); + file << ras_buffer; + file.close(); + } + + std::size_t expected_image_size = 45660; + int 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) { + 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; + CHECK(tile2.ParseFromString(buffer)); + CHECK(1 == tile2.layers_size()); + mapnik::vector::tile_layer const& layer2 = tile2.layers(0); + CHECK(std::string("layer") == layer2.name()); + CHECK(1 == layer2.features_size()); + mapnik::vector::tile_feature const& f2 = layer2.features(0); + CHECK(static_cast<mapnik::value_integer>(1) == static_cast<mapnik::value_integer>(f2.id())); + CHECK(0 == f2.geometry_size()); + CHECK(f2.has_raster()); + CHECK(!f2.raster().empty()); + if (!debug) { + CHECK(expected_image_size == f2.raster().size()); + } + mapnik::layer lyr2("layer",map2.srs()); + MAPNIK_SHARED_PTR<mapnik::vector::tile_datasource> ds2 = MAPNIK_MAKE_SHARED< + mapnik::vector::tile_datasource>( + layer2,_x,_y,_z,map2.width()); + ds2->set_envelope(bbox); + lyr2.set_datasource(ds2); + lyr2.add_style("style"); + map2.MAPNIK_ADD_LAYER(lyr2); + mapnik::load_map(map2,"test/raster_style.xml"); + map2.zoom_to_box(bbox); + mapnik::image_32 im(map2.width(),map2.height()); + mapnik::agg_renderer<mapnik::image_32> ren2(map2,im); + ren2.apply(); + if (debug) { + mapnik::save_to_file(im,"image.png"); + } + unsigned rgba = im.data()(128,128); + CHECK(rgba != 0); +} + +int main (int argc, char* const argv[]) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + int result = Catch::Session().run( argc, argv ); + if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n"); + google::protobuf::ShutdownProtobufLibrary(); + return result; +} diff --git a/test/style.xml b/test/style.xml new file mode 100644 index 0000000..0b403c3 --- /dev/null +++ b/test/style.xml @@ -0,0 +1,7 @@ +<Map> +<Style name="style"> + <Rule> + <MarkersSymbolizer fill="red" /> + </Rule> +</Style> +</Map> \ No newline at end of file diff --git a/test/test_utils.hpp b/test/test_utils.hpp index 55f680a..3e91716 100644 --- a/test/test_utils.hpp +++ b/test/test_utils.hpp @@ -1,10 +1,7 @@ // mapnik +#include "mapnik3x_compatibility.hpp" #include <mapnik/map.hpp> #include <mapnik/layer.hpp> -#include <mapnik/rule.hpp> -#include <mapnik/feature_type_style.hpp> -#include <mapnik/rule.hpp> -#include <mapnik/markers_symbolizer.hpp> #include <mapnik/image_util.hpp> #include <mapnik/graphics.hpp> #include <mapnik/agg_renderer.hpp> @@ -15,25 +12,25 @@ #include <mapnik/unicode.hpp> #include <mapnik/geometry.hpp> #include <mapnik/datasource.hpp> +#include <mapnik/load_map.hpp> #include <mapnik/memory_datasource.hpp> // boost -#include <boost/make_shared.hpp> -#include <boost/shared_ptr.hpp> +#include MAPNIK_SHARED_INCLUDE +#include MAPNIK_MAKE_SHARED_INCLUDE #include <string> -boost::shared_ptr<mapnik::memory_datasource> build_ds() { - mapnik::context_ptr ctx = boost::make_shared<mapnik::context_type>(); +MAPNIK_SHARED_PTR<mapnik::memory_datasource> build_ds(double x,double y) { + mapnik::context_ptr ctx = MAPNIK_MAKE_SHARED<mapnik::context_type>(); ctx->push("name"); mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1)); mapnik::transcoder tr("utf-8"); - UnicodeString ustr = tr.transcode("null island"); - feature->put("name",ustr); - mapnik::geometry_type * pt = new mapnik::geometry_type(mapnik::Point); - pt->move_to(0,0); + feature->put("name",tr.transcode("null island")); + mapnik::geometry_type * pt = new mapnik::geometry_type(MAPNIK_POINT); + pt->move_to(x,y); feature->add_geometry(pt); - boost::shared_ptr<mapnik::memory_datasource> ds = boost::make_shared<mapnik::memory_datasource>(); + MAPNIK_SHARED_PTR<mapnik::memory_datasource> ds = MAPNIK_MAKE_SHARED<mapnik::memory_datasource>(); ds->push(feature); return ds; } diff --git a/test/vector_tile.cpp b/test/vector_tile.cpp index 8b4d8a6..4ea681e 100644 --- a/test/vector_tile.cpp +++ b/test/vector_tile.cpp @@ -7,21 +7,22 @@ #include "vector_tile_projection.hpp" -const unsigned x=0,y=0,z=0; +const unsigned _x=0,_y=0,_z=0; const unsigned tile_size = 256; mapnik::box2d<double> bbox; // vector output api #include "vector_tile_processor.hpp" #include "vector_tile_backend_pbf.hpp" +#include "vector_tile_util.hpp" + +// vector input api +#include "vector_tile_datasource.hpp" TEST_CASE( "vector tile projection 1", "should support z/x/y to bbox conversion at 0/0/0" ) { mapnik::vector::spherical_mercator merc(256); - int x = 0; - int y = 0; - int z = 0; double minx,miny,maxx,maxy; - merc.xyz(x,y,z,minx,miny,maxx,maxy); + merc.xyz(_x,_y,_z,minx,miny,maxx,maxy); mapnik::box2d<double> map_extent(minx,miny,maxx,maxy); mapnik::box2d<double> e(-20037508.342789,-20037508.342789,20037508.342789,20037508.342789); double epsilon = 0.000001; @@ -47,7 +48,7 @@ TEST_CASE( "vector tile projection 2", "should support z/x/y to bbox conversion CHECK(std::fabs(map_extent.maxy() - e.maxy()) < epsilon); } -TEST_CASE( "vector tile output", "should create vector tile with one point" ) { +TEST_CASE( "vector tile output 1", "should create vector tile with one point" ) { typedef mapnik::vector::backend_pbf backend_type; typedef mapnik::vector::processor<backend_type> renderer_type; typedef mapnik::vector::tile tile_type; @@ -55,8 +56,8 @@ TEST_CASE( "vector tile output", "should create vector tile with one point" ) { backend_type backend(tile,16); mapnik::Map map(tile_size,tile_size); mapnik::layer lyr("layer"); - lyr.set_datasource(build_ds()); - map.addLayer(lyr); + lyr.set_datasource(build_ds(0,0)); + map.MAPNIK_ADD_LAYER(lyr); mapnik::request m_req(tile_size,tile_size,bbox); renderer_type ren(backend,map,m_req); ren.apply(); @@ -76,9 +77,67 @@ TEST_CASE( "vector tile output", "should create vector tile with one point" ) { CHECK(52 == buffer.size()); } -// vector input api -#include "vector_tile_datasource.hpp" -#include "vector_tile_backend_pbf.hpp" +TEST_CASE( "vector tile output 2", "adding empty layers should result in empty tile" ) { + typedef mapnik::vector::backend_pbf backend_type; + typedef mapnik::vector::processor<backend_type> renderer_type; + typedef mapnik::vector::tile tile_type; + tile_type tile; + backend_type backend(tile,16); + mapnik::Map map(tile_size,tile_size); + map.MAPNIK_ADD_LAYER(mapnik::layer("layer")); + map.zoom_to_box(bbox); + mapnik::request m_req(tile_size,tile_size,bbox); + renderer_type ren(backend,map,m_req); + ren.apply(); + CHECK(0 == tile.layers_size()); +} + +TEST_CASE( "vector tile output 3", "adding layers with geometries outside rendering extent should not add layer" ) { + typedef mapnik::vector::backend_pbf backend_type; + typedef mapnik::vector::processor<backend_type> renderer_type; + typedef mapnik::vector::tile tile_type; + tile_type tile; + backend_type backend(tile,16); + mapnik::Map map(tile_size,tile_size); + mapnik::layer lyr("layer"); + mapnik::context_ptr ctx = MAPNIK_MAKE_SHARED<mapnik::context_type>(); + mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx,1)); + MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_LINESTRING)); + g->move_to(0,0); + g->line_to(1,1); + feature->add_geometry(g.release()); + MAPNIK_SHARED_PTR<mapnik::memory_datasource> ds = MAPNIK_MAKE_SHARED<mapnik::memory_datasource>(); + ds->push(feature); + lyr.set_datasource(ds); + map.MAPNIK_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(1 == tile.layers_size()); +} + +TEST_CASE( "vector tile output 4", "adding layers with degenerate geometries should not add layer" ) { + typedef mapnik::vector::backend_pbf backend_type; + typedef mapnik::vector::processor<backend_type> renderer_type; + typedef mapnik::vector::tile tile_type; + tile_type tile; + backend_type backend(tile,16); + mapnik::Map map(tile_size,tile_size); + mapnik::layer lyr("layer"); + // create a datasource with a feature outside the map + MAPNIK_SHARED_PTR<mapnik::memory_datasource> ds = 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.MAPNIK_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(0 == tile.layers_size()); +} TEST_CASE( "vector tile input", "should be able to parse message and render point" ) { typedef mapnik::vector::backend_pbf backend_type; @@ -88,8 +147,8 @@ TEST_CASE( "vector tile input", "should be able to parse message and render poin backend_type backend(tile,16); mapnik::Map map(tile_size,tile_size); mapnik::layer lyr("layer"); - lyr.set_datasource(build_ds()); - map.addLayer(lyr); + lyr.set_datasource(build_ds(0,0)); + map.MAPNIK_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); @@ -106,9 +165,9 @@ TEST_CASE( "vector tile input", "should be able to parse message and render poin mapnik::vector::tile_layer const& layer2 = tile2.layers(0); CHECK(std::string("layer") == layer2.name()); mapnik::layer lyr2("layer"); - boost::shared_ptr<mapnik::vector::tile_datasource> ds = boost::make_shared< + MAPNIK_SHARED_PTR<mapnik::vector::tile_datasource> ds = MAPNIK_MAKE_SHARED< mapnik::vector::tile_datasource>( - layer2,x,y,z,map2.width()); + layer2,_x,_y,_z,map2.width()); ds->set_envelope(bbox); mapnik::layer_descriptor lay_desc = ds->get_descriptor(); std::vector<std::string> expected_names; @@ -120,22 +179,14 @@ TEST_CASE( "vector tile input", "should be able to parse message and render poin } CHECK(names == expected_names); lyr2.set_datasource(ds); - lyr2.add_style("style"); - map2.addLayer(lyr2); - mapnik::feature_type_style s; - mapnik::rule r; - mapnik::color red(255,0,0); - mapnik::markers_symbolizer sym; - sym.set_fill(red); - r.append(sym); - s.add_rule(r); - map2.insert_style("style",s); + map2.MAPNIK_ADD_LAYER(lyr2); + mapnik::load_map(map2,"test/style.xml"); map2.zoom_to_box(bbox); mapnik::image_32 im(map2.width(),map2.height()); mapnik::agg_renderer<mapnik::image_32> ren2(map2,im); ren2.apply(); unsigned rgba = im.data()(128,128); - CHECK(red.rgba() == rgba); + CHECK(0 == rgba); //mapnik::save_to_file(im,"test.png"); } @@ -147,8 +198,8 @@ TEST_CASE( "vector tile datasource", "should filter features outside extent" ) { backend_type backend(tile,16); mapnik::Map map(tile_size,tile_size); mapnik::layer lyr("layer"); - lyr.set_datasource(build_ds()); - map.addLayer(lyr); + lyr.set_datasource(build_ds(0,0)); + map.MAPNIK_ADD_LAYER(lyr); mapnik::request m_req(tile_size,tile_size,bbox); renderer_type ren(backend,map,m_req); ren.apply(); @@ -163,7 +214,7 @@ TEST_CASE( "vector tile datasource", "should filter features outside extent" ) { CHECK(4096 == f.geometry(1)); CHECK(4096 == f.geometry(2)); // now actually start the meat of the test - mapnik::vector::tile_datasource ds(layer,x,y,z,tile_size); + mapnik::vector::tile_datasource ds(layer,_x,_y,_z,tile_size); mapnik::featureset_ptr fs; // ensure we can query single feature @@ -211,13 +262,159 @@ TEST_CASE( "vector tile datasource", "should filter features outside extent" ) { CHECK(f_ptr->context()->size() == 1); } +// NOTE: encoding a 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 in error and its an optimization in the +// case where you know that lines do not need to be labeled in custom ways. +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 tht 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 + mapnik::vector::tile tile; + mapnik::vector::backend_pbf backend(tile,path_multiplier); + backend.start_tile_layer("layer"); + mapnik::feature_ptr feature(mapnik::feature_factory::create(MAPNIK_MAKE_SHARED<mapnik::context_type>(),1)); + backend.start_tile_feature(*feature); + MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_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->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.add_path(*g, tolerance, g->type()); + backend.stop_tile_feature(); + backend.stop_tile_layer(); + // done encoding single feature/geometry + CHECK(1 == tile.layers_size()); + mapnik::vector::tile_layer const& layer = tile.layers(0); + CHECK(1 == layer.features_size()); + mapnik::vector::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 +} + +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 + mapnik::vector::tile tile; + mapnik::vector::backend_pbf backend(tile,path_multiplier); + backend.start_tile_layer("layer"); + mapnik::feature_ptr feature(mapnik::feature_factory::create(MAPNIK_MAKE_SHARED<mapnik::context_type>(),1)); + backend.start_tile_feature(*feature); + MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_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 + backend.add_path(*g, tolerance, g->type()); + backend.stop_tile_feature(); + backend.stop_tile_layer(); + // done encoding single feature/geometry + CHECK(1 == tile.layers_size()); + mapnik::vector::tile_layer const& layer = tile.layers(0); + CHECK(1 == layer.features_size()); + mapnik::vector::tile_feature const& f = layer.features(0); + // sequence of 10 geometries given tolerance of 2 + CHECK(8 == 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(18 == line_value); + unsigned line_cmd = line_value & ((1 << 3) - 1); + CHECK(2 == line_cmd); + unsigned line_length = line_value >> 3; + CHECK(2 == 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 = (2 << 1) ^ (2 >> 31); + 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(1998 == f.geometry(6)); + CHECK(1998 == f.geometry(7)); +} + +// testcase for avoiding error in 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; + unsigned tolerance = 5; + mapnik::vector::tile tile; + mapnik::vector::backend_pbf backend(tile,path_multiplier); + backend.start_tile_layer("layer"); + mapnik::feature_ptr feature(mapnik::feature_factory::create(MAPNIK_MAKE_SHARED<mapnik::context_type>(),1)); + backend.start_tile_feature(*feature); + MAPNIK_UNIQUE_PTR<mapnik::geometry_type> g(new mapnik::geometry_type(MAPNIK_POLYGON)); + g->move_to(168.267850,-24.576888); + g->line_to(167.982618,-24.697145); + g->line_to(168.114561,-24.783548); + g->line_to(168.267850,-24.576888); + g->line_to(168.267850,-24.576888); + g->close_path(); + //g->push_vertex(256.000000,-0.00000, mapnik::SEG_CLOSE); + // todo - why does shape_io result in on-zero close path x,y? + backend.add_path(*g, tolerance, g->type()); + backend.stop_tile_feature(); + backend.stop_tile_layer(); + std::string key("test"); + is_solid_extent(tile,key); // should not throw! + CHECK(1 == tile.layers_size()); + mapnik::vector::tile_layer const& layer = tile.layers(0); + CHECK(1 == layer.features_size()); + mapnik::vector::tile_feature const& f = layer.features(0); + CHECK(7 == f.geometry_size()); +} + int main (int argc, char* const argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; // set up bbox double minx,miny,maxx,maxy; mapnik::vector::spherical_mercator merc(256); - merc.xyz(x,y,z,minx,miny,maxx,maxy); + merc.xyz(_x,_y,_z,minx,miny,maxx,maxy); bbox.init(minx,miny,maxx,maxy); int result = Catch::Session().run( argc, argv ); if (!result) printf("\x1b[1;32m ✓ \x1b[0m\n"); -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mapnik-vector-tile.git _______________________________________________ Pkg-grass-devel mailing list Pkg-grass-devel@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel