Skip to content

Commit 3d52ea8

Browse files
committed
Add support for node processing in second stage
This is completely analog to how way processing happens in the second stage. So from the select_relation_member() function in the Lua config file you can now also return a list of nodes that you request to process again. Note that this will only re-process the member nodes themselves, ways which have these nodes as members are not re-processed. Also there is no way to mark member nodes of ways, only member nodes of relations. So this will allow processing, say, stop positions in public transport route relations but not, say, barriers on roads. This change is now possible because we removed support for the old middle format which didn't allow storing the complete nodes (with tags).
1 parent 9b283f6 commit 3d52ea8

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)