Refactor expire code to not use WKB directly

This disentangles the WKB code from the expire code. There are further
refactorings to be done, but they will come in later commits to keep
things separate.
HEAD
Jochen Topf 2021-12-16 21:27:58 +01:00
parent 27ae4652af
commit c27ca826a7
9 changed files with 612 additions and 275 deletions

View File

@ -23,6 +23,7 @@
#include "expire-tiles.hpp"
#include "format.hpp"
#include "geom-functions.hpp"
#include "logging.hpp"
#include "options.hpp"
#include "reprojection.hpp"
@ -134,6 +135,52 @@ void expire_tiles::coords_to_tile(double lon, double lat, double *tilex,
*tiley = map_width * (0.5 - c.y() / EARTH_CIRCUMFERENCE);
}
void expire_tiles::from_point_list(geom::point_list_t const &list)
{
for_each_segment(list, [&](geom::point_t a, geom::point_t b) {
from_line(a.x(), a.y(), b.x(), b.y());
});
}
void expire_tiles::from_geometry(geom::geometry_t const &geom, osmid_t osm_id)
{
if (geom.srid() != 3857) {
return;
}
if (geom.is_point()) {
auto const box = geom::envelope(geom);
from_bbox(box);
} else if (geom.is_linestring()) {
from_point_list(geom.get<geom::linestring_t>());
} else if (geom.is_multilinestring()) {
for (auto const &list : geom.get<geom::multilinestring_t>()) {
from_point_list(list);
}
} else if (geom.is_polygon() || geom.is_multipolygon()) {
auto const box = geom::envelope(geom);
if (from_bbox(box)) {
/* Bounding box too big - just expire tiles on the line */
log_debug("Large polygon ({:.0f} x {:.0f} metres, OSM ID {})"
" - only expiring perimeter",
box.max_x() - box.min_x(), box.max_y() - box.min_y(), osm_id);
if (geom.is_polygon()) {
from_point_list(geom.get<geom::polygon_t>().outer());
for (auto const &inner : geom.get<geom::polygon_t>().inners()) {
from_point_list(inner);
}
} else if (geom.is_multipolygon()) {
for (auto const &polygon : geom.get<geom::multipolygon_t>()) {
from_point_list(polygon.outer());
for (auto const &inner : polygon.inners()) {
from_point_list(inner);
}
}
}
}
}
}
/*
* Expire tiles that a line crosses
*/
@ -213,6 +260,16 @@ void expire_tiles::from_line(double lon_a, double lat_a, double lon_b,
/*
* Expire tiles within a bounding box
*/
int expire_tiles::from_bbox(geom::box_t const &box)
{
double const min_lon = box.min_x();
double const min_lat = box.min_y();
double const max_lon = box.max_x();
double const max_lat = box.max_y();
return from_bbox(min_lon, min_lat, max_lon, max_lat);
}
int expire_tiles::from_bbox(double min_lon, double min_lat, double max_lon,
double max_lat)
{
@ -266,118 +323,6 @@ int expire_tiles::from_bbox(double min_lon, double min_lat, double max_lon,
return 0;
}
void expire_tiles::from_wkb(std::string const &wkb, osmid_t osm_id)
{
if (maxzoom == 0) {
return;
}
ewkb::parser_t parse{wkb};
switch (parse.read_header()) {
case ewkb::wkb_point:
from_wkb_point(&parse);
break;
case ewkb::wkb_line:
from_wkb_line(&parse);
break;
case ewkb::wkb_polygon:
from_wkb_polygon(&parse, osm_id);
break;
case ewkb::wkb_multi_line: {
auto num = parse.read_length();
for (unsigned i = 0; i < num; ++i) {
parse.read_header();
from_wkb_line(&parse);
}
break;
}
case ewkb::wkb_multi_polygon: {
auto num = parse.read_length();
for (unsigned i = 0; i < num; ++i) {
parse.read_header();
from_wkb_polygon(&parse, osm_id);
}
break;
}
default:
log_warn("OSM id {}: Unknown geometry type, cannot expire.", osm_id);
}
}
void expire_tiles::from_wkb_point(ewkb::parser_t *wkb)
{
auto const c = wkb->read_point();
from_bbox(c.x, c.y, c.x, c.y);
}
void expire_tiles::from_wkb_line(ewkb::parser_t *wkb)
{
auto const sz = wkb->read_length();
if (sz == 0) {
return;
}
if (sz == 1) {
from_wkb_point(wkb);
} else {
auto prev = wkb->read_point();
for (size_t i = 1; i < sz; ++i) {
auto const cur = wkb->read_point();
from_line(prev.x, prev.y, cur.x, cur.y);
prev = cur;
}
}
}
void expire_tiles::from_wkb_polygon(ewkb::parser_t *wkb, osmid_t osm_id)
{
auto const num_rings = wkb->read_length();
assert(num_rings > 0);
auto const start = wkb->save_pos();
auto const num_pt = wkb->read_length();
auto const initpt = wkb->read_point();
osmium::geom::Coordinates min{initpt};
osmium::geom::Coordinates max{initpt};
for (size_t i = 1; i < num_pt; ++i) {
auto const c = wkb->read_point();
if (c.x < min.x) {
min.x = c.x;
}
if (c.y < min.y) {
min.y = c.y;
}
if (c.x > max.x) {
max.x = c.x;
}
if (c.y > max.y) {
max.y = c.y;
}
}
if (from_bbox(min.x, min.y, max.x, max.y)) {
/* Bounding box too big - just expire tiles on the line */
log_debug("Large polygon ({:.0f} x {:.0f} metres, OSM ID {})"
" - only expiring perimeter",
max.x - min.x, max.y - min.y, osm_id);
wkb->rewind(start);
for (unsigned ring = 0; ring < num_rings; ++ring) {
from_wkb_line(wkb);
}
} else {
// ignore inner rings
for (unsigned ring = 1; ring < num_rings; ++ring) {
auto const inum_pt = wkb->read_length();
wkb->skip_points(inum_pt);
}
}
}
int expire_tiles::from_result(pg_result_t const &result, osmid_t osm_id)
{
//bail if we dont care about expiry
@ -389,8 +334,7 @@ int expire_tiles::from_result(pg_result_t const &result, osmid_t osm_id)
auto const num_tuples = result.num_tuples();
for (int i = 0; i < num_tuples; ++i) {
char const *const wkb = result.get_value(i, 0);
auto const binwkb = ewkb::parser_t::wkb_from_hex(wkb);
from_wkb(binwkb, osm_id);
from_geometry(ewkb_to_geom(decode_hex(wkb)), osm_id);
}
//return how many rows were affected

View File

@ -13,17 +13,13 @@
#include <memory>
#include <unordered_set>
#include "geom.hpp"
#include "geom-box.hpp"
#include "logging.hpp"
#include "osmtypes.hpp"
#include "pgsql.hpp"
class reprojection;
class table_t;
class tile;
namespace ewkb {
class parser_t;
} // namespace ewkb
/**
* \brief Simple struct for the x and y index of a tile ID.
@ -63,9 +59,11 @@ struct expire_tiles
bool enabled() const noexcept { return maxzoom != 0; }
void from_geometry(geom::geometry_t const &geom, osmid_t osm_id);
int from_bbox(geom::box_t const &box);
int from_bbox(double min_lon, double min_lat, double max_lon,
double max_lat);
void from_wkb(std::string const &wkb, osmid_t osm_id);
/**
* Expire tiles based on an osm id.
@ -181,9 +179,7 @@ private:
uint32_t normalise_tile_x_coord(int x) const;
void from_line(double lon_a, double lat_a, double lon_b, double lat_b);
void from_wkb_point(ewkb::parser_t *wkb);
void from_wkb_line(ewkb::parser_t *wkb);
void from_wkb_polygon(ewkb::parser_t *wkb, osmid_t osm_id);
void from_point_list(geom::point_list_t const &list);
double tile_width;
double max_bbox;

View File

@ -13,6 +13,7 @@
#include "geom.hpp"
#include "reprojection.hpp"
#include <utility>
#include <vector>
/**
@ -32,6 +33,24 @@ double distance(point_t p1, point_t p2) noexcept;
*/
point_t interpolate(point_t p1, point_t p2, double frac) noexcept;
/**
* Iterate over all segments (connections between two points) in a point list
* and call a function with both points.
*
* \pre \code !list.empty() \endcode
*/
template <typename FUNC>
void for_each_segment(point_list_t const &list, FUNC&& func)
{
assert(!list.empty());
auto it = list.cbegin();
auto prev = it;
for (++it; it != list.cend(); ++it) {
std::forward<FUNC>(func)(*prev, *it);
prev = it;
}
}
/**
* Transform a geometry in 4326 into some other projection.
*

View File

@ -1262,8 +1262,7 @@ void output_flex_t::add_row(table_connection_t *table_connection,
auto const geoms = geom::split_multi(geom, split_multi);
for (auto const &sgeom : geoms) {
auto const wkb = geom_to_ewkb(sgeom);
m_expire.from_wkb(wkb, id);
m_expire.from_geometry(sgeom, id);
write_row(table_connection, object.type(), id, sgeom,
table.geom_column().srid());
}

View File

@ -67,7 +67,7 @@ void output_pgsql_t::pgsql_out_way(osmium::Way const &way, taglist_t *tags,
auto const wkb = geom_to_ewkb(projected_geom);
if (!wkb.empty()) {
m_expire.from_wkb(wkb, way.id());
m_expire.from_geometry(projected_geom, way.id());
if (m_enable_way_area) {
double const area = calculate_area(m_options.reproject_area,
geom, projected_geom);
@ -82,8 +82,8 @@ void output_pgsql_t::pgsql_out_way(osmium::Way const &way, taglist_t *tags,
auto const geoms = geom::split_multi(geom::segmentize(
geom::transform(geom::create_linestring(way), *m_proj), split_at));
for (auto const &sgeom : geoms) {
m_expire.from_geometry(sgeom, way.id());
auto const wkb = geom_to_ewkb(sgeom);
m_expire.from_wkb(wkb, way.id());
m_tables[t_line]->write_row(way.id(), *tags, wkb);
if (roads) {
m_tables[t_roads]->write_row(way.id(), *tags, wkb);
@ -164,9 +164,9 @@ void output_pgsql_t::node_add(osmium::Node const &node)
return;
}
auto const wkb =
geom_to_ewkb(geom::transform(geom::create_point(node), *m_proj));
m_expire.from_wkb(wkb, node.id());
auto const geom = geom::transform(geom::create_point(node), *m_proj);
m_expire.from_geometry(geom, node.id());
auto const wkb = geom_to_ewkb(geom);
m_tables[t_point]->write_row(node.id(), outtags, wkb);
}
@ -268,8 +268,8 @@ void output_pgsql_t::pgsql_process_relation(osmium::Relation const &rel)
}
auto const geoms = geom::split_multi(projected_geom);
for (auto const &sgeom : geoms) {
m_expire.from_geometry(sgeom, -rel.id());
auto const wkb = geom_to_ewkb(sgeom);
m_expire.from_wkb(wkb, -rel.id());
m_tables[t_line]->write_row(-rel.id(), outtags, wkb);
if (roads) {
m_tables[t_roads]->write_row(-rel.id(), outtags, wkb);
@ -283,8 +283,8 @@ void output_pgsql_t::pgsql_process_relation(osmium::Relation const &rel)
geom::create_multipolygon(rel, m_buffer), !m_options.enable_multi);
for (auto const &sgeom : geoms) {
auto const projected_geom = geom::transform(sgeom, *m_proj);
m_expire.from_geometry(projected_geom, -rel.id());
auto const wkb = geom_to_ewkb(projected_geom);
m_expire.from_wkb(wkb, -rel.id());
if (m_enable_way_area) {
double const area = calculate_area(m_options.reproject_area,
sgeom, projected_geom);

View File

@ -9,8 +9,42 @@
#include "wkb.hpp"
#include "format.hpp"
#include <array>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <string>
#include <utility>
namespace ewkb {
namespace {
enum geometry_type : uint32_t
{
wkb_point = 1,
wkb_line = 2,
wkb_polygon = 3,
wkb_multi_point = 4,
wkb_multi_line = 5,
wkb_multi_polygon = 6,
wkb_collection = 7,
wkb_srid = 0x20000000 // SRID-presence flag (EWKB)
};
enum wkb_byte_order_type_t : uint8_t
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
Endian = 1 // Little Endian
#else
Endian = 0, // Big Endian
#endif
};
template <typename T>
static void str_push(std::string *data, T value)
{
@ -78,8 +112,6 @@ static void write_polygon(std::string *data, geom::polygon_t const &geom,
}
}
namespace {
class make_ewkb_visitor
{
public:
@ -118,11 +150,12 @@ public:
data.reserve(2 * 13 + geom.size() * (2 * 8));
write_header(&data, wkb_multi_line, m_srid);
write_length(&data, 1);
write_linestring(&data, geom, 0);
} else {
// 13 byte header plus n sets of coordinates
data.reserve(13 + geom.size() * (2 * 8));
write_linestring(&data, geom, m_srid);
}
write_linestring(&data, geom, m_srid);
return data;
}
@ -134,8 +167,10 @@ public:
if (m_ensure_multi) {
write_header(&data, wkb_multi_polygon, m_srid);
write_length(&data, 1);
write_polygon(&data, geom, 0);
} else {
write_polygon(&data, geom, m_srid);
}
write_polygon(&data, geom, m_srid);
return data;
}
@ -186,6 +221,216 @@ private:
}; // class make_ewkb_visitor
class ewkb_parser_t
{
public:
explicit ewkb_parser_t(std::string const &wkb)
: m_it(wkb.data()), m_end(wkb.data() + wkb.size()),
m_max_length(wkb.size() / (sizeof(double) * 2))
{}
geom::geometry_t operator()()
{
geom::geometry_t geom;
if (m_it == m_end) {
return geom;
}
uint32_t const type = parse_header(&geom);
switch (type) {
case geometry_type::wkb_point:
parse_point(&geom.set<geom::point_t>());
break;
case geometry_type::wkb_line:
parse_point_list(&geom.set<geom::linestring_t>(), 2);
break;
case geometry_type::wkb_polygon:
parse_polygon(&geom.set<geom::polygon_t>());
break;
case geometry_type::wkb_multi_point:
// XXX not implemented yet
break;
case geometry_type::wkb_multi_line:
parse_multi_linestring(&geom);
break;
case geometry_type::wkb_multi_polygon:
parse_multi_polygon(&geom);
break;
default:
throw std::runtime_error{
"Invalid WKB geometry: Unknown geometry type {}"_format(type)};
}
if (m_it != m_end) {
throw std::runtime_error{"Invalid WKB geometry: Extra data at end"};
}
return geom;
}
private:
void check_bytes(uint32_t bytes) const
{
if (static_cast<std::size_t>(m_end - m_it) < bytes) {
throw std::runtime_error{"Invalid WKB geometry: Incomplete"};
}
}
uint32_t parse_uint32()
{
check_bytes(sizeof(uint32_t));
uint32_t data = 0;
std::memcpy(&data, m_it, sizeof(uint32_t));
m_it += sizeof(uint32_t);
return data;
}
/**
* Get length field and check it against an upper bound based on the
* maximum number of points which could theoretically be stored in a string
* of the size of the input string (each point takes up at least
* 2*sizeof(double) bytes. This prevents an invalid WKB from leading us to
* reserve huge amounts of memory without having to define a constant upper
* bound.
*/
uint32_t parse_length()
{
auto const length = parse_uint32();
if (length > m_max_length) {
throw std::runtime_error{"Invalid WKB geometry: Length too large"};
}
return length;
}
uint32_t parse_header(geom::geometry_t *geom = nullptr)
{
if (static_cast<uint8_t>(*m_it++) !=
ewkb::wkb_byte_order_type_t::Endian) {
throw std::runtime_error
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
"Geometries in the database are returned in big-endian byte "
"order. "
#else
"Geometries in the database are returned in little-endian byte "
"order. "
#endif
"osm2pgsql can only process geometries in native byte order."
};
}
auto type = parse_uint32();
if (type & ewkb::geometry_type::wkb_srid) {
if (!geom) {
// If geom is not set than this is one of the geometries
// in a GeometryCollection or a Multi... geometry. They
// should not have a SRID set, because the SRID is already
// on the outer geometry.
throw std::runtime_error{
"Invalid WKB geometry: SRID set in geometry of collection"};
}
type &= ~ewkb::geometry_type::wkb_srid;
geom->set_srid(static_cast<int>(parse_uint32()));
}
return type;
}
void parse_point(geom::point_t *point)
{
check_bytes(sizeof(double) * 2);
std::array<double, 2> data;
std::memcpy(&data[0], m_it, sizeof(double) * 2);
m_it += sizeof(double) * 2;
point->set_x(data[0]);
point->set_y(data[1]);
}
void parse_point_list(geom::point_list_t *points, uint32_t min_points)
{
auto num_points = parse_length();
if (num_points < min_points) {
throw std::runtime_error{
"Invalid WKB geometry: {} are not"
" enough points (must be at least {})"_format(num_points,
min_points)};
}
points->reserve(num_points);
while (num_points-- > 0) {
parse_point(&points->emplace_back());
}
}
void parse_polygon(geom::polygon_t *polygon)
{
auto num_rings = parse_length();
if (num_rings == 0) {
throw std::runtime_error{
"Invalid WKB geometry: Polygon without rings"};
}
parse_point_list(&polygon->outer(), 4);
polygon->inners().reserve(num_rings - 1);
while (--num_rings > 0) {
parse_point_list(&polygon->inners().emplace_back(), 4);
}
}
void parse_multi_linestring(geom::geometry_t *geom)
{
auto &multilinestring = geom->set<geom::multilinestring_t>();
auto num_geoms = parse_length();
if (num_geoms == 0) {
throw std::runtime_error{
"Invalid WKB geometry: Multilinestring without linestrings"};
}
multilinestring.reserve(num_geoms);
while (num_geoms-- > 0) {
auto &linestring = multilinestring.emplace_back();
uint32_t const type = parse_header();
if (type != geometry_type::wkb_line) {
throw std::runtime_error{
"Invalid WKB geometry: Multilinestring containing"
" something other than linestring: {}"_format(type)};
}
parse_point_list(&linestring, 2);
}
}
void parse_multi_polygon(geom::geometry_t *geom)
{
auto &multipolygon = geom->set<geom::multipolygon_t>();
auto num_geoms = parse_length();
if (num_geoms == 0) {
throw std::runtime_error{
"Invalid WKB geometry: Multipolygon without polygons"};
}
multipolygon.reserve(num_geoms);
while (num_geoms-- > 0) {
auto &polygon = multipolygon.emplace_back();
uint32_t const type = parse_header();
if (type != geometry_type::wkb_polygon) {
throw std::runtime_error{
"Invalid WKB geometry: Multipolygon containing"
" something other than polygon: {}"_format(type)};
}
parse_polygon(&polygon);
}
}
char const *m_it;
char const *m_end;
uint32_t m_max_length;
}; // class ewkb_parser_t
} // anonymous namespace
} // namespace ewkb
@ -195,3 +440,41 @@ std::string geom_to_ewkb(geom::geometry_t const &geom, bool ensure_multi)
return geom.visit(ewkb::make_ewkb_visitor{
static_cast<uint32_t>(geom.srid()), ensure_multi});
}
geom::geometry_t ewkb_to_geom(std::string const &wkb)
{
ewkb::ewkb_parser_t parser{wkb};
return parser();
}
unsigned char decode_hex_char(char c)
{
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
throw std::runtime_error{"Invalid wkb: Not a hex character"};
}
std::string decode_hex(char const *hex)
{
std::string wkb;
while (*hex != '\0') {
unsigned int const c = decode_hex_char(*hex++);
if (*hex == '\0') {
throw std::runtime_error{"Invalid wkb: Not a valid hex string"};
}
wkb += static_cast<char>((c << 4U) | decode_hex_char(*hex++));
}
return wkb;
}

View File

@ -12,14 +12,7 @@
#include "geom.hpp"
#include <osmium/geom/coordinates.hpp>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <string>
#include <utility>
/**
* \file
@ -27,137 +20,6 @@
* Functions for converting geometries from and to (E)WKB.
*/
namespace ewkb {
enum geometry_type : uint32_t
{
wkb_point = 1,
wkb_line = 2,
wkb_polygon = 3,
wkb_multi_point = 4,
wkb_multi_line = 5,
wkb_multi_polygon = 6,
wkb_collection = 7,
wkb_srid = 0x20000000 // SRID-presence flag (EWKB)
};
enum wkb_byte_order_type_t : uint8_t
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
Endian = 1 // Little Endian
#else
Endian = 0, // Big Endian
#endif
};
/**
* Class that allows to iterate over the elements of a ewkb geometry.
*
* Note: this class assumes that the wkb was created by ewkb::writer_t.
* It implements the exact opposite decoding.
*/
class parser_t
{
public:
inline static std::string wkb_from_hex(std::string const &wkb)
{
std::string out;
bool front = true;
char outc;
for (char c : wkb) {
c -= 48;
if (c > 9) {
c -= 7;
}
if (front) {
outc = char(c << 4);
front = false;
} else {
out += outc | c;
front = true;
}
}
if (out[0] != Endian) {
throw std::runtime_error
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
"Geometries in the database are returned in big-endian byte "
"order. "
#else
"Geometries in the database are returned in little-endian byte "
"order. "
#endif
"osm2pgsql can only process geometries in native byte order."
};
}
return out;
}
explicit parser_t(std::string const &wkb) noexcept : m_wkb(&wkb) {}
std::size_t save_pos() const noexcept { return m_pos; }
void rewind(std::size_t pos) noexcept { m_pos = pos; }
uint32_t read_header()
{
m_pos += sizeof(uint8_t); // skip endianess marker
auto const type = read_data<uint32_t>();
if (type & wkb_srid) {
m_pos += sizeof(uint32_t); // skip SRID
}
return type & 0xffU;
}
uint32_t read_length() { return read_data<uint32_t>(); }
osmium::geom::Coordinates read_point()
{
auto const x = read_data<double>();
auto const y = read_data<double>();
return osmium::geom::Coordinates{x, y};
}
void skip_points(std::size_t num)
{
auto const length = sizeof(double) * 2 * num;
check_available(length);
m_pos += length;
}
private:
void check_available(std::size_t length)
{
if (m_pos + length > m_wkb->size()) {
throw std::runtime_error{"Invalid EWKB geometry found"};
}
}
template <typename T>
T read_data()
{
check_available(sizeof(T));
T data;
std::memcpy(&data, m_wkb->data() + m_pos, sizeof(T));
m_pos += sizeof(T);
return data;
}
std::string const *m_wkb;
std::size_t m_pos = 0;
};
} // namespace ewkb
/**
* Convert single geometry to EWKB
*
@ -168,4 +30,25 @@ private:
std::string geom_to_ewkb(geom::geometry_t const &geom,
bool ensure_multi = false);
/**
* Convert EWKB geometry to geometry object. If the input is empty, a null
* geometry is returned. If the WKB can not be parsed, an exception is thrown.
*
* \param wkb Input EWKB geometry in binary format
* \returns Geometry
*/
geom::geometry_t ewkb_to_geom(std::string const &wkb);
/**
* Decode one hex character (0-9A-F or 0-9a-f) and return its value. Throw
* an exception if not a valid hex character.
*/
unsigned char decode_hex_char(char c);
/**
* Decode a string of hex characters. Throws an exception if the input is not
* a valid hex encoding.
*/
std::string decode_hex(char const *hex);
#endif // OSM2PGSQL_WKB_HPP

View File

@ -67,6 +67,7 @@ set_test(test-reprojection LABELS NoDB)
set_test(test-taginfo LABELS NoDB)
set_test(test-util LABELS NoDB)
set_test(test-wildcard-match LABELS NoDB)
set_test(test-wkb LABELS NoDB)
# these tests require LUA support
if (HAVE_LUA)

212
tests/test-wkb.cpp Normal file
View File

@ -0,0 +1,212 @@
/**
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This file is part of osm2pgsql (https://osm2pgsql.org/).
*
* Copyright (C) 2006-2022 by the osm2pgsql developer community.
* For a full list of authors see the git log.
*/
#include <catch.hpp>
#include "geom.hpp"
#include "wkb.hpp"
TEST_CASE("wkb: nullgeom", "[NoDB]")
{
geom::geometry_t geom{};
auto const wkb = geom_to_ewkb(geom);
REQUIRE(wkb.empty());
auto const result = ewkb_to_geom(wkb);
REQUIRE(result.is_null());
}
TEST_CASE("wkb: point", "[NoDB]")
{
geom::geometry_t geom{geom::point_t{3.14, 2.17}, 42};
auto const result = ewkb_to_geom(geom_to_ewkb(geom));
REQUIRE(result.is_point());
REQUIRE(result.srid() == 42);
REQUIRE(result.get<geom::point_t>() == geom.get<geom::point_t>());
}
TEST_CASE("wkb: linestring", "[NoDB]")
{
geom::geometry_t geom{
geom::linestring_t{{1.2, 2.3}, {3.4, 4.5}, {5.6, 6.7}}, 43};
auto const result = ewkb_to_geom(geom_to_ewkb(geom));
REQUIRE(result.is_linestring());
REQUIRE(result.srid() == 43);
REQUIRE(result.get<geom::linestring_t>() == geom.get<geom::linestring_t>());
}
TEST_CASE("wkb: polygon without inner ring", "[NoDB]")
{
geom::geometry_t const geom{
geom::polygon_t{geom::ring_t{
{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}, {0.0, 0.0}}},
44};
auto const result = ewkb_to_geom(geom_to_ewkb(geom));
REQUIRE(result.is_polygon());
REQUIRE(result.srid() == 44);
REQUIRE(result.get<geom::polygon_t>().inners().empty());
REQUIRE(result.get<geom::polygon_t>().outer() ==
geom.get<geom::polygon_t>().outer());
}
TEST_CASE("wkb: polygon with inner rings", "[NoDB]")
{
geom::geometry_t geom{
geom::polygon_t{geom::ring_t{
{0.0, 0.0}, {3.0, 0.0}, {3.0, 3.0}, {0.0, 3.0}, {0.0, 0.0}}},
45};
geom.get<geom::polygon_t>().add_inner_ring(geom::ring_t{
{1.0, 1.0}, {2.0, 1.0}, {2.0, 2.0}, {1.0, 2.0}, {1.0, 1.0}});
auto const result = ewkb_to_geom(geom_to_ewkb(geom));
REQUIRE(result.is_polygon());
REQUIRE(result.srid() == 45);
REQUIRE(result.get<geom::polygon_t>().inners().size() == 1);
REQUIRE(result.get<geom::polygon_t>().outer() ==
geom.get<geom::polygon_t>().outer());
REQUIRE(result.get<geom::polygon_t>().inners().front() ==
geom.get<geom::polygon_t>().inners().front());
}
TEST_CASE("wkb: linestring as multilinestring", "[NoDB]")
{
geom::geometry_t geom{
geom::linestring_t{{1.2, 2.3}, {3.4, 4.5}, {5.6, 6.7}}, 43};
auto const result = ewkb_to_geom(geom_to_ewkb(geom, true));
REQUIRE(result.is_multilinestring());
REQUIRE(result.srid() == 43);
auto const &rml = result.get<geom::multilinestring_t>();
REQUIRE(rml.num_geometries() == 1);
REQUIRE(rml[0] == geom.get<geom::linestring_t>());
}
TEST_CASE("wkb: multilinestring", "[NoDB]")
{
geom::geometry_t geom{geom::multilinestring_t{}, 46};
auto &ml = geom.get<geom::multilinestring_t>();
ml.emplace_back(geom::linestring_t{{1.2, 2.3}, {3.4, 4.5}, {5.6, 6.7}});
ml.emplace_back(geom::linestring_t{{7.0, 7.0}, {8.0, 7.0}, {8.0, 8.0}});
auto const result = ewkb_to_geom(geom_to_ewkb(geom));
REQUIRE(result.is_multilinestring());
REQUIRE(result.srid() == 46);
auto const &rml = result.get<geom::multilinestring_t>();
REQUIRE(rml.num_geometries() == 2);
REQUIRE(rml[0] == ml[0]);
REQUIRE(rml[1] == ml[1]);
}
TEST_CASE("wkb: polygon as multipolygon", "[NoDB]")
{
geom::geometry_t const geom{
geom::polygon_t{geom::ring_t{
{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}, {0.0, 0.0}}},
44};
auto const result = ewkb_to_geom(geom_to_ewkb(geom, true));
REQUIRE(result.is_multipolygon());
REQUIRE(result.srid() == 44);
auto const &rmp = result.get<geom::multipolygon_t>();
REQUIRE(rmp.num_geometries() == 1);
REQUIRE(rmp[0].outer() == geom.get<geom::polygon_t>().outer());
REQUIRE(rmp[0].inners().empty());
}
TEST_CASE("wkb: multipolygon", "[NoDB]")
{
geom::geometry_t geom{geom::multipolygon_t{}, 47};
auto &mp = geom.get<geom::multipolygon_t>();
{
auto &p0 = mp.emplace_back(geom::ring_t{
{0.0, 0.0}, {3.0, 0.0}, {3.0, 3.0}, {0.0, 3.0}, {0.0, 0.0}});
p0.add_inner_ring(geom::ring_t{
{1.0, 1.0}, {2.0, 1.0}, {2.0, 2.0}, {1.0, 2.0}, {1.0, 1.0}});
}
mp.emplace_back(geom::ring_t{
{4.0, 4.0}, {5.0, 4.0}, {5.0, 5.0}, {4.0, 5.0}, {4.0, 4.0}});
auto const result = ewkb_to_geom(geom_to_ewkb(geom));
REQUIRE(result.is_multipolygon());
REQUIRE(result.srid() == 47);
auto const &rmp = result.get<geom::multipolygon_t>();
REQUIRE(rmp.num_geometries() == 2);
REQUIRE(rmp[0].outer() == mp[0].outer());
REQUIRE(rmp[0].inners().size() == 1);
REQUIRE(rmp[0].inners().front() == mp[0].inners().front());
REQUIRE(rmp[1].outer() == mp[1].outer());
REQUIRE(rmp[1].inners().empty());
}
TEST_CASE("wkb: invalid", "[NoDB]")
{
REQUIRE_THROWS(ewkb_to_geom("INVALID"));
}
TEST_CASE("wkb hex decode of valid hex characters")
{
REQUIRE(decode_hex_char('0') == 0);
REQUIRE(decode_hex_char('9') == 9);
REQUIRE(decode_hex_char('a') == 0x0a);
REQUIRE(decode_hex_char('f') == 0x0f);
REQUIRE(decode_hex_char('A') == 0x0a);
REQUIRE(decode_hex_char('F') == 0x0f);
REQUIRE_THROWS(decode_hex_char('x'));
}
TEST_CASE("wkb hex decode of valid hex string")
{
std::string const hex{"0001020F1099FF"};
std::string const data = {0x00,
0x01,
0x02,
0x0f,
0x10,
static_cast<char>(0x99),
static_cast<char>(0xff)};
auto const result = decode_hex(hex.c_str());
REQUIRE(result.size() == hex.size() / 2);
REQUIRE(result == data);
}
TEST_CASE("wkb hex decode of invalid hex string")
{
REQUIRE_THROWS(decode_hex("no"));
}
TEST_CASE("wkb hex decode of empty string is okay")
{
std::string const hex{};
REQUIRE(decode_hex(hex.c_str()).empty());
}
TEST_CASE("wkb hex decode of string with odd number of characters fails")
{
REQUIRE_THROWS(decode_hex("a"));
REQUIRE_THROWS(decode_hex("abc"));
REQUIRE_THROWS(decode_hex("00000"));
}