Skip to content

Commit 9ce32a8

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_multilinestring() (for relations) * as_multipolygon() (for relations) Geometry functions: * geometrytype() * srid() * transform(target_srid) * area() (for polygons) * centroid() (for polygons) * simplify(tolerance) (for linestrings) * split_multi() (for multi geometries) * #geom/__len() returns the number of geometries (0 for null geom, 1 for point/linestring/polygon, n for multi* and geometrycollection geoms) See the file flex-config/new-insert-syntax.lua for an example that shows how to use these.
1 parent 22e738b commit 9ce32a8

File tree

12 files changed

+956
-71
lines changed

12 files changed

+956
-71
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/new-insert-syntax.lua

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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 the insert() function is used instead of the old add_row()!
8+
9+
local tables = {}
10+
11+
-- This table will get all nodes and areas with an "addr:street" tag, for
12+
-- areas we'll calculate the centroid.
13+
tables.addr = osm2pgsql.define_table({
14+
name = 'addr',
15+
ids = { type = 'any', id_column = 'osm_id', type_column = 'osm_type' },
16+
columns = {
17+
{ column = 'tags', type = 'jsonb' },
18+
{ column = 'geom', type = 'point', projection = 4326 },
19+
}
20+
})
21+
22+
tables.ways = osm2pgsql.define_way_table('ways', {
23+
{ column = 'tags', type = 'jsonb' },
24+
{ column = 'geom', type = 'linestring', projection = 4326 },
25+
{ column = 'poly', type = 'polygon', projection = 4326 },
26+
{ column = 'geom3857', type = 'linestring', projection = 3857 },
27+
{ column = 'geomautoproj', type = 'linestring', projection = 3857 },
28+
})
29+
30+
tables.major_roads = osm2pgsql.define_way_table('major_roads', {
31+
{ column = 'tags', type = 'jsonb' },
32+
{ column = 'geom', type = 'linestring' },
33+
{ column = 'sgeom', type = 'linestring' }
34+
})
35+
36+
tables.polygons = osm2pgsql.define_area_table('polygons', {
37+
{ column = 'tags', type = 'jsonb' },
38+
{ column = 'geom', type = 'geometry' },
39+
{ column = 'area4326', type = 'real' },
40+
{ column = 'area3857', type = 'real' }
41+
})
42+
43+
tables.routes = osm2pgsql.define_relation_table('routes', {
44+
{ column = 'tags', type = 'jsonb' },
45+
{ column = 'geom', type = 'multilinestring', projection = 4326 },
46+
})
47+
48+
tables.route_parts = osm2pgsql.define_relation_table('route_parts', {
49+
{ column = 'tags', type = 'jsonb' },
50+
{ column = 'geom', type = 'linestring', projection = 4326 },
51+
})
52+
53+
-- Helper function to remove some of the tags we usually are not interested in.
54+
-- Returns true if there are no tags left.
55+
function clean_tags(tags)
56+
tags.odbl = nil
57+
tags.created_by = nil
58+
tags.source = nil
59+
tags['source:ref'] = nil
60+
61+
return next(tags) == nil
62+
end
63+
64+
-- Helper function that looks at the tags and decides if this is possibly
65+
-- an area.
66+
function has_area_tags(tags)
67+
if tags.area == 'yes' then
68+
return true
69+
end
70+
if tags.area == 'no' then
71+
return false
72+
end
73+
74+
return tags.aeroway
75+
or tags.amenity
76+
or tags.building
77+
or tags.harbour
78+
or tags.historic
79+
or tags.landuse
80+
or tags.leisure
81+
or tags.man_made
82+
or tags.military
83+
or tags.natural
84+
or tags.office
85+
or tags.place
86+
or tags.power
87+
or tags.public_transport
88+
or tags.shop
89+
or tags.sport
90+
or tags.tourism
91+
or tags.water
92+
or tags.waterway
93+
or tags.wetland
94+
or tags['abandoned:aeroway']
95+
or tags['abandoned:amenity']
96+
or tags['abandoned:building']
97+
or tags['abandoned:landuse']
98+
or tags['abandoned:power']
99+
or tags['area:highway']
100+
end
101+
102+
function osm2pgsql.process_node(object)
103+
if clean_tags(object.tags) then
104+
return
105+
end
106+
107+
if object.tags['addr:street'] then
108+
tables.addr:insert({
109+
tags = object.tags,
110+
geom = object:as_point()
111+
})
112+
end
113+
end
114+
115+
function osm2pgsql.process_way(object)
116+
if clean_tags(object.tags) then
117+
return
118+
end
119+
120+
if object.is_closed and object.tags['addr:street'] then
121+
tables.addr:insert({
122+
tags = object.tags,
123+
geom = object:as_polygon():centroid()
124+
})
125+
end
126+
127+
if object.tags.highway == 'motorway' or
128+
object.tags.highway == 'trunk' or
129+
object.tags.highway == 'primary' then
130+
tables.major_roads:insert({
131+
tags = object.tags,
132+
geom = object:as_linestring(),
133+
sgeom = object:as_linestring():simplify(100),
134+
})
135+
end
136+
137+
-- A closed way that also has the right tags for an area is a polygon.
138+
if object.is_closed and has_area_tags(object.tags) then
139+
local g = object:as_polygon()
140+
local a = g:area()
141+
if a < 0.0000001 then
142+
tables.polygons:insert({
143+
tags = object.tags,
144+
geom = g,
145+
area4326 = a,
146+
area3857 = g:transform(3857):area()
147+
})
148+
end
149+
else
150+
tables.ways:insert({
151+
tags = object.tags,
152+
geom = object:as_linestring(),
153+
poly = object:as_polygon(),
154+
geom3857 = object:as_linestring():transform(3857), -- project geometry into target srid 3857
155+
geomautoproj = object:as_linestring() -- automatically projected into projection of target column
156+
})
157+
end
158+
end
159+
160+
function osm2pgsql.process_relation(object)
161+
if clean_tags(object.tags) then
162+
return
163+
end
164+
165+
local relation_type = object:grab_tag('type')
166+
167+
if relation_type == 'multipolygon' then
168+
local g = object:as_multipolygon()
169+
local a = g:area()
170+
if a < 0.0000001 then
171+
tables.polygons:insert({
172+
tags = object.tags,
173+
geom = g,
174+
area4326 = a,
175+
area3857 = g:transform(3857):area()
176+
})
177+
end
178+
return
179+
end
180+
181+
if relation_type == 'route' then
182+
local route_geom = object:as_multilinestring()
183+
if #route_geom > 0 then -- check that this is not a null geometry
184+
-- print(object.id, tostring(route_geom), route_geom:srid(), #route_geom)
185+
tables.routes:insert({
186+
tags = object.tags,
187+
geom = route_geom
188+
})
189+
for n, line in pairs(route_geom:split_multi()) do
190+
tables.route_parts:insert({
191+
tags = object.tags,
192+
geom = line
193+
})
194+
end
195+
end
196+
end
197+
end
198+

src/flex-table.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ flex_table_column_t &flex_table_t::add_column(std::string const &name,
7777
auto &column = m_columns.back();
7878

7979
if (column.is_geometry_column()) {
80+
if (m_geom_column != std::numeric_limits<std::size_t>::max()) {
81+
m_has_multiple_geom_columns = true;
82+
}
8083
m_geom_column = m_columns.size() - 1;
8184
column.set_not_null();
8285
}

src/flex-table.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ class flex_table_t
191191
std::string full_name() const;
192192
std::string full_tmp_name() const;
193193

194+
bool has_multiple_geom_columns() const noexcept
195+
{
196+
return m_has_multiple_geom_columns;
197+
}
198+
194199
private:
195200
/// The name of the table
196201
std::string m_name;
@@ -225,6 +230,9 @@ class flex_table_t
225230
/// Cluster the table by geometry.
226231
bool m_cluster_by_geom = true;
227232

233+
/// Does this table have more than one geometry column?
234+
bool m_has_multiple_geom_columns = false;
235+
228236
}; // class flex_table_t
229237

230238
class table_connection_t

src/geom-from-osm.cpp

Lines changed: 48 additions & 22 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,29 +43,33 @@ 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

54-
geometry_t create_polygon(osmium::Way const &way)
65+
void create_polygon(geometry_t *geom, osmium::Way const &way)
5566
{
56-
geometry_t geom{polygon_t{}};
67+
auto &polygon = geom->set<polygon_t>();
5768

5869
// A closed way with less than 4 nodes can never be a valid polygon
5970
if (way.nodes().size() < 4U) {
60-
geom.reset();
61-
return geom;
71+
geom->reset();
72+
return;
6273
}
6374

6475
osmium::area::AssemblerConfig area_config;
@@ -67,22 +78,27 @@ geometry_t create_polygon(osmium::Way const &way)
6778
osmium::memory::Buffer area_buffer{1024};
6879

6980
if (!assembler(way, area_buffer)) {
70-
geom.reset();
71-
return geom;
81+
geom->reset();
82+
return;
7283
}
7384

7485
auto const &area = area_buffer.get<osmium::Area>(0);
7586
auto const &ring = *area.begin<osmium::OuterRing>();
7687

77-
fill_point_list(&geom.get<polygon_t>().outer(), ring);
88+
fill_point_list(&polygon.outer(), ring);
89+
}
7890

91+
geometry_t create_polygon(osmium::Way const &way)
92+
{
93+
geometry_t geom{};
94+
create_polygon(&geom, way);
7995
return geom;
8096
}
8197

82-
geometry_t create_multilinestring(osmium::memory::Buffer const &ways)
98+
void create_multilinestring(geometry_t *geom,
99+
osmium::memory::Buffer const &ways)
83100
{
84-
geometry_t geom{multilinestring_t{}};
85-
auto &mls = geom.get<multilinestring_t>();
101+
auto &mls = geom->set<multilinestring_t>();
86102

87103
for (auto const &way : ways.select<osmium::Way>()) {
88104
linestring_t line;
@@ -93,9 +109,14 @@ geometry_t create_multilinestring(osmium::memory::Buffer const &ways)
93109
}
94110

95111
if (mls.num_geometries() == 0) {
96-
geom.reset();
112+
geom->reset();
97113
}
114+
}
98115

116+
geometry_t create_multilinestring(osmium::memory::Buffer const &ways)
117+
{
118+
geometry_t geom{};
119+
create_multilinestring(&geom, ways);
99120
return geom;
100121
}
101122

@@ -116,34 +137,39 @@ static void fill_polygon(polygon_t *polygon, osmium::Area const &area,
116137
}
117138
}
118139

119-
geometry_t create_multipolygon(osmium::Relation const &relation,
120-
osmium::memory::Buffer const &way_buffer)
140+
void create_multipolygon(geometry_t *geom, osmium::Relation const &relation,
141+
osmium::memory::Buffer const &way_buffer)
121142
{
122-
geometry_t geom{};
123-
124143
osmium::area::AssemblerConfig area_config;
125144
area_config.ignore_invalid_locations = true;
126145
osmium::area::GeomAssembler assembler{area_config};
127146
osmium::memory::Buffer area_buffer{1024};
128147

129148
if (!assembler(relation, way_buffer, area_buffer)) {
130-
return geom;
149+
geom->reset();
150+
return;
131151
}
132152

133153
auto const &area = area_buffer.get<osmium::Area>(0);
134154

135155
if (area.is_multipolygon()) {
136-
auto &multipolygon = geom.set<multipolygon_t>();
156+
auto &multipolygon = geom->set<multipolygon_t>();
137157

138158
for (auto const &outer : area.outer_rings()) {
139159
auto &polygon = multipolygon.add_geometry();
140160
fill_polygon(&polygon, area, outer);
141161
}
142162
} else {
143-
auto &polygon = geom.set<polygon_t>();
163+
auto &polygon = geom->set<polygon_t>();
144164
fill_polygon(&polygon, area, *area.outer_rings().begin());
145165
}
166+
}
146167

168+
geometry_t create_multipolygon(osmium::Relation const &relation,
169+
osmium::memory::Buffer const &way_buffer)
170+
{
171+
geometry_t geom{};
172+
create_multipolygon(&geom, relation, way_buffer);
147173
return geom;
148174
}
149175

0 commit comments

Comments
 (0)