imported OsmApi 0.2.9 from svn.openstreetmap.org
[osm-ro-tools.git] / OsmApi.py
blob2cd5b928841229c9d8cad76b9ce196eedd331683
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.9 2009-10-13 automatic changeset management ##
28 ## ChangesetUpload implementation ##
29 ## 0.2.8 2009-10-13 *(Create|Update|Delete) use not unique _do method ##
30 ## 0.2.7 2009-10-09 implement all missing fonctions except ##
31 ## ChangesetsGet and GetCapabilities ##
32 ## 0.2.6 2009-10-09 encoding clean-up ##
33 ## 0.2.5 2009-10-09 implements NodesGet, WaysGet, RelationsGet ##
34 ## ParseOsm, ParseOsc ##
35 ## 0.2.4 2009-10-06 clean-up ##
36 ## 0.2.3 2009-09-09 keep http connection alive for multiple request ##
37 ## (Node|Way|Relation)Get return None when object ##
38 ## have been deleted (raising error before) ##
39 ## 0.2.2 2009-07-13 can identify applications built on top of the lib ##
40 ## 0.2.1 2009-05-05 some changes in constructor -- chove@crans.org ##
41 ## 0.2 2009-05-01 initial import ##
42 ###########################################################################
44 __version__ = '0.2.9'
46 import httplib, base64, xml.dom.minidom, time
48 ###########################################################################
49 ## Main class ##
51 class OsmApi:
53 def __init__(self,
54 username = None,
55 password = None,
56 passwordfile = None,
57 appid = "",
58 created_by = "PythonOsmApi/"+__version__,
59 api = "www.openstreetmap.org",
60 changesetauto = False,
61 changesetautotags = {},
62 changesetautosize = 500):
64 # Get username
65 if username:
66 self._username = username
67 elif passwordfile:
68 self._username = open(passwordfile).readline().split(":")[0].strip()
70 # Get password
71 if password:
72 self._password = password
73 elif passwordfile:
74 for l in open(passwordfile).readlines():
75 l = l.strip().split(":")
76 if l[0] == self._username:
77 self._password = l[1]
79 # Changest informations
80 self._changesetauto = changesetauto # auto create and close changesets
81 self._changesetautotags = changesetautotags # tags for automatic created changesets
82 self._changesetautosize = changesetautosize # change count for auto changeset
83 self._changesetautodata = [] # data to upload for auto group
85 # Get API
86 self._api = api
88 # Get created_by
89 if not appid:
90 self._created_by = created_by
91 else:
92 self._created_by = appid + " (" + created_by + ")"
94 # Initialisation
95 self._CurrentChangesetId = 0
97 # Http connection
98 self._conn = httplib.HTTPConnection(self._api, 80)
100 def __del__(self):
101 if self._changesetauto:
102 self._changesetautoflush(True)
103 return None
105 #######################################################################
106 # Capabilities #
107 #######################################################################
109 def Capabilities(self):
110 raise NotImplemented
112 #######################################################################
113 # Node #
114 #######################################################################
116 def NodeGet(self, NodeId, NodeVersion = -1):
117 """ Returns NodeData for node #NodeId. """
118 uri = "/api/0.6/node/"+str(NodeId)
119 if NodeVersion <> -1: uri += "/"+str(NodeVersion)
120 data = self._get(uri)
121 if not data: return data
122 data = xml.dom.minidom.parseString(data)
123 data = data.getElementsByTagName("osm")[0].getElementsByTagName("node")[0]
124 return self._DomParseNode(data)
126 def NodeCreate(self, NodeData):
127 """ Creates a node. Returns updated NodeData (without timestamp). """
128 return self._do("create", "node", NodeData)
130 def NodeUpdate(self, NodeData):
131 """ Updates node with NodeData. Returns updated NodeData (without timestamp). """
132 return self._do("update", "node", NodeData)
134 def NodeDelete(self, NodeData):
135 """ Delete node with NodeData. Returns updated NodeData (without timestamp). """
136 return self._do("delete", "node", NodeData)
138 def NodeHistory(self, NodeId):
139 """ Returns dict(NodeVerrsion: NodeData). """
140 uri = "/api/0.6/node/"+str(NodeId)+"/history"
141 data = self._get(uri)
142 data = xml.dom.minidom.parseString(data)
143 result = {}
144 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("node"):
145 data = self._DomParseNode(data)
146 result[data[u"version"]] = data
147 return result
149 def NodeWays(self, NodeId):
150 """ Returns [WayData, ... ] containing node #NodeId. """
151 uri = "/api/0.6/node/%d/ways"%NodeId
152 data = self._get(uri)
153 data = xml.dom.minidom.parseString(data)
154 result = []
155 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"):
156 data = self._DomParseRelation(data)
157 result.append(data)
158 return result
160 def NodeRelations(self, NodeId):
161 """ Returns [RelationData, ... ] containing node #NodeId. """
162 uri = "/api/0.6/node/%d/relations"%NodeId
163 data = self._get(uri)
164 data = xml.dom.minidom.parseString(data)
165 result = []
166 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
167 data = self._DomParseRelation(data)
168 result.append(data)
169 return result
171 def NodesGet(self, NodeIdList):
172 """ Returns dict(NodeId: NodeData) for each node in NodeIdList """
173 uri = "/api/0.6/nodes?nodes=" + ",".join([str(x) for x in NodeIdList])
174 data = self._get(uri)
175 data = xml.dom.minidom.parseString(data)
176 result = {}
177 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("node"):
178 data = self._DomParseNode(data)
179 result[data[u"id"]] = data
180 return result
182 #######################################################################
183 # Way #
184 #######################################################################
186 def WayGet(self, WayId, WayVersion = -1):
187 """ Returns WayData for way #WayId. """
188 uri = "/api/0.6/way/"+str(WayId)
189 if WayVersion <> -1: uri += "/"+str(WayVersion)
190 data = self._get(uri)
191 if not data: return data
192 data = xml.dom.minidom.parseString(data)
193 data = data.getElementsByTagName("osm")[0].getElementsByTagName("way")[0]
194 return self._DomParseWay(data)
196 def WayCreate(self, WayData):
197 """ Creates a way. Returns updated WayData (without timestamp). """
198 return self._do("create", "way", WayData)
200 def WayUpdate(self, WayData):
201 """ Updates way with WayData. Returns updated WayData (without timestamp). """
202 return self._do("update", "way", WayData)
204 def WayDelete(self, WayData):
205 """ Delete way with WayData. Returns updated WayData (without timestamp). """
206 return self._do("delete", "way", WayData)
208 def WayHistory(self, WayId):
209 """ Returns dict(WayVerrsion: WayData). """
210 uri = "/api/0.6/way/"+str(WayId)+"/history"
211 data = self._get(uri)
212 data = xml.dom.minidom.parseString(data)
213 result = {}
214 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"):
215 data = self._DomParseWay(data)
216 result[data[u"version"]] = data
217 return result
219 def WayRelations(self, WayId):
220 """ Returns [RelationData, ...] containing way #WayId. """
221 uri = "/api/0.6/way/%d/relations"%WayId
222 data = self._get(uri)
223 data = xml.dom.minidom.parseString(data)
224 result = []
225 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
226 data = self._DomParseRelation(data)
227 result.append(data)
228 return result
230 def WayFull(self, WayId):
231 """ Return full data for way WayId as list of {type: node|way|relation, data: {}}. """
232 uri = "/api/0.6/way/"+str(WayId)+"/full"
233 data = self._get(uri)
234 return self.ParseOsm(data)
236 def WaysGet(self, WayIdList):
237 """ Returns dict(WayId: WayData) for each way in WayIdList """
238 uri = "/api/0.6/ways?ways=" + ",".join([str(x) for x in WayIdList])
239 data = self._get(uri)
240 data = xml.dom.minidom.parseString(data)
241 result = {}
242 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"):
243 data = self._DomParseWay(data)
244 result[data[u"id"]] = data
245 return result
247 #######################################################################
248 # Relation #
249 #######################################################################
251 def RelationGet(self, RelationId, RelationVersion = -1):
252 """ Returns RelationData for relation #RelationId. """
253 uri = "/api/0.6/relation/"+str(RelationId)
254 if RelationVersion <> -1: uri += "/"+str(RelationVersion)
255 data = self._get(uri)
256 if not data: return data
257 data = xml.dom.minidom.parseString(data)
258 data = data.getElementsByTagName("osm")[0].getElementsByTagName("relation")[0]
259 return self._DomParseRelation(data)
261 def RelationCreate(self, RelationData):
262 """ Creates a relation. Returns updated RelationData (without timestamp). """
263 return self._do("create", "relation", RelationData)
265 def RelationUpdate(self, RelationData):
266 """ Updates relation with RelationData. Returns updated RelationData (without timestamp). """
267 return self._do("update", "relation", RelationData)
269 def RelationDelete(self, RelationData):
270 """ Delete relation with RelationData. Returns updated RelationData (without timestamp). """
271 return self._do("delete", "relation", RelationData)
273 def RelationHistory(self, RelationId):
274 """ Returns dict(RelationVerrsion: RelationData). """
275 uri = "/api/0.6/relation/"+str(RelationId)+"/history"
276 data = self._get(uri)
277 data = xml.dom.minidom.parseString(data)
278 result = {}
279 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
280 data = self._DomParseRelation(data)
281 result[data[u"version"]] = data
282 return result
284 def RelationRelations(self, RelationId):
285 """ Returns list of RelationData containing relation #RelationId. """
286 uri = "/api/0.6/relation/%d/relations"%RelationId
287 data = self._get(uri)
288 data = xml.dom.minidom.parseString(data)
289 result = []
290 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
291 data = self._DomParseRelation(data)
292 result.append(data)
293 return result
295 def RelationFull(self, RelationId):
296 """ Return full data for way WayId as list of {type: node|way|relation, data: {}}. """
297 uri = "/api/0.6/relation/"+str(RelationId)+"/full"
298 data = self._get(uri)
299 return self.ParseOsm(data)
301 def RelationsGet(self, RelationIdList):
302 """ Returns dict(RelationId: RelationData) for each relation in RelationIdList """
303 uri = "/api/0.6/relations?relations=" + ",".join([str(x) for x in RelationIdList])
304 data = self._get(uri)
305 data = xml.dom.minidom.parseString(data)
306 result = {}
307 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
308 data = self._DomParseRelation(data)
309 result[data[u"id"]] = data
310 return result
312 #######################################################################
313 # Changeset #
314 #######################################################################
316 def ChangesetGet(self, ChangesetId):
317 """ Returns ChangesetData for changeset #ChangesetId. """
318 data = self._get("/api/0.6/changeset/"+str(ChangesetId))
319 data = xml.dom.minidom.parseString(data)
320 data = data.getElementsByTagName("osm")[0].getElementsByTagName("changeset")[0]
321 return self._DomParseChangeset(data)
323 def ChangesetUpdate(self, ChangesetTags = {}):
324 """ Updates current changeset with ChangesetTags. """
325 if self._CurrentChangesetId == -1:
326 raise Exception, "No changeset currently opened"
327 if u"created_by" not in ChangesetTags:
328 ChangesetTags[u"created_by"] = self._created_by
329 result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId), self._XmlBuild("changeset", {u"tag": ChangesetTags}))
330 return self._CurrentChangesetId
332 def ChangesetCreate(self, ChangesetTags = {}):
333 """ Opens a changeset. Returns #ChangesetId. """
334 if self._CurrentChangesetId:
335 raise Exception, "Changeset alreadey opened"
336 if u"created_by" not in ChangesetTags:
337 ChangesetTags[u"created_by"] = self._created_by
338 result = self._put("/api/0.6/changeset/create", self._XmlBuild("changeset", {u"tag": ChangesetTags}))
339 self._CurrentChangesetId = int(result)
340 return self._CurrentChangesetId
342 def ChangesetClose(self):
343 """ Closes current changeset. Returns #ChangesetId. """
344 if not self._CurrentChangesetId:
345 raise Exception, "No changeset currently opened"
346 result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId)+"/close", u"")
347 CurrentChangesetId = self._CurrentChangesetId
348 self._CurrentChangesetId = 0
349 return CurrentChangesetId
351 def ChangesetUpload(self, ChangesData):
352 """ Upload data. ChangesData is a list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. Returns list with updated ids. """
353 data = ""
354 data += u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
355 data += u"<osmChange version=\"0.6\" generator=\"" + self._created_by + "\">\n"
356 for change in ChangesData:
357 data += u"<"+change["action"]+">\n"
358 change["data"]["changeset"] = self._CurrentChangesetId
359 data += self._XmlBuild(change["type"], change["data"], False)
360 data += u"</"+change["action"]+">\n"
361 data += u"</osmChange>"
362 data = self._http("POST", "/api/0.6/changeset/"+str(self._CurrentChangesetId)+"/upload", True, data)
363 data = xml.dom.minidom.parseString(data)
364 data = data.getElementsByTagName("diffResult")[0]
365 data = [x for x in data.childNodes if x.nodeType == x.ELEMENT_NODE]
366 for i in range(len(ChangesData)):
367 if ChangesData[i]["action"] == "delete":
368 ChangesData[i]["data"].pop("version")
369 else:
370 ChangesData[i]["data"]["version"] = int(data[i].getAttribute("new_id"))
371 return ChangesData
373 def ChangesetDownload(self, ChangesetId):
374 """ Download data from a changeset. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
375 uri = "/api/0.6/changeset/"+str(ChangesetId)+"/download"
376 data = self._get(uri)
377 return self.ParseOsc(data)
379 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):
380 """ Returns dict(ChangsetId: ChangesetData) matching all criteria. """
381 raise NotImplemented
383 #######################################################################
384 # Other #
385 #######################################################################
387 def Map(self, min_lon, min_lat, max_lon, max_lat):
388 """ Download data in bounding box. Returns list of dict {type: node|way|relation, data: {}}. """
389 uri = "/api/0.6/map?bbox=%f,%f,%f,%f"%(min_lon, min_lat, max_lon, max_lat)
390 data = self._get(uri)
391 return self.ParseOsm(data)
393 #######################################################################
394 # Data parser #
395 #######################################################################
397 def ParseOsm(self, data):
398 """ Parse osm data. Returns list of dict {type: node|way|relation, data: {}}. """
399 data = xml.dom.minidom.parseString(data)
400 data = data.getElementsByTagName("osm")[0]
401 result = []
402 for elem in data.childNodes:
403 if elem.nodeName == u"node":
404 result.append({u"type": elem.nodeName, u"data": self._DomParseNode(elem)})
405 elif elem.nodeName == u"way":
406 result.append({u"type": elem.nodeName, u"data": self._DomParseWay(elem)})
407 elif elem.nodeName == u"relation":
408 result.append({u"type": elem.nodeName, u"data": self._DomParseRelation(elem)})
409 return result
411 def ParseOsc(self, data):
412 """ Parse osc data. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
413 data = xml.dom.minidom.parseString(data)
414 data = data.getElementsByTagName("osmChange")[0]
415 result = []
416 for action in data.childNodes:
417 if action.nodeName == u"#text": continue
418 for elem in action.childNodes:
419 if elem.nodeName == u"node":
420 result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseNode(elem)})
421 elif elem.nodeName == u"way":
422 result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseWay(elem)})
423 elif elem.nodeName == u"relation":
424 result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseRelation(elem)})
425 return result
427 #######################################################################
428 # Internal http function #
429 #######################################################################
431 def _do(self, action, OsmType, OsmData):
432 if self._changesetauto:
433 self._changesetautodata.append({"action":action, "type":OsmType, "data":OsmData})
434 self._changesetautoflush()
435 return None
436 else:
437 return self._do_manu(action, OsmType, OsmData)
439 def _do_manu(self, action, OsmType, OsmData):
440 if not self._CurrentChangesetId:
441 raise Exception, "You need to open a changeset before uploading data"
442 if u"timestamp" in OsmData:
443 OsmData.pop(u"timestamp")
444 OsmData[u"changeset"] = self._CurrentChangesetId
445 if action == "create":
446 if OsmData.get(u"id", -1) > 0:
447 raise Exception, "This "+OsmType+" already exists"
448 result = self._put("/api/0.6/"+OsmType+"/create", self._XmlBuild(OsmType, OsmData))
449 OsmData[u"id"] = int(result.strip())
450 OsmData[u"version"] = 1
451 return OsmData
452 elif action == "update":
453 result = self._put("/api/0.6/"+OsmType+"/"+str(OsmData[u"id"]), self._XmlBuild(OsmType, OsmData))
454 OsmData[u"version"] = int(result.strip())
455 return OsmData
456 elif action =="delete":
457 result = self._delete("/api/0.6/"+OsmType+"/"+str(OsmData[u"id"]), self._XmlBuild(OsmType, OsmData))
458 OsmData[u"version"] = int(result.strip())
459 OsmData[u"visible"] = False
460 return OsmData
462 def _changesetautoflush(self, force = False):
463 while (len(self._changesetautodata) >= self._changesetautosize) or (force and self._changesetautodata):
464 self.ChangesetCreate(self._changesetautotags)
465 self.ChangesetUpload(self._changesetautodata[:self._changesetautosize])
466 self._changesetautodata = self._changesetautodata[self._changesetautosize:]
467 self.ChangesetClose()
468 return None
470 def _http_request(self, cmd, path, auth, send):
471 self._conn.putrequest(cmd, path)
472 self._conn.putheader('User-Agent', self._created_by)
473 if auth:
474 self._conn.putheader('Authorization', 'Basic ' + base64.encodestring(self._username + ':' + self._password).strip())
475 if send:
476 self._conn.putheader('Content-Length', len(send))
477 self._conn.endheaders()
478 if send:
479 self._conn.send(send)
480 response = self._conn.getresponse()
481 if response.status <> 200:
482 response.read()
483 if response.status == 410:
484 return None
485 raise Exception, "API returns unexpected status code "+str(response.status)+" ("+response.reason+")"
486 return response.read()
488 def _http(self, cmd, path, auth, send):
489 i = 0
490 while True:
491 i += 1
492 try:
493 return self._http_request(cmd, path, auth, send)
494 except:
495 if i == 5: raise
496 if i <> 1: time.sleep(2)
497 self._conn = httplib.HTTPConnection(self._api, 80)
499 def _get(self, path):
500 return self._http('GET', path, False, None)
502 def _put(self, path, data):
503 return self._http('PUT', path, True, data)
505 def _delete(self, path, data):
506 return self._http('DELETE', path, True, data)
508 #######################################################################
509 # Internal dom function #
510 #######################################################################
512 def _DomGetAttributes(self, DomElement):
513 """ Returns a formated dictionnary of attributes of a DomElement. """
514 result = {}
515 for k, v in DomElement.attributes.items():
516 if k == u"uid" : v = int(v)
517 elif k == u"changeset" : v = int(v)
518 elif k == u"version" : v = int(v)
519 elif k == u"id" : v = int(v)
520 elif k == u"lat" : v = float(v)
521 elif k == u"lon" : v = float(v)
522 elif k == u"open" : v = v=="true"
523 elif k == u"visible" : v = v=="true"
524 elif k == u"ref" : v = int(v)
525 result[k] = v
526 return result
528 def _DomGetTag(self, DomElement):
529 """ Returns the dictionnary of tags of a DomElement. """
530 result = {}
531 for t in DomElement.getElementsByTagName("tag"):
532 k = t.attributes["k"].value
533 v = t.attributes["v"].value
534 result[k] = v
535 return result
537 def _DomGetNd(self, DomElement):
538 """ Returns the list of nodes of a DomElement. """
539 result = []
540 for t in DomElement.getElementsByTagName("nd"):
541 result.append(int(int(t.attributes["ref"].value)))
542 return result
544 def _DomGetMember(self, DomElement):
545 """ Returns a list of relation members. """
546 result = []
547 for m in DomElement.getElementsByTagName("member"):
548 result.append(self._DomGetAttributes(m))
549 return result
551 def _DomParseNode(self, DomElement):
552 """ Returns NodeData for the node. """
553 result = self._DomGetAttributes(DomElement)
554 result[u"tag"] = self._DomGetTag(DomElement)
555 return result
557 def _DomParseWay(self, DomElement):
558 """ Returns WayData for the way. """
559 result = self._DomGetAttributes(DomElement)
560 result[u"tag"] = self._DomGetTag(DomElement)
561 result[u"nd"] = self._DomGetNd(DomElement)
562 return result
564 def _DomParseRelation(self, DomElement):
565 """ Returns RelationData for the relation. """
566 result = self._DomGetAttributes(DomElement)
567 result[u"tag"] = self._DomGetTag(DomElement)
568 result[u"member"] = self._DomGetMember(DomElement)
569 return result
571 def _DomParseChangeset(self, DomElement):
572 """ Returns ChangesetData for the changeset. """
573 result = self._DomGetAttributes(DomElement)
574 result[u"tag"] = self._DomGetTag(DomElement)
575 return result
577 #######################################################################
578 # Internal xml builder #
579 #######################################################################
581 def _XmlBuild(self, ElementType, ElementData, WithHeaders = True):
583 xml = u""
584 if WithHeaders:
585 xml += u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
586 xml += u"<osm version=\"0.6\" generator=\"" + self._created_by + "\">\n"
588 # <element attr="val">
589 xml += u" <" + ElementType
590 if u"id" in ElementData:
591 xml += u" id=\"" + str(ElementData[u"id"]) + u"\""
592 if u"lat" in ElementData:
593 xml += u" lat=\"" + str(ElementData[u"lat"]) + u"\""
594 if u"lon" in ElementData:
595 xml += u" lon=\"" + str(ElementData[u"lon"]) + u"\""
596 if u"version" in ElementData:
597 xml += u" version=\"" + str(ElementData[u"version"]) + u"\""
598 xml += u" visible=\"" + str(ElementData.get(u"visible", True)).lower() + u"\""
599 if ElementType in [u"node", u"way", u"relation"]:
600 xml += u" changeset=\"" + str(self._CurrentChangesetId) + u"\""
601 xml += u">\n"
603 # <tag... />
604 for k, v in ElementData.get(u"tag", {}).items():
605 xml += u" <tag k=\""+self._XmlEncode(k)+u"\" v=\""+self._XmlEncode(v)+u"\"/>\n"
607 # <member... />
608 for member in ElementData.get(u"member", []):
609 xml += u" <member type=\""+member[u"type"]+"\" ref=\""+str(member[u"ref"])+u"\" role=\""+self._XmlEncode(member[u"role"])+"\"/>\n"
611 # <nd... />
612 for ref in ElementData.get(u"nd", []):
613 xml += u" <nd ref=\""+str(ref)+u"\"/>\n"
615 # </element>
616 xml += u" </" + ElementType + u">\n"
618 if WithHeaders:
619 xml += u"</osm>\n"
621 return xml.encode("utf8")
623 def _XmlEncode(self, text):
624 return text.replace("&", "&amp;").replace("\"", "&quot;")
626 ## End of main class ##
627 ###########################################################################