Skip to content

Commit e29bc9c

Browse files
committed
Work-in-progress: Add geometry functions in Lua
This is intended to show how geometry processing in Lua could work. Only some example functions are implemented and not for all geometry types, error detection is limited etc. Geometry creation functions: * as_point() (for nodes) * as_linestring() (for ways) * as_polygon() (for ways) * as_multipolygon() (for relations) Geometry functions: * area() (for polygons) * centroid() (for polygons) * simplify() (for linestrings) See the file flex-config/custom-geometries.lua for an example that shows how to use these.
1 parent 70ea5d3 commit e29bc9c

File tree

11 files changed

+565
-12
lines changed

11 files changed

+565
-12
lines changed

.github/actions/win-install/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ runs:
44
using: composite
55
steps:
66
- name: Install packages
7-
run: vcpkg install bzip2:x64-windows expat:x64-windows zlib:x64-windows proj4:x64-windows boost-system:x64-windows boost-filesystem:x64-windows boost-property-tree:x64-windows lua:x64-windows libpq:x64-windows
7+
run: vcpkg install bzip2:x64-windows expat:x64-windows zlib:x64-windows proj4:x64-windows boost-geometry:x64-windows boost-system:x64-windows boost-filesystem:x64-windows boost-property-tree:x64-windows lua:x64-windows libpq:x64-windows
88
shell: bash
99
- name: Install psycopg2
1010
run: python -m pip install psycopg2

flex-config/custom-geometries.lua

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
-- This config example file is released into the Public Domain.
2+
3+
-- This is Lua config showing the handling of custom geometries.
4+
--
5+
-- This is "work in progress", changes are possible.
6+
--
7+
-- NOTE that all the geometry types have a leading "c" for "custom", this is
8+
-- different than the geometry types before.
9+
10+
local tables = {}
11+
12+
-- This table will get all nodes and areas with an "addr:street" tag, for
13+
-- areas we'll calculate the centroid.
14+
tables.addr = osm2pgsql.define_table({
15+
name = 'addr',
16+
ids = { type = 'any', id_column = 'osm_id', type_column = 'osm_type' },
17+
columns = {
18+
{ column = 'tags', type = 'jsonb' },
19+
{ column = 'geom', type = 'cpoint', projection = 4326 },
20+
}
21+
})
22+
23+
tables.ways = osm2pgsql.define_way_table('ways', {
24+
{ column = 'tags', type = 'jsonb' },
25+
{ column = 'geom', type = 'clinestring', projection = 4326 },
26+
{ column = 'poly', type = 'cpolygon', projection = 4326 },
27+
{ column = 'geom3857', type = 'clinestring', projection = 3857 },
28+
})
29+
30+
tables.major_roads = osm2pgsql.define_way_table('major_roads', {
31+
{ column = 'tags', type = 'jsonb' },
32+
{ column = 'geom', type = 'clinestring' },
33+
{ column = 'sgeom', type = 'clinestring' }
34+
})
35+
36+
tables.polygons = osm2pgsql.define_area_table('polygons', {
37+
{ column = 'tags', type = 'jsonb' },
38+
{ column = 'geom', type = 'cgeometry' },
39+
{ column = 'area', type = 'real' }
40+
})
41+
42+
-- Helper function to remove some of the tags we usually are not interested in.
43+
-- Returns true if there are no tags left.
44+
function clean_tags(tags)
45+
tags.odbl = nil
46+
tags.created_by = nil
47+
tags.source = nil
48+
tags['source:ref'] = nil
49+
50+
return next(tags) == nil
51+
end
52+
53+
-- Helper function that looks at the tags and decides if this is possibly
54+
-- an area.
55+
function has_area_tags(tags)
56+
if tags.area == 'yes' then
57+
return true
58+
end
59+
if tags.area == 'no' then
60+
return false
61+
end
62+
63+
return tags.aeroway
64+
or tags.amenity
65+
or tags.building
66+
or tags.harbour
67+
or tags.historic
68+
or tags.landuse
69+
or tags.leisure
70+
or tags.man_made
71+
or tags.military
72+
or tags.natural
73+
or tags.office
74+
or tags.place
75+
or tags.power
76+
or tags.public_transport
77+
or tags.shop
78+
or tags.sport
79+
or tags.tourism
80+
or tags.water
81+
or tags.waterway
82+
or tags.wetland
83+
or tags['abandoned:aeroway']
84+
or tags['abandoned:amenity']
85+
or tags['abandoned:building']
86+
or tags['abandoned:landuse']
87+
or tags['abandoned:power']
88+
or tags['area:highway']
89+
end
90+
91+
function osm2pgsql.process_node(object)
92+
if clean_tags(object.tags) then
93+
return
94+
end
95+
96+
if object.tags['addr:street'] then
97+
tables.addr:add_row({
98+
tags = object.tags,
99+
geom = object:as_point()
100+
})
101+
end
102+
end
103+
104+
function osm2pgsql.process_way(object)
105+
if clean_tags(object.tags) then
106+
return
107+
end
108+
109+
if object.is_closed and object.tags['addr:street'] then
110+
tables.addr:add_row({
111+
tags = object.tags,
112+
geom = object:as_polygon():centroid()
113+
})
114+
end
115+
116+
if object.tags.highway == 'motorway' or
117+
object.tags.highway == 'trunk' or
118+
object.tags.highway == 'primary' then
119+
tables.major_roads:add_row({
120+
tags = object.tags,
121+
geom = object:as_linestring(),
122+
sgeom = object:as_linestring():simplify(100),
123+
})
124+
end
125+
126+
-- A closed way that also has the right tags for an area is a polygon.
127+
if object.is_closed and has_area_tags(object.tags) then
128+
local a = object:as_polygon():area()
129+
if a < 0.0000001 then
130+
tables.polygons:add_row({
131+
tags = object.tags,
132+
geom = object:as_polygon(),
133+
area = object:as_polygon():area()
134+
})
135+
end
136+
else
137+
tables.ways:add_row({
138+
tags = object.tags,
139+
geom = object:as_linestring(),
140+
poly = object:as_polygon(),
141+
geom3857 = object:as_linestring()
142+
})
143+
end
144+
end
145+
146+
function osm2pgsql.process_relation(object)
147+
if clean_tags(object.tags) then
148+
return
149+
end
150+
151+
local type = object:grab_tag('type')
152+
153+
if type == 'multipolygon' then
154+
local g = object:as_multipolygon()
155+
local a = g:area()
156+
if a < 0.0000001 then
157+
tables.polygons:add_row({
158+
tags = object.tags,
159+
geom = g,
160+
area = g:area()
161+
})
162+
end
163+
end
164+
end
165+

src/flex-table-column.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct column_type_lookup
2323
table_column_type type;
2424
};
2525

26-
static std::array<column_type_lookup, 25> const column_types = {
26+
static std::array<column_type_lookup, 32> const column_types = {
2727
{{"text", table_column_type::text},
2828
{"boolean", table_column_type::boolean},
2929
{"bool", table_column_type::boolean},
@@ -39,6 +39,13 @@ static std::array<column_type_lookup, 25> const column_types = {
3939
{"json", table_column_type::json},
4040
{"jsonb", table_column_type::jsonb},
4141
{"direction", table_column_type::direction},
42+
{"cgeometry", table_column_type::custom_geometry},
43+
{"cpoint", table_column_type::custom_point},
44+
{"clinestring", table_column_type::custom_linestring},
45+
{"cpolygon", table_column_type::custom_polygon},
46+
{"cmultipoint", table_column_type::custom_multipoint},
47+
{"cmultilinestring", table_column_type::custom_multilinestring},
48+
{"cmultipolygon", table_column_type::custom_multipolygon},
4249
{"geometry", table_column_type::geometry},
4350
{"point", table_column_type::point},
4451
{"linestring", table_column_type::linestring},
@@ -140,6 +147,20 @@ std::string flex_table_column_t::sql_type_name() const
140147
return "jsonb";
141148
case table_column_type::direction:
142149
return "int2";
150+
case table_column_type::custom_geometry:
151+
return "Geometry(GEOMETRY, {})"_format(m_srid);
152+
case table_column_type::custom_point:
153+
return "Geometry(POINT, {})"_format(m_srid);
154+
case table_column_type::custom_linestring:
155+
return "Geometry(LINESTRING, {})"_format(m_srid);
156+
case table_column_type::custom_polygon:
157+
return "Geometry(POLYGON, {})"_format(m_srid);
158+
case table_column_type::custom_multipoint:
159+
return "Geometry(MULTIPOINT, {})"_format(m_srid);
160+
case table_column_type::custom_multilinestring:
161+
return "Geometry(MULTILINESTRING, {})"_format(m_srid);
162+
case table_column_type::custom_multipolygon:
163+
return "Geometry(MULTIPOLYGON, {})"_format(m_srid);
143164
case table_column_type::geometry:
144165
return "Geometry(GEOMETRY, {})"_format(m_srid);
145166
case table_column_type::point:

src/flex-table-column.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ enum class table_column_type : uint8_t
3232

3333
direction,
3434

35+
custom_geometry,
36+
custom_point,
37+
custom_linestring,
38+
custom_polygon,
39+
custom_multipoint,
40+
custom_multilinestring,
41+
custom_multipolygon,
42+
3543
geometry,
3644
point,
3745
linestring,
@@ -84,6 +92,12 @@ class flex_table_column_t
8492
(m_type <= table_column_type::multipolygon);
8593
}
8694

95+
bool is_custom_geometry_column() const noexcept
96+
{
97+
return (m_type >= table_column_type::custom_geometry) &&
98+
(m_type <= table_column_type::custom_multipolygon);
99+
}
100+
87101
/**
88102
* Do we need an ST_IsValid() check in the database for this geometry
89103
* column? If the SRID is 4326 the geometry validity is already assured

src/geom-from-osm.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717

1818
namespace geom {
1919

20+
void create_point(geometry_t *geom, osmium::Node const &node)
21+
{
22+
auto &point = geom->set<point_t>();
23+
point.set_x(node.location().lon());
24+
point.set_y(node.location().lat());
25+
}
26+
2027
geometry_t create_point(osmium::Node const &node)
2128
{
2229
return geometry_t{point_t{node.location()}};
@@ -36,18 +43,22 @@ static void fill_point_list(point_list_t *list,
3643
}
3744
}
3845

39-
geometry_t create_linestring(osmium::Way const &way)
46+
void create_linestring(geometry_t *geom, osmium::Way const &way)
4047
{
41-
geometry_t geom{linestring_t{}};
42-
auto &line = geom.get<linestring_t>();
48+
auto &line = geom->set<linestring_t>();
4349

4450
fill_point_list(&line, way.nodes());
4551

4652
// Return nullgeom_t if the line geometry is invalid
4753
if (line.size() <= 1U) {
48-
geom.reset();
54+
geom->reset();
4955
}
56+
}
5057

58+
geometry_t create_linestring(osmium::Way const &way)
59+
{
60+
geometry_t geom{};
61+
create_linestring(&geom, way);
5162
return geom;
5263
}
5364

src/geom-from-osm.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ namespace geom {
3131
*/
3232
geometry_t create_point(osmium::Node const &node);
3333

34+
void create_point(geometry_t *geom, osmium::Node const &node);
35+
3436
/**
3537
* Create a linestring geometry from a way. Nodes without location are ignored.
3638
* Consecutive nodes with the same location will only end up once in the
@@ -44,6 +46,8 @@ geometry_t create_point(osmium::Node const &node);
4446
*/
4547
geometry_t create_linestring(osmium::Way const &way);
4648

49+
void create_linestring(geometry_t *geom, osmium::Way const &way);
50+
4751
/**
4852
* Create a polygon geometry from a way.
4953
*

src/geom-functions.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* For a full list of authors see the git log.
88
*/
99

10+
#include "geom-boost-adaptor.hpp"
1011
#include "geom-functions.hpp"
1112

1213
#include <algorithm>
@@ -519,4 +520,48 @@ geometry_t line_merge(geometry_t geom)
519520
return output;
520521
}
521522

523+
namespace {
524+
525+
class centroid_helper
526+
{
527+
public:
528+
centroid_helper(point_t *output) : m_output(output) {}
529+
530+
void operator()(geom::nullgeom_t const & /*geom*/) const {}
531+
532+
// XXX not implemented yet
533+
void operator()(geom::collection_t const & /*geom*/) const {}
534+
535+
template <typename GEOM>
536+
void operator()(GEOM const &input) const
537+
{
538+
boost::geometry::centroid(input, *m_output);
539+
}
540+
541+
private:
542+
point_t *m_output;
543+
};
544+
545+
} // anonymous namespace
546+
547+
geometry_t centroid(geometry_t const &geom)
548+
{
549+
geom::geometry_t center{point_t{}, geom.srid()};
550+
geom.visit(centroid_helper{&center.get<point_t>()});
551+
return center;
552+
}
553+
554+
geometry_t simplify(geometry_t const &geom, double tolerance)
555+
{
556+
geom::geometry_t simplified{linestring_t{}, geom.srid()};
557+
if (!geom.is_linestring()) {
558+
simplified.reset();
559+
return simplified;
560+
}
561+
562+
boost::geometry::simplify(geom.get<linestring_t>(), simplified.get<linestring_t>(), tolerance);
563+
564+
return simplified;
565+
}
566+
522567
} // namespace geom

src/geom-functions.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ std::vector<geometry_t> split_multi(geometry_t geom, bool split_multi = true);
8686
*/
8787
geometry_t line_merge(geometry_t geom);
8888

89+
geometry_t centroid(geometry_t const &geom);
90+
91+
geometry_t simplify(geometry_t const &geom, double tolerance);
92+
8993
} // namespace geom
9094

9195
#endif // OSM2PGSQL_GEOM_FUNCTIONS_HPP

src/init.lua

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ local inner_metatable = {
179179
}
180180

181181
object_metatable = {
182-
__index = {
182+
__index = {
183183
grab_tag = function(data, tag)
184184
if not tag then
185185
error("Missing tag key", 2)
@@ -193,3 +193,7 @@ object_metatable = {
193193

194194
setmetatable(object_metatable.__index, inner_metatable)
195195

196+
-- This will be the metatable for the geometry objects
197+
geom_metatable = {
198+
__index = {}
199+
}

0 commit comments

Comments
 (0)