PMTile input plugin (WIP)

This commit is contained in:
Artem Pavlenko
2025-03-27 10:24:19 +00:00
parent c2730d825b
commit 0b702483b7
16 changed files with 132 additions and 1406 deletions

3
.gitmodules vendored
View File

@ -21,3 +21,6 @@
[submodule "deps/mapbox/polylabel"]
path = deps/mapbox/polylabel
url = https://github.com/mapbox/polylabel.git
[submodule "deps/mapbox/mapnik-vector-tile"]
path = deps/mapbox/mapnik-vector-tile
url = https://github.com/mapnik/mapnik-vector-tile.git

View File

@ -24,20 +24,26 @@ Import ('env')
PLUGIN_NAME = 'pmtiles'
MAPNIK_VECTOR_TILE = '../../../deps/mapbox/mapnik-vector-tile/src'
plugin_env = plugin_base.Clone()
plugin_env.Prepend(CPPPATH = '#deps/mapbox/mapnik-vector-tile/src')
plugin_env.Append(CPPDEFINES = 'MAPNIK_VECTOR_TILE_LIBRARY=1')
plugin_sources = Split(
"""
%(PLUGIN_NAME)s_datasource.cpp
%(PLUGIN_NAME)s_featureset.cpp
%(MAPNIK_VECTOR_TILE)s/vector_tile_compression.cpp
%(MAPNIK_VECTOR_TILE)s/vector_tile_geometry_decoder.cpp
mvt_io.cpp
vector_tile_compression.cpp
vector_tile_geometry_decoder.cpp
""" % locals()
)
# Link Library to Dependencies
libraries = [ 'sqlite3', 'boost_iostreams', 'boost_json' ]
libraries = [ 'sqlite3', 'boost_iostreams', 'boost_json' ] #FIXME!
libraries = [ 'boost_json' ]
linkflags = []
if env['SQLITE_LINKFLAGS']:

View File

@ -160,7 +160,7 @@ mapnik::feature_ptr mvt_layer::next_feature()
}
mapnik::vector_tile_impl::GeometryPBF geoms(geom_itr);
mapnik::geometry::geometry<double> geom =
mapnik::vector_tile_impl::decode_geometry<double>(geoms, geometry_type,
mapnik::vector_tile_impl::decode_geometry<double>(geoms, (std::int32_t)geometry_type,
1, tile_x_, tile_y_, scale_, -1.0 * scale_);
if (geom.is<mapnik::geometry::geometry_empty>())
{
@ -297,4 +297,3 @@ mvt_io::mvt_io(std::string&& data, mapnik::context_ptr const& ctx, const uint32_
}
}
}

View File

@ -1,4 +1,3 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
@ -23,6 +22,7 @@
#include "pmtiles_datasource.hpp"
#include "pmtiles_featureset.hpp"
#include "pmtiles_file.hpp"
#include "vector_tile_projection.hpp"
#include <mapnik/geom_util.hpp>
#include <mapnik/util/fs.hpp>
@ -30,6 +30,7 @@
#include <mapnik/datasource_plugin.hpp>
#include <string>
#include <algorithm>
#include <thread>
DATASOURCE_PLUGIN_IMPL(pmtiles_datasource_plugin, pmtiles_datasource);
DATASOURCE_PLUGIN_EXPORT(pmtiles_datasource_plugin);
@ -212,7 +213,7 @@ namespace {
{
for (std::int64_t zoom = 0; zoom < 19; ++zoom)
{
if (scale > scales[zoom]) return minzoom;
if (scale > scales[zoom]) return std::min(zoom, minzoom);
else if (scale < scales[zoom] && scale > scales[zoom + 1])
{
return std::min(zoom, maxzoom);
@ -221,12 +222,23 @@ namespace {
return maxzoom;
}
}
std::unordered_map<std::string, std::string> & pmtiles_datasource::tile_cache()
{
static thread_local std::unordered_map<std::string, std::string> vector_tile_cache;
return vector_tile_cache;
}
mapnik::featureset_ptr pmtiles_datasource::features(mapnik::query const& q) const
{
#ifdef MAPNIK_STATS
mapnik::progress_timer __stats__(std::clog, "pmtiles_datasource::features");
#endif
auto & vector_tile_cache = tile_cache();
if (vector_tile_cache.size() > 32) vector_tile_cache.clear();
std::cerr << "Address of Datasource:" << std::addressof(*this) << " cache size:" << vector_tile_cache.size() << std::endl;
auto datasource_hash = std::hash<std::string>{}(database_path_);
std::cerr << "vector_tile_cache address:" << std::addressof(vector_tile_cache) << std::endl;
mapnik::box2d<double> const& box = q.get_bbox();
std::cerr << "scale_denominator:" << q.scale_denominator() << std::endl;
auto zoom = scale_to_zoom(q.scale_denominator(), minzoom_, maxzoom_);
@ -237,7 +249,7 @@ mapnik::featureset_ptr pmtiles_datasource::features(mapnik::query const& q) cons
{
throw mapnik::datasource_exception("Failed to create memory mapping for " + database_path_);
}
return mapnik::featureset_ptr(new pmtiles_featureset(file_ptr_, context, zoom, box, layer_));
return mapnik::featureset_ptr(new pmtiles_featureset(file_ptr_, context, zoom, box, layer_, vector_tile_cache, datasource_hash));
}
mapnik::featureset_ptr pmtiles_datasource::features_at_point(mapnik::coord2d const& pt, double tol) const
@ -248,5 +260,6 @@ mapnik::featureset_ptr pmtiles_datasource::features_at_point(mapnik::coord2d con
mapnik::filter_at_point filter(pt, tol);
mapnik::context_ptr context = get_context_with_attributes();
return mapnik::featureset_ptr(new pmtiles_featureset(file_ptr_, context, zoom_, filter.box_, layer_));
auto datasource_hash = std::hash<std::string>{}(database_path_);
return mapnik::featureset_ptr(new pmtiles_featureset(file_ptr_, context, zoom_, filter.box_, layer_, tile_cache(), datasource_hash));
}

View File

@ -1,4 +1,3 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
@ -25,8 +24,6 @@
#define PMTILES_DATASOURCE_HPP_
#include <mapnik/datasource.hpp>
//#include <mapnik/featureset.hpp>
#include "sqlite_connection.hpp"
#include <mapnik/params.hpp>
#include <mapnik/query.hpp>
#include <mapnik/feature.hpp>
@ -37,10 +34,18 @@
#include <memory>
#include <string>
#include "pmtiles_file.hpp"
#include <tuple>
#include <unordered_map>
DATASOURCE_PLUGIN_DEF(pmtiles_datasource_plugin, pmtiles);
namespace mapnik {
using zxy_type = std::tuple<std::uint8_t, std::uint32_t, std::uint32_t>;
class pmtiles_file; //fwd decl
}
class pmtiles_datasource : public mapnik::datasource
{
public:
@ -60,6 +65,7 @@ private:
mapnik::context_ptr get_query_context(mapnik::query const& q) const;
std::string database_path_;
std::shared_ptr<mapnik::pmtiles_file> file_ptr_;
static std::unordered_map<std::string, std::string> & tile_cache();
public:
mapnik::box2d<double> extent_;
std::int64_t minzoom_ = 0;

View File

@ -1,9 +1,8 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/*****************************************************************************
*
* This file is part of Mapnik Vector Tile Plugin
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2023 Geofabrik GmbH
* Copyright (C) 2025 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -21,8 +20,10 @@
*
*****************************************************************************/
#include "pmtiles_featureset.hpp"
#include "vector_tile_compression.hpp"
#include "pmtiles_file.hpp"
#include <boost/format.hpp>
#include <math.h>
#if 0
@ -35,13 +36,18 @@
pmtiles_featureset::pmtiles_featureset(std::shared_ptr<mapnik::pmtiles_file> file_ptr,
mapnik::context_ptr const& ctx, const int zoom,
mapnik::box2d<double> const& extent, std::string const& layer) :
mapnik::box2d<double> const& extent, std::string const& layer,
std::unordered_map<std::string, std::string> & vector_tile_cache,
std::size_t datasource_hash)
:
file_ptr_(file_ptr),
context_(ctx),
zoom_(zoom),
extent_(extent),
layer_(layer),
vector_tile_(nullptr)
vector_tile_(nullptr),
vector_tile_cache_(vector_tile_cache),
datasource_hash_(datasource_hash)
{
int tile_count = 1 << zoom;
constexpr double width = 2.0 * 6378137 * M_PI;
@ -104,33 +110,46 @@ bool pmtiles_featureset::next_tile()
bool pmtiles_featureset::open_tile()
{
auto tile = file_ptr_->get_tile(zoom_, x_, y_);
std::cerr << layer_ << ":" << zoom_ << ":" << x_ << ":" << y_ << std::endl;
auto datasource_key = (boost::format("%1%-%2%-%3%-%4%") % datasource_hash_ % zoom_ % x_ % y_).str();
auto itr = vector_tile_cache_.find(datasource_key);
if (itr == vector_tile_cache_.end())
{
#if 0
using namespace boost::iostreams;
namespace io = boost::iostreams;
filtering_istream in;
if (mapnik::vector_tile_impl::is_gzip_compressed(file_ptr_->data() + tile.first, tile.second))
{
in.push(gzip_decompressor());
}
else if (mapnik::vector_tile_impl::is_zlib_compressed(file_ptr_->data() + tile.first, tile.second))
{
in.push(zlib_decompressor());
}
in.push(array_source(file_ptr_->data() + tile.first, tile.second));
std::string buffer;
io::copy(in, io::back_inserter(buffer));
vector_tile_.reset(new mvt_io(std::move(buffer), context_, x_, y_, zoom_, layer_));
using namespace boost::iostreams;
namespace io = boost::iostreams;
filtering_istream in;
if (mapnik::vector_tile_impl::is_gzip_compressed(file_ptr_->data() + tile.first, tile.second))
{
in.push(gzip_decompressor());
}
else if (mapnik::vector_tile_impl::is_zlib_compressed(file_ptr_->data() + tile.first, tile.second))
{
in.push(zlib_decompressor());
}
in.push(array_source(file_ptr_->data() + tile.first, tile.second));
std::string buffer;
io::copy(in, io::back_inserter(buffer));
vector_tile_.reset(new mvt_io(std::move(buffer), context_, x_, y_, zoom_, layer_));
#else
if (mapnik::vector_tile_impl::is_gzip_compressed(file_ptr_->data() + tile.first, tile.second) ||
mapnik::vector_tile_impl::is_zlib_compressed(file_ptr_->data() + tile.first, tile.second))
{
std::string decompressed;
mapnik::vector_tile_impl::zlib_decompress(file_ptr_->data() + tile.first, tile.second, decompressed);
vector_tile_.reset(new mvt_io(std::move(decompressed), context_, x_, y_, zoom_, layer_));
} else {
vector_tile_.reset(new mvt_io(std::string(file_ptr_->data() + tile.first, tile.second), context_, x_, y_, zoom_, layer_));
}
std::cerr << "\e[41m" << layer_ << " - " << datasource_key << "\e[0m" << std::endl;
if (mapnik::vector_tile_impl::is_gzip_compressed(file_ptr_->data() + tile.first, tile.second) ||
mapnik::vector_tile_impl::is_zlib_compressed(file_ptr_->data() + tile.first, tile.second))
{
std::string decompressed;
mapnik::vector_tile_impl::zlib_decompress(file_ptr_->data() + tile.first, tile.second, decompressed);
vector_tile_cache_.emplace(datasource_key, decompressed);
vector_tile_.reset(new mvt_io(std::move(decompressed), context_, x_, y_, zoom_, layer_));
} else {
vector_tile_.reset(new mvt_io(std::string(file_ptr_->data() + tile.first, tile.second), context_, x_, y_, zoom_, layer_));
}
#endif
}
else
{
std::cerr << "\e[42m" << layer_ << " - " << datasource_key << "\e[0m" << std::endl;
std::string tile = itr->second;
vector_tile_.reset(new mvt_io(std::move(tile), context_, x_, y_, zoom_, layer_));
}
return true;
}

View File

@ -1,9 +1,8 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/*****************************************************************************
*
* This file is part of Mapnik Vector Tile Plugin
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2023 Geofabrik GmbH
* Copyright (C) 2025 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -21,6 +20,7 @@
*
*****************************************************************************/
#ifndef PMTILES_FEATURESET_HPP_
#define PMTILES_FEATURESET_HPP_
@ -28,19 +28,24 @@
// mapnik
#include <mapnik/feature.hpp>
#include <mapnik/datasource.hpp>
// sqlite
#include "sqlite_connection.hpp"
#include "mvt_io.hpp"
#include "pmtiles_file.hpp"
namespace mapnik {
class pmtiles_file; //fwd decl
}
class pmtiles_featureset : public mapnik::Featureset
{
public:
pmtiles_featureset(std::shared_ptr<mapnik::pmtiles_file> file_ptr,
mapnik::context_ptr const& ctx,
const int zoom,
int const zoom,
mapnik::box2d<double> const& extent,
const std::string & layer);
std::string const& layer,
std::unordered_map<std::string,
std::string> & vector_tile_cache,
std::size_t datasource_hash);
virtual ~pmtiles_featureset();
mapnik::feature_ptr next();
@ -51,8 +56,9 @@ private:
mapnik::context_ptr context_;
int zoom_;
mapnik::box2d<double> const& extent_;
const std::string& layer_;
std::string const& layer_;
std::unique_ptr<mvt_io> vector_tile_;
std::unordered_map<std::string, std::string> & vector_tile_cache_;
int xmin_;
int xmax_;
int ymin_;
@ -61,6 +67,7 @@ private:
int x_ = 0;
/// y index of the currently accessed tile
int y_ = 0;
std::size_t datasource_hash_;
bool next_tile();
bool open_tile();
};

View File

@ -19,8 +19,9 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef PMTILES_FILE_HPP
#define PMTILES_FILE_HPP
#ifndef MAPNIK_PMTILES_FILE_HPP
#define MAPNIK_PMTILES_FILE_HPP
#include <mapnik/global.hpp>
#define MAPNIK_MEMORY_MAPPED_FILE
@ -33,9 +34,9 @@
#include <fstream>
// boost
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/gzip.hpp>
//#include <boost/iostreams/filtering_stream.hpp>
//#include <boost/iostreams/copy.hpp>
//#include <boost/iostreams/filter/gzip.hpp>
#include <boost/json.hpp>
//#include <boost/format.hpp>
#include "vector_tile_compression.hpp"
@ -348,8 +349,7 @@ class pmtiles_file : public mapnik::util::mapped_memory_file
char const* data_;
bool check_valid() const
{
return (std::string(data_, data_ + 7) == "PMTiles" &&
data_[7] == 0x3);
return (std::string(data_, data_ + 7) == "PMTiles");
}
int version() const { return data_[7]; }
std::uint64_t root_dir_offset() const
@ -434,9 +434,7 @@ public:
: mapped_memory_file(file_name)
//: file_(file_name.c_str(), boost::interprocess::read_only),
//region_(file_, boost::interprocess::read_only)
{
}
{}
~pmtiles_file() {}
@ -445,10 +443,9 @@ public:
{
header h(data());
if (!h.check_valid())
std::cerr << "FAIL" << std::endl;
std::cerr << "PMTiles: invalid magic number" << std::endl;
else
{
std::cerr << "Gotcha!" << std::endl;
std::cerr << "Version:" << h.version() << std::endl;
std::cerr << "Min zoom:" << h.min_zoom() << std::endl;
std::cerr << "Max zoom:" << h.max_zoom() << std::endl;
@ -472,16 +469,25 @@ public:
header h(data());
auto metadata_offset = h.metadata_offset();
auto metadata_length = h.metadata_length();
using namespace boost::iostreams;
namespace io = boost::iostreams;
//using namespace boost::iostreams;
//namespace io = boost::iostreams;
std::string metadata;
filtering_istream in;
//filtering_istream in;
//if (h.internal_compression() == compression_type::GZIP)
//{
// in.push(gzip_decompressor());
//}
//in.push(array_source(data() + metadata_offset, metadata_length));
//io::copy(in, io::back_inserter(metadata));
if (h.internal_compression() == compression_type::GZIP)
{
in.push(gzip_decompressor());
mapnik::vector_tile_impl::zlib_decompress(data() + metadata_offset, metadata_length, metadata);
}
else
{
metadata = {data() + metadata_offset, metadata_length};
}
in.push(array_source(data() + metadata_offset, metadata_length));
io::copy(in, io::back_inserter(metadata));
boost::json::value json_value;
try
{
@ -554,4 +560,4 @@ public:
};
}
#endif //PMTILES_FILE_HPP
#endif //MAPNIK_PMTILES_FILE_HPP

View File

@ -1,69 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
/**
* Copyright (c) MapBox
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* - Neither the name "MapBox" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "vector_tile_compression.hpp"
// zlib
#include <zlib.h>
// std
#include <stdexcept>
// decodes both zlib and gzip
// http://stackoverflow.com/a/1838702/2333354
void mapnik::vector_tile_impl::zlib_decompress(const char * data, std::size_t size, std::string & output)
{
z_stream inflate_s;
inflate_s.zalloc = Z_NULL;
inflate_s.zfree = Z_NULL;
inflate_s.opaque = Z_NULL;
inflate_s.avail_in = 0;
inflate_s.next_in = Z_NULL;
inflateInit2(&inflate_s, 32 + 15);
inflate_s.next_in = (Bytef *)data;
inflate_s.avail_in = size;
size_t length = 0;
do {
output.resize(length + 2 * size);
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)
{
std::string error_msg = inflate_s.msg;
inflateEnd(&inflate_s);
throw std::runtime_error(error_msg);
}
length += (2 * size - inflate_s.avail_out);
} while (inflate_s.avail_out == 0);
inflateEnd(&inflate_s);
output.resize(length);
}

View File

@ -1,69 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
/**
* Copyright (c) MapBox
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* - Neither the name "MapBox" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef VECTOR_TILE_COMPRESSION_HPP_
#define VECTOR_TILE_COMPRESSION_HPP_
#include <cstdint>
#include <string>
// zlib
#include <zlib.h>
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 ||
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)
{
return size > 2 && static_cast<uint8_t>(data[0]) == 0x1F && static_cast<uint8_t>(data[1]) == 0x8B;
}
// decodes both zlib and gzip
// http://stackoverflow.com/a/1838702/2333354
void zlib_decompress(const char * data, std::size_t size, std::string & output);
} // end ns vector_tile_impl
} // end ns mapnik
#endif /* VECTOR_TILE_COMPRESSION_HPP_ */

View File

@ -1,67 +0,0 @@
/**
* Copyright (c) MapBox
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* - Neither the name "MapBox" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// zlib
#include <zlib.h>
// std
#include <stdexcept>
#include "vector_tile_compression.hpp"
// decodes both zlib and gzip
// http://stackoverflow.com/a/1838702/2333354
void mapnik::vector_tile_impl::zlib_decompress(const char * data, std::size_t size, std::string & output)
{
z_stream inflate_s;
inflate_s.zalloc = Z_NULL;
inflate_s.zfree = Z_NULL;
inflate_s.opaque = Z_NULL;
inflate_s.avail_in = 0;
inflate_s.next_in = Z_NULL;
inflateInit2(&inflate_s, 32 + 15);
inflate_s.next_in = (Bytef *)data;
inflate_s.avail_in = size;
size_t length = 0;
do {
output.resize(length + 2 * size);
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)
{
std::string error_msg = inflate_s.msg;
inflateEnd(&inflate_s);
throw std::runtime_error(error_msg);
}
length += (2 * size - inflate_s.avail_out);
} while (inflate_s.avail_out == 0);
inflateEnd(&inflate_s);
output.resize(length);
}

View File

@ -1,73 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
/**
* Copyright (c) MapBox
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* - Neither the name "MapBox" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "vector_tile_geometry_decoder.hpp"
#include "vector_tile_geometry_decoder.ipp"
namespace mapnik
{
namespace vector_tile_impl
{
// decode geometry
template mapnik::geometry::geometry<double> decode_geometry<double>(GeometryPBF & paths,
mvt_message::geom_type geom_type,
unsigned version,
double tile_x,
double tile_y,
double scale_x,
double scale_y);
template mapnik::geometry::geometry<std::int64_t> decode_geometry<std::int64_t>(GeometryPBF & paths,
mvt_message::geom_type geom_type,
unsigned version,
std::int64_t tile_x,
std::int64_t tile_y,
double scale_x,
double scale_y);
template mapnik::geometry::geometry<double> decode_geometry<double>(GeometryPBF & paths,
mvt_message::geom_type geom_type,
unsigned version,
double tile_x,
double tile_y,
double scale_x,
double scale_y,
mapnik::box2d<double> const& bbox);
template mapnik::geometry::geometry<std::int64_t> decode_geometry<std::int64_t>(GeometryPBF & paths,
mvt_message::geom_type geom_type,
unsigned version,
std::int64_t tile_x,
std::int64_t tile_y,
double scale_x,
double scale_y,
mapnik::box2d<double> const& bbox);
} // end ns vector_tile_impl
} // end ns mapnik

View File

@ -1,119 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
/**
* Copyright (c) MapBox
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* - Neither the name "MapBox" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef VECTOR_TILE_GEOMETRY_DECODER_HPP_
#define VECTOR_TILE_GEOMETRY_DECODER_HPP_
//protozero
#include <protozero/pbf_reader.hpp>
//mapnik
#include <mapnik/geometry/box2d.hpp>
#include <mapnik/geometry.hpp>
#if defined(DEBUG)
#include <mapnik/debug.hpp>
#endif
#include "mvt_message.hpp"
//std
#include <algorithm>
#include <cmath>
#include <stdexcept>
namespace mapnik
{
namespace vector_tile_impl
{
// NOTE: this object is for one-time use. Once you've progressed to the end
// by calling next(), to re-iterate, you must construct a new object
class GeometryPBF
{
public:
using value_type = std::int64_t;
using iterator_type = protozero::pbf_reader::const_uint32_iterator;
using pbf_itr = protozero::iterator_range<iterator_type>;
explicit GeometryPBF(pbf_itr const& geo_iterator);
enum command : uint8_t
{
end = 0,
move_to = 1,
line_to = 2,
close = 7
};
uint32_t get_length() const
{
return length;
}
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);
private:
iterator_type geo_itr_;
iterator_type geo_end_itr_;
value_type x, y;
value_type ox, oy;
uint32_t length;
uint8_t cmd;
#if defined(DEBUG)
public:
bool already_had_error;
#endif
};
template <typename value_type>
mapnik::geometry::geometry<value_type> decode_geometry(GeometryPBF & paths,
mvt_message::geom_type geom_type,
unsigned version,
value_type tile_x,
value_type tile_y,
double scale_x,
double scale_y,
mapnik::box2d<double> const& bbox);
template <typename value_type>
mapnik::geometry::geometry<value_type> decode_geometry(GeometryPBF & paths,
mvt_message::geom_type geom_type,
unsigned version,
value_type tile_x,
value_type tile_y,
double scale_x,
double scale_y);
} // end ns vector_tile_impl
} // end ns mapnik
#endif /* VECTOR_TILE_GEOMETRY_DECODER_HPP_ */

View File

@ -1,877 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
/**
* Copyright (c) MapBox
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* - Neither the name "MapBox" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//protozero
#include <protozero/pbf_reader.hpp>
//mapnik
#include <mapnik/geometry/box2d.hpp>
#include <mapnik/geometry.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 double calculate_segment_area(value_type const x0, value_type const y0, value_type const x1, value_type const y1)
{
return (static_cast<double>(x0) * static_cast<double>(y1)) - (static_cast<double>(y0) * static_cast<double>(x1));
}
inline bool area_is_clockwise(double area)
{
return (area < 0.0);
}
template <typename value_type>
inline bool scaling_reversed_orientation(value_type const scale_x_, value_type const scale_y_)
{
return (scale_x_ * scale_y_) < 0;
}
template <typename value_type>
inline void move_cursor(value_type & x, value_type & y, std::int32_t dx, std::int32_t dy)
{
x += static_cast<value_type>(dx);
y += static_cast<value_type>(dy);
}
template <typename value_type>
inline value_type get_point_value(value_type const val,
double const scale_val,
double const tile_loc)
{
return (tile_loc + static_cast<value_type>(std::round(static_cast<double>(val) / scale_val)));
}
template <>
inline double get_point_value<double>(double const val,
double const scale_val,
double const tile_loc)
{
return tile_loc + (val / scale_val);
}
constexpr std::size_t max_reserve()
{
// Based on int64_t geometry being 16 bytes in size and
// maximum allocation size of 1 MB.
return (1024 * 1024) / 16;
}
template <typename geom_value_type>
void decode_point(mapnik::geometry::geometry<geom_value_type> & geom,
GeometryPBF & paths,
geom_value_type const tile_x,
geom_value_type const tile_y,
double const scale_x,
double const scale_y,
mapnik::box2d<double> const& bbox)
{
typename GeometryPBF::command cmd;
using pbf_value_type = GeometryPBF::value_type;
pbf_value_type x1, y1;
mapnik::geometry::multi_point<geom_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);
geom_value_type x1_ = get_point_value<geom_value_type>(x1, scale_x, tile_x);
geom_value_type y1_ = get_point_value<geom_value_type>(y1, scale_y, tile_y);
if (cmd == GeometryPBF::end)
{
geom = 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
constexpr std::size_t max_size = max_reserve();
if (paths.get_length() + 1 < max_size)
{
mp.reserve(paths.get_length() + 1);
}
else
{
mp.reserve(max_size);
}
mp.emplace_back(x1_, y1_);
break;
}
}
while ((cmd = paths.point_next(x1, y1)) != GeometryPBF::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
geom_value_type x1_ = get_point_value<geom_value_type>(x1, scale_x, tile_x);
geom_value_type y1_ = get_point_value<geom_value_type>(y1, scale_y, tile_y);
if (!bbox.intersects(x1_, y1_))
{
continue;
}
mp.emplace_back(x1_, y1_);
}
std::size_t num_points = mp.size();
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 geom_value_type>
void decode_linestring(mapnik::geometry::geometry<geom_value_type> & geom,
GeometryPBF & paths,
geom_value_type const tile_x,
geom_value_type const tile_y,
double scale_x,
double scale_y,
mapnik::box2d<double> const& bbox,
unsigned version)
{
using pbf_value_type = GeometryPBF::value_type;
typename GeometryPBF::command cmd;
pbf_value_type x0, y0;
pbf_value_type x1, y1;
geom_value_type x0_, y0_;
geom_value_type x1_, y1_;
mapnik::geometry::multi_line_string<geom_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 == GeometryPBF::end)
{
geom = mapnik::geometry::geometry_empty();
return;
}
else if (cmd != GeometryPBF::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 != GeometryPBF::line_to)
{
if (cmd == GeometryPBF::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 == GeometryPBF::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
constexpr std::size_t max_size = max_reserve();
if (paths.get_length() + 2 < max_size)
{
line.reserve(paths.get_length() + 2);
}
else
{
line.reserve(max_size);
}
// add moveto command position
x0_ = get_point_value<geom_value_type>(x0, scale_x, tile_x);
y0_ = get_point_value<geom_value_type>(y0, scale_y, tile_y);
line.emplace_back(x0_, y0_);
part_env.init(x0_, y0_, x0_, y0_);
// add first lineto
x1_ = get_point_value<geom_value_type>(x1, scale_x, tile_x);
y1_ = get_point_value<geom_value_type>(y1, scale_y, tile_y);
line.emplace_back(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)) == GeometryPBF::line_to)
{
x1_ = get_point_value<geom_value_type>(x1, scale_x, tile_x);
y1_ = get_point_value<geom_value_type>(y1, scale_y, tile_y);
line.emplace_back(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 == GeometryPBF::end)
{
break;
}
// else we are guaranteed it is a moveto
x0 = x1;
y0 = y1;
}
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
{
geom = mapnik::geometry::geometry_empty();
}
}
else if (num_lines > 1)
{
geom = std::move(multi_line);
}
}
template <typename geom_value_type>
void decode_polygon(mapnik::geometry::geometry<geom_value_type> & geom,
GeometryPBF & paths,
geom_value_type const tile_x,
geom_value_type const tile_y,
double scale_x,
double scale_y,
mapnik::box2d<double> const& bbox,
unsigned version)
{
typename GeometryPBF::command cmd;
using pbf_value_type = GeometryPBF::value_type;
pbf_value_type x0, y0;
pbf_value_type x1, y1;
pbf_value_type x2, y2;
geom_value_type x0_, y0_;
geom_value_type x1_, y1_;
geom_value_type x2_, y2_;
#if defined(DEBUG)
std::uint32_t previous_len;
#endif
double ring_area = 0.0;
bool first_ring = true;
bool first_ring_is_clockwise = false;
bool last_exterior_not_included = false;
std::vector<mapnik::geometry::linear_ring<geom_value_type> > rings;
std::vector<bool> rings_exterior;
mapnik::box2d<double> part_env;
cmd = paths.ring_next(x0, y0, false);
if (cmd == GeometryPBF::end)
{
geom = mapnik::geometry::geometry_empty();
return;
}
else if (cmd != GeometryPBF::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 != GeometryPBF::line_to)
{
if (cmd == GeometryPBF::close && version == 1)
{
// Version 1 of the specification was not clear on the command requirements for polygons
// lets just to recover from this situation.
cmd = paths.ring_next(x0, y0, false);
if (cmd == GeometryPBF::end)
{
break;
}
else if (cmd == GeometryPBF::move_to)
{
continue;
}
else if (cmd == GeometryPBF::close)
{
throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a CLOSE.");
}
else // cmd == GeometryPBF::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 != GeometryPBF::line_to)
{
if (cmd == GeometryPBF::close && version == 1)
{
// Version 1 of the specification was not clear on the command requirements for polygons
// lets just to recover from this situation.
cmd = paths.ring_next(x0, y0, false);
if (cmd == GeometryPBF::end)
{
break;
}
else if (cmd == GeometryPBF::move_to)
{
continue;
}
else if (cmd == GeometryPBF::close)
{
throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a CLOSE.");
}
else // cmd == GeometryPBF::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
constexpr std::size_t max_size = max_reserve();
if (paths.get_length() + 4 < max_size)
{
ring.reserve(paths.get_length() + 4);
}
else
{
ring.reserve(max_size);
}
// add moveto command position
x0_ = get_point_value<geom_value_type>(x0, scale_x, tile_x);
y0_ = get_point_value<geom_value_type>(y0, scale_y, tile_y);
ring.emplace_back(x0_, y0_);
part_env.init(x0_, y0_, x0_, y0_);
// add first lineto
x1_ = get_point_value<geom_value_type>(x1, scale_x, tile_x);
y1_ = get_point_value<geom_value_type>(y1, scale_y, tile_y);
ring.emplace_back(x1_, y1_);
part_env.expand_to_include(x1_, y1_);
ring_area += calculate_segment_area(x0, y0, x1, y1);
// add second lineto
x2_ = get_point_value<geom_value_type>(x2, scale_x, tile_x);
y2_ = get_point_value<geom_value_type>(y2, scale_y, tile_y);
ring.emplace_back(x2_, y2_);
part_env.expand_to_include(x2_, y2_);
ring_area += calculate_segment_area(x1, y1, x2, y2);
x1 = x2;
y1 = 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(x2, y2, true)) == GeometryPBF::line_to)
{
x2_ = get_point_value<geom_value_type>(x2, scale_x, tile_x);
y2_ = get_point_value<geom_value_type>(y2, scale_y, tile_y);
ring.emplace_back(x2_, y2_);
part_env.expand_to_include(x2_, y2_);
ring_area += calculate_segment_area(x1, y1, x2, y2);
x1 = x2;
y1 = 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
}
// Make sure we are now on a close command
if (cmd != GeometryPBF::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.emplace_back(x0_, y0_);
ring_area += calculate_segment_area(x1, y1, x0, y0);
}
if (ring.size() > 3)
{
if (first_ring)
{
first_ring_is_clockwise = area_is_clockwise(ring_area);
if (version != 1 && first_ring_is_clockwise)
{
throw std::runtime_error("Vector Tile has POLYGON with first ring clockwise. It is not valid according to v2 of VT spec.");
}
first_ring = false;
}
bool is_exterior = (first_ring_is_clockwise == area_is_clockwise(ring_area));
if ((!is_exterior && last_exterior_not_included) || !bbox.intersects(part_env))
{
// remove last linestring
if (is_exterior)
{
last_exterior_not_included = true;
}
rings.pop_back();
}
else
{
if (is_exterior)
{
last_exterior_not_included = false;
}
rings_exterior.push_back(is_exterior);
}
}
else
{
rings.pop_back();
}
ring_area = 0.0;
cmd = paths.ring_next(x0, y0, false);
if (cmd == GeometryPBF::end)
{
break;
}
else if (cmd != GeometryPBF::move_to)
{
if (cmd == GeometryPBF::close)
{
throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a CLOSE.");
}
else // cmd == GeometryPBF::line_to
{
throw std::runtime_error("Vector Tile has POLYGON type geometry where a CLOSE is followed by a LINETO.");
}
}
}
if (rings.size() == 0)
{
geom = mapnik::geometry::geometry_empty();
return;
}
bool reverse_rings = (scaling_reversed_orientation(scale_x, scale_y) != first_ring_is_clockwise);
auto rings_itr = std::make_move_iterator(rings.begin());
auto rings_end = std::make_move_iterator(rings.end());
mapnik::geometry::multi_polygon<geom_value_type> multi_poly;
for (std::size_t i = 0; rings_itr != rings_end; ++rings_itr,++i)
{
if (rings_exterior[i]) multi_poly.emplace_back();
auto & poly = multi_poly.back();
if (reverse_rings)
{
std::reverse(rings_itr->begin(), rings_itr->end());
}
poly.push_back(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
GeometryPBF::GeometryPBF(pbf_itr const& geo_iterator)
: geo_itr_(geo_iterator.begin()),
geo_end_itr_(geo_iterator.end()),
x(0),
y(0),
ox(0),
oy(0),
length(0),
cmd(move_to)
{
#if defined(DEBUG)
already_had_error = false;
#endif
}
typename GeometryPBF::command GeometryPBF::point_next(value_type & rx, value_type & ry)
{
if (length == 0)
{
if (geo_itr_ != geo_end_itr_)
{
uint32_t cmd_length = static_cast<uint32_t>(*geo_itr_++);
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_itr_++));
int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_itr_++));
detail::move_cursor(x, y, dx, dy);
rx = x;
ry = y;
return move_to;
}
typename GeometryPBF::command GeometryPBF::line_next(value_type & rx,
value_type & ry,
bool skip_lineto_zero)
{
if (length == 0)
{
if (geo_itr_ != geo_end_itr_)
{
uint32_t cmd_length = static_cast<uint32_t>(*geo_itr_++);
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_itr_++));
int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_itr_++));
detail::move_cursor(x, y, dx, dy);
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_itr_++));
int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_itr_++));
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);
rx = x;
ry = y;
return line_to;
}
typename GeometryPBF::command GeometryPBF::ring_next(value_type & rx,
value_type & ry,
bool skip_lineto_zero)
{
if (length == 0)
{
if (geo_itr_ != geo_end_itr_)
{
uint32_t cmd_length = static_cast<uint32_t>(*geo_itr_++);
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 two 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_itr_++));
int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_itr_++));
detail::move_cursor(x, y, dx, dy);
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_itr_++));
int32_t dy = protozero::decode_zigzag32(static_cast<uint32_t>(*geo_itr_++));
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);
rx = x;
ry = y;
return line_to;
}
template <typename value_type>
mapnik::geometry::geometry<value_type> decode_geometry(GeometryPBF & paths,
mvt_message::geom_type geom_type,
unsigned version,
value_type tile_x,
value_type tile_y,
double scale_x,
double scale_y,
mapnik::box2d<double> const& bbox)
{
mapnik::geometry::geometry<value_type> geom; // output geometry
switch (geom_type)
{
case mvt_message::geom_type::point:
{
detail::decode_point<value_type>(geom, paths, tile_x, tile_y, scale_x, scale_y, bbox);
break;
}
case mvt_message::geom_type::linestring:
{
detail::decode_linestring<value_type>(geom, paths, tile_x, tile_y, scale_x, scale_y, bbox, version);
break;
}
case mvt_message::geom_type::polygon:
{
detail::decode_polygon<value_type>(geom, paths, tile_x, tile_y, scale_x, scale_y, bbox, version);
break;
}
case mvt_message::geom_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 = mapnik::geometry::geometry_empty();
break;
}
}
return geom;
}
template <typename value_type>
mapnik::geometry::geometry<value_type> decode_geometry(GeometryPBF & paths,
mvt_message::geom_type geom_type,
unsigned version,
value_type tile_x,
value_type tile_y,
double scale_x,
double scale_y)
{
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<value_type>(paths, geom_type, version, tile_x, tile_y, scale_x, scale_y, bbox);
}
} // end ns vector_tile_impl
} // end ns mapnik

View File

@ -1,60 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
/**
* Copyright (c) MapBox
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* - Neither the name "MapBox" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __MAPNIK_VECTOR_TILE_PROJECTION_H__
#define __MAPNIK_VECTOR_TILE_PROJECTION_H__
// mapnik
#include <mapnik/geometry/box2d.hpp>
#include <mapnik/well_known_srs.hpp>
namespace mapnik
{
namespace vector_tile_impl
{
inline mapnik::box2d<double> tile_mercator_bbox(std::uint64_t x,
std::uint64_t y,
std::uint64_t z)
{
const double half_of_equator = M_PI * EARTH_RADIUS;
const double tile_size = 2.0 * half_of_equator / (1ull << z);
double minx = -half_of_equator + x * tile_size;
double miny = half_of_equator - (y + 1.0) * tile_size;
double maxx = -half_of_equator + (x + 1.0) * tile_size;
double maxy = half_of_equator - y * tile_size;
return mapnik::box2d<double>(minx,miny,maxx,maxy);
}
} // end vector_tile_impl ns
} // end mapnik ns
#endif // __MAPNIK_VECTOR_TILE_PROJECTION_H__