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