2 # -*- coding: iso-8859-15 -*-
5 """This module contains winterrodeln specific functions that are prcocessing the MediaWiki markup.
8 import xml.etree.ElementTree
11 import wrpylib.wrvalidators
12 import wrpylib.mwmarkup
14 WRMAP_POINT_TYPES = ['gasthaus', 'haltestelle', 'parkplatz', 'achtung', 'foto', 'verleih', 'punkt']
15 WRMAP_LINE_TYPES = ['rodelbahn', 'gehweg', 'alternative', 'lift', 'anfahrt', 'linie']
18 class ParseError(RuntimeError):
19 """Exception used by some of the functions"""
23 def _conv(fnct, value, fieldname):
25 Like one of the to_xxx functions (e.g. to_bool), but adds the field name to the error message"""
26 try: return fnct(value)
27 except formencode.Invalid as e: raise formencode.Invalid(u"Conversion error in field '%s': %s" % (fieldname, unicode(e)), e.value, e.state)
30 def rodelbahnbox_to_sledrun(wikitext, sledrun=None):
31 """Converts a sledrun wiki page containing the {{Rodelbahnbox}}
32 to a sledrun. sledrun may be an instance of WrSledrunCache or an "empty" class (object()) (default).
33 Raises a formencode.Invalid exception if the format is not OK or the Rodelbahnbox is not found.
34 :return: (start, end, sledrun) tuple of the Rodelbahnbox."""
36 start, end = wrpylib.mwmarkup.find_template(wikitext, u'Rodelbahnbox')
37 if start is None: raise formencode.Invalid(u"Rodelbahnbox nicht gefunden", wikitext, None)
46 state.sledrun = sledrun
47 return start, end, RodelbahnboxValidator().to_python(wikitext[start:end], state)
50 class RodelbahnboxDictConverter(formencode.Validator):
51 """Converts a dict with Rodelbahnbox properties to a Sledrun class. Does no validation."""
53 def to_python(self, value, state=None):
54 """value is a dict of properties. If state is an object with the attribute sledrun, this sledrun class will be populated or updated."""
56 if isinstance(state, object) and hasattr(state, 'sledrun'):
57 sledrun = state.sledrun
59 class Sledrun(object):
62 for k, v in props.iteritems():
63 if k == u'Position': sledrun.position_latitude, sledrun.position_longitude = v
64 elif k == u'Position oben': sledrun.top_latitude, sledrun.top_longitude = v
65 elif k == u'Höhe oben': sledrun.top_elevation = v
66 elif k == u'Position unten': sledrun.bottom_latitude, sledrun.bottom_longitude = v
67 elif k == u'Höhe unten': sledrun.bottom_elevation = v
68 elif k == u'Länge': sledrun.length = v
69 elif k == u'Schwierigkeit': sledrun.difficulty = v
70 elif k == u'Lawinen': sledrun.avalanches = v
71 elif k == u'Betreiber': sledrun.operator = v
72 elif k == u'Öffentliche Anreise': sledrun.public_transport = v
73 elif k == u'Aufstieg möglich': sledrun.walkup_possible = v
74 elif k == u'Aufstieg getrennt': sledrun.walkup_separate, sledrun.walkup_separate_comment = v
75 elif k == u'Gehzeit': sledrun.walkup_time = v
76 elif k == u'Aufstiegshilfe': sledrun.lift, sledrun.lift_details = v
77 elif k == u'Beleuchtungsanlage': sledrun.night_light, sledrun.night_light_comment = v
78 elif k == u'Beleuchtungstage': sledrun.night_light_days, sledrun.night_light_days_comment = v
79 elif k == u'Rodelverleih': sledrun.sled_rental, sledrun.sled_rental_comment = v
80 elif k == u'Gütesiegel': sledrun.cachet = v
81 elif k == u'Webauskunft': sledrun.information_web = v
82 elif k == u'Telefonauskunft': sledrun.information_phone = v
83 elif k == u'Bild': sledrun.image = v
84 elif k == u'In Übersichtskarte': sledrun.show_in_overview = v
85 elif k == u'Forumid': sledrun.forum_id = v
88 def from_python(self, value, state=None):
89 """Converts a sledrun class to a dict of Rodelbahnbox properties. value is a sledrun instance."""
91 r = collections.OrderedDict()
92 r[u'Position'] = (sledrun.position_latitude, sledrun.position_longitude)
93 r[u'Position oben'] = (sledrun.top_latitude, sledrun.top_longitude)
94 r[u'Höhe oben'] = sledrun.top_elevation
95 r[u'Position unten'] = (sledrun.bottom_latitude, sledrun.bottom_longitude)
96 r[u'Höhe unten'] = sledrun.bottom_elevation
97 r[u'Länge'] = sledrun.length
98 r[u'Schwierigkeit'] = sledrun.difficulty
99 r[u'Lawinen'] = sledrun.avalanches
100 r[u'Betreiber'] = sledrun.operator
101 r[u'Öffentliche Anreise'] = sledrun.public_transport
102 r[u'Aufstieg möglich'] = sledrun.walkup_possible
103 r[u'Aufstieg getrennt'] = (sledrun.walkup_separate, sledrun.walkup_separate_comment)
104 r[u'Gehzeit'] = sledrun.walkup_time
105 r[u'Aufstiegshilfe'] = (sledrun.lift, sledrun.lift_details)
106 r[u'Beleuchtungsanlage'] = (sledrun.night_light, sledrun.night_light_comment)
107 r[u'Beleuchtungstage'] = (sledrun.night_light_days, sledrun.night_light_days_comment)
108 r[u'Rodelverleih'] = (sledrun.sled_rental, sledrun.sled_rental_comment)
109 r[u'Gütesiegel'] = sledrun.cachet
110 r[u'Webauskunft'] = sledrun.information_web
111 r[u'Telefonauskunft'] = sledrun.information_phone
112 r[u'Bild'] = sledrun.image
113 r[u'In Übersichtskarte'] = sledrun.show_in_overview
114 r[u'Forumid'] = sledrun.forum_id
118 class RodelbahnboxTemplateDict(formencode.Validator):
119 """Private helper class for RodelbahnboxValidator"""
120 def to_python(self, value, state):
121 title, anonym_params, named_params = value
122 if title != u'Rodelbahnbox':
123 raise Invalud('Template title has to be "Rodelbahnbox".', value, state)
124 if len(anonym_params) > 0:
125 raise Invalid('No anonymous parameters are allowed in "Rodelbahnbox".', value, state)
128 def from_python(self, value, state):
129 return u'Rodelbahnbox', [], value
132 class RodelbahnboxValidator(wrpylib.wrvalidators.RodelbahnboxDictValidator):
134 wrpylib.wrvalidators.RodelbahnboxDictValidator.__init__(self)
135 self.pre_validators=[wrpylib.mwmarkup.TemplateValidator(as_table=True, as_table_keylen=20), RodelbahnboxTemplateDict()]
136 self.chained_validators = [RodelbahnboxDictConverter()]
139 def sledrun_to_rodelbahnbox(sledrun, version=None):
140 """Converts a sledrun class to the {{Rodelbahnbox}} representation.
141 The sledrun class has to have properties like position_latitude, ...
142 See the table sledruncache for field (column) values.
143 :param sledrun: an arbitrary class that contains the right properties
144 :param version: a string specifying the version of the rodelbahnbox zu produce.
145 Version '1.4' is supported."""
146 assert version in [None, '1.4']
147 return RodelbahnboxValidator().from_python(sledrun)
150 def gasthausbox_to_inn(wikitext, inn=None):
151 """Converts a inn wiki page containing a {{Gasthausbox}} to an inn.
152 raises a formencode.Invalid exception if an error occurs.
153 :return: (start, end, inn) tuple."""
155 class Inn(object): pass
159 start, end = wrpylib.mwmarkup.find_template(wikitext, u'Gasthausbox')
160 if start is None: raise formencode.Invalid(u"No 'Gasthausbox' found", wikitext, None)
161 template_title, properties = wrpylib.mwmarkup.split_template(wikitext[start:end])
164 for key, value in properties.iteritems():
165 if key == u'Position': inn.position_latitude, inn.position_longitude = _conv(wrpylib.wrvalidators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
166 elif key == u'Höhe': inn.position_elevation = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key)
167 elif key == u'Betreiber': inn.operator = _conv(wrpylib.wrvalidators.UnicodeNone().to_python, value, key)
168 elif key == u'Sitzplätze': inn.seats = _conv(wrpylib.wrvalidators.UnsignedNone().to_python, value, key)
169 elif key == u'Übernachtung': inn.overnight, inn.overnight_comment = _conv(wrpylib.wrvalidators.BoolUnicodeTupleValidator().to_python, value, key)
170 elif key == u'Rauchfrei': inn.nonsmoker_area, inn.smoker_area = _conv(wrpylib.wrvalidators.GermanTristateTuple().to_python, value, key)
171 elif key == u'Rodelverleih': inn.sled_rental, inn.sled_rental_comment = _conv(wrpylib.wrvalidators.BoolUnicodeTupleValidator().to_python, value, key)
172 elif key == u'Handyempfang': inn.mobile_provider = _conv(wrpylib.wrvalidators.ValueCommentListNeinLoopNone().to_python, value, key)
173 elif key == u'Homepage': inn.homepage = _conv(wrpylib.wrvalidators.UrlNeinNone().to_python, value, key)
174 elif key == u'E-Mail': inn.email_list = _conv(wrpylib.wrvalidators.EmailCommentListNeinLoopNone(allow_masked_email=True).to_python, value, key)
175 elif key == u'Telefon': inn.phone_list = _conv(wrpylib.wrvalidators.PhoneCommentListNeinLoopNone(comments_are_optional=True).to_python, value, key)
176 elif key == u'Bild': inn.image = _conv(wrpylib.wrvalidators.UnicodeNone().to_python, value, key)
177 elif key == u'Rodelbahnen': inn.sledding_list = _conv(wrpylib.wrvalidators.WikiPageListLoopNone().to_python, value, key)
178 else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Gasthausbox: '%s' (mit Wert '%s')" % (key, value), value, None)
179 return start, end, inn
182 def inn_to_gasthausbox(inn):
183 """Converts the inn class to the {{Gasthausbox}} representation."""
186 keys.append(u'Position')
187 values.append(wrpylib.wrvalidators.GeoNone().from_python((inn.position_latitude, inn.position_longitude)))
189 values.append(wrpylib.wrvalidators.UnsignedNone().from_python(inn.position_elevation))
190 keys.append(u'Betreiber')
191 values.append(wrpylib.wrvalidators.UnicodeNone().from_python(inn.operator))
192 keys.append(u'Sitzplätze')
193 values.append(wrpylib.wrvalidators.UnsignedNone().from_python(inn.seats))
194 keys.append(u'Übernachtung')
195 values.append(wrpylib.wrvalidators.BoolUnicodeTupleValidator().from_python((inn.overnight, inn.overnight_comment)))
196 keys.append(u'Rauchfrei')
197 values.append(wrpylib.wrvalidators.GermanTristateTuple().from_python((inn.nonsmoker_area, inn.smoker_area)))
198 keys.append(u'Rodelverleih')
199 values.append(wrpylib.wrvalidators.BoolUnicodeTupleValidator().from_python((inn.sled_rental, inn.sled_rental_comment)))
200 keys.append(u'Handyempfang')
201 values.append(wrpylib.wrvalidators.ValueCommentListNeinLoopNone().from_python(inn.mobile_provider))
202 keys.append(u'Homepage')
203 values.append(wrpylib.wrvalidators.UrlNeinNone().from_python(inn.homepage))
204 keys.append(u'E-Mail')
205 values.append(wrpylib.wrvalidators.EmailCommentListNeinLoopNone(allow_masked_email=True).from_python(inn.email_list))
206 keys.append(u'Telefon')
207 values.append(wrpylib.wrvalidators.PhoneCommentListNeinLoopNone(comments_are_optional=True).from_python(inn.phone_list))
209 values.append(wrpylib.wrvalidators.UnicodeNone().from_python(inn.image))
210 keys.append(u'Rodelbahnen')
211 values.append(wrpylib.wrvalidators.WikiPageListLoopNone().from_python(inn.sledding_list))
212 result = [u'{{Gasthausbox']
213 return wrpylib.mwmarkup.create_template(u'Gasthausbox', [], keys, values, True)
216 def find_template_latlon_ele(wikitext, template_title):
217 """Finds the first occurance of the '{{template_title|47.076207 N 11.453553 E|1890}}' template
218 and returns the tuple (start, end, lat, lon, ele) or (None, None, None, None, None) if the
219 template was not found. If the template has no valid format, an exception is thrown."""
220 start, end = wrpylib.mwmarkup.find_template(wikitext, template_title)
221 if start is None: return (None,) * 5
222 title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
223 lat, lon = wrpylib.wrvalidators.GeoNone().to_python(params[u'1'].strip())
224 ele = wrpylib.wrvalidators.UnsignedNone().to_python(params[u'2'].strip())
225 return start, end, lat, lon, ele
228 def create_template_latlon_ele(template_title, lat, lon, ele):
229 geo = wrpylib.wrvalidators.GeoNone().from_python((lat, lon))
230 if len(geo) == 0: geo = u' '
231 ele = wrpylib.wrvalidators.UnsignedNone().from_python(ele)
232 if len(ele) == 0: ele = u' '
233 return wrpylib.mwmarkup.create_template(template_title, [geo, ele])
236 def find_template_PositionOben(wikitext):
237 """Same as find_template_latlon_ele with template '{{Position oben|47.076207 N 11.453553 E|1890}}'"""
238 return find_template_latlon_ele(wikitext, u'Position oben')
241 def create_template_PositionOben(lat, lon, ele):
242 return create_template_latlon_ele(u'Position, oben', lat, lon, ele)
245 def find_template_PositionUnten(wikitext):
246 """Same as find_template_latlon_ele with template '{{Position unten|47.076207 N 11.453553 E|1890}}'"""
247 return find_template_latlon_ele(wikitext, u'Position unten')
250 def find_template_unsigned(wikitext, template_title):
251 """Finds the first occurance of the '{{template_title|1890}}' template
252 and returns the tuple (start, end, unsigned_value) or (None, None, None) if the
253 template was not found. If the template has no valid format, an exception is thrown."""
254 start, end = wrpylib.mwmarkup.find_template(wikitext, template_title)
255 if start is None: return (None,) * 3
256 title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
257 unsigned_value = wrpylib.wrvalidators.UnsignedNone().to_python(params[u'1'].strip())
258 return start, end, unsigned_value
261 def create_template_unsigned(template_title, unsigned):
262 unsigned = wrpylib.wrvalidators.UnsignedNone().from_python(unsigned)
263 if len(unsigned) == 0: unsigned = u' '
264 return wrpylib.mwmarkup.create_template(template_title, [unsigned])
267 def find_template_Hoehenunterschied(wikitext):
268 """Same as find_template_unsigned with template '{{Höhenunterschied|350}}'"""
269 return find_template_unsigned(wikitext, u'Höhenunterschied')
272 def create_template_Hoehenunterschied(ele_diff):
273 return create_template_unsigned(u'Höhenunterschied', ele_diff)
276 def find_template_Bahnlaenge(wikitext):
277 """Same as find_template_unsigned with template '{{Bahnlänge|4500}}'"""
278 return find_template_unsigned(wikitext, u'Bahnlänge')
281 def create_template_Bahnlaenge(length):
282 return create_template_unsigned(u'Bahnlänge', length)
285 def find_template_Gehzeit(wikitext):
286 """Same as find_template_unsigned with template '{{Gehzeit|60}}'"""
287 return find_template_unsigned(wikitext, u'Gehzeit')
290 def create_template_Gehzeit(walkup_time):
291 return create_template_unsigned(u'Gehzeit', walkup_time)
294 def find_template_Forumlink(wikitext):
295 """Same as find_template_unsigned with template '{{Forumlink|26}}'"""
296 start, end = wrpylib.mwmarkup.find_template(wikitext, u'Forumlink')
297 if start is None: return (None,) * 3
298 title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
299 forumid = params[u'1'].strip()
300 if forumid == u'<nummer einfügen>': unsigned_value = None
301 else: unsigned_value = wrpylib.wrvalidators.UnsignedNone().to_python(forumid)
302 return start, end, unsigned_value
303 # return find_template_unsigned(wikitext, u'Forumlink')
306 def find_template_Parkplatz(wikitext):
307 """Same as find_template_latlon_ele with template '{{Parkplatz|47.076207 N 11.453553 E|1890}}'"""
308 return find_template_latlon_ele(wikitext, u'Parkplatz')
311 def find_template_Haltestelle(wikitext):
312 """Finds the first occurance of the '{{Haltestelle|Ortsname|Haltestellenname|47.076207 N 11.453553 E|1890}}' template
313 and returns the tuple (start, end, city, stop, lat, lon, ele) or (None, None, None, None, None, None, None) if the
314 template was not found. If the template has no valid format, an exception is thrown."""
315 start, end = wrpylib.mwmarkup.find_template(wikitext, u'Haltestelle')
316 if start is None: return (None,) * 7
317 title, params = wrpylib.mwmarkup.split_template(wikitext[start:end])
318 city = wrpylib.wrvalidators.UnicodeNone().to_python(params[u'1'].strip())
319 stop = wrpylib.wrvalidators.UnicodeNone().to_python(params[u'2'].strip())
320 lat, lon = wrpylib.wrvalidators.GeoNone().to_python(params[u'3'].strip())
321 ele = wrpylib.wrvalidators.UnsignedNone().to_python(params[u'4'].strip())
322 return start, end, city, stop, lat, lon, ele
325 def find_all_templates(wikitext, find_func):
326 """Returns a list of return values of find_func that searches for a template.
328 >>> find_all_tempaltes(wikitext, find_template_Haltestelle)
329 Returns an empty list if the template was not found at all.
332 result = find_func(wikitext)
333 start, end = result[:2]
334 while start is not None:
335 results.append(result)
336 result = find_func(wikitext[end:])
337 if result[0] is None:
340 start = result[0] + end
342 result = (start, end) + result[2:]
346 def googlemap_to_wrmap(attributes, coords, paths):
347 """Converts the output of parse_googlemap to the GeoJSON format wrmap uses.
348 :returns: (GeoJSON as nested Python datatypes)
354 lon, lat, symbol, title = point
355 properties = {'type': 'punkt' if symbol is None else symbol.lower()}
356 if title is not None: properties['name'] = title
357 json_features.append({
359 'geometry': {'type': 'Point', 'coordinates': [lon, lat]},
360 'properties': properties})
364 style, entries = path
365 style = style.lower()
366 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'}
367 if PATH_TYPES.has_key(style):
368 properties = {'type': PATH_TYPES[style]}
370 properties = {'type': 'line'}
371 properties['dicke'] = style[0]
372 properties['farbe'] = style[4:]
373 json_features.append({
376 'type': 'LineString',
377 'coordinates': [[lon, lat] for lon, lat, symbol, title in entries]},
378 'properties': properties})
381 'type': 'FeatureCollection',
382 'features': json_features,
383 'properties': attributes}
387 def parse_wrmap_coordinates(coords):
388 '''gets a string coordinates and returns an array of lon/lat coordinate pairs, e.g.
392 [[11.87, 47.12], [11.70, 47.13]]'''
395 for match in re.finditer(r'\s*(\d+\.?\d*)\s*N?\s+(\d+\.?\d*)\s*E?\s*', coords):
396 if match.start() != pos:
398 result.append([float(match.groups()[1]), float(match.groups()[0])])
401 if pos == len(coords):
403 raise RuntimeError('Wrong coordinate format: {}'.format(coords))
406 def parse_wrmap(wikitext):
407 """Parses the (unicode) u'<wrmap ...>content</wrmap>' of the Winterrodeln wrmap extension.
408 If wikitext does not contain the <wrmap> tag or if the <wrmap> tag contains
409 invalid formatted lines, a ParseError is raised.
410 Use wrpylib.mwmarkup.find_tag(wikitext, 'wrmap') to find the wrmap tag within an arbitrary
411 wikitext before using this function.
413 :param wikitext: wikitext containing only the template. Example:
416 <wrmap lat="47.2417134" lon="11.21408895" zoom="14" width="700" height="400">
417 <gasthaus name="Rosskogelhütte" wiki="Rosskogelhütte">47.240689 11.190454</gasthaus>
418 <parkplatz>47.245789 11.238971</parkplatz>
419 <haltestelle name="Oberperfuss Rangger Köpfl Lift">47.245711 11.238283</haltestelle>
427 :returns: GeoJSON as nested Python datatype
431 wrmap_xml = xml.etree.ElementTree.fromstring(wikitext.encode('utf-8'))
432 except xml.etree.ElementTree.ParseError as e:
433 row, column = e.position
434 raise ParseError("XML parse error on row {}, column {}: {}".format(row, column, e))
435 if wrmap_xml.tag not in ['wrmap', 'wrgmap']:
436 raise ParseError('No valid tag name')
438 # convert XML to geojson (http://www.geojson.org/geojson-spec.html)
440 for feature in wrmap_xml:
441 # determine feature type
442 is_point = feature.tag in WRMAP_POINT_TYPES
443 is_line = feature.tag in WRMAP_LINE_TYPES
444 if (not is_point and not is_line):
445 raise ParseError('Unknown element <{}>.'.format(feature.tag))
449 properties = {'type': feature.tag}
450 allowed_properties = set(['name', 'wiki'])
451 wrong_properties = set(feature.attrib.keys()) - allowed_properties
452 if len(wrong_properties) > 0:
453 raise ParseError("The attribute '{}' is not allowed at <{}>.".format(list(wrong_properties)[0], feature.tag))
454 properties.update(feature.attrib)
455 coordinates = parse_wrmap_coordinates(feature.text)
456 if len(coordinates) != 1:
457 raise ParseError('The element <{}> has to have exactly one coordinate pair.'.format(feature.tag))
458 json_features.append({
460 'geometry': {'type': 'Point', 'coordinates': coordinates[0]},
461 'properties': properties})
465 properties = {'type': feature.tag}
466 allowed_properties = set(['farbe', 'dicke'])
467 wrong_properties = set(feature.attrib.keys()) - allowed_properties
468 if len(wrong_properties) > 0:
469 raise ParseError("The attribute '{}' is not allowed at <{}>.".format(list(wrong_properties)[0], feature.tag))
470 if feature.attrib.has_key('farbe'):
471 if not re.match('#[0-9a-fA-F]{6}$', feature.attrib['farbe']):
472 raise ParseError('The attribute "farbe" has to have a format like "#a0bb43".')
473 properties['strokeColor'] = feature.attrib['farbe'] # e.g. #a200b7
474 if feature.attrib.has_key('dicke'):
476 properties['strokeWidth'] = int(feature.attrib['dicke']) # e.g. 6
478 raise ParseError('The attribute "dicke" has to be an integer.')
479 json_features.append({
481 'geometry': {'type': 'LineString', 'coordinates': parse_wrmap_coordinates(feature.text)},
482 'properties': properties})
486 for k, v in wrmap_xml.attrib.iteritems():
487 if k in ['lat', 'lon']:
489 properties[k] = float(v)
491 raise ParseError('Attribute "{}" has to be a float value.'.format(k))
492 elif k in ['zoom', 'width', 'height']:
494 properties[k] = int(v)
496 raise ParseError('Attribute "{}" has to be an integer value.'.format(k))
498 raise ParseError('Unknown attribute "{}".'.format(k))
501 'type': 'FeatureCollection',
502 'features': json_features,
503 'properties': properties}
508 def create_wrmap_coordinates(coords):
511 result.append('{:.6f} N {:.6f} E'.format(coord[1], coord[0]))
512 return '\n'.join(result)
515 def create_wrmap(geojson):
516 """Creates a <wrmap> wikitext from geojson (as python types)."""
517 wrmap_xml = xml.etree.ElementTree.Element('wrmap')
518 wrmap_xml.text = '\n\n'
519 for k, v in geojson['properties'].iteritems():
520 if k in ['lon', 'lat']:
521 wrmap_xml.attrib[k] = '{:.6f}'.format(v)
523 wrmap_xml.attrib[k] = str(v)
525 assert geojson['type'] == 'FeatureCollection'
526 json_features = geojson['features']
527 last_json_feature = None
528 for json_feature in json_features:
529 feature_xml = xml.etree.ElementTree.SubElement(wrmap_xml, json_feature['properties']['type'])
530 geo = json_feature['geometry']
531 if geo['type'] == 'Point':
532 feature_xml.text = create_wrmap_coordinates([geo['coordinates']])
533 if last_json_feature is not None:
534 last_json_feature.tail = '\n'
536 if last_json_feature is not None:
537 last_json_feature.tail = '\n\n'
538 feature_xml.text = '\n' + create_wrmap_coordinates(geo['coordinates']) + '\n'
539 last_json_feature = feature_xml
540 feature_xml.attrib = json_feature['properties']
541 del feature_xml.attrib['type']
543 if last_json_feature is not None:
544 last_json_feature.tail = '\n\n'
545 return xml.etree.ElementTree.tostring(wrmap_xml, encoding='utf-8').decode('utf-8')