/***************************************************************************** * * 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 #include #include #include #include #include #include #include #include #include #include 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 void operator()(T val) { feature_.put_new(name_, val); } feature_impl& feature_; transcoder const& tr_; std::string const& name_; }; } // namespace detail template struct geobuf : util::noncopyable { using value_type = util::variant; unsigned dim = 2; double precision = std::pow(10, 6); bool is_topo = false; bool transformed = false; std::size_t lengths = 0; std::vector keys_; std::vector values_; protozero::pbf_reader reader_; FeatureCallback& callback_; context_ptr ctx_; const std::unique_ptr tr_; public: // ctor geobuf(char const* buf, std::size_t size, FeatureCallback& callback) : reader_(buf, size) , callback_(callback) , ctx_(std::make_shared()) , 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(input)) : (input / precision); } template 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(reader.get_uint32())); break; } case 4: { values_.emplace_back(-static_cast(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 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 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 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 geometry::point 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(x, y); } template geometry::geometry read_coords(T& reader, geometry_type_e type, std::optional> const& lengths) { geometry::geometry 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 std::vector read_lengths(T& reader) { std::vector lengths; auto pi = reader.get_packed_uint32(); for (auto it = pi.first; it != pi.second; ++it) { lengths.push_back(*it); } return lengths; } template 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 geometry::multi_point read_multi_point(T& reader) { geometry::multi_point 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 pt(transform(x), transform(y)); multi_point.push_back(std::move(pt)); } ++count; } return multi_point; } template geometry::line_string read_line_string(T& reader) { geometry::line_string line; auto pi = reader.get_packed_sint64(); line.reserve(pi.size()); read_linear_ring(reader, pi.first, pi.second, line); return line; } template geometry::multi_line_string read_multi_linestring(T& reader, std::optional> const& lengths) { geometry::multi_line_string multi_line; multi_line.reserve(!lengths ? 1 : lengths->size()); auto pi = reader.get_packed_sint64(); if (!lengths) { geometry::line_string 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 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 geometry::polygon read_polygon(T& reader, std::optional> const& lengths) { geometry::polygon poly; poly.reserve(!lengths ? 1 : lengths->size()); auto pi = reader.get_packed_sint64(); if (!lengths) { geometry::linear_ring 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 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 geometry::multi_polygon read_multi_polygon(T& reader, std::optional> const& lengths) { geometry::multi_polygon 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 poly; for (std::size_t k = 0; k < (*lengths)[j]; ++k) { geometry::linear_ring 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 geometry::geometry read_geometry(T& reader) { geometry::geometry geom = geometry::geometry_empty(); geometry_type_e type = Unknown; std::optional> lengths; while (reader.next()) { switch (reader.tag()) { case 1: // type { type = static_cast(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()) geom = geometry::geometry_collection(); auto& collection = geom.get>(); 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