imported OsmApi 0.2.4 from svn.openstreetmap.org
[osm-ro-tools.git] / OsmApi.py
blob1a1fe5a539914ec4b27bb6ad08e2d591c066eb2d
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.4 2009-10-06 clean ##
28 ## 0.2.3 2009-09-09 keep http connection alive for multiple request ##
29 ## (Node|Way|Relation)Get return None when object ##
30 ## have been deleted (raising error before) ##
31 ## 0.2.2 2009-07-13 can identify applications built on top of the lib ##
32 ## 0.2.1 2009-05-05 some changes in constructor -- chove@crans.org ##
33 ## 0.2 2009-05-01 initial import ##
34 ###########################################################################
36 __version__ = '0.2.4'
38 import httplib, base64, xml.dom.minidom, time
40 ###########################################################################
41 ## Main class ##
43 class OsmApi:
45 def __init__(self, username = None, password = None, passwordfile = None, appid = "", created_by = "PythonOsmApi/"+__version__, api = "www.openstreetmap.org"):
47 # Get username
48 if username:
49 self._username = username
50 elif passwordfile:
51 self._username = open(passwordfile).readline().split(":")[0].strip()
53 # Get password
54 if password:
55 self._password = password
56 elif passwordfile:
57 for l in open(passwordfile).readlines():
58 l = l.strip().split(":")
59 if l[0] == self._username:
60 self._password = l[1]
62 # Get API
63 self._api = api
65 # Get created_by
66 if appid == "":
67 self._created_by = created_by
68 else:
69 self._created_by = appid + " (" + created_by + ")"
71 # Initialisation
72 self._CurrentChangesetId = -1
74 # Http connection
75 self._conn = httplib.HTTPConnection(self._api, 80)
77 #######################################################################
78 # Capabilities #
79 #######################################################################
81 def Capabilities(self):
82 raise NotImplemented
84 #######################################################################
85 # Node #
86 #######################################################################
88 def NodeGet(self, NodeId, NodeVersion = -1):
89 """ Returns NodeData for node #NodeId. """
90 uri = "/api/0.6/node/"+str(NodeId)
91 if NodeVersion <> -1: uri += "/"+str(NodeVersion)
92 data = self._get(uri)
93 if not data: return data
94 data = xml.dom.minidom.parseString(data.encode("utf-8"))
95 data = data.getElementsByTagName("osm")[0].getElementsByTagName("node")[0]
96 return self._DomParseNode(data)
98 def NodeUpdate(self, NodeData):
99 """ Updates node with NodeData. Returns updated NodeData (without timestamp). """
100 if self._CurrentChangesetId == -1:
101 raise Exception, "No changeset currently opened"
102 NodeData[u"changeset"] = self._CurrentChangesetId
103 result = self._put("/api/0.6/node/"+str(NodeData[u"id"]), self._XmlBuild("node", NodeData))
104 NodeData[u"version"] = int(result.strip())
105 if u"timestamp" in NodeData: NodeData.pop(u"timestamp")
106 return NodeData
108 def NodeDelete(self, NodeData):
109 """ Delete node with NodeData. Returns updated NodeData (without timestamp). """
110 if self._CurrentChangesetId == -1:
111 raise Exception, "No changeset currently opened"
112 NodeData[u"changeset"] = self._CurrentChangesetId
113 result = self._delete("/api/0.6/node/"+str(NodeData[u"id"]), self._XmlBuild("node", NodeData))
114 NodeData[u"version"] = int(result.strip())
115 NodeData[u"visible"] = False
116 if u"timestamp" in NodeData: NodeData.pop(u"timestamp")
117 return NodeData
119 def NodeCreate(self, NodeData):
120 """ Creates a node. Returns updated NodeData (without timestamp). """
121 if self._CurrentChangesetId == -1:
122 raise Exception, "No changeset currently opened"
123 if NodeData.get(u"id", -1) > 0:
124 raise Exception, "This node already exists"
125 NodeData[u"changeset"] = self._CurrentChangesetId
126 result = self._put("/api/0.6/node/create", self._XmlBuild("node", NodeData))
127 NodeData[u"id"] = int(result.strip())
128 NodeData[u"version"] = 1
129 if u"timestamp" in NodeData: NodeData.pop(u"timestamp")
130 return NodeData
132 def NodeHistory(self, NodeId):
133 """ Returns dict(NodeVerrsion: NodeData). """
134 uri = "/api/0.6/node/"+str(NodeId)+"/history"
135 data = self._get(uri)
136 data = xml.dom.minidom.parseString(data.encode("utf-8"))
137 result = {}
138 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("node"):
139 data = self._DomParseNode(data)
140 result[data[u"version"]] = data
141 return result
143 def NodeWays(self, NodeId):
144 """ Returns [WayData, ... ] containing node #NodeId. """
145 # GET node/#/ways TODO
146 raise NotImplemented
148 def NodeRelations(self, NodeId):
149 """ Returns [RelationData, ... ] containing node #NodeId. """
150 # GET node/#/relations TODO
151 raise NotImplemented
153 def NodesGet(self, NodeIdList):
154 """ Will not be implemented. """
155 raise NotImplemented
157 #######################################################################
158 # Way #
159 #######################################################################
161 def WayGet(self, WayId, WayVersion = -1):
162 """ Returns WayData for way #WayId. """
163 uri = "/api/0.6/way/"+str(WayId)
164 if WayVersion <> -1: uri += "/"+str(WayVersion)
165 data = self._get(uri)
166 if not data: return data
167 data = xml.dom.minidom.parseString(data.encode("utf-8"))
168 data = data.getElementsByTagName("osm")[0].getElementsByTagName("way")[0]
169 return self._DomParseWay(data)
171 def WayUpdate(self, WayData):
172 """ Updates way with WayData. Returns updated WayData (without timestamp). """
173 if self._CurrentChangesetId == -1:
174 raise Exception, "No changeset currently opened"
175 WayData[u"changeset"] = self._CurrentChangesetId
176 result = self._put("/api/0.6/way/"+str(WayData[u"id"]), self._XmlBuild("way", WayData))
177 WayData[u"version"] = int(result.strip())
178 if u"timestamp" in WayData: WayData.pop(u"timestamp")
179 return WayData
181 def WayDelete(self, WayData):
182 """ Delete way with WayData. Returns updated WayData (without timestamp). """
183 if self._CurrentChangesetId == -1:
184 raise Exception, "No changeset currently opened"
185 WayData[u"changeset"] = self._CurrentChangesetId
186 result = self._delete("/api/0.6/way/"+str(WayData[u"id"]), self._XmlBuild("way", WayData))
187 WayData[u"version"] = int(result.strip())
188 WayData[u"visible"] = False
189 if u"timestamp" in WayData: WayData.pop(u"timestamp")
190 return WayData
192 def WayCreate(self, WayData):
193 """ Creates a way. Returns updated WayData (without timestamp). """
194 if self._CurrentChangesetId == -1:
195 raise Exception, "No changeset currently opened"
196 if NodeData.get(u"id", -1) > 0:
197 raise Exception, "This way already exists"
198 WayData[u"changeset"] = self._CurrentChangesetId
199 result = self._put("/api/0.6/way/create", self._XmlBuild("way", WayData))
200 WayData[u"id"] = int(result.strip())
201 WayData[u"version"] = 1
202 if u"timestamp" in WayData: WayData.pop(u"timestamp")
203 return WayData
205 def WayHistory(self, WayId):
206 """ Returns dict(WayVerrsion: WayData). """
207 uri = "/api/0.6/way/"+str(WayId)+"/history"
208 data = self._get(uri)
209 data = xml.dom.minidom.parseString(data.encode("utf-8"))
210 result = {}
211 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"):
212 data = self._DomParseWay(data)
213 result[data[u"version"]] = data
214 return result
216 def WayRelations(self, WayId):
217 """ Returns [RelationData, ...] containing way #WayId. """
218 # GET way/#/relations
219 raise NotImplemented
221 def WayFull(self, WayId):
222 """ Will not be implemented. """
223 raise NotImplemented
225 def WaysGet(self, WayIdList):
226 """ Will not be implemented. """
227 raise NotImplemented
229 #######################################################################
230 # Relation #
231 #######################################################################
233 def RelationGet(self, RelationId, RelationVersion = -1):
234 """ Returns RelationData for relation #RelationId. """
235 uri = "/api/0.6/relation/"+str(RelationId)
236 if RelationVersion <> -1: uri += "/"+str(RelationVersion)
237 data = self._get(uri)
238 if not data: return data
239 data = xml.dom.minidom.parseString(data.encode("utf-8"))
240 data = data.getElementsByTagName("osm")[0].getElementsByTagName("relation")[0]
241 return self._DomParseRelation(data)
243 def RelationUpdate(self, RelationData):
244 """ Updates relation with RelationData. Returns updated RelationData (without timestamp). """
245 if self._CurrentChangesetId == -1:
246 raise Exception, "No changeset currently opened"
247 RelationData[u"changeset"] = self._CurrentChangesetId
248 result = self._put("/api/0.6/relation/"+str(RelationData[u"id"]), self._XmlBuild("relation", RelationData))
249 RelationData[u"version"] = int(result.strip())
250 if u"timestamp" in RelationData: RelationData.pop(u"timestamp")
251 return RelationData
253 def RelationDelete(self, RelationData):
254 """ Delete relation with RelationData. Returns updated RelationData (without timestamp). """
255 if self._CurrentChangesetId == -1:
256 raise Exception, "No changeset currently opened"
257 RelationData[u"changeset"] = self._CurrentChangesetId
258 result = self._delete("/api/0.6/relation/"+str(RelationData[u"id"]), self._XmlBuild("relation", RelationData))
259 RelationData[u"version"] = int(result.strip())
260 RelationData[u"visible"] = False
261 if u"timestamp" in RelationData: RelationData.pop(u"timestamp")
262 return RelationData
264 def RelationCreate(self, RelationData):
265 """ Creates a relation. Returns updated RelationData (without timestamp). """
266 if self._CurrentChangesetId == -1:
267 raise Exception, "No changeset currently opened"
268 if NodeData.get(u"id", -1) > 0:
269 raise Exception, "This relation already exists"
270 RelationData[u"changeset"] = self._CurrentChangesetId
271 result = self._put("/api/0.6/relation/create", self._XmlBuild("relation", RelationData))
272 RelationData[u"id"] = int(result.strip())
273 RelationData[u"version"] = 1
274 if u"timestamp" in RelationData: RelationData.pop(u"timestamp")
275 return RelationData
277 def RelationHistory(self, RelationId):
278 """ Returns dict(RelationVerrsion: RelationData). """
279 uri = "/api/0.6/relation/"+str(RelationId)+"/history"
280 data = self._get(uri)
281 data = xml.dom.minidom.parseString(data.encode("utf-8"))
282 result = {}
283 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
284 data = self._DomParseRelation(data)
285 result[data[u"version"]] = data
286 return result
288 def RelationRelations(self, RelationId):
289 """ Returns list of RelationData containing relation #RelationId. """
290 # GET relation/#/relations TODO
291 raise NotImplemented
293 def RelationFull(self, RelationId):
294 """ Will not be implemented. """
295 raise NotImplemented
297 def RelationsGet(self, RelationIdList):
298 """ Will not be implemented. """
299 raise NotImplemented
301 #######################################################################
302 # Changeset #
303 #######################################################################
305 def ChangesetGet(self, ChangesetId):
306 """ Returns ChangesetData for changeset #ChangesetId. """
307 data = self._get("/api/0.6/changeset/"+str(ChangesetId))
308 data = xml.dom.minidom.parseString(data.encode("utf-8"))
309 data = data.getElementsByTagName("osm")[0].getElementsByTagName("changeset")[0]
310 return self._DomParseChangeset(data)
312 def ChangesetUpdate(self, ChangesetTags = {}):
313 """ Updates current changeset with ChangesetTags. """
314 if self._CurrentChangesetId == -1:
315 raise Exception, "No changeset currently opened"
316 if u"created_by" not in ChangesetTags:
317 ChangesetTags[u"created_by"] = self._created_by
318 result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId), self._XmlBuild("changeset", {u"tag": ChangesetTags}))
319 return self._CurrentChangesetId
321 def ChangesetCreate(self, ChangesetTags = {}):
322 """ Opens a changeset. Returns #ChangesetId. """
323 if self._CurrentChangesetId <> -1:
324 raise Exception, "Changeset alreadey opened"
325 if u"created_by" not in ChangesetTags:
326 ChangesetTags[u"created_by"] = self._created_by
327 result = self._put("/api/0.6/changeset/create", self._XmlBuild("changeset", {u"tag": ChangesetTags}))
328 self._CurrentChangesetId = int(result)
329 self._CurrentChangesetTags = ChangesetTags
330 self._CurrentChangesetCpt = 0
331 return self._CurrentChangesetId
333 def ChangesetClose(self):
334 """ Closes current changeset. Returns #ChangesetId. """
335 if self._CurrentChangesetId == -1:
336 raise Exception, "No changeset currently opened"
337 result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId)+"/close", u"")
338 CurrentChangesetId = self._CurrentChangesetId
339 self._CurrentChangesetId = -1
340 return CurrentChangesetId
342 def ChangesetUpload(self):
343 raise NotImplemented
345 def ChangesetDownload(self, ChangesetId):
346 """ Download data from a changeset. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
347 uri = "/api/0.6/changeset/"+str(ChangesetId)+"/download"
348 data = self._get(uri)
349 data = xml.dom.minidom.parseString(data.encode("utf-8"))
350 data = data.getElementsByTagName("osmChange")[0]
351 result = []
352 for action in data.childNodes:
353 if action.nodeName == u"#text": continue
354 for elem in action.childNodes:
355 if elem.nodeName == u"node":
356 result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseNode(elem)})
357 elif elem.nodeName == u"way":
358 result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseWay(elem)})
359 elif elem.nodeName == u"relation":
360 result.append({u"action":action.nodeName, u"type": elem.nodeName, u"data": self._DomParseRelation(elem)})
361 return result
363 def ChangesetsGet(self):
364 raise NotImplemented
366 #######################################################################
367 # Other #
368 #######################################################################
370 def Map(self):
371 raise NotImplemented
373 def Trackpoints(self):
374 raise NotImplemented
376 def Changes(self):
377 raise NotImplemented
379 #######################################################################
380 # Internal http function #
381 #######################################################################
383 def _http_request(self, cmd, path, auth, send):
384 self._conn.putrequest(cmd, path)
385 self._conn.putheader('User-Agent', self._created_by)
386 if auth:
387 self._conn.putheader('Authorization', 'Basic ' + base64.encodestring(self._username + ':' + self._password).strip())
388 if send:
389 send = send.encode("utf-8")
390 self._conn.putheader('Content-Length', len(send))
391 self._conn.endheaders()
392 if send:
393 self._conn.send(send)
394 response = self._conn.getresponse()
395 if response.status <> 200:
396 response.read()
397 if response.status == 410:
398 return None
399 raise Exception, "API returns unexpected status code "+str(response.status)+" ("+response.reason+")"
400 return response.read().decode("utf-8")
402 def _http(self, cmd, path, auth, send):
403 i = 0
404 while True:
405 i += 1
406 try:
407 return self._http_request(cmd, path, auth, send)
408 except:
409 if i == 5: raise
410 if i <> 1: time.sleep(2)
411 self._conn = httplib.HTTPConnection(self._api, 80)
413 def _get(self, path):
414 return self._http('GET', path, False, None)
416 def _put(self, path, data):
417 return self._http('PUT', path, True, data)
419 def _delete(self, path, data):
420 return self._http('DELETE', path, True, data)
422 #######################################################################
423 # Internal dom function #
424 #######################################################################
426 def _DomGetAttributes(self, DomElement):
427 """ Returns a formated dictionnary of attributes of a DomElement. """
428 result = {}
429 for k, v in DomElement.attributes.items():
430 k = k #.decode("utf8")
431 v = v #.decode("utf8")
432 if k == u"uid" : v = int(v)
433 elif k == u"changeset" : v = int(v)
434 elif k == u"version" : v = int(v)
435 elif k == u"id" : v = int(v)
436 elif k == u"lat" : v = float(v)
437 elif k == u"lon" : v = float(v)
438 elif k == u"open" : v = v=="true"
439 elif k == u"visible" : v = v=="true"
440 elif k == u"ref" : v = int(v)
441 result[k] = v
442 return result
444 def _DomGetTag(self, DomElement):
445 """ Returns the dictionnary of tags of a DomElement. """
446 result = {}
447 for t in DomElement.getElementsByTagName("tag"):
448 k = t.attributes["k"].value #.decode("utf8")
449 v = t.attributes["v"].value #.decode("utf8")
450 result[k] = v
451 return result
453 def _DomGetNd(self, DomElement):
454 """ Returns the list of nodes of a DomElement. """
455 result = []
456 for t in DomElement.getElementsByTagName("nd"):
457 result.append(int(int(t.attributes["ref"].value)))
458 return result
460 def _DomGetMember(self, DomElement):
461 """ Returns a list of relation members. """
462 result = []
463 for m in DomElement.getElementsByTagName("member"):
464 result.append(self._DomGetAttributes(m))
465 return result
467 def _DomParseNode(self, DomElement):
468 """ Returns NodeData for the node. """
469 result = self._DomGetAttributes(DomElement)
470 result[u"tag"] = self._DomGetTag(DomElement)
471 return result
473 def _DomParseWay(self, DomElement):
474 """ Returns WayData for the way. """
475 result = self._DomGetAttributes(DomElement)
476 result[u"tag"] = self._DomGetTag(DomElement)
477 result[u"nd"] = self._DomGetNd(DomElement)
478 return result
480 def _DomParseRelation(self, DomElement):
481 """ Returns RelationData for the relation. """
482 result = self._DomGetAttributes(DomElement)
483 result[u"tag"] = self._DomGetTag(DomElement)
484 result[u"member"] = self._DomGetMember(DomElement)
485 return result
487 def _DomParseChangeset(self, DomElement):
488 """ Returns ChangesetData for the changeset. """
489 result = self._DomGetAttributes(DomElement)
490 result[u"tag"] = self._DomGetTag(DomElement)
491 return result
493 #######################################################################
494 # Internal xml builder #
495 #######################################################################
497 def _XmlBuild(self, ElementType, ElementData):
499 xml = u""
500 xml += u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
501 xml += u"<osm version=\"0.6\" generator=\"" + self._created_by + "\">\n"
503 # <element attr="val">
504 xml += u" <" + ElementType
505 if u"id" in ElementData:
506 xml += u" id=\"" + str(ElementData[u"id"]) + u"\""
507 if u"lat" in ElementData:
508 xml += u" lat=\"" + str(ElementData[u"lat"]) + u"\""
509 if u"lon" in ElementData:
510 xml += u" lon=\"" + str(ElementData[u"lon"]) + u"\""
511 if u"version" in ElementData:
512 xml += u" version=\"" + str(ElementData[u"version"]) + u"\""
513 xml += u" visible=\"" + str(ElementData.get(u"visible", True)).lower() + u"\""
514 if ElementType in [u"node", u"way", u"relation"]:
515 xml += u" changeset=\"" + str(self._CurrentChangesetId) + u"\""
516 xml += u">\n"
518 # <tag... />
519 for k, v in ElementData.get(u"tag", {}).items():
520 xml += u" <tag k=\""+self._XmlEncode(k)+u"\" v=\""+self._XmlEncode(v)+u"\"/>\n"
522 # <member... />
523 for member in ElementData.get(u"member", []):
524 xml += u" <member type=\""+member[u"type"]+"\" ref=\""+str(member[u"ref"])+u"\" role=\""+self._XmlEncode(member[u"role"])+"\"/>\n"
526 # <nd... />
527 for ref in ElementData.get(u"nd", []):
528 xml += u" <nd ref=\""+str(ref)+u"\"/>\n"
530 # </element>
531 xml += u" </" + ElementType + u">\n"
533 xml += u"</osm>\n"
535 return xml
537 def _XmlEncode(self, text):
538 return text.replace("&", "&amp;").replace("\"", "&quot;")
540 ## End of main class ##
541 ###########################################################################