from osm_parser import get_default_parser from collections import defaultdict class Node: def __init__(self, id, lat, lng): self.id = id self.lat = float(lat) self.lng = float(lng) self.neighbours_bike = [] self.neighbours_car = [] self.parent = None self.size = 1 def find_root(self): """ Recursively find the root of this node. """ if not self.parent: return self else: return self.parent.find_root() def union(self, other): """ Mark this node and some other node as part of the same tree. """ this = self.find_root() other = other.find_root() # If we share the same root we are already part of the same tree if this == other: return # Minimize tree size. This ensures optimal complexity. # We could also store the rank instead. if this.size < other.size: this, other = other, this # This is safe since other is a root node and currently doesn't have a # parent. other.parent = this this.size += other.size def coord_tuple(self): return self.lat, self.lng parser = None # Have a global reusable parser object def add_neighbours_bike(nodes): for way in parser.iter_ways(): if 'highway' not in way['tags']: continue print(way) road = way['road'] for i in range(len(road) - 1): node1 = road[i] node2 = road[i + 1] <<<<<<< HEAD nodes[node1].neighbours.append(nodes[node2]) nodes[node2].neighbours.append(nodes[node1]) # These two are neighbours and should be part of the same tree. nodes[node1].union(nodes[node2]) ======= if suitable_car(way): nodes[node1].neighbours_car.append(nodes[node2]) nodes[node2].neighbours_car.append(nodes[node1]) if suitable_bike(way): nodes[node1].neighbours_bike.append(nodes[node2]) nodes[node2].neighbours_bike.append(nodes[node1]) >>>>>>> 532c0cb... wip return nodes def add_neighbours_car(nodes): for way in parser.iter_ways(): if 'highway' not in way['tags']: continue def extract_osm_nodes(f_name): global parser parser = get_default_parser(f_name) nodes = dict() grid = defaultdict() for node in parser.iter_nodes(): new_node = Node(node['id'], node['lat'], node['lon']) nodes[node['id']] = new_node add_neighbours(nodes) # Remove nodes without neighbours for node_id, node in nodes.copy().items(): if not node.neighbours_car or not node.neighbours_bike: del nodes[node_id] # Construct a forest of disjoint trees. # The forest is a { root: tree }-dict. If we construct a relation where # two nodes relate to each other if they are a part of the same tree, # this can be shown to be an equivalence relation where the equivalence # class is some node in the tree. In our case, the equivalence class # becomes the root of each node since it will be the same for all nodes # in the same tree due our use of union-find. forest = defaultdict(dict) for node_id, node in nodes.items(): forest[node.find_root().id][node_id] = node # Find the largest disjoint tree. # We store the root so we can easily test if a node is part of this # tree or not. best_size = 0 best_tree = None best_root = None for root in forest: tree = forest[root] size = len(tree) if size > best_size: best_size = size best_tree = tree best_root = root unconnected_nodes = [] for _, node in nodes.items(): if node.find_root().id != best_root: unconnected_nodes.append(node) nodes = best_tree # create a "grid" by grouping nearby nodes. for node in nodes.copy().values(): key = (int(round(node.lat, 3) * 1000), int(round(node.lng, 3) * 1000)) if key in grid.keys(): grid[key].append(node) else: grid[key] = [node] return nodes, grid, unconnected_nodes def select_nodes_in_rectangle(nodes, min_lat, max_lat, min_long, max_long): return [node for node in nodes.values() if min_lat <= node.lat <= max_lat and min_long <= node.lng <= max_long] def get_relevant_neighbours(nodes, transport_mode): if transport_mode == "bike": return nodes.neighbours_bike else: return nodes.neighbours_car suitable_highway_types_bike = [ # Special road types 'living_street', 'service', 'pedestrian', 'track', 'road' ] def suitable_bike(way): tags = way['tags'] suitable_generic_type = tags['highway'] in suitable_highway_types_bike suitable_bike = tags['bicycle'] == 'yes' if 'bicycle' in tags else False return suitable_generic_type or suitable_bike suitable_highway_types_car = [ # Roads 'motorway', 'trunk', 'primary', 'secondary', 'tertiary', 'unclassified', 'residential', # Link roads 'motorway_link', 'trunk_link', 'primary_link', 'secondary_link', 'tertiary_link', # Special road types 'living_street', 'service', 'pedestrian', 'road' # FIXME: Handle predestrian and road differently? ] def suitable_car(way): return way['tags']['highway'] in suitable_highway_types_car