osm2pgsql style: merge main tag and pre-filter handling

Defining a tag as deleteable/extratag and main tag is mutually exclusive
and deleting certain key/value combinations to exclude them from being
used as a main tag is confusing. By merging the handling, such
excludes can now be made explicit in the main list.

By using the same lookup table, it is now also possible to have a
short-cut for uninteresting objects.
This commit is contained in:
Sarah Hoffmann
2024-12-06 09:17:33 +01:00
parent 1eed2fa395
commit 70e351c528

View File

@@ -22,10 +22,9 @@
local module = {} local module = {}
local PRE_DELETE = nil
local PRE_EXTRAS = nil
local POST_DELETE = nil local POST_DELETE = nil
local MAIN_KEYS = nil local MAIN_KEYS = {admin_level = {'delete'}}
local PRE_FILTER = {prefix = {}, suffix = {}}
local NAMES = nil local NAMES = nil
local ADDRESS_TAGS = nil local ADDRESS_TAGS = nil
local SAVE_EXTRA_MAINS = false local SAVE_EXTRA_MAINS = false
@@ -95,6 +94,140 @@ module.RELATION_TYPES = {
waterway = module.relation_as_multiline waterway = module.relation_as_multiline
} }
--------- Built-in place transformation functions --------------------------
local PlaceTransform = {}
-- Special transform meanings which are interpreted elsewhere
PlaceTransform.fallback = 'fallback'
PlaceTransform.delete = 'delete'
PlaceTransform.extra = 'extra'
-- always: unconditionally use that place
function PlaceTransform.always(place)
return place
end
-- never: unconditionally drop the place
function PlaceTransform.never()
return nil
end
-- named: use the place if it has a fully-qualified name
function PlaceTransform.named(place)
if place.has_name then
return place
end
end
-- named_with_key: use place if there is a name with the main key prefix
function PlaceTransform.named_with_key(place, k)
local names = {}
local prefix = k .. ':name'
for namek, namev in pairs(place.intags) do
if namek:sub(1, #prefix) == prefix
and (#namek == #prefix
or namek:sub(#prefix + 1, #prefix + 1) == ':') then
names[namek:sub(#k + 2)] = namev
end
end
if next(names) ~= nil then
return place:clone{names=names}
end
end
----------------- other helper functions -----------------------------
local function lookup_prefilter_classification(k, v)
-- full matches
local desc = MAIN_KEYS[k]
local fullmatch = desc and (desc[v] or desc[1])
if fullmatch ~= nil then
return fullmatch
end
-- suffixes
for slen, slist in pairs(PRE_FILTER.suffix) do
if #k >= slen then
local group = slist[k:sub(-slen)]
if group ~= nil then
return group
end
end
end
-- prefixes
for slen, slist in pairs(PRE_FILTER.prefix) do
if #k >= slen then
local group = slist[k:sub(1, slen)]
if group ~= nil then
return group
end
end
end
end
local function merge_filters_into_main(group, keys, tags)
if keys ~= nil then
for _, key in pairs(keys) do
-- ignore suffix and prefix matches
if key:sub(1, 1) ~= '*' and key:sub(#key, #key) ~= '*' then
if MAIN_KEYS[key] == nil then
MAIN_KEYS[key] = {}
end
MAIN_KEYS[key][1] = group
end
end
end
if tags ~= nil then
for key, values in pairs(tags) do
if MAIN_KEYS[key] == nil then
MAIN_KEYS[key] = {}
end
for _, v in pairs(values) do
MAIN_KEYS[key][v] = group
end
end
end
end
local function remove_group_from_main(group)
for key, values in pairs(MAIN_KEYS) do
for _, ttype in pairs(values) do
if ttype == group then
values[ttype] = nil
end
end
if next(values) == nil then
MAIN_KEYS[key] = nil
end
end
end
local function add_pre_filter(data)
for group, keys in pairs(data) do
for _, key in pairs(keys) do
local klen = #key - 1
if key:sub(1, 1) == '*' then
if klen > 0 then
if PRE_FILTER.suffix[klen] == nil then
PRE_FILTER.suffix[klen] = {}
end
PRE_FILTER.suffix[klen][key:sub(2)] = group
end
elseif key:sub(#key, #key) == '*' then
if PRE_FILTER.prefix[klen] == nil then
PRE_FILTER.prefix[klen] = {}
end
PRE_FILTER.prefix[klen][key:sub(1, klen)] = group
end
end
end
end
------------- Place class ------------------------------------------ ------------- Place class ------------------------------------------
local Place = {} local Place = {}
@@ -119,16 +252,25 @@ function Place.new(object, geom_func)
self.extratags = {} self.extratags = {}
self.intags = {} self.intags = {}
local has_main_tags = false
for k, v in pairs(self.object.tags) do for k, v in pairs(self.object.tags) do
if PRE_DELETE ~= nil and PRE_DELETE(k, v) then local group = lookup_prefilter_classification(k, v)
-- ignore if group == 'extra' then
elseif PRE_EXTRAS ~= nil and PRE_EXTRAS(k, v) then
self.extratags[k] = v self.extratags[k] = v
elseif k ~= 'admin_level' then elseif group ~= 'delete' then
self.intags[k] = v self.intags[k] = v
if group ~= nil then
has_main_tags = true
end
end end
end end
if not has_main_tags then
-- no interesting tags, don't bother processing
self.intags = {}
end
return self return self
end end
@@ -222,7 +364,7 @@ function Place:grab_name_parts(data)
self.has_name = true self.has_name = true
elseif atype == 'house' then elseif atype == 'house' then
self.has_name = true self.has_name = true
fallback = {'place', 'house', 'always'} fallback = {'place', 'house', PlaceTransform.always}
end end
end end
end end
@@ -232,45 +374,17 @@ function Place:grab_name_parts(data)
end end
function Place:write_place(k, v, mtype, save_extra_mains) function Place:write_place(k, v, mfunc, save_extra_mains)
if mtype == nil then
return 0
end
v = v or self.intags[k] v = v or self.intags[k]
if v == nil then if v == nil then
return 0 return 0
end end
if type(mtype) == 'table' then local place = mfunc(self, k, v)
mtype = mtype[v] or mtype[1] if place then
end local res = place:write_row(k, v, save_extra_mains)
self.num_entries = self.num_entries + res
if mtype == 'always' or (self.has_name and mtype == 'named') then return res
return self:write_row(k, v, save_extra_mains)
end
if mtype == 'named_with_key' then
local names = {}
local prefix = k .. ':name'
for namek, namev in pairs(self.intags) do
if namek:sub(1, #prefix) == prefix
and (#namek == #prefix
or namek:sub(#prefix + 1, #prefix + 1) == ':') then
names[namek:sub(#k + 2)] = namev
end
end
if next(names) ~= nil then
local saved_names = self.names
self.names = names
local results = self:write_row(k, v, save_extra_mains)
self.names = saved_names
return results
end
end end
return 0 return 0
@@ -310,12 +424,25 @@ function Place:write_row(k, v, save_extra_mains)
end end
end end
self.num_entries = self.num_entries + 1
return 1 return 1
end end
function Place:clone(data)
local cp = setmetatable({}, Place)
cp.object = self.object
cp.geometry = data.geometry or self.geometry
cp.geom_func = self.geom_func
cp.intags = data.intags or self.intags
cp.admin_level = data.admin_level or self.admin_level
cp.names = data.names or self.names
cp.address = data.address or self.address
cp.extratags = data.extratags or self.extratags
return cp
end
function module.tag_match(data) function module.tag_match(data)
if data == nil or next(data) == nil then if data == nil or next(data) == nil then
return nil return nil
@@ -489,6 +616,10 @@ else
end end
function module.process_tags(o) function module.process_tags(o)
if next(o.intags) == nil then
return -- shortcut when pre-filtering has removed all tags
end
-- Exception for boundary/place double tagging -- Exception for boundary/place double tagging
if o.intags.boundary == 'administrative' then if o.intags.boundary == 'administrative' then
o:grab_extratags{match = function (k, v) o:grab_extratags{match = function (k, v)
@@ -501,17 +632,17 @@ function module.process_tags(o)
-- address keys -- address keys
if o:grab_address_parts{groups=ADDRESS_TAGS} > 0 and fallback == nil then if o:grab_address_parts{groups=ADDRESS_TAGS} > 0 and fallback == nil then
fallback = {'place', 'house', 'always'} fallback = {'place', 'house', PlaceTransform.always}
end end
if o.address.country ~= nil and #o.address.country ~= 2 then if o.address.country ~= nil and #o.address.country ~= 2 then
o.address['country'] = nil o.address['country'] = nil
end end
if POSTCODE_FALLBACK and fallback == nil and o.address.postcode ~= nil then if POSTCODE_FALLBACK and fallback == nil and o.address.postcode ~= nil then
fallback = {'place', 'postcode', 'always'} fallback = {'place', 'postcode', PlaceTransform.always}
end end
if o.address.interpolation ~= nil then if o.address.interpolation ~= nil then
o:write_place('place', 'houses', 'always', SAVE_EXTRA_MAINS) o:write_place('place', 'houses', PlaceTransform.always, SAVE_EXTRA_MAINS)
return return
end end
@@ -519,13 +650,14 @@ function module.process_tags(o)
-- collect main keys -- collect main keys
for k, v in pairs(o.intags) do for k, v in pairs(o.intags) do
local ktype = MAIN_KEYS[k] local ktable = MAIN_KEYS[k]
if ktype == 'fallback' then if ktable then
if o.has_name then local ktype = ktable[v] or ktable[1]
fallback = {k, v, 'named'} if type(ktype) == 'function' then
o:write_place(k, v, ktype, SAVE_EXTRA_MAINS)
elseif ktype == 'fallback' and o.has_name then
fallback = {k, v, PlaceTransform.named}
end end
elseif ktype ~= nil then
o:write_place(k, v, MAIN_KEYS[k], SAVE_EXTRA_MAINS)
end end
end end
@@ -536,23 +668,67 @@ end
--------- Convenience functions for simple style configuration ----------------- --------- Convenience functions for simple style configuration -----------------
function module.set_prefilters(data) function module.set_prefilters(data)
PRE_DELETE = module.tag_match{keys = data.delete_keys, tags = data.delete_tags} remove_group_from_main('delete')
PRE_EXTRAS = module.tag_match{keys = data.extra_keys, merge_filters_into_main('delete', data.delete_keys, data.delete_tags)
tags = data.extra_tags}
module.TAGINFO_MAIN.delete_tags = data.delete_tags remove_group_from_main('extra')
merge_filters_into_main('extra', data.extra_keys, data.extra_tags)
PRE_FILTER = {prefix = {}, suffix = {}}
add_pre_filter{delete = data.delete_keys, extra = data.extra_keys}
end end
function module.ignore_tags(data)
merge_filters_into_main('delete', data)
add_pre_filter{delete = data}
end
function module.add_for_extratags(data)
merge_filters_into_main('extra', data)
add_pre_filter{extra = data}
end
function module.set_main_tags(data) function module.set_main_tags(data)
MAIN_KEYS = data for key, values in pairs(MAIN_KEYS) do
local keys = {} for _, ttype in pairs(values) do
for k, _ in pairs(data) do if ttype == 'fallback' or type(ttype) == 'function' then
table.insert(keys, k) values[ttype] = nil
end
end
if next(values) == nil then
MAIN_KEYS[key] = nil
end
end end
module.TAGINFO_MAIN.keys = keys module.add_main_tags(data)
end end
function module.add_main_tags(data)
for k, v in pairs(data) do
if MAIN_KEYS[k] == nil then
MAIN_KEYS[k] = {}
end
if type(v) == 'function' then
MAIN_KEYS[k][1] = v
elseif type(v) == 'string' then
MAIN_KEYS[k][1] = PlaceTransform[v]
elseif type(v) == 'table' then
for subk, subv in pairs(v) do
if type(subv) == 'function' then
MAIN_KEYS[k][subk] = subv
else
MAIN_KEYS[k][subk] = PlaceTransform[subv]
end
end
end
end
end
function module.set_name_tags(data) function module.set_name_tags(data)
NAMES = module.tag_group(data) NAMES = module.tag_group(data)
@@ -564,8 +740,11 @@ function module.set_name_tags(data)
end end
end end
end end
remove_group_from_main('fallback:name')
merge_filters_into_main('fallback:name', data.house)
end end
function module.set_address_tags(data) function module.set_address_tags(data)
if data.postcode_fallback ~= nil then if data.postcode_fallback ~= nil then
POSTCODE_FALLBACK = data.postcode_fallback POSTCODE_FALLBACK = data.postcode_fallback
@@ -583,8 +762,17 @@ function module.set_address_tags(data)
end end
end end
end end
remove_group_from_main('fallback:address')
remove_group_from_main('fallback:postcode')
merge_filters_into_main('fallback:address', data.main)
if POSTCODE_FALLBACK then
merge_filters_into_main('fallback:postcode', data.postcode)
end
merge_filters_into_main('fallback:address', data.interpolation)
end end
function module.set_unused_handling(data) function module.set_unused_handling(data)
if data.extra_keys == nil and data.extra_tags == nil then if data.extra_keys == nil and data.extra_tags == nil then
POST_DELETE = module.tag_match{keys = data.delete_keys, tags = data.delete_tags} POST_DELETE = module.tag_match{keys = data.delete_keys, tags = data.delete_tags}