mirror of
https://github.com/mapnik/mapnik.git
synced 2025-08-16 17:41:13 +00:00
PMTiles input plugin initial commit
This commit is contained in:
70
plugins/input/pmtiles/build.py
Normal file
70
plugins/input/pmtiles/build.py
Normal file
@ -0,0 +1,70 @@
|
||||
#
|
||||
# This file is part of Mapnik (c++ mapping toolkit)
|
||||
#
|
||||
# Copyright (C) 2025 Artem Pavlenko
|
||||
#
|
||||
# Mapnik is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
#
|
||||
|
||||
Import ('plugin_base')
|
||||
Import ('env')
|
||||
|
||||
PLUGIN_NAME = 'pmtiles'
|
||||
|
||||
plugin_env = plugin_base.Clone()
|
||||
|
||||
plugin_sources = Split(
|
||||
"""
|
||||
%(PLUGIN_NAME)s_datasource.cpp
|
||||
%(PLUGIN_NAME)s_featureset.cpp
|
||||
mvt_io.cpp
|
||||
vector_tile_compression.cpp
|
||||
vector_tile_geometry_decoder.cpp
|
||||
""" % locals()
|
||||
)
|
||||
|
||||
# Link Library to Dependencies
|
||||
libraries = [ 'sqlite3', 'boost_iostreams', 'boost_json' ]
|
||||
|
||||
linkflags = []
|
||||
if env['SQLITE_LINKFLAGS']:
|
||||
linkflags.append(env['SQLITE_LINKFLAGS'])
|
||||
plugin_env.Append(LINKFLAGS=linkflags)
|
||||
|
||||
if env['PLUGIN_LINKING'] == 'shared':
|
||||
libraries.insert(0,env['MAPNIK_NAME'])
|
||||
libraries.append(env['ICU_LIB_NAME'])
|
||||
|
||||
TARGET = plugin_env.SharedLibrary('../%s' % PLUGIN_NAME,
|
||||
SHLIBPREFIX='',
|
||||
SHLIBSUFFIX='.input',
|
||||
source=plugin_sources,
|
||||
LIBS=libraries)
|
||||
|
||||
# if the plugin links to libmapnik ensure it is built first
|
||||
Depends(TARGET, env.subst('../../../src/%s' % env['MAPNIK_LIB_NAME']))
|
||||
|
||||
if 'uninstall' not in COMMAND_LINE_TARGETS:
|
||||
env.Install(env['MAPNIK_INPUT_PLUGINS_DEST'], TARGET)
|
||||
env.Alias('install', env['MAPNIK_INPUT_PLUGINS_DEST'])
|
||||
|
||||
plugin_obj = {
|
||||
'LIBS': libraries,
|
||||
'SOURCES': plugin_sources,
|
||||
'LINKFLAGS': linkflags,
|
||||
}
|
||||
|
||||
Return('plugin_obj')
|
300
plugins/input/pmtiles/mvt_io.cpp
Normal file
300
plugins/input/pmtiles/mvt_io.cpp
Normal file
@ -0,0 +1,300 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik Vector Tile Plugin
|
||||
*
|
||||
* Copyright (C) 2023 Geofabrik GmbH
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#include "mvt_io.hpp"
|
||||
#include "vector_tile_geometry_decoder.hpp"
|
||||
#include <mapnik/feature_factory.hpp>
|
||||
#include <mapnik/well_known_srs.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
mvt_layer::mvt_layer(const uint32_t x, const uint32_t y, const uint32_t zoom,
|
||||
mapnik::context_ptr const& ctx)
|
||||
: keys_(),
|
||||
values_(),
|
||||
context_(ctx),
|
||||
tr_("utf-8")
|
||||
{
|
||||
resolution_ = mapnik::EARTH_CIRCUMFERENCE / (1 << zoom);
|
||||
tile_x_ = -0.5 * mapnik::EARTH_CIRCUMFERENCE + x * resolution_;
|
||||
tile_y_ = 0.5 * mapnik::EARTH_CIRCUMFERENCE - y * resolution_;
|
||||
}
|
||||
|
||||
void mvt_layer::add_feature(const protozero::data_view& feature)
|
||||
{
|
||||
features_.emplace_back(feature);
|
||||
}
|
||||
|
||||
bool mvt_layer::has_features() const
|
||||
{
|
||||
return !features_.empty();
|
||||
}
|
||||
|
||||
void mvt_layer::add_key(std::string&& key)
|
||||
{
|
||||
keys_.push_back(key);
|
||||
}
|
||||
|
||||
void mvt_layer::add_value(pbf_attr_value_type value)
|
||||
{
|
||||
values_.push_back(value);
|
||||
}
|
||||
|
||||
void mvt_layer::set_name(std::string&& name)
|
||||
{
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
void mvt_layer::set_extent(uint32_t extent)
|
||||
{
|
||||
extent_ = extent;
|
||||
}
|
||||
|
||||
uint32_t mvt_layer::extent() const
|
||||
{
|
||||
return extent_;
|
||||
}
|
||||
|
||||
void mvt_layer::finish_reading()
|
||||
{
|
||||
num_keys_ = keys_.size();
|
||||
num_values_ = values_.size();
|
||||
scale_ = static_cast<double>(extent_ / resolution_);
|
||||
}
|
||||
|
||||
mapnik::feature_ptr mvt_layer::next_feature()
|
||||
{
|
||||
while (feature_index_ < features_.size())
|
||||
{
|
||||
const protozero::data_view d (features_.at(feature_index_));
|
||||
protozero::pbf_reader f (d);
|
||||
mapnik::feature_ptr feature = mapnik::feature_factory::create(context_, feature_index_);
|
||||
++feature_index_;
|
||||
mvt_message::geom_type geometry_type = mvt_message::geom_type::unknown;
|
||||
bool has_geometry = false;
|
||||
bool has_geometry_type = false;
|
||||
mapnik::vector_tile_impl::GeometryPBF::pbf_itr geom_itr;
|
||||
while (f.next())
|
||||
{
|
||||
switch(f.tag())
|
||||
{
|
||||
case static_cast<uint32_t>(mvt_message::feature::id):
|
||||
feature->set_id(f.get_uint64());
|
||||
break;
|
||||
case static_cast<uint32_t>(mvt_message::feature::tags):
|
||||
{
|
||||
auto tag_iterator = f.get_packed_uint32();
|
||||
for (auto _i = tag_iterator.begin(); _i != tag_iterator.end();)
|
||||
{
|
||||
std::size_t key_name = *(_i++);
|
||||
if (_i == tag_iterator.end())
|
||||
{
|
||||
throw std::runtime_error("Vector Tile has a feature with an odd number of tags, therefore the tile is invalid.");
|
||||
}
|
||||
std::size_t key_value = *(_i++);
|
||||
if (key_name < num_keys_ && key_value < num_values_)
|
||||
{
|
||||
std::string const& name = keys_.at(key_name);
|
||||
if (feature->has_key(name))
|
||||
{
|
||||
pbf_attr_value_type val = values_.at(key_value);
|
||||
value_visitor vv(tr_, feature, name);
|
||||
mapnik::util::apply_visitor(vv, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case static_cast<uint32_t>(mvt_message::feature::type):
|
||||
has_geometry_type = true;
|
||||
geometry_type = static_cast<mvt_message::geom_type>(f.get_enum());
|
||||
switch (geometry_type)
|
||||
{
|
||||
case mvt_message::geom_type::point:
|
||||
case mvt_message::geom_type::linestring:
|
||||
case mvt_message::geom_type::polygon:
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Vector tile has an unknown geometry type "
|
||||
+ std::to_string(static_cast<protozero::pbf_tag_type>(geometry_type)) + " in feature");
|
||||
}
|
||||
break;
|
||||
case static_cast<uint32_t>(mvt_message::feature::geometry):
|
||||
if (has_geometry)
|
||||
{
|
||||
throw std::runtime_error("Vector Tile has a feature with multiple geometry fields, it must have only one of them");
|
||||
}
|
||||
has_geometry = true;
|
||||
geom_itr = f.get_packed_uint32();
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Vector Tile contains unknown field type "
|
||||
+ std::to_string(static_cast<protozero::pbf_tag_type>(f.tag())) +" in feature");
|
||||
}
|
||||
}
|
||||
if (has_geometry)
|
||||
{
|
||||
if (!has_geometry_type)
|
||||
{
|
||||
throw std::runtime_error("Vector Tile has a feature that does not define the required geometry type.");
|
||||
}
|
||||
mapnik::vector_tile_impl::GeometryPBF geoms(geom_itr);
|
||||
mapnik::geometry::geometry<double> geom =
|
||||
mapnik::vector_tile_impl::decode_geometry<double>(geoms, geometry_type,
|
||||
1, tile_x_, tile_y_, scale_, -1.0 * scale_);
|
||||
if (geom.is<mapnik::geometry::geometry_empty>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// #if defined(DEBUG)
|
||||
// mapnik::box2d<double> envelope = mapnik::geometry::envelope(geom);
|
||||
// if (!filter_.pass(envelope))
|
||||
// {
|
||||
// MAPNIK_LOG_ERROR(tile_featureset_pbf) << "tile_featureset_pbf: filter:pass should not get here";
|
||||
// continue;
|
||||
// }
|
||||
// #endif
|
||||
feature->set_geometry(std::move(geom));
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
return mapnik::feature_ptr();
|
||||
}
|
||||
|
||||
bool mvt_io::read_layer(protozero::pbf_message<mvt_message::layer>& pbf_layer)
|
||||
{
|
||||
layer_.reset(new mvt_layer(x_, y_, zoom_, context_));
|
||||
bool ignore_layer = false;
|
||||
while (pbf_layer.next())
|
||||
{
|
||||
if (ignore_layer) {
|
||||
pbf_layer.skip();
|
||||
continue;
|
||||
}
|
||||
switch (pbf_layer.tag())
|
||||
{
|
||||
case mvt_message::layer::name:
|
||||
{
|
||||
std::string name = pbf_layer.get_string();
|
||||
if (name != layer_name_)
|
||||
{
|
||||
ignore_layer = true;
|
||||
}
|
||||
layer_->set_name(std::move(name));
|
||||
break;
|
||||
}
|
||||
case mvt_message::layer::extent:
|
||||
layer_->set_extent(pbf_layer.get_uint32());
|
||||
if (layer_->extent() == 0)
|
||||
{
|
||||
throw std::runtime_error{"Vector tile layer has extent 0."};
|
||||
}
|
||||
break;
|
||||
case mvt_message::layer::keys:
|
||||
layer_->add_key(std::move(pbf_layer.get_string()));
|
||||
break;
|
||||
case mvt_message::layer::values:
|
||||
{
|
||||
const auto data_view(pbf_layer.get_view());
|
||||
protozero::pbf_reader val_msg(data_view);
|
||||
while (val_msg.next())
|
||||
{
|
||||
switch(val_msg.tag()) {
|
||||
case static_cast<uint32_t>(mvt_message::value::string_value):
|
||||
layer_->add_value(val_msg.get_string());
|
||||
break;
|
||||
case static_cast<uint32_t>(mvt_message::value::float_value):
|
||||
layer_->add_value(val_msg.get_float());
|
||||
break;
|
||||
case static_cast<uint32_t>(mvt_message::value::double_value):
|
||||
layer_->add_value(val_msg.get_double());
|
||||
break;
|
||||
case static_cast<uint32_t>(mvt_message::value::int_value):
|
||||
layer_->add_value(val_msg.get_int64());
|
||||
break;
|
||||
case static_cast<uint32_t>(mvt_message::value::uint_value):
|
||||
layer_->add_value(val_msg.get_uint64());
|
||||
break;
|
||||
case static_cast<uint32_t>(mvt_message::value::sint_value):
|
||||
layer_->add_value(val_msg.get_sint64());
|
||||
break;
|
||||
case static_cast<uint32_t>(mvt_message::value::bool_value):
|
||||
layer_->add_value(val_msg.get_bool());
|
||||
break;
|
||||
default:
|
||||
val_msg.skip();
|
||||
throw std::runtime_error("unknown Value type " + std::to_string(static_cast<int>(val_msg.tag())) + " in layer->values");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case mvt_message::layer::features:
|
||||
{
|
||||
const auto data_view(pbf_layer.get_view());
|
||||
layer_->add_feature(data_view);
|
||||
break;
|
||||
}
|
||||
case mvt_message::layer::version:
|
||||
{
|
||||
uint32_t version = pbf_layer.get_uint32();
|
||||
if (version != 2) {
|
||||
throw std::runtime_error("Vector tile does not have major version 2.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::string msg = "Unsupported tag " + std::to_string(static_cast<uint32_t>(pbf_layer.tag())) + " found in layer message.";
|
||||
throw std::runtime_error(msg);
|
||||
pbf_layer.skip();
|
||||
}
|
||||
}
|
||||
layer_->finish_reading();
|
||||
return !ignore_layer;
|
||||
}
|
||||
|
||||
mapnik::feature_ptr mvt_io::next()
|
||||
{
|
||||
if (!layer_ || !layer_->has_features())
|
||||
{
|
||||
return mapnik::feature_ptr();
|
||||
}
|
||||
return layer_->next_feature();
|
||||
}
|
||||
|
||||
mvt_io::mvt_io(std::string&& data, mapnik::context_ptr const& ctx, const uint32_t x, const uint32_t y, const uint32_t zoom, std::string layer_name)
|
||||
: reader_(data),
|
||||
context_(ctx),
|
||||
x_(x),
|
||||
y_(y),
|
||||
zoom_(zoom),
|
||||
layer_name_(layer_name)
|
||||
{
|
||||
while (reader_.next(static_cast<uint32_t>(mvt_message::tile::layer))) {
|
||||
const auto data_view(reader_.get_view());
|
||||
protozero::pbf_message<mvt_message::layer> msg_layer(data_view);
|
||||
if (read_layer(msg_layer)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
134
plugins/input/pmtiles/mvt_io.hpp
Normal file
134
plugins/input/pmtiles/mvt_io.hpp
Normal file
@ -0,0 +1,134 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik Vector Tile Plugin
|
||||
*
|
||||
* Copyright (C) 2023 Geofabrik GmbH
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef PLUGINS_INPUT_MBTILES_VECTOR_MVT_IO_HPP_
|
||||
#define PLUGINS_INPUT_MBTILES_VECTOR_MVT_IO_HPP_
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <protozero/pbf_message.hpp>
|
||||
#include <mapnik/feature.hpp>
|
||||
#include <mapnik/unicode.hpp>
|
||||
#include "mvt_message.hpp"
|
||||
|
||||
using pbf_attr_value_type = mapnik::util::variant<std::string, float, double, int64_t, uint64_t, bool>;
|
||||
|
||||
struct value_visitor
|
||||
{
|
||||
mapnik::transcoder & tr_;
|
||||
mapnik::feature_ptr & feature_;
|
||||
std::string const& name_;
|
||||
|
||||
value_visitor(mapnik::transcoder & tr,
|
||||
mapnik::feature_ptr & feature,
|
||||
std::string const& name)
|
||||
: tr_(tr),
|
||||
feature_(feature),
|
||||
name_(name) {}
|
||||
|
||||
void operator() (std::string const& val)
|
||||
{
|
||||
feature_->put(name_, tr_.transcode(val.data(), val.length()));
|
||||
}
|
||||
|
||||
void operator() (bool const& val)
|
||||
{
|
||||
feature_->put(name_, static_cast<mapnik::value_bool>(val));
|
||||
}
|
||||
|
||||
void operator() (int64_t const& val)
|
||||
{
|
||||
feature_->put(name_, static_cast<mapnik::value_integer>(val));
|
||||
}
|
||||
|
||||
void operator() (uint64_t const& val)
|
||||
{
|
||||
feature_->put(name_, static_cast<mapnik::value_integer>(val));
|
||||
}
|
||||
|
||||
void operator() (double const& val)
|
||||
{
|
||||
feature_->put(name_, static_cast<mapnik::value_double>(val));
|
||||
}
|
||||
|
||||
void operator() (float const& val)
|
||||
{
|
||||
feature_->put(name_, static_cast<mapnik::value_double>(val));
|
||||
}
|
||||
};
|
||||
|
||||
class mvt_layer
|
||||
{
|
||||
// We have to store the features as strings because they go out of scope otherwise (leading to PBF parsing errors).
|
||||
std::vector<std::string> features_;
|
||||
size_t feature_index_ = 0;
|
||||
std::vector<std::string> keys_;
|
||||
std::vector<pbf_attr_value_type> values_;
|
||||
mapnik::context_ptr const& context_;
|
||||
std::size_t num_keys_ = 0;
|
||||
std::size_t num_values_ = 0;
|
||||
mapnik::transcoder tr_;
|
||||
double tile_x_;
|
||||
double tile_y_;
|
||||
double resolution_;
|
||||
double scale_ = 0;
|
||||
std::string name_;
|
||||
uint32_t extent_ = 4096;
|
||||
public:
|
||||
explicit mvt_layer(const uint32_t x, const uint32_t y, const uint32_t zoom, mapnik::context_ptr const& ctx);
|
||||
void add_feature(const protozero::data_view& feature);
|
||||
bool has_features() const;
|
||||
void add_key(std::string&& key);
|
||||
void add_value(pbf_attr_value_type value);
|
||||
|
||||
void set_name(std::string&& name);
|
||||
void set_extent(uint32_t extent);
|
||||
mapnik::feature_ptr next_feature();
|
||||
uint32_t extent() const;
|
||||
void finish_reading();
|
||||
};
|
||||
|
||||
class mvt_io
|
||||
{
|
||||
protozero::pbf_reader reader_;
|
||||
mapnik::context_ptr context_;
|
||||
const uint32_t x_;
|
||||
const uint32_t y_;
|
||||
const uint32_t zoom_;
|
||||
std::string layer_name_;
|
||||
const int tile_extent_ = -1;
|
||||
std::unique_ptr<mvt_layer> layer_;
|
||||
|
||||
/**
|
||||
* Read a layer from PBF. Returns true if requested layer was parsed.
|
||||
*/
|
||||
bool read_layer(protozero::pbf_message<mvt_message::layer>& l);
|
||||
|
||||
public:
|
||||
explicit mvt_io(std::string&& data, mapnik::context_ptr const& ctx, const uint32_t x, const uint32_t y, const uint32_t zoom, std::string layer_name);
|
||||
mapnik::feature_ptr next();
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* PLUGINS_INPUT_MBTILES_VECTOR_MVT_IO_HPP_ */
|
70
plugins/input/pmtiles/mvt_message.hpp
Normal file
70
plugins/input/pmtiles/mvt_message.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik Vector Tile Plugin
|
||||
*
|
||||
* Copyright (C) 2023 Geofabrik GmbH
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef PLUGINS_INPUT_MBTILES_VECTOR_MVT_MESSAGE_HPP_
|
||||
#define PLUGINS_INPUT_MBTILES_VECTOR_MVT_MESSAGE_HPP_
|
||||
|
||||
namespace mvt_message
|
||||
{
|
||||
enum class tile : protozero::pbf_tag_type
|
||||
{
|
||||
layer = 3
|
||||
};
|
||||
enum class layer : protozero::pbf_tag_type
|
||||
{
|
||||
version = 15,
|
||||
name = 1,
|
||||
features = 2,
|
||||
keys = 3,
|
||||
values = 4,
|
||||
extent = 5
|
||||
};
|
||||
enum class value : protozero::pbf_tag_type
|
||||
{
|
||||
string_value = 1,
|
||||
float_value = 2,
|
||||
double_value = 3,
|
||||
int_value = 4,
|
||||
uint_value = 5,
|
||||
sint_value = 6,
|
||||
bool_value = 7
|
||||
};
|
||||
enum class feature : protozero::pbf_tag_type
|
||||
{
|
||||
id = 1,
|
||||
tags = 2,
|
||||
type = 3,
|
||||
geometry = 4
|
||||
};
|
||||
enum class geom_type : int32_t
|
||||
{
|
||||
unknown = 0,
|
||||
point = 1,
|
||||
linestring = 2,
|
||||
polygon = 3
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif /* PLUGINS_INPUT_MBTILES_VECTOR_MVT_MESSAGE_HPP_ */
|
252
plugins/input/pmtiles/pmtiles_datasource.cpp
Normal file
252
plugins/input/pmtiles/pmtiles_datasource.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* 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
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#include "pmtiles_datasource.hpp"
|
||||
#include "pmtiles_featureset.hpp"
|
||||
#include "vector_tile_projection.hpp"
|
||||
#include <mapnik/geom_util.hpp>
|
||||
#include <mapnik/util/fs.hpp>
|
||||
#include <mapnik/unicode.hpp>
|
||||
#include <mapnik/datasource_plugin.hpp>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
DATASOURCE_PLUGIN_IMPL(pmtiles_datasource_plugin, pmtiles_datasource);
|
||||
DATASOURCE_PLUGIN_EXPORT(pmtiles_datasource_plugin);
|
||||
DATASOURCE_PLUGIN_EMPTY_AFTER_LOAD(pmtiles_datasource_plugin);
|
||||
DATASOURCE_PLUGIN_EMPTY_BEFORE_UNLOAD(pmtiles_datasource_plugin);
|
||||
|
||||
pmtiles_datasource::pmtiles_datasource(mapnik::parameters const& params)
|
||||
: datasource(params),
|
||||
desc_(pmtiles_datasource::name(), *params.get<std::string>("encoding", "utf-8"))
|
||||
{
|
||||
init(params);
|
||||
}
|
||||
|
||||
pmtiles_datasource::~pmtiles_datasource() {}
|
||||
|
||||
mapnik::datasource::datasource_t pmtiles_datasource::type() const
|
||||
{
|
||||
return datasource::Vector;
|
||||
}
|
||||
|
||||
const char * pmtiles_datasource::name()
|
||||
{
|
||||
return "pmtiles";
|
||||
}
|
||||
|
||||
mapnik::layer_descriptor pmtiles_datasource::get_descriptor() const
|
||||
{
|
||||
return desc_;
|
||||
}
|
||||
|
||||
std::optional<mapnik::datasource_geometry_t> pmtiles_datasource::get_geometry_type() const
|
||||
{
|
||||
return mapnik::datasource_geometry_t::Collection;
|
||||
}
|
||||
|
||||
mapnik::box2d<double> pmtiles_datasource::envelope() const
|
||||
{
|
||||
return extent_;
|
||||
}
|
||||
|
||||
void pmtiles_datasource::init(mapnik::parameters const& params)
|
||||
{
|
||||
std::optional<std::string> file = params.get<std::string>("file");
|
||||
if (!file)
|
||||
{
|
||||
throw mapnik::datasource_exception("pmtiles Plugin: missing <file> parameter");
|
||||
}
|
||||
|
||||
std::optional<std::string> base = params.get<std::string>("base");
|
||||
if (base)
|
||||
{
|
||||
database_path_ = *base + "/" + *file;
|
||||
}
|
||||
else
|
||||
{
|
||||
database_path_ = *file;
|
||||
}
|
||||
if (!mapnik::util::exists(database_path_))
|
||||
{
|
||||
throw mapnik::datasource_exception("pmtiles Plugin: " + database_path_ + " does not exist");
|
||||
}
|
||||
std::optional<std::string> layer = params.get<std::string>("layer");
|
||||
try
|
||||
{
|
||||
layer_ = layer.value();
|
||||
}
|
||||
catch (std::bad_optional_access&)
|
||||
{
|
||||
throw mapnik::datasource_exception("pmtiles Plugin: parameter 'layer' is missing.");
|
||||
}
|
||||
|
||||
file_ptr_ = std::make_shared<mapnik::pmtiles_file>(database_path_);
|
||||
if (!file_ptr_->is_good())
|
||||
{
|
||||
throw mapnik::datasource_exception("Failed to create memory mapping for " + database_path_);
|
||||
}
|
||||
|
||||
file_ptr_->read_header(*this);
|
||||
|
||||
// overwrite envelope with user supplied
|
||||
std::optional<std::string> ext = params.get<std::string>("extent");
|
||||
if (ext && !ext->empty())
|
||||
{
|
||||
extent_.from_string(*ext);
|
||||
}
|
||||
if (!extent_.valid())
|
||||
{
|
||||
throw mapnik::datasource_exception("pmtiles Plugin: " + database_path_ + " extent is invalid.");
|
||||
}
|
||||
// Bounds are specified in EPSG:4326, therefore transformation is required.
|
||||
mapnik::lonlat2merc(extent_.minx_, extent_.miny_);
|
||||
mapnik::lonlat2merc(extent_.maxx_, extent_.maxy_);
|
||||
|
||||
std::optional<mapnik::value_integer> zoom = params.get<mapnik::value_integer>("zoom");
|
||||
if (!zoom)
|
||||
{
|
||||
throw mapnik::datasource_exception("pmtiles Plugin: parameter 'zoom' missing");
|
||||
}
|
||||
zoom_ = *zoom;
|
||||
|
||||
auto metadata = file_ptr_->metadata();
|
||||
auto layers = metadata.at("vector_layers");
|
||||
bool found = false;
|
||||
for (auto const& layer : layers.as_array())
|
||||
{
|
||||
std::string id = layer.at("id").as_string().c_str();
|
||||
if (id == layer_)
|
||||
{
|
||||
found = true;
|
||||
if (auto const* p = layer.as_object().if_contains("minzoom"))
|
||||
{
|
||||
minzoom_ = std::max(minzoom_, p->as_int64());
|
||||
}
|
||||
if (auto const* p = layer.as_object().if_contains("maxzoom"))
|
||||
{
|
||||
maxzoom_ = std::min(maxzoom_, p->as_int64());
|
||||
}
|
||||
std::cerr << "====== Layer ID:" << id << " min/max zoom:" << minzoom_ << ":" << maxzoom_ << std::endl;
|
||||
for (auto const& field : layer.at("fields").as_object())
|
||||
{
|
||||
std::string name{field.key_c_str()};
|
||||
if (field.value() == "String")
|
||||
{
|
||||
desc_.add_descriptor(mapnik::attribute_descriptor(name, mapnik::String));
|
||||
}
|
||||
else if (field.value() == "Number")
|
||||
{
|
||||
desc_.add_descriptor(mapnik::attribute_descriptor(name, mapnik::Float));
|
||||
}
|
||||
else if (field.value() == "Boolean")
|
||||
{
|
||||
desc_.add_descriptor(mapnik::attribute_descriptor(name, mapnik::Boolean));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw mapnik::datasource_exception("Requested layer '" + layer_ + "' not found.");
|
||||
}
|
||||
}
|
||||
|
||||
mapnik::context_ptr pmtiles_datasource::get_context_with_attributes() const
|
||||
{
|
||||
mapnik::context_ptr context = std::make_shared<mapnik::context_type>();
|
||||
std::vector<mapnik::attribute_descriptor> const& desc_ar = desc_.get_descriptors();
|
||||
for (auto const& attr_info : desc_ar)
|
||||
{
|
||||
context->push(attr_info.get_name());
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
mapnik::context_ptr pmtiles_datasource::get_query_context(mapnik::query const& q) const
|
||||
{
|
||||
mapnik::context_ptr context = std::make_shared<mapnik::context_type>();
|
||||
std::vector<mapnik::attribute_descriptor> const& desc_ar = desc_.get_descriptors();
|
||||
for (auto const& name : q.property_names())
|
||||
{
|
||||
for (auto const& attr_info : desc_ar)
|
||||
{
|
||||
if (name == attr_info.get_name())
|
||||
{
|
||||
context->push(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
double scales[] = {279541132.014, 139770566.007, 69885283.0036, 34942641.5018, 17471320.7509,
|
||||
8735660.37545, 4367830.18772, 2183915.09386, 1091957.54693, 545978.773466,
|
||||
272989.386733, 136494.693366, 68247.3466832, 34123.6733416, 17061.8366708,
|
||||
8530.9183354, 4265.4591677, 2132.72958385, 1066.36479192, 533.182395962};
|
||||
std::int64_t scale_to_zoom(double scale, std::int64_t minzoom, std::int64_t maxzoom)
|
||||
{
|
||||
for (std::int64_t zoom = 0; zoom < 19; ++zoom)
|
||||
{
|
||||
if (scale > scales[zoom]) return minzoom;
|
||||
else if (scale < scales[zoom] && scale > scales[zoom + 1])
|
||||
{
|
||||
return std::min(zoom, maxzoom);
|
||||
}
|
||||
}
|
||||
return maxzoom;
|
||||
}
|
||||
}
|
||||
mapnik::featureset_ptr pmtiles_datasource::features(mapnik::query const& q) const
|
||||
{
|
||||
#ifdef MAPNIK_STATS
|
||||
mapnik::progress_timer __stats__(std::clog, "pmtiles_datasource::features");
|
||||
#endif
|
||||
|
||||
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_);
|
||||
std::cerr << "zoom:" << zoom << std::endl;
|
||||
mapnik::context_ptr context = get_query_context(q);
|
||||
auto && file_ptr = std::make_unique<mapnik::pmtiles_file>(database_path_);
|
||||
if (!file_ptr->is_good())
|
||||
{
|
||||
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_));
|
||||
}
|
||||
|
||||
mapnik::featureset_ptr pmtiles_datasource::features_at_point(mapnik::coord2d const& pt, double tol) const
|
||||
{
|
||||
#ifdef MAPNIK_STATS
|
||||
mapnik::progress_timer __stats__(std::clog, "pmtiles_datasource::features");
|
||||
#endif
|
||||
|
||||
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_));
|
||||
}
|
75
plugins/input/pmtiles/pmtiles_datasource.hpp
Normal file
75
plugins/input/pmtiles/pmtiles_datasource.hpp
Normal file
@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* 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
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef PMTILES_DATASOURCE_HPP_
|
||||
#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>
|
||||
#include <mapnik/geometry/box2d.hpp>
|
||||
#include <mapnik/coord.hpp>
|
||||
#include <mapnik/feature_layer_desc.hpp>
|
||||
#include <mapnik/datasource_plugin.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "pmtiles_file.hpp"
|
||||
|
||||
DATASOURCE_PLUGIN_DEF(pmtiles_datasource_plugin, pmtiles);
|
||||
|
||||
class pmtiles_datasource : public mapnik::datasource
|
||||
{
|
||||
public:
|
||||
pmtiles_datasource(mapnik::parameters const& params);
|
||||
virtual ~pmtiles_datasource ();
|
||||
mapnik::datasource::datasource_t type() const;
|
||||
static const char * name();
|
||||
mapnik::featureset_ptr features(mapnik::query const& q) const;
|
||||
mapnik::featureset_ptr features_at_point(mapnik::coord2d const& pt, double tol = 0) const;
|
||||
mapnik::box2d<double> envelope() const;
|
||||
std::optional<mapnik::datasource_geometry_t> get_geometry_type() const;
|
||||
mapnik::layer_descriptor get_descriptor() const;
|
||||
|
||||
private:
|
||||
void init(mapnik::parameters const& params);
|
||||
mapnik::context_ptr get_context_with_attributes() const;
|
||||
mapnik::context_ptr get_query_context(mapnik::query const& q) const;
|
||||
std::string database_path_;
|
||||
std::shared_ptr<mapnik::pmtiles_file> file_ptr_;
|
||||
public:
|
||||
mapnik::box2d<double> extent_;
|
||||
std::int64_t minzoom_ = 0;
|
||||
std::int64_t maxzoom_ = 14;
|
||||
std::int64_t zoom_ = 14;
|
||||
std::string json_;
|
||||
std::string layer_;
|
||||
mapnik::layer_descriptor desc_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // PMTILES_DATASOURCE_HPP_
|
136
plugins/input/pmtiles/pmtiles_featureset.cpp
Normal file
136
plugins/input/pmtiles/pmtiles_featureset.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik Vector Tile Plugin
|
||||
*
|
||||
* Copyright (C) 2023 Geofabrik GmbH
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#include "pmtiles_featureset.hpp"
|
||||
#include "vector_tile_compression.hpp"
|
||||
#include <boost/format.hpp>
|
||||
#include <math.h>
|
||||
#if 0
|
||||
#include <boost/iostreams/filtering_stream.hpp>
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/filter/gzip.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
#include <iterator>
|
||||
#endif
|
||||
|
||||
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) :
|
||||
file_ptr_(file_ptr),
|
||||
context_(ctx),
|
||||
zoom_(zoom),
|
||||
extent_(extent),
|
||||
layer_(layer),
|
||||
vector_tile_(nullptr)
|
||||
{
|
||||
int tile_count = 1 << zoom;
|
||||
constexpr double width = 2.0 * 6378137 * M_PI;
|
||||
xmin_ = static_cast<int>((extent_.minx() + width / 2) * (tile_count / width));
|
||||
xmax_ = static_cast<int>((extent_.maxx() + width / 2) * (tile_count / width));
|
||||
ymin_ = static_cast<int>(((width / 2) - extent_.maxy()) * (tile_count / width));
|
||||
ymax_ = static_cast<int>(((width / 2) - extent_.miny()) * (tile_count / width));
|
||||
x_ = xmin_;
|
||||
y_ = ymin_;
|
||||
open_tile();
|
||||
}
|
||||
|
||||
pmtiles_featureset::~pmtiles_featureset() { }
|
||||
|
||||
bool pmtiles_featureset::valid() const
|
||||
{
|
||||
return vector_tile_.get() != nullptr;
|
||||
}
|
||||
|
||||
mapnik::feature_ptr pmtiles_featureset::next_feature()
|
||||
{
|
||||
mapnik::feature_ptr f = mapnik::feature_ptr();
|
||||
if (valid()) {
|
||||
f = vector_tile_->next();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
mapnik::feature_ptr pmtiles_featureset::next()
|
||||
{
|
||||
// If current tile is processed completely, go forward to the next tile.
|
||||
// else step forward to the next feature
|
||||
mapnik::feature_ptr f = next_feature();
|
||||
if (f) {
|
||||
return f;
|
||||
}
|
||||
while (next_tile() && open_tile() && valid())
|
||||
{
|
||||
f = next_feature();
|
||||
if (f)
|
||||
{
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return mapnik::feature_ptr();
|
||||
}
|
||||
|
||||
bool pmtiles_featureset::next_tile()
|
||||
{
|
||||
++x_;
|
||||
if (x_ <= xmax_)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
x_ = xmin_;
|
||||
++y_;
|
||||
return y_ <= ymax_;
|
||||
}
|
||||
|
||||
bool pmtiles_featureset::open_tile()
|
||||
{
|
||||
auto tile = file_ptr_->get_tile(zoom_, x_, y_);
|
||||
std::cerr << layer_ << ":" << zoom_ << ":" << x_ << ":" << y_ << std::endl;
|
||||
#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_));
|
||||
#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_));
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
68
plugins/input/pmtiles/pmtiles_featureset.hpp
Normal file
68
plugins/input/pmtiles/pmtiles_featureset.hpp
Normal file
@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik Vector Tile Plugin
|
||||
*
|
||||
* Copyright (C) 2023 Geofabrik GmbH
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef PMTILES_FEATURESET_HPP_
|
||||
#define PMTILES_FEATURESET_HPP_
|
||||
|
||||
#include <memory>
|
||||
// mapnik
|
||||
#include <mapnik/feature.hpp>
|
||||
#include <mapnik/datasource.hpp>
|
||||
// sqlite
|
||||
#include "sqlite_connection.hpp"
|
||||
#include "mvt_io.hpp"
|
||||
#include "pmtiles_file.hpp"
|
||||
|
||||
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,
|
||||
mapnik::box2d<double> const& extent,
|
||||
const std::string & layer);
|
||||
|
||||
virtual ~pmtiles_featureset();
|
||||
mapnik::feature_ptr next();
|
||||
private:
|
||||
mapnik::feature_ptr next_feature();
|
||||
bool valid() const;
|
||||
std::shared_ptr<mapnik::pmtiles_file> file_ptr_;
|
||||
mapnik::context_ptr context_;
|
||||
int zoom_;
|
||||
mapnik::box2d<double> const& extent_;
|
||||
const std::string& layer_;
|
||||
std::unique_ptr<mvt_io> vector_tile_;
|
||||
int xmin_;
|
||||
int xmax_;
|
||||
int ymin_;
|
||||
int ymax_;
|
||||
/// x index of the currently accessed tile
|
||||
int x_ = 0;
|
||||
/// y index of the currently accessed tile
|
||||
int y_ = 0;
|
||||
bool next_tile();
|
||||
bool open_tile();
|
||||
};
|
||||
|
||||
#endif /* PMTILES_FEATURESET_HPP_ */
|
557
plugins/input/pmtiles/pmtiles_file.hpp
Normal file
557
plugins/input/pmtiles/pmtiles_file.hpp
Normal file
@ -0,0 +1,557 @@
|
||||
/*****************************************************************************
|
||||
*
|
||||
* This file is part of Mapnik (c++ mapping toolkit)
|
||||
*
|
||||
* 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
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*****************************************************************************/
|
||||
#ifndef PMTILES_FILE_HPP
|
||||
#define PMTILES_FILE_HPP
|
||||
|
||||
#include <mapnik/global.hpp>
|
||||
#define MAPNIK_MEMORY_MAPPED_FILE
|
||||
#include <mapnik/util/mapped_memory_file.hpp>
|
||||
#include <boost/interprocess/file_mapping.hpp>
|
||||
#include <boost/interprocess/mapped_region.hpp>
|
||||
// stl
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <fstream>
|
||||
|
||||
// boost
|
||||
#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"
|
||||
|
||||
namespace mapnik {
|
||||
|
||||
enum class compression_type : std::uint8_t
|
||||
{
|
||||
UNKNOWN = 0x0,
|
||||
NONE = 0x1,
|
||||
GZIP = 0x2,
|
||||
BROTLI = 0x3,
|
||||
ZSTD = 0x4
|
||||
};
|
||||
|
||||
struct entryv3 {
|
||||
uint64_t tile_id;
|
||||
uint64_t offset;
|
||||
uint32_t length;
|
||||
uint32_t run_length;
|
||||
|
||||
entryv3()
|
||||
: tile_id(0), offset(0), length(0), run_length(0) {}
|
||||
|
||||
entryv3(uint64_t _tile_id, uint64_t _offset, uint32_t _length, uint32_t _run_length)
|
||||
: tile_id(_tile_id), offset(_offset), length(_length), run_length(_run_length) {
|
||||
}
|
||||
};
|
||||
namespace {
|
||||
struct varint_too_long_exception : std::exception
|
||||
{
|
||||
const char *what() const noexcept override {
|
||||
return "varint too long exception";
|
||||
}
|
||||
};
|
||||
|
||||
struct end_of_buffer_exception : std::exception
|
||||
{
|
||||
const char *what() const noexcept override
|
||||
{
|
||||
return "end of buffer exception";
|
||||
}
|
||||
};
|
||||
struct malformed_directory_exception : std::exception {
|
||||
const char *what() const noexcept override {
|
||||
return "malformed directory exception";
|
||||
}
|
||||
};
|
||||
|
||||
constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1;
|
||||
// from https://github.com/mapbox/protozero/blob/master/include/protozero/varint.hpp
|
||||
uint64_t decode_varint_impl(const char **data, const char *end)
|
||||
{
|
||||
const auto *begin = reinterpret_cast<const int8_t *>(*data);
|
||||
const auto *iend = reinterpret_cast<const int8_t *>(end);
|
||||
const int8_t *p = begin;
|
||||
uint64_t val = 0;
|
||||
if (iend - begin >= max_varint_length) { // fast path
|
||||
do {
|
||||
int64_t b = *p++;
|
||||
val = ((uint64_t(b) & 0x7fU));
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
b = *p++;
|
||||
val |= ((uint64_t(b) & 0x7fU) << 7U);
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
b = *p++;
|
||||
val |= ((uint64_t(b) & 0x7fU) << 14U);
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
b = *p++;
|
||||
val |= ((uint64_t(b) & 0x7fU) << 21U);
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
b = *p++;
|
||||
val |= ((uint64_t(b) & 0x7fU) << 28U);
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
b = *p++;
|
||||
val |= ((uint64_t(b) & 0x7fU) << 35U);
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
b = *p++;
|
||||
val |= ((uint64_t(b) & 0x7fU) << 42U);
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
b = *p++;
|
||||
val |= ((uint64_t(b) & 0x7fU) << 49U);
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
b = *p++;
|
||||
val |= ((uint64_t(b) & 0x7fU) << 56U);
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
b = *p++;
|
||||
val |= ((uint64_t(b) & 0x01U) << 63U);
|
||||
if (b >= 0) {
|
||||
break;
|
||||
}
|
||||
throw varint_too_long_exception{};
|
||||
} while (false);
|
||||
} else {
|
||||
unsigned int shift = 0;
|
||||
while (p != iend && *p < 0) {
|
||||
val |= (uint64_t(*p++) & 0x7fU) << shift;
|
||||
shift += 7;
|
||||
}
|
||||
if (p == iend) {
|
||||
throw end_of_buffer_exception{};
|
||||
}
|
||||
val |= uint64_t(*p++) << shift;
|
||||
}
|
||||
|
||||
*data = reinterpret_cast<const char *>(p);
|
||||
return val;
|
||||
}
|
||||
|
||||
uint64_t decode_varint(const char **data, const char *end) {
|
||||
// If this is a one-byte varint, decode it here.
|
||||
if (end != *data && ((static_cast<uint64_t>(**data) & 0x80U) == 0)) {
|
||||
const auto val = static_cast<uint64_t>(**data);
|
||||
++(*data);
|
||||
return val;
|
||||
}
|
||||
// If this varint is more than one byte, defer to complete implementation.
|
||||
return decode_varint_impl(data, end);
|
||||
}
|
||||
|
||||
inline std::vector<entryv3> deserialize_directory(std::string const& decompressed)
|
||||
{
|
||||
const char *t = decompressed.data();
|
||||
const char *end = t + decompressed.size();
|
||||
|
||||
const uint64_t num_entries_64bit = decode_varint(&t, end);
|
||||
// Sanity check to avoid excessive memory allocation attempt:
|
||||
// each directory entry takes at least 4 bytes
|
||||
if (num_entries_64bit / 4U > decompressed.size()) {
|
||||
throw malformed_directory_exception();
|
||||
}
|
||||
const size_t num_entries = static_cast<size_t>(num_entries_64bit);
|
||||
//std::cerr << "Decompressed size" << decompressed.size() << std::endl;
|
||||
std::vector<entryv3> result;
|
||||
result.resize(num_entries);
|
||||
|
||||
uint64_t last_id = 0;
|
||||
for (std::size_t i = 0; i < num_entries; ++i)
|
||||
{
|
||||
const uint64_t val = decode_varint(&t, end);
|
||||
if (val > std::numeric_limits<uint64_t>::max() - last_id)
|
||||
{
|
||||
throw malformed_directory_exception();
|
||||
}
|
||||
const uint64_t tile_id = last_id + val;
|
||||
result[i].tile_id = tile_id;
|
||||
last_id = tile_id;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < num_entries; ++i)
|
||||
{
|
||||
const uint64_t val = decode_varint(&t, end);
|
||||
if (val > std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
throw malformed_directory_exception();
|
||||
}
|
||||
result[i].run_length = static_cast<uint32_t>(val);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_entries; i++)
|
||||
{
|
||||
const uint64_t val = decode_varint(&t, end);
|
||||
if (val > std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
throw malformed_directory_exception();
|
||||
}
|
||||
result[i].length = static_cast<uint32_t>(val);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_entries; i++)
|
||||
{
|
||||
uint64_t tmp = decode_varint(&t, end);
|
||||
|
||||
if (i > 0 && tmp == 0)
|
||||
{
|
||||
if (result[i - 1].offset > std::numeric_limits<uint64_t>::max() - result[i - 1].length)
|
||||
{
|
||||
throw malformed_directory_exception();
|
||||
}
|
||||
result[i].offset = result[i - 1].offset + result[i - 1].length;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[i].offset = tmp - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// assert the directory has been fully consumed
|
||||
if (t != end)
|
||||
{
|
||||
throw malformed_directory_exception();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// use a 0 length entry as a null value.
|
||||
entryv3 find_tile(std::vector<entryv3> const& entries, uint64_t tile_id)
|
||||
{
|
||||
int m = 0;
|
||||
int n = static_cast<int>(entries.size()) - 1;
|
||||
while (m <= n)
|
||||
{
|
||||
int k = (n + m) >> 1;
|
||||
if (tile_id > entries[k].tile_id)
|
||||
{
|
||||
m = k + 1;
|
||||
}
|
||||
else if (tile_id < entries[k].tile_id)
|
||||
{
|
||||
n = k - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entries[k];
|
||||
}
|
||||
}
|
||||
|
||||
if (n >= 0)
|
||||
{
|
||||
if (entries[n].run_length == 0)
|
||||
{
|
||||
return entries[n];
|
||||
}
|
||||
if (tile_id - entries[n].tile_id < entries[n].run_length)
|
||||
{
|
||||
return entries[n];
|
||||
}
|
||||
}
|
||||
|
||||
return entryv3{0, 0, 0, 0};
|
||||
}
|
||||
void rotate(int64_t n, uint32_t &x, uint32_t &y, uint32_t rx, uint32_t ry)
|
||||
{
|
||||
if (ry == 0)
|
||||
{
|
||||
if (rx != 0)
|
||||
{
|
||||
x = n - 1 - x;
|
||||
y = n - 1 - y;
|
||||
}
|
||||
uint32_t t = x;
|
||||
x = y;
|
||||
y = t;
|
||||
}
|
||||
}
|
||||
|
||||
inline uint64_t zxy_to_tileid(uint8_t z, uint32_t x, uint32_t y)
|
||||
{
|
||||
if (z > 31)
|
||||
{
|
||||
throw std::overflow_error("tile zoom exceeds 64-bit limit");
|
||||
}
|
||||
if (x > (1U << z) - 1U || y > (1U << z) - 1U)
|
||||
{
|
||||
throw std::overflow_error("tile x/y outside zoom level bounds");
|
||||
}
|
||||
uint64_t acc = ((1LL << (z * 2U)) - 1) / 3;
|
||||
uint32_t tx = x, ty = y;
|
||||
int a = z - 1;
|
||||
for (uint32_t s = 1LL << a; s > 0; s >>= 1) {
|
||||
uint32_t rx = s & tx;
|
||||
uint32_t ry = s & ty;
|
||||
rotate(s, tx, ty, rx, ry);
|
||||
acc += ((3LL * rx) ^ ry) << a;
|
||||
a--;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
inline std::int32_t read_int32_ndr(char const* buf, std::size_t pos)
|
||||
{
|
||||
std::int32_t val;
|
||||
std::memcpy(&val, &buf[pos], 4);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline std::uint64_t read_uint64_xdr(char const* buf, std::size_t pos)
|
||||
{
|
||||
std::uint64_t val;
|
||||
std::memcpy(&val, &buf[pos], 8);
|
||||
return val;
|
||||
}
|
||||
|
||||
class pmtiles_file : public mapnik::util::mapped_memory_file
|
||||
{
|
||||
struct header
|
||||
{
|
||||
explicit header(char const* data)
|
||||
: data_(data) {}
|
||||
char const* data_;
|
||||
bool check_valid() const
|
||||
{
|
||||
return (std::string(data_, data_ + 7) == "PMTiles" &&
|
||||
data_[7] == 0x3);
|
||||
}
|
||||
int version() const { return data_[7]; }
|
||||
std::uint64_t root_dir_offset() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 8);
|
||||
}
|
||||
std::uint64_t root_dir_length() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 16);
|
||||
}
|
||||
std::uint64_t metadata_offset() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 24);
|
||||
}
|
||||
std::uint64_t metadata_length() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 32);
|
||||
}
|
||||
std::uint64_t leaf_directories_offset() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 40);
|
||||
}
|
||||
std::uint64_t leaf_directories_length() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 48);
|
||||
}
|
||||
std::uint64_t tile_data_offset() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 56);
|
||||
}
|
||||
std::uint64_t tile_data_length() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 64);
|
||||
}
|
||||
std::uint64_t addressed_tile_count() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 72);
|
||||
}
|
||||
std::uint64_t tile_entries_count() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 80);
|
||||
}
|
||||
std::uint64_t tile_content_count() const
|
||||
{
|
||||
return read_uint64_xdr(data_, 88);
|
||||
}
|
||||
|
||||
int min_zoom() const { return static_cast<int>(data_[100]); }
|
||||
int max_zoom() const { return static_cast<int>(data_[101]); }
|
||||
double minx() const
|
||||
{
|
||||
return read_int32_ndr(data_, 102)/1e7;
|
||||
}
|
||||
double miny() const
|
||||
{
|
||||
return read_int32_ndr(data_, 106)/1e7;
|
||||
}
|
||||
double maxx() const
|
||||
{
|
||||
return read_int32_ndr(data_, 110)/1e7;
|
||||
}
|
||||
double maxy() const
|
||||
{
|
||||
return read_int32_ndr(data_, 114)/1e7;
|
||||
}
|
||||
compression_type internal_compression() const
|
||||
{
|
||||
return compression_type(data_[97]);
|
||||
}
|
||||
compression_type tile_compression() const
|
||||
{
|
||||
return compression_type(data_[98]);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
public:
|
||||
|
||||
pmtiles_file() {}
|
||||
|
||||
pmtiles_file(std::string const& file_name)
|
||||
: mapped_memory_file(file_name)
|
||||
//: file_(file_name.c_str(), boost::interprocess::read_only),
|
||||
//region_(file_, boost::interprocess::read_only)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~pmtiles_file() {}
|
||||
|
||||
template<typename Datasource>
|
||||
void read_header(Datasource & source)
|
||||
{
|
||||
header h(data());
|
||||
if (!h.check_valid())
|
||||
std::cerr << "FAIL" << 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;
|
||||
std::cerr << "Min Lon/Lat:" << h.minx() << "," << h.miny() << std::endl;
|
||||
std::cerr << "Max Lon/Lat:" << h.maxx() << "," << h.maxy() << std::endl;
|
||||
|
||||
source.minzoom_ = h.min_zoom();
|
||||
source.maxzoom_ = h.max_zoom();
|
||||
source.extent_ = mapnik::box2d<double>{h.minx(), h.miny(),h.maxx(), h.maxy()};
|
||||
root_dir_offset_ = h.root_dir_offset();
|
||||
root_dir_length_ = h.root_dir_length();
|
||||
tile_data_offset_ = h.tile_data_offset();
|
||||
leaf_directories_offset_ = h.leaf_directories_offset();
|
||||
std::cerr << "Internal compression:" << (int)h.internal_compression() << std::endl;
|
||||
std::cerr << "Tile compression:" << (int)h.tile_compression() << std::endl;
|
||||
std::cerr << "Addressed tile count:" << h.addressed_tile_count() << std::endl;
|
||||
}
|
||||
}
|
||||
boost::json::value metadata() const
|
||||
{
|
||||
header h(data());
|
||||
auto metadata_offset = h.metadata_offset();
|
||||
auto metadata_length = h.metadata_length();
|
||||
using namespace boost::iostreams;
|
||||
namespace io = boost::iostreams;
|
||||
std::string metadata;
|
||||
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));
|
||||
boost::json::value json_value;
|
||||
try
|
||||
{
|
||||
json_value = boost::json::parse(metadata);
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
std::cerr << ex.what() << std::endl;
|
||||
}
|
||||
return json_value;
|
||||
}
|
||||
inline void seek(std::streampos pos) { file_.seekg(pos, std::ios::beg); }
|
||||
inline std::streampos pos() { return file_.tellg(); }
|
||||
inline bool is_eof() { return file_.eof(); }
|
||||
inline bool is_good() { return file_.good(); }
|
||||
//inline bool is_good() { return (region_.get_size() > 0);}
|
||||
//inline char const* data() const { return reinterpret_cast<char const*>(region_.get_address()); }
|
||||
inline char const* data() const { return file_.buffer().first; }
|
||||
//boost::interprocess::file_mapping file_;
|
||||
//boost::interprocess::mapped_region region_;
|
||||
std::uint64_t root_dir_offset_;
|
||||
std::uint64_t root_dir_length_;
|
||||
std::uint64_t tile_data_offset_;
|
||||
std::uint64_t leaf_directories_offset_;
|
||||
|
||||
std::pair<uint64_t, uint32_t> get_tile(std::uint8_t z, std::uint32_t x, std::uint32_t y)
|
||||
{
|
||||
auto tile_id = zxy_to_tileid(z, x, y);
|
||||
//std::cerr << "TileID:" << tile_id << std::endl;
|
||||
//header h(file_.buffer().first);
|
||||
//std::cerr << "Root dir offset:" << root_dir_offset_ << std::endl;
|
||||
//std::cerr << "Root dir length:" << root_dir_length_ << std::endl;
|
||||
std::uint64_t dir_offset = root_dir_offset_;
|
||||
std::uint64_t dir_length = root_dir_length_;
|
||||
//using namespace boost::iostreams;
|
||||
//namespace io = boost::iostreams;
|
||||
//filtering_istream in;
|
||||
//in.set_auto_close(false);
|
||||
|
||||
//if (h.internal_compression() == compression_type::GZIP)
|
||||
//{
|
||||
// in.push(gzip_decompressor());
|
||||
//}
|
||||
|
||||
for (std::size_t depth = 0; depth < 4; ++depth)
|
||||
{
|
||||
std::string decompressed_dir;
|
||||
mapnik::vector_tile_impl::zlib_decompress(data() + dir_offset, dir_length, decompressed_dir);
|
||||
auto dir_entries = deserialize_directory(decompressed_dir);
|
||||
auto entry = find_tile(dir_entries, tile_id);
|
||||
if (entry.length > 0)
|
||||
{
|
||||
if (entry.run_length > 0)
|
||||
{
|
||||
return std::make_pair(tile_data_offset_ + entry.offset, entry.length);
|
||||
}
|
||||
else
|
||||
{
|
||||
dir_offset = leaf_directories_offset_ + entry.offset;
|
||||
dir_length = entry.length;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::make_pair(0, 0);
|
||||
}
|
||||
}
|
||||
return std::make_pair(0, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //PMTILES_FILE_HPP
|
69
plugins/input/pmtiles/vector_tile_compression.cpp
Normal file
69
plugins/input/pmtiles/vector_tile_compression.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
// 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);
|
||||
}
|
||||
|
69
plugins/input/pmtiles/vector_tile_compression.hpp
Normal file
69
plugins/input/pmtiles/vector_tile_compression.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
// 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_ */
|
67
plugins/input/pmtiles/vector_tile_compression.ipp
Normal file
67
plugins/input/pmtiles/vector_tile_compression.ipp
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
73
plugins/input/pmtiles/vector_tile_geometry_decoder.cpp
Normal file
73
plugins/input/pmtiles/vector_tile_geometry_decoder.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
// 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
|
119
plugins/input/pmtiles/vector_tile_geometry_decoder.hpp
Normal file
119
plugins/input/pmtiles/vector_tile_geometry_decoder.hpp
Normal file
@ -0,0 +1,119 @@
|
||||
// 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_ */
|
877
plugins/input/pmtiles/vector_tile_geometry_decoder.ipp
Normal file
877
plugins/input/pmtiles/vector_tile_geometry_decoder.ipp
Normal file
@ -0,0 +1,877 @@
|
||||
// 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
|
60
plugins/input/pmtiles/vector_tile_projection.hpp
Normal file
60
plugins/input/pmtiles/vector_tile_projection.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
// 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__
|
Reference in New Issue
Block a user