imported OsmApi 0.2.7 from svn.openstreetmap.org
[osm-ro-tools.git] / OsmApi.py
blob3a6c4ea8c297f7ac372001dcca7140512c07682a
1 #-*- coding: utf-8 -*-
3 ###########################################################################
4 ## ##
5 ## Copyrights Etienne Chové <chove@crans.org> 2009 ##
6 ## ##
7 ## This program is free software: you can redistribute it and/or modify ##
8 ## it under the terms of the GNU General Public License as published by ##
9 ## the Free Software Foundation, either version 3 of the License, or ##
10 ## (at your option) any later version. ##
11 ## ##
12 ## This program is distributed in the hope that it will be useful, ##
13 ## but WITHOUT ANY WARRANTY; without even the implied warranty of ##
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ##
15 ## GNU General Public License for more details. ##
16 ## ##
17 ## You should have received a copy of the GNU General Public License ##
18 ## along with this program. If not, see <http://www.gnu.org/licenses/>. ##
19 ## ##
20 ###########################################################################
22 ## HomePage : http://wiki.openstreetmap.org/wiki/PythonOsmApi
24 ###########################################################################
25 ## History ##
26 ###########################################################################
27 ## 0.2.7 2009-10-09 implement all missing fonctions except ##
28 ## ChangesetsGet and GetCapabilities ##
29 ## 0.2.6 2009-10-09 encoding clean-up ##
30 ## 0.2.5 2009-10-09 implements NodesGet, WaysGet, RelationsGet ##
31 ## ParseOsm, ParseOsc ##
32 ## 0.2.4 2009-10-06 clean-up ##
33 ## 0.2.3 2009-09-09 keep http connection alive for multiple request ##
34 ## (Node|Way|Relation)Get return None when object ##
35 ## have been deleted (raising error before) ##
36 ## 0.2.2 2009-07-13 can identify applications built on top of the lib ##
37 ## 0.2.1 2009-05-05 some changes in constructor -- chove@crans.org ##
38 ## 0.2 2009-05-01 initial import ##
39 ###########################################################################
41 __version__ = '0.2.7'
43 import httplib, base64, xml.dom.minidom, time
45 ###########################################################################
46 ## Main class ##
48 class OsmApi:
50 def __init__(self, username = None, password = None, passwordfile = None, appid = "", created_by = "PythonOsmApi/"+__version__, api = "www.openstreetmap.org"):
52 # Get username
53 if username:
54 self._username = username
55 elif passwordfile:
56 self._username = open(passwordfile).readline().split(":")[0].strip()
58 # Get password
59 if password:
60 self._password = password
61 elif passwordfile:
62 for l in open(passwordfile).readlines():
63 l = l.strip().split(":")
64 if l[0] == self._username:
65 self._password = l[1]
67 # Get API
68 self._api = api
70 # Get created_by
71 if appid == "":
72 self._created_by = created_by
73 else:
74 self._created_by = appid + " (" + created_by + ")"
76 # Initialisation
77 self._CurrentChangesetId = -1
79 # Http connection
80 self._conn = httplib.HTTPConnection(self._api, 80)
82 #######################################################################
83 # Capabilities #
84 #######################################################################
86 def Capabilities(self):
87 raise NotImplemented
89 #######################################################################
90 # Node #
91 #######################################################################
93 def NodeGet(self, NodeId, NodeVersion = -1):
94 """ Returns NodeData for node #NodeId. """
95 uri = "/api/0.6/node/"+str(NodeId)
96 if NodeVersion <> -1: uri += "/"+str(NodeVersion)
97 data = self._get(uri)
98 if not data: return data
99 data = xml.dom.minidom.parseString(data)
100 data = data.getElementsByTagName("osm")[0].getElementsByTagName("node")[0]
101 return self._DomParseNode(data)
103 def NodeUpdate(self, NodeData):
104 """ Updates node with NodeData. Returns updated NodeData (without timestamp). """
105 if self._CurrentChangesetId == -1:
106 raise Exception, "No changeset currently opened"
107 NodeData[u"changeset"] = self._CurrentChangesetId
108 result = self._put("/api/0.6/node/"+str(NodeData[u"id"]), self._XmlBuild("node", NodeData))
109 NodeData[u"version"] = int(result.strip())
110 if u"timestamp" in NodeData: NodeData.pop(u"timestamp")
111 return NodeData
113 def NodeDelete(self, NodeData):
114 """ Delete node with NodeData. Returns updated NodeData (without timestamp). """
115 if self._CurrentChangesetId == -1:
116 raise Exception, "No changeset currently opened"
117 NodeData[u"changeset"] = self._CurrentChangesetId
118 result = self._delete("/api/0.6/node/"+str(NodeData[u"id"]), self._XmlBuild("node", NodeData))
119 NodeData[u"version"] = int(result.strip())
120 NodeData[u"visible"] = False
121 if u"timestamp" in NodeData: NodeData.pop(u"timestamp")
122 return NodeData
124 def NodeCreate(self, NodeData):
125 """ Creates a node. Returns updated NodeData (without timestamp). """
126 if self._CurrentChangesetId == -1:
127 raise Exception, "No changeset currently opened"
128 if NodeData.get(u"id", -1) > 0:
129 raise Exception, "This node already exists"
130 NodeData[u"changeset"] = self._CurrentChangesetId
131 result = self._put("/api/0.6/node/create", self._XmlBuild("node", NodeData))
132 NodeData[u"id"] = int(result.strip())
133 NodeData[u"version"] = 1
134 if u"timestamp" in NodeData: NodeData.pop(u"timestamp")
135 return NodeData
137 def NodeHistory(self, NodeId):
138 """ Returns dict(NodeVerrsion: NodeData). """
139 uri = "/api/0.6/node/"+str(NodeId)+"/history"
140 data = self._get(uri)
141 data = xml.dom.minidom.parseString(data)
142 result = {}
143 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("node"):
144 data = self._DomParseNode(data)
145 result[data[u"version"]] = data
146 return result
148 def NodeWays(self, NodeId):
149 """ Returns [WayData, ... ] containing node #NodeId. """
150 uri = "/api/0.6/node/%d/ways"%NodeId
151 data = self._get(uri)
152 data = xml.dom.minidom.parseString(data)
153 result = []
154 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"):
155 data = self._DomParseRelation(data)
156 result.append(data)
157 return result
159 def NodeRelations(self, NodeId):
160 """ Returns [RelationData, ... ] containing node #NodeId. """
161 uri = "/api/0.6/node/%d/relations"%NodeId
162 data = self._get(uri)
163 data = xml.dom.minidom.parseString(data)
164 result = []
165 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
166 data = self._DomParseRelation(data)
167 result.append(data)
168 return result
170 def NodesGet(self, NodeIdList):
171 """ Returns dict(NodeId: NodeData) for each node in NodeIdList """
172 uri = "/api/0.6/nodes?nodes=" + ",".join([str(x) for x in NodeIdList])
173 data = self._get(uri)
174 data = xml.dom.minidom.parseString(data)
175 result = {}
176 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("node"):
177 data = self._DomParseNode(data)
178 result[data[u"id"]] = data
179 return result
181 #######################################################################
182 # Way #
183 #######################################################################
185 def WayGet(self, WayId, WayVersion = -1):
186 """ Returns WayData for way #WayId. """
187 uri = "/api/0.6/way/"+str(WayId)
188 if WayVersion <> -1: uri += "/"+str(WayVersion)
189 data = self._get(uri)
190 if not data: return data
191 data = xml.dom.minidom.parseString(data)
192 data = data.getElementsByTagName("osm")[0].getElementsByTagName("way")[0]
193 return self._DomParseWay(data)
195 def WayUpdate(self, WayData):
196 """ Updates way with WayData. Returns updated WayData (without timestamp). """
197 if self._CurrentChangesetId == -1:
198 raise Exception, "No changeset currently opened"
199 WayData[u"changeset"] = self._CurrentChangesetId
200 result = self._put("/api/0.6/way/"+str(WayData[u"id"]), self._XmlBuild("way", WayData))
201 WayData[u"version"] = int(result.strip())
202 if u"timestamp" in WayData: WayData.pop(u"timestamp")
203 return WayData
205 def WayDelete(self, WayData):
206 """ Delete way with WayData. Returns updated WayData (without timestamp). """
207 if self._CurrentChangesetId == -1:
208 raise Exception, "No changeset currently opened"
209 WayData[u"changeset"] = self._CurrentChangesetId
210 result = self._delete("/api/0.6/way/"+str(WayData[u"id"]), self._XmlBuild("way", WayData))
211 WayData[u"version"] = int(result.strip())
212 WayData[u"visible"] = False
213 if u"timestamp" in WayData: WayData.pop(u"timestamp")
214 return WayData
216 def WayCreate(self, WayData):
217 """ Creates a way. Returns updated WayData (without timestamp). """
218 if self._CurrentChangesetId == -1:
219 raise Exception, "No changeset currently opened"
220 if NodeData.get(u"id", -1) > 0:
221 raise Exception, "This way already exists"
222 WayData[u"changeset"] = self._CurrentChangesetId
223 result = self._put("/api/0.6/way/create", self._XmlBuild("way", WayData))
224 WayData[u"id"] = int(result.strip())
225 WayData[u"version"] = 1
226 if u"timestamp" in WayData: WayData.pop(u"timestamp")
227 return WayData
229 def WayHistory(self, WayId):
230 """ Returns dict(WayVerrsion: WayData). """
231 uri = "/api/0.6/way/"+str(WayId)+"/history"
232 data = self._get(uri)
233 data = xml.dom.minidom.parseString(data)
234 result = {}
235 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"):
236 data = self._DomParseWay(data)
237 result[data[u"version"]] = data
238 return result
240 def WayRelations(self, WayId):
241 """ Returns [RelationData, ...] containing way #WayId. """
242 uri = "/api/0.6/way/%d/relations"%WayId
243 data = self._get(uri)
244 data = xml.dom.minidom.parseString(data)
245 result = []
246 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
247 data = self._DomParseRelation(data)
248 result.append(data)
249 return result
251 def WayFull(self, WayId):
252 """ Return full data for way WayId as list of {type: node|way|relation, data: {}}. """
253 uri = "/api/0.6/way/"+str(WayId)+"/full"
254 data = self._get(uri)
255 return self.ParseOsm(data)
257 def WaysGet(self, WayIdList):
258 """ Returns dict(WayId: WayData) for each way in WayIdList """
259 uri = "/api/0.6/ways?ways=" + ",".join([str(x) for x in WayIdList])
260 data = self._get(uri)
261 data = xml.dom.minidom.parseString(data)
262 result = {}
263 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"):
264 data = self._DomParseWay(data)
265 result[data[u"id"]] = data
266 return result
268 #######################################################################
269 # Relation #
270 #######################################################################
272 def RelationGet(self, RelationId, RelationVersion = -1):
273 """ Returns RelationData for relation #RelationId. """
274 uri = "/api/0.6/relation/"+str(RelationId)
275 if RelationVersion <> -1: uri += "/"+str(RelationVersion)
276 data = self._get(uri)
277 if not data: return data
278 data = xml.dom.minidom.parseString(data)
279 data = data.getElementsByTagName("osm")[0].getElementsByTagName("relation")[0]
280 return self._DomParseRelation(data)
282 def RelationUpdate(self, RelationData):
283 """ Updates relation with RelationData. Returns updated RelationData (without timestamp). """
284 if self._CurrentChangesetId == -1:
285 raise Exception, "No changeset currently opened"
286 RelationData[u"changeset"] = self._CurrentChangesetId
287 result = self._put("/api/0.6/relation/"+str(RelationData[u"id"]), self._XmlBuild("relation", RelationData))
288 RelationData[u"version"] = int(result.strip())
289 if u"timestamp" in RelationData: RelationData.pop(u"timestamp")
290 return RelationData
292 def RelationDelete(self, RelationData):
293 """ Delete relation with RelationData. Returns updated RelationData (without timestamp). """
294 if self._CurrentChangesetId == -1:
295 raise Exception, "No changeset currently opened"
296 RelationData[u"changeset"] = self._CurrentChangesetId
297 result = self._delete("/api/0.6/relation/"+str(RelationData[u"id"]), self._XmlBuild("relation", RelationData))
298 RelationData[u"version"] = int(result.strip())
299 RelationData[u"visible"] = False
300 if u"timestamp" in RelationData: RelationData.pop(u"timestamp")
301 return RelationData
303 def RelationCreate(self, RelationData):
304 """ Creates a relation. Returns updated RelationData (without timestamp). """
305 if self._CurrentChangesetId == -1:
306 raise Exception, "No changeset currently opened"
307 if NodeData.get(u"id", -1) > 0:
308 raise Exception, "This relation already exists"
309 RelationData[u"changeset"] = self._CurrentChangesetId
310 result = self._put("/api/0.6/relation/create", self._XmlBuild("relation", RelationData))
311 RelationData[u"id"] = int(result.strip())
312 RelationData[u"version"] = 1
313 if u"timestamp" in RelationData: RelationData.pop(u"timestamp")
314 return RelationData
316 def RelationHistory(self, RelationId):
317 """ Returns dict(RelationVerrsion: RelationData). """
318 uri = "/api/0.6/relation/"+str(RelationId)+"/history"
319 data = self._get(uri)
320 data = xml.dom.minidom.parseString(data)
321 result = {}
322 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
323 data = self._DomParseRelation(data)
324 result[data[u"version"]] = data
325 return result
327 def RelationRelations(self, RelationId):
328 """ Returns list of RelationData containing relation #RelationId. """
329 uri = "/api/0.6/relation/%d/relations"%RelationId
330 data = self._get(uri)
331 data = xml.dom.minidom.parseString(data)
332 result = []
333 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
334 data = self._DomParseRelation(data)
335 result.append(data)
336 return result
338 def RelationFull(self, RelationId):
339 """ Return full data for way WayId as list of {type: node|way|relation, data: {}}. """
340 uri = "/api/0.6/relation/"+str(RelationId)+"/full"
341 data = self._get(uri)
342 return self.ParseOsm(data)
344 def RelationsGet(self, RelationIdList):
345 """ Returns dict(RelationId: RelationData) for each relation in RelationIdList """
346 uri = "/api/0.6/relations?relations=" + ",".join([str(x) for x in RelationIdList])
347 data = self._get(uri)
348 data = xml.dom.minidom.parseString(data)
349 result = {}
350 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
351 data = self._DomParseRelation(data)
352 result[data[u"id"]] = data
353 return result
355 #######################################################################
356 # Changeset #
357 #######################################################################
359 def ChangesetGet(self, ChangesetId):
360 """ Returns ChangesetData for changeset #ChangesetId. """
361 data = self._get("/api/0.6/changeset/"+str(ChangesetId))
362 data = xml.dom.minidom.parseString(data)
363 data = data.getElementsByTagName("osm")[0].getElementsByTagName("changeset")[0]
364 return self._DomParseChangeset(data)
366 def ChangesetUpdate(self, ChangesetTags = {}):
367 """ Updates current changeset with ChangesetTags. """
368 if self._CurrentChangesetId == -1:
369 raise Exception, "No changeset currently opened"
370 if u"created_by" not in ChangesetTags:
371 ChangesetTags[u"created_by"] = self._created_by
372 result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId), self._XmlBuild("changeset", {u"tag": ChangesetTags}))
373 return self._CurrentChangesetId
375 def ChangesetCreate(self, ChangesetTags = {}):
376 """ Opens a changeset. Returns #ChangesetId. """
377 if self._CurrentChangesetId <> -1:
378 raise Exception, "Changeset alreadey opened"
379 if u"created_by" not in ChangesetTags:
380 ChangesetTags[u"created_by"] = self._created_by
381 result = self._put("/api/0.6/changeset/create", self._XmlBuild("changeset", {u"tag": ChangesetTags}))
382 self._CurrentChangesetId = int(result)
383 self._CurrentChangesetTags = ChangesetTags
384 self._CurrentChangesetCpt = 0
385 return self._CurrentChangesetId
387 def ChangesetClose(self):
388 """ Closes current changeset. Returns #ChangesetId. """
389 if self._CurrentChangesetId == -1:
390 raise Exception, "No changeset currently opened"
391 result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId)+"/close", u"")
392 CurrentChangesetId = self._CurrentChangesetId
393 self._CurrentChangesetId = -1
394 return CurrentChangesetId
396 def ChangesetUpload(self):
397 raise NotImplemented
399 def ChangesetDownload(self, ChangesetId):
400 """ Download data from a changeset. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
401 uri = "/api/0.6/changeset/"+str(ChangesetId)+"/download"
402 data = self._get(uri)
403 return self.ParseOsc(data)
405 def ChangesetsGet(self, min_lon=None, min_lat=None, max_lon=None, max_lat=None, userid=None, closed_after=None, created_before=None, only_open=False, only_closed=False):
406 """ Returns dict(ChangsetId: ChangesetData) matching all criteria. """
407 raise NotImplemented
409 #######################################################################
410 # Other #
411 #######################################################################
413 def Map(self, min_lon, min_lat, max_lon, max_lat):
414 """ Download data in bounding box. Returns list of dict {type: node|way|relation, data: {}}. """
415 uri = "/api/0.6/map?bbox=%f,%f,%f,%f"%(min_lon, min_lat, max_lon, max_lat)
416 data = self._get(uri)
417 return self.ParseOsm(data)
419 #######################################################################
420 # Data parser #
421 #######################################################################
423 def ParseOsm(self, data):
424 """ Parse osm data. Returns list of dict {type: node|way|relation, data: {}}. """
425 data = xml.dom.minidom.parseString(data)
426 data = data.getElementsByTagName("osm")[0]
427 result = []
428 for elem in data.childNodes:
429 if elem.nodeName == u"node":
430 result.append({u"type": elem.nodeName, u"data": self._DomParseNode(elem)})
431 elif elem.nodeName == u"way":
432 result.append({u"type": elem.nodeName, u"data": self._DomParseWay(elem)})
433 elif elem.nodeName == u"relation":
434 result.append({u"type": elem.nodeName, u"data": self._DomParseRelation(elem)})
435 return result
437 def ParseOsc(self, data):
438 """ Parse osc data. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
439 data = xml.dom.minidom.parseString(data)
440 data = data.getElementsByTagName("osmChange")[0]
441 result = []
442 for action in data.childNodes:
443 if action.nodeName == u"#text": continue
444 for elem in action.childNodes:
445 if elem.nodeName == u"node":
446 result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseNode(elem)})
447 elif elem.nodeName == u"way":
448 result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseWay(elem)})
449 elif elem.nodeName == u"relation":
450 result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseRelation(elem)})
451 return result
453 #######################################################################
454 # Internal http function #
455 #######################################################################
457 def _http_request(self, cmd, path, auth, send):
458 self._conn.putrequest(cmd, path)
459 self._conn.putheader('User-Agent', self._created_by)
460 if auth:
461 self._conn.putheader('Authorization', 'Basic ' + base64.encodestring(self._username + ':' + self._password).strip())
462 if send:
463 self._conn.putheader('Content-Length', len(send))
464 self._conn.endheaders()
465 if send:
466 self._conn.send(send)
467 response = self._conn.getresponse()
468 if response.status <> 200:
469 response.read()
470 if response.status == 410:
471 return None
472 raise Exception, "API returns unexpected status code "+str(response.status)+" ("+response.reason+")"
473 return response.read()
475 def _http(self, cmd, path, auth, send):
476 i = 0
477 while True:
478 i += 1
479 try:
480 return self._http_request(cmd, path, auth, send)
481 except:
482 if i == 5: raise
483 if i <> 1: time.sleep(2)
484 self._conn = httplib.HTTPConnection(self._api, 80)
486 def _get(self, path):
487 return self._http('GET', path, False, None)
489 def _put(self, path, data):
490 return self._http('PUT', path, True, data)
492 def _delete(self, path, data):
493 return self._http('DELETE', path, True, data)
495 #######################################################################
496 # Internal dom function #
497 #######################################################################
499 def _DomGetAttributes(self, DomElement):
500 """ Returns a formated dictionnary of attributes of a DomElement. """
501 result = {}
502 for k, v in DomElement.attributes.items():
503 if k == u"uid" : v = int(v)
504 elif k == u"changeset" : v = int(v)
505 elif k == u"version" : v = int(v)
506 elif k == u"id" : v = int(v)
507 elif k == u"lat" : v = float(v)
508 elif k == u"lon" : v = float(v)
509 elif k == u"open" : v = v=="true"
510 elif k == u"visible" : v = v=="true"
511 elif k == u"ref" : v = int(v)
512 result[k] = v
513 return result
515 def _DomGetTag(self, DomElement):
516 """ Returns the dictionnary of tags of a DomElement. """
517 result = {}
518 for t in DomElement.getElementsByTagName("tag"):
519 k = t.attributes["k"].value
520 v = t.attributes["v"].value
521 result[k] = v
522 return result
524 def _DomGetNd(self, DomElement):
525 """ Returns the list of nodes of a DomElement. """
526 result = []
527 for t in DomElement.getElementsByTagName("nd"):
528 result.append(int(int(t.attributes["ref"].value)))
529 return result
531 def _DomGetMember(self, DomElement):
532 """ Returns a list of relation members. """
533 result = []
534 for m in DomElement.getElementsByTagName("member"):
535 result.append(self._DomGetAttributes(m))
536 return result
538 def _DomParseNode(self, DomElement):
539 """ Returns NodeData for the node. """
540 result = self._DomGetAttributes(DomElement)
541 result[u"tag"] = self._DomGetTag(DomElement)
542 return result
544 def _DomParseWay(self, DomElement):
545 """ Returns WayData for the way. """
546 result = self._DomGetAttributes(DomElement)
547 result[u"tag"] = self._DomGetTag(DomElement)
548 result[u"nd"] = self._DomGetNd(DomElement)
549 return result
551 def _DomParseRelation(self, DomElement):
552 """ Returns RelationData for the relation. """
553 result = self._DomGetAttributes(DomElement)
554 result[u"tag"] = self._DomGetTag(DomElement)
555 result[u"member"] = self._DomGetMember(DomElement)
556 return result
558 def _DomParseChangeset(self, DomElement):
559 """ Returns ChangesetData for the changeset. """
560 result = self._DomGetAttributes(DomElement)
561 result[u"tag"] = self._DomGetTag(DomElement)
562 return result
564 #######################################################################
565 # Internal xml builder #
566 #######################################################################
568 def _XmlBuild(self, ElementType, ElementData):
570 xml = u""
571 xml += u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
572 xml += u"<osm version=\"0.6\" generator=\"" + self._created_by + "\">\n"
574 # <element attr="val">
575 xml += u" <" + ElementType
576 if u"id" in ElementData:
577 xml += u" id=\"" + str(ElementData[u"id"]) + u"\""
578 if u"lat" in ElementData:
579 xml += u" lat=\"" + str(ElementData[u"lat"]) + u"\""
580 if u"lon" in ElementData:
581 xml += u" lon=\"" + str(ElementData[u"lon"]) + u"\""
582 if u"version" in ElementData:
583 xml += u" version=\"" + str(ElementData[u"version"]) + u"\""
584 xml += u" visible=\"" + str(ElementData.get(u"visible", True)).lower() + u"\""
585 if ElementType in [u"node", u"way", u"relation"]:
586 xml += u" changeset=\"" + str(self._CurrentChangesetId) + u"\""
587 xml += u">\n"
589 # <tag... />
590 for k, v in ElementData.get(u"tag", {}).items():
591 xml += u" <tag k=\""+self._XmlEncode(k)+u"\" v=\""+self._XmlEncode(v)+u"\"/>\n"
593 # <member... />
594 for member in ElementData.get(u"member", []):
595 xml += u" <member type=\""+member[u"type"]+"\" ref=\""+str(member[u"ref"])+u"\" role=\""+self._XmlEncode(member[u"role"])+"\"/>\n"
597 # <nd... />
598 for ref in ElementData.get(u"nd", []):
599 xml += u" <nd ref=\""+str(ref)+u"\"/>\n"
601 # </element>
602 xml += u" </" + ElementType + u">\n"
604 xml += u"</osm>\n"
606 return xml.encode("utf8")
608 def _XmlEncode(self, text):
609 return text.replace("&", "&amp;").replace("\"", "&quot;")
611 ## End of main class ##
612 ###########################################################################