545 lines
16 KiB
C++
545 lines
16 KiB
C++
/*****************************************************************************
|
|
*
|
|
* This file is part of Mapnik (c++ mapping toolkit)
|
|
*
|
|
* Copyright (C) 2024 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 <optional>
|
|
|
|
#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, std::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,
|
|
std::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, std::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,
|
|
std::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;
|
|
std::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
|