Skip to content

Commit 24ce38f

Browse files
committed
New feature: Check location of OSM objects against list of regions
This commit introduces a new feature: Locators. A locator is initialized with one or more regions, each region has a name and a polygon or bounding box. A geometry of an OSM object can then be checked against this region list to figure out in which region(s) it is located. This check is much faster than it would be to do this inside the database after import. Locators can be used for all sorts of interesting features: * Read larger OSM file but import only data inside some area. * Annotate each OSM object with the country (or other region) it is in. This can then, for instance, be used to show special highway shields for each country. * Use the information which region the data is in for further processing, for instance setting of default values for the speed limit or using special language transliterations rules based on country. Locators are created in Lua with `define_locator()`. Bounding boxes can be added with `add_bbox()`. Polygons can be added from the database by calling `add_from_db()` and specifiying an SQL query which can return any number of rows each defining a region with the name and the (multi)polygon as columns. A locator can then be queried using `all_intersecting()` returning a list of names of all regions that intersect the specified OSM object geometry. Or the `first_intersecting()` function can be used which only returns a single region for those cases where there can be no overlapping data or where the details of objects straddling region boundaries don't matter. Several example config files are provided in the flex-config/locator directory.
1 parent 60e7aba commit 24ce38f

22 files changed

+1156
-6
lines changed

.github/workflows/luacheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ jobs:
1515
sudo apt-get install -yq --no-install-suggests --no-install-recommends lua-check
1616
1717
- name: Run luacheck
18-
run: luacheck flex-config/*.lua flex-config/gen/*.lua tests/data/*.lua tests/lua/tests.lua
18+
run: luacheck flex-config/*.lua flex-config/*/*.lua tests/data/*.lua tests/lua/tests.lua
1919

flex-config/locator/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Flex Output Configuration for Locator
2+
3+
These are config file examples for use with the *locator* functionality.
4+
5+
First use the `import-countries.lua` to import country boundaries, then
6+
use these with the other config files.
7+
8+
You should be able to run this on the planet file or on any extracts you want.
9+

flex-config/locator/buildings.lua

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
-- This config example file is released into the Public Domain.
2+
3+
-- This file shows how to use a locator to tag all buildings with the country
4+
-- they are in.
5+
6+
-- Define the "countries" locator and get all country geometries from the
7+
-- database. Use the import-countries.lua file to import them first, before
8+
-- you run this.
9+
local countries = osm2pgsql.define_locator({ name = 'countries' })
10+
11+
-- The SELECT query must return the regions the locator will use. The first
12+
-- column must contain the name of the region, the second the geometry in
13+
-- WGS84 lon/lat coordinates.
14+
-- To improve the efficiency of the country lookup, we'll subdivide the
15+
-- country polygons into smaller pieces. (If you run this often, for instance
16+
-- when doing updates, do the subdivide first in the database and store the
17+
-- result in its own table.)
18+
countries:add_from_db('SELECT code, ST_Subdivide(geom, 200) FROM countries')
19+
20+
-- You have to decide whether you are interested in getting all regions
21+
-- intersecting with any of the objects or only one of them.
22+
--
23+
-- * Getting all regions makes sure that you get everything, even if regions
24+
-- overlap or objects straddle the border between regions. Use the function
25+
-- all_intersecting() for that.
26+
-- * Getting only one region is faster because osm2pgsql can stop looking
27+
-- for matches after the first one. Use first_intersecting() for that.
28+
-- This makes sense if you only have a single region anyway or if your
29+
-- regions don't overlap or you are not so concerned with what happens at
30+
-- the borders.
31+
--
32+
-- Just for demonstration, we do both in this example, in the "country" and
33+
-- "countries" columns, respectively.
34+
local buildings = osm2pgsql.define_area_table('buildings', {
35+
36+
-- This will contain the country code of the first matching country
37+
-- (which can be any of the countries because there is no order here).
38+
{ column = 'country', type = 'text' },
39+
40+
-- This array will contain the country codes of all matching countries.
41+
{ column = 'countries', sql_type = 'text[]' },
42+
{ column = 'tags', type = 'jsonb' },
43+
{ column = 'geom', type = 'polygon', not_null = true },
44+
})
45+
46+
local function add(geom, tags)
47+
buildings:insert({
48+
country = countries:first_intersecting(geom), -- or use geom:centroid()
49+
50+
-- We have to create the format that PostgreSQL expects for text
51+
-- arrays. We assume that the region names do not contain any special
52+
-- characters, otherwise we would need to do some escaping here.
53+
countries = '{' .. table.concat(countries:all_intersecting(geom), ',') .. '}',
54+
55+
tags = tags,
56+
geom = geom,
57+
})
58+
end
59+
60+
function osm2pgsql.process_way(object)
61+
if object.tags.building then
62+
add(object:as_polygon(), object.tags)
63+
end
64+
end
65+
66+
function osm2pgsql.process_relation(object)
67+
if object.tags.building then
68+
local geom = object:as_multipolygon()
69+
for p in geom:geometries() do
70+
add(p, object.tags)
71+
end
72+
end
73+
end
74+

flex-config/locator/iceland.lua

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-- This config example file is released into the Public Domain.
2+
3+
-- This file shows how to use a locator with a bounding box to import only
4+
-- the data for a region. In this case only highways in Iceland are imported
5+
-- even if you run this on the full planet file.
6+
7+
local iceland = osm2pgsql.define_locator({ name = 'iceland' })
8+
9+
iceland:add_bbox('IS', -25.0, 62.0, -12.0, 68.0)
10+
11+
local highways = osm2pgsql.define_way_table('highways', {
12+
{ column = 'hwtype', type = 'text', not_null = true },
13+
{ column = 'name', type = 'text' },
14+
{ column = 'ref', type = 'text' },
15+
{ column = 'geom', type = 'linestring', not_null = true },
16+
})
17+
18+
function osm2pgsql.process_way(object)
19+
local t = object.tags
20+
if t.highway then
21+
local geom = object:as_linestring()
22+
local region = iceland:first_intersecting(geom)
23+
if region then
24+
highways:insert({
25+
hwtype = t.highway,
26+
name = t.name,
27+
ref = t.ref,
28+
geom = geom,
29+
})
30+
end
31+
end
32+
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
-- This config example file is released into the Public Domain.
2+
3+
-- This file is part of the examples for using the locator function of
4+
-- osm2pgsql. It is used to import all country boundaries into a database.
5+
6+
local countries = osm2pgsql.define_relation_table('countries', {
7+
-- For the ISO3166-1 Alpha-2 country code
8+
-- https://en.wikipedia.org/wiki/ISO_3166-1
9+
{ column = 'code', type = 'text', not_null = true },
10+
-- Because we want to use the geometries for the locator feature they
11+
-- must be in 4326! We use a polygon type here and will later split
12+
-- multipolygons into their parts.
13+
{ column = 'geom', type = 'polygon', not_null = true, projection = 4326 },
14+
})
15+
16+
function osm2pgsql.process_relation(object)
17+
local t = object.tags
18+
19+
if t.boundary == 'administrative' and t.admin_level == '2' then
20+
local code = t['ISO3166-1']
21+
22+
-- Ignore entries with syntactically invalid ISO code
23+
if not code or not string.match(code, '^%u%u$') then
24+
return
25+
end
26+
27+
for geom in object:as_multipolygon():geometries() do
28+
countries:insert({
29+
code = code,
30+
geom = geom,
31+
})
32+
end
33+
end
34+
end
35+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-- This config example file is released into the Public Domain.
2+
3+
-- This file shows how to use a locator to find the country-specific colour
4+
5+
-- Define the "countries" locator and get all country geometries from the
6+
-- database. Use the import-countries.lua file to import them first, before
7+
-- you run this.
8+
local countries = osm2pgsql.define_locator({ name = 'countries' })
9+
countries:add_from_db('SELECT code, ST_Subdivide(geom, 200) FROM countries')
10+
11+
local highways = osm2pgsql.define_way_table('highways', {
12+
{ column = 'hwtype', type = 'text' },
13+
{ column = 'country', type = 'text' },
14+
{ column = 'colour', type = 'text' },
15+
{ column = 'geom', type = 'linestring', not_null = true },
16+
})
17+
18+
-- Each country uses their own colour for motorways. Here is the beginning
19+
-- of a list of some countries in Europe. Source:
20+
-- https://en.wikipedia.org/wiki/Comparison_of_European_road_signs
21+
local cc2colour = {
22+
BE = '#2d00e5',
23+
CH = '#128044',
24+
DE = '#174688',
25+
FR = '#333b97',
26+
NL = '#064269',
27+
}
28+
29+
function osm2pgsql.process_way(object)
30+
if object.tags.highway then
31+
local geom = object:as_linestring()
32+
local cc = countries:first_intersecting(geom)
33+
highways:insert({
34+
hwtype = object.tags.highway,
35+
country = cc,
36+
colour = cc2colour[cc],
37+
geom = geom,
38+
})
39+
end
40+
end

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ target_sources(osm2pgsql_lib PRIVATE
2121
flex-lua-expire-output.cpp
2222
flex-lua-geom.cpp
2323
flex-lua-index.cpp
24+
flex-lua-locator.cpp
2425
flex-lua-table.cpp
2526
flex-table-column.cpp
2627
flex-table.cpp
@@ -33,6 +34,7 @@ target_sources(osm2pgsql_lib PRIVATE
3334
geom.cpp
3435
idlist.cpp
3536
input.cpp
37+
locator.cpp
3638
logging.cpp
3739
lua-setup.cpp
3840
lua-utils.cpp

0 commit comments

Comments
 (0)