Initial commit of the OsmApi class from Etienne Chové
[osmose-modules.git] / OsmApi.py
blob89f4924fe7ff5e6232aebf44d9cf760720f0de69
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 import httplib, base64, xml.dom.minidom
24 class OsmApi:
26 def __init__(self, api = "www.openstreetmap.org", username = "EtienneChove", password = None, created_by = "PythonOsmApi/0.2"):
28 self._api = api
29 self._username = username
30 self._password = password
31 self._created_by = created_by
33 try:
34 if password == None:
35 for l in open("/home/etienne/osm/passwords"):
36 if l.strip().split(":")[0] == username:
37 self._password = l.strip().split(":")[1]
38 except:
39 pass
41 self._CurrentChangesetId = -1
43 #######################################################################
44 # Capabilities #
45 #######################################################################
47 def Capabilities(self):
48 raise NotImplemented
50 #######################################################################
51 # Node #
52 #######################################################################
54 def NodeGet(self, NodeId, NodeVersion = -1):
55 """ Returns NodeData for node #NodeId. """
56 uri = "/api/0.6/node/"+str(NodeId)
57 if NodeVersion <> -1: uri += "/"+str(NodeVersion)
58 data = self._get(uri)
59 data = xml.dom.minidom.parseString(data.encode("utf-8"))
60 data = data.getElementsByTagName("osm")[0].getElementsByTagName("node")[0]
61 return self._DomParseNode(data)
63 def NodeUpdate(self, NodeData):
64 """ Updates node with NodeData. Returns updated NodeData (without timestamp). """
65 if self._CurrentChangesetId == -1:
66 raise Execption, "No changeset currently opened"
67 NodeData[u"changeset"] = self._CurrentChangesetId
68 result = self._put("/api/0.6/node/"+str(NodeData[u"id"]), self._XmlBuild("node", NodeData))
69 NodeData[u"version"] = int(result.strip())
70 if u"timestamp" in NodeData: NodeData.pop(u"timestamp")
71 return NodeData
73 def NodeDelete(self, NodeData):
74 """ Delete node with NodeData. Returns updated NodeData (without timestamp). """
75 if self._CurrentChangesetId == -1:
76 raise Execption, "No changeset currently opened"
77 NodeData[u"changeset"] = self._CurrentChangesetId
78 result = self._delete("/api/0.6/node/"+str(NodeData[u"id"]), self._XmlBuild("node", NodeData))
79 NodeData[u"version"] = int(result.strip())
80 NodeData[u"visible"] = False
81 if u"timestamp" in NodeData: NodeData.pop(u"timestamp")
82 return NodeData
84 def NodeCreate(self, NodeData):
85 """ Creates a node. Returns updated NodeData (without timestamp). """
86 if self._CurrentChangesetId == -1:
87 raise Execption, "No changeset currently opened"
88 NodeData[u"changeset"] = self._CurrentChangesetId
89 result = self._put("/api/0.6/node/create", self._XmlBuild("node", NodeData))
90 NodeData[u"id"] = int(result.strip())
91 NodeData[u"version"] = 1
92 if u"timestamp" in NodeData: NodeData.pop(u"timestamp")
93 return NodeData
95 def NodeHistory(self, NodeId):
96 """ Returns dict(NodeVerrsion: NodeData). """
97 uri = "/api/0.6/node/"+str(NodeId)+"/history"
98 data = self._get(uri)
99 data = xml.dom.minidom.parseString(data.encode("utf-8"))
100 result = {}
101 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("node"):
102 data = self._DomParseNode(data)
103 result[data[u"version"]] = data
104 return result
106 def NodeWays(self, NodeId):
107 """ Returns [WayData, ... ] containing node #NodeId. """
108 # GET node/#/ways TODO
109 raise NotImplemented
111 def NodeRelations(self, NodeId):
112 """ Returns [RelationData, ... ] containing node #NodeId. """
113 # GET node/#/relations TODO
114 raise NotImplemented
116 def NodesGet(self, NodeIdList):
117 """ Will not be implemented. """
118 raise NotImplemented
120 #######################################################################
121 # Way #
122 #######################################################################
124 def WayGet(self, WayId, WayVersion = -1):
125 """ Returns WayData for way #WayId. """
126 uri = "/api/0.6/way/"+str(WayId)
127 if WayVersion <> -1: uri += "/"+str(WayVersion)
128 data = self._get(uri)
129 data = xml.dom.minidom.parseString(data.encode("utf-8"))
130 data = data.getElementsByTagName("osm")[0].getElementsByTagName("way")[0]
131 return self._DomParseWay(data)
133 def WayUpdate(self, WayData):
134 """ Updates way with WayData. Returns updated WayData (without timestamp). """
135 if self._CurrentChangesetId == -1:
136 raise Execption, "No changeset currently opened"
137 WayData[u"changeset"] = self._CurrentChangesetId
138 result = self._put("/api/0.6/way/"+str(WayData[u"id"]), self._XmlBuild("way", WayData))
139 WayData[u"version"] = int(result.strip())
140 if u"timestamp" in WayData: WayData.pop(u"timestamp")
141 return WayData
143 def WayDelete(self, WayData):
144 """ Delete way with WayData. Returns updated WayData (without timestamp). """
145 if self._CurrentChangesetId == -1:
146 raise Execption, "No changeset currently opened"
147 WayData[u"changeset"] = self._CurrentChangesetId
148 result = self._delete("/api/0.6/way/"+str(WayData[u"id"]), self._XmlBuild("way", WayData))
149 WayData[u"version"] = int(result.strip())
150 WayData[u"visible"] = False
151 if u"timestamp" in WayData: WayData.pop(u"timestamp")
152 return WayData
154 def WayCreate(self, WayData):
155 """ Creates a way. Returns updated WayData (without timestamp). """
156 if self._CurrentChangesetId == -1:
157 raise Execption, "No changeset currently opened"
158 WayData[u"changeset"] = self._CurrentChangesetId
159 result = self._put("/api/0.6/way/create", self._XmlBuild("way", WayData))
160 WayData[u"id"] = int(result.strip())
161 WayData[u"version"] = 1
162 if u"timestamp" in WayData: WayData.pop(u"timestamp")
163 return WayData
165 def WayHistory(self, WayId):
166 """ Returns dict(WayVerrsion: WayData). """
167 uri = "/api/0.6/way/"+str(WayId)+"/history"
168 data = self._get(uri)
169 data = xml.dom.minidom.parseString(data.encode("utf-8"))
170 result = {}
171 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("way"):
172 data = self._DomParseWay(data)
173 result[data[u"version"]] = data
174 return result
176 def WayRelations(self, WayId):
177 """ Returns [RelationData, ...] containing way #WayId. """
178 # GET way/#/relations
179 raise NotImplemented
181 def WayFull(self, WayId):
182 """ Will not be implemented. """
183 raise NotImplemented
185 def WaysGet(self, WayIdList):
186 """ Will not be implemented. """
187 raise NotImplemented
189 #######################################################################
190 # Relation #
191 #######################################################################
193 def RelationGet(self, RelationId, RelationVersion = -1):
194 """ Returns RelationData for relation #RelationId. """
195 uri = "/api/0.6/relation/"+str(RelationId)
196 if RelationVersion <> -1: uri += "/"+str(RelationVersion)
197 data = self._get(uri)
198 #print type(data)
199 data = xml.dom.minidom.parseString(data.encode("utf-8"))
200 data = data.getElementsByTagName("osm")[0].getElementsByTagName("relation")[0]
201 return self._DomParseRelation(data)
203 def RelationUpdate(self, RelationData):
204 """ Updates relation with RelationData. Returns updated RelationData (without timestamp). """
205 if self._CurrentChangesetId == -1:
206 raise Execption, "No changeset currently opened"
207 RelationData[u"changeset"] = self._CurrentChangesetId
208 result = self._put("/api/0.6/relation/"+str(RelationData[u"id"]), self._XmlBuild("relation", RelationData))
209 RelationData[u"version"] = int(result.strip())
210 if u"timestamp" in RelationData: RelationData.pop(u"timestamp")
211 return RelationData
213 def RelationDelete(self, RelationData):
214 """ Delete relation with RelationData. Returns updated RelationData (without timestamp). """
215 if self._CurrentChangesetId == -1:
216 raise Execption, "No changeset currently opened"
217 RelationData[u"changeset"] = self._CurrentChangesetId
218 result = self._delete("/api/0.6/relation/"+str(RelationData[u"id"]), self._XmlBuild("relation", RelationData))
219 RelationData[u"version"] = int(result.strip())
220 RelationData[u"visible"] = False
221 if u"timestamp" in RelationData: RelationData.pop(u"timestamp")
222 return RelationData
224 def RelationCreate(self, RelationData):
225 """ Creates a relation. Returns updated RelationData (without timestamp). """
226 if self._CurrentChangesetId == -1:
227 raise Execption, "No changeset currently opened"
228 RelationData[u"changeset"] = self._CurrentChangesetId
229 result = self._put("/api/0.6/relation/create", self._XmlBuild("relation", RelationData))
230 RelationData[u"id"] = int(result.strip())
231 RelationData[u"version"] = 1
232 if u"timestamp" in RelationData: RelationData.pop(u"timestamp")
233 return RelationData
235 def RelationHistory(self, RelationId):
236 """ Returns dict(RelationVerrsion: RelationData). """
237 uri = "/api/0.6/relation/"+str(RelationId)+"/history"
238 data = self._get(uri)
239 data = xml.dom.minidom.parseString(data.encode("utf-8"))
240 result = {}
241 for data in data.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
242 data = self._DomParseRelation(data)
243 result[data[u"version"]] = data
244 return result
246 def RelationRelations(self, RelationId):
247 """ Returns list of RelationData containing relation #RelationId. """
248 # GET relation/#/relations TODO
249 raise NotImplemented
251 def RelationFull(self, RelationId):
252 """ Will not be implemented. """
253 raise NotImplemented
255 def RelationsGet(self, RelationIdList):
256 """ Will not be implemented. """
257 raise NotImplemented
259 #######################################################################
260 # Changeset #
261 #######################################################################
263 def ChangesetGet(self, ChangesetId):
264 """ Returns ChangesetData for changeset #ChangesetId. """
265 data = self._get("/api/0.6/changeset/"+str(ChangesetId))
266 data = xml.dom.minidom.parseString(data.encode("utf-8"))
267 data = data.getElementsByTagName("osm")[0].getElementsByTagName("changeset")[0]
268 return self._DomParseChangeset(data)
270 def ChangesetUpdate(self, ChangesetTags = {}):
271 """ Updates current changeset with ChangesetTags. """
272 if self._CurrentChangesetId == -1:
273 raise Execption, "No changeset currently opened"
274 result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId), self._XmlBuild("changeset", {u"tag": ChangesetTags}))
275 return self._CurrentChangesetId
277 def ChangesetCreate(self, ChangesetTags = {}):
278 """ Opens a changeset. Returns #ChangesetId. """
279 result = self._put("/api/0.6/changeset/create", self._XmlBuild("changeset", {u"tag": ChangesetTags}))
280 self._CurrentChangesetId = int(result)
281 self._CurrentChangesetTags = ChangesetTags
282 self._CurrentChangesetCpt = 0
283 return self._CurrentChangesetId
285 def ChangesetClose(self):
286 """ Closes current changeset. Returns #ChangesetId. """
287 if self._CurrentChangesetId == -1:
288 raise Execption, "No changeset currently opened"
289 result = self._put("/api/0.6/changeset/"+str(self._CurrentChangesetId)+"/close", u"")
290 CurrentChangesetId = self._CurrentChangesetId
291 self._CurrentChangesetId = -1
292 return CurrentChangesetId
294 def ChangesetUpload(self):
295 raise NotImplemented
297 def ChangesetDownload(self):
298 raise NotImplemented
300 def ChangesetsGet(self):
301 raise NotImplemented
303 #######################################################################
304 # Other #
305 #######################################################################
307 def Map(self):
308 raise NotImplemented
310 def Trackpoints(self):
311 raise NotImplemented
313 def Changes(self):
314 raise NotImplemented
316 #######################################################################
317 # Internal http function #
318 #######################################################################
320 def _http(self, cmd, path, auth, send):
321 h = httplib.HTTPConnection(self._api, 80)
322 h.putrequest(cmd, path)
323 h.putheader('User-Agent', self._created_by)
324 if auth:
325 h.putheader('Authorization', 'Basic ' + base64.encodestring(self._username + ':' + self._password).strip())
326 if send:
327 send = send.encode("utf-8")
328 h.putheader('Content-Length', len(send))
329 h.endheaders()
330 if send:
331 h.send(send)
332 response = h.getresponse()
333 if response.status <> 200:
334 raise Exception, "API returns unexpected status code "+str(response.status)+" ("+response.reason+")"
335 return response.read().decode("utf-8")
337 def _get(self, path):
338 return self._http('GET', path, False, None)
340 def _put(self, path, data):
341 return self._http('PUT', path, True, data)
343 def _delete(self, path, data):
344 return self._http('DELETE', path, True, data)
346 #######################################################################
347 # Internal dom function #
348 #######################################################################
350 def _DomGetAttributes(self, DomElement):
351 """ Returns a formated dictionnary of attributes of a DomElement. """
352 result = {}
353 for k, v in DomElement.attributes.items():
354 k = k #.decode("utf8")
355 v = v #.decode("utf8")
356 if k == u"uid" : v = int(v)
357 elif k == u"changeset" : v = int(v)
358 elif k == u"version" : v = int(v)
359 elif k == u"id" : v = int(v)
360 elif k == u"lat" : v = float(v)
361 elif k == u"lon" : v = float(v)
362 elif k == u"open" : v = v=="true"
363 elif k == u"visible" : v = v=="true"
364 elif k == u"ref" : v = int(v)
365 result[k] = v
366 return result
368 def _DomGetTag(self, DomElement):
369 """ Returns the dictionnary of tags of a DomElement. """
370 result = {}
371 for t in DomElement.getElementsByTagName("tag"):
372 k = t.attributes["k"].value #.decode("utf8")
373 v = t.attributes["v"].value #.decode("utf8")
374 result[k] = v
375 return result
377 def _DomGetNd(self, DomElement):
378 """ Returns the list of nodes of a DomElement. """
379 result = []
380 for t in DomElement.getElementsByTagName("nd"):
381 result.append(int(int(t.attributes["ref"].value)))
382 return result
384 def _DomGetMember(self, DomElement):
385 """ Returns a list of relation members. """
386 result = []
387 for m in DomElement.getElementsByTagName("member"):
388 result.append(self._DomGetAttributes(m))
389 return result
391 def _DomParseNode(self, DomElement):
392 """ Returns NodeData for the node. """
393 result = self._DomGetAttributes(DomElement)
394 result[u"tag"] = self._DomGetTag(DomElement)
395 return result
397 def _DomParseWay(self, DomElement):
398 """ Returns WayData for the way. """
399 result = self._DomGetAttributes(DomElement)
400 result[u"tag"] = self._DomGetTag(DomElement)
401 result[u"nd"] = self._DomGetNd(DomElement)
402 return result
404 def _DomParseRelation(self, DomElement):
405 """ Returns RelationData for the relation. """
406 result = self._DomGetAttributes(DomElement)
407 result[u"tag"] = self._DomGetTag(DomElement)
408 result[u"member"] = self._DomGetMember(DomElement)
409 return result
411 def _DomParseChangeset(self, DomElement):
412 """ Returns ChangesetData for the changeset. """
413 result = self._DomGetAttributes(DomElement)
414 result[u"tag"] = self._DomGetTag(DomElement)
415 return result
417 #######################################################################
418 # Internal xml builder #
419 #######################################################################
421 def _XmlBuild(self, ElementType, ElementData):
423 xml = u""
424 xml += u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
425 xml += u"<osm version=\"0.6\" generator=\"" + self._created_by + "\">\n"
427 # <element attr="val">
428 xml += u" <" + ElementType
429 if u"id" in ElementData:
430 xml += u" id=\"" + str(ElementData[u"id"]) + u"\""
431 if u"lat" in ElementData:
432 xml += u" lat=\"" + str(ElementData[u"lat"]) + u"\""
433 if u"lon" in ElementData:
434 xml += u" lon=\"" + str(ElementData[u"lon"]) + u"\""
435 if u"version" in ElementData:
436 xml += u" version=\"" + str(ElementData[u"version"]) + u"\""
437 xml += u" visible=\"" + str(ElementData.get(u"visible", True)).lower() + u"\""
438 if ElementType in [u"node", u"way", u"relation"]:
439 xml += u" changeset=\"" + str(self._CurrentChangesetId) + u"\""
440 xml += u">\n"
442 # <tag... />
443 for k, v in ElementData.get(u"tag", {}).items():
444 xml += u" <tag k=\""+self._XmlEncode(k)+u"\" v=\""+self._XmlEncode(v)+u"\"/>\n"
446 # <member... />
447 for member in ElementData.get(u"member", []):
448 xml += u" <member type=\""+member[u"type"]+"\" ref=\""+str(member[u"ref"])+u"\" role=\""+self._XmlEncode(member[u"role"])+"\"/>\n"
450 # <nd... />
451 for ref in ElementData.get(u"nd", []):
452 xml += u" <nd ref=\""+str(ref)+u"\"/>\n"
454 # </element>
455 xml += u" </" + ElementType + u">\n"
457 xml += u"</osm>\n"
459 return xml
461 def _XmlEncode(self, text):
462 return text.replace("&", "&amp;").replace("\"", "&quot;")
464 #######################################################################
465 # End of class OsmApi #
466 #######################################################################
468 if __name__ == "__main__":
470 z = OsmApi(api="api06.dev.openstreetmap.org")
472 # Changeset open
474 c1 = {u"note": u"Python OsmApi tests"}
475 print "ChangesetCreate : " + str(c1)
476 c1 = z.ChangesetCreate(c1)
477 print " => " + str(c1)
479 # Node tests
481 n1 = {u"lat":1, u"lon":1, u"tag":{u"name":u"Etienne Chové"}}
482 print "NodeCreate : " + str(n1)
483 n1 = z.NodeCreate(n1)
484 print " => " + str(n1)
486 print "NodeGet : " + str(n1[u"id"])
487 n1 = z.NodeGet(n1[u"id"])
488 print " => " + str(n1)
490 n1[u"tag"][u"note"] = u"This is a test"
491 print "NodeUpdate : " + str(n1)
492 n1 = z.NodeUpdate(n1)
493 print " => " + str(n1)
495 print "NodeDelete : " + str(n1)
496 n1 = z.NodeDelete(n1)
497 print " => " + str(n1)
499 print
501 # Way tests
503 n1 = {u"lat":1, u"lon":1, u"tag":{u"name":u"node 1"}}
504 n2 = {u"lat":1.1, u"lon":1.1, u"tag":{u"name":u"node 2"}}
505 n1 = z.NodeCreate(n1)
506 n2 = z.NodeCreate(n2)
507 w1 = {u"nd":[n1[u"id"], n2[u"id"]], u"tag":{}}
509 print "WayCreate : " + str(w1)
510 w1 = z.WayCreate(w1)
511 print " => " + str(w1)
513 print "WayGet : " + str(w1[u"id"])
514 w1 = z.WayGet(w1[u"id"])
515 print " => " + str(w1)
517 w1[u"nd"].append(n1[u"id"])
518 print "WayUpdate : " + str(w1)
519 w1 = z.WayUpdate(w1)
520 print " => " + str(w1)
522 print "WayDelete : " + str(w1)
523 w1 = z.WayDelete(w1)
524 print " => " + str(w1)
526 z.NodeDelete(n1)
527 z.NodeDelete(n2)
529 print
531 # Relation tests
533 n1 = {u"lat":1, u"lon":1, u"tag":{u"name":u"node 1"}}
534 n2 = {u"lat":1.1, u"lon":1.1, u"tag":{u"name":u"node 2"}}
535 n1 = z.NodeCreate(n1)
536 n2 = z.NodeCreate(n2)
537 r1 = {u"member":[{u"type": u"node", u"ref": n1[u"id"], u"role": u"role1"}], u"tag":{}}
539 print "RelationCreate : " + str(r1)
540 r1 = z.RelationCreate(r1)
541 print " => " + str(r1)
543 print "RelationGet : " + str(r1[u"id"])
544 r1 = z.RelationGet(r1[u"id"])
545 print " => " + str(r1)
547 r1[u"member"].append({u"type": u"node", u"ref": n2[u"id"], u"role": u""})
548 print "RelationUpdate : " + str(w1)
549 r1 = z.RelationUpdate(r1)
550 print " => " + str(r1)
552 print "RelationDelete : " + str(r1)
553 r1 = z.RelationDelete(r1)
554 print " => " + str(r1)
556 print
558 # Changeset close
560 print "ChangesetClose"
561 c1 = z.ChangesetClose()
562 print " => " + str(c1)