Large refactoring of the geometry code

Pulls apart the different concerns:
* geometry generation from OSM data
* geometry processing (like merging and splitting linestrings)
* conversion to WKB

For all of this to work we switch to C++17 as minimum requirement.

This also adds some code (in src/geom-boost-adaptor.hpp) that adapts our
data structures to boost::geometry. This is not used yet, but can in the
future be used for more complex geometry processing.
HEAD
Jochen Topf 2021-12-10 10:40:16 +01:00
parent 02d7d7de09
commit 943ca1ab25
41 changed files with 1927 additions and 1497 deletions

View File

@ -77,7 +77,7 @@ jobs:
LUAJIT_OPTION: OFF
POSTGRESQL_VERSION: 10
POSTGIS_VERSION: 3
CPP_VERSION: 14
CPP_VERSION: 17
BUILD_TYPE: Debug
steps:
@ -96,7 +96,7 @@ jobs:
LUAJIT_OPTION: OFF
POSTGRESQL_VERSION: 11
POSTGIS_VERSION: 2.5
CPP_VERSION: 14
CPP_VERSION: 17
BUILD_TYPE: Debug
steps:
@ -114,7 +114,7 @@ jobs:
LUAJIT_OPTION: ON
POSTGRESQL_VERSION: 12
POSTGIS_VERSION: 2.5
CPP_VERSION: 14
CPP_VERSION: 17
BUILD_TYPE: Debug
steps:
@ -133,7 +133,7 @@ jobs:
LUAJIT_OPTION: ON
POSTGRESQL_VERSION: 13
POSTGIS_VERSION: 3
CPP_VERSION: 14
CPP_VERSION: 17
BUILD_TYPE: Debug
steps:
@ -151,7 +151,7 @@ jobs:
LUAJIT_OPTION: OFF
POSTGRESQL_VERSION: 13
POSTGIS_VERSION: 3
CPP_VERSION: 14
CPP_VERSION: 17
USE_PROJ_LIB: 6
BUILD_TYPE: Debug
@ -170,7 +170,7 @@ jobs:
LUAJIT_OPTION: OFF
POSTGRESQL_VERSION: 13
POSTGIS_VERSION: 3
CPP_VERSION: 14
CPP_VERSION: 17
USE_PROJ_LIB: off
BUILD_TYPE: Debug
@ -208,7 +208,7 @@ jobs:
LUAJIT_OPTION: ON
POSTGRESQL_VERSION: 13
POSTGIS_VERSION: 2.5
CPP_VERSION: 14
CPP_VERSION: 17
BUILD_TYPE: Release
steps:

View File

@ -38,7 +38,7 @@ if (NOT CMAKE_BUILD_TYPE)
endif()
if (NOT "${CMAKE_CXX_STANDARD}")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 17)
endif()
message(STATUS "Building in C++${CMAKE_CXX_STANDARD} mode")
set(CMAKE_CXX_EXTENSIONS OFF)

View File

@ -70,7 +70,7 @@ It also requires access to a database server running
[PostGIS](https://www.postgis.net/) 2.2+.
Make sure you have installed the development packages for the libraries
mentioned in the requirements section and a C++ compiler which supports C++14.
mentioned in the requirements section and a C++ compiler which supports C++17.
We officially support gcc >= 7.0 and clang >= 8.
To rebuild the included man page you'll need the [pandoc](https://pandoc.org/)

View File

@ -8,6 +8,8 @@ target_sources(osm2pgsql_lib PRIVATE
expire-tiles.cpp
gazetteer-style.cpp
geom.cpp
geom-from-osm.cpp
geom-functions.cpp
input.cpp
logging.cpp
middle.cpp
@ -18,7 +20,6 @@ target_sources(osm2pgsql_lib PRIVATE
options.cpp
ordered-index.cpp
osmdata.cpp
osmium-builder.cpp
output-gazetteer.cpp
output-null.cpp
output-pgsql.cpp
@ -34,6 +35,7 @@ target_sources(osm2pgsql_lib PRIVATE
thread-pool.cpp
util.cpp
wildcmp.cpp
wkb.cpp
)
if (LUA_FOUND OR LUAJIT_FOUND)

View File

@ -128,11 +128,10 @@ uint32_t expire_tiles::normalise_tile_x_coord(int x) const
void expire_tiles::coords_to_tile(double lon, double lat, double *tilex,
double *tiley)
{
auto const c =
projection->target_to_tile(osmium::geom::Coordinates{lon, lat});
auto const c = projection->target_to_tile(geom::point_t{lon, lat});
*tilex = map_width * (0.5 + c.x / EARTH_CIRCUMFERENCE);
*tiley = map_width * (0.5 - c.y / EARTH_CIRCUMFERENCE);
*tilex = map_width * (0.5 + c.x() / EARTH_CIRCUMFERENCE);
*tiley = map_width * (0.5 - c.y() / EARTH_CIRCUMFERENCE);
}
/*

View File

@ -12,8 +12,8 @@
#include "db-copy-mgr.hpp"
#include "flex-table-column.hpp"
#include "osmium-builder.hpp"
#include "pgsql.hpp"
#include "reprojection.hpp"
#include "thread-pool.hpp"
#include <osmium/osm/item_type.hpp>
@ -232,7 +232,7 @@ class table_connection_t
public:
table_connection_t(flex_table_t *table,
std::shared_ptr<db_copy_thread_t> const &copy_thread)
: m_builder(reprojection::create_projection(table->srid())), m_table(table),
: m_proj(reprojection::create_projection(table->srid())), m_table(table),
m_target(std::make_shared<db_target_descr_t>(
table->name(), table->id_column_names(),
table->build_sql_column_list())),
@ -268,7 +268,11 @@ public:
void delete_rows_with(osmium::item_type type, osmid_t id);
geom::osmium_builder_t *get_builder() { return &m_builder; }
reprojection const &proj() const noexcept
{
assert(m_proj);
return *m_proj;
}
void task_set(std::future<std::chrono::milliseconds> &&future)
{
@ -278,7 +282,7 @@ public:
void task_wait();
private:
geom::osmium_builder_t m_builder;
std::shared_ptr<reprojection> m_proj;
flex_table_t *m_table;

150
src/geom-from-osm.cpp Normal file
View File

@ -0,0 +1,150 @@
/**
* 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 "geom-from-osm.hpp"
#include "osmtypes.hpp"
#include <osmium/area/geom_assembler.hpp>
#include <osmium/osm/way.hpp>
#include <utility>
namespace geom {
geometry_t create_point(osmium::Node const &node)
{
return geometry_t{point_t{node.location()}};
}
static void fill_point_list(point_list_t *list,
osmium::NodeRefList const &nodes)
{
osmium::Location last{};
for (auto const &node : nodes) {
auto const loc = node.location();
if (loc.valid() && loc != last) {
list->emplace_back(loc);
last = loc;
}
}
}
geometry_t create_linestring(osmium::Way const &way)
{
geometry_t geom{linestring_t{}};
auto &line = geom.get<linestring_t>();
fill_point_list(&line, way.nodes());
// Return nullgeom_t if the line geometry is invalid
if (line.size() <= 1U) {
geom.reset();
}
return geom;
}
geometry_t create_polygon(osmium::Way const &way)
{
geometry_t geom{polygon_t{}};
// A closed way with less than 4 nodes can never be a valid polygon
if (way.nodes().size() < 4U) {
geom.reset();
return geom;
}
osmium::area::AssemblerConfig area_config;
area_config.ignore_invalid_locations = true;
osmium::area::GeomAssembler assembler{area_config};
osmium::memory::Buffer area_buffer{1024};
if (!assembler(way, area_buffer)) {
geom.reset();
return geom;
}
auto const &area = area_buffer.get<osmium::Area>(0);
auto const &ring = *area.begin<osmium::OuterRing>();
fill_point_list(&geom.get<polygon_t>().outer(), ring);
return geom;
}
geometry_t create_multilinestring(osmium::memory::Buffer const &ways)
{
geometry_t geom{multilinestring_t{}};
auto &mls = geom.get<multilinestring_t>();
for (auto const &way : ways.select<osmium::Way>()) {
linestring_t line;
fill_point_list(&line, way.nodes());
if (line.size() >= 2U) {
mls.add_geometry(std::move(line));
}
}
if (mls.num_geometries() == 0) {
geom.reset();
}
return geom;
}
static void fill_polygon(polygon_t *polygon, osmium::Area const &area,
osmium::OuterRing const &outer_ring)
{
assert(polygon->inners().empty());
for (auto const &nr : outer_ring) {
polygon->outer().emplace_back(nr.location());
}
for (auto const &inner_ring : area.inner_rings(outer_ring)) {
auto &ring = polygon->inners().emplace_back();
for (auto const &nr : inner_ring) {
ring.emplace_back(nr.location());
}
}
}
geometry_t create_multipolygon(osmium::Relation const &relation,
osmium::memory::Buffer const &way_buffer)
{
geometry_t geom{};
osmium::area::AssemblerConfig area_config;
area_config.ignore_invalid_locations = true;
osmium::area::GeomAssembler assembler{area_config};
osmium::memory::Buffer area_buffer{1024};
if (!assembler(relation, way_buffer, area_buffer)) {
return geom;
}
auto const &area = area_buffer.get<osmium::Area>(0);
if (area.is_multipolygon()) {
auto &multipolygon = geom.set<multipolygon_t>();
for (auto const &outer : area.outer_rings()) {
auto &polygon = multipolygon.add_geometry();
fill_polygon(&polygon, area, outer);
}
} else {
auto &polygon = geom.set<polygon_t>();
fill_polygon(&polygon, area, *area.outer_rings().begin());
}
return geom;
}
} // namespace geom

85
src/geom-from-osm.hpp Normal file
View File

@ -0,0 +1,85 @@
#ifndef OSM2PGSQL_GEOM_FROM_OSM_HPP
#define OSM2PGSQL_GEOM_FROM_OSM_HPP
/**
* 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 "geom.hpp"
#include <osmium/fwd.hpp>
#include <osmium/memory/buffer.hpp>
/**
* \file
*
* Functions to create geometries from OSM data.
*/
namespace geom {
/**
* Create a point geometry from a node.
*
* \param node The input node.
* \returns The created geometry.
*/
geometry_t create_point(osmium::Node const &node);
/**
* Create a linestring geometry from a way. Nodes without location are ignored.
* Consecutive nodes with the same location will only end up once in the
* linestring.
*
* If the resulting linestring would be invalid (< 1 nodes), a null geometry
* is returned.
*
* \param way The input way.
* \returns The created geometry.
*/
geometry_t create_linestring(osmium::Way const &way);
/**
* Create a polygon geometry from a way.
*
* If the resulting polygon would be invalid, a null geometry is returned.
*
* \param way The input way.
* \returns The created geometry.
*/
geometry_t create_polygon(osmium::Way const &way);
/**
* Create a multilinestring geometry from a bunch of ways (usually this
* would be used for member ways of a relation). The result is always a
* multilinestring, even if it only contains one linestring.
*
* If the resulting multilinestring would be invalid, a null geometry is
* returned.
*
* \param ways Buffer containing all the input ways.
* \returns The created geometry.
*/
geometry_t create_multilinestring(osmium::memory::Buffer const &ways);
/**
* Create a (multi)polygon geometry from a relation and member ways.
*
* If the resulting (multi)polygon would be invalid, a null geometry is
* returned.
*
* \param relation The input relation.
* \param way_buffer Buffer containing all member ways.
* \returns The created geometry.
*/
geometry_t create_multipolygon(osmium::Relation const &relation,
osmium::memory::Buffer const &way_buffer);
} // namespace geom
#endif // OSM2PGSQL_GEOM_FROM_OSM_HPP

522
src/geom-functions.cpp Normal file
View File

@ -0,0 +1,522 @@
/**
* 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 "geom-functions.hpp"
#include <algorithm>
#include <cmath>
#include <iterator>
#include <tuple>
#include <utility>
namespace geom {
double distance(point_t p1, point_t p2) noexcept
{
double const dx = p1.x() - p2.x();
double const dy = p1.y() - p2.y();
return std::sqrt(dx * dx + dy * dy);
}
point_t interpolate(point_t p1, point_t p2, double frac) noexcept
{
return point_t{frac * (p1.x() - p2.x()) + p2.x(),
frac * (p1.y() - p2.y()) + p2.y()};
}
namespace {
class transform_visitor
{
public:
explicit transform_visitor(reprojection const *reprojection)
: m_reprojection(reprojection)
{}
geometry_t operator()(geom::nullgeom_t const & /*geom*/) const
{
return {};
}
geometry_t operator()(geom::point_t const &geom) const
{
return geometry_t{project(geom), srid()};
}
geometry_t operator()(geom::linestring_t const &geom) const
{
geometry_t output{linestring_t{}, srid()};
transform_points(&output.get<linestring_t>(), geom);
return output;
}
geometry_t operator()(geom::polygon_t const &geom) const
{
geometry_t output{polygon_t{}, srid()};
transform_polygon(&output.get<polygon_t>(), geom);
return output;
}
geometry_t operator()(geom::multipoint_t const & /*geom*/) const
{
assert(false);
return {};
}
geometry_t operator()(geom::multilinestring_t const &geom) const
{
geometry_t output{multilinestring_t{}, srid()};
for (auto const &line : geom) {
transform_points(&output.get<multilinestring_t>().add_geometry(),
line);
}
return output;
}
geometry_t operator()(geom::multipolygon_t const &geom) const
{
geometry_t output{multipolygon_t{}, srid()};
for (auto const &polygon : geom) {
transform_polygon(&output.get<multipolygon_t>().add_geometry(),
polygon);
}
return output;
}
geometry_t operator()(geom::collection_t const & /*geom*/) const
{
return {}; // XXX not implemented
}
private:
int srid() const noexcept { return m_reprojection->target_srs(); }
point_t project(point_t point) const
{
return m_reprojection->reproject(point);
}
void transform_points(point_list_t *output, point_list_t const &input) const
{
output->reserve(input.size());
for (auto const &point : input) {
output->push_back(project(point));
}
}
void transform_polygon(polygon_t *output, polygon_t const &input) const
{
transform_points(&output->outer(), input.outer());
output->inners().reserve(input.inners().size());
for (auto const &inner : input.inners()) {
auto &oring = output->inners().emplace_back();
transform_points(&oring, inner);
}
}
reprojection const *m_reprojection;
}; // class transform_visitor
} // anonymous namespace
geometry_t transform(geometry_t const &geom, reprojection const &reprojection)
{
assert(geom.srid() == 4326);
return geom.visit(transform_visitor{&reprojection});
}
namespace {
/**
* Helper class for iterating over all points except the first one in a point
* list.
*/
class without_first
{
public:
explicit without_first(point_list_t const &list) : m_list(list) {}
point_list_t::const_iterator begin()
{
assert(m_list.begin() != m_list.end());
return std::next(m_list.begin());
}
point_list_t::const_iterator end() { return m_list.end(); }
private:
point_list_t const &m_list;
}; // class without_first
} // anonymous namespace
static void split_linestring(linestring_t const &line, double split_at,
multilinestring_t *output)
{
double dist = 0;
point_t prev_pt{line.front()};
linestring_t *out = &output->add_geometry();
out->push_back(prev_pt);
for (auto const &this_pt : without_first(line)) {
double const delta = distance(prev_pt, this_pt);
// figure out if the addition of this point would take the total
// length of the line in `segment` over the `split_at` distance.
if (dist + delta > split_at) {
auto const splits = (size_t)std::floor((dist + delta) / split_at);
// use the splitting distance to split the current segment up
// into as many parts as necessary to keep each part below
// the `split_at` distance.
point_t ipoint;
for (size_t j = 0; j < splits; ++j) {
double const frac = ((double)(j + 1) * split_at - dist) / delta;
ipoint = interpolate(this_pt, prev_pt, frac);
if (frac != 0.0) {
out->emplace_back(ipoint);
}
// start a new segment
out = &output->add_geometry();
out->emplace_back(ipoint);
}
// reset the distance based on the final splitting point for
// the next iteration.
if (this_pt == ipoint) {
dist = 0;
prev_pt = this_pt;
continue;
}
dist = distance(this_pt, ipoint);
} else {
dist += delta;
}
out->push_back(this_pt);
prev_pt = this_pt;
}
if (out->size() <= 1) {
output->resize(output->num_geometries() - 1);
}
}
geometry_t segmentize(geometry_t const &geom, double max_segment_length)
{
geometry_t output{multilinestring_t{}, geom.srid()};
auto *multilinestring = &output.get<multilinestring_t>();
if (geom.is_linestring()) {
split_linestring(geom.get<linestring_t>(), max_segment_length,
multilinestring);
} else if (geom.is_multilinestring()) {
for (auto const &line : geom.get<multilinestring_t>()) {
split_linestring(line, max_segment_length, multilinestring);
}
} else {
output.reset();
}
return output;
}
static double get_ring_area(ring_t const &ring) noexcept
{
assert(ring.size() > 3);
double total = 0.0;
auto it = ring.begin();
auto prev = *it++;
while (it != ring.end()) {
auto const cur = *it;
total += prev.x() * cur.y() - cur.x() * prev.y();
prev = cur;
++it;
}
return total;
}
static double get_polygon_area(polygon_t const &polygon)
{
double total = get_ring_area(polygon.outer());
for (auto const &ring : polygon.inners()) {
total += get_ring_area(ring);
}
return total * 0.5;
}
double area(geometry_t const &geom)
{
double total = 0.0;
if (geom.is_polygon()) {
total = get_polygon_area(geom.get<polygon_t>());
} else if (geom.is_multipolygon()) {
for (auto const &polygon : geom.get<multipolygon_t>()) {
total += get_polygon_area(polygon);
}
}
return total;
}
namespace {
class split_visitor
{
public:
split_visitor(std::vector<geometry_t> *output, uint32_t srid) noexcept
: m_output(output), m_srid(srid)
{}
template <typename T>
void operator()(T) const
{}
void operator()(geom::collection_t const &geom) const
{
for (auto sgeom : geom) {
m_output->push_back(std::move(sgeom));
}
}
template <typename T>
void operator()(geom::multigeometry_t<T> const &geom) const
{
for (auto sgeom : geom) {
m_output->emplace_back(std::move(sgeom), m_srid);
}
}
private:
std::vector<geometry_t> *m_output;
uint32_t m_srid;
}; // class split_visitor
} // anonymous namespace
std::vector<geometry_t> split_multi(geometry_t geom, bool split_multi)
{
std::vector<geometry_t> output;
if (split_multi && geom.is_multi()) {
geom.visit(split_visitor{&output, static_cast<uint32_t>(geom.srid())});
} else if (!geom.is_null()) {
output.push_back(std::move(geom));
}
return output;
}
/**
* Add points specified by iterators to the linestring. If linestring is not
* empty, do not add the first point returned by *it.
*/
template <typename ITERATOR>
static void add_nodes_to_linestring(linestring_t *linestring, ITERATOR it,
ITERATOR end)
{
if (!linestring->empty()) {
assert(it != end);
++it;
}
while (it != end) {
linestring->push_back(*it);
++it;
}
}
geometry_t line_merge(geometry_t geom)
{
geometry_t output{multilinestring_t{}, geom.srid()};
if (geom.is_null()) {
output.reset();
return output;
}
assert(geom.is_multilinestring());
// Make a list of all endpoints...
struct endpoint_t
{
point_t c;
std::size_t n;
bool is_front;
endpoint_t(point_t coords, std::size_t size, bool front) noexcept
: c(coords), n(size), is_front(front)
{}
bool operator==(endpoint_t const &rhs) const noexcept
{
return c == rhs.c;
}
bool operator<(endpoint_t const &rhs) const noexcept
{
return std::tuple<double, double, std::size_t, bool>{c.x(), c.y(),
n, is_front} <
std::tuple<double, double, std::size_t, bool>{
rhs.c.x(), rhs.c.y(), rhs.n, rhs.is_front};
}
};
std::vector<endpoint_t> endpoints;
// ...and a list of connections.
constexpr std::size_t const NOCONN = -1UL;
struct connection_t
{
std::size_t left = NOCONN;
geom::linestring_t const *ls;
std::size_t right = NOCONN;
explicit connection_t(geom::linestring_t const *l) noexcept : ls(l) {}
};
std::vector<connection_t> conns;
// Initialize the two lists.
for (auto const &line : geom.get<multilinestring_t>()) {
endpoints.emplace_back(line.front(), conns.size(), true);
endpoints.emplace_back(line.back(), conns.size(), false);
conns.emplace_back(&line);
}
std::sort(endpoints.begin(), endpoints.end());
// Now fill the connection list based on the sorted enpoints list.
for (auto it = std::adjacent_find(endpoints.cbegin(), endpoints.cend());
it != endpoints.cend();
it = std::adjacent_find(it + 2, endpoints.cend())) {
auto const previd = it->n;
auto const ptid = std::next(it)->n;
if (it->is_front) {
conns[previd].left = ptid;
} else {
conns[previd].right = ptid;
}
if (std::next(it)->is_front) {
conns[ptid].left = previd;
} else {
conns[ptid].right = previd;
}
}
auto &linestrings = output.get<multilinestring_t>();
// First find all open ends and use them as starting points to assemble
// linestrings. Mark ways as "done" as we go.
std::size_t done_ways = 0;
std::size_t const todo_ways = conns.size();
for (std::size_t i = 0; i < todo_ways; ++i) {
if (!conns[i].ls ||
(conns[i].left != NOCONN && conns[i].right != NOCONN)) {
continue; // way already done or not the beginning of a segment
}
linestring_t linestring;
{
std::size_t prev = NOCONN;
std::size_t cur = i;
do {
auto &conn = conns[cur];
assert(conn.ls);
auto const &nl = *conn.ls;
bool const forward = conn.left == prev;
prev = cur;
// add line
if (forward) {
add_nodes_to_linestring(&linestring, nl.cbegin(),
nl.cend());
cur = conn.right;
} else {
add_nodes_to_linestring(&linestring, nl.crbegin(),
nl.crend());
cur = conn.left;
}
// mark line as done
conns[prev].ls = nullptr;
++done_ways;
} while (cur != NOCONN);
}
// found a line end
linestrings.add_geometry(std::move(linestring));
}
// If all ways have been "done", i.e. are part of a linestring now, we
// are finished.
if (done_ways < todo_ways) {
// oh dear, there must be circular ways without an end
// need to do the same shebang again
for (std::size_t i = 0; i < todo_ways; ++i) {
if (!conns[i].ls) {
continue; // way already done
}
linestring_t linestring;
{
std::size_t prev = conns[i].left;
std::size_t cur = i;
do {
auto &conn = conns[cur];
assert(conn.ls);
auto const &nl = *conn.ls;
bool const forward =
(conn.left == prev &&
(!conns[conn.left].ls ||
conns[conn.left].ls->back() == nl.front()));
prev = cur;
if (forward) {
// add line forwards
add_nodes_to_linestring(&linestring, nl.cbegin(),
nl.cend());
cur = conn.right;
} else {
// add line backwards
add_nodes_to_linestring(&linestring, nl.crbegin(),
nl.crend());
cur = conn.left;
}
// mark line as done
conns[prev].ls = nullptr;
} while (cur != i);
}
// found a line end
linestrings.add_geometry(std::move(linestring));
}
}
if (linestrings.num_geometries() == 0) {
output.reset();
}
return output;
}
} // namespace geom

91
src/geom-functions.hpp Normal file
View File

@ -0,0 +1,91 @@
#ifndef OSM2PGSQL_GEOM_FUNCTIONS_HPP
#define OSM2PGSQL_GEOM_FUNCTIONS_HPP
/**
* 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 "geom.hpp"
#include "reprojection.hpp"
#include <vector>
/**
* \file
*
* Functions on geometries.
*/
namespace geom {
/// Calculate the Euclidean distance between two points
double distance(point_t p1, point_t p2) noexcept;
/**
* Calculate a point on the vector p1 -> p2 that is a fraction of the distance
* between those points.
*/
point_t interpolate(point_t p1, point_t p2, double frac) noexcept;
/**
* Transform a geometry in 4326 into some other projection.
*
* \param geom Input geometry.
* \param reprojection Target projection.
* \returns Reprojected geometry.
*
* \pre \code geom.srid() == 4326 \endcode
*/
geometry_t transform(geometry_t const &geom, reprojection const &reprojection);
/**
* Returns a modified geometry having no segment longer than the given
* max_segment_length.
*
* \param line The input geometry, must be a linestring or multilinestring.
* \param max_segment_length The maximum length (using Euclidean distance
* in the length unit of the srs of the geometry) of each resulting
* linestring.
* \returns Resulting multilinestring geometry, nullgeom_t on error.
*/
geometry_t segmentize(geometry_t const &geom, double max_segment_length);
/**
* Calculate area of geometry.
* For geometry types other than polygon or multipolygon this will always
* return 0.
*
* \param geom Input geometry.
* \returns Area.
*/
double area(geometry_t const &geom);
/**
* Split multigeometries into their parts. Non-multi geometries are left
* alone and will end up as the only geometry in the result vector. If the
* input geometry is a nullgeom_t, the result vector will be empty.
*
* \param geom Input geometry.
* \param split_multi Only split of this is set to true.
* \returns Vector of result geometries.
*/
std::vector<geometry_t> split_multi(geometry_t geom, bool split_multi = true);
/**
* Merge lines in a multilinestring end-to-end as far as possible. Always
* returns a multilinestring unless there is an error or the input geometry
* is a nullgeom_t, in which case nullgeom_t is returned.
*
* \param geom Input geometry.
* \returns Result multilinestring.
*/
geometry_t line_merge(geometry_t geom);
} // namespace geom
#endif // OSM2PGSQL_GEOM_FUNCTIONS_HPP

View File

@ -7,6 +7,8 @@
* For a full list of authors see the git log.
*/
#include "geom-from-osm.hpp"
#include "geom-functions.hpp"
#include "geom-transform.hpp"
#include "logging.hpp"
@ -22,14 +24,10 @@ bool geom_transform_point_t::is_compatible_with(
geom_type == table_column_type::geometry;
}
geom::osmium_builder_t::wkbs_t
geom_transform_point_t::run(geom::osmium_builder_t *builder,
table_column_type /*target_geom_type*/,
osmium::Node const &node) const
geom::geometry_t geom_transform_point_t::convert(reprojection const &proj,
osmium::Node const &node) const
{
assert(builder);
return {builder->get_wkb_node(node.location())};
return geom::transform(geom::create_point(node), proj);
}
bool geom_transform_line_t::set_param(char const *name, lua_State *lua_state)
@ -56,26 +54,27 @@ bool geom_transform_line_t::is_compatible_with(
geom_type == table_column_type::geometry;
}
geom::osmium_builder_t::wkbs_t
geom_transform_line_t::run(geom::osmium_builder_t *builder,
table_column_type /*target_geom_type*/,
osmium::Way *way) const
geom::geometry_t geom_transform_line_t::convert(reprojection const &proj,
osmium::Way const &way) const
{
assert(builder);
assert(way);
return builder->get_wkb_line(way->nodes(), m_split_at);
auto geom = geom::transform(geom::create_linestring(way), proj);
if (!geom.is_null() && m_split_at > 0.0) {
geom = geom::segmentize(geom, m_split_at);
}
return geom;
}
geom::osmium_builder_t::wkbs_t
geom_transform_line_t::run(geom::osmium_builder_t *builder,
table_column_type /*target_geom_type*/,
osmium::Relation const & /*relation*/,
osmium::memory::Buffer const &buffer) const
geom::geometry_t
geom_transform_line_t::convert(reprojection const &proj,
osmium::Relation const & /*relation*/,
osmium::memory::Buffer const &buffer) const
{
assert(builder);
return builder->get_wkb_multiline(buffer, m_split_at);
auto geom = geom::transform(
geom::line_merge(geom::create_multilinestring(buffer)), proj);
if (!geom.is_null() && m_split_at > 0.0) {
geom = geom::segmentize(geom, m_split_at);
}
return geom;
}
bool geom_transform_area_t::set_param(char const *name, lua_State *lua_state)
@ -115,42 +114,18 @@ bool geom_transform_area_t::is_compatible_with(
geom_type == table_column_type::geometry;
}
geom::osmium_builder_t::wkbs_t
geom_transform_area_t::run(geom::osmium_builder_t *builder,
table_column_type target_geom_type,
osmium::Way *way) const
geom::geometry_t geom_transform_area_t::convert(reprojection const & /*proj*/,
osmium::Way const &way) const
{
assert(builder);
assert(way);
geom::osmium_builder_t::wkbs_t result;
if (!way->is_closed()) {
return result;
}
result.push_back(builder->get_wkb_polygon(*way));
if (result.front().empty()) {
result.clear();
} else if (target_geom_type == table_column_type::multipolygon) {
builder->wrap_in_multipolygon(&result);
}
return result;
return geom::create_polygon(way);
}
geom::osmium_builder_t::wkbs_t
geom_transform_area_t::run(geom::osmium_builder_t *builder,
table_column_type target_geom_type,
osmium::Relation const &relation,
osmium::memory::Buffer const &buffer) const
geom::geometry_t
geom_transform_area_t::convert(reprojection const & /*proj*/,
osmium::Relation const &relation,
osmium::memory::Buffer const &buffer) const
{
assert(builder);
bool const wrap_multi = target_geom_type == table_column_type::multipolygon;
return builder->get_wkb_multipolygon(relation, buffer, m_multi, wrap_multi);
return geom::create_multipolygon(relation, buffer);
}
std::unique_ptr<geom_transform_t> create_geom_transform(char const *type)

View File

@ -11,9 +11,11 @@
*/
#include "flex-table-column.hpp"
#include "osmium-builder.hpp"
#include "geom.hpp"
#include "reprojection.hpp"
#include <osmium/fwd.hpp>
#include <osmium/memory/buffer.hpp>
extern "C"
{
@ -36,44 +38,41 @@ public:
return false;
}
virtual bool is_compatible_with(table_column_type geom_type) const
noexcept = 0;
virtual bool
is_compatible_with(table_column_type geom_type) const noexcept = 0;
virtual geom::osmium_builder_t::wkbs_t
run(geom::osmium_builder_t * /*builder*/,
table_column_type /*target_geom_type*/,
osmium::Node const & /*node*/) const
virtual geom::geometry_t convert(reprojection const & /*proj*/,
osmium::Node const & /*node*/) const
{
return {};
}
virtual geom::osmium_builder_t::wkbs_t
run(geom::osmium_builder_t * /*builder*/,
table_column_type /*target_geom_type*/, osmium::Way * /*way*/) const
virtual geom::geometry_t convert(reprojection const & /*proj*/,
osmium::Way const & /*way*/) const
{
return {};
}
virtual geom::osmium_builder_t::wkbs_t
run(geom::osmium_builder_t * /*builder*/,
table_column_type /*target_geom_type*/,
osmium::Relation const & /*relation*/,
osmium::memory::Buffer const & /*buffer*/) const
virtual geom::geometry_t
convert(reprojection const & /*proj*/,
osmium::Relation const & /*relation*/,
osmium::memory::Buffer const & /*buffer*/) const
{
return {};
}
virtual bool split() const noexcept { return false; }
}; // class geom_transform_t
class geom_transform_point_t : public geom_transform_t
{
public:
bool is_compatible_with(table_column_type geom_type) const
noexcept override;
bool
is_compatible_with(table_column_type geom_type) const noexcept override;
geom::osmium_builder_t::wkbs_t run(geom::osmium_builder_t *builder,
table_column_type target_geom_type,
osmium::Node const &node) const override;
geom::geometry_t convert(reprojection const &proj,
osmium::Node const &node) const override;
}; // class geom_transform_point_t
@ -82,17 +81,15 @@ class geom_transform_line_t : public geom_transform_t
public:
bool set_param(char const *name, lua_State *lua_state) override;
bool is_compatible_with(table_column_type geom_type) const
noexcept override;
bool
is_compatible_with(table_column_type geom_type) const noexcept override;
geom::osmium_builder_t::wkbs_t run(geom::osmium_builder_t *builder,
table_column_type target_geom_type,
osmium::Way *way) const override;
geom::geometry_t convert(reprojection const &proj,
osmium::Way const &way) const override;
geom::osmium_builder_t::wkbs_t
run(geom::osmium_builder_t *builder, table_column_type target_geom_type,
osmium::Relation const &relation,
osmium::memory::Buffer const &buffer) const override;
geom::geometry_t
convert(reprojection const &proj, osmium::Relation const &relation,
osmium::memory::Buffer const &buffer) const override;
private:
double m_split_at = 0.0;
@ -104,17 +101,17 @@ class geom_transform_area_t : public geom_transform_t
public:
bool set_param(char const *name, lua_State *lua_state) override;
bool is_compatible_with(table_column_type geom_type) const
noexcept override;
bool
is_compatible_with(table_column_type geom_type) const noexcept override;
geom::osmium_builder_t::wkbs_t run(geom::osmium_builder_t *builder,
table_column_type target_geom_type,
osmium::Way *way) const override;
geom::geometry_t convert(reprojection const &proj,
osmium::Way const &way) const override;
geom::osmium_builder_t::wkbs_t
run(geom::osmium_builder_t *builder, table_column_type target_geom_type,
osmium::Relation const &relation,
osmium::memory::Buffer const &buffer) const override;
geom::geometry_t
convert(reprojection const &proj, osmium::Relation const &relation,
osmium::memory::Buffer const &buffer) const override;
bool split() const noexcept override { return !m_multi; }
private:
bool m_multi = true;

View File

@ -9,287 +9,18 @@
#include "geom.hpp"
#include "osmtypes.hpp"
#include <osmium/osm/way.hpp>
#include <algorithm>
#include <cmath>
#include <iterator>
#include <tuple>
namespace geom {
double distance(osmium::geom::Coordinates p1,
osmium::geom::Coordinates p2) noexcept
bool operator==(point_list_t const &a, point_list_t const &b) noexcept
{
double const dx = p1.x - p2.x;
double const dy = p1.y - p2.y;
return std::sqrt(dx * dx + dy * dy);
return std::equal(a.cbegin(), a.cend(), b.cbegin(), b.cend());
}
osmium::geom::Coordinates interpolate(osmium::geom::Coordinates p1,
osmium::geom::Coordinates p2,
double frac) noexcept
bool operator!=(point_list_t const &a, point_list_t const &b) noexcept
{
return osmium::geom::Coordinates{frac * (p1.x - p2.x) + p2.x,
frac * (p1.y - p2.y) + p2.y};
}
linestring_t::linestring_t(
std::initializer_list<osmium::geom::Coordinates> coords)
{
std::copy(coords.begin(), coords.end(), std::back_inserter(m_coordinates));
}
linestring_t::linestring_t(osmium::NodeRefList const &nodes,
reprojection const &proj)
{
osmium::Location last{};
for (auto const &node : nodes) {
auto const loc = node.location();
if (loc.valid() && loc != last) {
add_point(proj.reproject(loc));
last = loc;
}
}
if (size() <= 1) {
m_coordinates.clear();
}
}
void split_linestring(linestring_t const &line, double split_at,
std::vector<linestring_t> *out)
{
double dist = 0;
osmium::geom::Coordinates prev_pt{};
out->emplace_back();
for (auto const &this_pt : line) {
if (prev_pt.valid()) {
double const delta = distance(prev_pt, this_pt);
// figure out if the addition of this point would take the total
// length of the line in `segment` over the `split_at` distance.
if (dist + delta > split_at) {
auto const splits =
(size_t)std::floor((dist + delta) / split_at);
// use the splitting distance to split the current segment up
// into as many parts as necessary to keep each part below
// the `split_at` distance.
osmium::geom::Coordinates ipoint;
for (size_t j = 0; j < splits; ++j) {
double const frac =
((double)(j + 1) * split_at - dist) / delta;
ipoint = interpolate(this_pt, prev_pt, frac);
if (frac != 0.0) {
out->back().add_point(ipoint);
}
// start a new segment
out->emplace_back();
out->back().add_point(ipoint);
}
// reset the distance based on the final splitting point for
// the next iteration.
if (this_pt == ipoint) {
dist = 0;
prev_pt = this_pt;
continue;
}
dist = distance(this_pt, ipoint);
} else {
dist += delta;
}
}
out->back().add_point(this_pt);
prev_pt = this_pt;
}
if (out->back().size() <= 1) {
out->pop_back();
}
}
void make_line(linestring_t &&line, double split_at,
std::vector<linestring_t> *out)
{
assert(out);
if (line.empty()) {
return;
}
if (split_at > 0.0) {
split_linestring(line, split_at, out);
} else {
out->emplace_back(std::move(line));
}
}
void make_multiline(osmium::memory::Buffer const &ways, double split_at,
reprojection const &proj, std::vector<linestring_t> *out)
{
// make a list of all endpoints
struct endpoint_t {
osmid_t id;
std::size_t n;
bool is_front;
endpoint_t(osmid_t ref, std::size_t size, bool front) noexcept
: id(ref), n(size), is_front(front)
{}
bool operator==(endpoint_t const &rhs) const noexcept
{
return id == rhs.id;
}
};
std::vector<endpoint_t> endpoints;
// and a list of way connections
enum lmt : size_t
{
NOCONN = -1UL
};
struct connection_t {
std::size_t left = NOCONN;
osmium::Way const *way;
std::size_t right = NOCONN;
explicit connection_t(osmium::Way const *w) noexcept : way(w) {}
};
std::vector<connection_t> conns;
// initialise the two lists
for (auto const &w : ways.select<osmium::Way>()) {
if (w.nodes().size() > 1) {
endpoints.emplace_back(w.nodes().front().ref(), conns.size(), true);
endpoints.emplace_back(w.nodes().back().ref(), conns.size(), false);
conns.emplace_back(&w);
}
}
// sort by node id
std::sort(endpoints.begin(), endpoints.end(), [
](endpoint_t const &a, endpoint_t const &b) noexcept {
return std::tuple<osmid_t, std::size_t, bool>(a.id, a.n, a.is_front) <
std::tuple<osmid_t, std::size_t, bool>(b.id, b.n, b.is_front);
});
// now fill the connection list based on the sorted list
for (auto it = std::adjacent_find(endpoints.cbegin(), endpoints.cend());
it != endpoints.cend();
it = std::adjacent_find(it + 2, endpoints.cend())) {
auto const previd = it->n;
auto const ptid = std::next(it)->n;
if (it->is_front) {
conns[previd].left = ptid;
} else {
conns[previd].right = ptid;
}
if (std::next(it)->is_front) {
conns[ptid].left = previd;
} else {
conns[ptid].right = previd;
}
}
// First find all open ends and use them as starting points to assemble
// linestrings. Mark ways as "done" as we go.
std::size_t done_ways = 0;
std::size_t const todo_ways = conns.size();
for (std::size_t i = 0; i < todo_ways; ++i) {
if (!conns[i].way ||
(conns[i].left != NOCONN && conns[i].right != NOCONN)) {
continue; // way already done or not the beginning of a segment
}
linestring_t linestring;
{
std::size_t prev = NOCONN;
std::size_t cur = i;
do {
auto &conn = conns[cur];
assert(conn.way);
auto const &nl = conn.way->nodes();
bool const forward = conn.left == prev;
prev = cur;
// add way nodes
if (forward) {
add_nodes_to_linestring(linestring, proj, nl.cbegin(),
nl.cend());
cur = conn.right;
} else {
add_nodes_to_linestring(linestring, proj, nl.crbegin(),
nl.crend());
cur = conn.left;
}
// mark way as done
conns[prev].way = nullptr;
++done_ways;
} while (cur != NOCONN);
}
// found a line end, create the wkbs
make_line(std::move(linestring), split_at, out);
}
// If all ways have been "done", i.e. are part of a linestring now, we
// are finished.
if (done_ways >= todo_ways) {
return;
}
// oh dear, there must be circular ways without an end
// need to do the same shebang again
for (size_t i = 0; i < todo_ways; ++i) {
if (!conns[i].way) {
continue; // way already done
}
linestring_t linestring;
{
size_t prev = conns[i].left;
size_t cur = i;
do {
auto &conn = conns[cur];
assert(conn.way);
auto const &nl = conn.way->nodes();
bool const forward =
(conn.left == prev &&
(!conns[conn.left].way ||
conns[conn.left].way->nodes().back() == nl.front()));
prev = cur;
if (forward) {
// add way forwards
add_nodes_to_linestring(linestring, proj, nl.cbegin(),
nl.cend());
cur = conn.right;
} else {
// add way backwards
add_nodes_to_linestring(linestring, proj, nl.crbegin(),
nl.crend());
cur = conn.left;
}
// mark way as done
conns[prev].way = nullptr;
} while (cur != i);
}
// found a line end, create the wkbs
make_line(std::move(linestring), split_at, out);
}
return !(a == b);
}
} // namespace geom

View File

@ -13,168 +13,229 @@
/**
* \file
*
* Low level geometry functions and types.
* Basic geometry types and functions.
*/
#include "reprojection.hpp"
#include <osmium/osm/location.hpp>
#include <osmium/geom/coordinates.hpp>
#include <osmium/osm/node_ref_list.hpp>
#include <osmium/memory/buffer.hpp>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <initializer_list>
#include <ostream>
#include <utility>
#include <variant>
#include <vector>
namespace geom {
/// Calculate the Euclidean distance between two coordinates
double distance(osmium::geom::Coordinates p1,
osmium::geom::Coordinates p2) noexcept;
class nullgeom_t
{
}; // class nullgeom_t
osmium::geom::Coordinates interpolate(osmium::geom::Coordinates p1,
osmium::geom::Coordinates p2,
double frac) noexcept;
class point_t
{
public:
point_t() = default;
class linestring_t
explicit point_t(osmium::Location location) noexcept
: m_x(location.lon_without_check()), m_y(location.lat_without_check())
{}
constexpr point_t(double x, double y) noexcept : m_x(x), m_y(y) {}
constexpr double x() const noexcept { return m_x; }
constexpr double y() const noexcept { return m_y; }
constexpr void set_x(double value) noexcept { m_x = value; }
constexpr void set_y(double value) noexcept { m_y = value; }
constexpr friend bool operator==(point_t a, point_t b) noexcept
{
return a.x() == b.x() && a.y() == b.y();
}
constexpr friend bool operator!=(point_t a, point_t b) noexcept
{
return !(a == b);
}
private:
double m_x = 0.0;
double m_y = 0.0;
}; // class point_t
/// This type is used as the basis for linestrings and rings.
using point_list_t = std::vector<point_t>;
bool operator==(point_list_t const &a, point_list_t const &b) noexcept;
bool operator!=(point_list_t const &a, point_list_t const &b) noexcept;
class linestring_t : public point_list_t
{
public:
linestring_t() = default;
linestring_t(std::initializer_list<osmium::geom::Coordinates> coords);
template <typename Iterator>
linestring_t(Iterator begin, Iterator end) : point_list_t(begin, end)
{}
/**
* Construct a linestring from a way node list. Nodes without location
* are ignored. Consecutive nodes with the same location will only end
* up once in the linestring.
*
* The resulting linestring will either be empty or have at least two
* points in it.
*
* \param nodes The input nodes.
* \param proj The projection used to project all coordinates.
*/
linestring_t(osmium::NodeRefList const &nodes, reprojection const &proj);
using iterator = std::vector<osmium::geom::Coordinates>::iterator;
using const_iterator =
std::vector<osmium::geom::Coordinates>::const_iterator;
bool empty() const noexcept { return m_coordinates.empty(); }
std::size_t size() const noexcept { return m_coordinates.size(); }
void clear() noexcept { m_coordinates.clear(); }
void add_point(osmium::geom::Coordinates coordinates)
{
m_coordinates.emplace_back(coordinates);
}
iterator begin() noexcept { return m_coordinates.begin(); }
iterator end() noexcept { return m_coordinates.end(); }
const_iterator begin() const noexcept { return m_coordinates.cbegin(); }
const_iterator end() const noexcept { return m_coordinates.cend(); }
const_iterator cbegin() const noexcept { return m_coordinates.cbegin(); }
const_iterator cend() const noexcept { return m_coordinates.cend(); }
osmium::geom::Coordinates &operator[](std::size_t n) noexcept
{
return m_coordinates[n];
}
osmium::geom::Coordinates operator[](std::size_t n) const noexcept
{
return m_coordinates[n];
}
friend bool operator==(linestring_t const &a,
linestring_t const &b) noexcept
{
if (a.size() != b.size()) {
return false;
}
return std::equal(a.cbegin(), a.cend(), b.cbegin());
}
private:
std::vector<osmium::geom::Coordinates> m_coordinates;
linestring_t(std::initializer_list<point_t> list)
: point_list_t(list.begin(), list.end())
{}
}; // class linestring_t
/// Output a linestring in WKT format. Used for debugging.
template <typename TChar, typename TTraits>
std::basic_ostream<TChar, TTraits> &
operator<<(std::basic_ostream<TChar, TTraits> &out, const linestring_t &line)
class ring_t : public point_list_t
{
out << "LINESTRING(";
public:
ring_t() = default;
bool first = true;
for (auto const &coord : line) {
if (first) {
first = false;
} else {
out << ',';
}
out << coord.x << ' ' << coord.y;
}
template <typename Iterator>
ring_t(Iterator begin, Iterator end) : point_list_t(begin, end)
{}
return out << ')';
}
ring_t(std::initializer_list<point_t> list)
: point_list_t(list.begin(), list.end())
{}
}; // class ring_t
class polygon_t
{
public:
polygon_t() = default;
explicit polygon_t(ring_t &&ring) : m_outer(std::move(ring)) {}
ring_t const &outer() const noexcept { return m_outer; }
ring_t &outer() noexcept { return m_outer; }
std::vector<ring_t> const &inners() const noexcept { return m_inners; }
std::vector<ring_t> &inners() noexcept { return m_inners; }
void add_inner_ring(ring_t &&ring) { m_inners.push_back(std::move(ring)); }
private:
ring_t m_outer;
std::vector<ring_t> m_inners;
}; // class polygon_t
template <typename GEOM>
class multigeometry_t : public std::vector<GEOM>
{
public:
using const_iterator = typename std::vector<GEOM>::const_iterator;
std::size_t num_geometries() const noexcept { return this->size(); }
void add_geometry(GEOM &&geom) { this->push_back(std::move(geom)); }
GEOM &add_geometry() { return this->emplace_back(); }
}; // class multigeometry_t
using multipoint_t = multigeometry_t<point_t>;
using multilinestring_t = multigeometry_t<linestring_t>;
using multipolygon_t = multigeometry_t<polygon_t>;
class geometry_t;
using collection_t = multigeometry_t<geometry_t>;
/**
* Possibly split linestring into several linestrings making sure each one
* is no longer than split_at.
*
* \param line The input line string.
* \param split_at The maximum length (using Euclidean distance) of each
* resulting linestring.
* \param out Add resulting linestrings to this vector.
* This is a variant type holding any one of the geometry types (including
* nullgeom_t) and a SRID.
*/
void split_linestring(linestring_t const &line, double split_at,
std::vector<linestring_t> *out);
void make_line(linestring_t &&line, double split_at,
std::vector<linestring_t> *out);
/**
* Add nodes specified by iterators to the linestring projecting them in the
* process. If linestring is not empty, do not add the first node returned
* by *begin.
*/
template <typename ITERATOR>
void add_nodes_to_linestring(geom::linestring_t &linestring,
reprojection const &proj, ITERATOR const &begin,
ITERATOR const &end)
class geometry_t
{
auto it = begin;
if (!linestring.empty()) {
assert(it != end);
++it;
public:
constexpr geometry_t() = default;
template <typename T>
constexpr explicit geometry_t(T geom, int srid = 4326)
: m_geom(std::move(geom)), m_srid(srid)
{}
constexpr int srid() const noexcept { return m_srid; }
constexpr void set_srid(int srid) noexcept { m_srid = srid; }
constexpr bool is_null() const noexcept
{
return std::holds_alternative<nullgeom_t>(m_geom);
}
constexpr bool is_point() const noexcept
{
return std::holds_alternative<point_t>(m_geom);
}
constexpr bool is_linestring() const noexcept
{
return std::holds_alternative<linestring_t>(m_geom);
}
constexpr bool is_polygon() const noexcept
{
return std::holds_alternative<polygon_t>(m_geom);
}
constexpr bool is_multipoint() const noexcept
{
return std::holds_alternative<multipoint_t>(m_geom);
}
constexpr bool is_multilinestring() const noexcept
{
return std::holds_alternative<multilinestring_t>(m_geom);
}
constexpr bool is_multipolygon() const noexcept
{
return std::holds_alternative<multipolygon_t>(m_geom);
}
constexpr bool is_collection() const noexcept
{
return std::holds_alternative<collection_t>(m_geom);
}
osmium::Location last{};
while (it != end) {
auto const loc = it->location();
if (loc.valid() && loc != last) {
linestring.add_point(proj.reproject(loc));
last = loc;
}
++it;
constexpr bool is_multi() const noexcept
{
return is_multipoint() || is_multilinestring() || is_multipolygon() ||
is_collection();
}
}
void make_multiline(osmium::memory::Buffer const &ways, double split_at,
reprojection const &proj, std::vector<linestring_t> *out);
template <typename T>
constexpr T const &get() const
{
return std::get<T>(m_geom);
}
template <typename T>
constexpr T &get()
{
return std::get<T>(m_geom);
}
void reset() { m_geom.emplace<nullgeom_t>(); }
template <typename T>
constexpr T &set()
{
return m_geom.emplace<T>();
}
template <typename V>
constexpr auto visit(V &&visitor) const
{
return std::visit(std::forward<V>(visitor), m_geom);
}
private:
std::variant<nullgeom_t, point_t, linestring_t, polygon_t, multipoint_t,
multilinestring_t, multipolygon_t, collection_t>
m_geom = nullgeom_t{};
int m_srid = 4326;
}; // class geometry_t
} // namespace geom

View File

@ -1,204 +0,0 @@
/**
* 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 <cassert>
#include <vector>
#include <osmium/area/geom_assembler.hpp>
#include "geom.hpp"
#include "osmium-builder.hpp"
namespace geom {
void osmium_builder_t::wrap_in_multipolygon(
osmium_builder_t::wkbs_t *geometries)
{
assert(!geometries->empty());
m_writer.multipolygon_start();
for (auto const &p : *geometries) {
m_writer.add_sub_geometry(p);
}
(*geometries)[0] = m_writer.multipolygon_finish(geometries->size());
geometries->resize(1);
}
void osmium_builder_t::wrap_in_multipolygon(osmium_builder_t::wkb_t *geometry)
{
m_writer.multipolygon_start();
m_writer.add_sub_geometry(*geometry);
*geometry = m_writer.multipolygon_finish(1);
}
osmium_builder_t::wkb_t
osmium_builder_t::get_wkb_node(osmium::Location const &loc) const
{
return m_writer.make_point(m_proj->reproject(loc));
}
osmium_builder_t::wkbs_t
osmium_builder_t::get_wkb_line(osmium::WayNodeList const &nodes,
double split_at)
{
std::vector<linestring_t> linestrings;
geom::make_line(linestring_t{nodes, *m_proj}, split_at, &linestrings);
wkbs_t ret;
for (auto const &line : linestrings) {
m_writer.linestring_start();
for (auto const &coord : line) {
m_writer.add_location(coord);
}
ret.push_back(m_writer.linestring_finish(line.size()));
}
return ret;
}
osmium_builder_t::wkb_t
osmium_builder_t::get_wkb_polygon(osmium::Way const &way)
{
osmium::area::AssemblerConfig area_config;
area_config.ignore_invalid_locations = true;
osmium::area::GeomAssembler assembler{area_config};
m_buffer.clear();
if (!assembler(way, m_buffer)) {
return wkb_t();
}
auto const wkbs = create_polygons(m_buffer.get<osmium::Area>(0));
return wkbs.empty() ? wkb_t{} : wkbs[0];
}
osmium_builder_t::wkbs_t
osmium_builder_t::get_wkb_multipolygon(osmium::Relation const &rel,
osmium::memory::Buffer const &ways,
bool build_multigeoms, bool wrap_multi)
{
osmium::area::AssemblerConfig area_config;
area_config.ignore_invalid_locations = true;
osmium::area::GeomAssembler assembler{area_config};
m_buffer.clear();
wkbs_t ret;
if (assembler(rel, ways, m_buffer)) {
auto const &area = m_buffer.get<osmium::Area>(0);
// This returns a vector of one or more polygons
ret = create_polygons(area);
assert(!ret.empty());
if (build_multigeoms) {
if (ret.size() > 1 || wrap_multi) {
wrap_in_multipolygon(&ret);
}
} else {
if (wrap_multi) {
for (auto &wkb : ret) {
// wrap each polygon into its own multipolygon
wrap_in_multipolygon(&wkb);
}
}
}
}
return ret;
}
osmium_builder_t::wkbs_t
osmium_builder_t::get_wkb_multiline(osmium::memory::Buffer const &ways,
double split_at)
{
std::vector<linestring_t> linestrings;
make_multiline(ways, split_at, *m_proj, &linestrings);
wkbs_t ret;
for (auto const &line : linestrings) {
m_writer.linestring_start();
for (auto const &coord : line) {
m_writer.add_location(coord);
}
ret.push_back(m_writer.linestring_finish(line.size()));
}
if (split_at <= 0.0 && !ret.empty()) {
auto const num_lines = ret.size();
m_writer.multilinestring_start();
for (auto const &line : ret) {
m_writer.add_sub_geometry(line);
}
ret.clear();
ret.push_back(m_writer.multilinestring_finish(num_lines));
}
return ret;
}
size_t osmium_builder_t::add_mp_points(osmium::NodeRefList const &nodes)
{
size_t num_points = 0;
osmium::Location last_location;
for (auto const &node_ref : nodes) {
if (node_ref.location().valid() &&
last_location != node_ref.location()) {
last_location = node_ref.location();
m_writer.add_location(m_proj->reproject(last_location));
++num_points;
}
}
return num_points;
}
osmium_builder_t::wkbs_t
osmium_builder_t::create_polygons(osmium::Area const &area)
{
wkbs_t ret;
try {
size_t num_rings = 0;
for (auto const &item : area) {
if (item.type() == osmium::item_type::outer_ring) {
auto const &ring = static_cast<osmium::OuterRing const &>(item);
if (num_rings > 0) {
ret.push_back(m_writer.polygon_finish(num_rings));
num_rings = 0;
}
m_writer.polygon_start();
m_writer.polygon_ring_start();
auto const num_points = add_mp_points(ring);
m_writer.polygon_ring_finish(num_points);
++num_rings;
} else if (item.type() == osmium::item_type::inner_ring) {
auto const &ring = static_cast<osmium::InnerRing const &>(item);
m_writer.polygon_ring_start();
auto const num_points = add_mp_points(ring);
m_writer.polygon_ring_finish(num_points);
++num_rings;
}
}
auto const wkb = m_writer.polygon_finish(num_rings);
if (num_rings > 0) {
ret.push_back(wkb);
}
} catch (osmium::geometry_error const &) { /* ignored */
}
return ret;
}
} // namespace geom

View File

@ -1,71 +0,0 @@
#ifndef OSM2PGSQL_OSMIUM_BUILDER_HPP
#define OSM2PGSQL_OSMIUM_BUILDER_HPP
/**
* 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 <memory>
#include <string>
#include <vector>
#include <osmium/memory/buffer.hpp>
#include <osmium/osm.hpp>
#include "reprojection.hpp"
#include "wkb.hpp"
namespace geom {
class osmium_builder_t
{
public:
using wkb_t = std::string;
using wkbs_t = std::vector<std::string>;
explicit osmium_builder_t(std::shared_ptr<reprojection> const &proj)
: m_proj(proj), m_buffer(1024, osmium::memory::Buffer::auto_grow::yes),
m_writer(m_proj->target_srs())
{}
wkb_t get_wkb_node(osmium::Location const &loc) const;
wkbs_t get_wkb_line(osmium::WayNodeList const &nodes, double split_at);
wkb_t get_wkb_polygon(osmium::Way const &way);
wkbs_t get_wkb_multipolygon(osmium::Relation const &rel,
osmium::memory::Buffer const &ways,
bool build_multigeoms, bool wrap_multi = false);
wkbs_t get_wkb_multiline(osmium::memory::Buffer const &ways,
double split_at);
/**
* Wrap the geometries (must be one or more polygons) in the parameter
* into a single multipolygon which is returned in-place in geometries.
*/
void wrap_in_multipolygon(wkbs_t *geometries);
/**
* Wrap the polygon geometry in the parameter into a multipolygon which
* is returned in-place in geometry.
*/
void wrap_in_multipolygon(wkb_t *geometry);
private:
wkbs_t create_polygons(osmium::Area const &area);
size_t add_mp_points(osmium::NodeRefList const &nodes);
std::shared_ptr<reprojection> m_proj;
// internal buffer for creating areas
osmium::memory::Buffer m_buffer;
ewkb::writer_t m_writer;
};
} // namespace geom
#endif // OSM2PGSQL_OSMIUM_BUILDER_HPP

View File

@ -10,6 +10,7 @@
#include "db-copy.hpp"
#include "expire-tiles.hpp"
#include "format.hpp"
#include "geom-functions.hpp"
#include "geom-transform.hpp"
#include "logging.hpp"
#include "lua-init.hpp"
@ -644,12 +645,20 @@ void output_flex_t::write_column(
void output_flex_t::write_row(table_connection_t *table_connection,
osmium::item_type id_type, osmid_t id,
std::string const &geom, int srid)
geom::geometry_t const &geom, int srid)
{
assert(table_connection);
table_connection->new_line();
auto *copy_mgr = table_connection->copy_mgr();
geom::geometry_t projected_geom;
geom::geometry_t const* output_geom = &geom;
if (srid && geom.srid() != srid) {
auto const proj = reprojection::create_projection(srid);
projected_geom = geom::transform(geom, *proj);
output_geom = &projected_geom;
}
for (auto const &column : table_connection->table()) {
if (column.create_only()) {
continue;
@ -659,19 +668,28 @@ void output_flex_t::write_row(table_connection_t *table_connection,
} else if (column.type() == table_column_type::id_num) {
copy_mgr->add_column(id);
} else if (column.is_geometry_column()) {
assert(!geom.empty());
copy_mgr->add_hex_geom(geom);
assert(!geom.is_null());
auto const type = column.type();
bool const wrap_multi =
(type == table_column_type::multilinestring ||
type == table_column_type::multipolygon);
copy_mgr->add_hex_geom(geom_to_ewkb(*output_geom, wrap_multi));
} else if (column.type() == table_column_type::area) {
if (geom.empty()) {
if (geom.is_null()) {
write_null(copy_mgr, column);
} else {
// if srid of the area column is the same as for the geom column
double const area =
column.srid() == srid
? ewkb::parser_t(geom)
.get_area<osmium::geom::IdentityProjection>()
: ewkb::parser_t(geom).get_area<reprojection>(
reprojection::create_projection(srid).get());
double area = 0;
if (column.srid() == 4326) {
area = geom::area(geom);
} else if (column.srid() == srid) {
area = geom::area(projected_geom);
} else {
// XXX there is some overhead here always dynamically
// creating the same projection object. Needs refactoring.
auto const mproj = reprojection::create_projection(column.srid());
area = geom::area(geom::transform(geom, *mproj));
}
copy_mgr->add_column(area);
}
} else {
@ -1158,32 +1176,27 @@ get_default_transform(flex_table_column_t const &column,
column.name())};
}
geom::osmium_builder_t::wkbs_t
output_flex_t::run_transform(geom::osmium_builder_t *builder,
geom_transform_t const *transform,
table_column_type target_geom_type,
osmium::Node const &node)
geom::geometry_t output_flex_t::run_transform(reprojection const &proj,
geom_transform_t const *transform,
osmium::Node const &node)
{
return transform->run(builder, target_geom_type, node);
return transform->convert(proj, node);
}
geom::osmium_builder_t::wkbs_t
output_flex_t::run_transform(geom::osmium_builder_t *builder,
geom_transform_t const *transform,
table_column_type target_geom_type,
osmium::Way const & /*way*/)
geom::geometry_t output_flex_t::run_transform(reprojection const &proj,
geom_transform_t const *transform,
osmium::Way const & /*way*/)
{
if (get_way_nodes() <= 1U) {
return {};
}
return transform->run(builder, target_geom_type, m_context_way);
return transform->convert(proj, *m_context_way);
}
geom::osmium_builder_t::wkbs_t
output_flex_t::run_transform(geom::osmium_builder_t *builder,
geom_transform_t const *transform,
table_column_type target_geom_type,
osmium::Relation const &relation)
geom::geometry_t output_flex_t::run_transform(reprojection const &proj,
geom_transform_t const *transform,
osmium::Relation const &relation)
{
m_buffer.clear();
auto const num_ways = middle().rel_members_get(
@ -1197,7 +1210,7 @@ output_flex_t::run_transform(geom::osmium_builder_t *builder,
middle().nodes_get_list(&(way.nodes()));
}
return transform->run(builder, target_geom_type, relation, m_buffer);
return transform->convert(proj, relation, m_buffer);
}
template <typename OBJECT>
@ -1210,7 +1223,7 @@ void output_flex_t::add_row(table_connection_t *table_connection,
osmid_t const id = table.map_id(object.type(), object.id());
if (!table.has_geom_column()) {
write_row(table_connection, object.type(), id, "", 0);
write_row(table_connection, object.type(), id, {}, 0);
return;
}
@ -1231,12 +1244,27 @@ void output_flex_t::add_row(table_connection_t *table_connection,
transform = get_default_transform(table.geom_column(), object.type());
}
auto *builder = table_connection->get_builder();
auto const wkbs =
run_transform(builder, transform, table.geom_column().type(), object);
for (auto const &wkb : wkbs) {
auto const &proj = table_connection->proj();
auto const type = table.geom_column().type();
// The geometry returned by run_transform() is in 4326 if it is a
// (multi)polygon. If it is a point or linestring, it is already in the
// target geometry.
auto const geom = run_transform(proj, transform, object);
// We need to split a multi geometry into its parts if the geometry
// column can only take non-multi geometries or if the transform
// explicitly asked us to split, which is the case when an area
// transform explicitly set `split_at = 'multi'`.
bool const split_multi = type == table_column_type::linestring ||
type == table_column_type::polygon ||
transform->split();
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);
write_row(table_connection, object.type(), id, wkb,
write_row(table_connection, object.type(), id, sgeom,
table.geom_column().srid());
}
}

View File

@ -13,7 +13,7 @@
#include "expire-tiles.hpp"
#include "flex-table-column.hpp"
#include "flex-table.hpp"
#include "osmium-builder.hpp"
#include "geom.hpp"
#include "output.hpp"
#include <osmium/index/id_set.hpp>
@ -191,21 +191,19 @@ private:
flex_table_column_t const &column);
void write_row(table_connection_t *table_connection,
osmium::item_type id_type, osmid_t id,
std::string const &geom, int srid);
geom::geometry_t const &geom, int srid);
geom::osmium_builder_t::wkbs_t
run_transform(geom::osmium_builder_t *builder,
geom_transform_t const *transform,
table_column_type target_geom_type, osmium::Node const &node);
geom::geometry_t run_transform(reprojection const &proj,
geom_transform_t const *transform,
osmium::Node const &node);
geom::osmium_builder_t::wkbs_t
run_transform(geom::osmium_builder_t *builder,
geom_transform_t const *transform,
table_column_type target_geom_type, osmium::Way const &way);
geom::geometry_t run_transform(reprojection const &proj,
geom_transform_t const *transform,
osmium::Way const &way);
geom::osmium_builder_t::wkbs_t run_transform(
geom::osmium_builder_t *builder, geom_transform_t const *transform,
table_column_type target_geom_type, osmium::Relation const &relation);
geom::geometry_t run_transform(reprojection const &proj,
geom_transform_t const *transform,
osmium::Relation const &relation);
template <typename OBJECT>
void add_row(table_connection_t *table_connection, OBJECT const &object);

View File

@ -8,6 +8,9 @@
*/
#include "format.hpp"
#include "geom.hpp"
#include "geom-from-osm.hpp"
#include "geom-functions.hpp"
#include "middle.hpp"
#include "options.hpp"
#include "osmtypes.hpp"
@ -99,7 +102,8 @@ bool output_gazetteer_t::process_node(osmium::Node const &node)
return false;
}
auto const wkb = m_builder.get_wkb_node(node.location());
auto const wkb =
geom_to_ewkb(geom::transform(geom::create_point(node), *m_proj));
delete_unused_classes('N', node.id());
m_style.copy_out(node, wkb, m_copy);
@ -132,21 +136,19 @@ bool output_gazetteer_t::process_way(osmium::Way *way)
middle().nodes_get_list(&(way->nodes()));
// Get the geometry of the object.
geom::osmium_builder_t::wkb_t geom;
geom::geometry_t geom;
if (way->is_closed()) {
geom = m_builder.get_wkb_polygon(*way);
geom = geom::transform(geom::create_polygon(*way), *m_proj);
}
if (geom.empty()) {
auto const wkbs = m_builder.get_wkb_line(way->nodes(), 0.0);
if (wkbs.empty()) {
if (geom.is_null()) {
geom = geom::transform(geom::create_linestring(*way), *m_proj);
if (geom.is_null()) {
return false;
}
geom = wkbs[0];
}
delete_unused_classes('W', way->id());
m_style.copy_out(*way, geom, m_copy);
m_style.copy_out(*way, geom_to_ewkb(geom), m_copy);
return true;
}
@ -200,17 +202,17 @@ bool output_gazetteer_t::process_relation(osmium::Relation const &rel)
middle().nodes_get_list(&(w.nodes()));
}
auto const geoms =
is_waterway
? m_builder.get_wkb_multiline(m_osmium_buffer, 0.0)
: m_builder.get_wkb_multipolygon(rel, m_osmium_buffer, true);
auto const geom = geom::transform(
is_waterway ? geom::create_multilinestring(m_osmium_buffer)
: geom::create_multipolygon(rel, m_osmium_buffer),
*m_proj);
if (geoms.empty()) {
if (geom.is_null()) {
return false;
}
delete_unused_classes('R', rel.id());
m_style.copy_out(rel, geoms[0], m_copy);
m_style.copy_out(rel, geom_to_ewkb(geom), m_copy);
return true;
}

View File

@ -16,9 +16,10 @@
#include <osmium/memory/buffer.hpp>
#include "gazetteer-style.hpp"
#include "osmium-builder.hpp"
#include "osmtypes.hpp"
#include "output.hpp"
#include "reprojection.hpp"
#include "wkb.hpp"
class db_copy_thread_t;
class thread_pool_t;
@ -31,7 +32,7 @@ class output_gazetteer_t : public output_t
std::shared_ptr<middle_query_t> const &cloned_mid,
std::shared_ptr<db_copy_thread_t> const &copy_thread)
: output_t(cloned_mid, other->m_thread_pool, other->m_options),
m_copy(copy_thread), m_builder(other->m_options.projection),
m_copy(copy_thread), m_proj(other->m_options.projection),
m_osmium_buffer(PLACE_BUFFER_SIZE, osmium::memory::Buffer::auto_grow::yes)
{}
@ -41,7 +42,7 @@ public:
options_t const &options,
std::shared_ptr<db_copy_thread_t> const &copy_thread)
: output_t(mid, std::move(thread_pool), options), m_copy(copy_thread),
m_builder(options.projection),
m_proj(options.projection),
m_osmium_buffer(PLACE_BUFFER_SIZE, osmium::memory::Buffer::auto_grow::yes)
{
m_style.load_style(options.style);
@ -91,7 +92,7 @@ private:
gazetteer_copy_mgr_t m_copy;
gazetteer_style_t m_style;
geom::osmium_builder_t m_builder;
std::shared_ptr<reprojection> m_proj;
osmium::memory::Buffer m_osmium_buffer;
};

View File

@ -30,6 +30,8 @@
#include <unistd.h>
#include "expire-tiles.hpp"
#include "geom-from-osm.hpp"
#include "geom-functions.hpp"
#include "logging.hpp"
#include "middle.hpp"
#include "options.hpp"
@ -43,20 +45,33 @@
#include "wildcmp.hpp"
#include "wkb.hpp"
static double calculate_area(bool reproject_area,
geom::geometry_t const &geom4326,
geom::geometry_t const &geom)
{
if (reproject_area) {
// XXX there is some overhead here always dynamically
// creating the same projection object. Needs refactoring.
auto const ogeom =
geom::transform(geom4326, *reprojection::create_projection(3857));
return geom::area(ogeom);
}
return geom::area(geom);
}
void output_pgsql_t::pgsql_out_way(osmium::Way const &way, taglist_t *tags,
bool polygon, bool roads)
{
if (polygon && way.is_closed()) {
auto wkb = m_builder.get_wkb_polygon(way);
auto const geom = geom::create_polygon(way);
auto const projected_geom = geom::transform(geom, *m_proj);
auto const wkb = geom_to_ewkb(projected_geom);
if (!wkb.empty()) {
m_expire.from_wkb(wkb, way.id());
if (m_enable_way_area) {
auto const area =
m_options.reproject_area
? ewkb::parser_t(wkb).get_area<reprojection>(
m_options.projection.get())
: ewkb::parser_t(wkb)
.get_area<osmium::geom::IdentityProjection>();
double const area = calculate_area(m_options.reproject_area,
geom, projected_geom);
util::double_to_buffer tmp{area};
tags->set("way_area", tmp.c_str());
}
@ -65,7 +80,10 @@ void output_pgsql_t::pgsql_out_way(osmium::Way const &way, taglist_t *tags,
} else {
double const split_at =
m_options.projection->target_latlon() ? 1 : 100 * 1000;
for (auto const &wkb : m_builder.get_wkb_line(way.nodes(), split_at)) {
auto const geoms = geom::split_multi(geom::segmentize(
geom::transform(geom::create_linestring(way), *m_proj), split_at));
for (auto const &sgeom : geoms) {
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) {
@ -147,7 +165,8 @@ void output_pgsql_t::node_add(osmium::Node const &node)
return;
}
auto wkb = m_builder.get_wkb_node(node.location());
auto const wkb =
geom_to_ewkb(geom::transform(geom::create_point(node), *m_proj));
m_expire.from_wkb(wkb, node.id());
m_tables[t_point]->write_row(node.id(), outtags, wkb);
}
@ -243,8 +262,14 @@ void output_pgsql_t::pgsql_process_relation(osmium::Relation const &rel)
if (!make_polygon) {
double const split_at =
m_options.projection->target_latlon() ? 1 : 100 * 1000;
auto wkbs = m_builder.get_wkb_multiline(m_buffer, split_at);
for (auto const &wkb : wkbs) {
auto geom = geom::line_merge(geom::create_multilinestring(m_buffer));
auto projected_geom = geom::transform(geom, *m_proj);
if (!projected_geom.is_null() && split_at > 0.0) {
projected_geom = geom::segmentize(projected_geom, split_at);
}
auto const geoms = geom::split_multi(projected_geom);
for (auto const &sgeom : geoms) {
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) {
@ -255,18 +280,15 @@ void output_pgsql_t::pgsql_process_relation(osmium::Relation const &rel)
// multipolygons and boundaries
if (make_boundary || make_polygon) {
auto wkbs = m_builder.get_wkb_multipolygon(rel, m_buffer,
m_options.enable_multi);
for (auto const &wkb : wkbs) {
auto const geoms = geom::split_multi(
geom::create_multipolygon(rel, m_buffer), !m_options.enable_multi);
for (auto const &sgeom : geoms) {
auto const projected_geom = geom::transform(sgeom, *m_proj);
auto const wkb = geom_to_ewkb(projected_geom);
m_expire.from_wkb(wkb, -rel.id());
if (m_enable_way_area) {
auto const area =
m_options.reproject_area
? ewkb::parser_t(wkb).get_area<reprojection>(
m_options.projection.get())
: ewkb::parser_t(wkb)
.get_area<osmium::geom::IdentityProjection>();
double const area = calculate_area(m_options.reproject_area,
sgeom, projected_geom);
util::double_to_buffer tmp{area};
outtags.set("way_area", tmp.c_str());
}
@ -391,7 +413,7 @@ output_pgsql_t::output_pgsql_t(
std::shared_ptr<middle_query_t> const &mid,
std::shared_ptr<thread_pool_t> thread_pool, options_t const &o,
std::shared_ptr<db_copy_thread_t> const &copy_thread)
: output_t(mid, std::move(thread_pool), o), m_builder(o.projection),
: output_t(mid, std::move(thread_pool), o), m_proj(o.projection),
m_expire(o.expire_tiles_zoom, o.expire_tiles_max_bbox, o.projection),
m_buffer(32768, osmium::memory::Buffer::auto_grow::yes),
m_rels_buffer(1024, osmium::memory::Buffer::auto_grow::yes)
@ -449,7 +471,7 @@ output_pgsql_t::output_pgsql_t(
std::shared_ptr<db_copy_thread_t> const &copy_thread)
: output_t(mid, other->m_thread_pool, other->m_options),
m_tagtransform(other->m_tagtransform->clone()),
m_enable_way_area(other->m_enable_way_area), m_builder(m_options.projection),
m_enable_way_area(other->m_enable_way_area), m_proj(m_options.projection),
m_expire(m_options.expire_tiles_zoom, m_options.expire_tiles_max_bbox,
m_options.projection),
m_buffer(1024, osmium::memory::Buffer::auto_grow::yes),

View File

@ -17,8 +17,8 @@
#include "db-copy.hpp"
#include "expire-tiles.hpp"
#include "osmium-builder.hpp"
#include "output.hpp"
#include "reprojection.hpp"
#include "table.hpp"
#include "tagtransform.hpp"
@ -89,7 +89,7 @@ protected:
std::array<std::unique_ptr<table_t>, t_MAX> m_tables;
geom::osmium_builder_t m_builder;
std::shared_ptr<reprojection> m_proj;
expire_tiles m_expire;
osmium::memory::Buffer m_buffer;

View File

@ -23,19 +23,22 @@ public:
explicit generic_reprojection_t(int srs) : m_target_srs(srs), pj_target(srs)
{}
osmium::geom::Coordinates reproject(osmium::Location loc) const override
geom::point_t reproject(geom::point_t point) const override
{
double const lon = osmium::geom::deg_to_rad(loc.lon_without_check());
double const lat = osmium::geom::deg_to_rad(loc.lat_without_check());
double const lon = osmium::geom::deg_to_rad(point.x());
double const lat = osmium::geom::deg_to_rad(point.y());
return osmium::geom::transform(pj_source, pj_target,
osmium::geom::Coordinates{lon, lat});
auto const c = osmium::geom::transform(
pj_source, pj_target, osmium::geom::Coordinates{lon, lat});
return {c.x, c.y};
}
osmium::geom::Coordinates
target_to_tile(osmium::geom::Coordinates coords) const override
geom::point_t target_to_tile(geom::point_t point) const override
{
return osmium::geom::transform(pj_target, pj_tile, coords);
auto const c = osmium::geom::transform(
pj_target, pj_tile,
osmium::geom::Coordinates{point.x(), point.y()});
return {c.x, c.y};
}
int target_srs() const noexcept override { return m_target_srs; }

View File

@ -20,17 +20,16 @@ public:
m_transformation_tile(create_transformation(srs, PROJ_SPHERE_MERC))
{}
osmium::geom::Coordinates reproject(osmium::Location loc) const override
geom::point_t
reproject(geom::point_t point) const noexcept override
{
return transform(m_transformation.get(),
osmium::geom::Coordinates{loc.lon_without_check(),
loc.lat_without_check()});
return transform(m_transformation.get(), point);
}
osmium::geom::Coordinates
target_to_tile(osmium::geom::Coordinates coords) const override
geom::point_t
target_to_tile(geom::point_t point) const override
{
return transform(m_transformation_tile.get(), coords);
return transform(m_transformation_tile.get(), point);
}
int target_srs() const noexcept override { return m_target_srs; }
@ -85,20 +84,19 @@ private:
return trans_vis;
}
osmium::geom::Coordinates transform(PJ *transformation,
osmium::geom::Coordinates coords) const
noexcept
geom::point_t transform(PJ *transformation,
geom::point_t point) const noexcept
{
PJ_COORD c_in;
c_in.lpzt.z = 0.0;
c_in.lpzt.t = HUGE_VAL;
c_in.lpzt.lam = coords.x;
c_in.lpzt.phi = coords.y;
c_in.lpzt.lam = point.x();
c_in.lpzt.phi = point.y();
auto const c_out = proj_trans(transformation, PJ_FWD, c_in);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
return osmium::geom::Coordinates{c_out.xy.x, c_out.xy.y};
return {c_out.xy.x, c_out.xy.y};
}
int m_target_srs;

View File

@ -14,31 +14,31 @@
namespace {
osmium::geom::Coordinates lonlat2merc(osmium::geom::Coordinates coords)
geom::point_t lonlat2merc(geom::point_t point)
{
osmium::geom::Coordinates coords{point.x(), point.y()};
if (coords.y > 89.99) {
coords.y = 89.99;
} else if (coords.y < -89.99) {
coords.y = -89.99;
}
return osmium::geom::lonlat_to_mercator(coords);
auto c = osmium::geom::lonlat_to_mercator(coords);
return {c.x, c.y};
}
class latlon_reprojection_t : public reprojection
{
public:
osmium::geom::Coordinates reproject(osmium::Location loc) const
noexcept override
geom::point_t reproject(geom::point_t point) const noexcept override
{
return osmium::geom::Coordinates{loc.lon_without_check(),
loc.lat_without_check()};
return point;
}
osmium::geom::Coordinates target_to_tile(osmium::geom::Coordinates c) const
noexcept override
geom::point_t target_to_tile(geom::point_t point) const noexcept override
{
return lonlat2merc(c);
return lonlat2merc(point);
}
int target_srs() const noexcept override { return PROJ_LATLONG; }
@ -49,15 +49,12 @@ public:
class merc_reprojection_t : public reprojection
{
public:
osmium::geom::Coordinates reproject(osmium::Location loc) const override
geom::point_t reproject(geom::point_t coords) const noexcept override
{
osmium::geom::Coordinates const coords{loc.lon_without_check(),
loc.lat_without_check()};
return lonlat2merc(coords);
}
osmium::geom::Coordinates target_to_tile(osmium::geom::Coordinates c) const
noexcept override
geom::point_t target_to_tile(geom::point_t c) const noexcept override
{
return c;
}

View File

@ -9,8 +9,7 @@
* It contains the reprojection class.
*/
#include <osmium/geom/coordinates.hpp>
#include <osmium/osm/location.hpp>
#include "geom.hpp"
#include <memory>
#include <string>
@ -44,14 +43,13 @@ public:
* Reproject from the source projection lat/lon (EPSG:4326)
* to target projection.
*/
virtual osmium::geom::Coordinates reproject(osmium::Location loc) const = 0;
virtual geom::point_t reproject(geom::point_t point) const = 0;
/**
* Converts coordinates from target projection to tile projection
* (EPSG:3857)
*/
virtual osmium::geom::Coordinates
target_to_tile(osmium::geom::Coordinates) const = 0;
virtual geom::point_t target_to_tile(geom::point_t point) const = 0;
virtual int target_srs() const noexcept = 0;

197
src/wkb.cpp Normal file
View File

@ -0,0 +1,197 @@
/**
* 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 "wkb.hpp"
namespace ewkb {
template <typename T>
static void str_push(std::string *data, T value)
{
data->append(reinterpret_cast<char const *const>(&value), sizeof(T));
}
/**
* Add a EWKB header without length field to the string.
*
* This header is always 1 + 4 + 4 = 9 bytes long
*
* \pre \code data != nullptr \endcode
*/
static void write_header(std::string *data, geometry_type type, uint32_t srid)
{
str_push(data, Endian);
if (srid) {
str_push(data, type | geometry_type::wkb_srid);
str_push(data, srid);
} else {
str_push(data, type);
}
}
/**
* Add a EWKB 32bit unsigned int length field to the string.
*
* This header is always 4 bytes long
*
* \pre \code data != nullptr \endcode
*/
static void write_length(std::string *data, std::size_t length)
{
str_push(data, static_cast<uint32_t>(length));
}
static void write_points(std::string *data, geom::point_list_t const &points)
{
write_length(data, points.size());
for (auto const &point : points) {
str_push(data, point.x());
str_push(data, point.y());
}
}
static void write_linestring(std::string *data, geom::linestring_t const &geom,
uint32_t srid)
{
assert(data);
write_header(data, wkb_line, srid);
write_points(data, geom);
}
static void write_polygon(std::string *data, geom::polygon_t const &geom,
uint32_t srid)
{
assert(data);
write_header(data, wkb_polygon, srid);
write_length(data, geom.inners().size() + 1);
write_points(data, geom.outer());
for (auto const &ring : geom.inners()) {
write_points(data, ring);
}
}
namespace {
class make_ewkb_visitor
{
public:
make_ewkb_visitor(uint32_t srid, bool ensure_multi) noexcept
: m_srid(srid), m_ensure_multi(ensure_multi)
{}
std::string operator()(geom::nullgeom_t const & /*geom*/) const
{
return {};
}
std::string operator()(geom::point_t const &geom) const
{
// 9 byte header plus one set of coordinates
constexpr const std::size_t size = 9 + 2 * 8;
std::string data;
data.reserve(size);
write_header(&data, wkb_point, m_srid);
str_push(&data, geom.x());
str_push(&data, geom.y());
assert(data.size() == size);
return data;
}
std::string operator()(geom::linestring_t const &geom) const
{
std::string data;
if (m_ensure_multi) {
// Two 13 bytes headers plus n sets of coordinates
data.reserve(2 * 13 + geom.size() * (2 * 8));
write_header(&data, wkb_multi_line, m_srid);
write_length(&data, 1);
} else {
// 13 byte header plus n sets of coordinates
data.reserve(13 + geom.size() * (2 * 8));
}
write_linestring(&data, geom, m_srid);
return data;
}
std::string operator()(geom::polygon_t const &geom) const
{
std::string data;
if (m_ensure_multi) {
write_header(&data, wkb_multi_polygon, m_srid);
write_length(&data, 1);
}
write_polygon(&data, geom, m_srid);
return data;
}
std::string operator()(geom::multipoint_t const & /*geom*/) const
{
assert(false);
return {}; // XXX not used yet, no implementation
}
std::string operator()(geom::multilinestring_t const &geom) const
{
std::string data;
write_header(&data, wkb_multi_line, m_srid);
write_length(&data, geom.num_geometries());
for (auto const &line : geom) {
write_linestring(&data, line, 0);
}
return data;
}
std::string operator()(geom::multipolygon_t const &geom) const
{
std::string data;
write_header(&data, wkb_multi_polygon, m_srid);
write_length(&data, geom.num_geometries());
for (auto const &polygon : geom) {
write_polygon(&data, polygon, 0);
}
return data;
}
std::string operator()(geom::collection_t const & /*geom*/) const
{
assert(false);
return {}; // XXX not used yet, no implementation
}
private:
uint32_t m_srid;
bool m_ensure_multi;
}; // class make_ewkb_visitor
} // anonymous namespace
} // namespace ewkb
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});
}

View File

@ -10,14 +10,22 @@
* For a full list of authors see the git log.
*/
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <string>
#include "geom.hpp"
#include <osmium/geom/coordinates.hpp>
#include <osmium/geom/factory.hpp>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <string>
#include <utility>
/**
* \file
*
* Functions for converting geometries from and to (E)WKB.
*/
namespace ewkb {
@ -43,199 +51,6 @@ enum wkb_byte_order_type_t : uint8_t
#endif
};
template <typename T>
static void str_push(std::string *str, T data)
{
assert(str);
str->append(reinterpret_cast<char const *const>(&data), sizeof(T));
}
/**
* Add a EWKB header to the string.
*
* \pre \code str != nullptr \endcode
*/
inline std::size_t write_header(std::string *str, geometry_type type,
uint32_t srid)
{
assert(str);
str_push(str, Endian);
str_push(str, type | wkb_srid);
str_push(str, srid);
return str->size();
}
/**
* Add a EWKB header to the string and add a dummy placeholder length of 0.
* This can later be replaced by the real length.
*
* \pre \code str != nullptr \endcode
*/
inline std::size_t write_header_with_length(std::string *str,
geometry_type type, uint32_t srid)
{
auto const offset = write_header(str, type, srid);
str_push(str, static_cast<uint32_t>(0));
return offset;
}
/// Create EWKB Point geometry.
inline std::string create_point(double x, double y, uint32_t srid = 4326)
{
std::string data;
data.reserve(25); // Point geometries are always 25 bytes
write_header(&data, wkb_point, srid);
str_push(&data, x);
str_push(&data, y);
return data;
}
/**
* Writer for EWKB data suitable for postgres.
*
* Code has been largely derived from osmium::geom::WKBFactoryImpl.
*/
class writer_t
{
public:
explicit writer_t(int srid) : m_srid(static_cast<uint32_t>(srid))
{
assert(srid > 0);
}
void add_sub_geometry(std::string const &part)
{
assert(!m_data.empty());
m_data.append(part);
}
void add_location(osmium::geom::Coordinates const &xy)
{
assert(!m_data.empty());
str_push(&m_data, xy.x);
str_push(&m_data, xy.y);
}
/* Point */
std::string make_point(osmium::geom::Coordinates const &xy) const
{
return create_point(xy.x, xy.y, m_srid);
}
/* LineString */
void linestring_start()
{
assert(m_data.empty());
m_geometry_size_offset =
write_header_with_length(&m_data, wkb_line, m_srid);
}
std::string linestring_finish(std::size_t num_points)
{
set_size(m_geometry_size_offset, num_points);
std::string data;
using std::swap;
swap(data, m_data);
return data;
}
/* MultiLineString */
void multilinestring_start()
{
assert(m_data.empty());
m_multigeometry_size_offset =
write_header_with_length(&m_data, wkb_multi_line, m_srid);
}
std::string multilinestring_finish(std::size_t num_lines)
{
set_size(m_multigeometry_size_offset, num_lines);
std::string data;
using std::swap;
swap(data, m_data);
return data;
}
/* Polygon */
void polygon_start()
{
assert(m_data.empty());
m_geometry_size_offset =
write_header_with_length(&m_data, wkb_polygon, m_srid);
}
void polygon_ring_start()
{
m_ring_size_offset = m_data.size();
str_push(&m_data, static_cast<uint32_t>(0));
}
void polygon_ring_finish(std::size_t num_points)
{
set_size(m_ring_size_offset, num_points);
}
std::string polygon_finish(std::size_t num_rings)
{
set_size(m_geometry_size_offset, num_rings);
std::string data;
using std::swap;
swap(data, m_data);
return data;
}
/* MultiPolygon */
void multipolygon_start()
{
assert(m_data.empty());
m_multigeometry_size_offset =
write_header_with_length(&m_data, wkb_multi_polygon, m_srid);
}
std::string multipolygon_finish(std::size_t num_polygons)
{
set_size(m_multigeometry_size_offset, num_polygons);
std::string data;
using std::swap;
swap(data, m_data);
return data;
}
private:
void set_size(std::size_t offset, std::size_t size)
{
assert(m_data.size() >= offset + sizeof(uint32_t));
auto const s = static_cast<uint32_t>(size);
std::memcpy(&m_data[offset], reinterpret_cast<char const *>(&s),
sizeof(uint32_t));
}
std::string m_data;
std::size_t m_geometry_size_offset = 0;
std::size_t m_multigeometry_size_offset = 0;
std::size_t m_ring_size_offset = 0;
uint32_t m_srid;
}; // class writer_t
/**
* Class that allows to iterate over the elements of a ewkb geometry.
*
@ -317,85 +132,7 @@ public:
m_pos += length;
}
template <typename PROJ>
double get_area(PROJ *proj = nullptr)
{
double total = 0;
auto const type = read_header();
if (type == wkb_polygon) {
total = get_polygon_area(proj);
} else if (type == wkb_multi_polygon) {
auto const num_poly = read_length();
for (unsigned i = 0; i < num_poly; ++i) {
auto ptype = read_header();
(void)ptype;
assert(ptype == wkb_polygon);
total += get_polygon_area(proj);
}
}
return total;
}
private:
template <typename PROJ>
double get_polygon_area(PROJ *proj)
{
auto const num_rings = read_length();
assert(num_rings > 0);
double total = get_ring_area(proj);
for (unsigned i = 1; i < num_rings; ++i) {
total -= get_ring_area(proj);
}
return total;
}
template <typename PROJ>
double get_ring_area(PROJ *proj)
{
// Algorithm borrowed from
// http://stackoverflow.com/questions/451426/how-do-i-calculate-the-area-of-a-2d-polygon
// XXX numerically not stable (useless for latlon)
auto const num_pts = read_length();
assert(num_pts > 3);
double total = 0;
auto prev = proj->target_to_tile(read_point());
for (unsigned i = 1; i < num_pts; ++i) {
auto const cur = proj->target_to_tile(read_point());
total += prev.x * cur.y - cur.x * prev.y;
prev = cur;
}
return std::abs(total) * 0.5;
}
double get_ring_area(osmium::geom::IdentityProjection *)
{
// Algorithm borrowed from
// http://stackoverflow.com/questions/451426/how-do-i-calculate-the-area-of-a-2d-polygon
auto const num_pts = read_length();
assert(num_pts > 3);
double total = 0;
auto prev = read_point();
for (unsigned i = 1; i < num_pts; ++i) {
auto const cur = read_point();
total += prev.x * cur.y - cur.x * prev.y;
prev = cur;
}
return std::abs(total) * 0.5;
}
void check_available(std::size_t length)
{
if (m_pos + length > m_wkb->size()) {
@ -421,4 +158,14 @@ private:
} // namespace ewkb
/**
* Convert single geometry to EWKB
*
* \param geom Input geometry
* \param ensure_multi Wrap non-multi geometries in multi geometries
* \returns String with EWKB encoded geometry
*/
std::string geom_to_ewkb(geom::geometry_t const &geom,
bool ensure_multi = false);
#endif // OSM2PGSQL_WKB_HPP

View File

@ -0,0 +1,55 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--
Nodes:
7 8
3 4 11 12
9 10
1 2 5 6
-->
<osm version="0.6">
<node id="1" version="1" lat="50.0" lon="9.0"/>
<node id="2" version="1" lat="50.0" lon="9.1"/>
<node id="3" version="1" lat="50.1" lon="9.0"/>
<node id="4" version="1" lat="50.1" lon="9.1"/>
<node id="5" version="1" lat="50.0" lon="9.2"/>
<node id="6" version="1" lat="50.0" lon="9.5"/>
<node id="7" version="1" lat="50.3" lon="9.2"/>
<node id="8" version="1" lat="50.3" lon="9.5"/>
<node id="9" version="1" lat="50.1" lon="9.3"/>
<node id="10" version="1" lat="50.1" lon="9.4"/>
<node id="11" version="1" lat="50.2" lon="9.3"/>
<node id="12" version="1" lat="50.2" lon="9.4"/>
<way id="1" version="1">
<nd ref="1"/>
<nd ref="2"/>
<nd ref="4"/>
<nd ref="3"/>
<nd ref="1"/>
<tag k="natural" v="water"/>
<tag k="name" v="poly"/>
</way>
<way id="2" version="1">
<nd ref="5"/>
<nd ref="6"/>
<nd ref="8"/>
<nd ref="7"/>
<nd ref="5"/>
</way>
<way id="3" version="1">
<nd ref="9"/>
<nd ref="10"/>
<nd ref="12"/>
<nd ref="11"/>
<nd ref="9"/>
</way>
<relation id='1' version='1'>
<member type="way" ref="2" role=""/>
<member type="way" ref="3" role=""/>
<tag k="type" v="multipolygon"/>
<tag k="natural" v="water"/>
<tag k="name" v="multi"/>
</relation>
</osm>

View File

@ -0,0 +1,5 @@
test = { geom_proj = 25832, area_proj = 25832 }
dofile(os.getenv('SRCPATH') .. '/data/test_output_flex_area.lua')

View File

@ -0,0 +1,5 @@
test = { geom_proj = 25832, area_proj = 3857 }
dofile(os.getenv('SRCPATH') .. '/data/test_output_flex_area.lua')

View File

@ -0,0 +1,5 @@
test = { geom_proj = 25832, area_proj = 4326 }
dofile(os.getenv('SRCPATH') .. '/data/test_output_flex_area.lua')

View File

@ -0,0 +1,5 @@
test = { geom_proj = 3857, area_proj = 25832 }
dofile(os.getenv('SRCPATH') .. '/data/test_output_flex_area.lua')

View File

@ -0,0 +1,5 @@
test = { geom_proj = 3857, area_proj = 4326 }
dofile(os.getenv('SRCPATH') .. '/data/test_output_flex_area.lua')

View File

@ -0,0 +1,5 @@
test = { geom_proj = 4326, area_proj = 25832 }
dofile(os.getenv('SRCPATH') .. '/data/test_output_flex_area.lua')

View File

@ -0,0 +1,5 @@
test = { geom_proj = 4326, area_proj = 3857 }
dofile(os.getenv('SRCPATH') .. '/data/test_output_flex_area.lua')

View File

@ -11,18 +11,18 @@
#include "common-buffer.hpp"
#include "geom-from-osm.hpp"
#include "geom-functions.hpp"
#include "geom.hpp"
#include "reprojection.hpp"
#include <array>
using Coordinates = osmium::geom::Coordinates;
TEST_CASE("geom::distance", "[NoDB]")
{
Coordinates const p1{10, 10};
Coordinates const p2{20, 10};
Coordinates const p3{13, 14};
geom::point_t const p1{10, 10};
geom::point_t const p2{20, 10};
geom::point_t const p3{13, 14};
REQUIRE(geom::distance(p1, p1) == Approx(0.0));
REQUIRE(geom::distance(p1, p2) == Approx(10.0));
@ -31,20 +31,20 @@ TEST_CASE("geom::distance", "[NoDB]")
TEST_CASE("geom::interpolate", "[NoDB]")
{
Coordinates const p1{10, 10};
Coordinates const p2{20, 10};
geom::point_t const p1{10, 10};
geom::point_t const p2{20, 10};
auto const i1 = geom::interpolate(p1, p1, 0.5);
REQUIRE(i1.x == 10);
REQUIRE(i1.y == 10);
REQUIRE(i1.x() == 10);
REQUIRE(i1.y() == 10);
auto const i2 = geom::interpolate(p1, p2, 0.5);
REQUIRE(i2.x == 15);
REQUIRE(i2.y == 10);
REQUIRE(i2.x() == 15);
REQUIRE(i2.y() == 10);
auto const i3 = geom::interpolate(p2, p1, 0.5);
REQUIRE(i3.x == 15);
REQUIRE(i3.y == 10);
REQUIRE(i3.x() == 15);
REQUIRE(i3.y() == 10);
}
TEST_CASE("geom::linestring_t", "[NoDB]")
@ -52,308 +52,290 @@ TEST_CASE("geom::linestring_t", "[NoDB]")
geom::linestring_t ls1;
REQUIRE(ls1.empty());
ls1.add_point(Coordinates{17, 42});
ls1.add_point(Coordinates{-3, 22});
REQUIRE(!ls1.empty());
ls1.emplace_back(17, 42);
ls1.emplace_back(-3, 22);
REQUIRE(ls1.size() == 2);
auto it = ls1.cbegin();
REQUIRE(it != ls1.cend());
REQUIRE(it->x == 17);
REQUIRE(it->x() == 17);
++it;
REQUIRE(it != ls1.cend());
REQUIRE(it->y == 22);
REQUIRE(it->y() == 22);
++it;
REQUIRE(it == ls1.cend());
}
TEST_CASE("geom::split_linestring w/o split", "[NoDB]")
TEST_CASE("geom::segmentize w/o split", "[NoDB]")
{
geom::linestring_t const line{Coordinates{0, 0}, Coordinates{1, 2},
Coordinates{2, 2}};
geom::linestring_t const line{{0, 0}, {1, 2}, {2, 2}};
std::vector<geom::linestring_t> result;
auto const geom = geom::segmentize(geom::geometry_t{line}, 10.0);
geom::split_linestring(line, 10.0, &result);
REQUIRE(result.size() == 1);
REQUIRE(result[0] == line);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == line);
}
TEST_CASE("geom::split_linestring with split 0.5", "[NoDB]")
TEST_CASE("geom::segmentize with split 0.5", "[NoDB]")
{
geom::linestring_t const line{Coordinates{0, 0}, Coordinates{1, 0}};
geom::linestring_t const line{{0, 0}, {1, 0}};
std::array<geom::linestring_t, 2> const expected{
geom::linestring_t{Coordinates{0, 0}, Coordinates{0.5, 0}},
geom::linestring_t{Coordinates{0.5, 0}, Coordinates{1, 0}}};
geom::linestring_t{{0, 0}, {0.5, 0}},
geom::linestring_t{{0.5, 0}, {1, 0}}};
std::vector<geom::linestring_t> result;
auto const geom = geom::segmentize(geom::geometry_t{line}, 0.5);
geom::split_linestring(line, 0.5, &result);
REQUIRE(result.size() == 2);
REQUIRE(result[0] == expected[0]);
REQUIRE(result[1] == expected[1]);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 2);
REQUIRE(ml[0] == expected[0]);
REQUIRE(ml[1] == expected[1]);
}
TEST_CASE("geom::split_linestring with split 0.4", "[NoDB]")
TEST_CASE("geom::segmentize with split 0.4", "[NoDB]")
{
geom::linestring_t const line{Coordinates{0, 0}, Coordinates{1, 0}};
geom::linestring_t const line{{0, 0}, {1, 0}};
std::array<geom::linestring_t, 3> const expected{
geom::linestring_t{Coordinates{0, 0}, Coordinates{0.4, 0}},
geom::linestring_t{Coordinates{0.4, 0}, Coordinates{0.8, 0}},
geom::linestring_t{Coordinates{0.8, 0}, Coordinates{1, 0}}};
geom::linestring_t{{0, 0}, {0.4, 0}},
geom::linestring_t{{0.4, 0}, {0.8, 0}},
geom::linestring_t{{0.8, 0}, {1, 0}}};
std::vector<geom::linestring_t> result;
auto const geom = geom::segmentize(geom::geometry_t{line}, 0.4);
geom::split_linestring(line, 0.4, &result);
REQUIRE(result.size() == 3);
REQUIRE(result[0] == expected[0]);
REQUIRE(result[1] == expected[1]);
REQUIRE(result[2] == expected[2]);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 3);
REQUIRE(ml[0] == expected[0]);
REQUIRE(ml[1] == expected[1]);
REQUIRE(ml[2] == expected[2]);
}
TEST_CASE("geom::split_linestring with split 1.0 at start", "[NoDB]")
TEST_CASE("geom::segmentize with split 1.0 at start", "[NoDB]")
{
geom::linestring_t const line{Coordinates{0, 0}, Coordinates{2, 0},
Coordinates{3, 0}, Coordinates{4, 0}};
geom::linestring_t const line{{0, 0}, {2, 0}, {3, 0}, {4, 0}};
std::array<geom::linestring_t, 4> const expected{
geom::linestring_t{Coordinates{0, 0}, Coordinates{1, 0}},
geom::linestring_t{Coordinates{1, 0}, Coordinates{2, 0}},
geom::linestring_t{Coordinates{2, 0}, Coordinates{3, 0}},
geom::linestring_t{Coordinates{3, 0}, Coordinates{4, 0}}};
geom::linestring_t{{0, 0}, {1, 0}}, geom::linestring_t{{1, 0}, {2, 0}},
geom::linestring_t{{2, 0}, {3, 0}}, geom::linestring_t{{3, 0}, {4, 0}}};
std::vector<geom::linestring_t> result;
auto const geom = geom::segmentize(geom::geometry_t{line}, 1.0);
geom::split_linestring(line, 1.0, &result);
REQUIRE(result.size() == 4);
REQUIRE(result[0] == expected[0]);
REQUIRE(result[1] == expected[1]);
REQUIRE(result[2] == expected[2]);
REQUIRE(result[3] == expected[3]);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 4);
REQUIRE(ml[0] == expected[0]);
REQUIRE(ml[1] == expected[1]);
REQUIRE(ml[2] == expected[2]);
REQUIRE(ml[3] == expected[3]);
}
TEST_CASE("geom::split_linestring with split 1.0 in middle", "[NoDB]")
TEST_CASE("geom::segmentize with split 1.0 in middle", "[NoDB]")
{
geom::linestring_t const line{Coordinates{0, 0}, Coordinates{1, 0},
Coordinates{3, 0}, Coordinates{4, 0}};
geom::linestring_t const line{{0, 0}, {1, 0}, {3, 0}, {4, 0}};
std::array<geom::linestring_t, 4> const expected{
geom::linestring_t{Coordinates{0, 0}, Coordinates{1, 0}},
geom::linestring_t{Coordinates{1, 0}, Coordinates{2, 0}},
geom::linestring_t{Coordinates{2, 0}, Coordinates{3, 0}},
geom::linestring_t{Coordinates{3, 0}, Coordinates{4, 0}}};
geom::linestring_t{{0, 0}, {1, 0}}, geom::linestring_t{{1, 0}, {2, 0}},
geom::linestring_t{{2, 0}, {3, 0}}, geom::linestring_t{{3, 0}, {4, 0}}};
std::vector<geom::linestring_t> result;
auto const geom = geom::segmentize(geom::geometry_t{line}, 1.0);
geom::split_linestring(line, 1.0, &result);
REQUIRE(result.size() == 4);
REQUIRE(result[0] == expected[0]);
REQUIRE(result[1] == expected[1]);
REQUIRE(result[2] == expected[2]);
REQUIRE(result[3] == expected[3]);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 4);
REQUIRE(ml[0] == expected[0]);
REQUIRE(ml[1] == expected[1]);
REQUIRE(ml[2] == expected[2]);
REQUIRE(ml[3] == expected[3]);
}
TEST_CASE("geom::split_linestring with split 1.0 at end", "[NoDB]")
TEST_CASE("geom::segmentize with split 1.0 at end", "[NoDB]")
{
geom::linestring_t const line{Coordinates{0, 0}, Coordinates{1, 0},
Coordinates{2, 0}, Coordinates{4, 0}};
geom::linestring_t const line{{0, 0}, {1, 0}, {2, 0}, {4, 0}};
std::array<geom::linestring_t, 4> const expected{
geom::linestring_t{Coordinates{0, 0}, Coordinates{1, 0}},
geom::linestring_t{Coordinates{1, 0}, Coordinates{2, 0}},
geom::linestring_t{Coordinates{2, 0}, Coordinates{3, 0}},
geom::linestring_t{Coordinates{3, 0}, Coordinates{4, 0}}};
geom::linestring_t{{0, 0}, {1, 0}}, geom::linestring_t{{1, 0}, {2, 0}},
geom::linestring_t{{2, 0}, {3, 0}}, geom::linestring_t{{3, 0}, {4, 0}}};
std::vector<geom::linestring_t> result;
auto const geom = geom::segmentize(geom::geometry_t{line}, 1.0);
geom::split_linestring(line, 1.0, &result);
REQUIRE(result.size() == 4);
REQUIRE(result[0] == expected[0]);
REQUIRE(result[1] == expected[1]);
REQUIRE(result[2] == expected[2]);
REQUIRE(result[3] == expected[3]);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 4);
REQUIRE(ml[0] == expected[0]);
REQUIRE(ml[1] == expected[1]);
REQUIRE(ml[2] == expected[2]);
REQUIRE(ml[3] == expected[3]);
}
TEST_CASE("make_multiline with single line", "[NoDB]")
TEST_CASE("create_multilinestring with single line", "[NoDB]")
{
geom::linestring_t const expected{Coordinates{1, 1}, Coordinates{2, 1}};
geom::linestring_t const expected{{1, 1}, {2, 1}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 1);
REQUIRE(lines[0] == expected);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == expected);
}
TEST_CASE("make_multiline with single line forming a ring", "[NoDB]")
TEST_CASE("create_multilinestring with single line forming a ring", "[NoDB]")
{
geom::linestring_t const expected{Coordinates{1, 1}, Coordinates{2, 1},
Coordinates{2, 2}, Coordinates{1, 1}};
geom::linestring_t const expected{{1, 1}, {2, 1}, {2, 2}, {1, 1}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1,n12x2y2,n10x1y1");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 1);
REQUIRE(lines[0] == expected);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == expected);
}
TEST_CASE("make_multiline from two non-joined lines", "[NoDB]")
TEST_CASE("create_multilinestring from two non-joined lines", "[NoDB]")
{
std::array<geom::linestring_t, 2> const expected{
geom::linestring_t{Coordinates{1, 1}, Coordinates{2, 1}},
geom::linestring_t{Coordinates{2, 2}, Coordinates{3, 2}}};
geom::linestring_t{{1, 1}, {2, 1}}, geom::linestring_t{{2, 2}, {3, 2}}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1");
buffer.add_way("w21 Nn12x2y2,n13x3y2");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 2);
REQUIRE(lines[0] == expected[0]);
REQUIRE(lines[1] == expected[1]);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 2);
REQUIRE(ml[0] == expected[0]);
REQUIRE(ml[1] == expected[1]);
}
TEST_CASE("make_multiline from two lines end to end", "[NoDB]")
TEST_CASE("create_multilinestring from two lines end to end", "[NoDB]")
{
geom::linestring_t const expected{Coordinates{1, 1}, Coordinates{2, 1},
Coordinates{2, 2}};
geom::linestring_t const expected{{1, 1}, {2, 1}, {2, 2}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1");
buffer.add_way("w21 Nn11x2y1,n12x2y2");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 1);
REQUIRE(lines[0] == expected);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == expected);
}
TEST_CASE("make_multiline from two lines with same start point", "[NoDB]")
TEST_CASE("create_multilinestring from two lines with same start point",
"[NoDB]")
{
geom::linestring_t const expected{Coordinates{2, 1}, Coordinates{1, 1},
Coordinates{1, 2}};
geom::linestring_t const expected{{2, 1}, {1, 1}, {1, 2}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1");
buffer.add_way("w21 Nn10x1y1,n12x1y2");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 1);
REQUIRE(lines[0] == expected);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == expected);
}
TEST_CASE("make_multiline from two lines with same end point", "[NoDB]")
TEST_CASE("create_multilinestring from two lines with same end point", "[NoDB]")
{
geom::linestring_t const expected{Coordinates{1, 2}, Coordinates{1, 1},
Coordinates{2, 1}};
geom::linestring_t const expected{{1, 2}, {1, 1}, {2, 1}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y2,n11x1y1");
buffer.add_way("w21 Nn12x2y1,n11x1y1");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 1);
REQUIRE(lines[0] == expected);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == expected);
}
TEST_CASE("make_multiline from two lines connected end to end forming a ring", "[NoDB]")
TEST_CASE("create_multilinestring from two lines connected end to end forming "
"a ring",
"[NoDB]")
{
geom::linestring_t const expected{Coordinates{1, 1}, Coordinates{2, 1},
Coordinates{2, 2}, Coordinates{1, 2},
Coordinates{1, 1}};
geom::linestring_t const expected{{1, 1}, {2, 1}, {2, 2}, {1, 2}, {1, 1}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1,n13x2y2");
buffer.add_way("w21 Nn13x2y2,n12x1y2,n10x1y1");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 1);
REQUIRE(lines[0] == expected);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == expected);
}
TEST_CASE("make_multiline from two lines with same start and end point", "[NoDB]")
TEST_CASE("create_multilinestring from two lines with same start and end point",
"[NoDB]")
{
geom::linestring_t const expected{Coordinates{2, 2}, Coordinates{2, 1},
Coordinates{1, 1}, Coordinates{1, 2},
Coordinates{2, 2}};
geom::linestring_t const expected{{2, 2}, {2, 1}, {1, 1}, {1, 2}, {2, 2}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1,n13x2y2");
buffer.add_way("w21 Nn10x1y1,n12x1y2,n13x2y2");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 1);
REQUIRE(lines[0] == expected);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == expected);
}
TEST_CASE("make_multiline from three lines, two with same start and end point", "[NoDB]")
TEST_CASE("create_multilinestring from three lines, two with same start and "
"end point",
"[NoDB]")
{
geom::linestring_t const expected{Coordinates{2, 2}, Coordinates{2, 1},
Coordinates{1, 1}, Coordinates{1, 2},
Coordinates{2, 2}};
geom::linestring_t const expected{{2, 2}, {2, 1}, {1, 1}, {1, 2}, {2, 2}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1,n13x2y2");
buffer.add_way("w21 Nn10x1y1,n12x1y2");
buffer.add_way("w22 Nn12x1y2,n13x2y2");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 1);
REQUIRE(lines[0] == expected);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == expected);
}
TEST_CASE("make_multiline from four lines forming two rings", "[NoDB]")
TEST_CASE("create_multilinestring from four lines forming two rings", "[NoDB]")
{
std::array<geom::linestring_t, 2> const expected{
geom::linestring_t{Coordinates{2, 1}, Coordinates{1, 1},
Coordinates{1, 2}},
geom::linestring_t{Coordinates{3, 4}, Coordinates{3, 3},
Coordinates{4, 3}}};
geom::linestring_t{{2, 1}, {1, 1}, {1, 2}},
geom::linestring_t{{3, 4}, {3, 3}, {4, 3}}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1");
@ -361,82 +343,76 @@ TEST_CASE("make_multiline from four lines forming two rings", "[NoDB]")
buffer.add_way("w22 Nn13x3y4,n14x3y3");
buffer.add_way("w23 Nn15x4y3,n14x3y3");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 2);
REQUIRE(lines[0] == expected[0]);
REQUIRE(lines[1] == expected[1]);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 2);
REQUIRE(ml[0] == expected[0]);
REQUIRE(ml[1] == expected[1]);
}
TEST_CASE("make_multiline from Y shape", "[NoDB]")
TEST_CASE("create_multilinestring from Y shape", "[NoDB]")
{
std::array<geom::linestring_t, 2> const expected{
geom::linestring_t{Coordinates{2, 1}, Coordinates{1, 1},
Coordinates{1, 2}},
geom::linestring_t{
Coordinates{1, 1},
Coordinates{2, 2},
}};
geom::linestring_t{{2, 1}, {1, 1}, {1, 2}}, geom::linestring_t{
{1, 1},
{2, 2},
}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x2y1");
buffer.add_way("w21 Nn10x1y1,n12x1y2");
buffer.add_way("w22 Nn10x1y1,n13x2y2");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 2);
REQUIRE(lines[0] == expected[0]);
REQUIRE(lines[1] == expected[1]);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 2);
REQUIRE(ml[0] == expected[0]);
REQUIRE(ml[1] == expected[1]);
}
TEST_CASE("make_multiline from P shape", "[NoDB]")
TEST_CASE("create_multilinestring from P shape", "[NoDB]")
{
geom::linestring_t const expected{Coordinates{1, 1}, Coordinates{1, 2},
Coordinates{1, 3}, Coordinates{2, 3},
Coordinates{1, 2}};
geom::linestring_t const expected{{1, 1}, {1, 2}, {1, 3}, {2, 3}, {1, 2}};
test_buffer_t buffer;
buffer.add_way("w20 Nn10x1y1,n11x1y2,n12x1y3");
buffer.add_way("w21 Nn12x1y3,n13x2y3,n11x1y2");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 1);
REQUIRE(lines[0] == expected);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 1);
REQUIRE(ml[0] == expected);
}
TEST_CASE("make_multiline from P shape with closed way", "[NoDB]")
TEST_CASE("create_multilinestring from P shape with closed way", "[NoDB]")
{
std::array<geom::linestring_t, 2> const expected{
geom::linestring_t{Coordinates{1, 2}, Coordinates{1, 1}},
geom::linestring_t{
Coordinates{1, 2},
Coordinates{1, 3},
Coordinates{2, 3},
Coordinates{1, 2},
}};
geom::linestring_t{{1, 2}, {1, 1}}, geom::linestring_t{
{1, 2},
{1, 3},
{2, 3},
{1, 2},
}};
test_buffer_t buffer;
buffer.add_way("w20 Nn11x1y2,n12x1y3,n13x2y3,n11x1y2");
buffer.add_way("w21 Nn11x1y2,n10x1y1");
std::vector<geom::linestring_t> lines;
auto const geom =
geom::line_merge(geom::create_multilinestring(buffer.buffer()));
auto const proj = reprojection::create_projection(4326);
geom::make_multiline(buffer.buffer(), 0.0, *proj, &lines);
REQUIRE(lines.size() == 2);
REQUIRE(lines[0] == expected[0]);
REQUIRE(lines[1] == expected[1]);
REQUIRE(geom.is_multilinestring());
auto const &ml = geom.get<geom::multilinestring_t>();
REQUIRE(ml.num_geometries() == 2);
REQUIRE(ml[0] == expected[0]);
REQUIRE(ml[1] == expected[1]);
}

View File

@ -12,80 +12,111 @@
#include "common-import.hpp"
#include "common-options.hpp"
#include "config.h"
#include "format.hpp"
#include <array>
static testing::db::import_t db;
static char const *const conf_file_3857 = "test_output_flex_area_3857.lua";
static char const *const conf_file_4326 = "test_output_flex_area_4326.lua";
static char const *const conf_file_mix = "test_output_flex_area_mix.lua";
static char const *const data_file = "test_output_pgsql_area.osm";
static char const *const data_file = "test_output_flex_area.osm";
TEST_CASE("area calculation in default projection")
// Area values for 4326, 3857, 25832 (ETRS89 / UTM zone 32N).
// First is for polygon, second for multipolygon.
static const double area[3][2] = {{0.01, 0.08},
{192987010.0, 1547130000.0},
{79600737.5375453234, 635499542.9545904398}};
static std::array<char const *, 3> const names = {"4326", "3857", "25832"};
// Indexes into area arrays above
enum projs
{
options_t const options = testing::opt_t().flex(conf_file_3857);
p4326 = 0,
p3857 = 1,
p25832 = 2
};
void check(int p1, int p2)
{
std::string conf_file{
"test_output_flex_area_{}_{}.lua"_format(names[p1], names[p2])};
options_t const options = testing::opt_t().flex(conf_file.c_str());
REQUIRE_NOTHROW(db.run_file(options, data_file));
auto conn = db.db().connect();
REQUIRE(2 == conn.get_count("osm2pgsql_test_polygon"));
// polygon
conn.assert_double(
1.23927e+10,
area[p1][0],
"SELECT ST_Area(geom) FROM osm2pgsql_test_polygon WHERE name='poly'");
conn.assert_double(
area[p2][0],
"SELECT area FROM osm2pgsql_test_polygon WHERE name='poly'");
conn.assert_double(area[p4326][0],
"SELECT ST_Area(ST_Transform(geom, 4326)) "
"FROM osm2pgsql_test_polygon WHERE name='poly'");
// multipolygon
conn.assert_double(
1.23927e+10,
"SELECT ST_Area(geom) FROM osm2pgsql_test_polygon WHERE name='poly'");
conn.assert_double(1.0, "SELECT ST_Area(ST_Transform(geom, 4326)) "
"FROM osm2pgsql_test_polygon WHERE name='poly'");
area[p1][1],
"SELECT ST_Area(geom) FROM osm2pgsql_test_polygon WHERE name='multi'");
conn.assert_double(
9.91828e+10,
area[p2][1],
"SELECT area FROM osm2pgsql_test_polygon WHERE name='multi'");
conn.assert_double(
9.91828e+10,
"SELECT ST_Area(geom) FROM osm2pgsql_test_polygon WHERE name='multi'");
conn.assert_double(8.0, "SELECT ST_Area(ST_Transform(geom, 4326)) "
"FROM osm2pgsql_test_polygon WHERE name='multi'");
conn.assert_double(area[p4326][1],
"SELECT ST_Area(ST_Transform(geom, 4326)) "
"FROM osm2pgsql_test_polygon WHERE name='multi'");
}
TEST_CASE("area calculation in latlon projection")
TEST_CASE("area and area calculation in latlon (4326) projection")
{
options_t const options = testing::opt_t().flex(conf_file_4326);
REQUIRE_NOTHROW(db.run_file(options, data_file));
auto conn = db.db().connect();
REQUIRE(2 == conn.get_count("osm2pgsql_test_polygon"));
conn.assert_double(
1.0, "SELECT area FROM osm2pgsql_test_polygon WHERE name='poly'");
conn.assert_double(
1.0,
"SELECT ST_Area(geom) FROM osm2pgsql_test_polygon WHERE name='poly'");
conn.assert_double(
8.0, "SELECT area FROM osm2pgsql_test_polygon WHERE name='multi'");
conn.assert_double(
8.0,
"SELECT ST_Area(geom) FROM osm2pgsql_test_polygon WHERE name='multi'");
check(p4326, p4326);
}
TEST_CASE("area calculation in latlon projection with way area reprojection")
TEST_CASE("area in mercator with area calculation in latlon")
{
options_t options = testing::opt_t().flex(conf_file_mix);
REQUIRE_NOTHROW(db.run_file(options, data_file));
auto conn = db.db().connect();
REQUIRE(2 == conn.get_count("osm2pgsql_test_polygon"));
conn.assert_double(
1.23927e+10,
"SELECT area FROM osm2pgsql_test_polygon WHERE name='poly'");
conn.assert_double(
1.0,
"SELECT ST_Area(geom) FROM osm2pgsql_test_polygon WHERE name='poly'");
conn.assert_double(
9.91828e+10,
"SELECT area FROM osm2pgsql_test_polygon WHERE name='multi'");
conn.assert_double(
8.0,
"SELECT ST_Area(geom) FROM osm2pgsql_test_polygon WHERE name='multi'");
check(p4326, p3857);
}
#ifdef HAVE_GENERIC_PROJ
TEST_CASE("area in latlon with area calculation in 25832 projection")
{
check(p4326, p25832);
}
#endif
TEST_CASE("area in latlon with area calculation in mercator projection")
{
check(p3857, p4326);
}
TEST_CASE("area and area calculation in default (3857) projection")
{
check(p3857, p3857);
}
#ifdef HAVE_GENERIC_PROJ
TEST_CASE("area in mercator with area calculation in 25832 projection")
{
check(p3857, p25832);
}
TEST_CASE("area in 25832 with area calculation in latlon projection")
{
check(p25832, p4326);
}
TEST_CASE("area in 25832 with area calculation in mercator projection")
{
check(p25832, p3857);
}
TEST_CASE("area and area calculation in 25832 projection")
{
check(p25832, p25832);
}
#endif

View File

@ -21,13 +21,13 @@ TEST_CASE("projection 4326", "[NoDB]")
REQUIRE(reprojection->target_srs() == srs);
REQUIRE(reprojection->target_latlon());
auto const c = reprojection->reproject(loc);
REQUIRE(c.x == Approx(10.0));
REQUIRE(c.y == Approx(53.0));
auto const c = reprojection->reproject(geom::point_t{loc});
REQUIRE(c.x() == Approx(10.0));
REQUIRE(c.y() == Approx(53.0));
auto const ct = reprojection->target_to_tile(c);
REQUIRE(ct.x == Approx(1113194.91));
REQUIRE(ct.y == Approx(6982997.92));
REQUIRE(ct.x() == Approx(1113194.91));
REQUIRE(ct.y() == Approx(6982997.92));
}
TEST_CASE("projection 3857", "[NoDB]")
@ -39,13 +39,13 @@ TEST_CASE("projection 3857", "[NoDB]")
REQUIRE(reprojection->target_srs() == srs);
REQUIRE_FALSE(reprojection->target_latlon());
auto const c = reprojection->reproject(loc);
REQUIRE(c.x == Approx(1113194.91));
REQUIRE(c.y == Approx(6982997.92));
auto const c = reprojection->reproject(geom::point_t{loc});
REQUIRE(c.x() == Approx(1113194.91));
REQUIRE(c.y() == Approx(6982997.92));
auto const ct = reprojection->target_to_tile(c);
REQUIRE(ct.x == Approx(1113194.91));
REQUIRE(ct.y == Approx(6982997.92));
REQUIRE(ct.x() == Approx(1113194.91));
REQUIRE(ct.y() == Approx(6982997.92));
}
#ifdef HAVE_GENERIC_PROJ
@ -58,12 +58,12 @@ TEST_CASE("projection 5651", "[NoDB]")
REQUIRE(reprojection->target_srs() == srs);
REQUIRE_FALSE(reprojection->target_latlon());
auto const c = reprojection->reproject(loc);
REQUIRE(c.x == Approx(31969448.78));
REQUIRE(c.y == Approx(5895222.39));
auto const c = reprojection->reproject(geom::point_t{loc});
REQUIRE(c.x() == Approx(31969448.78));
REQUIRE(c.y() == Approx(5895222.39));
auto const ct = reprojection->target_to_tile(c);
REQUIRE(ct.x == Approx(1113194.91));
REQUIRE(ct.y == Approx(6982997.92));
REQUIRE(ct.x() == Approx(1113194.91));
REQUIRE(ct.y() == Approx(6982997.92));
}
#endif