1
2 """Loading OSM XML data and storing it into an OSMModel.
3
4 OSM XML data is loaded and manipulated via OSMXMLFileParser
5 within OSMModel. An OSMModel stores the OSM data for simulation.
6 These and subordinated classes are originally built upon code from
7 https://github.com/rory/python-osm
8
9 """
10
11 from __future__ import absolute_import
12
13 import logging
14 import time
15 import xml.sax
16 import math
17
18 from . import utm
19 from mosp import routing
20 from mosp import collide
21
22 __author__ = "B. Henne, F. Ludwig, P. Tute, R. McCann"
23 __contact__ = "henne@dcsec.uni-hannover.de"
24 __copyright__ = "(c) 2008-2011 Rory McCann, 2010-2012, DCSec, Leibniz Universitaet Hannover, Germany"
25 __license__ = "GPLv3"
26
27 GLOBAL_SIM = None
28
29 ROADTYPE_NODIMENSION = 0
30 ROADTYPE_ONEWAY_NOSIDEWALK = 1
31 ROADTYPE_TWOWAY_NOSIDEWALK = 2
32 ROADTYPE_ONEWAY_ONSIDEWALK = 3
33
34 ROADTYPE = ROADTYPE_NODIMENSION
35
36 ROADWIDTH_DEFAULTS = { 'footway':2, 'service':2, 'tertiary':3, 'secondary':4, 'primary':4, 'else':2}
37
38
40 """Rounds UTM coordinate to demanded precision.
41
42 Rounds value x up to 3 decimal places. UTM value precision down to millimeter.
43 @author: B. Henne"""
44 return round(x, 3)
45
46
47 -class Node(routing.RoutingNode):
48 """A geo-model node.
49 @author: P. Tute
50 @author: Rory McCann"""
51
52 - def __init__(self, id=None, x=None, y=None, zone=None, lon=None, lat=None, tags=None):
53 """Initialize the geo-model node.
54
55 @param id: node id
56 @param x: UTM easting
57 @param y: UTM northing
58 @param zone: UTM zone
59 @param lon: WGS84 Longitude
60 @param lat: WGS84 Latitude
61 @param tags: OSM tags"""
62
63 self._x = None
64 self._y = None
65 self._z = None
66 self._lon = None
67 self._lat = None
68
69 if lon is not None and lat is not None:
70 self._lon, self._lat = float(lon), float(lat)
71 self._z = utm.long_to_zone(self._lon)
72 self._x, self._y = utm.latlong_to_utm(self._lon,
73 self._lat,
74 utm.long_to_zone(self._lon))
75
76 if x is not None and y is not None and zone is not None:
77 self._x, self._y, self._z = x, y, zone
78 self._lon, self._lat = utm.utm_to_latlong(x, y, zone)
79
80 self._x = round_utm_coord(self._x)
81 self._y = round_utm_coord(self._y)
82
83 if tags:
84 self.tags = tags
85 else:
86 self.tags = {}
87
88 routing.RoutingNode.__init__(self, int(id))
89 a = ""
90
92 """Returns the node's Latitude."""
93 return self._lat
94
96 """Returns the nodes's Longitude."""
97 return self._lon
98
107
108
117
118
119 lat = property(getLat, setLat)
120 lon = property(getLon, setLon)
121
123 """Returns the node's x coordinate, it's UTM easting."""
124 return self._x
125
127 """Returns the node's y coordinate, it's UTM northing."""
128 return self._y
129
131 """Returns the node's UTM zone."""
132 return self._z
133
138
143
148
149 x = property(getX, setX)
150 y = property(getY, setY)
151 z = property(getZone, setZone)
152
154 n = ['Node(id=%r, distance=%f)' % (n.id, d) for n, d in self.neighbors.items()]
155 return "Node(id=%r, lon=%r, lat=%r, tags=%r, neighbors=%r)"\
156 % (self.id, self.lon, self.lat, self.tags, n)
157
158
160 """A geo-model way.
161 @author: Rory McCann"""
162 - def __init__(self, id, nodes=None, tags=None):
163 self.id = id
164 if nodes:
165 self.nodes = nodes
166 else:
167 self.nodes = []
168 if tags:
169 self.tags = tags
170 else:
171 self.tags = {}
172
174 return "Way(id=%r, nodes=%r, tags=%r)" % (self.id, self.nodes, self.tags)
175
176
178 """Calculates the angle between the way from src_node to dest_node and positive x-axis of a plane.
179 @author: B. Henne"""
180 y = dest_node.y - src_node.y
181 x = dest_node.x - src_node.x
182 return math.ceil(math.atan2(y,x)/math.pi*180)
183
184
186 """A geo-model way segment, the line between two nodes
187 @author: B. Henne
188 @author: P. Tute"""
189 - def __init__(self, node0, node1, width=None, tags=None):
190 """Initializes WaySegment, see update method."""
191 self.nodes = [None, None]
192 self.width = [0,0]
193 self.tags = {}
194 self.persons = []
195 self.update(node0, node1, width, tags)
196
197 - def update(self, node0=None, node1=None, width=None, tags=None):
198 """Updates WaySegment parameters.
199
200 @param node0: start node of the segment
201 @param node1: end node of the segment
202 @param width: is road width at this WaySegment
203 @param tags: carries corresponding osm way tags"""
204
205 if node0:
206 self.nodes[0] = node0
207 if node1:
208 self.nodes[1] = node1
209 if width:
210 if (ROADTYPE == ROADTYPE_NODIMENSION):
211 self.width = [0,0]
212 elif (ROADTYPE == ROADTYPE_ONEWAY_NOSIDEWALK):
213 self.width = [0,width]
214 elif (ROADTYPE == ROADTYPE_TWOWAY_NOSIDEWALK):
215 self.width = [-width,width]
216 elif (ROADTYPE == ROADTYPE_ONEWAY_ONSIDEWALK):
217 assert len(width) == 2
218 self.width = width
219 else:
220 assert len(width) == 2
221 self.width = width
222 if tags:
223 self.tags = tags
224 node0 = self.nodes[0]
225 node1 = self.nodes[1]
226 self.directions = {node0:wayangle(node1, node0), node1:wayangle(node0, node1)}
227 super(WaySegment, self).__init__(node0.x, node0.y, node1.x, node1.y)
228
230 """Compare WaySegments by id"""
231 return cmp(self.id, o.id)
232
234
235
236 return str(self.id)
237
238
240 """Placeholder for OSM nodes while parsing the OSM data.
241
242 NodePlaceHolder later is replaced by (references to) Nodes.
243 @author: Rory McCann"""
245 """Inits the NodePlaceHolder"""
246 self.id = id
247
249 return "NodePlaceHolder(id=%r)" % (self.id)
250
251
253 """Calculates the width of a way based on osm tags and width defaults.
254 @author: B. Henne
255 @todo: add more road width calculation magic"""
256 if 'width' in way.tags:
257 re = way.tags['width'].replace(',','.',1)
258 elif 'approx_width' in way.tags:
259 re = way.tags['approx_width'].replace(',','.',1)
260
261 elif 'highway' in way.tags:
262 if way.tags['highway'] in ROADWIDTH_DEFAULTS:
263 re = ROADWIDTH_DEFAULTS[way.tags['highway']]
264 else:
265 re = ROADWIDTH_DEFAULTS['else']
266 return float(re)
267
268
270 """Calculates the euclidean distance of two nodes in the plane.
271
272 Calculates the distance of two nodes described by UTM coordinates x,y
273 in the area by the euclidean distance using the Pythagorean theorem.
274 @author: B. Henne"""
275 x = node1.x - node2.x
276 y = node1.y - node2.y
277 return math.sqrt(x**2 + y**2)
278
279
281 """A OSM-based geo-model. A simulation world with geo data.
282 @author: F. Ludwig
283 @author: P. Tute
284 @author: B. Henne"""
286 """Initializes OSMModel object.
287
288 Call initialize() to load OSM XML data from fname."""
289 super(OSMModel, self).__init__(**kwargs)
290 self.fobj = open(fname)
291 self.path = fname
292 self.nodes = {}
293 self.ways = {}
294
307
309 """Initializes the model by parsing and manipulating OSM XML data."""
310
311 parser = xml.sax.make_parser()
312 handler = OSMXMLFileParser(self)
313 parser.setContentHandler(handler)
314 parser.parse(self.fobj)
315
316 assert self.bounds["minlon"] < self.bounds["maxlon"]
317 assert self.bounds["minlat"] < self.bounds["maxlat"]
318
319
320 self.bounds["min_x"], self.bounds["min_y"] = utm.latlong_to_utm(self.bounds["minlon"],
321 self.bounds["minlat"])
322 self.bounds["max_x"], self.bounds["max_y"] = utm.latlong_to_utm(self.bounds["maxlon"],
323 self.bounds["maxlat"])
324
325
326 for way in self.ways.values():
327 way.nodes = [self.nodes[handler.node_id_map[node_pl.id]]
328 for node_pl in way.nodes]
329
330
331
332
333
334
335
336
337
338 del handler
339
340
341 for j, way in enumerate(self.ways.values()):
342 for i in xrange(len(way.nodes)-1):
343 current_node = way.nodes[i]
344 next_node = way.nodes[i+1]
345
346
347 way_segment = WaySegment(current_node, next_node, width=calc_width(way), tags=way.tags)
348 way_segment.id = i + 1000 * j
349 self.add(way_segment)
350 next_node.ways[current_node] = way_segment
351 current_node.ways[next_node] = way_segment
352
353
354 current_node.neighbors[next_node] = int(distance(current_node, next_node))
355 next_node.neighbors[current_node] = int(distance(current_node, next_node))
356 del self.ways
357
358
359 self.non_way_nodes = []
360 self.way_nodes = []
361 for n in self.nodes.values():
362
363 if n.neighbors:
364 self.way_nodes.append(n)
365
366 elif len(n.tags) > 0:
367 self.non_way_nodes.append(n)
368
369 t = time.time()
370
371
372 ways = sorted(filter(lambda w: isinstance(w, collide.Line), list(self.obj)))
373
374 x_min, y_min = self.bounds["min_x"], self.bounds["min_y"]
375 x_max, y_max = self.bounds["max_x"], self.bounds["max_y"]
376
377 for func, arg0, arg1, arg2 in ((collide.Line.collide_vertical_line, x_min, y_min, y_max),
378 (collide.Line.collide_vertical_line, x_max, y_min, y_max),
379 (collide.Line.collide_horizontal_line, x_min, x_max, y_max),
380 (collide.Line.collide_horizontal_line, x_min, x_max, y_min)):
381 for way in ways:
382 collision = func(way, arg0, arg1, arg2)
383 colliding = False
384 if collision[0]:
385
386 if (self.out_of_bb(way.nodes[0]) and
387 self.out_of_bb(way.nodes[1])):
388
389 self.obj = set([w for w in self.obj if not w.id == way.id])
390
391 if way.nodes[0] in self.way_nodes:
392 self.way_nodes.remove(way.nodes[0])
393 if way.nodes[1] in self.way_nodes:
394 self.way_nodes.remove(way.nodes[1])
395 if way.nodes[1] in way.nodes[0].neighbors.keys():
396 del way.nodes[0].neighbors[way.nodes[1]]
397 if way.nodes[0] in way.nodes[1].neighbors.keys():
398 del way.nodes[1].neighbors[way.nodes[0]]
399 continue
400 for node in way.nodes:
401
402 if node.x < x_min:
403 node.x = collision[1]
404 node.y = collision[2]
405 colliding = True
406 side = "west"
407 elif node.x > x_max:
408 node.x = collision[1]
409 node.y = collision[2]
410 colliding = True
411 side = "east"
412 if node.y < y_min:
413 node.x = collision[1]
414 node.y = collision[2]
415 colliding = True
416 side = "south"
417 elif node.y > y_max:
418 node.x = collision[1]
419 node.y = collision[2]
420 colliding = True
421 side = "north"
422 if colliding:
423 node.tags["border"] = side
424 break
425
426 start_node = way.nodes[0]
427 end_node = way.nodes[1]
428 dist = int(distance(start_node, end_node))
429 start_node.neighbors[end_node] = dist
430 end_node.neighbors[start_node] = dist
431 way.update()
432 elif (self.out_of_bb(way.nodes[0])
433 and self.out_of_bb(way.nodes[1]) and
434 way in self.obj):
435 self.obj.remove(way)
436
437 for node in self.way_nodes[:]:
438
439 if self.out_of_bb(node):
440 self.way_nodes.remove(node)
441
442
443 for neighbor in node.neighbors.keys():
444 if self.out_of_bb(neighbor):
445 del node.neighbors[neighbor]
446
447
448 for node in self.non_way_nodes[:]:
449
450 if self.out_of_bb(node):
451 self.non_way_nodes.remove(node)
452
453
454 ways = sorted(filter(lambda w: isinstance(w, collide.Line), list(self.obj)))
455 for way in ways:
456 if (way.nodes[0] not in self.way_nodes or
457 way.nodes[1] not in self.way_nodes):
458 self.obj = set([w for w in self.obj if not w.id == way.id])
459
460
461 self.way_nodes = sorted(self.way_nodes, key=lambda n:n.id)
462 for i, node in enumerate(self.way_nodes):
463 node.id = i
464
465 pass
466
467
468 if enable_routing:
469 t = time.time()
470 routing.calc(self.way_nodes, self.path[:-4])
471
472 pass
473
474
475 t = time.time()
476 self.calculate_grid(cache_base_path=self.path[:-4])
477 pass
478
479
480 if not enable_routing:
481 for node in self.way_nodes:
482 node.neighbors = {}
483
484 self.way_nodes_by_id = {}
485 for node in self.way_nodes:
486 self.way_nodes_by_id[node.id] = node
487
488
489
490 self.map_nodeid_osmnodeid = {}
491 self.map_osmnodeid_nodeid = {}
492 for n in self.way_nodes:
493 self.map_nodeid_osmnodeid[n.id] = n.osm_id
494 self.map_osmnodeid_nodeid[n.osm_id] = n.id
495
496
498 """The OSM XML parser.
499
500 Parses OSM data. highway_blacklist can be used to filter it.
501 @author: F. Ludwig
502 @author: B. Henne
503 @author: Rory McCann"""
505 self.containing_obj = containing_obj
506 self.bounds = {}
507 self.zone = 0
508 self.curr_node = None
509 self.curr_way = None
510 self.node_id = 0
511 self.node_id_map = {}
512 self.highway_blacklist = ['motorway','motorway_link',
513 'trunk', 'trunk_link',
514
515
516
517
518
519
520 ]
521
523 """If Opening a xml element ..."""
524 if name == 'bounds':
525 if self.bounds == {}:
526 self.bounds['minlat'] = float(attrs['minlat'])
527 self.bounds['minlon'] = float(attrs['minlon'])
528 self.bounds['maxlat'] = float(attrs['maxlat'])
529 self.bounds['maxlon'] = float(attrs['maxlon'])
530 elif name == 'bound':
531 if self.bounds == {}:
532 box = attrs['box'].split(',')
533 self.bounds['minlat'] = float(box[0])
534 self.bounds['minlon'] = float(box[1])
535 self.bounds['maxlat'] = float(box[2])
536 self.bounds['maxlon'] = float(box[3])
537 elif name == 'node':
538 if not ((attrs.has_key('action')) and (attrs['action'] == 'delete')):
539 self.node_id_map[attrs['id']] = self.node_id
540 self.curr_node = Node(id=self.node_id, lon=attrs['lon'], lat=attrs['lat'])
541 self.curr_node.osm_id = attrs['id']
542 self.node_id += 1
543 else:
544 self.curr_node = None
545 elif name == 'way':
546 if not ((attrs.has_key('action')) and (attrs['action'] == 'delete')):
547 self.curr_way = Way(id=attrs['id'])
548 else:
549 self.curr_way = None
550 elif name == 'tag':
551 if self.curr_node:
552 self.curr_node.tags[attrs['k']] = attrs['v']
553 elif self.curr_way:
554 self.curr_way.tags[attrs['k']] = attrs['v']
555 elif name == "nd":
556 assert self.curr_node is None, "curr_node (%r) is non-none" % (self.curr_node)
557 assert self.curr_way is not None, "curr_way is None"
558 self.curr_way.nodes.append(NodePlaceHolder(id=attrs['ref']))
559
561 """If closing the xml element ..."""
562 if name == "bounds" or name == "bound":
563 self.containing_obj.bounds = self.bounds
564 self.containing_obj.zone = self.zone = int(utm.long_to_zone(self.bounds['minlon']+((self.bounds['maxlon']-self.bounds['minlon'])/2)))
565 elif name == "node":
566 if self.curr_node is not None:
567 self.containing_obj.nodes[self.curr_node.id] = self.curr_node
568 self.curr_node = None
569 elif name == "way":
570 if self.curr_way is not None:
571 if 'highway' in self.curr_way.tags:
572 self.containing_obj.ways[self.curr_way.id] = self.curr_way
573 self.curr_way = None
574
575
576
577
578
579
580