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.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 ###########################################################################
38 import httplib
, base64
, xml
.dom
.minidom
, time
40 ###########################################################################
45 def __init__(self
, username
= None, password
= None, passwordfile
= None, appid
= "", created_by
= "PythonOsmApi/"+__version__
, api
= "www.openstreetmap.org"):
49 self
._username
= username
51 self
._username
= open(passwordfile
).readline().split(":")[0].strip()
55 self
._password
= password
57 for l
in open(passwordfile
).readlines():
58 l
= l
.strip().split(":")
59 if l
[0] == self
._username
:
67 self
._created
_by
= created_by
69 self
._created
_by
= appid
+ " (" + created_by
+ ")"
72 self
._CurrentChangesetId
= -1
75 self
._conn
= httplib
.HTTPConnection(self
._api
, 80)
77 #######################################################################
79 #######################################################################
81 def Capabilities(self
):
84 #######################################################################
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
)
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")
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")
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")
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"))
138 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("node"):
139 data
= self
._DomParseNode
(data
)
140 result
[data
[u
"version"]] = data
143 def NodeWays(self
, NodeId
):
144 """ Returns [WayData, ... ] containing node #NodeId. """
145 # GET node/#/ways TODO
148 def NodeRelations(self
, NodeId
):
149 """ Returns [RelationData, ... ] containing node #NodeId. """
150 # GET node/#/relations TODO
153 def NodesGet(self
, NodeIdList
):
154 """ Will not be implemented. """
157 #######################################################################
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")
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")
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")
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"))
211 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("way"):
212 data
= self
._DomParseWay
(data
)
213 result
[data
[u
"version"]] = data
216 def WayRelations(self
, WayId
):
217 """ Returns [RelationData, ...] containing way #WayId. """
218 # GET way/#/relations
221 def WayFull(self
, WayId
):
222 """ Will not be implemented. """
225 def WaysGet(self
, WayIdList
):
226 """ Will not be implemented. """
229 #######################################################################
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")
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")
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")
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"))
283 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
284 data
= self
._DomParseRelation
(data
)
285 result
[data
[u
"version"]] = data
288 def RelationRelations(self
, RelationId
):
289 """ Returns list of RelationData containing relation #RelationId. """
290 # GET relation/#/relations TODO
293 def RelationFull(self
, RelationId
):
294 """ Will not be implemented. """
297 def RelationsGet(self
, RelationIdList
):
298 """ Will not be implemented. """
301 #######################################################################
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
):
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]
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
)})
363 def ChangesetsGet(self
):
366 #######################################################################
368 #######################################################################
373 def Trackpoints(self
):
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
)
387 self
._conn
.putheader('Authorization', 'Basic ' + base64
.encodestring(self
._username
+ ':' + self
._password
).strip())
389 send
= send
.encode("utf-8")
390 self
._conn
.putheader('Content-Length', len(send
))
391 self
._conn
.endheaders()
393 self
._conn
.send(send
)
394 response
= self
._conn
.getresponse()
395 if response
.status
<> 200:
397 if response
.status
== 410:
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
):
407 return self
._http
_request
(cmd
, path
, auth
, send
)
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. """
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
)
444 def _DomGetTag(self
, DomElement
):
445 """ Returns the dictionnary of tags of a DomElement. """
447 for t
in DomElement
.getElementsByTagName("tag"):
448 k
= t
.attributes
["k"].value
#.decode("utf8")
449 v
= t
.attributes
["v"].value
#.decode("utf8")
453 def _DomGetNd(self
, DomElement
):
454 """ Returns the list of nodes of a DomElement. """
456 for t
in DomElement
.getElementsByTagName("nd"):
457 result
.append(int(int(t
.attributes
["ref"].value
)))
460 def _DomGetMember(self
, DomElement
):
461 """ Returns a list of relation members. """
463 for m
in DomElement
.getElementsByTagName("member"):
464 result
.append(self
._DomGetAttributes
(m
))
467 def _DomParseNode(self
, DomElement
):
468 """ Returns NodeData for the node. """
469 result
= self
._DomGetAttributes
(DomElement
)
470 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
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
)
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
)
487 def _DomParseChangeset(self
, DomElement
):
488 """ Returns ChangesetData for the changeset. """
489 result
= self
._DomGetAttributes
(DomElement
)
490 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
493 #######################################################################
494 # Internal xml builder #
495 #######################################################################
497 def _XmlBuild(self
, ElementType
, ElementData
):
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
"\""
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"
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"
527 for ref
in ElementData
.get(u
"nd", []):
528 xml
+= u
" <nd ref=\""+str(ref
)+u
"\"/>\n"
531 xml
+= u
" </" + ElementType
+ u
">\n"
537 def _XmlEncode(self
, text
):
538 return text
.replace("&", "&").replace("\"", """)
540 ## End of main class ##
541 ###########################################################################