diff --git a/src/geom-from-osm.cpp b/src/geom-from-osm.cpp index c1f0f10d8..e7002d29a 100644 --- a/src/geom-from-osm.cpp +++ b/src/geom-from-osm.cpp @@ -19,11 +19,18 @@ namespace geom { +void create_point(geometry_t *geom, osmium::Location const &location) +{ + if (location.valid()) { + auto &point = geom->set(); + point.set_x(location.lon()); + point.set_y(location.lat()); + } +} + void create_point(geometry_t *geom, osmium::Node const &node) { - auto &point = geom->set(); - point.set_x(node.location().lon()); - point.set_y(node.location().lat()); + create_point(geom, node.location()); } geometry_t create_point(osmium::Node const &node) diff --git a/src/geom-from-osm.hpp b/src/geom-from-osm.hpp index 4db2c8654..0d295cebf 100644 --- a/src/geom-from-osm.hpp +++ b/src/geom-from-osm.hpp @@ -27,6 +27,15 @@ namespace geom { +/** + * Create a point geometry from a location. If the location is not valid, + * the output will not be changed. + * + * \param geom Pointer to an existing geometry which will be used as output. + * \param location The input location. + */ +void create_point(geometry_t *geom, osmium::Location const &location); + /** * Create a point geometry from a node. * diff --git a/src/output-flex.cpp b/src/output-flex.cpp index 5c27e901d..3b554e25e 100644 --- a/src/output-flex.cpp +++ b/src/output-flex.cpp @@ -454,11 +454,46 @@ int output_flex_t::app_get_bbox() int output_flex_t::app_as_point() { - check_context_and_state("as_point", "node", - m_calling_context != calling_context::process_node); + check_for_object(lua_state(), "as_point"); + + if (m_calling_context == calling_context::process_node) { + if (lua_gettop(lua_state()) > 1) { + throw fmt_error("No parameter(s) needed for as_point()."); + } + auto *geom = create_lua_geometry_object(lua_state()); + geom::create_point(geom, *m_context_node); + return 1; + } + + if (m_calling_context != calling_context::process_way) { + throw fmt_error( + "The function as_point() can only be called (directly " + "or indirectly) from the process_[untagged]_node/way() functions."); + } + + if (lua_gettop(lua_state()) > 2) { + throw fmt_error("Too many arguments for function as_point()"); + } + + m_way_cache.add_nodes(middle()); + auto const &nodes = m_way_cache.get().nodes(); + + int64_t n = 1; // get first node by default + if (lua_gettop(lua_state()) > 1) { + if (lua_type(lua_state(), 2) != LUA_TNUMBER) { + throw std::runtime_error{ + "Argument #1 to 'as_point()' must be an integer."}; + } + n = lua_tointeger(lua_state(), 2); + if (n < 0) { // negative index values count from the back + n += static_cast(nodes.size()) + 1; + } + } auto *geom = create_lua_geometry_object(lua_state()); - geom::create_point(geom, *m_context_node); + if (n > 0 && static_cast(n) <= nodes.size()) { + geom::create_point(geom, nodes[n - 1].location()); + } // fall through returning null geometry if 0 or too large or small return 1; } diff --git a/tests/bdd/flex/geometry-point.feature b/tests/bdd/flex/geometry-point.feature new file mode 100644 index 000000000..493c246b6 --- /dev/null +++ b/tests/bdd/flex/geometry-point.feature @@ -0,0 +1,95 @@ +Feature: Creating point features from way + + Scenario: + Given the grid + | 1 | 2 | | + | 4 | | 3 | + | | 5 | | + And the OSM data + """ + w20 Thighway=motorway Nn1,n2,n3 + w21 Thighway=motorway Nn4,n5 + """ + And the lua style + """ + local points = osm2pgsql.define_way_table('osm2pgsql_test_points', { + { column = 'n', type = 'int' }, + { column = 'geom', type = 'point', projection = 4326, not_null = false }, + }) + + function osm2pgsql.process_way(object) + if object.tags.highway == 'motorway' then + points:insert({ n = nil, geom = object:as_point() }) + points:insert({ n = 0, geom = object:as_point(0) }) + points:insert({ n = 1, geom = object:as_point(1) }) + points:insert({ n = 2, geom = object:as_point(2) }) + points:insert({ n = 3, geom = object:as_point(3) }) + points:insert({ n = 4, geom = object:as_point(4) }) + points:insert({ n = -1, geom = object:as_point(-1) }) + points:insert({ n = -2, geom = object:as_point(-2) }) + points:insert({ n = -3, geom = object:as_point(-3) }) + end + end + """ + When running osm2pgsql flex + + Then table osm2pgsql_test_points contains exactly + | way_id | n | ST_AsText(geom) | + | 20 | NULL | 1 | + | 20 | 0 | NULL | + | 20 | 1 | 1 | + | 20 | 2 | 2 | + | 20 | 3 | 3 | + | 20 | 4 | NULL | + | 20 | -1 | 3 | + | 20 | -2 | 2 | + | 20 | -3 | 1 | + | 21 | NULL | 4 | + | 21 | 0 | NULL | + | 21 | 1 | 4 | + | 21 | 2 | 5 | + | 21 | 3 | NULL | + | 21 | 4 | NULL | + | 21 | -1 | 5 | + | 21 | -2 | 4 | + | 21 | -3 | NULL | + + Scenario: + Given the grid + | 1 | 2 | + And the OSM data + """ + w20 Thighway=motorway Nn1,n2 + """ + And the lua style + """ + function osm2pgsql.process_way(object) + local geom = object:as_point('foo') + end + """ + Then running osm2pgsql flex fails + + And the error output contains + """ + Argument #1 to 'as_point()' must be an integer. + """ + + Scenario: + Given the grid + | 1 | 2 | + And the OSM data + """ + w20 Thighway=motorway Nn1,n2 + """ + And the lua style + """ + function osm2pgsql.process_way(object) + local geom = object:as_point(1, 'foo') + end + """ + Then running osm2pgsql flex fails + + And the error output contains + """ + Too many arguments for function as_point() + """ diff --git a/tests/test-geom-linestrings.cpp b/tests/test-geom-linestrings.cpp index 33420de6f..b50de68ef 100644 --- a/tests/test-geom-linestrings.cpp +++ b/tests/test-geom-linestrings.cpp @@ -115,6 +115,50 @@ TEST_CASE("create_linestring from invalid OSM data", "[NoDB]") REQUIRE(geom.is_null()); } +TEST_CASE("create_point from OSM way data", "[NoDB]") +{ + test_buffer_t buffer; + buffer.add_way("w20 Nn1x1y1,n2x2y2"); + + auto const &nodes = buffer.buffer().get(0).nodes(); + geom::geometry_t geom; + geom::create_point(&geom, nodes[0].location()); + + REQUIRE(geom.is_point()); + REQUIRE(geometry_type(geom) == "POINT"); + REQUIRE(dimension(geom) == 0); + REQUIRE(num_geometries(geom) == 1); + REQUIRE(geom.get() == geom::point_t{1, 1}); +} + +TEST_CASE("create_point from OSM data without locations", "[NoDB]") +{ + test_buffer_t buffer; + buffer.add_way("w20 Nn1,n2"); + + auto const &nodes = buffer.buffer().get(0).nodes(); + geom::geometry_t geom; + geom::create_point(&geom, nodes[0].location()); + + REQUIRE(geom.is_null()); +} + +TEST_CASE("create_point from way with single node", "[NoDB]") +{ + test_buffer_t buffer; + buffer.add_way("w20 Nn1x1y1"); + + auto const &nodes = buffer.buffer().get(0).nodes(); + geom::geometry_t geom; + geom::create_point(&geom, nodes[0].location()); + + REQUIRE(geom.is_point()); + REQUIRE(geometry_type(geom) == "POINT"); + REQUIRE(dimension(geom) == 0); + REQUIRE(num_geometries(geom) == 1); + REQUIRE(geom.get() == geom::point_t{1, 1}); +} + TEST_CASE("geom::segmentize w/o split", "[NoDB]") { geom::linestring_t const expected{{0, 0}, {1, 2}, {2, 2}}; diff --git a/tests/test-geom-points.cpp b/tests/test-geom-points.cpp index 13b07e8d4..94e5e0418 100644 --- a/tests/test-geom-points.cpp +++ b/tests/test-geom-points.cpp @@ -42,6 +42,20 @@ TEST_CASE("geom::point_t from location", "[NoDB]") REQUIRE(p == geom::point_t{3.141, 2.718}); } +TEST_CASE("geom::point_t from location with create_point", "[NoDB]") +{ + osmium::Location const location{1.1, 2.2}; + + geom::geometry_t geom; + geom::create_point(&geom, location); + REQUIRE(geom.is_point()); + + auto const &p = geom.get(); + REQUIRE(p.x() == Approx(1.1)); + REQUIRE(p.y() == Approx(2.2)); + REQUIRE(p == geom::point_t{1.1, 2.2}); +} + TEST_CASE("create_point from OSM data", "[NoDB]") { test_buffer_t buffer;