Skip to content

Commit 9ed53e5

Browse files
authored
Merge pull request #2252 from joto/two-stage-proc-nodes
Add support for node processing in second stage
2 parents ff5051a + 3d52ea8 commit 9ed53e5

File tree

12 files changed

+420
-50
lines changed

12 files changed

+420
-50
lines changed

flex-config/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ following order (from easiest to understand to the more complex ones):
1717

1818
After that you can dive into more advanced topics:
1919

20+
* [public-transport.lua](public-transport.lua) -- Use multi-stage processing
21+
to bring tags from public transport relations to member nodes and ways
2022
* [route-relations.lua](route-relations.lua) -- Use multi-stage processing
2123
to bring tags from relations to member ways
2224
* [unitable.lua](unitable.lua) -- Put all OSM data into a single table

flex-config/public-transport.lua

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
-- This config example file is released into the Public Domain.
2+
3+
-- This file shows how to use multi-stage processing to bring tags from
4+
-- public transport relations into member nodes and ways. This allows
5+
-- advanced processing of public transport networks including stops.
6+
7+
-- Nodes tagged as public transport stops are imported into the 'stops' table,
8+
-- if they are part of a public transport relation. Ways tagged as highway or
9+
-- railway or imported into the 'lines' table. The public transport routes
10+
-- themselves will be in the 'routes' table, but without any geometry. As a
11+
-- "bonus" public transport stop area relations will be imported into the
12+
-- 'stop_areas' table.
13+
--
14+
-- For the 'stops' and 'lines' table two-stage processing is used. The
15+
-- 'rel_refs' text column will contain a list of all ref tags found in parent
16+
-- relations with type=route and route=public_transport. The 'rel_ids' column
17+
-- will be an integer array containing the relation ids. These could be used,
18+
-- for instance, to look up other relation tags from the 'routes' table.
19+
20+
local tables = {}
21+
22+
tables.stops = osm2pgsql.define_node_table('stops', {
23+
{ column = 'tags', type = 'jsonb' },
24+
{ column = 'rel_refs', type = 'text' }, -- for the refs from the relations
25+
{ column = 'rel_ids', sql_type = 'int8[]' }, -- array with integers (for relation IDs)
26+
{ column = 'geom', type = 'point', not_null = true },
27+
})
28+
29+
tables.lines = osm2pgsql.define_way_table('lines', {
30+
{ column = 'tags', type = 'jsonb' },
31+
{ column = 'rel_refs', type = 'text' }, -- for the refs from the relations
32+
{ column = 'rel_ids', sql_type = 'int8[]' }, -- array with integers (for relation IDs)
33+
{ column = 'geom', type = 'linestring', not_null = true },
34+
})
35+
36+
-- Tables don't have to have a geometry column
37+
tables.routes = osm2pgsql.define_relation_table('routes', {
38+
{ column = 'ref', type = 'text' },
39+
{ column = 'type', type = 'text' },
40+
{ column = 'from', type = 'text' },
41+
{ column = 'to', type = 'text' },
42+
{ column = 'tags', type = 'jsonb' },
43+
})
44+
45+
-- Stop areas contain everything belonging to a specific public transport
46+
-- stop. We model them here by adding a center point as geometry plus the
47+
-- radius of a circle that contains everything in that stop.
48+
tables.stop_areas = osm2pgsql.define_relation_table('stop_areas', {
49+
{ column = 'tags', type = 'jsonb' },
50+
{ column = 'radius', type = 'real', not_null = true },
51+
{ column = 'geom', type = 'point', not_null = true },
52+
})
53+
54+
-- This will be used to store information about relations queryable by member
55+
-- node/way id. These are table of tables. The outer table is indexed by the
56+
-- node/way id, the inner table indexed by the relation id. This way even if
57+
-- the information about a relation is added twice, it will be in there only
58+
-- once. It is always good to write your osm2pgsql Lua code in an idempotent
59+
-- way, i.e. it can be called any number of times and will lead to the same
60+
-- result.
61+
local n2r = {}
62+
local w2r = {}
63+
64+
local function clean_tags(tags)
65+
tags.odbl = nil
66+
tags.created_by = nil
67+
tags.source = nil
68+
tags['source:ref'] = nil
69+
70+
return next(tags) == nil
71+
end
72+
73+
local function unique_array(array)
74+
local result = {}
75+
76+
local last = nil
77+
for _, v in ipairs(array) do
78+
if v ~= last then
79+
result[#result + 1] = v
80+
last = v
81+
end
82+
end
83+
84+
return result
85+
end
86+
87+
local separator = '·' -- use middle dot as separator character
88+
89+
local function add_rel_data(row, d)
90+
if not d then
91+
return
92+
end
93+
94+
local refs = {}
95+
local ids = {}
96+
for rel_id, rel_ref in pairs(d) do
97+
refs[#refs + 1] = rel_ref
98+
ids[#ids + 1] = rel_id
99+
end
100+
table.sort(refs)
101+
table.sort(ids)
102+
103+
row.rel_refs = table.concat(unique_array(refs), separator)
104+
row.rel_ids = '{' .. table.concat(unique_array(ids), ',') .. '}'
105+
end
106+
107+
function osm2pgsql.process_node(object)
108+
-- We are only interested in public transport stops here, and they are
109+
-- only available in the second stage.
110+
if osm2pgsql.stage ~= 2 then
111+
return
112+
end
113+
114+
local row = {
115+
tags = object.tags,
116+
geom = object:as_point()
117+
}
118+
119+
-- If there is any data from parent relations, add it in
120+
add_rel_data(row, n2r[object.id])
121+
122+
tables.stops:insert(row)
123+
end
124+
125+
function osm2pgsql.process_way(object)
126+
-- We are only interested in highways and railways
127+
if not object.tags.highway and not object.tags.railway then
128+
return
129+
end
130+
131+
clean_tags(object.tags)
132+
133+
-- Data we will store in the 'lines' table always has the tags from
134+
-- the way
135+
local row = {
136+
tags = object.tags,
137+
geom = object:as_linestring()
138+
}
139+
140+
-- If there is any data from parent relations, add it in
141+
add_rel_data(row, w2r[object.id])
142+
143+
tables.lines:insert(row)
144+
end
145+
146+
local pt = {
147+
bus = true,
148+
light_rail = true,
149+
subway = true,
150+
tram = true,
151+
trolleybus = true,
152+
}
153+
154+
-- We are only interested in certain route relations with a ref tag
155+
local function wanted_relation(tags)
156+
return tags.type == 'route' and pt[tags.route] and tags.ref
157+
end
158+
159+
-- This function is called for every added, modified, or deleted relation.
160+
-- Its only job is to return the ids of all member nodes/ways of the specified
161+
-- relation we want to see in stage 2 again. It MUST NOT store any information
162+
-- about the relation!
163+
function osm2pgsql.select_relation_members(relation)
164+
-- Only interested in public transport relations with refs
165+
if wanted_relation(relation.tags) then
166+
local node_ids = {}
167+
local way_ids = {}
168+
169+
for _, member in ipairs(relation.members) do
170+
if member.type == 'n' and member.role == 'stop' then
171+
node_ids[#node_ids + 1] = member.ref
172+
elseif member.type == 'w' and member.role == '' then
173+
way_ids[#way_ids + 1] = member.ref
174+
end
175+
end
176+
177+
return {
178+
nodes = node_ids,
179+
ways = way_ids,
180+
}
181+
end
182+
end
183+
184+
-- The process_relation() function should store all information about relation
185+
-- members that might be needed in stage 2.
186+
function osm2pgsql.process_relation(object)
187+
if object.tags.type == 'public_transport' and object.tags.public_transport == 'stop_area' then
188+
local x1, y1, x2, y2 = object:as_geometrycollection():transform(3857):get_bbox()
189+
local radius = math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))
190+
tables.stop_areas:insert({
191+
tags = object.tags,
192+
geom = object:as_geometrycollection():centroid(),
193+
radius = radius,
194+
})
195+
return
196+
end
197+
198+
if wanted_relation(object.tags) then
199+
tables.routes:insert({
200+
type = object.tags.route,
201+
ref = object.tags.ref,
202+
from = object.tags.from,
203+
to = object.tags.to,
204+
tags = object.tags,
205+
})
206+
207+
-- Go through all the members and store relation ids and refs so they
208+
-- can be found by the member node/way id.
209+
for _, member in ipairs(object.members) do
210+
if member.type == 'n' then
211+
if not n2r[member.ref] then
212+
n2r[member.ref] = {}
213+
end
214+
n2r[member.ref][object.id] = object.tags.ref
215+
elseif member.type == 'w' then
216+
if not w2r[member.ref] then
217+
w2r[member.ref] = {}
218+
end
219+
w2r[member.ref][object.id] = object.tags.ref
220+
end
221+
end
222+
end
223+
end
224+

src/init.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ function osm2pgsql.define_area_table(_name, _columns, _options)
4646
return _define_table_impl('area', _name, _columns, _options)
4747
end
4848

49+
function osm2pgsql.node_member_ids(relation)
50+
local ids = {}
51+
for _, member in ipairs(relation.members) do
52+
if member.type == 'n' then
53+
ids[#ids + 1] = member.ref
54+
end
55+
end
56+
return ids
57+
end
58+
4959
function osm2pgsql.way_member_ids(relation)
5060
local ids = {}
5161
for _, member in ipairs(relation.members) do

src/middle-pgsql.cpp

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,10 @@ INSERT INTO osm2pgsql_changed_relations
691691
m_db_connection.exec(build_sql(*m_options, query));
692692
}
693693

694-
load_id_list(m_db_connection, "osm2pgsql_changed_ways", parent_ways);
694+
if (parent_ways) {
695+
load_id_list(m_db_connection, "osm2pgsql_changed_ways", parent_ways);
696+
}
697+
695698
load_id_list(m_db_connection, "osm2pgsql_changed_relations",
696699
parent_relations);
697700

@@ -700,9 +703,17 @@ INSERT INTO osm2pgsql_changed_relations
700703
timer.stop();
701704

702705
log_debug("Found {} new/changed nodes in input.", changed_nodes.size());
703-
log_debug(" Found in {} their {} parent ways and {} parent relations.",
704-
std::chrono::duration_cast<std::chrono::seconds>(timer.elapsed()),
705-
parent_ways->size(), parent_relations->size());
706+
707+
auto const elapsed_sec =
708+
std::chrono::duration_cast<std::chrono::seconds>(timer.elapsed());
709+
710+
if (parent_ways) {
711+
log_debug(" Found in {} their {} parent ways and {} parent relations.",
712+
elapsed_sec, parent_ways->size(), parent_relations->size());
713+
} else {
714+
log_debug(" Found in {} their {} parent relations.", elapsed_sec,
715+
parent_relations->size());
716+
}
706717
}
707718

708719
void middle_pgsql_t::get_way_parents(idlist_t const &changed_ways,
@@ -771,6 +782,21 @@ void middle_pgsql_t::way_set(osmium::Way const &way)
771782

772783
namespace {
773784

785+
/**
786+
* Build node in buffer from database results.
787+
*/
788+
void build_node(osmid_t id, pg_result_t const &res, int res_num, int offset,
789+
osmium::memory::Buffer *buffer, bool with_attributes)
790+
{
791+
osmium::builder::NodeBuilder builder{*buffer};
792+
builder.set_id(id);
793+
794+
if (with_attributes) {
795+
set_attributes_on_builder(&builder, res, res_num, offset);
796+
}
797+
pgsql_parse_json_tags(res.get_value(res_num, offset + 1), buffer, &builder);
798+
}
799+
774800
/**
775801
* Build way in buffer from database results.
776802
*/
@@ -789,6 +815,24 @@ void build_way(osmid_t id, pg_result_t const &res, int res_num, int offset,
789815

790816
} // anonymous namespace
791817

818+
bool middle_query_pgsql_t::node_get(osmid_t id,
819+
osmium::memory::Buffer *buffer) const
820+
{
821+
assert(buffer);
822+
823+
auto const res = m_db_connection.exec_prepared("get_node", id);
824+
825+
if (res.num_tuples() != 1) {
826+
return false;
827+
}
828+
829+
build_node(id, res, 0, 0, buffer, m_store_options.with_attributes);
830+
831+
buffer->commit();
832+
833+
return true;
834+
}
835+
792836
bool middle_query_pgsql_t::way_get(osmid_t id,
793837
osmium::memory::Buffer *buffer) const
794838
{

src/middle-pgsql.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ class middle_query_pgsql_t : public middle_query_t
5959

6060
size_t nodes_get_list(osmium::WayNodeList *nodes) const override;
6161

62+
bool node_get(osmid_t id, osmium::memory::Buffer *buffer) const override;
63+
6264
bool way_get(osmid_t id, osmium::memory::Buffer *buffer) const override;
6365

6466
size_t rel_members_get(osmium::Relation const &rel,

src/middle-ram.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,16 @@ std::size_t middle_ram_t::nodes_get_list(osmium::WayNodeList *nodes) const
278278
return count;
279279
}
280280

281+
bool middle_ram_t::node_get(osmid_t id, osmium::memory::Buffer *buffer) const
282+
{
283+
assert(buffer);
284+
285+
if (m_store_options.nodes) {
286+
return get_object(osmium::item_type::node, id, buffer);
287+
}
288+
return false;
289+
}
290+
281291
bool middle_ram_t::way_get(osmid_t id, osmium::memory::Buffer *buffer) const
282292
{
283293
assert(buffer);

src/middle-ram.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class middle_ram_t : public middle_t, public middle_query_t
6868

6969
std::size_t nodes_get_list(osmium::WayNodeList *nodes) const override;
7070

71+
bool node_get(osmid_t id, osmium::memory::Buffer *buffer) const override;
72+
7173
bool way_get(osmid_t id, osmium::memory::Buffer *buffer) const override;
7274

7375
size_t rel_members_get(osmium::Relation const &rel,

0 commit comments

Comments
 (0)