Skip to content

Commit f4a33ec

Browse files
committed
grouped linemerge
1 parent ce7d24a commit f4a33ec

7 files changed

Lines changed: 872 additions & 2 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ else()
264264
src/gen/gen-base.cpp
265265
src/gen/gen-create.cpp
266266
src/gen/gen-discrete-isolation.cpp
267+
src/gen/gen-grouped-linemerge.cpp
267268
src/gen/gen-rivers.cpp
268269
src/gen/gen-tile-builtup.cpp
269270
src/gen/gen-tile-raster.cpp
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
-- This config example file is released into the Public Domain.
2+
--
3+
-- This Lua config demonstrates the 'grouped-linemerge' generalization. It
4+
-- merges connected lines that share the same set of grouping columns into
5+
-- single (multi-)lines, the equivalent of
6+
--
7+
-- SELECT cols..., (ST_Dump(ST_LineMerge(ST_Collect(geom)))).geom
8+
-- FROM roads GROUP BY cols...
9+
--
10+
-- but done globally and maintained incrementally on updates. A typical use is
11+
-- merging road segments that render identically (same name/ref/highway/layer)
12+
-- so that labels and route shields are placed on the whole road instead of on
13+
-- each individual OSM way, without the artifacts you get when merging only
14+
-- within a tile.
15+
--
16+
-- NOTE THAT THE GENERALIZATION SUPPORT IS EXPERIMENTAL AND MIGHT CHANGE
17+
-- WITHOUT NOTICE!
18+
--
19+
-- Workflow:
20+
-- * Import as usual: osm2pgsql -O flex -S grouped-linemerge.lua DATA.osm.pbf
21+
-- * Build the merged table: osm2pgsql-gen -S grouped-linemerge.lua
22+
-- * Apply an update: osm2pgsql -a -O flex -S grouped-linemerge.lua CHANGES.osc.gz
23+
-- * Update the merged table: osm2pgsql-gen -a -S grouped-linemerge.lua
24+
25+
-- An expire output records which tiles changed during an update. The
26+
-- grouped-linemerge generalization uses it only as a seed for "where did line
27+
-- geometry change" - it then walks each affected connected component out from
28+
-- there and re-merges it. Use a high maxzoom so the seed regions are small.
29+
local exp_roads = osm2pgsql.define_expire_output({
30+
maxzoom = 18,
31+
table = 'exp_roads',
32+
})
33+
34+
-- The source table with the original road segments (one row per OSM way).
35+
local roads = osm2pgsql.define_table({
36+
name = 'roads',
37+
ids = { type = 'way', id_column = 'way_id' },
38+
columns = {
39+
{ column = 'name', type = 'text' },
40+
{ column = 'ref', type = 'text' },
41+
{ column = 'highway', type = 'text' },
42+
{ column = 'layer', type = 'int' },
43+
-- Attach the expire output to the geometry so that any change to a
44+
-- road's geometry (add/modify/delete) expires the tiles it covers.
45+
{ column = 'geom', type = 'linestring', not_null = true,
46+
expire = { { output = exp_roads } } },
47+
}
48+
})
49+
50+
-- The destination table with the merged roads. Its columns are exactly the
51+
-- grouping columns plus the geometry. It has no OSM id column (it is derived
52+
-- data maintained by osm2pgsql-gen, not by the normal update process); the
53+
-- warning osm2pgsql prints about that is expected.
54+
osm2pgsql.define_table({
55+
name = 'roads_merged',
56+
columns = {
57+
{ column = 'name', type = 'text' },
58+
{ column = 'ref', type = 'text' },
59+
{ column = 'highway', type = 'text' },
60+
{ column = 'layer', type = 'int' },
61+
{ column = 'geom', type = 'linestring', not_null = true },
62+
}
63+
})
64+
65+
function osm2pgsql.process_way(object)
66+
local highway = object.tags.highway
67+
if not highway then
68+
return
69+
end
70+
roads:insert({
71+
name = object.tags.name,
72+
ref = object.tags.ref,
73+
highway = highway,
74+
layer = tonumber(object.tags.layer),
75+
geom = object:as_linestring(),
76+
})
77+
end
78+
79+
function osm2pgsql.process_gen()
80+
osm2pgsql.run_gen('grouped-linemerge', {
81+
name = 'roads', -- name (for logging)
82+
debug = false, -- set to true for more detailed debug output
83+
src_table = 'roads', -- input table with the line segments
84+
dest_table = 'roads_merged', -- output table for the merged lines
85+
geom_column = 'geom', -- geometry column (same in src and dest)
86+
87+
-- Lines are merged when ALL of these columns are equal (NULLs compare
88+
-- equal). Pass them as a comma-separated list.
89+
group_by_columns = 'name, ref, highway, layer',
90+
91+
-- Optional pre-filter (SQL boolean expression on the source columns).
92+
-- Lines not matching are completely excluded from the generalization.
93+
-- Here we only merge roads that carry a label or a shield.
94+
where = 'name IS NOT NULL OR ref IS NOT NULL',
95+
96+
-- In append mode, where to read the expired tiles from, and the zoom
97+
-- level they were captured at (must match the expire output's maxzoom).
98+
expire_list = 'exp_roads',
99+
zoom = 18,
100+
101+
-- Create functional endpoint indexes on the src/dest tables in create
102+
-- mode. These make the incremental component walk fast. Set to false
103+
-- if you manage the indexes yourself.
104+
create_indexes = true,
105+
})
106+
end

src/gen/gen-create.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "format.hpp"
1313
#include "gen-base.hpp"
1414
#include "gen-discrete-isolation.hpp"
15+
#include "gen-grouped-linemerge.hpp"
1516
#include "gen-rivers.hpp"
1617
#include "gen-tile-builtup.hpp"
1718
#include "gen-tile-raster.hpp"
@@ -31,6 +32,10 @@ std::unique_ptr<gen_base_t> create_generalizer(std::string const &strategy,
3132
if (strategy == "discrete-isolation") {
3233
return std::make_unique<gen_di_t>(connection, append, params);
3334
}
35+
if (strategy == "grouped-linemerge") {
36+
return std::make_unique<gen_grouped_linemerge_t>(connection, append,
37+
params);
38+
}
3439
if (strategy == "raster-union") {
3540
return std::make_unique<gen_tile_raster_union_t>(connection, append,
3641
params);

0 commit comments

Comments
 (0)