]> git.openstreetmap.org Git - nominatim.git/blob - lib-lua/themes/nominatim/init.lua
convert import styles to themepark
[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 POSTCODE_FALLBACK = true
33
34 -- This file can also be directly require'd instead of running it under
35 -- the themepark framework. In that case the first parameter is usually
36 -- the module name. Lets check for that, so that further down we can call
37 -- the low-level osm2pgsql functions instead of themepark functions.
38 local themepark = ...
39 if type(themepark) ~= 'table' then
40     themepark = nil
41 end
42
43 -- tables required for taginfo
44 module.TAGINFO_MAIN = {keys = {}, delete_tags = {}}
45 module.TAGINFO_NAME_KEYS = {}
46 module.TAGINFO_ADDRESS_KEYS = {}
47
48
49 -- The single place table.
50 local place_table_definition = {
51     name = "place",
52     ids = { type = 'any', id_column = 'osm_id', type_column = 'osm_type' },
53     columns = {
54         { column = 'class', type = 'text', not_null = true },
55         { column = 'type', type = 'text', not_null = true },
56         { column = 'admin_level', type = 'smallint' },
57         { column = 'name', type = 'hstore' },
58         { column = 'address', type = 'hstore' },
59         { column = 'extratags', type = 'hstore' },
60         { column = 'geometry', type = 'geometry', projection = 'WGS84', not_null = true },
61     },
62     data_tablespace = os.getenv("NOMINATIM_TABLESPACE_PLACE_DATA"),
63     index_tablespace = os.getenv("NOMINATIM_TABLESPACE_PLACE_INDEX"),
64     indexes = {}
65 }
66
67 local insert_row
68 local script_path = debug.getinfo(1, "S").source:match("@?(.*/)")
69 local PRESETS = loadfile(script_path .. 'presets.lua')()
70
71 if themepark then
72     themepark:add_table(place_table_definition)
73     insert_row = function(columns)
74         themepark:insert('place', columns, {}, {})
75     end
76 else
77     local place_table = osm2pgsql.define_table(place_table_definition)
78     insert_row = function(columns)
79         place_table:insert(columns)
80     end
81 end
82
83 ------------ Geometry functions for relations ---------------------
84
85 function module.relation_as_multipolygon(o)
86     return o:as_multipolygon()
87 end
88
89 function module.relation_as_multiline(o)
90     return o:as_multilinestring():line_merge()
91 end
92
93
94 module.RELATION_TYPES = {
95     multipolygon = module.relation_as_multipolygon,
96     boundary = module.relation_as_multipolygon,
97     waterway = module.relation_as_multiline
98 }
99
100 --------- Built-in place transformation functions --------------------------
101
102 local PlaceTransform = {}
103
104 -- Special transform meanings which are interpreted elsewhere
105 PlaceTransform.fallback = 'fallback'
106 PlaceTransform.delete = 'delete'
107 PlaceTransform.extra = 'extra'
108
109 -- always: unconditionally use that place
110 function PlaceTransform.always(place)
111     return place
112 end
113
114 -- never: unconditionally drop the place
115 function PlaceTransform.never()
116     return nil
117 end
118
119 -- named: use the place if it has a fully-qualified name
120 function PlaceTransform.named(place)
121     if place.has_name then
122         return place
123     end
124 end
125
126 -- named_with_key: use place if there is a name with the main key prefix
127 function PlaceTransform.named_with_key(place, k)
128     local names = {}
129     local prefix = k .. ':name'
130     for namek, namev in pairs(place.intags) do
131         if namek:sub(1, #prefix) == prefix
132            and (#namek == #prefix
133                 or namek:sub(#prefix + 1, #prefix + 1) == ':') then
134             names[namek:sub(#k + 2)] = namev
135         end
136     end
137
138     if next(names) ~= nil then
139         return place:clone{names=names}
140     end
141 end
142
143 --------- Built-in extratags transformation functions ---------------
144
145 local function default_extratags_filter(p, k)
146     -- Default handling is to copy over place tag for boundaries.
147     -- Nominatim needs this.
148     if k ~= 'boundary' or p.intags.place == nil then
149         return p.extratags
150     end
151
152     local extra = { place = p.intags.place }
153     for kin, vin in pairs(p.extratags) do
154         extra[kin] = vin
155     end
156
157     return extra
158 end
159 EXTRATAGS_FILTER = default_extratags_filter
160
161 ----------------- other helper functions -----------------------------
162
163 local function lookup_prefilter_classification(k, v)
164     -- full matches
165     local desc = MAIN_KEYS[k]
166     local fullmatch = desc and (desc[v] or desc[1])
167     if fullmatch ~= nil then
168         return fullmatch
169     end
170     -- suffixes
171     for slen, slist in pairs(PRE_FILTER.suffix) do
172         if #k >= slen then
173             local group = slist[k:sub(-slen)]
174             if group ~= nil then
175                 return group
176             end
177         end
178     end
179     -- prefixes
180     for slen, slist in pairs(PRE_FILTER.prefix) do
181         if #k >= slen then
182             local group = slist[k:sub(1, slen)]
183             if group ~= nil then
184                 return group
185             end
186         end
187     end
188 end
189
190
191 local function merge_filters_into_main(group, keys, tags)
192     if keys ~= nil then
193         for _, key in pairs(keys) do
194             -- ignore suffix and prefix matches
195             if key:sub(1, 1) ~= '*' and key:sub(#key, #key) ~= '*' then
196                 if MAIN_KEYS[key] == nil then
197                     MAIN_KEYS[key] = {}
198                 end
199                 MAIN_KEYS[key][1] = group
200             end
201         end
202     end
203
204     if tags ~= nil then
205         for key, values in pairs(tags) do
206             if MAIN_KEYS[key] == nil then
207                 MAIN_KEYS[key] = {}
208             end
209             for _, v in pairs(values) do
210                 MAIN_KEYS[key][v] = group
211             end
212         end
213     end
214 end
215
216
217 local function remove_group_from_main(group)
218     for key, values in pairs(MAIN_KEYS) do
219         for _, ttype in pairs(values) do
220             if ttype == group then
221                 values[ttype] = nil
222             end
223         end
224         if next(values) == nil then
225             MAIN_KEYS[key] = nil
226         end
227     end
228 end
229
230
231 local function add_pre_filter(data)
232     for group, keys in pairs(data) do
233         for _, key in pairs(keys) do
234             local klen = #key - 1
235             if key:sub(1, 1) == '*' then
236                 if klen > 0 then
237                     if PRE_FILTER.suffix[klen] == nil then
238                         PRE_FILTER.suffix[klen] = {}
239                     end
240                     PRE_FILTER.suffix[klen][key:sub(2)] = group
241                 end
242             elseif key:sub(#key, #key) == '*' then
243                 if PRE_FILTER.prefix[klen] == nil then
244                     PRE_FILTER.prefix[klen] = {}
245                 end
246                 PRE_FILTER.prefix[klen][key:sub(1, klen)] = group
247             end
248         end
249     end
250 end
251
252 ------------- Place class ------------------------------------------
253
254 local Place = {}
255 Place.__index = Place
256
257 function Place.new(object, geom_func)
258     local self = setmetatable({}, Place)
259     self.object = object
260     self.geom_func = geom_func
261
262     self.admin_level = tonumber(self.object.tags.admin_level or 15) or 15
263     if self.admin_level == nil
264        or self.admin_level <= 0 or self.admin_level > 15
265        or math.floor(self.admin_level) ~= self.admin_level then
266         self.admin_level = 15
267     end
268
269     self.num_entries = 0
270     self.has_name = false
271     self.names = {}
272     self.address = {}
273     self.extratags = {}
274
275     self.intags = {}
276
277     local has_main_tags = false
278     for k, v in pairs(self.object.tags) do
279         local group = lookup_prefilter_classification(k, v)
280         if group == 'extra' then
281             self.extratags[k] = v
282         elseif group ~= 'delete' then
283             self.intags[k] = v
284             if group ~= nil then
285                 has_main_tags = true
286             end
287         end
288     end
289
290     if not has_main_tags then
291         -- no interesting tags, don't bother processing
292         self.intags = {}
293     end
294
295     return self
296 end
297
298 function Place:clean(data)
299     for k, v in pairs(self.intags) do
300         if data.delete ~= nil and data.delete(k, v) then
301             self.intags[k] = nil
302         elseif data.extra ~= nil and data.extra(k, v) then
303             self.extratags[k] = v
304             self.intags[k] = nil
305         end
306     end
307 end
308
309 function Place:delete(data)
310     if data.match ~= nil then
311         for k, v in pairs(self.intags) do
312             if data.match(k, v) then
313                 self.intags[k] = nil
314             end
315         end
316     end
317 end
318
319 function Place:grab_extratags(data)
320     local count = 0
321
322     if data.match ~= nil then
323         for k, v in pairs(self.intags) do
324             if data.match(k, v) then
325                 self.intags[k] = nil
326                 self.extratags[k] = v
327                 count = count + 1
328             end
329         end
330     end
331
332     return count
333 end
334
335 local function strip_address_prefix(k)
336     if k:sub(1, 5) == 'addr:' then
337         return k:sub(6)
338     end
339
340     if k:sub(1, 6) == 'is_in:' then
341         return k:sub(7)
342     end
343
344     return k
345 end
346
347
348 function Place:grab_address_parts(data)
349     local count = 0
350
351     if data.groups ~= nil then
352         for k, v in pairs(self.intags) do
353             local atype = data.groups(k, v)
354
355             if atype ~= nil then
356                 if atype == 'main' then
357                     self.has_name = true
358                     self.address[strip_address_prefix(k)] = v
359                     count = count + 1
360                 elseif atype == 'extra' then
361                     self.address[strip_address_prefix(k)] = v
362                 else
363                     self.address[atype] = v
364                 end
365                 self.intags[k] = nil
366             end
367         end
368     end
369
370     return count
371 end
372
373
374 function Place:grab_name_parts(data)
375     local fallback = nil
376
377     if data.groups ~= nil then
378         for k, v in pairs(self.intags) do
379             local atype = data.groups(k, v)
380
381             if atype ~= nil then
382                 self.names[k] = v
383                 self.intags[k] = nil
384                 if atype == 'main' then
385                     self.has_name = true
386                 elseif atype == 'house' then
387                     self.has_name = true
388                     fallback = {'place', 'house', PlaceTransform.always}
389                 end
390             end
391         end
392     end
393
394     return fallback
395 end
396
397
398 function Place:write_place(k, v, mfunc)
399     v = v or self.intags[k]
400     if v == nil then
401         return 0
402     end
403
404     local place = mfunc(self, k, v)
405     if place then
406         local res = place:write_row(k, v)
407         self.num_entries = self.num_entries + res
408         return res
409     end
410
411     return 0
412 end
413
414 function Place:write_row(k, v)
415     if self.geometry == nil then
416         self.geometry = self.geom_func(self.object)
417     end
418     if self.geometry:is_null() then
419         return 0
420     end
421
422     local extratags = EXTRATAGS_FILTER(self, k, v)
423     if not (extratags and next(extratags)) then
424         extratags = nil
425     end
426
427     insert_row{
428         class = k,
429         type = v,
430         admin_level = self.admin_level,
431         name = next(self.names) and self.names,
432         address = next(self.address) and self.address,
433         extratags = extratags,
434         geometry = self.geometry
435     }
436
437     return 1
438 end
439
440
441 function Place:clone(data)
442     local cp = setmetatable({}, Place)
443     cp.object = self.object
444     cp.geometry = data.geometry or self.geometry
445     cp.geom_func = self.geom_func
446     cp.intags = data.intags or self.intags
447     cp.admin_level = data.admin_level or self.admin_level
448     cp.names = data.names or self.names
449     cp.address = data.address or self.address
450     cp.extratags = data.extratags or self.extratags
451
452     return cp
453 end
454
455
456 function module.tag_match(data)
457     if data == nil or next(data) == nil then
458         return nil
459     end
460
461     local fullmatches = {}
462     local key_prefixes = {}
463     local key_suffixes = {}
464
465     if data.keys ~= nil then
466         for _, key in pairs(data.keys) do
467             if key:sub(1, 1) == '*' then
468                 if #key > 1 then
469                     if key_suffixes[#key - 1] == nil then
470                         key_suffixes[#key - 1] = {}
471                     end
472                     key_suffixes[#key - 1][key:sub(2)] = true
473                 end
474             elseif key:sub(#key, #key) == '*' then
475                 if key_prefixes[#key - 1] == nil then
476                     key_prefixes[#key - 1] = {}
477                 end
478                 key_prefixes[#key - 1][key:sub(1, #key - 1)] = true
479             else
480                 fullmatches[key] = true
481             end
482         end
483     end
484
485     if data.tags ~= nil then
486         for k, vlist in pairs(data.tags) do
487             if fullmatches[k] == nil then
488                 fullmatches[k] = {}
489                 for _, v in pairs(vlist) do
490                     fullmatches[k][v] = true
491                 end
492             end
493         end
494     end
495
496     return function (k, v)
497         if fullmatches[k] ~= nil and (fullmatches[k] == true or fullmatches[k][v] ~= nil) then
498             return true
499         end
500
501         for slen, slist in pairs(key_suffixes) do
502             if #k >= slen and slist[k:sub(-slen)] ~= nil then
503                 return true
504             end
505         end
506
507         for slen, slist in pairs(key_prefixes) do
508             if #k >= slen and slist[k:sub(1, slen)] ~= nil then
509                 return true
510             end
511         end
512
513         return false
514     end
515 end
516
517
518 function module.tag_group(data)
519     if data == nil or next(data) == nil then
520         return nil
521     end
522
523     local fullmatches = {}
524     local key_prefixes = {}
525     local key_suffixes = {}
526
527     for group, tags in pairs(data) do
528         for _, key in pairs(tags) do
529             if key:sub(1, 1) == '*' then
530                 if #key > 1 then
531                     if key_suffixes[#key - 1] == nil then
532                         key_suffixes[#key - 1] = {}
533                     end
534                     key_suffixes[#key - 1][key:sub(2)] = group
535                 end
536             elseif key:sub(#key, #key) == '*' then
537                 if key_prefixes[#key - 1] == nil then
538                     key_prefixes[#key - 1] = {}
539                 end
540                 key_prefixes[#key - 1][key:sub(1, #key - 1)] = group
541             else
542                 fullmatches[key] = group
543             end
544         end
545     end
546
547     return function (k)
548         local val = fullmatches[k]
549         if val ~= nil then
550             return val
551         end
552
553         for slen, slist in pairs(key_suffixes) do
554             if #k >= slen then
555                 val = slist[k:sub(-slen)]
556                 if val ~= nil then
557                     return val
558                 end
559             end
560         end
561
562         for slen, slist in pairs(key_prefixes) do
563             if #k >= slen then
564                 val = slist[k:sub(1, slen)]
565                 if val ~= nil then
566                     return val
567                 end
568             end
569         end
570     end
571 end
572
573 -- Returns prefix part of the keys, and reject suffix matching keys
574 local function process_key(key)
575     if key:sub(1, 1) == '*' then
576         return nil
577     end
578     if key:sub(#key, #key) == '*' then
579         return key:sub(1, #key - 2)
580     end
581     return key
582 end
583
584 -- Process functions for all data types
585 function module.process_node(object)
586
587     local function geom_func(o)
588         return o:as_point()
589     end
590
591     module.process_tags(Place.new(object, geom_func))
592 end
593
594 function module.process_way(object)
595
596     local function geom_func(o)
597         local geom = o:as_polygon()
598
599         if geom:is_null() then
600             geom = o:as_linestring()
601         end
602
603         return geom
604     end
605
606     module.process_tags(Place.new(object, geom_func))
607 end
608
609 function module.process_relation(object)
610     local geom_func = module.RELATION_TYPES[object.tags.type]
611
612     if geom_func ~= nil then
613         module.process_tags(Place.new(object, geom_func))
614     end
615 end
616
617 -- The process functions are used by default by osm2pgsql.
618 if themepark then
619     themepark:add_proc('node', module.process_node)
620     themepark:add_proc('way', module.process_way)
621     themepark:add_proc('relation', module.process_relation)
622 else
623     osm2pgsql.process_node = module.process_node
624     osm2pgsql.process_way = module.process_way
625     osm2pgsql.process_relation = module.process_relation
626 end
627
628 function module.process_tags(o)
629     if next(o.intags) == nil then
630         return  -- shortcut when pre-filtering has removed all tags
631     end
632
633     -- Exception for boundary/place double tagging
634     if o.intags.boundary == 'administrative' then
635         o:grab_extratags{match = function (k, v)
636             return k == 'place' and v:sub(1,3) ~= 'isl'
637         end}
638     end
639
640     -- name keys
641     local fallback = o:grab_name_parts{groups=NAME_FILTER}
642
643     -- address keys
644     if o:grab_address_parts{groups=ADDRESS_FILTER} > 0 and fallback == nil then
645         fallback = {'place', 'house', PlaceTransform.always}
646     end
647     if o.address.country ~= nil and #o.address.country ~= 2 then
648         o.address['country'] = nil
649     end
650     if POSTCODE_FALLBACK and fallback == nil and o.address.postcode ~= nil then
651         fallback = {'place', 'postcode', PlaceTransform.always}
652     end
653
654     if o.address.interpolation ~= nil then
655         o:write_place('place', 'houses', PlaceTransform.always)
656         return
657     end
658
659     -- collect main keys
660     for k, v in pairs(o.intags) do
661         local ktable = MAIN_KEYS[k]
662         if ktable then
663             local ktype = ktable[v] or ktable[1]
664             if type(ktype) == 'function' then
665                 o:write_place(k, v, ktype)
666             elseif ktype == 'fallback' and o.has_name then
667                 fallback = {k, v, PlaceTransform.named}
668             end
669         end
670     end
671
672     if fallback ~= nil and o.num_entries == 0 then
673         o:write_place(fallback[1], fallback[2], fallback[3])
674     end
675 end
676
677 --------- Convenience functions for simple style configuration -----------------
678
679 function module.set_prefilters(data)
680     remove_group_from_main('delete')
681     merge_filters_into_main('delete', data.delete_keys, data.delete_tags)
682
683     remove_group_from_main('extra')
684     merge_filters_into_main('extra', data.extra_keys, data.extra_tags)
685
686     PRE_FILTER = {prefix = {}, suffix = {}}
687     add_pre_filter{delete = data.delete_keys, extra = data.extra_keys}
688 end
689
690
691 function module.ignore_keys(data)
692     if type(data) == 'string' then
693         local preset = data
694         data = PRESETS.IGNORE_KEYS[data]
695         if data == nil then
696             error('Unknown preset for ignored keys: ' .. preset)
697         end
698     end
699     merge_filters_into_main('delete', data)
700     add_pre_filter{delete = data}
701 end
702
703
704 function module.add_for_extratags(data)
705     if type(data) == 'string' then
706         local preset = data
707         data = PRESETS.EXTRATAGS[data] or PRESETS.IGNORE_KEYS[data]
708         if data == nil then
709             error('Unknown preset for extratags: ' .. preset)
710         end
711     end
712     merge_filters_into_main('extra', data)
713     add_pre_filter{extra = data}
714 end
715
716
717 function module.set_main_tags(data)
718     for key, values in pairs(MAIN_KEYS) do
719         for _, ttype in pairs(values) do
720             if ttype == 'fallback' or type(ttype) == 'function' then
721                 values[ttype] = nil
722             end
723         end
724         if next(values) == nil then
725             MAIN_KEYS[key] = nil
726         end
727     end
728     module.modify_main_tags(data)
729 end
730
731
732 function module.modify_main_tags(data)
733     if type(data) == 'string' then
734         local preset = data
735         if data:sub(1, 7) == 'street/' then
736             data = PRESETS.MAIN_TAGS_STREETS[data:sub(8)]
737         elseif data:sub(1, 4) == 'poi/' then
738             data = PRESETS.MAIN_TAGS_POIS(data:sub(5))
739         else
740             data = PRESETS.MAIN_TAGS[data]
741         end
742         if data == nil then
743             error('Unknown preset for main tags: ' .. preset)
744         end
745     end
746
747     for k, v in pairs(data) do
748         if MAIN_KEYS[k] == nil then
749             MAIN_KEYS[k] = {}
750         end
751         if type(v) == 'function' then
752             MAIN_KEYS[k][1] = v
753         elseif type(v) == 'string' then
754             MAIN_KEYS[k][1] = PlaceTransform[v]
755         elseif type(v) == 'table' then
756             for subk, subv in pairs(v) do
757                 if type(subv) == 'function' then
758                     MAIN_KEYS[k][subk] = subv
759                 else
760                     MAIN_KEYS[k][subk] = PlaceTransform[subv]
761                 end
762             end
763         end
764     end
765 end
766
767
768 function module.modify_name_tags(data)
769     if type(data) == 'string' then
770         local preset = data
771         data = PRESETS.NAME_TAGS[data]
772         if data == nil then
773             error('Unknown preset for name keys: ' .. preset)
774         end
775     end
776
777     for k,v in pairs(data) do
778         if next(v) then
779             NAMES[k] = v
780         else
781             NAMES[k] = nil
782         end
783     end
784     NAME_FILTER = module.tag_group(NAMES)
785     remove_group_from_main('fallback:name')
786     if data.house ~= nil then
787         merge_filters_into_main('fallback:name', data.house)
788     end
789 end
790
791
792 function module.set_name_tags(data)
793     NAMES = {}
794     module.modify_name_tags(data)
795 end
796
797
798 function module.set_address_tags(data)
799     ADDRESS_TAGS = {}
800     module.modify_address_tags(data)
801 end
802
803
804 function module.modify_address_tags(data)
805     if type(data) == 'string' then
806         local preset = data
807         data = PRESETS.ADDRESS_TAGS[data]
808         if data == nil then
809             error('Unknown preset for address keys: ' .. preset)
810         end
811     end
812
813     for k, v in pairs(data) do
814         if k == 'postcode_fallback' then
815             POSTCODE_FALLBACK = v
816         elseif next(v) == nil then
817             ADDRESS_TAGS[k] = nil
818         else
819             ADDRESS_TAGS[k] = v
820         end
821     end
822
823     ADDRESS_FILTER = module.tag_group(ADDRESS_TAGS)
824
825     remove_group_from_main('fallback:address')
826     merge_filters_into_main('fallback:address', data.main)
827     merge_filters_into_main('fallback:address', data.interpolation)
828     remove_group_from_main('fallback:postcode')
829     if POSTCODE_FALLBACK then
830         merge_filters_into_main('fallback:postcode', data.postcode)
831     end
832 end
833
834
835 function module.set_address_tags(data)
836     ADDRESS_TAGS_SOURCE = {}
837     module.modify_address_tags(data)
838 end
839
840
841 function module.set_postcode_fallback(enable)
842     if POSTCODE_FALLBACK ~= enable then
843         remove_group_from_main('fallback:postcode')
844         if enable then
845             merge_filters_into_main('fallback:postcode', ADDRESS_TAGS.postcode)
846         end
847     end
848     POSTCODE_FALLBACK = enable
849 end
850
851
852 function module.set_unused_handling(data)
853     if type(data) == 'function' then
854         EXTRATAGS_FILTER = data
855     elseif data == nil then
856         EXTRATAGS_FILTER = default_extratags_filter
857     elseif data.extra_keys == nil and data.extra_tags == nil then
858         local delfilter = module.tag_match{keys = data.delete_keys, tags = data.delete_tags}
859         EXTRATAGS_FILTER = function (p, k)
860             local extra = {}
861             for kin, vin in pairs(p.intags) do
862                 if kin ~= k and not delfilter(kin, vin) then
863                     extra[kin] = vin
864                 end
865             end
866             if next(extra) == nil then
867                 return p.extratags
868             end
869             for kextra, vextra in pairs(p.extratags) do
870                 extra[kextra] = vextra
871             end
872             return extra
873         end
874     elseif data.delete_keys == nil and data.delete_tags == nil then
875         local incfilter = module.tag_match{keys = data.extra_keys, tags = data.extra_tags}
876         EXTRATAGS_FILTER = function (p, k)
877             local extra = {}
878             for kin, vin in pairs(p.intags) do
879                 if kin ~= k and incfilter(kin, vin) then
880                     extra[kin] = vin
881                 end
882             end
883             if next(extra) == nil then
884                 return p.extratags
885             end
886             for kextra, vextra in pairs(p.extratags) do
887                 extra[kextra] = vextra
888             end
889             return extra
890         end
891     else
892         error("unused handler can have only 'extra_keys' or 'delete_keys' set.")
893     end
894 end
895
896 function module.set_relation_types(data)
897     module.RELATION_TYPES = {}
898     for k, v in data do
899         if v == 'multipolygon' then
900             module.RELATION_TYPES[k] = module.relation_as_multipolygon
901         elseif v == 'multiline' then
902             module.RELATION_TYPES[k] = module.relation_as_multiline
903         end
904     end
905 end
906
907 return module