X-Git-Url: https://git.openstreetmap.org./nominatim.git/blobdiff_plain/438b8fed35ea5d250b826e9d6bd9d21f51ffb9d2..0cf636a80ce668e5c2a0d0000208f63a33315071:/lib-lua/themes/nominatim/init.lua diff --git a/lib-lua/themes/nominatim/init.lua b/lib-lua/themes/nominatim/init.lua index 9b7882b3..fef86f91 100644 --- a/lib-lua/themes/nominatim/init.lua +++ b/lib-lua/themes/nominatim/init.lua @@ -22,13 +22,13 @@ local module = {} -local PRE_DELETE = nil -local PRE_EXTRAS = nil -local POST_DELETE = nil -local MAIN_KEYS = nil -local NAMES = nil -local ADDRESS_TAGS = nil -local SAVE_EXTRA_MAINS = false +local MAIN_KEYS = {admin_level = {'delete'}} +local PRE_FILTER = {prefix = {}, suffix = {}} +local NAMES = {} +local NAME_FILTER = nil +local ADDRESS_TAGS = {} +local ADDRESS_FILTER = nil +local EXTRATAGS_FILTER local POSTCODE_FALLBACK = true -- This file can also be directly require'd instead of running it under @@ -40,12 +40,6 @@ if type(themepark) ~= 'table' then themepark = nil end --- tables required for taginfo -module.TAGINFO_MAIN = {keys = {}, delete_tags = {}} -module.TAGINFO_NAME_KEYS = {} -module.TAGINFO_ADDRESS_KEYS = {} - - -- The single place table. local place_table_definition = { name = "place", @@ -65,6 +59,8 @@ local place_table_definition = { } local insert_row +local script_path = debug.getinfo(1, "S").source:match("@?(.*/)") +local PRESETS = loadfile(script_path .. 'presets.lua')() if themepark then themepark:add_table(place_table_definition) @@ -95,6 +91,174 @@ module.RELATION_TYPES = { 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 + +-- Special transform used with address fallbacks: ignore all names +-- except for those marked as being part of the address. +local function address_fallback(place) + if next(place.names) == nil or NAMES.house == nil then + return place + end + + local names = {} + for k, v in pairs(place.names) do + if NAME_FILTER(k, v) == 'house' then + names[k] = v + end + end + return place:clone{names=names} +end + +--------- Built-in extratags transformation functions --------------- + +local function default_extratags_filter(p, k) + -- Default handling is to copy over place tag for boundaries. + -- Nominatim needs this. + if k ~= 'boundary' or p.intags.place == nil then + return p.extratags + end + + local extra = { place = p.intags.place } + for kin, vin in pairs(p.extratags) do + extra[kin] = vin + end + + return extra +end +EXTRATAGS_FILTER = default_extratags_filter + +----------------- 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 ------------------------------------------ local Place = {} @@ -105,7 +269,7 @@ function Place.new(object, geom_func) self.object = object self.geom_func = geom_func - self.admin_level = tonumber(self.object:grab_tag('admin_level')) + self.admin_level = tonumber(self.object.tags.admin_level or 15) or 15 if self.admin_level == nil or self.admin_level <= 0 or self.admin_level > 15 or math.floor(self.admin_level) ~= self.admin_level then @@ -118,25 +282,45 @@ function Place.new(object, geom_func) self.address = {} self.extratags = {} + self.intags = {} + + local has_main_tags = false + for k, v in pairs(self.object.tags) do + local group = lookup_prefilter_classification(k, v) + if group == 'extra' then + self.extratags[k] = v + elseif group ~= 'delete' then + self.intags[k] = v + if group ~= nil then + has_main_tags = true + end + end + end + + if not has_main_tags then + -- no interesting tags, don't bother processing + self.intags = {} + end + return self end function Place:clean(data) - for k, v in pairs(self.object.tags) do + for k, v in pairs(self.intags) do if data.delete ~= nil and data.delete(k, v) then - self.object.tags[k] = nil + self.intags[k] = nil elseif data.extra ~= nil and data.extra(k, v) then self.extratags[k] = v - self.object.tags[k] = nil + self.intags[k] = nil end end end function Place:delete(data) if data.match ~= nil then - for k, v in pairs(self.object.tags) do + for k, v in pairs(self.intags) do if data.match(k, v) then - self.object.tags[k] = nil + self.intags[k] = nil end end end @@ -146,9 +330,9 @@ function Place:grab_extratags(data) local count = 0 if data.match ~= nil then - for k, v in pairs(self.object.tags) do + for k, v in pairs(self.intags) do if data.match(k, v) then - self.object.tags[k] = nil + self.intags[k] = nil self.extratags[k] = v count = count + 1 end @@ -175,7 +359,7 @@ function Place:grab_address_parts(data) local count = 0 if data.groups ~= nil then - for k, v in pairs(self.object.tags) do + for k, v in pairs(self.intags) do local atype = data.groups(k, v) if atype ~= nil then @@ -188,7 +372,7 @@ function Place:grab_address_parts(data) else self.address[atype] = v end - self.object.tags[k] = nil + self.intags[k] = nil end end end @@ -201,17 +385,17 @@ function Place:grab_name_parts(data) local fallback = nil if data.groups ~= nil then - for k, v in pairs(self.object.tags) do + for k, v in pairs(self.intags) do local atype = data.groups(k, v) if atype ~= nil then self.names[k] = v - self.object.tags[k] = nil + self.intags[k] = nil if atype == 'main' then self.has_name = true elseif atype == 'house' then self.has_name = true - fallback = {'place', 'house', 'always'} + fallback = {'place', 'house', address_fallback} end end end @@ -221,64 +405,33 @@ function Place:grab_name_parts(data) end -function Place:write_place(k, v, mtype, save_extra_mains) - if mtype == nil then - return 0 - end - - v = v or self.object.tags[k] +function Place:write_place(k, v, mfunc) + v = v or self.intags[k] if v == nil then return 0 end - if type(mtype) == 'table' then - mtype = mtype[v] or mtype[1] - end - - if mtype == 'always' or (self.has_name and mtype == 'named') then - 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.object.tags) 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 + local place = mfunc(self, k, v) + if place then + local res = place:write_row(k, v) + self.num_entries = self.num_entries + res + return res end return 0 end -function Place:write_row(k, v, save_extra_mains) +function Place:write_row(k, v) if self.geometry == nil then self.geometry = self.geom_func(self.object) end - if self.geometry:is_null() then + if self.geometry == nil or self.geometry:is_null() then return 0 end - if save_extra_mains ~= nil then - for extra_k, extra_v in pairs(self.object.tags) do - if extra_k ~= k and save_extra_mains(extra_k, extra_v) then - self.extratags[extra_k] = extra_v - end - end + local extratags = EXTRATAGS_FILTER(self, k, v) + if not (extratags and next(extratags)) then + extratags = nil end insert_row{ @@ -287,21 +440,26 @@ function Place:write_row(k, v, save_extra_mains) admin_level = self.admin_level, name = next(self.names) and self.names, address = next(self.address) and self.address, - extratags = next(self.extratags) and self.extratags, + extratags = extratags, geometry = self.geometry } - if save_extra_mains then - for tk, tv in pairs(self.object.tags) do - if save_extra_mains(tk, tv) then - self.extratags[tk] = nil - end - end - end + return 1 +end - self.num_entries = self.num_entries + 1 - return 1 +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 @@ -396,7 +554,7 @@ function module.tag_group(data) end end - return function (k, v) + return function (k) local val = fullmatches[k] if val ~= nil then return val @@ -450,6 +608,9 @@ function module.process_way(object) if geom:is_null() then geom = o:as_linestring() + if geom:is_null() or geom:length() > 30 then + return nil + end end return geom @@ -478,111 +639,268 @@ else end function module.process_tags(o) - o:clean{delete = PRE_DELETE, extra = PRE_EXTRAS} + if next(o.intags) == nil then + return -- shortcut when pre-filtering has removed all tags + end -- Exception for boundary/place double tagging - if o.object.tags.boundary == 'administrative' then + if o.intags.boundary == 'administrative' then o:grab_extratags{match = function (k, v) return k == 'place' and v:sub(1,3) ~= 'isl' end} end -- name keys - local fallback = o:grab_name_parts{groups=NAMES} + local fallback = o:grab_name_parts{groups=NAME_FILTER} -- address keys - if o:grab_address_parts{groups=ADDRESS_TAGS} > 0 and fallback == nil then - fallback = {'place', 'house', 'always'} + if o:grab_address_parts{groups=ADDRESS_FILTER} > 0 and fallback == nil then + fallback = {'place', 'house', address_fallback} end if o.address.country ~= nil and #o.address.country ~= 2 then o.address['country'] = nil end if POSTCODE_FALLBACK and fallback == nil and o.address.postcode ~= nil then - fallback = {'place', 'postcode', 'always'} + fallback = {'place', 'postcode', PlaceTransform.always} end if o.address.interpolation ~= nil then - o:write_place('place', 'houses', 'always', SAVE_EXTRA_MAINS) + o:write_place('place', 'houses', PlaceTransform.always) return end - o:clean{delete = POST_DELETE} - -- collect main keys - for k, v in pairs(o.object.tags) do - local ktype = MAIN_KEYS[k] - if ktype == 'fallback' then - if o.has_name then - fallback = {k, v, 'named'} + for k, v in pairs(o.intags) do + local ktable = MAIN_KEYS[k] + if ktable then + local ktype = ktable[v] or ktable[1] + if type(ktype) == 'function' then + o:write_place(k, v, ktype) + elseif ktype == 'fallback' and o.has_name then + fallback = {k, v, PlaceTransform.named} end - elseif ktype ~= nil then - o:write_place(k, v, MAIN_KEYS[k], SAVE_EXTRA_MAINS) end end if fallback ~= nil and o.num_entries == 0 then - o:write_place(fallback[1], fallback[2], fallback[3], SAVE_EXTRA_MAINS) + o:write_place(fallback[1], fallback[2], fallback[3]) end end --------- Convenience functions for simple style configuration ----------------- - function module.set_prefilters(data) - PRE_DELETE = module.tag_match{keys = data.delete_keys, tags = data.delete_tags} - PRE_EXTRAS = module.tag_match{keys = data.extra_keys, - tags = data.extra_tags} - module.TAGINFO_MAIN.delete_tags = data.delete_tags + remove_group_from_main('delete') + merge_filters_into_main('delete', data.delete_keys, 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 -function module.set_main_tags(data) - MAIN_KEYS = data - local keys = {} - for k, _ in pairs(data) do - table.insert(keys, k) + +function module.ignore_keys(data) + if type(data) == 'string' then + local preset = data + data = PRESETS.IGNORE_KEYS[data] + if data == nil then + error('Unknown preset for ignored keys: ' .. preset) + end end - module.TAGINFO_MAIN.keys = keys + merge_filters_into_main('delete', data) + add_pre_filter{delete = data} end -function module.set_name_tags(data) - NAMES = module.tag_group(data) - for _, lst in pairs(data) do - for _, k in ipairs(lst) do - local key = process_key(k) - if key ~= nil then - module.TAGINFO_NAME_KEYS[key] = true +function module.add_for_extratags(data) + if type(data) == 'string' then + local preset = data + data = PRESETS.EXTRATAGS[data] or PRESETS.IGNORE_KEYS[data] + if data == nil then + error('Unknown preset for extratags: ' .. preset) + end + end + merge_filters_into_main('extra', data) + add_pre_filter{extra = data} +end + + +function module.set_main_tags(data) + for key, values in pairs(MAIN_KEYS) do + for _, ttype in pairs(values) do + if ttype == 'fallback' or type(ttype) == 'function' then + values[ttype] = nil end end + if next(values) == nil then + MAIN_KEYS[key] = nil + end end + module.modify_main_tags(data) end -function module.set_address_tags(data) - if data.postcode_fallback ~= nil then - POSTCODE_FALLBACK = data.postcode_fallback - data.postcode_fallback = nil - end - ADDRESS_TAGS = module.tag_group(data) - - for _, lst in pairs(data) do - if lst ~= nil then - for _, k in ipairs(lst) do - local key = process_key(k) - if key ~= nil then - module.TAGINFO_ADDRESS_KEYS[key] = true + +function module.modify_main_tags(data) + if type(data) == 'string' then + local preset = data + if data:sub(1, 7) == 'street/' then + data = PRESETS.MAIN_TAGS_STREETS[data:sub(8)] + elseif data:sub(1, 4) == 'poi/' then + data = PRESETS.MAIN_TAGS_POIS(data:sub(5)) + else + data = PRESETS.MAIN_TAGS[data] + end + if data == nil then + error('Unknown preset for main tags: ' .. preset) + end + end + + 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.modify_name_tags(data) + if type(data) == 'string' then + local preset = data + data = PRESETS.NAME_TAGS[data] + if data == nil then + error('Unknown preset for name keys: ' .. preset) + end + end + + for k,v in pairs(data) do + if next(v) then + NAMES[k] = v + else + NAMES[k] = nil + end + end + NAME_FILTER = module.tag_group(NAMES) + remove_group_from_main('fallback:name') + if data.house ~= nil then + merge_filters_into_main('fallback:name', data.house) + end +end + + +function module.set_name_tags(data) + NAMES = {} + module.modify_name_tags(data) +end + + +function module.set_address_tags(data) + ADDRESS_TAGS = {} + module.modify_address_tags(data) +end + + +function module.modify_address_tags(data) + if type(data) == 'string' then + local preset = data + data = PRESETS.ADDRESS_TAGS[data] + if data == nil then + error('Unknown preset for address keys: ' .. preset) + end + end + + for k, v in pairs(data) do + if k == 'postcode_fallback' then + POSTCODE_FALLBACK = v + elseif next(v) == nil then + ADDRESS_TAGS[k] = nil + else + ADDRESS_TAGS[k] = v + end + end + + ADDRESS_FILTER = module.tag_group(ADDRESS_TAGS) + + remove_group_from_main('fallback:address') + merge_filters_into_main('fallback:address', data.main) + merge_filters_into_main('fallback:address', data.interpolation) + remove_group_from_main('fallback:postcode') + if POSTCODE_FALLBACK then + merge_filters_into_main('fallback:postcode', data.postcode) + end +end + + +function module.set_address_tags(data) + ADDRESS_TAGS_SOURCE = {} + module.modify_address_tags(data) +end + + +function module.set_postcode_fallback(enable) + if POSTCODE_FALLBACK ~= enable then + remove_group_from_main('fallback:postcode') + if enable then + merge_filters_into_main('fallback:postcode', ADDRESS_TAGS.postcode) + end + end + POSTCODE_FALLBACK = enable +end + + function module.set_unused_handling(data) - if data.extra_keys == nil and data.extra_tags == nil then - POST_DELETE = module.tag_match{keys = data.delete_keys, tags = data.delete_tags} - SAVE_EXTRA_MAINS = function() return true end + if type(data) == 'function' then + EXTRATAGS_FILTER = data + elseif data == nil then + EXTRATAGS_FILTER = default_extratags_filter + elseif data.extra_keys == nil and data.extra_tags == nil then + local delfilter = module.tag_match{keys = data.delete_keys, tags = data.delete_tags} + EXTRATAGS_FILTER = function (p, k) + local extra = {} + for kin, vin in pairs(p.intags) do + if kin ~= k and not delfilter(kin, vin) then + extra[kin] = vin + end + end + if next(extra) == nil then + return p.extratags + end + for kextra, vextra in pairs(p.extratags) do + extra[kextra] = vextra + end + return extra + end elseif data.delete_keys == nil and data.delete_tags == nil then - POST_DELETE = nil - SAVE_EXTRA_MAINS = module.tag_match{keys = data.extra_keys, tags = data.extra_tags} + local incfilter = module.tag_match{keys = data.extra_keys, tags = data.extra_tags} + EXTRATAGS_FILTER = function (p, k) + local extra = {} + for kin, vin in pairs(p.intags) do + if kin ~= k and incfilter(kin, vin) then + extra[kin] = vin + end + end + if next(extra) == nil then + return p.extratags + end + for kextra, vextra in pairs(p.extratags) do + extra[kextra] = vextra + end + return extra + end else error("unused handler can have only 'extra_keys' or 'delete_keys' set.") end @@ -599,4 +917,9 @@ function module.set_relation_types(data) end end + +function module.get_taginfo() + return {main = MAIN_KEYS, name = NAMES, address = ADDRESS_TAGS} +end + return module