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.5 2009-10-09 implements NodesGet, WaysGet, RelationsGet ##
28 ## ParseOsm, ParseOsc ##
29 ## 0.2.4 2009-10-06 clean-up ##
30 ## 0.2.3 2009-09-09 keep http connection alive for multiple request ##
31 ## (Node|Way|Relation)Get return None when object ##
32 ## have been deleted (raising error before) ##
33 ## 0.2.2 2009-07-13 can identify applications built on top of the lib ##
34 ## 0.2.1 2009-05-05 some changes in constructor -- chove@crans.org ##
35 ## 0.2 2009-05-01 initial import ##
36 ###########################################################################
40 import httplib
, base64
, xml
.dom
.minidom
, time
42 ###########################################################################
47 def __init__(self
, username
= None, password
= None, passwordfile
= None, appid
= "", created_by
= "PythonOsmApi/"+__version__
, api
= "www.openstreetmap.org"):
51 self
._username
= username
53 self
._username
= open(passwordfile
).readline().split(":")[0].strip()
57 self
._password
= password
59 for l
in open(passwordfile
).readlines():
60 l
= l
.strip().split(":")
61 if l
[0] == self
._username
:
69 self
._created
_by
= created_by
71 self
._created
_by
= appid
+ " (" + created_by
+ ")"
74 self
._CurrentChangesetId
= -1
77 self
._conn
= httplib
.HTTPConnection(self
._api
, 80)
79 #######################################################################
81 #######################################################################
83 def Capabilities(self
):
86 #######################################################################
88 #######################################################################
90 def NodeGet(self
, NodeId
, NodeVersion
= -1):
91 """ Returns NodeData for node #NodeId. """
92 uri
= "/api/0.6/node/"+str(NodeId
)
93 if NodeVersion
<> -1: uri
+= "/"+str(NodeVersion
)
95 if not data
: return data
96 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
97 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("node")[0]
98 return self
._DomParseNode
(data
)
100 def NodeUpdate(self
, NodeData
):
101 """ Updates 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
._put
("/api/0.6/node/"+str(NodeData
[u
"id"]), self
._XmlBuild
("node", NodeData
))
106 NodeData
[u
"version"] = int(result
.strip())
107 if u
"timestamp" in NodeData
: NodeData
.pop(u
"timestamp")
110 def NodeDelete(self
, NodeData
):
111 """ Delete node with NodeData. Returns updated NodeData (without timestamp). """
112 if self
._CurrentChangesetId
== -1:
113 raise Exception, "No changeset currently opened"
114 NodeData
[u
"changeset"] = self
._CurrentChangesetId
115 result
= self
._delete
("/api/0.6/node/"+str(NodeData
[u
"id"]), self
._XmlBuild
("node", NodeData
))
116 NodeData
[u
"version"] = int(result
.strip())
117 NodeData
[u
"visible"] = False
118 if u
"timestamp" in NodeData
: NodeData
.pop(u
"timestamp")
121 def NodeCreate(self
, NodeData
):
122 """ Creates a node. Returns updated NodeData (without timestamp). """
123 if self
._CurrentChangesetId
== -1:
124 raise Exception, "No changeset currently opened"
125 if NodeData
.get(u
"id", -1) > 0:
126 raise Exception, "This node already exists"
127 NodeData
[u
"changeset"] = self
._CurrentChangesetId
128 result
= self
._put
("/api/0.6/node/create", self
._XmlBuild
("node", NodeData
))
129 NodeData
[u
"id"] = int(result
.strip())
130 NodeData
[u
"version"] = 1
131 if u
"timestamp" in NodeData
: NodeData
.pop(u
"timestamp")
134 def NodeHistory(self
, NodeId
):
135 """ Returns dict(NodeVerrsion: NodeData). """
136 uri
= "/api/0.6/node/"+str(NodeId
)+"/history"
137 data
= self
._get
(uri
)
138 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
140 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("node"):
141 data
= self
._DomParseNode
(data
)
142 result
[data
[u
"version"]] = data
145 def NodeWays(self
, NodeId
):
146 """ Returns [WayData, ... ] containing node #NodeId. """
147 # GET node/#/ways TODO
150 def NodeRelations(self
, NodeId
):
151 """ Returns [RelationData, ... ] containing node #NodeId. """
152 # GET node/#/relations TODO
155 def NodesGet(self
, NodeIdList
):
156 """ Returns dict(NodeId: NodeData) for each node in NodeIdList """
157 uri
= "/api/0.6/nodes?nodes=" + ",".join([str(x
) for x
in NodeIdList
])
158 data
= self
._get
(uri
)
159 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
161 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("node"):
162 data
= self
._DomParseNode
(data
)
163 result
[data
[u
"id"]] = data
166 #######################################################################
168 #######################################################################
170 def WayGet(self
, WayId
, WayVersion
= -1):
171 """ Returns WayData for way #WayId. """
172 uri
= "/api/0.6/way/"+str(WayId
)
173 if WayVersion
<> -1: uri
+= "/"+str(WayVersion
)
174 data
= self
._get
(uri
)
175 if not data
: return data
176 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
177 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("way")[0]
178 return self
._DomParseWay
(data
)
180 def WayUpdate(self
, WayData
):
181 """ Updates way with WayData. Returns updated WayData (without timestamp). """
182 if self
._CurrentChangesetId
== -1:
183 raise Exception, "No changeset currently opened"
184 WayData
[u
"changeset"] = self
._CurrentChangesetId
185 result
= self
._put
("/api/0.6/way/"+str(WayData
[u
"id"]), self
._XmlBuild
("way", WayData
))
186 WayData
[u
"version"] = int(result
.strip())
187 if u
"timestamp" in WayData
: WayData
.pop(u
"timestamp")
190 def WayDelete(self
, WayData
):
191 """ Delete way with WayData. Returns updated WayData (without timestamp). """
192 if self
._CurrentChangesetId
== -1:
193 raise Exception, "No changeset currently opened"
194 WayData
[u
"changeset"] = self
._CurrentChangesetId
195 result
= self
._delete
("/api/0.6/way/"+str(WayData
[u
"id"]), self
._XmlBuild
("way", WayData
))
196 WayData
[u
"version"] = int(result
.strip())
197 WayData
[u
"visible"] = False
198 if u
"timestamp" in WayData
: WayData
.pop(u
"timestamp")
201 def WayCreate(self
, WayData
):
202 """ Creates a way. Returns updated WayData (without timestamp). """
203 if self
._CurrentChangesetId
== -1:
204 raise Exception, "No changeset currently opened"
205 if NodeData
.get(u
"id", -1) > 0:
206 raise Exception, "This way already exists"
207 WayData
[u
"changeset"] = self
._CurrentChangesetId
208 result
= self
._put
("/api/0.6/way/create", self
._XmlBuild
("way", WayData
))
209 WayData
[u
"id"] = int(result
.strip())
210 WayData
[u
"version"] = 1
211 if u
"timestamp" in WayData
: WayData
.pop(u
"timestamp")
214 def WayHistory(self
, WayId
):
215 """ Returns dict(WayVerrsion: WayData). """
216 uri
= "/api/0.6/way/"+str(WayId
)+"/history"
217 data
= self
._get
(uri
)
218 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
220 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("way"):
221 data
= self
._DomParseWay
(data
)
222 result
[data
[u
"version"]] = data
225 def WayRelations(self
, WayId
):
226 """ Returns [RelationData, ...] containing way #WayId. """
227 # GET way/#/relations
230 def WayFull(self
, WayId
):
231 """ Will not be implemented. """
234 def WaysGet(self
, WayIdList
):
235 """ Returns dict(WayId: WayData) for each way in WayIdList """
236 uri
= "/api/0.6/ways?ways=" + ",".join([str(x
) for x
in WayIdList
])
237 data
= self
._get
(uri
)
238 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
240 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("way"):
241 data
= self
._DomParseWay
(data
)
242 result
[data
[u
"id"]] = data
245 #######################################################################
247 #######################################################################
249 def RelationGet(self
, RelationId
, RelationVersion
= -1):
250 """ Returns RelationData for relation #RelationId. """
251 uri
= "/api/0.6/relation/"+str(RelationId
)
252 if RelationVersion
<> -1: uri
+= "/"+str(RelationVersion
)
253 data
= self
._get
(uri
)
254 if not data
: return data
255 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
256 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("relation")[0]
257 return self
._DomParseRelation
(data
)
259 def RelationUpdate(self
, RelationData
):
260 """ Updates relation with RelationData. Returns updated RelationData (without timestamp). """
261 if self
._CurrentChangesetId
== -1:
262 raise Exception, "No changeset currently opened"
263 RelationData
[u
"changeset"] = self
._CurrentChangesetId
264 result
= self
._put
("/api/0.6/relation/"+str(RelationData
[u
"id"]), self
._XmlBuild
("relation", RelationData
))
265 RelationData
[u
"version"] = int(result
.strip())
266 if u
"timestamp" in RelationData
: RelationData
.pop(u
"timestamp")
269 def RelationDelete(self
, RelationData
):
270 """ Delete relation with RelationData. Returns updated RelationData (without timestamp). """
271 if self
._CurrentChangesetId
== -1:
272 raise Exception, "No changeset currently opened"
273 RelationData
[u
"changeset"] = self
._CurrentChangesetId
274 result
= self
._delete
("/api/0.6/relation/"+str(RelationData
[u
"id"]), self
._XmlBuild
("relation", RelationData
))
275 RelationData
[u
"version"] = int(result
.strip())
276 RelationData
[u
"visible"] = False
277 if u
"timestamp" in RelationData
: RelationData
.pop(u
"timestamp")
280 def RelationCreate(self
, RelationData
):
281 """ Creates a relation. Returns updated RelationData (without timestamp). """
282 if self
._CurrentChangesetId
== -1:
283 raise Exception, "No changeset currently opened"
284 if NodeData
.get(u
"id", -1) > 0:
285 raise Exception, "This relation already exists"
286 RelationData
[u
"changeset"] = self
._CurrentChangesetId
287 result
= self
._put
("/api/0.6/relation/create", self
._XmlBuild
("relation", RelationData
))
288 RelationData
[u
"id"] = int(result
.strip())
289 RelationData
[u
"version"] = 1
290 if u
"timestamp" in RelationData
: RelationData
.pop(u
"timestamp")
293 def RelationHistory(self
, RelationId
):
294 """ Returns dict(RelationVerrsion: RelationData). """
295 uri
= "/api/0.6/relation/"+str(RelationId
)+"/history"
296 data
= self
._get
(uri
)
297 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
299 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
300 data
= self
._DomParseRelation
(data
)
301 result
[data
[u
"version"]] = data
304 def RelationRelations(self
, RelationId
):
305 """ Returns list of RelationData containing relation #RelationId. """
306 # GET relation/#/relations TODO
309 def RelationFull(self
, RelationId
):
310 """ Will not be implemented. """
313 def RelationsGet(self
, RelationIdList
):
314 """ Returns dict(RelationId: RelationData) for each relation in RelationIdList """
315 uri
= "/api/0.6/relations?relations=" + ",".join([str(x
) for x
in RelationIdList
])
316 data
= self
._get
(uri
)
317 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
319 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
320 data
= self
._DomParseRelation
(data
)
321 result
[data
[u
"id"]] = data
324 #######################################################################
326 #######################################################################
328 def ChangesetGet(self
, ChangesetId
):
329 """ Returns ChangesetData for changeset #ChangesetId. """
330 data
= self
._get
("/api/0.6/changeset/"+str(ChangesetId
))
331 data
= xml
.dom
.minidom
.parseString(data
.encode("utf-8"))
332 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("changeset")[0]
333 return self
._DomParseChangeset
(data
)
335 def ChangesetUpdate(self
, ChangesetTags
= {}):
336 """ Updates current changeset with ChangesetTags. """
337 if self
._CurrentChangesetId
== -1:
338 raise Exception, "No changeset currently opened"
339 if u
"created_by" not in ChangesetTags
:
340 ChangesetTags
[u
"created_by"] = self
._created
_by
341 result
= self
._put
("/api/0.6/changeset/"+str(self
._CurrentChangesetId
), self
._XmlBuild
("changeset", {u
"tag": ChangesetTags
}))
342 return self
._CurrentChangesetId
344 def ChangesetCreate(self
, ChangesetTags
= {}):
345 """ Opens a changeset. Returns #ChangesetId. """
346 if self
._CurrentChangesetId
<> -1:
347 raise Exception, "Changeset alreadey opened"
348 if u
"created_by" not in ChangesetTags
:
349 ChangesetTags
[u
"created_by"] = self
._created
_by
350 result
= self
._put
("/api/0.6/changeset/create", self
._XmlBuild
("changeset", {u
"tag": ChangesetTags
}))
351 self
._CurrentChangesetId
= int(result
)
352 self
._CurrentChangesetTags
= ChangesetTags
353 self
._CurrentChangesetCpt
= 0
354 return self
._CurrentChangesetId
356 def ChangesetClose(self
):
357 """ Closes current changeset. Returns #ChangesetId. """
358 if self
._CurrentChangesetId
== -1:
359 raise Exception, "No changeset currently opened"
360 result
= self
._put
("/api/0.6/changeset/"+str(self
._CurrentChangesetId
)+"/close", u
"")
361 CurrentChangesetId
= self
._CurrentChangesetId
362 self
._CurrentChangesetId
= -1
363 return CurrentChangesetId
365 def ChangesetUpload(self
):
368 def ChangesetDownload(self
, ChangesetId
):
369 """ Download data from a changeset. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
370 uri
= "/api/0.6/changeset/"+str(ChangesetId
)+"/download"
371 data
= self
._get
(uri
)
372 return self
.ParseOsc(data
.encode("utf-8"))
374 def ChangesetsGet(self
):
377 #######################################################################
379 #######################################################################
384 def Trackpoints(self
):
390 #######################################################################
392 #######################################################################
394 def ParseOsm(self
, data
):
395 """ Parse osm data. Returns list of dict {type: node|way|relation, data: {}}. """
396 data
= xml
.dom
.minidom
.parseString(data
)
397 data
= data
.getElementsByTagName("osm")[0]
399 for elem
in data
.childNodes
:
400 if elem
.nodeName
== u
"node":
401 result
.append({u
"type": elem
.nodeName
, u
"data": self
._DomParseNode
(elem
)})
402 elif elem
.nodeName
== u
"way":
403 result
.append({u
"type": elem
.nodeName
, u
"data": self
._DomParseWay
(elem
)})
404 elif elem
.nodeName
== u
"relation":
405 result
.append({u
"type": elem
.nodeName
, u
"data": self
._DomParseRelation
(elem
)})
408 def ParseOsc(self
, data
):
409 """ Parse osc data. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
410 data
= xml
.dom
.minidom
.parseString(data
)
411 data
= data
.getElementsByTagName("osmChange")[0]
413 for action
in data
.childNodes
:
414 if action
.nodeName
== u
"#text": continue
415 for elem
in action
.childNodes
:
416 if elem
.nodeName
== u
"node":
417 result
.append({u
"action":action
.nodeName
, u
"type": elem
.nodeName
, u
"data": self
._DomParseNode
(elem
)})
418 elif elem
.nodeName
== u
"way":
419 result
.append({u
"action":action
.nodeName
, u
"type": elem
.nodeName
, u
"data": self
._DomParseWay
(elem
)})
420 elif elem
.nodeName
== u
"relation":
421 result
.append({u
"action":action
.nodeName
, u
"type": elem
.nodeName
, u
"data": self
._DomParseRelation
(elem
)})
424 #######################################################################
425 # Internal http function #
426 #######################################################################
428 def _http_request(self
, cmd
, path
, auth
, send
):
429 self
._conn
.putrequest(cmd
, path
)
430 self
._conn
.putheader('User-Agent', self
._created
_by
)
432 self
._conn
.putheader('Authorization', 'Basic ' + base64
.encodestring(self
._username
+ ':' + self
._password
).strip())
434 send
= send
.encode("utf-8")
435 self
._conn
.putheader('Content-Length', len(send
))
436 self
._conn
.endheaders()
438 self
._conn
.send(send
)
439 response
= self
._conn
.getresponse()
440 if response
.status
<> 200:
442 if response
.status
== 410:
444 raise Exception, "API returns unexpected status code "+str(response
.status
)+" ("+response
.reason
+")"
445 return response
.read().decode("utf-8")
447 def _http(self
, cmd
, path
, auth
, send
):
452 return self
._http
_request
(cmd
, path
, auth
, send
)
455 if i
<> 1: time
.sleep(2)
456 self
._conn
= httplib
.HTTPConnection(self
._api
, 80)
458 def _get(self
, path
):
459 return self
._http
('GET', path
, False, None)
461 def _put(self
, path
, data
):
462 return self
._http
('PUT', path
, True, data
)
464 def _delete(self
, path
, data
):
465 return self
._http
('DELETE', path
, True, data
)
467 #######################################################################
468 # Internal dom function #
469 #######################################################################
471 def _DomGetAttributes(self
, DomElement
):
472 """ Returns a formated dictionnary of attributes of a DomElement. """
474 for k
, v
in DomElement
.attributes
.items():
475 k
= k
#.decode("utf8")
476 v
= v
#.decode("utf8")
477 if k
== u
"uid" : v
= int(v
)
478 elif k
== u
"changeset" : v
= int(v
)
479 elif k
== u
"version" : v
= int(v
)
480 elif k
== u
"id" : v
= int(v
)
481 elif k
== u
"lat" : v
= float(v
)
482 elif k
== u
"lon" : v
= float(v
)
483 elif k
== u
"open" : v
= v
=="true"
484 elif k
== u
"visible" : v
= v
=="true"
485 elif k
== u
"ref" : v
= int(v
)
489 def _DomGetTag(self
, DomElement
):
490 """ Returns the dictionnary of tags of a DomElement. """
492 for t
in DomElement
.getElementsByTagName("tag"):
493 k
= t
.attributes
["k"].value
#.decode("utf8")
494 v
= t
.attributes
["v"].value
#.decode("utf8")
498 def _DomGetNd(self
, DomElement
):
499 """ Returns the list of nodes of a DomElement. """
501 for t
in DomElement
.getElementsByTagName("nd"):
502 result
.append(int(int(t
.attributes
["ref"].value
)))
505 def _DomGetMember(self
, DomElement
):
506 """ Returns a list of relation members. """
508 for m
in DomElement
.getElementsByTagName("member"):
509 result
.append(self
._DomGetAttributes
(m
))
512 def _DomParseNode(self
, DomElement
):
513 """ Returns NodeData for the node. """
514 result
= self
._DomGetAttributes
(DomElement
)
515 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
518 def _DomParseWay(self
, DomElement
):
519 """ Returns WayData for the way. """
520 result
= self
._DomGetAttributes
(DomElement
)
521 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
522 result
[u
"nd"] = self
._DomGetNd
(DomElement
)
525 def _DomParseRelation(self
, DomElement
):
526 """ Returns RelationData for the relation. """
527 result
= self
._DomGetAttributes
(DomElement
)
528 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
529 result
[u
"member"] = self
._DomGetMember
(DomElement
)
532 def _DomParseChangeset(self
, DomElement
):
533 """ Returns ChangesetData for the changeset. """
534 result
= self
._DomGetAttributes
(DomElement
)
535 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
538 #######################################################################
539 # Internal xml builder #
540 #######################################################################
542 def _XmlBuild(self
, ElementType
, ElementData
):
545 xml
+= u
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
546 xml
+= u
"<osm version=\"0.6\" generator=\"" + self
._created
_by
+ "\">\n"
548 # <element attr="val">
549 xml
+= u
" <" + ElementType
550 if u
"id" in ElementData
:
551 xml
+= u
" id=\"" + str(ElementData
[u
"id"]) + u
"\""
552 if u
"lat" in ElementData
:
553 xml
+= u
" lat=\"" + str(ElementData
[u
"lat"]) + u
"\""
554 if u
"lon" in ElementData
:
555 xml
+= u
" lon=\"" + str(ElementData
[u
"lon"]) + u
"\""
556 if u
"version" in ElementData
:
557 xml
+= u
" version=\"" + str(ElementData
[u
"version"]) + u
"\""
558 xml
+= u
" visible=\"" + str(ElementData
.get(u
"visible", True)).lower() + u
"\""
559 if ElementType
in [u
"node", u
"way", u
"relation"]:
560 xml
+= u
" changeset=\"" + str(self
._CurrentChangesetId
) + u
"\""
564 for k
, v
in ElementData
.get(u
"tag", {}).items():
565 xml
+= u
" <tag k=\""+self
._XmlEncode
(k
)+u
"\" v=\""+self
._XmlEncode
(v
)+u
"\"/>\n"
568 for member
in ElementData
.get(u
"member", []):
569 xml
+= u
" <member type=\""+member
[u
"type"]+"\" ref=\""+str(member
[u
"ref"])+u
"\" role=\""+self
._XmlEncode
(member
[u
"role"])+"\"/>\n"
572 for ref
in ElementData
.get(u
"nd", []):
573 xml
+= u
" <nd ref=\""+str(ref
)+u
"\"/>\n"
576 xml
+= u
" </" + ElementType
+ u
">\n"
582 def _XmlEncode(self
, text
):
583 return text
.replace("&", "&").replace("\"", """)
585 ## End of main class ##
586 ###########################################################################