sadFaceEmoji commited on
Commit
ef35e05
1 Parent(s): 490218f
Files changed (1) hide show
  1. pub_crawl_script.py +172 -0
pub_crawl_script.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import osmnx as ox
2
+ import pandas as pd
3
+ import networkx as nx
4
+ import matplotlib.pyplot as plt
5
+ from ortools.constraint_solver import pywrapcp
6
+ from ortools.constraint_solver import routing_enums_pb2
7
+
8
+ class pub_crawl:
9
+ def __init__(self, df, G):
10
+ '''Initialise a pub_crawl instance
11
+
12
+ df: pd.Dataframe containing pubs 'name' and coordinates ('latitude', 'longitude')
13
+ G: nx.MultiDiGraph of the map area
14
+ '''
15
+ self.df = df
16
+ self.G = G
17
+
18
+ self.pub_names = df['name'].to_list()
19
+ self.initial_route = self.pub_names
20
+
21
+ self.optimal_route = None
22
+ self.optimal_distance = None
23
+
24
+ self.pub_nodes = self.create_pub_nodes()
25
+
26
+ def create_pub_nodes(self):
27
+ # Dictionary of pub names and coordinates
28
+ pubs_dict = self.df.drop('address', axis=1).set_index('name').T.to_dict('list')
29
+
30
+ # Get graph nodes for each pub
31
+ pub_nodes = {}
32
+ for k, v in pubs_dict.items():
33
+ pub_nodes[k] = ox.nearest_nodes(self.G, X = v[1], Y = v[0])
34
+
35
+ return pub_nodes
36
+
37
+ def get_route_length(self, p0, p1):
38
+ # Find length in meters of shortest path from pub p0 to pub p1
39
+ route = nx.shortest_path(self.G, self.pub_nodes[p0], self.pub_nodes[p1], weight='length')
40
+ route_lengths = ox.utils_graph.get_route_edge_attributes(self.G, route, attribute = 'length')
41
+ route_length_total = sum(route_lengths)
42
+ return route_length_total
43
+
44
+ def create_distance_matrix(self, pubs_considered):
45
+ distance_matrix = []
46
+ for i in range(len(pubs_considered)):
47
+ row = []
48
+ for j in range(len(pubs_considered)):
49
+ distance = self.get_route_length(pubs_considered[i], pubs_considered[j])
50
+ row.append(round(distance*1000)) # avoids rounding error in Google's OR-Tools package
51
+ distance_matrix.append(row)
52
+
53
+ return distance_matrix
54
+
55
+ def plot_map(self):
56
+ node_colours = ['#FF0000' if i in list(self.pub_nodes.values()) else '#999999' for i in self.G.nodes]
57
+ fig, ax = ox.plot_graph(self.G, bgcolor='#FFFFFF', node_color=node_colours, show=False, close=False)
58
+ for _, node in ox.graph_to_gdfs(self.G, nodes=True, edges=False).fillna("").iterrows():
59
+ for k, v in self.pub_nodes.items():
60
+ if node.name == v:
61
+ c = node["geometry"].centroid
62
+ ax.annotate(k, xy=(c.x, c.y), xycoords='data', xytext=(3, -2), textcoords='offset points', size=8)
63
+ plt.show()
64
+
65
+ def get_route_nodes(self, route):
66
+ # Find intermediary route nodes for plotting
67
+ route_nodes = []
68
+ for i in range(len(route)-1):
69
+ path = nx.shortest_path(self.G, self.pub_nodes[route[i]], self.pub_nodes[route[i+1]], weight='length')
70
+ route_nodes.append(path)
71
+ return route_nodes
72
+
73
+ def plot_route(self, route):
74
+ route_nodes = self.get_route_nodes(route)
75
+ fig, ax = ox.plot_graph_routes(self.G, route_nodes, bgcolor='#FFFFFF', show=False, close=False,
76
+ figsize=(12, 12))
77
+ for _, node in ox.graph_to_gdfs(self.G, nodes=True, edges=False).fillna("").iterrows():
78
+ for i, k in enumerate(route):
79
+ if node.name == self.pub_nodes[k]:
80
+ c = node["geometry"].centroid
81
+ ax.annotate(f'{i}: {k}', xy=(c.x, c.y), xycoords='data', xytext=(3, -2), textcoords='offset points', size=8)
82
+
83
+ return fig
84
+
85
+ def create_data(self, start, pubs_considered):
86
+ data = {}
87
+ start_index = pubs_considered.index(start)
88
+ data['distance_matrix'] = self.create_distance_matrix(pubs_considered)
89
+ data['num_vehicles'] = 1
90
+ data['depot'] = start_index
91
+ return data
92
+
93
+ def format_solution(self, manager, routing, solution, pubs_considered):
94
+ """Formats solution for osmnx plotting"""
95
+ index = routing.Start(0)
96
+ route = [index]
97
+
98
+ leg_distances = []
99
+ route_distance = 0
100
+ while not routing.IsEnd(index):
101
+ previous_index = index
102
+ index = solution.Value(routing.NextVar(index))
103
+ if route[0] == manager.IndexToNode(index):
104
+ break
105
+ route.append(manager.IndexToNode(index))
106
+ leg_distance = routing.GetArcCostForVehicle(previous_index, index, 0)
107
+ leg_distances.append(leg_distance)
108
+ route_distance += leg_distance
109
+
110
+ self.optimal_route = [pubs_considered[i] for i in route]
111
+ self.optimal_distance = round(route_distance/1000)
112
+
113
+ def optimise(self, start_point, pubs_considered):
114
+
115
+ def distance_callback(from_index, to_index):
116
+ """Returns the distance between the two nodes."""
117
+ # Convert from routing variable Index to distance matrix NodeIndex
118
+ from_node = manager.IndexToNode(from_index)
119
+ to_node = manager.IndexToNode(to_index)
120
+ return data['distance_matrix'][from_node][to_node]
121
+
122
+ data = self.create_data(start_point, pubs_considered)
123
+
124
+ # Create the routing index manager
125
+ manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
126
+ data['num_vehicles'], data['depot'])
127
+
128
+ # Create Routing Model
129
+ routing = pywrapcp.RoutingModel(manager)
130
+
131
+ transit_callback_index = routing.RegisterTransitCallback(distance_callback)
132
+
133
+ # Define cost of each arc
134
+ routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
135
+
136
+ # Settings for simualted annealing
137
+ search_parameters = pywrapcp.DefaultRoutingSearchParameters()
138
+ search_parameters.local_search_metaheuristic = (
139
+ routing_enums_pb2.LocalSearchMetaheuristic.SIMULATED_ANNEALING)
140
+ search_parameters.time_limit.seconds = 5
141
+ search_parameters.log_search = True
142
+
143
+ # Solve the problem
144
+ solution = routing.SolveWithParameters(search_parameters)
145
+
146
+ # Format solution
147
+ if solution:
148
+ self.format_solution(manager, routing, solution, pubs_considered)
149
+
150
+ if __name__=='main':
151
+ df = pd.read_csv('galway_pubs.csv')
152
+ G = ox.io.load_graphml('galway.graphml')
153
+
154
+ crawler = pub_crawl(df, G)
155
+
156
+ print('Locations of pubs in Galway to consider:')
157
+ crawler.plot_map()
158
+
159
+ initial_route = crawler.initial_route
160
+
161
+ print('Initial route before optimisation (default pub ordering):')
162
+ crawler.plot_route(initial_route)
163
+
164
+ start_pub = 'The Sliding Rock'
165
+ crawler.optimise(start_pub, ['The Sliding Rock', 'The Salt House', 'Caribou'])
166
+
167
+ optimal_route = crawler.optimal_route
168
+ print('Optimised route:')
169
+ print(f'Route distance: {crawler.optimal_distance} meters')
170
+ crawler.plot_route(optimal_route)
171
+
172
+