mapnik/plugins/input/geobuf/geobuf.hpp
2024-07-22 10:20:47 +01:00

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