3 ###########################################################################
5 ## Copyrights Etienne Chové <chove@crans.org> 2009 ##
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. ##
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. ##
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/>. ##
20 ###########################################################################
22 ## HomePage : http://wiki.openstreetmap.org/wiki/PythonOsmApi
24 ###########################################################################
26 ###########################################################################
27 ## 0.2.2 2009-07-13 can identify applications built on top of the lib ##
28 ## 0.2.1 2009-05-05 some changes in constructor -- chove@crans.org ##
29 ## 0.2 2009-05-01 initial import ##
30 ###########################################################################
34 import httplib
, base64
, xml
.dom
.minidom
36 ###########################################################################
41 def __init__(self
, username
= None, password
= None, passwordfile
= None, appid
= "", created_by
= "PythonOsmApi/"+__version__
, api
= "www.openstreetmap.org"):
45 self
._username
= username
47 self
._username
= open(passwordfile
).readline().split(":")[0].strip()
51 self
._password
= password
53 for l
in open(passwordfile
).readlines():
54 l
= l
.strip().split(":")
55 if l
[0] == self
._username
:
63 self
._created
_by
= created_by
65 self
._created
_by
= appid
+ " (" + created_by
+ ")"
68 self
._CurrentChangesetId
= -1
70 #######################################################################
72 #######################################################################
74 def Capabilities(self
):
77 #######################################################################
79 #######################################################################
81 def NodeGet(self
, NodeId
, NodeVersion
= -1):
82 """ Returns NodeData for node #NodeId. """
83 uri
= "/api/0.6/node/"+str(NodeId
)
84 if NodeVersion
<> -1: uri
+= "/"+str(NodeVersion
)
86 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
87 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("node")[0]
88 return self
._DomParseNode
(data
)
90 def NodeUpdate(self
, NodeData
):
91 """ Updates node with NodeData. Returns updated NodeData (without timestamp). """
92 if self
._CurrentChangesetId
== -1:
93 raise Exception, "No changeset currently opened"
94 NodeData
[u
"changeset"] = self
._CurrentChangesetId
95 result
= self
._put
("/api/0.6/node/"+str(NodeData
[u
"id"]), self
._XmlBuild
("node", NodeData
))
96 NodeData
[u
"version"] = int(result
.strip())
97 if u
"timestamp" in NodeData
: NodeData
.pop(u
"timestamp")
100 def NodeDelete(self
, NodeData
):
101 """ Delete node with NodeData. Returns updated NodeData (without timestamp). """
102 if self
._CurrentChangesetId
== -1:
103 raise Exception, "No changeset currently opened"
104 NodeData
[u
"changeset"] = self
._CurrentChangesetId
105 result
= self
._delete
("/api/0.6/node/"+str(NodeData
[u
"id"]), self
._XmlBuild
("node", NodeData
))
106 NodeData
[u
"version"] = int(result
.strip())
107 NodeData
[u
"visible"] = False
108 if u
"timestamp" in NodeData
: NodeData
.pop(u
"timestamp")
111 def NodeCreate(self
, NodeData
):
112 """ Creates a node. Returns updated NodeData (without timestamp). """
113 if self
._CurrentChangesetId
== -1:
114 raise Exception, "No changeset currently opened"
115 if NodeData
.get(u
"id", -1) > 0:
116 raise Exception, "This node already exists"
117 NodeData
[u
"changeset"] = self
._CurrentChangesetId
118 result
= self
._put
("/api/0.6/node/create", self
._XmlBuild
("node", NodeData
))
119 NodeData
[u
"id"] = int(result
.strip())
120 NodeData
[u
"version"] = 1
121 if u
"timestamp" in NodeData
: NodeData
.pop(u
"timestamp")
124 def NodeHistory(self
, NodeId
):
125 """ Returns dict(NodeVerrsion: NodeData). """
126 uri
= "/api/0.6/node/"+str(NodeId
)+"/history"
127 data
= self
._get
(uri
)
128 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
130 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("node"):
131 data
= self
._DomParseNode
(data
)
132 result
[data
[u
"version"]] = data
135 def NodeWays(self
, NodeId
):
136 """ Returns [WayData, ... ] containing node #NodeId. """
137 # GET node/#/ways TODO
140 def NodeRelations(self
, NodeId
):
141 """ Returns [RelationData, ... ] containing node #NodeId. """
142 # GET node/#/relations TODO
145 def NodesGet(self
, NodeIdList
):
146 """ Will not be implemented. """
149 #######################################################################
151 #######################################################################
153 def WayGet(self
, WayId
, WayVersion
= -1):
154 """ Returns WayData for way #WayId. """
155 uri
= "/api/0.6/way/"+str(WayId
)
156 if WayVersion
<> -1: uri
+= "/"+str(WayVersion
)
157 data
= self
._get
(uri
)
158 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
159 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("way")[0]
160 return self
._DomParseWay
(data
)
162 def WayUpdate(self
, WayData
):
163 """ Updates way with WayData. Returns updated WayData (without timestamp). """
164 if self
._CurrentChangesetId
== -1:
165 raise Exception, "No changeset currently opened"
166 WayData
[u
"changeset"] = self
._CurrentChangesetId
167 result
= self
._put
("/api/0.6/way/"+str(WayData
[u
"id"]), self
._XmlBuild
("way", WayData
))
168 WayData
[u
"version"] = int(result
.strip())
169 if u
"timestamp" in WayData
: WayData
.pop(u
"timestamp")
172 def WayDelete(self
, WayData
):
173 """ Delete way with WayData. Returns updated WayData (without timestamp). """
174 if self
._CurrentChangesetId
== -1:
175 raise Exception, "No changeset currently opened"
176 WayData
[u
"changeset"] = self
._CurrentChangesetId
177 result
= self
._delete
("/api/0.6/way/"+str(WayData
[u
"id"]), self
._XmlBuild
("way", WayData
))
178 WayData
[u
"version"] = int(result
.strip())
179 WayData
[u
"visible"] = False
180 if u
"timestamp" in WayData
: WayData
.pop(u
"timestamp")
183 def WayCreate(self
, WayData
):
184 """ Creates a way. Returns updated WayData (without timestamp). """
185 if self
._CurrentChangesetId
== -1:
186 raise Exception, "No changeset currently opened"
187 if NodeData
.get(u
"id", -1) > 0:
188 raise Exception, "This way already exists"
189 WayData
[u
"changeset"] = self
._CurrentChangesetId
190 result
= self
._put
("/api/0.6/way/create", self
._XmlBuild
("way", WayData
))
191 WayData
[u
"id"] = int(result
.strip())
192 WayData
[u
"version"] = 1
193 if u
"timestamp" in WayData
: WayData
.pop(u
"timestamp")
196 def WayHistory(self
, WayId
):
197 """ Returns dict(WayVerrsion: WayData). """
198 uri
= "/api/0.6/way/"+str(WayId
)+"/history"
199 data
= self
._get
(uri
)
200 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
202 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("way"):
203 data
= self
._DomParseWay
(data
)
204 result
[data
[u
"version"]] = data
207 def WayRelations(self
, WayId
):
208 """ Returns [RelationData, ...] containing way #WayId. """
209 # GET way/#/relations
212 def WayFull(self
, WayId
):
213 """ Will not be implemented. """
216 def WaysGet(self
, WayIdList
):
217 """ Will not be implemented. """
220 #######################################################################
222 #######################################################################
224 def RelationGet(self
, RelationId
, RelationVersion
= -1):
225 """ Returns RelationData for relation #RelationId. """
226 uri
= "/api/0.6/relation/"+str(RelationId
)
227 if RelationVersion
<> -1: uri
+= "/"+str(RelationVersion
)
228 data
= self
._get
(uri
)
230 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
231 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("relation")[0]
232 return self
._DomParseRelation
(data
)
234 def RelationUpdate(self
, RelationData
):
235 """ Updates relation with RelationData. Returns updated RelationData (without timestamp). """
236 if self
._CurrentChangesetId
== -1:
237 raise Exception, "No changeset currently opened"
238 RelationData
[u
"changeset"] = self
._CurrentChangesetId
239 result
= self
._put
("/api/0.6/relation/"+str(RelationData
[u
"id"]), self
._XmlBuild
("relation", RelationData
))
240 RelationData
[u
"version"] = int(result
.strip())
241 if u
"timestamp" in RelationData
: RelationData
.pop(u
"timestamp")
244 def RelationDelete(self
, RelationData
):
245 """ Delete relation with RelationData. Returns updated RelationData (without timestamp). """
246 if self
._CurrentChangesetId
== -1:
247 raise Exception, "No changeset currently opened"
248 RelationData
[u
"changeset"] = self
._CurrentChangesetId
249 result
= self
._delete
("/api/0.6/relation/"+str(RelationData
[u
"id"]), self
._XmlBuild
("relation", RelationData
))
250 RelationData
[u
"version"] = int(result
.strip())
251 RelationData
[u
"visible"] = False
252 if u
"timestamp" in RelationData
: RelationData
.pop(u
"timestamp")
255 def RelationCreate(self
, RelationData
):
256 """ Creates a relation. Returns updated RelationData (without timestamp). """
257 if self
._CurrentChangesetId
== -1:
258 raise Exception, "No changeset currently opened"
259 if NodeData
.get(u
"id", -1) > 0:
260 raise Exception, "This relation already exists"
261 RelationData
[u
"changeset"] = self
._CurrentChangesetId
262 result
= self
._put
("/api/0.6/relation/create", self
._XmlBuild
("relation", RelationData
))
263 RelationData
[u
"id"] = int(result
.strip())
264 RelationData
[u
"version"] = 1
265 if u
"timestamp" in RelationData
: RelationData
.pop(u
"timestamp")
268 def RelationHistory(self
, RelationId
):
269 """ Returns dict(RelationVerrsion: RelationData). """
270 uri
= "/api/0.6/relation/"+str(RelationId
)+"/history"
271 data
= self
._get
(uri
)
272 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
274 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
275 data
= self
._DomParseRelation
(data
)
276 result
[data
[u
"version"]] = data
279 def RelationRelations(self
, RelationId
):
280 """ Returns list of RelationData containing relation #RelationId. """
281 # GET relation/#/relations TODO
284 def RelationFull(self
, RelationId
):
285 """ Will not be implemented. """
288 def RelationsGet(self
, RelationIdList
):
289 """ Will not be implemented. """
292 #######################################################################
294 #######################################################################
296 def ChangesetGet(self
, ChangesetId
):
297 """ Returns ChangesetData for changeset #ChangesetId. """
298 data
= self
._get
("/api/0.6/changeset/"+str(ChangesetId
))
299 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
300 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("changeset")[0]
301 return self
._DomParseChangeset
(data
)
303 def ChangesetUpdate(self
, ChangesetTags
= {}):
304 """ Updates current changeset with ChangesetTags. """
305 if self
._CurrentChangesetId
== -1:
306 raise Exception, "No changeset currently opened"
307 if u
"created_by" not in ChangesetTags
:
308 ChangesetTags
[u
"created_by"] = self
._created
_by
309 result
= self
._put
("/api/0.6/changeset/"+str(self
._CurrentChangesetId
), self
._XmlBuild
("changeset", {u
"tag": ChangesetTags
}))
310 return self
._CurrentChangesetId
312 def ChangesetCreate(self
, ChangesetTags
= {}):
313 """ Opens a changeset. Returns #ChangesetId. """
314 if self
._CurrentChangesetId
<> -1:
315 raise Exception, "Changeset alreadey 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/create", self
._XmlBuild
("changeset", {u
"tag": ChangesetTags
}))
319 self
._CurrentChangesetId
= int(result
)
320 self
._CurrentChangesetTags
= ChangesetTags
321 self
._CurrentChangesetCpt
= 0
322 return self
._CurrentChangesetId
324 def ChangesetClose(self
):
325 """ Closes current changeset. Returns #ChangesetId. """
326 if self
._CurrentChangesetId
== -1:
327 raise Exception, "No changeset currently opened"
328 result
= self
._put
("/api/0.6/changeset/"+str(self
._CurrentChangesetId
)+"/close", u
"")
329 CurrentChangesetId
= self
._CurrentChangesetId
330 self
._CurrentChangesetId
= -1
331 return CurrentChangesetId
333 def ChangesetUpload(self
):
336 def ChangesetDownload(self
, ChangesetId
):
337 """ Download data from a changeset. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
338 uri
= "/api/0.6/changeset/"+str(ChangesetId
)+"/download"
339 data
= self
._get
(uri
)
340 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
341 data
= data
.getElementsByTagName("osmChange")[0]
343 for action
in data
.childNodes
:
344 if action
.nodeName
== u
"#text": continue
345 for elem
in action
.childNodes
:
346 if elem
.nodeName
== u
"node":
347 result
.append({u
"action":action
.nodeName
, u
"type": elem
.nodeName
, u
"data": self
._DomParseNode
(elem
)})
348 elif elem
.nodeName
== u
"way":
349 result
.append({u
"action":action
.nodeName
, u
"type": elem
.nodeName
, u
"data": self
._DomParseWay
(elem
)})
350 elif elem
.nodeName
== u
"relation":
351 result
.append({u
"action":action
.nodeName
, u
"type": elem
.nodeName
, u
"data": self
._DomParseRelation
(elem
)})
354 def ChangesetsGet(self
):
357 #######################################################################
359 #######################################################################
364 def Trackpoints(self
):
370 #######################################################################
371 # Internal http function #
372 #######################################################################
374 def _http(self
, cmd
, path
, auth
, send
):
375 h
= httplib
.HTTPConnection(self
._api
, 80)
376 h
.putrequest(cmd
, path
)
377 h
.putheader('User-Agent', self
._created
_by
)
379 h
.putheader('Authorization', 'Basic ' + base64
.encodestring(self
._username
+ ':' + self
._password
).strip())
381 send
= send
.encode("utf-8")
382 h
.putheader('Content-Length', len(send
))
386 response
= h
.getresponse()
387 if response
.status
<> 200:
388 raise Exception, "API returns unexpected status code "+str(response
.status
)+" ("+response
.reason
+")"
389 return response
.read().decode("utf-8")
391 def _get(self
, path
):
392 return self
._http
('GET', path
, False, None)
394 def _put(self
, path
, data
):
395 return self
._http
('PUT', path
, True, data
)
397 def _delete(self
, path
, data
):
398 return self
._http
('DELETE', path
, True, data
)
400 #######################################################################
401 # Internal dom function #
402 #######################################################################
404 def _DomGetAttributes(self
, DomElement
):
405 """ Returns a formated dictionnary of attributes of a DomElement. """
407 for k
, v
in DomElement
.attributes
.items():
408 k
= k
#.decode("utf8")
409 v
= v
#.decode("utf8")
410 if k
== u
"uid" : v
= int(v
)
411 elif k
== u
"changeset" : v
= int(v
)
412 elif k
== u
"version" : v
= int(v
)
413 elif k
== u
"id" : v
= int(v
)
414 elif k
== u
"lat" : v
= float(v
)
415 elif k
== u
"lon" : v
= float(v
)
416 elif k
== u
"open" : v
= v
=="true"
417 elif k
== u
"visible" : v
= v
=="true"
418 elif k
== u
"ref" : v
= int(v
)
422 def _DomGetTag(self
, DomElement
):
423 """ Returns the dictionnary of tags of a DomElement. """
425 for t
in DomElement
.getElementsByTagName("tag"):
426 k
= t
.attributes
["k"].value
#.decode("utf8")
427 v
= t
.attributes
["v"].value
#.decode("utf8")
431 def _DomGetNd(self
, DomElement
):
432 """ Returns the list of nodes of a DomElement. """
434 for t
in DomElement
.getElementsByTagName("nd"):
435 result
.append(int(int(t
.attributes
["ref"].value
)))
438 def _DomGetMember(self
, DomElement
):
439 """ Returns a list of relation members. """
441 for m
in DomElement
.getElementsByTagName("member"):
442 result
.append(self
._DomGetAttributes
(m
))
445 def _DomParseNode(self
, DomElement
):
446 """ Returns NodeData for the node. """
447 result
= self
._DomGetAttributes
(DomElement
)
448 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
451 def _DomParseWay(self
, DomElement
):
452 """ Returns WayData for the way. """
453 result
= self
._DomGetAttributes
(DomElement
)
454 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
455 result
[u
"nd"] = self
._DomGetNd
(DomElement
)
458 def _DomParseRelation(self
, DomElement
):
459 """ Returns RelationData for the relation. """
460 result
= self
._DomGetAttributes
(DomElement
)
461 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
462 result
[u
"member"] = self
._DomGetMember
(DomElement
)
465 def _DomParseChangeset(self
, DomElement
):
466 """ Returns ChangesetData for the changeset. """
467 result
= self
._DomGetAttributes
(DomElement
)
468 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
471 #######################################################################
472 # Internal xml builder #
473 #######################################################################
475 def _XmlBuild(self
, ElementType
, ElementData
):
478 xml
+= u
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
479 xml
+= u
"<osm version=\"0.6\" generator=\"" + self
._created
_by
+ "\">\n"
481 # <element attr="val">
482 xml
+= u
" <" + ElementType
483 if u
"id" in ElementData
:
484 xml
+= u
" id=\"" + str(ElementData
[u
"id"]) + u
"\""
485 if u
"lat" in ElementData
:
486 xml
+= u
" lat=\"" + str(ElementData
[u
"lat"]) + u
"\""
487 if u
"lon" in ElementData
:
488 xml
+= u
" lon=\"" + str(ElementData
[u
"lon"]) + u
"\""
489 if u
"version" in ElementData
:
490 xml
+= u
" version=\"" + str(ElementData
[u
"version"]) + u
"\""
491 xml
+= u
" visible=\"" + str(ElementData
.get(u
"visible", True)).lower() + u
"\""
492 if ElementType
in [u
"node", u
"way", u
"relation"]:
493 xml
+= u
" changeset=\"" + str(self
._CurrentChangesetId
) + u
"\""
497 for k
, v
in ElementData
.get(u
"tag", {}).items():
498 xml
+= u
" <tag k=\""+self
._XmlEncode
(k
)+u
"\" v=\""+self
._XmlEncode
(v
)+u
"\"/>\n"
501 for member
in ElementData
.get(u
"member", []):
502 xml
+= u
" <member type=\""+member
[u
"type"]+"\" ref=\""+str(member
[u
"ref"])+u
"\" role=\""+self
._XmlEncode
(member
[u
"role"])+"\"/>\n"
505 for ref
in ElementData
.get(u
"nd", []):
506 xml
+= u
" <nd ref=\""+str(ref
)+u
"\"/>\n"
509 xml
+= u
" </" + ElementType
+ u
">\n"
515 def _XmlEncode(self
, text
):
516 return text
.replace("&", "&").replace("\"", """)
518 ## End of main class ##
519 ###########################################################################
522 ###########################################################################
523 ## Reverting tools : DO NOT USE SINCE IT'S INT DEV ##
525 def RevertAnalyse(self
, ChangesetId
):
526 """ Returns a string saying if changes are revertable or not. """
528 data
= self
.ChangesetDownload(ChangesetId
)
533 if elem
[u
"type"] == u
"node":
534 hist
= self
.NodeHistory(elem
[u
"data"][u
"id"])
535 elem_latest
= hist
[sorted(hist
.keys())[-1]]
536 elif elem
[u
"type"] == u
"way":
537 hist
= self
.WayHistory(elem
[u
"data"][u
"id"])
538 elem_latest
= hist
[sorted(hist
.keys())[-1]]
539 elif elem
[u
"type"] == u
"relation":
540 hist
= self
.RelationHistory(elem
[u
"data"][u
"id"])
541 elem_latest
= hist
[sorted(hist
.keys())[-1]]
543 result
+= elem
[u
"action"] + u
" " * (10-len(elem
[u
"action"]))
544 result
+= elem
[u
"type"] + u
" " * (10-len(elem
[u
"type"]))
545 result
+= str(elem
[u
"data"][u
"id"]) + u
" " * (10-len(str(elem
[u
"data"][u
"id"])))
547 if elem
[u
"data"][u
"version"] == elem_latest
[u
"version"]:
548 result
+= u
"revertable\n"
550 result
+= u
"not revertable\n"
552 return result
.strip()
554 def RevertIfPossible(self
, ChangesetId
):
555 """ Revert all changes if possible. """
559 #print RevertAnalyse(z, 912290)
561 ## End of reverting tools ##
562 ###########################################################################