c00ea56026037e9c15ea57c04d8c85f06342cc1c
[philipp/winterrodeln/wrpylib.git] / wrpylib / wrmwmarkup.py
1 #!/usr/bin/python2.7
2 # -*- coding: iso-8859-15 -*-
3 # $Id$
4 # $HeadURL$
5 """This module contains winterrodeln specific functions that are processing the MediaWiki markup.
6 """
7 import re
8 import xml.etree.ElementTree
9 import collections
10 import formencode
11 import wrpylib.wrvalidators
12 import wrpylib.mwmarkup
13
14 WRMAP_POINT_TYPES = ['gasthaus', 'haltestelle', 'parkplatz', 'achtung', 'foto', 'verleih', 'punkt']
15 WRMAP_LINE_TYPES = ['rodelbahn', 'gehweg', 'alternative', 'lift', 'anfahrt', 'linie']
16
17
18 class ParseError(RuntimeError):
19     """Exception used by some of the functions"""
20     pass
21
22
23 class RodelbahnboxDictConverter(formencode.Validator):
24     """Converts a dict with Rodelbahnbox properties to a Sledrun class. Does no validation."""
25
26     def to_python(self, value, state=None):
27         """value is a dict of properties. If state is an object with the attribute sledrun, this sledrun class will be populated or updated."""
28         props = value
29         if isinstance(state, object) and hasattr(state, 'sledrun'):
30             sledrun = state.sledrun
31         else:
32             class Sledrun(object):
33                 pass
34             sledrun = Sledrun()
35         for k, v in props.iteritems():
36             if   k == u'Position': sledrun.position_latitude, sledrun.position_longitude = v
37             elif k == u'Position oben': sledrun.top_latitude, sledrun.top_longitude = v
38             elif k == u'Höhe oben': sledrun.top_elevation = v
39             elif k == u'Position unten': sledrun.bottom_latitude, sledrun.bottom_longitude = v
40             elif k == u'Höhe unten': sledrun.bottom_elevation = v
41             elif k == u'Länge': sledrun.length = v
42             elif k == u'Schwierigkeit': sledrun.difficulty = v
43             elif k == u'Lawinen': sledrun.avalanches = v
44             elif k == u'Betreiber': sledrun.operator = v
45             elif k == u'Öffentliche Anreise': sledrun.public_transport = v
46             elif k == u'Aufstieg möglich': sledrun.walkup_possible = v
47             elif k == u'Aufstieg getrennt': sledrun.walkup_separate, sledrun.walkup_separate_comment = v
48             elif k == u'Gehzeit': sledrun.walkup_time = v
49             elif k == u'Aufstiegshilfe': sledrun.lift, sledrun.lift_details = v
50             elif k == u'Beleuchtungsanlage': sledrun.night_light, sledrun.night_light_comment = v
51             elif k == u'Beleuchtungstage': sledrun.night_light_days, sledrun.night_light_days_comment = v
52             elif k == u'Rodelverleih': sledrun.sled_rental, sledrun.sled_rental_comment = v
53             elif k == u'Gütesiegel': sledrun.cachet = v
54             elif k == u'Webauskunft': sledrun.information_web = v
55             elif k == u'Telefonauskunft': sledrun.information_phone = v
56             elif k == u'Bild': sledrun.image = v
57             elif k == u'In Übersichtskarte': sledrun.show_in_overview = v
58             elif k == u'Forumid': sledrun.forum_id = v
59         return sledrun
60
61     def from_python(self, value, state=None):
62         """Converts a sledrun class to a dict of Rodelbahnbox properties. value is a sledrun instance."""
63         sledrun = value
64         r = collections.OrderedDict()
65         r[u'Position'] = (sledrun.position_latitude, sledrun.position_longitude)
66         r[u'Position oben'] = (sledrun.top_latitude, sledrun.top_longitude)
67         r[u'Höhe oben'] = sledrun.top_elevation
68         r[u'Position unten'] = (sledrun.bottom_latitude, sledrun.bottom_longitude)
69         r[u'Höhe unten'] = sledrun.bottom_elevation
70         r[u'Länge'] = sledrun.length
71         r[u'Schwierigkeit'] = sledrun.difficulty
72         r[u'Lawinen'] = sledrun.avalanches
73         r[u'Betreiber'] = sledrun.operator
74         r[u'Öffentliche Anreise'] = sledrun.public_transport
75         r[u'Aufstieg möglich'] = sledrun.walkup_possible
76         r[u'Aufstieg getrennt'] = (sledrun.walkup_separate, sledrun.walkup_separate_comment)
77         r[u'Gehzeit'] = sledrun.walkup_time
78         r[u'Aufstiegshilfe'] = (sledrun.lift, sledrun.lift_details)
79         r[u'Beleuchtungsanlage'] = (sledrun.night_light, sledrun.night_light_comment)
80         r[u'Beleuchtungstage'] = (sledrun.night_light_days, sledrun.night_light_days_comment)
81         r[u'Rodelverleih'] = (sledrun.sled_rental, sledrun.sled_rental_comment)
82         r[u'Gütesiegel'] = sledrun.cachet
83         r[u'Webauskunft'] = sledrun.information_web
84         r[u'Telefonauskunft'] = sledrun.information_phone
85         r[u'Bild'] = sledrun.image
86         r[u'In Übersichtskarte'] = sledrun.show_in_overview
87         r[u'Forumid'] = sledrun.forum_id
88         return r
89
90
91 class WinterrodelnTemplateDict(formencode.Validator):
92     """Private helper class for RodelbahnboxValidator or GasthausboxValidator"""
93     def __init__(self, template_title):
94         self.template_title = template_title
95
96     def to_python(self, value, state):
97         title, anonym_params, named_params = value
98         if title != self.template_title:
99             raise Invalud(u'Template title has to be "{}".'.format(self.template_title), value, state)
100         if len(anonym_params) > 0:
101             raise Invalid(u'No anonymous parameters are allowed in "{}".'.format(self.template_title), value, state)
102         return named_params
103
104     def from_python(self, value, state):
105         return self.template_title, [], value
106
107
108 class RodelbahnboxValidator(wrpylib.wrvalidators.RodelbahnboxDictValidator):
109     def __init__(self):
110         wrpylib.wrvalidators.RodelbahnboxDictValidator.__init__(self)
111         self.pre_validators=[wrpylib.mwmarkup.TemplateValidator(as_table=True, as_table_keylen=20), WinterrodelnTemplateDict(u'Rodelbahnbox')]
112         self.chained_validators = [RodelbahnboxDictConverter()]
113
114
115 def rodelbahnbox_to_sledrun(wikitext, sledrun=None):
116     """Converts a sledrun wiki page containing the {{Rodelbahnbox}}
117     to a sledrun. sledrun may be an instance of WrSledrunCache or an "empty" class (object()) (default).
118     Raises a formencode.Invalid exception if the format is not OK or the Rodelbahnbox is not found.
119     :return: (start, end, sledrun) tuple of the Rodelbahnbox."""
120     # find Rodelbahnbox
121     start, end = wrpylib.mwmarkup.find_template(wikitext, u'Rodelbahnbox')
122     if start is None: raise formencode.Invalid(u"Rodelbahnbox nicht gefunden", wikitext, None)
123
124     # convert to sledrun
125     if sledrun is None:
126         state = None
127     else:
128         class State(object):
129             pass
130         state = State()
131         state.sledrun = sledrun
132     return start, end, RodelbahnboxValidator().to_python(wikitext[start:end], state)
133
134
135 def sledrun_to_rodelbahnbox(sledrun, version=None):
136     """Converts a sledrun class to the {{Rodelbahnbox}} representation.
137     The sledrun class has to have properties like position_latitude, ...
138     See the table sledruncache for field (column) values.
139     :param sledrun: an arbitrary class that contains the right properties
140     :param version: a string specifying the version of the rodelbahnbox zu produce.
141         Version '1.4' is supported."""
142     assert version in [None, '1.4']
143     return RodelbahnboxValidator().from_python(sledrun)
144
145
146 class GasthausboxDictConverter(formencode.Validator):
147     """Converts a dict with Gasthausbox properties to a Inn class. Does no validation."""
148
149     def to_python(self, value, state=None):
150         """value is a dict of properties. If state is an object with the attribute inn, this inn class will be populated or updated."""
151         props = value
152         if isinstance(state, object) and hasattr(state, 'inn'):
153             sledrun = state.sledrun
154         else:
155             class Inn(object):
156                 pass
157             inn = Inn()
158         for k, v in props.iteritems():
159             if   k == u'Position': inn.position_latitude, inn.position_longitude = v
160             elif k == u'Höhe': inn.position_elevation = v
161             elif k == u'Betreiber': inn.operator = v
162             elif k == u'Sitzplätze': inn.seats = v
163             elif k == u'Übernachtung': inn.overnight, inn.overnight_comment = v
164             elif k == u'Rauchfrei': inn.nonsmoker_area, inn.smoker_area = v
165             elif k == u'Rodelverleih': inn.sled_rental, inn.sled_rental_comment = v
166             elif k == u'Handyempfang': inn.mobile_provider = v
167             elif k == u'Homepage': inn.homepage = v
168             elif k == u'E-Mail': inn.email_list = v
169             elif k == u'Telefon': inn.phone_list = v
170             elif k == u'Bild': inn.image = v
171             elif k == u'Rodelbahnen': inn.sledding_list = v
172         return inn
173
174     def from_python(self, value, state=None):
175         """Converts an inn class to a dict of Gasthausbox properties. value is an Inn instance."""
176         inn = value
177         r = collections.OrderedDict()
178         r[u'Position'] = (inn.position_latitude, inn.position_longitude)
179         r[u'Höhe'] = inn.position_elevation
180         r[u'Betreiber'] = inn.operator
181         r[u'Sitzplätze'] = inn.seats
182         r[u'Übernachtung'] = (inn.overnight, inn.overnight_comment)
183         r[u'Rauchfrei'] = (inn.nonsmoker_area, inn.smoker_area)
184         r[u'Rodelverleih'] = (inn.sled_rental, inn.sled_rental_comment)
185         r[u'Handyempfang'] = inn.mobile_provider
186         r[u'Homepage'] = inn.homepage
187         r[u'E-Mail'] = inn.email_list
188         r[u'Telefon'] = inn.phone_list
189         r[u'Bild'] = inn.image
190         r[u'Rodelbahnen'] = inn.sledding_list
191         return r
192
193
194 class GasthausboxValidator(wrpylib.wrvalidators.GasthausboxDictValidator):
195     def __init__(self):
196         wrpylib.wrvalidators.GasthausboxDictValidator.__init__(self)
197         self.pre_validators=[wrpylib.mwmarkup.TemplateValidator(as_table=True, as_table_keylen=17), WinterrodelnTemplateDict(u'Gasthausbox')]
198         self.chained_validators = [GasthausboxDictConverter()]
199
200
201 def gasthausbox_to_inn(wikitext, inn=None):
202     """Converts a inn wiki page containing a {{Gasthausbox}} to an inn.
203     inn may be an instance of WrInnCache or an "empty" class (default).
204     raises a formencode.Invalid exception if the format is not OK or the Gasthausbox is not found.
205     :return: (start, end, inn) tuple."""
206     # find Gasthausbox
207     start, end = wrpylib.mwmarkup.find_template(wikitext, u'Gasthausbox')
208     if start is None: raise formencode.Invalid(u"No 'Gasthausbox' found", wikitext, None)
209
210     # convert to inn
211     if inn is None:
212         state = None
213     else:
214         class State(object):
215             pass
216         state = State()
217         state.inn = inn
218     return start, end, GasthausboxValidator().to_python(wikitext[start:end], state)
219
220
221 def inn_to_gasthausbox(inn):
222     """Converts the inn class to the {{Gasthausbox}} representation."""
223     return GasthausboxValidator().from_python(inn)
224
225
226 def find_template_latlon_ele(wikitext, template_title):
227     """Finds the first occurance of the '{{template_title|47.076207 N 11.453553 E|1890}}' template
228     and returns the tuple (start, end, lat, lon, ele) or (None, None, None, None, None) if the
229     template was not found. If the template has no valid format, an exception is thrown."""
230     start, end = wrpylib.mwmarkup.find_template(wikitext, template_title)
231     if start is None: return (None,) * 5
232     title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
233     lat, lon = wrpylib.wrvalidators.GeoNone().to_python(params[u'1'].strip())
234     ele = wrpylib.wrvalidators.UnsignedNone().to_python(params[u'2'].strip())
235     return start, end, lat, lon, ele
236
237
238 def create_template_latlon_ele(template_title, lat, lon, ele):
239     geo = wrpylib.wrvalidators.GeoNone().from_python((lat, lon))
240     if len(geo) == 0: geo = u' '
241     ele = wrpylib.wrvalidators.UnsignedNone().from_python(ele)
242     if len(ele) == 0: ele = u' '
243     return wrpylib.mwmarkup.create_template(template_title, [geo, ele])
244
245
246 def find_template_PositionOben(wikitext):
247     """Same as find_template_latlon_ele with template '{{Position oben|47.076207 N 11.453553 E|1890}}'"""
248     return find_template_latlon_ele(wikitext, u'Position oben')
249
250
251 def create_template_PositionOben(lat, lon, ele):
252     return create_template_latlon_ele(u'Position, oben', lat, lon, ele)
253
254
255 def find_template_PositionUnten(wikitext):
256     """Same as find_template_latlon_ele with template '{{Position unten|47.076207 N 11.453553 E|1890}}'"""
257     return find_template_latlon_ele(wikitext, u'Position unten')
258
259
260 def find_template_unsigned(wikitext, template_title):
261     """Finds the first occurance of the '{{template_title|1890}}' template
262     and returns the tuple (start, end, unsigned_value) or (None, None, None) if the
263     template was not found. If the template has no valid format, an exception is thrown."""
264     start, end = wrpylib.mwmarkup.find_template(wikitext, template_title)
265     if start is None: return (None,) * 3
266     title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
267     unsigned_value = wrpylib.wrvalidators.UnsignedNone().to_python(params[u'1'].strip())
268     return start, end, unsigned_value
269
270
271 def create_template_unsigned(template_title, unsigned):
272     unsigned = wrpylib.wrvalidators.UnsignedNone().from_python(unsigned)
273     if len(unsigned) == 0: unsigned = u' '
274     return wrpylib.mwmarkup.create_template(template_title, [unsigned])
275
276
277 def find_template_Hoehenunterschied(wikitext):
278     """Same as find_template_unsigned with template '{{Höhenunterschied|350}}'"""
279     return find_template_unsigned(wikitext, u'Höhenunterschied')
280
281
282 def create_template_Hoehenunterschied(ele_diff):
283     return create_template_unsigned(u'Höhenunterschied', ele_diff)
284
285
286 def find_template_Bahnlaenge(wikitext):
287     """Same as find_template_unsigned with template '{{Bahnlänge|4500}}'"""
288     return find_template_unsigned(wikitext, u'Bahnlänge')
289
290
291 def create_template_Bahnlaenge(length):
292     return create_template_unsigned(u'Bahnlänge', length)
293
294
295 def find_template_Gehzeit(wikitext):
296     """Same as find_template_unsigned with template '{{Gehzeit|60}}'"""
297     return find_template_unsigned(wikitext, u'Gehzeit')
298
299
300 def create_template_Gehzeit(walkup_time):
301     return create_template_unsigned(u'Gehzeit', walkup_time)
302
303
304 def find_template_Forumlink(wikitext):
305     """Same as find_template_unsigned with template '{{Forumlink|26}}'"""
306     start, end = wrpylib.mwmarkup.find_template(wikitext, u'Forumlink')
307     if start is None: return (None,) * 3
308     title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
309     forumid = params[u'1'].strip()
310     if forumid == u'<nummer einfügen>': unsigned_value = None
311     else: unsigned_value = wrpylib.wrvalidators.UnsignedNone().to_python(forumid)
312     return start, end, unsigned_value
313     # return find_template_unsigned(wikitext, u'Forumlink')
314
315
316 def find_template_Parkplatz(wikitext):
317     """Same as find_template_latlon_ele with template '{{Parkplatz|47.076207 N 11.453553 E|1890}}'"""
318     return find_template_latlon_ele(wikitext, u'Parkplatz')
319
320
321 def find_template_Haltestelle(wikitext):
322     """Finds the first occurance of the '{{Haltestelle|Ortsname|Haltestellenname|47.076207 N 11.453553 E|1890}}' template
323     and returns the tuple (start, end, city, stop, lat, lon, ele) or (None, None, None, None, None, None, None) if the
324     template was not found. If the template has no valid format, an exception is thrown."""
325     start, end = wrpylib.mwmarkup.find_template(wikitext, u'Haltestelle')
326     if start is None: return (None,) * 7
327     title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
328     city = wrpylib.wrvalidators.UnicodeNone().to_python(params[u'1'].strip())
329     stop = wrpylib.wrvalidators.UnicodeNone().to_python(params[u'2'].strip())
330     lat, lon = wrpylib.wrvalidators.GeoNone().to_python(params[u'3'].strip())
331     ele = wrpylib.wrvalidators.UnsignedNone().to_python(params[u'4'].strip())
332     return start, end, city, stop, lat, lon, ele
333
334
335 def find_all_templates(wikitext, find_func):
336     """Returns a list of return values of find_func that searches for a template.
337     Example:
338     >>> find_all_tempaltes(wikitext, find_template_Haltestelle)
339     Returns an empty list if the template was not found at all.
340     """
341     results = []
342     result = find_func(wikitext)
343     start, end = result[:2]
344     while start is not None:
345         results.append(result)
346         result = find_func(wikitext[end:])
347         if result[0] is None:
348             start = None
349         else:
350             start = result[0] + end
351             end  += result[1]
352             result = (start, end) + result[2:]
353     return results
354
355
356 def googlemap_to_wrmap(attributes, coords, paths):
357     """Converts the output of parse_googlemap to the GeoJSON format wrmap uses.
358     :returns: (GeoJSON as nested Python datatypes)
359     """
360     json_features = []
361
362     # point
363     for point in coords:
364         lon, lat, symbol, title = point
365         properties = {'type': 'punkt' if symbol is None else symbol.lower()}
366         if title is not None: properties['name'] = title
367         json_features.append({
368             'type': 'Feature',
369             'geometry': {'type': 'Point', 'coordinates': [lon, lat]},
370             'properties': properties})
371         
372     # path
373     for path in paths:
374         style, entries = path
375         style = style.lower()
376         PATH_TYPES = {u'6#ff014e9a': u'rodelbahn', u'6#ffe98401': u'gehweg', u'6#ff7f7fff': u'alternative', u'3#ff000000': u'lift', u'3#ffe1e100': u'anfahrt'}
377         if PATH_TYPES.has_key(style):
378             properties = {'type': PATH_TYPES[style]}
379         else:
380             properties = {'type': 'line'}
381             properties['dicke'] = style[0]
382             properties['farbe'] = style[4:]
383         json_features.append({
384             'type': 'Feature',
385             'geometry': {
386                 'type': 'LineString',
387                 'coordinates': [[lon, lat] for lon, lat, symbol, title in entries]},
388             'properties': properties})
389
390     geojson = {
391             'type': 'FeatureCollection',
392             'features': json_features,
393             'properties': attributes}
394     return geojson
395
396
397 def parse_wrmap_coordinates(coords):
398     '''gets a string coordinates and returns an array of lon/lat coordinate pairs, e.g.
399     47.12 N 11.87 E
400     47.13 N 11.70 E
401     ->
402     [[11.87, 47.12], [11.70, 47.13]]'''
403     result = []
404     pos = 0
405     for match in re.finditer(r'\s*(\d+\.?\d*)\s*N?\s+(\d+\.?\d*)\s*E?\s*', coords):
406         if match.start() != pos:
407             break
408         result.append([float(match.groups()[1]), float(match.groups()[0])])
409         pos = match.end()
410     else:
411         if pos == len(coords):
412             return result
413     raise RuntimeError('Wrong coordinate format: {}'.format(coords))
414
415
416 def parse_wrmap(wikitext):
417     """Parses the (unicode) u'<wrmap ...>content</wrmap>' of the Winterrodeln wrmap extension.
418     If wikitext does not contain the <wrmap> tag or if the <wrmap> tag contains 
419     invalid formatted lines, a ParseError is raised.
420     Use wrpylib.mwmarkup.find_tag(wikitext, 'wrmap') to find the wrmap tag within an arbitrary
421     wikitext before using this function.
422
423     :param wikitext: wikitext containing only the template. Example:
424
425     wikitext = u'''
426     <wrmap lat="47.2417134" lon="11.21408895" zoom="14" width="700" height="400">
427     <gasthaus name="Rosskogelhütte" wiki="Rosskogelhütte">47.240689 11.190454</gasthaus>
428     <parkplatz>47.245789 11.238971</parkplatz>
429     <haltestelle name="Oberperfuss Rangger Köpfl Lift">47.245711 11.238283</haltestelle>
430     <rodelbahn>
431         47.238587 11.203360
432         47.244951 11.230868
433         47.245470 11.237853
434     </rodelbahn>
435     </wrmap>
436     '''
437     :returns: GeoJSON as nested Python datatype
438     """
439     # parse XML
440     try:
441         wrmap_xml = xml.etree.ElementTree.fromstring(wikitext.encode('utf-8'))
442     except xml.etree.ElementTree.ParseError as e:
443         row, column = e.position
444         raise ParseError("XML parse error on row {}, column {}: {}".format(row, column, e))
445     if wrmap_xml.tag not in ['wrmap', 'wrgmap']:
446         raise ParseError('No valid tag name')
447
448     # convert XML to geojson (http://www.geojson.org/geojson-spec.html)
449     json_features = []
450     for feature in wrmap_xml:
451         # determine feature type
452         is_point = feature.tag in WRMAP_POINT_TYPES
453         is_line = feature.tag in WRMAP_LINE_TYPES
454         if (not is_point and not is_line):
455             raise ParseError('Unknown element <{}>.'.format(feature.tag))
456
457         # point
458         if is_point:
459             properties = {'type': feature.tag}
460             allowed_properties = set(['name', 'wiki'])
461             wrong_properties = set(feature.attrib.keys()) - allowed_properties
462             if len(wrong_properties) > 0:
463                 raise ParseError("The attribute '{}' is not allowed at <{}>.".format(list(wrong_properties)[0], feature.tag))
464             properties.update(feature.attrib)
465             coordinates = parse_wrmap_coordinates(feature.text)
466             if len(coordinates) != 1:
467                 raise ParseError('The element <{}> has to have exactly one coordinate pair.'.format(feature.tag))
468             json_features.append({
469                 'type': 'Feature',
470                 'geometry': {'type': 'Point', 'coordinates': coordinates[0]},
471                 'properties': properties})
472
473         # line
474         if is_line:
475             properties = {'type': feature.tag}
476             allowed_properties = set(['farbe', 'dicke'])
477             wrong_properties = set(feature.attrib.keys()) - allowed_properties
478             if len(wrong_properties) > 0:
479                 raise ParseError("The attribute '{}' is not allowed at <{}>.".format(list(wrong_properties)[0], feature.tag))
480             if feature.attrib.has_key('farbe'): 
481                 if not re.match('#[0-9a-fA-F]{6}$', feature.attrib['farbe']):
482                     raise ParseError('The attribute "farbe" has to have a format like "#a0bb43".')
483                 properties['strokeColor'] = feature.attrib['farbe'] # e.g. #a200b7
484             if feature.attrib.has_key('dicke'):
485                 try:
486                     properties['strokeWidth'] = int(feature.attrib['dicke']) # e.g. 6
487                 except ValueError:
488                     raise ParseError('The attribute "dicke" has to be an integer.')
489             json_features.append({
490                 'type': 'Feature',
491                 'geometry': {'type': 'LineString', 'coordinates': parse_wrmap_coordinates(feature.text)},
492                 'properties': properties})
493
494     # attributes
495     properties = {}
496     for k, v in wrmap_xml.attrib.iteritems():
497         if k in ['lat', 'lon']:
498             try:
499                 properties[k] = float(v)
500             except ValueError:
501                 raise ParseError('Attribute "{}" has to be a float value.'.format(k))
502         elif k in ['zoom', 'width', 'height']:
503             try:
504                 properties[k] = int(v)
505             except ValueError:
506                 raise ParseError('Attribute "{}" has to be an integer value.'.format(k))
507         else:
508             raise ParseError('Unknown attribute "{}".'.format(k))
509
510     geojson = {
511         'type': 'FeatureCollection',
512         'features': json_features,
513         'properties': properties}
514
515     return geojson
516
517
518 def create_wrmap_coordinates(coords):
519     result = []
520     for coord in coords:
521         result.append('{:.6f} N {:.6f} E'.format(coord[1], coord[0]))
522     return '\n'.join(result)
523  
524
525 def create_wrmap(geojson):
526     """Creates a <wrmap> wikitext from geojson (as python types)."""
527     wrmap_xml = xml.etree.ElementTree.Element('wrmap')
528     wrmap_xml.text = '\n\n'
529     for k, v in geojson['properties'].iteritems():
530         if k in ['lon', 'lat']:
531             wrmap_xml.attrib[k] = '{:.6f}'.format(v)
532         else:
533             wrmap_xml.attrib[k] = str(v)
534
535     assert geojson['type'] == 'FeatureCollection'
536     json_features = geojson['features']
537     last_json_feature = None
538     for json_feature in json_features:
539         feature_xml = xml.etree.ElementTree.SubElement(wrmap_xml, json_feature['properties']['type'])
540         geo = json_feature['geometry']
541         if geo['type'] == 'Point':
542             feature_xml.text = create_wrmap_coordinates([geo['coordinates']])
543             if last_json_feature is not None:
544                 last_json_feature.tail = '\n'
545         else:
546             if last_json_feature is not None:
547                 last_json_feature.tail = '\n\n'
548             feature_xml.text = '\n' + create_wrmap_coordinates(geo['coordinates']) + '\n'
549         last_json_feature = feature_xml
550         feature_xml.attrib = json_feature['properties']
551         del feature_xml.attrib['type']
552
553     if last_json_feature is not None:
554         last_json_feature.tail = '\n\n'
555     return xml.etree.ElementTree.tostring(wrmap_xml, encoding='utf-8').decode('utf-8')
556