]> git.openstreetmap.org Git - nominatim.git/blob - lib-lua/themes/nominatim/init.lua
release 5.3.2.post9
[nominatim.git] / lib-lua / themes / nominatim / init.lua
1 -- Nominatim themepark theme.
2 --
3 -- The Nominatim theme creates a fixed set of import tables for use with
4 -- Nominatim. Creation and object processing are directly controlled by
5 -- the theme. Topics provide preset configurations. You should add exactly
6 -- one topic to your project.
7 --
8 -- The theme also exports a number of functions that can be used to configure
9 -- its behaviour. These may be directly called in the style file after
10 -- importing the theme:
11 --
12 --      local nominatim = themepark:init_theme('nominatim')
13 --      nominatim.set_main_tags{boundary = 'always'}
14 --
15 -- This allows to write your own configuration from scratch. You can also
16 -- use it to customize topics. In that case, first add the topic, then
17 -- change the configuration:
18 --
19 --      themepark:add_topic('nominatim/full')
20 --      local nominatim = themepark:init_theme('nominatim')
21 --      nominatim.ignore_tags{'amenity'}
22
23 local module = {}
24
25 local MAIN_KEYS = {admin_level = {'delete'}}
26 local PRE_FILTER = {prefix = {}, suffix = {}}
27 local NAMES = {}
28 local NAME_FILTER = nil
29 local ADDRESS_TAGS = {}
30 local ADDRESS_FILTER = nil
31 local EXTRATAGS_FILTER
32 local REQUIRED_EXTRATAGS_FILTER
33 local POSTCODE_FALLBACK = true
34 local ENTRANCE_FUNCTION = nil
35
36 -- This file can also be directly require'd instead of running it under
37 -- the themepark framework. In that case the first parameter is usually
38 -- the module name. Lets check for that, so that further down we can call
39 -- the low-level osm2pgsql functions instead of themepark functions.
40 local themepark = ...
41 if type(themepark) ~= 'table' then
42     themepark = nil
43 end
44
45 -- The place tables carry the raw OSM information.
46 local table_definitions = {
47     place = {
48         ids = { type = 'any', id_column = 'osm_id', type_column = 'osm_type' },
49         columns = {
50             { column = 'class', type = 'text', not_null = true },
51             { column = 'type', type = 'text', not_null = true },
52             { column = 'admin_level', type = 'smallint' },
53             { column = 'name', type = 'hstore' },
54             { column = 'address', type = 'hstore' },
55             { column = 'extratags', type = 'hstore' },
56             { column = 'geometry', type = 'geometry', projection = 'WGS84', not_null = true },
57             { column = 'categories', sql_type = 'ltree[]' }
58         },
59         indexes = {}
60     },
61     place_entrance = {
62         ids = { type = 'node', id_column = 'osm_id' },
63         columns = {
64             { column = 'type', type = 'text', not_null = true },
65             { column = 'extratags', type = 'hstore' },
66             { column = 'geometry', type = 'geometry', projection = 'WGS84', not_null = true }
67         },
68         indexes = {}
69     },
70     place_postcode = {
71         ids = { type = 'any', id_column = 'osm_id', type_column = 'osm_type' },
72         columns = {
73             { column = 'postcode', type = 'text', not_null = true },
74             { column = 'country_code', type = 'text' },
75             { column = 'centroid', type = 'point', projection = 'WGS84', not_null = true },
76             { column = 'geometry', type = 'geometry', projection = 'WGS84' }
77         },
78         indexes = {
79             { column = 'postcode', method = 'btree' }
80         }
81      },
82      place_interpolation = {
83         ids = { type = 'way', id_column = 'osm_id' },
84         columns = {
85             { column = 'type', type = 'text', not_null = true },
86             { column = 'address', type = 'hstore' },
87             { column = 'nodes', type = 'text', sql_type = 'bigint[]', not_null = true },
88             { column = 'geometry', type = 'linestring', projection = 'WGS84', not_null = true },
89         },
90         indexes = {
91             { column = 'nodes', method = 'gin' }
92         }
93      },
94     place_associated_street = {
95         ids = { type = 'relation', id_column = 'relation_id' },
96         columns = {
97             { column = 'member_type', type = 'text', not_null = true },
98             { column = 'member_id', type = 'int8', not_null = true },
99             { column = 'member_role', type = 'text', not_null = true }
100         },
101         indexes = {
102             { column = { 'member_type', 'member_id' }, method = 'btree' }
103         }
104     }
105 }
106
107 local insert_row = {}
108 local script_path = debug.getinfo(1, "S").source:match("@?(.*/)")
109 local PRESETS = loadfile(script_path .. 'presets.lua')()
110
111 for table_name, table_definition in pairs(table_definitions) do
112     table_definition.name = table_name
113     table_definition.data_tablespace = os.getenv("NOMINATIM_TABLESPACE_PLACE_DATA")
114     table_definition.index_tablespace = os.getenv("NOMINATIM_TABLESPACE_PLACE_INDEX")
115
116     if themepark then
117         themepark:add_table(table_definition)
118         insert_row[table_name] = function(columns)
119             themepark:insert(table_name, columns, {}, {})
120         end
121     else
122         local place_table = osm2pgsql.define_table(table_definition)
123         insert_row[table_name] = function(columns)
124             place_table:insert(columns)
125         end
126     end
127 end
128
129 ------------ Geometry functions for relations ---------------------
130
131 function module.relation_as_multipolygon(o)
132     return o:as_multipolygon()
133 end
134
135 function module.relation_as_multiline(o)
136     return o:as_multilinestring():line_merge()
137 end
138
139
140 module.RELATION_TYPES = {
141     multipolygon = module.relation_as_multipolygon,
142     boundary = module.relation_as_multipolygon,
143     waterway = module.relation_as_multiline
144 }
145
146 --------- Built-in place transformation functions --------------------------
147
148 local PlaceTransform = {}
149
150 -- Special transform meanings which are interpreted elsewhere
151 PlaceTransform.fallback = 'fallback'
152 PlaceTransform.postcode_area = 'postcode_area'
153 PlaceTransform.delete = 'delete'
154 PlaceTransform.extra = 'extra'
155
156 -- always: unconditionally use that place
157 function PlaceTransform.always(place)
158     return place
159 end
160
161 -- never: unconditionally drop the place
162 function PlaceTransform.never()
163     return nil
164 end
165
166 -- named: use the place if it has a fully-qualified name
167 function PlaceTransform.named(place)
168     if place.has_name then
169         return place
170     end
171 end
172
173 -- named_with_key: use place if there is a name with the main key prefix
174 function PlaceTransform.named_with_key(place, k)
175     local prefix = k .. ':name'
176     local found = false
177     for namek, namev in pairs(place.intags) do
178         if namek:sub(1, #prefix) == prefix
179            and (#namek == #prefix
180                 or namek:sub(#prefix + 1, #prefix + 1) == ':') then
181             place.names[namek:sub(#k + 2)] = namev
182             found = true
183         end
184     end
185
186     if found then
187         return place
188     end
189 end
190
191
192 ----------------- other helper functions -----------------------------
193
194 local function lookup_prefilter_classification(k, v)
195     -- full matches
196     local desc = MAIN_KEYS[k]
197     local fullmatch = desc and (desc[v] or desc[1])
198     if fullmatch ~= nil then
199         return fullmatch
200     end
201     -- suffixes
202     for slen, slist in pairs(PRE_FILTER.suffix) do
203         if #k >= slen then
204             local group = slist[k:sub(-slen)]
205             if group ~= nil then
206                 return group
207             end
208         end
209     end
210     -- prefixes
211     for slen, slist in pairs(PRE_FILTER.prefix) do
212         if #k >= slen then
213             local group = slist[k:sub(1, slen)]
214             if group ~= nil then
215                 return group
216             end
217         end
218     end
219 end
220
221
222 local function merge_filters_into_main(group, keys, tags)
223     if keys ~= nil then
224         for _, key in pairs(keys) do
225             -- ignore suffix and prefix matches
226             if key:sub(1, 1) ~= '*' and key:sub(#key, #key) ~= '*' then
227                 if MAIN_KEYS[key] == nil then
228                     MAIN_KEYS[key] = {}
229                 end
230                 MAIN_KEYS[key][1] = group
231             end
232         end
233     end
234
235     if tags ~= nil then
236         for key, values in pairs(tags) do
237             if MAIN_KEYS[key] == nil then
238                 MAIN_KEYS[key] = {}
239             end
240             for _, v in pairs(values) do
241                 MAIN_KEYS[key][v] = group
242             end
243         end
244     end
245 end
246
247
248 local function remove_group_from_main(group)
249     for key, values in pairs(MAIN_KEYS) do
250         for _, ttype in pairs(values) do
251             if ttype == group then
252                 values[ttype] = nil
253             end
254         end
255         if next(values) == nil then
256             MAIN_KEYS[key] = nil
257         end
258     end
259 end
260
261
262 local function add_pre_filter(data)
263     for group, keys in pairs(data) do
264         for _, key in pairs(keys) do
265             local klen = #key - 1
266             if key:sub(1, 1) == '*' then
267                 if klen > 0 then
268                     if PRE_FILTER.suffix[klen] == nil then
269                         PRE_FILTER.suffix[klen] = {}
270                     end
271                     PRE_FILTER.suffix[klen][key:sub(2)] = group
272                 end
273             elseif key:sub(#key, #key) == '*' then
274                 if PRE_FILTER.prefix[klen] == nil then
275                     PRE_FILTER.prefix[klen] = {}
276                 end
277                 PRE_FILTER.prefix[klen][key:sub(1, klen)] = group
278             end
279         end
280     end
281 end
282
283 ------------- Place class ------------------------------------------
284
285 local Place = {}
286 Place.__index = Place
287
288 -- ltree labels: validate-or-fallback matching Photon's CATEGORY_PATTERN.
289 -- Hyphens replaced with underscore for PG < 16 ltree compat.
290 -- Any other invalid char -> fall back to 'yes'.
291 -- https://www.postgresql.org/message-id/E1pDtub-002Nio-Tv%40gemulon.postgresql.org
292 local function sanitize_label(s)
293     if s == nil or s == '' then return 'yes' end
294     if s:match('[^A-Za-z0-9_-]') then
295         return 'yes'
296     end
297     return s:gsub('-', '_')
298 end
299
300 -- Build an ltree-compatible category string: osm.<key>.<value>.
301 -- Returns nil if the key cannot be sanitized to a valid ltree label.
302 local function get_category(k, v)
303     if k == nil or k == '' or k:match('[^A-Za-z0-9_-]') then
304         return nil
305     end
306     return 'osm.' .. sanitize_label(k) .. '.' .. sanitize_label(v)
307 end
308
309
310 function Place.new(object, geom_func)
311     local self = setmetatable({}, Place)
312     self.object = object
313     self.geom_func = geom_func
314
315     self.admin_level = tonumber(self.object.tags.admin_level or 15) or 15
316     if self.admin_level == nil
317        or self.admin_level <= 0 or self.admin_level > 15
318        or math.floor(self.admin_level) ~= self.admin_level then
319         self.admin_level = 15
320     end
321
322     self.has_name = false
323     self.names = {}
324     self.address = {}
325     self.extratags = {}
326
327     self.intags = {}
328
329     local has_main_tags = false
330     for k, v in pairs(self.object.tags) do
331         local group = lookup_prefilter_classification(k, v)
332         if group == 'extra' then
333             self.extratags[k] = v
334         elseif group ~= 'delete' then
335             self.intags[k] = v
336             if group ~= nil then
337                 has_main_tags = true
338             end
339         end
340     end
341
342     if not has_main_tags then
343         -- no interesting tags, don't bother processing
344         self.intags = {}
345     end
346
347     return self
348 end
349
350 function Place:clean(data)
351     for k, v in pairs(self.intags) do
352         if data.delete ~= nil and data.delete(k, v) then
353             self.intags[k] = nil
354         elseif data.extra ~= nil and data.extra(k, v) then
355             self.extratags[k] = v
356             self.intags[k] = nil
357         end
358     end
359 end
360
361 function Place:delete(data)
362     if data.match ~= nil then
363         for k, v in pairs(self.intags) do
364             if data.match(k, v) then
365                 self.intags[k] = nil
366             end
367         end
368     end
369 end
370
371 function Place:grab_extratags(data)
372     local count = 0
373
374     if data.match ~= nil then
375         for k, v in pairs(self.intags) do
376             if data.match(k, v) then
377                 self.intags[k] = nil
378                 self.extratags[k] = v
379                 count = count + 1
380             end
381         end
382     end
383
384     return count
385 end
386
387 local function strip_address_prefix(k)
388     if k:sub(1, 5) == 'addr:' then
389         return k:sub(6)
390     end
391
392     if k:sub(1, 6) == 'is_in:' then
393         return k:sub(7)
394     end
395
396     return k
397 end
398
399
400 function Place:grab_address_parts(data)
401     local count = 0
402
403     if data.groups ~= nil then
404         for k, v in pairs(self.intags) do
405             local atype = data.groups(k, v)
406
407             if atype ~= nil then
408                 if atype == 'main' then
409                     self.has_name = true
410                     self.address[strip_address_prefix(k)] = v
411                     count = count + 1
412                 elseif atype == 'extra' then
413                     self.address[strip_address_prefix(k)] = v
414                 else
415                     self.address[atype] = v
416                 end
417                 self.intags[k] = nil
418             end
419         end
420     end
421
422     return count
423 end
424
425
426 function Place:grab_name_parts(data)
427     local fallback = false
428
429     if data.groups ~= nil then
430         for k, v in pairs(self.intags) do
431             local atype = data.groups(k, v)
432
433             if atype ~= nil then
434                 self.names[k] = v
435                 self.intags[k] = nil
436                 if atype == 'main' then
437                     self.has_name = true
438                 elseif atype == 'house' then
439                     self.has_name = true
440                     fallback = true
441                 end
442             end
443         end
444     end
445
446     return fallback
447 end
448
449
450
451
452 function Place:geometry_is_valid()
453     if self.geometry == nil then
454         self.geometry = self.geom_func(self.object)
455
456         if self.geometry == nil or self.geometry:is_null() then
457             self.geometry = false
458             return false
459         end
460
461         return true
462     end
463
464     return self.geometry ~= false
465 end
466
467
468
469 function Place:clone(data)
470     local cp = setmetatable({}, Place)
471     cp.object = self.object
472     cp.geometry = data.geometry or self.geometry
473     cp.geom_func = self.geom_func
474     cp.intags = data.intags or self.intags
475     cp.admin_level = data.admin_level or self.admin_level
476     cp.names = data.names or self.names
477     cp.address = data.address or self.address
478     cp.extratags = data.extratags or self.extratags
479
480     return cp
481 end
482
483
484 function module.tag_match(data)
485     if data == nil or next(data) == nil then
486         return nil
487     end
488
489     local fullmatches = {}
490     local key_prefixes = {}
491     local key_suffixes = {}
492
493     if data.keys ~= nil then
494         for _, key in pairs(data.keys) do
495             if key:sub(1, 1) == '*' then
496                 if #key > 1 then
497                     if key_suffixes[#key - 1] == nil then
498                         key_suffixes[#key - 1] = {}
499                     end
500                     key_suffixes[#key - 1][key:sub(2)] = true
501                 end
502             elseif key:sub(#key, #key) == '*' then
503                 if key_prefixes[#key - 1] == nil then
504                     key_prefixes[#key - 1] = {}
505                 end
506                 key_prefixes[#key - 1][key:sub(1, #key - 1)] = true
507             else
508                 fullmatches[key] = true
509             end
510         end
511     end
512
513     if data.tags ~= nil then
514         for k, vlist in pairs(data.tags) do
515             if fullmatches[k] == nil then
516                 fullmatches[k] = {}
517                 for _, v in pairs(vlist) do
518                     fullmatches[k][v] = true
519                 end
520             end
521         end
522     end
523
524     return function (k, v)
525         if fullmatches[k] ~= nil and (fullmatches[k] == true or fullmatches[k][v] ~= nil) then
526             return true
527         end
528
529         for slen, slist in pairs(key_suffixes) do
530             if #k >= slen and slist[k:sub(-slen)] ~= nil then
531                 return true
532             end
533         end
534
535         for slen, slist in pairs(key_prefixes) do
536             if #k >= slen and slist[k:sub(1, slen)] ~= nil then
537                 return true
538             end
539         end
540
541         return false
542     end
543 end
544
545
546 function module.tag_group(data)
547     if data == nil or next(data) == nil then
548         return nil
549     end
550
551     local fullmatches = {}
552     local key_prefixes = {}
553     local key_suffixes = {}
554
555     for group, tags in pairs(data) do
556         for _, key in pairs(tags) do
557             if key:sub(1, 1) == '*' then
558                 if #key > 1 then
559                     if key_suffixes[#key - 1] == nil then
560                         key_suffixes[#key - 1] = {}
561                     end
562                     key_suffixes[#key - 1][key:sub(2)] = group
563                 end
564             elseif key:sub(#key, #key) == '*' then
565                 if key_prefixes[#key - 1] == nil then
566                     key_prefixes[#key - 1] = {}
567                 end
568                 key_prefixes[#key - 1][key:sub(1, #key - 1)] = group
569             else
570                 fullmatches[key] = group
571             end
572         end
573     end
574
575     return function (k)
576         local val = fullmatches[k]
577         if val ~= nil then
578             return val
579         end
580
581         for slen, slist in pairs(key_suffixes) do
582             if #k >= slen then
583                 val = slist[k:sub(-slen)]
584                 if val ~= nil then
585                     return val
586                 end
587             end
588         end
589
590         for slen, slist in pairs(key_prefixes) do
591             if #k >= slen then
592                 val = slist[k:sub(1, slen)]
593                 if val ~= nil then
594                     return val
595                 end
596             end
597         end
598     end
599 end
600
601 -- Returns prefix part of the keys, and reject suffix matching keys
602 local function process_key(key)
603     if key:sub(1, 1) == '*' then
604         return nil
605     end
606     if key:sub(#key, #key) == '*' then
607         return key:sub(1, #key - 2)
608     end
609     return key
610 end
611
612 -- Process functions for all data types
613 function module.process_node(object)
614     if ENTRANCE_FUNCTION ~= nil then
615         local entrance_info = ENTRANCE_FUNCTION(object)
616         if entrance_info ~= nil then
617             insert_row.place_entrance{
618                 type = entrance_info.entrance,
619                 extratags = entrance_info.extratags,
620                 geometry = object:as_point()
621             }
622         end
623     end
624
625     local function geom_func(o)
626         return o:as_point()
627     end
628
629     module.process_tags(Place.new(object, geom_func))
630 end
631
632 function module.process_way(object)
633
634     local function geom_func(o)
635         local geom = o:as_polygon()
636
637         if geom:is_null() then
638             geom = o:as_linestring()
639             if geom:is_null() or geom:length() > 30 then
640                 return nil
641             end
642         end
643
644         return geom
645     end
646
647     module.process_tags(Place.new(object, geom_func))
648 end
649
650 function module.process_relation(object)
651     if object.tags.type == 'associatedStreet' then
652         local has_street = false
653         for _, member in ipairs(object.members) do
654             -- Streets can only be ways or relations, not nodes
655             if member.role == 'street' and (member.type == 'w' or member.type == 'r') then
656                 has_street = true
657                 break
658             end
659         end
660         if has_street then
661             for _, member in ipairs(object.members) do
662                 -- Only insert streets that are ways or relations
663                 if member.role == 'street' and (member.type == 'w' or member.type == 'r') then
664                     insert_row.place_associated_street{
665                         member_type = member.type:upper(),
666                         member_id = member.ref,
667                         member_role = member.role
668                     }
669                 elseif member.role == 'house' then
670                     insert_row.place_associated_street{
671                         member_type = member.type:upper(),
672                         member_id = member.ref,
673                         member_role = member.role
674                     }
675                 end
676             end
677         end
678     end
679
680     local geom_func = module.RELATION_TYPES[object.tags.type]
681
682     if geom_func ~= nil then
683         module.process_tags(Place.new(object, geom_func))
684     end
685 end
686
687 -- The process functions are used by default by osm2pgsql.
688 if themepark then
689     themepark:add_proc('node', module.process_node)
690     themepark:add_proc('way', module.process_way)
691     themepark:add_proc('relation', module.process_relation)
692 else
693     osm2pgsql.process_node = module.process_node
694     osm2pgsql.process_way = module.process_way
695     osm2pgsql.process_relation = module.process_relation
696 end
697
698 -- Build extratags for a merged row: filter via EXTRATAGS_FILTER,
699 -- remove sibling main tags (they belong in categories, not extratags),
700 -- then add any required extratags (wikipedia, wikidata, etc.).
701 local function build_extratags(place, k, v)
702     local extra = EXTRATAGS_FILTER(place, k, v) or {}
703     for ek, ev in pairs(extra) do
704         local ktable = MAIN_KEYS[ek]
705         if ktable ~= nil then
706             local transform = ktable[ev] or ktable[1]
707             if type(transform) == 'function' then
708                 extra[ek] = nil
709             end
710         end
711     end
712     for tk, tv in pairs(place.object.tags) do
713         if REQUIRED_EXTRATAGS_FILTER(tk, tv) and extra[tk] == nil then
714             extra[tk] = tv
715         end
716     end
717     if next(extra) == nil then return nil end
718     return extra
719 end
720
721 function module.process_tags(o)
722     if next(o.intags) == nil then
723         return  -- shortcut when pre-filtering has removed all tags
724     end
725
726     -- name keys
727     local needs_address_fallback = o:grab_name_parts{groups=NAME_FILTER}
728
729     -- address keys
730     if o:grab_address_parts{groups=ADDRESS_FILTER} > 0 and not needs_address_fallback then
731         needs_address_fallback = true
732     end
733     if o.address.country ~= nil and #o.address.country ~= 2 then
734         o.address['country'] = nil
735     end
736
737     if o.address.interpolation ~= nil and o.address.housenumber == nil
738             and o.object.type == 'way' and o.object.nodes ~= nil then
739         local extra_addr = nil
740         for k, v in pairs(o.address) do
741             if k ~= 'interpolation' then
742                 if extra_addr == nil then
743                     extra_addr = {}
744                 end
745                 extra_addr[k] = v
746             end
747         end
748
749         insert_row.place_interpolation{
750             type = o.address.interpolation,
751             address = extra_addr,
752             nodes = '{' .. table.concat(o.object.nodes, ',') .. '}',
753             geometry = o.object:as_linestring()
754         }
755     end
756
757     -- Collect categories from main keys into a single row.
758     local categories = {}
759     local main_class, main_type = nil, nil
760     local merged_extratags = nil
761     local postcode_collect = false
762     local tag_fallback = nil
763
764     for k, v in pairs(o.intags) do
765         local ktable = MAIN_KEYS[k]
766         if ktable then
767             local ktype = ktable[v] or ktable[1]
768             if type(ktype) == 'function' then
769                 local result = ktype(o, k, v)
770                 if result then
771                     -- If transform returned a clone (lock_transform, etc.),
772                     -- use its names for the final row
773                     if result ~= o then
774                         o.names = result.names
775                     end
776
777                     -- Collect category
778                     local cat = get_category(k, v)
779                     if cat ~= nil then
780                         table.insert(categories, cat)
781
782                         -- TODO: alphabetical winner selection is a temporary heuristic.
783                         -- Later we will choose by rankability (e.g. avoid boundary on non-area ways)
784                         -- or by explicit user/config priority or idk.
785                         if main_class == nil
786                            or k < main_class
787                            or (k == main_class and v < main_type) then
788                             main_class = k
789                             main_type = v
790                         end
791                     end
792                 end
793             elseif ktype == 'postcode_area' then
794                 postcode_collect = true
795                 if o.object.type == 'relation'
796                         and o.address.postcode ~= nil
797                         and o:geometry_is_valid() then
798                     insert_row.place_postcode{
799                         postcode = o.address.postcode,
800                         centroid = o.geometry:centroid(),
801                         geometry = o.geometry
802                     }
803                 end
804             elseif ktype == 'fallback' and o.has_name then
805                 tag_fallback = {k, v}
806             end
807         end
808     end
809
810     -- Build extratags once after the loop (filters out main keys automatically)
811     merged_extratags = build_extratags(o, nil, nil)
812
813     -- Handle tag-based fallback: always add category, set class/type only if sole producer
814     if tag_fallback ~= nil then
815         local fk, fv = tag_fallback[1], tag_fallback[2]
816         local was_empty = (#categories == 0)
817         local cat = get_category(fk, fv)
818         if cat ~= nil then
819             table.insert(categories, cat)
820             if was_empty then
821                 main_class = fk
822                 main_type = fv
823             end
824         end
825     end
826
827     -- Handle address/house fallback if no main tags produced categories
828     if #categories == 0 then
829         if needs_address_fallback then
830             if next(o.names) ~= nil and NAMES.house ~= nil then
831                 local names = {}
832                 for k, v in pairs(o.names) do
833                     if NAME_FILTER(k, v) == 'house' then
834                         names[k] = v
835                     end
836                 end
837                 o.names = names
838             end
839
840             table.insert(categories, 'osm.place.house')
841             main_class = 'place'
842             main_type = 'house'
843         elseif POSTCODE_FALLBACK and not postcode_collect
844                 and o.address.postcode ~= nil
845                 and o:geometry_is_valid() then
846             insert_row.place_postcode{
847                 postcode = o.address.postcode,
848                 centroid = o.geometry:centroid()
849             }
850         end
851     end
852
853     -- Build and insert single row with all collected categories
854     if #categories > 0 and o:geometry_is_valid() then
855         insert_row.place{
856             class = main_class,
857             type = main_type,
858             admin_level = o.admin_level,
859             name = next(o.names) and o.names,
860             address = next(o.address) and o.address,
861             extratags = merged_extratags and next(merged_extratags) and merged_extratags,
862             geometry = o.geometry,
863             categories = '{' .. table.concat(categories, ',') .. '}'
864         }
865     end
866 end
867
868 --------- Extratags post-processing functions ---------------
869
870 local function default_extratags_filter(p, k)
871     return p.extratags
872 end
873
874 EXTRATAGS_FILTER = default_extratags_filter
875 REQUIRED_EXTRATAGS_FILTER = module.tag_match(PRESETS.EXTRATAGS)
876
877 --------- Convenience functions for simple style configuration -----------------
878
879 function module.set_prefilters(data)
880     remove_group_from_main('delete')
881     merge_filters_into_main('delete', data.delete_keys, data.delete_tags)
882
883     remove_group_from_main('extra')
884     merge_filters_into_main('extra', data.extra_keys, data.extra_tags)
885
886     PRE_FILTER = {prefix = {}, suffix = {}}
887     add_pre_filter{delete = data.delete_keys, extra = data.extra_keys}
888 end
889
890
891 function module.ignore_keys(data)
892     if type(data) == 'string' then
893         local preset = data
894         data = PRESETS.IGNORE_KEYS[data]
895         if data == nil then
896             error('Unknown preset for ignored keys: ' .. preset)
897         end
898     end
899     merge_filters_into_main('delete', data)
900     add_pre_filter{delete = data}
901 end
902
903
904 function module.add_for_extratags(data)
905     if type(data) == 'string' then
906         local preset = data
907         data = PRESETS.IGNORE_KEYS[data]
908         if data == nil then
909             error('Unknown preset for extratags: ' .. preset)
910         end
911     end
912     merge_filters_into_main('extra', data)
913     add_pre_filter{extra = data}
914 end
915
916
917 function module.set_main_tags(data)
918     for key, values in pairs(MAIN_KEYS) do
919         for _, ttype in pairs(values) do
920             if ttype == 'fallback' or type(ttype) == 'function' then
921                 values[ttype] = nil
922             end
923         end
924         if next(values) == nil then
925             MAIN_KEYS[key] = nil
926         end
927     end
928     module.modify_main_tags(data)
929 end
930
931
932 function module.modify_main_tags(data)
933     if type(data) == 'string' then
934         local preset = data
935         if data:sub(1, 7) == 'street/' then
936             data = PRESETS.MAIN_TAGS_STREETS[data:sub(8)]
937         elseif data:sub(1, 4) == 'poi/' then
938             data = PRESETS.MAIN_TAGS_POIS(data:sub(5))
939         else
940             data = PRESETS.MAIN_TAGS[data]
941         end
942         if data == nil then
943             error('Unknown preset for main tags: ' .. preset)
944         end
945     end
946
947     for k, v in pairs(data) do
948         if MAIN_KEYS[k] == nil then
949             MAIN_KEYS[k] = {}
950         end
951         if type(v) == 'function' then
952             MAIN_KEYS[k][1] = v
953         elseif type(v) == 'string' then
954             MAIN_KEYS[k][1] = PlaceTransform[v]
955         elseif type(v) == 'table' then
956             for subk, subv in pairs(v) do
957                 if type(subv) == 'function' then
958                     MAIN_KEYS[k][subk] = subv
959                 else
960                     MAIN_KEYS[k][subk] = PlaceTransform[subv]
961                 end
962             end
963         end
964     end
965 end
966
967
968 function module.modify_name_tags(data)
969     if type(data) == 'string' then
970         local preset = data
971         data = PRESETS.NAME_TAGS[data]
972         if data == nil then
973             error('Unknown preset for name keys: ' .. preset)
974         end
975     end
976
977     for k,v in pairs(data) do
978         if next(v) then
979             NAMES[k] = v
980         else
981             NAMES[k] = nil
982         end
983     end
984     NAME_FILTER = module.tag_group(NAMES)
985     remove_group_from_main('fallback:name')
986     if data.house ~= nil then
987         merge_filters_into_main('fallback:name', data.house)
988     end
989 end
990
991
992 function module.set_name_tags(data)
993     NAMES = {}
994     module.modify_name_tags(data)
995 end
996
997
998 function module.set_address_tags(data)
999     ADDRESS_TAGS = {}
1000     module.modify_address_tags(data)
1001 end
1002
1003
1004 function module.modify_address_tags(data)
1005     if type(data) == 'string' then
1006         local preset = data
1007         data = PRESETS.ADDRESS_TAGS[data]
1008         if data == nil then
1009             error('Unknown preset for address keys: ' .. preset)
1010         end
1011     end
1012
1013     for k, v in pairs(data) do
1014         if k == 'postcode_fallback' then
1015             POSTCODE_FALLBACK = v
1016         elseif next(v) == nil then
1017             ADDRESS_TAGS[k] = nil
1018         else
1019             ADDRESS_TAGS[k] = v
1020         end
1021     end
1022
1023     ADDRESS_FILTER = module.tag_group(ADDRESS_TAGS)
1024
1025     remove_group_from_main('fallback:address')
1026     merge_filters_into_main('fallback:address', data.main)
1027     merge_filters_into_main('fallback:address', data.interpolation)
1028     remove_group_from_main('fallback:postcode')
1029     if POSTCODE_FALLBACK then
1030         merge_filters_into_main('fallback:postcode', data.postcode)
1031     end
1032 end
1033
1034
1035 function module.set_address_tags(data)
1036     ADDRESS_TAGS_SOURCE = {}
1037     module.modify_address_tags(data)
1038 end
1039
1040
1041 function module.set_postcode_fallback(enable)
1042     if POSTCODE_FALLBACK ~= enable then
1043         remove_group_from_main('fallback:postcode')
1044         if enable then
1045             merge_filters_into_main('fallback:postcode', ADDRESS_TAGS.postcode)
1046         end
1047     end
1048     POSTCODE_FALLBACK = enable
1049 end
1050
1051
1052 function module.set_unused_handling(data)
1053     if type(data) == 'function' then
1054         EXTRATAGS_FILTER = data
1055     elseif data == nil then
1056         EXTRATAGS_FILTER = default_extratags_filter
1057     elseif data.extra_keys == nil and data.extra_tags == nil then
1058         local delfilter = module.tag_match{keys = data.delete_keys, tags = data.delete_tags}
1059         EXTRATAGS_FILTER = function (p, k)
1060             local extra = {}
1061             for kin, vin in pairs(p.intags) do
1062                 if kin ~= k and not delfilter(kin, vin) then
1063                     extra[kin] = vin
1064                 end
1065             end
1066             if next(extra) == nil then
1067                 return p.extratags
1068             end
1069             for kextra, vextra in pairs(p.extratags) do
1070                 extra[kextra] = vextra
1071             end
1072             return extra
1073         end
1074     elseif data.delete_keys == nil and data.delete_tags == nil then
1075         local incfilter = module.tag_match{keys = data.extra_keys, tags = data.extra_tags}
1076         EXTRATAGS_FILTER = function (p, k)
1077             local extra = {}
1078             for kin, vin in pairs(p.intags) do
1079                 if kin ~= k and incfilter(kin, vin) then
1080                     extra[kin] = vin
1081                 end
1082             end
1083             if next(extra) == nil then
1084                 return p.extratags
1085             end
1086             for kextra, vextra in pairs(p.extratags) do
1087                 extra[kextra] = vextra
1088             end
1089             return extra
1090         end
1091     else
1092         error("unused handler can have only 'extra_keys' or 'delete_keys' set.")
1093     end
1094 end
1095
1096 function module.set_relation_types(data)
1097     module.RELATION_TYPES = {}
1098     for k, v in data do
1099         if v == 'multipolygon' then
1100             module.RELATION_TYPES[k] = module.relation_as_multipolygon
1101         elseif v == 'multiline' then
1102             module.RELATION_TYPES[k] = module.relation_as_multiline
1103         end
1104     end
1105 end
1106
1107 function module.set_entrance_filter(data)
1108     if data == nil or type(data) == 'function' then
1109         ENTRANCE_FUNCTION = data
1110         return nil
1111     end
1112
1113     if type(data) == 'string' then
1114         local preset = data
1115         data = PRESETS.ENTRANCE_TABLE[data]
1116         if data == nil then
1117             error('Unknown preset for entrance table: ' .. preset)
1118         end
1119     end
1120
1121     ENTRANCE_FUNCTION = nil
1122
1123     if data.main_tags ~= nil and next(data.main_tags) ~= nil then
1124         if data.extra_include ~= nil and next(data.extra_include) == nil then
1125             -- shortcut: no extra tags requested
1126             ENTRANCE_FUNCTION = function(o)
1127                 for _, v in ipairs(data.main_tags) do
1128                     if o.tags[v] ~= nil then
1129                         return {entrance = o.tags[v]}
1130                     end
1131                 end
1132                 return nil
1133             end
1134         else
1135             if data.extra_include ~= nil then
1136                 local tags = {}
1137                 for _, v in pairs(data.extra_include) do
1138                     tags[v] = true
1139                 end
1140                 if data.extra_exclude ~= nil then
1141                     for _, v in pairs(data.extra_exclude) do
1142                         tags[v] = nil
1143                     end
1144                 end
1145                 for _, v in pairs(data.main_tags) do
1146                     tags[v] = nil
1147                 end
1148
1149                 ENTRANCE_FUNCTION = function(o)
1150                     for _, v in ipairs(data.main_tags) do
1151                         if o.tags[v] ~= nil then
1152                             local entrance = o.tags[v]
1153                             local extra = {}
1154                             for k, v in pairs(tags) do
1155                                 extra[k] = o.tags[k]
1156                             end
1157                             if next(extra) == nil then
1158                                 extra = nil
1159                             end
1160                             return {entrance = entrance, extratags = extra}
1161                         end
1162                     end
1163
1164                     return nil
1165                 end
1166             else
1167                 local notags = {}
1168                 if data.extra_exclude ~= nil then
1169                     for _, v in pairs(data.extra_exclude) do
1170                         notags[v] = 1
1171                     end
1172                 end
1173                 for _, v in pairs(data.main_tags) do
1174                     notags[v] = 1
1175                 end
1176
1177                 ENTRANCE_FUNCTION = function(o)
1178                     for _, v in ipairs(data.main_tags) do
1179                         if o.tags[v] ~= nil then
1180                             local entrance = o.tags[v]
1181                             local extra = {}
1182                             for k, v in pairs(o.tags) do
1183                                 if notags[k] ~= 1 then
1184                                     extra[k] = v
1185                                 end
1186                             end
1187                             if next(extra) == nil then
1188                                 extra = nil
1189                             end
1190                             return {entrance = entrance, extratags = extra}
1191                         end
1192                     end
1193
1194                     return nil
1195                 end
1196             end
1197         end
1198     end
1199 end
1200
1201 function module.get_taginfo()
1202     return {main = MAIN_KEYS, name = NAMES, address = ADDRESS_TAGS}
1203 end
1204
1205 return module