]> git.openstreetmap.org Git - nominatim.git/blobdiff - lib-lua/themes/nominatim/init.lua
osm2pgsql style: simplify computation of extra tags
[nominatim.git] / lib-lua / themes / nominatim / init.lua
index 9b7882b3f7a08ac0f3ac77e7f03c632de8535e08..c2dfb130c7744de582660f8321805e6bda8dc3ae 100644 (file)
 
 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
@@ -95,6 +95,158 @@ 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
+
+--------- 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 +257,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 +270,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 +318,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 +347,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 +360,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 +373,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', PlaceTransform.always}
                 end
             end
         end
@@ -221,51 +393,23 @@ 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
@@ -273,12 +417,9 @@ function Place:write_row(k, v, save_extra_mains)
         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 +428,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 +542,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
@@ -478,10 +624,12 @@ 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}
@@ -492,58 +640,101 @@ function module.process_tags(o)
 
     -- address keys
     if o:grab_address_parts{groups=ADDRESS_TAGS} > 0 and fallback == nil then
-        fallback = {'place', 'house', 'always'}
+        fallback = {'place', 'house', PlaceTransform.always}
     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.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)
-    MAIN_KEYS = data
-    local keys = {}
-    for k, _ in pairs(data) do
-        table.insert(keys, k)
+    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.TAGINFO_MAIN.keys = keys
+    module.add_main_tags(data)
 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)
     NAMES = module.tag_group(data)
 
@@ -555,8 +746,11 @@ function module.set_name_tags(data)
             end
         end
     end
+    remove_group_from_main('fallback:name')
+    merge_filters_into_main('fallback:name', data.house)
 end
 
+
 function module.set_address_tags(data)
     if data.postcode_fallback ~= nil then
         POSTCODE_FALLBACK = data.postcode_fallback
@@ -574,15 +768,56 @@ function module.set_address_tags(data)
             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
 
+
 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