e7c3d04309
format all files Revert "format all files" This reverts commit 95d5812e49e7f916b68e786596f5a8eb5bcac414. Revert "format some files" This reverts commit ed3c8762d4d828b2b28e7b18809fc33f4f8ccaf5. format all files fix formatting in dir include fix formatting of debug macro
545 lines
16 KiB
C++
545 lines
16 KiB
C++
/*****************************************************************************
|
|
*
|
|
* This file is part of Mapnik (c++ mapping toolkit)
|
|
*
|
|
* Copyright (C) 2021 Artem Pavlenko
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#ifndef MAPNIK_UTIL_GEOBUF_HPP
|
|
#define MAPNIK_UTIL_GEOBUF_HPP
|
|
|
|
#include <mapnik/debug.hpp>
|
|
#include <mapnik/value.hpp>
|
|
#include <mapnik/unicode.hpp>
|
|
#include <mapnik/feature.hpp>
|
|
#include <mapnik/feature_factory.hpp>
|
|
#include <mapnik/util/noncopyable.hpp>
|
|
#include <cmath>
|
|
#include <cassert>
|
|
#include <vector>
|
|
|
|
#include <boost/optional.hpp>
|
|
#include <protozero/pbf_reader.hpp>
|
|
|
|
namespace mapnik {
|
|
namespace util {
|
|
|
|
enum geometry_type_e {
|
|
Unknown = -1,
|
|
Point = 0,
|
|
MultiPoint = 1,
|
|
LineString = 2,
|
|
MultiLineString = 3,
|
|
Polygon = 4,
|
|
MultiPolygon = 5,
|
|
GeometryCollection = 6
|
|
};
|
|
|
|
namespace detail {
|
|
struct value_visitor
|
|
{
|
|
value_visitor(feature_impl& feature, transcoder const& tr, std::string const& name)
|
|
: feature_(feature)
|
|
, tr_(tr)
|
|
, name_(name)
|
|
{}
|
|
|
|
void operator()(std::string const& val) // unicode
|
|
{
|
|
feature_.put_new(name_, tr_.transcode(val.c_str()));
|
|
}
|
|
|
|
template<typename T>
|
|
void operator()(T val)
|
|
{
|
|
feature_.put_new(name_, val);
|
|
}
|
|
|
|
feature_impl& feature_;
|
|
transcoder const& tr_;
|
|
std::string const& name_;
|
|
};
|
|
} // namespace detail
|
|
|
|
template<typename FeatureCallback>
|
|
struct geobuf : util::noncopyable
|
|
{
|
|
using value_type = util::variant<bool, int, double, std::string>;
|
|
unsigned dim = 2;
|
|
double precision = std::pow(10, 6);
|
|
bool is_topo = false;
|
|
bool transformed = false;
|
|
std::size_t lengths = 0;
|
|
std::vector<std::string> keys_;
|
|
std::vector<value_type> values_;
|
|
protozero::pbf_reader reader_;
|
|
FeatureCallback& callback_;
|
|
context_ptr ctx_;
|
|
const std::unique_ptr<transcoder> tr_;
|
|
|
|
public:
|
|
// ctor
|
|
geobuf(char const* buf, std::size_t size, FeatureCallback& callback)
|
|
: reader_(buf, size)
|
|
, callback_(callback)
|
|
, ctx_(std::make_shared<context_type>())
|
|
, tr_(new transcoder("utf8"))
|
|
{}
|
|
|
|
void read()
|
|
{
|
|
while (reader_.next())
|
|
{
|
|
switch (reader_.tag())
|
|
{
|
|
case 1: // keys
|
|
{
|
|
keys_.push_back(reader_.get_string());
|
|
break;
|
|
}
|
|
case 2: {
|
|
dim = reader_.get_uint32();
|
|
break;
|
|
}
|
|
case 3: {
|
|
precision = std::pow(10, reader_.get_uint32());
|
|
break;
|
|
}
|
|
case 4: {
|
|
auto feature_collection = reader_.get_message();
|
|
read_feature_collection(feature_collection);
|
|
break;
|
|
}
|
|
case 5: {
|
|
// standalone Feature
|
|
auto message = reader_.get_message();
|
|
read_feature(message);
|
|
break;
|
|
}
|
|
case 6: {
|
|
// standalone Geometry
|
|
auto feature = feature_factory::create(ctx_, 1);
|
|
auto message = reader_.get_message();
|
|
feature->set_geometry(std::move(read_geometry(message)));
|
|
callback_(feature);
|
|
break;
|
|
}
|
|
default:
|
|
MAPNIK_LOG_DEBUG(geobuf) << "Unsupported tag=" << reader_.tag();
|
|
reader_.skip();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
double transform(std::int64_t input) { return (transformed) ? (static_cast<double>(input)) : (input / precision); }
|
|
|
|
template<typename T>
|
|
void read_value(T& reader)
|
|
{
|
|
while (reader.next())
|
|
{
|
|
switch (reader.tag())
|
|
{
|
|
case 1: {
|
|
values_.emplace_back(reader.get_string());
|
|
break;
|
|
}
|
|
case 2: {
|
|
values_.emplace_back(reader.get_double());
|
|
break;
|
|
}
|
|
case 3: {
|
|
values_.emplace_back(static_cast<int>(reader.get_uint32()));
|
|
break;
|
|
}
|
|
case 4: {
|
|
values_.emplace_back(-static_cast<int>(reader.get_uint32()));
|
|
break;
|
|
}
|
|
case 5: {
|
|
values_.emplace_back(reader.get_bool());
|
|
break;
|
|
}
|
|
case 6: {
|
|
values_.emplace_back(reader.get_string()); // JSON value
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename T, typename Feature>
|
|
void read_props(T& reader, Feature& feature)
|
|
{
|
|
auto pi = reader.get_packed_uint32();
|
|
for (auto it = pi.first; it != pi.second; ++it)
|
|
{
|
|
auto key_index = *it++;
|
|
auto value_index = *it;
|
|
assert(key_index < keys_.size());
|
|
assert(value_index < values_.size());
|
|
std::string const& name = keys_[key_index];
|
|
util::apply_visitor(detail::value_visitor(feature, *tr_, name), values_[value_index]);
|
|
}
|
|
values_.clear();
|
|
}
|
|
|
|
template<typename T>
|
|
void read_feature(T& reader)
|
|
{
|
|
auto feature = feature_factory::create(ctx_, 1);
|
|
while (reader.next())
|
|
{
|
|
switch (reader.tag())
|
|
{
|
|
case 1: {
|
|
auto message = reader.get_message();
|
|
auto geom = read_geometry(message);
|
|
feature->set_geometry(std::move(geom));
|
|
break;
|
|
}
|
|
case 11: {
|
|
auto feature_id = reader.get_string();
|
|
break;
|
|
}
|
|
case 12: {
|
|
feature->set_id(reader.get_sint64());
|
|
break;
|
|
}
|
|
case 13: {
|
|
auto message = reader.get_message();
|
|
read_value(message);
|
|
break;
|
|
}
|
|
case 14: {
|
|
// feature props
|
|
read_props(reader, *feature);
|
|
break;
|
|
}
|
|
case 15: {
|
|
// generic props
|
|
read_props(reader, *feature);
|
|
break;
|
|
}
|
|
default:
|
|
MAPNIK_LOG_DEBUG(geobuf) << "Unsupported tag=" << reader.tag();
|
|
break;
|
|
}
|
|
}
|
|
callback_(feature);
|
|
}
|
|
|
|
template<typename T>
|
|
void read_feature_collection(T& reader)
|
|
{
|
|
while (reader.next())
|
|
{
|
|
switch (reader.tag())
|
|
{
|
|
case 1: {
|
|
auto message = reader.get_message();
|
|
read_feature(message);
|
|
break;
|
|
}
|
|
case 13: {
|
|
auto message = reader.get_message();
|
|
read_value(message);
|
|
break;
|
|
}
|
|
default: {
|
|
reader.skip();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
geometry::point<double> read_point(T& reader)
|
|
{
|
|
double x = 0.0;
|
|
double y = 0.0;
|
|
auto pi = reader.get_packed_sint64();
|
|
std::size_t count = 0;
|
|
for (auto it = pi.first; it != pi.second; ++it, ++count)
|
|
{
|
|
if (count == 0)
|
|
x = transform(*it);
|
|
else if (count == 1)
|
|
y = transform(*it);
|
|
}
|
|
return geometry::point<double>(x, y);
|
|
}
|
|
|
|
template<typename T>
|
|
geometry::geometry<double>
|
|
read_coords(T& reader, geometry_type_e type, boost::optional<std::vector<std::uint32_t>> const& lengths)
|
|
{
|
|
geometry::geometry<double> geom = geometry::geometry_empty();
|
|
switch (type)
|
|
{
|
|
case Point: {
|
|
geom = read_point(reader);
|
|
break;
|
|
}
|
|
case Polygon: {
|
|
geom = read_polygon(reader, lengths);
|
|
break;
|
|
}
|
|
case LineString: {
|
|
geom = read_line_string(reader);
|
|
break;
|
|
}
|
|
case MultiPoint: {
|
|
geom = read_multi_point(reader);
|
|
break;
|
|
}
|
|
case MultiLineString: {
|
|
geom = read_multi_linestring(reader, lengths);
|
|
break;
|
|
}
|
|
case MultiPolygon: {
|
|
geom = read_multi_polygon(reader, lengths);
|
|
break;
|
|
}
|
|
default: {
|
|
reader.skip();
|
|
break;
|
|
}
|
|
}
|
|
return geom;
|
|
}
|
|
|
|
template<typename T>
|
|
std::vector<std::uint32_t> read_lengths(T& reader)
|
|
{
|
|
std::vector<std::uint32_t> lengths;
|
|
auto pi = reader.get_packed_uint32();
|
|
for (auto it = pi.first; it != pi.second; ++it)
|
|
{
|
|
lengths.push_back(*it);
|
|
}
|
|
return lengths;
|
|
}
|
|
|
|
template<typename T, typename Iterator, typename Ring>
|
|
void read_linear_ring(T& reader, Iterator begin, Iterator end, Ring& ring, bool close = false)
|
|
{
|
|
double x = 0.0;
|
|
double y = 0.0;
|
|
std::size_t count = 0;
|
|
for (auto it = begin; it != end; ++it)
|
|
{
|
|
std::int64_t delta = *it;
|
|
auto d = count % dim;
|
|
if (d == 0)
|
|
x += delta;
|
|
else if (d == 1)
|
|
{
|
|
y += delta;
|
|
ring.emplace_back(transform(x), transform(y));
|
|
}
|
|
// we're only interested in X and Y, ignoring any extra coordinates
|
|
++count;
|
|
}
|
|
if (close && !ring.empty())
|
|
{
|
|
ring.emplace_back(ring.front());
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
geometry::multi_point<double> read_multi_point(T& reader)
|
|
{
|
|
geometry::multi_point<double> multi_point;
|
|
double x = 0.0;
|
|
double y = 0.0;
|
|
auto pi = reader.get_packed_sint64();
|
|
std::size_t count = 0;
|
|
for (auto it = pi.first; it != pi.second; ++it)
|
|
{
|
|
auto delta = *it;
|
|
auto d = count % dim;
|
|
if (d == 0)
|
|
x += delta;
|
|
else if (d == 1)
|
|
{
|
|
y += delta;
|
|
geometry::point<double> pt(transform(x), transform(y));
|
|
multi_point.push_back(std::move(pt));
|
|
}
|
|
++count;
|
|
}
|
|
return multi_point;
|
|
}
|
|
|
|
template<typename T>
|
|
geometry::line_string<double> read_line_string(T& reader)
|
|
{
|
|
geometry::line_string<double> line;
|
|
auto pi = reader.get_packed_sint64();
|
|
line.reserve(pi.size());
|
|
read_linear_ring(reader, pi.first, pi.second, line);
|
|
return line;
|
|
}
|
|
|
|
template<typename T>
|
|
geometry::multi_line_string<double>
|
|
read_multi_linestring(T& reader, boost::optional<std::vector<std::uint32_t>> const& lengths)
|
|
{
|
|
geometry::multi_line_string<double> multi_line;
|
|
multi_line.reserve(!lengths ? 1 : lengths->size());
|
|
auto pi = reader.get_packed_sint64();
|
|
if (!lengths)
|
|
{
|
|
geometry::line_string<double> line;
|
|
line.reserve(pi.size());
|
|
read_linear_ring(reader, pi.first, pi.second, line);
|
|
multi_line.push_back(std::move(line));
|
|
}
|
|
else
|
|
{
|
|
for (auto len : *lengths)
|
|
{
|
|
geometry::line_string<double> line;
|
|
line.reserve(len);
|
|
read_linear_ring(reader, pi.first, std::next(pi.first, dim * len), line);
|
|
multi_line.push_back(std::move(line));
|
|
std::advance(pi.first, dim * len);
|
|
}
|
|
}
|
|
return multi_line;
|
|
}
|
|
|
|
template<typename T>
|
|
geometry::polygon<double> read_polygon(T& reader, boost::optional<std::vector<std::uint32_t>> const& lengths)
|
|
{
|
|
geometry::polygon<double> poly;
|
|
poly.reserve(!lengths ? 1 : lengths->size());
|
|
auto pi = reader.get_packed_sint64();
|
|
if (!lengths)
|
|
{
|
|
geometry::linear_ring<double> ring;
|
|
ring.reserve(pi.size());
|
|
read_linear_ring(reader, pi.first, pi.second, ring, true);
|
|
poly.push_back(std::move(ring));
|
|
}
|
|
else
|
|
{
|
|
for (auto len : *lengths)
|
|
{
|
|
geometry::linear_ring<double> ring;
|
|
ring.reserve(len);
|
|
read_linear_ring(reader, pi.first, std::next(pi.first, dim * len), ring, true);
|
|
poly.push_back(std::move(ring));
|
|
std::advance(pi.first, dim * len);
|
|
}
|
|
}
|
|
return poly;
|
|
}
|
|
|
|
template<typename T>
|
|
geometry::multi_polygon<double> read_multi_polygon(T& reader,
|
|
boost::optional<std::vector<std::uint32_t>> const& lengths)
|
|
{
|
|
geometry::multi_polygon<double> multi_poly;
|
|
if (!lengths)
|
|
{
|
|
auto poly = read_polygon(reader, lengths);
|
|
multi_poly.push_back(std::move(poly));
|
|
}
|
|
else if ((*lengths).size() > 0)
|
|
{
|
|
int j = 1;
|
|
auto pi = reader.get_packed_sint64();
|
|
for (std::size_t i = 0; i < (*lengths)[0]; ++i)
|
|
{
|
|
geometry::polygon<double> poly;
|
|
for (std::size_t k = 0; k < (*lengths)[j]; ++k)
|
|
{
|
|
geometry::linear_ring<double> ring;
|
|
std::size_t len = (*lengths)[j + k + 1];
|
|
ring.reserve(len);
|
|
read_linear_ring(reader, pi.first, std::next(pi.first, len * dim), ring, true);
|
|
poly.push_back(std::move(ring));
|
|
std::advance(pi.first, len * dim);
|
|
}
|
|
multi_poly.push_back(std::move(poly));
|
|
j += (*lengths)[j] + 1;
|
|
}
|
|
}
|
|
return multi_poly;
|
|
}
|
|
|
|
template<typename T>
|
|
geometry::geometry<double> read_geometry(T& reader)
|
|
{
|
|
geometry::geometry<double> geom = geometry::geometry_empty();
|
|
geometry_type_e type = Unknown;
|
|
boost::optional<std::vector<std::uint32_t>> lengths;
|
|
while (reader.next())
|
|
{
|
|
switch (reader.tag())
|
|
{
|
|
case 1: // type
|
|
{
|
|
type = static_cast<geometry_type_e>(reader.get_uint32());
|
|
break;
|
|
}
|
|
case 2: {
|
|
auto val = read_lengths(reader);
|
|
if (!val.empty())
|
|
lengths = std::move(val);
|
|
break;
|
|
}
|
|
case 3: {
|
|
geom = std::move(read_coords(reader, type, lengths));
|
|
break;
|
|
}
|
|
case 4: {
|
|
if (geom.is<geometry::geometry_empty>())
|
|
geom = geometry::geometry_collection<double>();
|
|
auto& collection = geom.get<geometry::geometry_collection<double>>();
|
|
auto message = reader.get_message();
|
|
collection.push_back(std::move(read_geometry(message)));
|
|
break;
|
|
}
|
|
case 13: {
|
|
auto value = reader.get_message();
|
|
read_value(value);
|
|
break;
|
|
}
|
|
default: {
|
|
reader.skip();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return geom;
|
|
}
|
|
};
|
|
|
|
} // namespace util
|
|
} // namespace mapnik
|
|
|
|
#endif // MAPNIK_UTIL_GEOBUF_HPP
|