diff --git a/include/mapnik/wkb.hpp b/include/mapnik/wkb.hpp index 53f1e850d..98b3d30c9 100644 --- a/include/mapnik/wkb.hpp +++ b/include/mapnik/wkb.hpp @@ -60,9 +60,11 @@ class MAPNIK_DECL geometry_utils : private util::noncopyable { public: - static mapnik::geometry::geometry from_wkb(const char* wkb, - std::size_t size, - wkbFormat format = wkbGeneric); + static geometry::geometry from_wkb(char const* wkb, + std::size_t size, + wkbFormat format = wkbGeneric); + + static geometry::geometry from_twkb(char const* twkb, std::size_t size); }; } diff --git a/src/build.py b/src/build.py index c586cf130..6dd0f24a9 100644 --- a/src/build.py +++ b/src/build.py @@ -205,6 +205,7 @@ source = Split( rule.cpp save_map.cpp wkb.cpp + twkb.cpp projection.cpp proj_transform.cpp scale_denominator.cpp diff --git a/src/twkb.cpp b/src/twkb.cpp new file mode 100644 index 000000000..f1928e5a6 --- /dev/null +++ b/src/twkb.cpp @@ -0,0 +1,387 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2016 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 + * + *****************************************************************************/ + + +// mapnik +#include +#include +#include +#include +#include +#include + +namespace mapnik { namespace detail { + +struct twkb_reader : mapnik::util::noncopyable +{ +private: + const char *twkb_; + size_t size_; + unsigned int pos_; + // Metadata on the geometry we are parsing + uint8_t twkb_type_; + uint8_t has_bbox_; + uint8_t has_size_; + uint8_t has_idlist_; + uint8_t has_z_; + uint8_t has_m_; + uint8_t is_empty_; + // Precision factors to convert ints to double + double factor_xy_; + double factor_z_; + double factor_m_; + // An array to keep delta values from 4 dimensions + int64_t coord_x_; + int64_t coord_y_; + int64_t coord_z_; + int64_t coord_m_; + +public: + enum twkbGeometryType : std::uint8_t + { + twkbPoint = 1, + twkbLineString = 2, + twkbPolygon = 3, + twkbMultiPoint = 4, + twkbMultiLineString = 5, + twkbMultiPolygon = 6, + twkbGeometryCollection = 7 + }; + + twkb_reader(char const* twkb, size_t size) + : twkb_(twkb), size_(size), pos_(0), twkb_type_(0), // Geometry type + has_bbox_(0), // Bounding box? + has_size_(0), // Size attribute? + has_idlist_(0), // Presence of X/Y + has_z_(0), // Presence of Z + has_m_(0), // Presence of M + is_empty_(0), // Empty? + factor_xy_(0.0), // Expansion factor for X/Y + factor_z_(0.0), // Expansion factor for Z + factor_m_(0.0) // Expansion factor for M + {} + + mapnik::geometry::geometry read() + { + mapnik::geometry::geometry geom = mapnik::geometry::geometry_empty(); + // Read the metadata bytes, populating all the + // information about optional fields, extended (z/m) dimensions + // expansion factors and so on + read_header(); + + // Each new read call has to reset the coordinate accumulators + coord_x_ = 0; // Accumulation register (x) + coord_y_ = 0; // Accumulation register (y) + coord_z_ = 0; // Accumulation register (z) + coord_m_ = 0; // Accumulation register (m) + + // If the geometry is empty, add nothing to the paths array + if (is_empty_) + return geom; + + // Read the [optional] size information + if (has_size_) + size_ = read_unsigned_integer(); + + // Read the [optional] bounding box information + if (has_bbox_) read_bbox(); + + switch (twkb_type_) + { + case twkbPoint: + geom = read_point(); + break; + case twkbLineString: + geom = read_linestring(); + break; + case twkbPolygon: + geom = read_polygon(); + break; + case twkbMultiPoint: + geom = read_multipoint(); + break; + case twkbMultiLineString: + geom = read_multilinestring(); + break; + case twkbMultiPolygon: + geom = read_multipolygon(); + break; + case twkbGeometryCollection: + geom = read_collection(); + default: + break; + } + return geom; + } + +private: + int64_t unzigzag64(uint64_t val) + { + if (val & 0x01) + return -1 * (int64_t)((val + 1) >> 1); + else + return (int64_t)(val >> 1); + } + + int32_t unzigzag32(uint32_t val) + { + if (val & 0x01) return -1 * (int32_t)((val + 1) >> 1); + else return (int32_t)(val >> 1); + } + + int8_t unzigzag8(uint8_t val) + { + if (val & 0x01) return -1 * (int8_t)((val + 1) >> 1); + else return (int8_t)(val >> 1); + } + + // Read from signed 64bit varint + int64_t read_signed_integer() { return unzigzag64(read_unsigned_integer()); } + + // Read from unsigned 64bit varint + uint64_t read_unsigned_integer() + { + uint64_t nVal = 0; + int nShift = 0; + uint8_t nByte; + + // Check so we don't read beyond the twkb + while (pos_ < size_) + { + nByte = twkb_[pos_]; + // We get here when there is more to read in the input varInt + // Here we take the least significant 7 bits of the read + // byte and put it in the most significant place in the result variable. + nVal |= ((uint64_t)(nByte & 0x7f)) << nShift; + // move the "cursor" of the input buffer step (8 bits) + pos_++; + // move the cursor in the resulting variable (7 bits) + nShift += 7; + // Hibit isn't set, so this is the last byte + if (!(nByte & 0x80)) { + return nVal; + } + } + return 0; + } + + // Every TWKB geometry starts with a metadata header + // + // type_and_dims byte + // metadata_header byte + // [extended_dims] byte + // [size] uvarint + // [bounds] bbox + // + void read_header() + { + uint8_t type_precision = twkb_[pos_++]; + uint8_t metadata = twkb_[pos_++]; + twkb_type_ = type_precision & 0x0F; + int8_t precision = unzigzag8((type_precision & 0xF0) >> 4); + factor_xy_ = std::pow(10, static_cast(precision)); + has_bbox_ = metadata & 0x01; + has_size_ = (metadata & 0x02) >> 1; + has_idlist_ = (metadata & 0x04) >> 2; + uint8_t zm = (metadata & 0x08) >> 3; + is_empty_ = (metadata & 0x10) >> 4; + + // Flag for higher dimensions means read a third byte + // of extended dimension information + if (zm) + { + zm = twkb_[pos_++]; + // Strip Z/M presence and precision from ext byte + has_z_ = (zm & 0x01); + has_m_ = (zm & 0x02) >> 1; + // Convert the precision into factor + int8_t precision_z = (zm & 0x1C) >> 2; + int8_t precision_m = (zm & 0xE0) >> 5; + factor_z_ = pow(10, (double)precision_z); + factor_m_ = pow(10, (double)precision_m); + } + } + + void read_bbox() + { + // we have nowhere to store this box information + // for now, so we'll just move the read head forward + // an appropriate number of times + if (has_bbox_) + { + read_signed_integer(); // uint64_t xmin + read_signed_integer(); // uint64_t xdelta + read_signed_integer(); // uint64_t ymin + read_signed_integer(); // uint64_t ydelta + if (has_z_) + { + read_signed_integer(); // uint64_t zmin + read_signed_integer(); // uint64_t zdelta + } + if (has_m_) + { + read_signed_integer(); // uint64_t mmin + read_signed_integer(); // uint64_t mdelta + } + } + } + + void read_idlist(unsigned int num_ids) + { + // we have nowhere to store this id information + // for now, so we'll just move the read head + // forward an appropriate number of times + if (has_idlist_) + { + for (unsigned int i = 0; i < num_ids; ++i) + { + read_signed_integer(); // uint64_t id + } + } + } + + template + void read_coords(Ring & ring, std::size_t num_points) + { + for (std::size_t i = 0; i < num_points; ++i) + { + coord_x_ += read_signed_integer(); + coord_y_ += read_signed_integer(); + ring.emplace_back( coord_x_ / factor_xy_, coord_y_ / factor_xy_); + // Skip Z and M + if (has_z_) coord_z_ += read_signed_integer(); + if (has_m_) coord_m_ += read_signed_integer(); + } + } + + mapnik::geometry::point read_point() + { + coord_x_ += read_signed_integer(); + coord_y_ += read_signed_integer(); + double x = coord_x_ / factor_xy_; + double y = coord_y_ / factor_xy_; + return mapnik::geometry::point(x, y); + } + + mapnik::geometry::multi_point read_multipoint() + { + mapnik::geometry::multi_point multi_point; + unsigned int num_points = read_unsigned_integer(); + if (has_idlist_) read_idlist(num_points); + + if (num_points > 0) + { + multi_point.reserve(num_points); + for (unsigned int i = 0; i < num_points; ++i) + { + multi_point.emplace_back(read_point()); + } + } + return multi_point; + } + + mapnik::geometry::line_string read_linestring() + { + mapnik::geometry::line_string line; + unsigned int num_points = read_unsigned_integer(); + if (num_points > 0) + { + line.reserve(num_points); + read_coords>(line, num_points); + } + return line; + } + + mapnik::geometry::multi_line_string read_multilinestring() + { + mapnik::geometry::multi_line_string multi_line; + unsigned int num_lines = read_unsigned_integer(); + if (has_idlist_) read_idlist(num_lines); + multi_line.reserve(num_lines); + for (unsigned int i = 0; i < num_lines; ++i) + { + multi_line.push_back(read_linestring()); + } + return multi_line; + } + + mapnik::geometry::polygon read_polygon() + { + unsigned int num_rings = read_unsigned_integer(); + mapnik::geometry::polygon poly; + if (num_rings > 1) + { + poly.interior_rings.reserve(num_rings - 1); + } + + for (unsigned int i = 0; i < num_rings; ++i) + { + mapnik::geometry::linear_ring ring; + unsigned int num_points = read_unsigned_integer(); + if (num_points > 0) + { + ring.reserve(num_points); + read_coords>(ring, num_points); + } + if ( i == 0) poly.set_exterior_ring(std::move(ring)); + else poly.add_hole(std::move(ring)); + } + return poly; + } + + mapnik::geometry::multi_polygon read_multipolygon() + { + mapnik::geometry::multi_polygon multi_poly; + unsigned int num_polys = read_unsigned_integer(); + if (has_idlist_) read_idlist(num_polys); + for (unsigned int i = 0; i < num_polys; ++i) + { + multi_poly.push_back(read_polygon()); + } + return multi_poly; + } + + mapnik::geometry::geometry_collection read_collection() + { + unsigned int num_geometries = read_unsigned_integer(); + mapnik::geometry::geometry_collection collection; + if (has_idlist_) read_idlist(num_geometries); + for (unsigned int i = 0; i < num_geometries; ++i) + { + collection.push_back(read()); + } + return collection; + } +}; + +} // namespace detail + +mapnik::geometry::geometry geometry_utils::from_twkb(const char* wkb, std::size_t size) +{ + detail::twkb_reader reader(wkb, size); + mapnik::geometry::geometry geom(reader.read()); + // note: this will only be applied to polygons + mapnik::geometry::correct(geom); + return geom; +} + +} // namespace mapnik