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.8 2009-10-13 *(Create|Update|Delete) use not unique _do method ##
28 ## 0.2.7 2009-10-09 implement all missing fonctions except ##
29 ## ChangesetsGet and GetCapabilities ##
30 ## 0.2.6 2009-10-09 encoding clean-up ##
31 ## 0.2.5 2009-10-09 implements NodesGet, WaysGet, RelationsGet ##
32 ## ParseOsm, ParseOsc ##
33 ## 0.2.4 2009-10-06 clean-up ##
34 ## 0.2.3 2009-09-09 keep http connection alive for multiple request ##
35 ## (Node|Way|Relation)Get return None when object ##
36 ## have been deleted (raising error before) ##
37 ## 0.2.2 2009-07-13 can identify applications built on top of the lib ##
38 ## 0.2.1 2009-05-05 some changes in constructor -- chove@crans.org ##
39 ## 0.2 2009-05-01 initial import ##
40 ###########################################################################
44 import httplib
, base64
, xml
.dom
.minidom
, time
46 ###########################################################################
56 created_by
= "PythonOsmApi/"+__version__
,
57 api
= "www.openstreetmap.org",
58 changesetauto
= False,
59 changesetautotags
= {},
60 changesetautosize
= 500,
61 changesetautogroup
= False):
65 self
._username
= username
67 self
._username
= open(passwordfile
).readline().split(":")[0].strip()
71 self
._password
= password
73 for l
in open(passwordfile
).readlines():
74 l
= l
.strip().split(":")
75 if l
[0] == self
._username
:
78 # Changest informations
79 self
._changesetauto
= changesetauto
# auto create and close changesets
80 self
._changesetautotags
= changesetautotags
# tags for automatic created changesets
81 self
._changesetautosize
= changesetautosize
# change count for auto changeset
82 self
._changesetautogroup
= changesetautogroup
# group data to upload (NodeCreate, NodeUpdate... will return None)
83 self
._changesetautodata
= [] # data to upload for auto group
90 self
._created
_by
= created_by
92 self
._created
_by
= appid
+ " (" + created_by
+ ")"
95 self
._CurrentChangesetId
= 0
98 self
._conn
= httplib
.HTTPConnection(self
._api
, 80)
100 #######################################################################
102 #######################################################################
104 def Capabilities(self
):
107 #######################################################################
109 #######################################################################
111 def NodeGet(self
, NodeId
, NodeVersion
= -1):
112 """ Returns NodeData for node #NodeId. """
113 uri
= "/api/0.6/node/"+str(NodeId
)
114 if NodeVersion
<> -1: uri
+= "/"+str(NodeVersion
)
115 data
= self
._get
(uri
)
116 if not data
: return data
117 data
= xml
.dom
.minidom
.parseString(data
)
118 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("node")[0]
119 return self
._DomParseNode
(data
)
121 def NodeCreate(self
, NodeData
):
122 """ Creates a node. Returns updated NodeData (without timestamp). """
123 return self
._do
("create", "node", NodeData
)
125 def NodeUpdate(self
, NodeData
):
126 """ Updates node with NodeData. Returns updated NodeData (without timestamp). """
127 return self
._do
("update", "node", NodeData
)
129 def NodeDelete(self
, NodeData
):
130 """ Delete node with NodeData. Returns updated NodeData (without timestamp). """
131 return self
._do
("delete", "node", NodeData
)
133 def NodeHistory(self
, NodeId
):
134 """ Returns dict(NodeVerrsion: NodeData). """
135 uri
= "/api/0.6/node/"+str(NodeId
)+"/history"
136 data
= self
._get
(uri
)
137 data
= xml
.dom
.minidom
.parseString(data
)
139 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("node"):
140 data
= self
._DomParseNode
(data
)
141 result
[data
[u
"version"]] = data
144 def NodeWays(self
, NodeId
):
145 """ Returns [WayData, ... ] containing node #NodeId. """
146 uri
= "/api/0.6/node/%d/ways"%NodeId
147 data
= self
._get
(uri
)
148 data
= xml
.dom
.minidom
.parseString(data
)
150 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("way"):
151 data
= self
._DomParseRelation
(data
)
155 def NodeRelations(self
, NodeId
):
156 """ Returns [RelationData, ... ] containing node #NodeId. """
157 uri
= "/api/0.6/node/%d/relations"%NodeId
158 data
= self
._get
(uri
)
159 data
= xml
.dom
.minidom
.parseString(data
)
161 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
162 data
= self
._DomParseRelation
(data
)
166 def NodesGet(self
, NodeIdList
):
167 """ Returns dict(NodeId: NodeData) for each node in NodeIdList """
168 uri
= "/api/0.6/nodes?nodes=" + ",".join([str(x
) for x
in NodeIdList
])
169 data
= self
._get
(uri
)
170 data
= xml
.dom
.minidom
.parseString(data
)
172 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("node"):
173 data
= self
._DomParseNode
(data
)
174 result
[data
[u
"id"]] = data
177 #######################################################################
179 #######################################################################
181 def WayGet(self
, WayId
, WayVersion
= -1):
182 """ Returns WayData for way #WayId. """
183 uri
= "/api/0.6/way/"+str(WayId
)
184 if WayVersion
<> -1: uri
+= "/"+str(WayVersion
)
185 data
= self
._get
(uri
)
186 if not data
: return data
187 data
= xml
.dom
.minidom
.parseString(data
)
188 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("way")[0]
189 return self
._DomParseWay
(data
)
191 def WayCreate(self
, WayData
):
192 """ Creates a way. Returns updated WayData (without timestamp). """
193 return self
._do
("create", "way", WayData
)
195 def WayUpdate(self
, WayData
):
196 """ Updates way with WayData. Returns updated WayData (without timestamp). """
197 return self
._do
("update", "way", WayData
)
199 def WayDelete(self
, WayData
):
200 """ Delete way with WayData. Returns updated WayData (without timestamp). """
201 return self
._do
("delete", "way", WayData
)
203 def WayHistory(self
, WayId
):
204 """ Returns dict(WayVerrsion: WayData). """
205 uri
= "/api/0.6/way/"+str(WayId
)+"/history"
206 data
= self
._get
(uri
)
207 data
= xml
.dom
.minidom
.parseString(data
)
209 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("way"):
210 data
= self
._DomParseWay
(data
)
211 result
[data
[u
"version"]] = data
214 def WayRelations(self
, WayId
):
215 """ Returns [RelationData, ...] containing way #WayId. """
216 uri
= "/api/0.6/way/%d/relations"%WayId
217 data
= self
._get
(uri
)
218 data
= xml
.dom
.minidom
.parseString(data
)
220 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
221 data
= self
._DomParseRelation
(data
)
225 def WayFull(self
, WayId
):
226 """ Return full data for way WayId as list of {type: node|way|relation, data: {}}. """
227 uri
= "/api/0.6/way/"+str(WayId
)+"/full"
228 data
= self
._get
(uri
)
229 return self
.ParseOsm(data
)
231 def WaysGet(self
, WayIdList
):
232 """ Returns dict(WayId: WayData) for each way in WayIdList """
233 uri
= "/api/0.6/ways?ways=" + ",".join([str(x
) for x
in WayIdList
])
234 data
= self
._get
(uri
)
235 data
= xml
.dom
.minidom
.parseString(data
)
237 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("way"):
238 data
= self
._DomParseWay
(data
)
239 result
[data
[u
"id"]] = data
242 #######################################################################
244 #######################################################################
246 def RelationGet(self
, RelationId
, RelationVersion
= -1):
247 """ Returns RelationData for relation #RelationId. """
248 uri
= "/api/0.6/relation/"+str(RelationId
)
249 if RelationVersion
<> -1: uri
+= "/"+str(RelationVersion
)
250 data
= self
._get
(uri
)
251 if not data
: return data
252 data
= xml
.dom
.minidom
.parseString(data
)
253 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("relation")[0]
254 return self
._DomParseRelation
(data
)
256 def RelationCreate(self
, RelationData
):
257 """ Creates a relation. Returns updated RelationData (without timestamp). """
258 return self
._do
("create", "relation", RelationData
)
260 def RelationUpdate(self
, RelationData
):
261 """ Updates relation with RelationData. Returns updated RelationData (without timestamp). """
262 return self
._do
("update", "relation", RelationData
)
264 def RelationDelete(self
, RelationData
):
265 """ Delete relation with RelationData. Returns updated RelationData (without timestamp). """
266 return self
._do
("delete", "relation", RelationData
)
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
)
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 uri
= "/api/0.6/relation/%d/relations"%RelationId
282 data
= self
._get
(uri
)
283 data
= xml
.dom
.minidom
.parseString(data
)
285 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
286 data
= self
._DomParseRelation
(data
)
290 def RelationFull(self
, RelationId
):
291 """ Return full data for way WayId as list of {type: node|way|relation, data: {}}. """
292 uri
= "/api/0.6/relation/"+str(RelationId
)+"/full"
293 data
= self
._get
(uri
)
294 return self
.ParseOsm(data
)
296 def RelationsGet(self
, RelationIdList
):
297 """ Returns dict(RelationId: RelationData) for each relation in RelationIdList """
298 uri
= "/api/0.6/relations?relations=" + ",".join([str(x
) for x
in RelationIdList
])
299 data
= self
._get
(uri
)
300 data
= xml
.dom
.minidom
.parseString(data
)
302 for data
in data
.getElementsByTagName("osm")[0].getElementsByTagName("relation"):
303 data
= self
._DomParseRelation
(data
)
304 result
[data
[u
"id"]] = data
307 #######################################################################
309 #######################################################################
311 def ChangesetGet(self
, ChangesetId
):
312 """ Returns ChangesetData for changeset #ChangesetId. """
313 data
= self
._get
("/api/0.6/changeset/"+str(ChangesetId
))
314 data
= xml
.dom
.minidom
.parseString(data
)
315 data
= data
.getElementsByTagName("osm")[0].getElementsByTagName("changeset")[0]
316 return self
._DomParseChangeset
(data
)
318 def ChangesetUpdate(self
, ChangesetTags
= {}):
319 """ Updates current changeset with ChangesetTags. """
320 if self
._CurrentChangesetId
== -1:
321 raise Exception, "No changeset currently opened"
322 if u
"created_by" not in ChangesetTags
:
323 ChangesetTags
[u
"created_by"] = self
._created
_by
324 result
= self
._put
("/api/0.6/changeset/"+str(self
._CurrentChangesetId
), self
._XmlBuild
("changeset", {u
"tag": ChangesetTags
}))
325 return self
._CurrentChangesetId
327 def ChangesetCreate(self
, ChangesetTags
= {}):
328 """ Opens a changeset. Returns #ChangesetId. """
329 if self
._CurrentChangesetId
:
330 raise Exception, "Changeset alreadey opened"
331 if u
"created_by" not in ChangesetTags
:
332 ChangesetTags
[u
"created_by"] = self
._created
_by
333 result
= self
._put
("/api/0.6/changeset/create", self
._XmlBuild
("changeset", {u
"tag": ChangesetTags
}))
334 self
._CurrentChangesetId
= int(result
)
335 return self
._CurrentChangesetId
337 def ChangesetClose(self
):
338 """ Closes current changeset. Returns #ChangesetId. """
339 if not self
._CurrentChangesetId
:
340 raise Exception, "No changeset currently opened"
341 result
= self
._put
("/api/0.6/changeset/"+str(self
._CurrentChangesetId
)+"/close", u
"")
342 CurrentChangesetId
= self
._CurrentChangesetId
343 self
._CurrentChangesetId
= 0
344 return CurrentChangesetId
346 def ChangesetUpload(self
):
349 def ChangesetDownload(self
, ChangesetId
):
350 """ Download data from a changeset. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
351 uri
= "/api/0.6/changeset/"+str(ChangesetId
)+"/download"
352 data
= self
._get
(uri
)
353 return self
.ParseOsc(data
)
355 def ChangesetsGet(self
, min_lon
=None, min_lat
=None, max_lon
=None, max_lat
=None, userid
=None, closed_after
=None, created_before
=None, only_open
=False, only_closed
=False):
356 """ Returns dict(ChangsetId: ChangesetData) matching all criteria. """
359 #######################################################################
361 #######################################################################
363 def Map(self
, min_lon
, min_lat
, max_lon
, max_lat
):
364 """ Download data in bounding box. Returns list of dict {type: node|way|relation, data: {}}. """
365 uri
= "/api/0.6/map?bbox=%f,%f,%f,%f"%(min_lon
, min_lat
, max_lon
, max_lat
)
366 data
= self
._get
(uri
)
367 return self
.ParseOsm(data
)
369 #######################################################################
371 #######################################################################
373 def ParseOsm(self
, data
):
374 """ Parse osm data. Returns list of dict {type: node|way|relation, data: {}}. """
375 data
= xml
.dom
.minidom
.parseString(data
)
376 data
= data
.getElementsByTagName("osm")[0]
378 for elem
in data
.childNodes
:
379 if elem
.nodeName
== u
"node":
380 result
.append({u
"type": elem
.nodeName
, u
"data": self
._DomParseNode
(elem
)})
381 elif elem
.nodeName
== u
"way":
382 result
.append({u
"type": elem
.nodeName
, u
"data": self
._DomParseWay
(elem
)})
383 elif elem
.nodeName
== u
"relation":
384 result
.append({u
"type": elem
.nodeName
, u
"data": self
._DomParseRelation
(elem
)})
387 def ParseOsc(self
, data
):
388 """ Parse osc data. Returns list of dict {type: node|way|relation, action: create|delete|modify, data: {}}. """
389 data
= xml
.dom
.minidom
.parseString(data
)
390 data
= data
.getElementsByTagName("osmChange")[0]
392 for action
in data
.childNodes
:
393 if action
.nodeName
== u
"#text": continue
394 for elem
in action
.childNodes
:
395 if elem
.nodeName
== u
"node":
396 result
.append({u
"action":action
.nodeName
, u
"type": elem
.nodeName
, u
"data": self
._DomParseNode
(elem
)})
397 elif elem
.nodeName
== u
"way":
398 result
.append({u
"action":action
.nodeName
, u
"type": elem
.nodeName
, u
"data": self
._DomParseWay
(elem
)})
399 elif elem
.nodeName
== u
"relation":
400 result
.append({u
"action":action
.nodeName
, u
"type": elem
.nodeName
, u
"data": self
._DomParseRelation
(elem
)})
403 #######################################################################
404 # Internal http function #
405 #######################################################################
407 def _do(self
, action
, OsmType
, OsmData
):
409 if u
"timestamp" in OsmData
:
410 OsmData
.pop(u
"timestamp")
412 if not self
._CurrentChangesetId
:
413 raise Exception, "You need to open a changeset before uploading data"
414 OsmData
[u
"changeset"] = self
._CurrentChangesetId
415 if action
== "create":
416 if OsmData
.get(u
"id", -1) > 0:
417 raise Exception, "This "+OsmType
+" already exists"
418 result
= self
._put
("/api/0.6/"+OsmType
+"/create", self
._XmlBuild
(OsmType
, OsmData
))
419 OsmData
[u
"id"] = int(result
.strip())
420 OsmData
[u
"version"] = 1
422 elif action
== "update":
423 result
= self
._put
("/api/0.6/"+OsmType
+"/"+str(OsmData
[u
"id"]), self
._XmlBuild
(OsmType
, OsmData
))
424 OsmData
[u
"version"] = int(result
.strip())
426 elif action
=="delete":
427 result
= self
._delete
("/api/0.6/"+OsmType
+"/"+str(OsmData
[u
"id"]), self
._XmlBuild
(OsmType
, OsmData
))
428 OsmData
[u
"version"] = int(result
.strip())
429 OsmData
[u
"visible"] = False
432 def _http_request(self
, cmd
, path
, auth
, send
):
433 self
._conn
.putrequest(cmd
, path
)
434 self
._conn
.putheader('User-Agent', self
._created
_by
)
436 self
._conn
.putheader('Authorization', 'Basic ' + base64
.encodestring(self
._username
+ ':' + self
._password
).strip())
438 self
._conn
.putheader('Content-Length', len(send
))
439 self
._conn
.endheaders()
441 self
._conn
.send(send
)
442 response
= self
._conn
.getresponse()
443 if response
.status
<> 200:
445 if response
.status
== 410:
447 raise Exception, "API returns unexpected status code "+str(response
.status
)+" ("+response
.reason
+")"
448 return response
.read()
450 def _http(self
, cmd
, path
, auth
, send
):
455 return self
._http
_request
(cmd
, path
, auth
, send
)
458 if i
<> 1: time
.sleep(2)
459 self
._conn
= httplib
.HTTPConnection(self
._api
, 80)
461 def _get(self
, path
):
462 return self
._http
('GET', path
, False, None)
464 def _put(self
, path
, data
):
465 return self
._http
('PUT', path
, True, data
)
467 def _delete(self
, path
, data
):
468 return self
._http
('DELETE', path
, True, data
)
470 #######################################################################
471 # Internal dom function #
472 #######################################################################
474 def _DomGetAttributes(self
, DomElement
):
475 """ Returns a formated dictionnary of attributes of a DomElement. """
477 for k
, v
in DomElement
.attributes
.items():
478 if k
== u
"uid" : v
= int(v
)
479 elif k
== u
"changeset" : v
= int(v
)
480 elif k
== u
"version" : v
= int(v
)
481 elif k
== u
"id" : v
= int(v
)
482 elif k
== u
"lat" : v
= float(v
)
483 elif k
== u
"lon" : v
= float(v
)
484 elif k
== u
"open" : v
= v
=="true"
485 elif k
== u
"visible" : v
= v
=="true"
486 elif k
== u
"ref" : v
= int(v
)
490 def _DomGetTag(self
, DomElement
):
491 """ Returns the dictionnary of tags of a DomElement. """
493 for t
in DomElement
.getElementsByTagName("tag"):
494 k
= t
.attributes
["k"].value
495 v
= t
.attributes
["v"].value
499 def _DomGetNd(self
, DomElement
):
500 """ Returns the list of nodes of a DomElement. """
502 for t
in DomElement
.getElementsByTagName("nd"):
503 result
.append(int(int(t
.attributes
["ref"].value
)))
506 def _DomGetMember(self
, DomElement
):
507 """ Returns a list of relation members. """
509 for m
in DomElement
.getElementsByTagName("member"):
510 result
.append(self
._DomGetAttributes
(m
))
513 def _DomParseNode(self
, DomElement
):
514 """ Returns NodeData for the node. """
515 result
= self
._DomGetAttributes
(DomElement
)
516 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
519 def _DomParseWay(self
, DomElement
):
520 """ Returns WayData for the way. """
521 result
= self
._DomGetAttributes
(DomElement
)
522 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
523 result
[u
"nd"] = self
._DomGetNd
(DomElement
)
526 def _DomParseRelation(self
, DomElement
):
527 """ Returns RelationData for the relation. """
528 result
= self
._DomGetAttributes
(DomElement
)
529 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
530 result
[u
"member"] = self
._DomGetMember
(DomElement
)
533 def _DomParseChangeset(self
, DomElement
):
534 """ Returns ChangesetData for the changeset. """
535 result
= self
._DomGetAttributes
(DomElement
)
536 result
[u
"tag"] = self
._DomGetTag
(DomElement
)
539 #######################################################################
540 # Internal xml builder #
541 #######################################################################
543 def _XmlBuild(self
, ElementType
, ElementData
):
546 xml
+= u
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
547 xml
+= u
"<osm version=\"0.6\" generator=\"" + self
._created
_by
+ "\">\n"
549 # <element attr="val">
550 xml
+= u
" <" + ElementType
551 if u
"id" in ElementData
:
552 xml
+= u
" id=\"" + str(ElementData
[u
"id"]) + u
"\""
553 if u
"lat" in ElementData
:
554 xml
+= u
" lat=\"" + str(ElementData
[u
"lat"]) + u
"\""
555 if u
"lon" in ElementData
:
556 xml
+= u
" lon=\"" + str(ElementData
[u
"lon"]) + u
"\""
557 if u
"version" in ElementData
:
558 xml
+= u
" version=\"" + str(ElementData
[u
"version"]) + u
"\""
559 xml
+= u
" visible=\"" + str(ElementData
.get(u
"visible", True)).lower() + u
"\""
560 if ElementType
in [u
"node", u
"way", u
"relation"]:
561 xml
+= u
" changeset=\"" + str(self
._CurrentChangesetId
) + u
"\""
565 for k
, v
in ElementData
.get(u
"tag", {}).items():
566 xml
+= u
" <tag k=\""+self
._XmlEncode
(k
)+u
"\" v=\""+self
._XmlEncode
(v
)+u
"\"/>\n"
569 for member
in ElementData
.get(u
"member", []):
570 xml
+= u
" <member type=\""+member
[u
"type"]+"\" ref=\""+str(member
[u
"ref"])+u
"\" role=\""+self
._XmlEncode
(member
[u
"role"])+"\"/>\n"
573 for ref
in ElementData
.get(u
"nd", []):
574 xml
+= u
" <nd ref=\""+str(ref
)+u
"\"/>\n"
577 xml
+= u
" </" + ElementType
+ u
">\n"
581 return xml
.encode("utf8")
583 def _XmlEncode(self
, text
):
584 return text
.replace("&", "&").replace("\"", """)
586 ## End of main class ##
587 ###########################################################################