source: subversion/applications/utils/python_lib/OsmApi/OsmApi.py @ 18112

Last change on this file since 18112 was 18112, checked in by echove, 11 years ago

ChangesetUpload? and auto changeset management

File size: 28.1 KB
Line 
1#-*- coding: utf-8 -*-
2
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###########################################################################
21
22## HomePage : http://wiki.openstreetmap.org/wiki/PythonOsmApi
23
24###########################################################################
25## History                                                               ##
26###########################################################################
27## 0.2.9   2009-10-13 automatic changeset management                     ##
28##                    ChangesetsUpload 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###########################################################################
43
44__version__ = '0.2.9'
45
46import httplib, base64, xml.dom.minidom, time
47
48###########################################################################
49## Main class                                                            ##
50
51class OsmApi:
52   
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):
63
64        # Get username
65        if username:
66            self._username = username
67        elif passwordfile:
68            self._username =  open(passwordfile).readline().split(":")[0].strip()           
69   
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]
78
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
84       
85        # Get API
86        self._api = api
87
88        # Get created_by
89        if not appid:
90            self._created_by = created_by
91        else:
92            self._created_by = appid + " (" + created_by + ")"
93
94        # Initialisation     
95        self._CurrentChangesetId = 0
96       
97        # Http connection
98        self._conn = httplib.HTTPConnection(self._api, 80)
99
100    def __del__(self):
101        if self._changesetauto:
102            self._changesetautoflush(True)
103        return None
104
105    #######################################################################
106    # Capabilities                                                        #
107    #######################################################################
108   
109    def Capabilities(self):
110        raise NotImplemented
111
112    #######################################################################
113    # Node                                                                #
114    #######################################################################
115
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)
125
126    def NodeCreate(self, NodeData):
127        """ Creates a node. Returns updated NodeData (without timestamp). """
128        return self._do("create", "node", NodeData)
129           
130    def NodeUpdate(self, NodeData):
131        """ Updates node with NodeData. Returns updated NodeData (without timestamp). """
132        return self._do("update", "node", NodeData)
133
134    def NodeDelete(self, NodeData):
135        """ Delete node with NodeData. Returns updated NodeData (without timestamp). """
136        return self._do("delete", "node", NodeData)
137
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
148
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
159   
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
170
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
181
182    #######################################################################
183    # Way                                                                 #
184    #######################################################################
185
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)
195   
196    def WayCreate(self, WayData):
197        """ Creates a way. Returns updated WayData (without timestamp). """
198        return self._do("create", "way", WayData)
199
200    def WayUpdate(self, WayData):
201        """ Updates way with WayData. Returns updated WayData (without timestamp). """
202        return self._do("update", "way", WayData)
203
204    def WayDelete(self, WayData):
205        """ Delete way with WayData. Returns updated WayData (without timestamp). """
206        return self._do("delete", "way", WayData)
207
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
218   
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
229
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)
235
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
246
247    #######################################################################
248    # Relation                                                            #
249    #######################################################################
250
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)
260
261    def RelationCreate(self, RelationData):
262        """ Creates a relation. Returns updated RelationData (without timestamp). """
263        return self._do("create", "relation", RelationData)
264   
265    def RelationUpdate(self, RelationData):
266        """ Updates relation with RelationData. Returns updated RelationData (without timestamp). """
267        return self._do("update", "relation", RelationData)
268
269    def RelationDelete(self, RelationData):
270        """ Delete relation with RelationData. Returns updated RelationData (without timestamp). """
271        return self._do("delete", "relation", RelationData)
272
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
283   
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
294
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)
300
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
311
312    #######################################################################
313    # Changeset                                                           #
314    #######################################################################
315
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)
322   
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
331
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
341   
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
350
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
372       
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)
378   
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
382   
383    #######################################################################
384    # Other                                                               #
385    #######################################################################
386
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)
392
393    #######################################################################
394    # Data parser                                                         #
395    #######################################################################
396   
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   
410
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
426
427    #######################################################################
428    # Internal http function                                              #
429    #######################################################################
430
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)
438           
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
461   
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
469       
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()
487   
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)
498   
499    def _get(self, path):
500        return self._http('GET', path, False, None)
501
502    def _put(self, path, data):
503        return self._http('PUT', path, True, data)
504   
505    def _delete(self, path, data):
506        return self._http('DELETE', path, True, data)
507   
508    #######################################################################
509    # Internal dom function                                               #
510    #######################################################################
511   
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           
527       
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
536
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           
543
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
550
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
556
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
563   
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
570
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
576
577    #######################################################################
578    # Internal xml builder                                                #
579    #######################################################################
580
581    def _XmlBuild(self, ElementType, ElementData, WithHeaders = True):
582
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"
587
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"
602
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"
606
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"
610
611        # <nd... />
612        for ref in ElementData.get(u"nd", []):
613            xml += u"    <nd ref=\""+str(ref)+u"\"/>\n"
614
615        # </element>
616        xml += u"  </" + ElementType + u">\n"
617       
618        if WithHeaders:
619            xml += u"</osm>\n"
620
621        return xml.encode("utf8")
622
623    def _XmlEncode(self, text):
624        return text.replace("&", "&amp;").replace("\"", "&quot;")
625
626## End of main class                                                     ##
627###########################################################################
Note: See TracBrowser for help on using the repository browser.