2 import xml
.sax
, math
, tempfile
, urllib
, urllib2
, os
5 OSM_API_BASE_URL
= "http://api.openstreetmaps.org/api/0.5"
7 class Property(object):
9 A metaclass to make it easy to add properties to objects
11 class __metaclass__(type):
12 def __init__(cls
, name
, bases
, dct
):
13 for fname
in ['get', 'set', 'delete']:
15 setattr(cls
, fname
, staticmethod(dct
[fname
]))
16 def __get__(cls
, obj
, objtype
=None):
19 fget
= getattr(cls
, 'get')
21 def __set__(cls
, obj
, value
):
22 fset
= getattr(cls
, 'set')
24 def __delete__(cls
, obj
):
25 fdel
= getattr(cls
, 'delete')
30 __slots__
= ['left', 'right', 'top', 'bottom']
31 def __init__(self
, *args
, **kwargs
):
32 if sorted(kwargs
.keys()) == ['bottom', 'left', 'right', 'top']:
33 self
.left
= kwargs
['left']
34 self
.right
= kwargs
['right']
35 self
.top
= kwargs
['top']
36 self
.bottom
= kwargs
['bottom']
37 elif sorted(kwargs
.keys()) == ['maxlat', 'maxlon', 'minlat', 'minlon']:
38 self
.left
= kwargs
['minlat']
39 self
.right
= kwargs
['maxlat']
40 self
.bottom
= kwargs
['maxlon']
41 self
.top
= kwargs
['minlon']
43 raise TypeError("Insufficent arguments to BBox contsructor")
45 class minlat(Property
):
51 class maxlat(Property
):
57 class minlon(Property
):
63 class maxlon(Property
):
70 return "BBox(left=%r, bottom=%r, right=%r, top=%r)" % (self
.left
, self
.bottom
, self
.right
, self
.top
)
72 def __in__(self
, obj
):
73 if instanceof(obj
, Node
):
74 return self
.minlat
< node
.lat
< self
.maxlat
and self
.minlon
< node
.lon
< self
.maxlon
76 raise TypeError("Object %r is not a node" % obj
)
79 __slots__
= ['id', 'lon', 'lat', 'tags']
81 def __init__(self
, id=None, lon
=None, lat
=None, tags
=None):
83 self
.lon
, self
.lat
= lon
, lat
90 return "Node(id=%r, lon=%r, lat=%r, tags=%r)" % (self
.id, self
.lon
, self
.lat
, self
.tags
)
92 def distance(self
, other
):
94 Returns the distance between this point the other in metres
96 lat1
=float(self
.lat
) * math
.pi
/ 180
97 lat2
=float(other
.lat
) * math
.pi
/ 180
98 lon1
=float(self
.lon
) * math
.pi
/ 180
99 lon2
=float(other
.lon
) * math
.pi
/ 180
100 dist
= math
.atan(math
.sqrt(math
.pow(math
.cos(lat2
)*math
.sin(abs(lon1
-lon2
)),2) + math
.pow(math
.cos(lat1
)*math
.sin(lat2
) - math
.sin(lat1
)*math
.cos(lat2
)*math
.cos(lon1
-lon2
),2)) / (math
.sin(lat1
)*math
.sin(lat2
) + math
.cos(lat1
)*math
.cos(lat2
)*math
.cos(lon1
-lon2
)))
101 dist
*= 6372795 # convert from radians to meters
105 __slots__
= ['id', 'nodes', 'tags']
107 def __init__(self
, id=None, nodes
=None, tags
=None):
119 return "Way(id=%r, nodes=%r, tags=%r)" % (self
.id, self
.nodes
, self
.tags
)
123 Returns the length of the way in metres
125 if len(self
.nodes
) < 2:
127 return sum(self
.nodes
[i
].distance(self
.nodes
[i
+1]) for i
in range(len(self
.nodes
)-1))
131 class NodePlaceHolder(object):
134 def __init__(self
, id):
138 return "NodePlaceHolder(id=%r)" % (self
.id)
140 class WayPlaceHolder(object):
143 def __init__(self
, id):
147 return "WayPlaceHolder(id=%r)" % (self
.id)
149 class Relation(object):
150 __slots__
= ['id', 'roles', 'tags']
152 def __init__(self
, id):
157 def add(self
, item
, role
=None):
159 Add the item to this relation with that role. If role is unspecified,
165 if role
not in self
.roles
:
166 self
.roles
[role
] = set()
167 self
.roles
[role
].add(item
)
171 class OSMXMLFile(object):
172 def __init__(self
, datasource
):
173 self
.datasource
= datasource
178 self
.invalid_ways
= []
183 """Parse the given XML file"""
185 if isinstance(self
.datasource
, basestring
):
186 parser
= xml
.sax
.parseString(self
.datasource
, OSMXMLFileParser(self
))
188 parser
= xml
.sax
.parse(self
.datasource
, OSMXMLFileParser(self
))
190 # now fix up all the refereneces
191 for index
, way
in self
.ways
.items():
193 way
.nodes
= [self
.nodes
[node_pl
.id] for node_pl
in way
.nodes
]
195 print "Way (id=%s) referes to a node that doesn't exist, skipping that way" % (index
)
196 self
.invalid_ways
.append(way
)
200 # convert them back to lists
201 self
.nodes
= self
.nodes
.values()
202 self
.ways
= self
.ways
.values()
205 class OSMXMLFileParser(xml
.sax
.ContentHandler
):
206 def __init__(self
, containing_obj
):
207 self
.containing_obj
= containing_obj
208 self
.curr_node
= None
210 self
.curr_relation
= None
212 def startElement(self
, name
, attrs
):
213 #print "Start of node " + name
215 self
.curr_node
= Node(id=attrs
['id'], lon
=attrs
['lon'], lat
=attrs
['lat'])
217 #self.containing_obj.ways.append(Way())
218 self
.curr_way
= Way(id=attrs
['id'])
219 elif name
== 'relation':
220 self
.curr_relation
= Relation(id=attrs
['id'])
222 #assert not self.curr_node and not self.curr_way, "curr_node (%r) and curr_way (%r) are both non-None" % (self.curr_node, self.curr_way)
223 if self
.curr_node
is not None:
224 self
.curr_node
.tags
[attrs
['k']] = attrs
['v']
225 elif self
.curr_way
is not None:
226 self
.curr_way
.tags
[attrs
['k']] = attrs
['v']
228 assert self
.curr_node
is None, "curr_node (%r) is non-none" % (self
.curr_node
)
229 assert self
.curr_way
is not None, "curr_way is None"
230 self
.curr_way
.nodes
.append(NodePlaceHolder(id=attrs
['ref']))
231 elif name
== "member":
232 #import pdb ; pdb.set_trace()
233 assert self
.curr_relation
is not None, "<member> tag and no relation"
234 if attrs
['type'] == 'way':
235 self
.curr_relation
.add(WayPlaceHolder(id=attrs
['ref']), role
=attrs
['role'])
236 elif attrs
['type'] == 'node':
237 self
.curr_relation
.add(NodePlaceHolder(id=attrs
['ref']), role
=attrs
['role'])
239 assert False, "Unknown member type "+repr(attrs
['type'])
240 elif name
in ["osm", "bounds"]:
243 print "Unknown node: "+name
246 def endElement(self
, name
):
247 #print "End of node " + name
248 #assert not self.curr_node and not self.curr_way, "curr_node (%r) and curr_way (%r) are both non-None" % (self.curr_node, self.curr_way)
250 self
.containing_obj
.nodes
[self
.curr_node
.id] = self
.curr_node
251 self
.curr_node
= None
253 self
.containing_obj
.ways
[self
.curr_way
.id] = self
.curr_way
256 class GPSData(object):
258 Downloads data GPS track data from OpenStreetMap Server
260 def __init__(self
, bbox
, download
=True):
264 self
._download
_from
_api
()
266 def _download_from_api(self
):
267 url
= "http://api.openstreetmap.org/api/0.5/trackpoints?bbox=%s,%s,%s,%s&page=%%d" % (
268 self
.bbox
.left
, self
.bbox
.bottom
, self
.bbox
.right
, self
.bbox
.top
)
271 point_last_time
= None
273 while page
== 0 or point_last_time
== 5000:
274 tmpfile_fp
, tmpfilename
= tempfile
.mkstemp(suffix
=".gpx",
275 prefix
="osm-gps_%s,%s,%s,%s_%d_" % (self
.bbox
.left
, self
.bbox
.bottom
, self
.bbox
.right
, self
.bbox
.top
, page
))
276 urllib
.urlretrieve(url
% page
, filename
=tmpfilename
)
277 old_points_total
= sum(len(way
.nodes
) for way
in self
.tracks
)
278 self
._parse
_file
(tmpfilename
)
279 os
.remove(tmpfilename
)
280 point_last_time
= sum(len(way
.nodes
) for way
in self
.tracks
) - old_points_total
284 def _parse_file(self
, filename
):
285 parser
= xml
.sax
.make_parser()
286 parser
.setContentHandler(GPXParser(self
))
287 parser
.parse(filename
)
289 def save(self
, filename
):
290 fp
= open(filename
, 'w')
291 fp
.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
292 fp
.write("<gpx version=\"1.0\" creator=\"PyOSM\" xmlns=\"http://www.topografix.com/GPS/1/0/\">\n")
293 for track
in self
.tracks
:
294 fp
.write(" <trk>\n <trkseg>\n")
295 for node
in track
.nodes
:
296 fp
.write(' <trkpt lat="%s" lon="%s" />\n' % (node
.lat
, node
.lon
))
297 fp
.write(" </trkseg>\n </trk>\n")
301 class GPXParser(xml
.sax
.ContentHandler
):
303 Parses GPX files from the OSM GPS trackpoint downloader. Converts them to OSM format
305 def __init__(self
, containing_obj
):
307 self
.__current
_way
= None
308 self
.containing_obj
= containing_obj
310 def startElement(self
, name
, attrs
):
312 self
.__current
_way
= Way()
313 elif name
== "trkpt":
314 assert self
.__current
_way
is not None, "Invalid GPX file, we've encountered a trkpt tag before a trkseg tag"
315 self
.__current
_way
.nodes
.append(Node(lat
=attrs
['lat'], lon
=attrs
['lon']))
317 def endElement(self
, name
):
319 self
.containing_obj
.tracks
.append(self
.__current
_way
)
320 self
.__current
_way
= None
322 class OSMServer(object):
323 def __init__(self
, api_root
):
324 self
.api_root
= api_root
326 def _get_data(self
, subpath
):
327 if subpath
[0] != '/' and self
.api_root
[-1] != '/':
330 return urllib2
.urlopen(self
.api_root
+ subpath
).read()
333 def node(self
, node_id
):
334 osm_data
= OSMXMLFile(self
._get
_data
("node/%s" % node_id
))
336 if len(osm_data
.nodes
) == 0:
339 elif len(osm_data
.nodes
) == 1:
340 return osm_data
.nodes
[0]
342 def way(self
, way_id
):
343 osm_data
= OSMXMLFile(self
._get
_data
("way/%s" % way_id
))
345 if len(osm_data
.ways
) == 0:
348 elif len(osm_data
.ways
) == 1:
349 return osm_data
.ways
[0]
351 def relation(self
, relation_id
):
352 osm_data
= OSMXMLFile(self
._get
_data
("relation/%s" % relation_id
))
354 if len(osm_data
.relations
) == 0:
357 elif len(osm_data
.relations
) == 1:
358 return osm_data
.relations
[0]
361 # Mark Pilgram's excellent openAnything
362 def open_anything(source
):
363 # try to open with urllib (if source is http, ftp, or file URL)
365 return urllib
.urlopen(source
)
366 except (IOError, OSError):
369 # try to open with native open function (if source is pathname)
372 except (IOError, OSError):
375 # treat source as string
376 return StringIO
.StringIO(str(source
))
380 osm_web
= OSMServer("http://api.openstreetmap.org/api/0.5/")