/***************************************************************************** * * This file is part of Mapnik (c++ mapping toolkit) * * Copyright (C) 2017 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 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; poly.reserve(num_rings); 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); } poly.push_back(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