-
Notifications
You must be signed in to change notification settings - Fork 2
/
vrppd_streamlit.py
229 lines (192 loc) · 9.49 KB
/
vrppd_streamlit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import googlemaps
from vrppd_api import vrppd_api_key, vrppd_init_code
import pandas as pd
import numpy as np
from networkx import DiGraph, from_numpy_matrix, relabel_nodes
import networkx as nx
import matplotlib.pyplot as plt
from numpy import array
from vrpy import VehicleRoutingProblem
import streamlit as st
def read_csv_file(uploaded_file):
if uploaded_file is not None:
df = pd.read_csv(uploaded_file)
df = df.groupby(['Pickup Address', 'Dropoff Address'])['Units'].sum().reset_index()
return df
else:
st.write("Please upload a CSV file")
def run_app(df,
uhaul_address,
keg_dropoff_address,
init_code,
vrppd_api_key,
load_capacity,
num_stops,
correct_init_code):
if init_code == correct_init_code:
def read_data(file_path):
return pd.read_csv(file_path)
def prepare_location_data(df, uhaul_address):
locations = [uhaul_address]
for _, row in df.iterrows():
locations.extend([row['Pickup Address'], row['Dropoff Address']])
return locations
def extract_pickups_deliveries(df):
pickups_deliveries = {}
address_dict = {}
address_dict_reverse = {}
counter = 0
for _, row in df.iterrows():
counter += 1
pickup_index = counter
address_dict_reverse[pickup_index] = row['Pickup Address']
address_dict = update_address_dict(address_dict, row['Pickup Address'], pickup_index)
counter += 1
dropoff_index = counter
address_dict_reverse[dropoff_index] = row['Dropoff Address']
address_dict = update_address_dict(address_dict, row['Dropoff Address'], dropoff_index)
pickups_deliveries[(pickup_index, dropoff_index)] = row['Units']
return pickups_deliveries, address_dict, address_dict_reverse
def update_address_dict(address_dict, address, index):
if address in address_dict:
address_dict[address].append(index)
else:
address_dict[address] = [index]
return address_dict
def get_final_route(prob, address_dict_reverse, uhaul_address, keg_dropoff_address):
addresses = []
ids = []
for key in prob.best_routes:
for item in prob.best_routes[key]:
if isinstance(item, int):
addresses.append(address_dict_reverse[item])
ids.append(item)
stops_to_remove = remove_stops(ids, address_dict_reverse, address_dict)
final_route_ids = [i for i in ids if i not in stops_to_remove]
final_route = [address_dict_reverse[key] for key in final_route_ids]
final_route.insert(0, keg_dropoff_address)
final_route.append(keg_dropoff_address)
final_route.insert(0, uhaul_address)
final_route.append(uhaul_address)
return final_route
def remove_stops(ids, address_dict_reverse, address_dict):
stops_to_remove = []
for i in range(len(ids)):
list_before = ids[:i]
list_after = ids[i+1:]
if ids[i] % 2 == 0:
if any(element in list_after for element in address_dict[address_dict_reverse[ids[i]]]):
stops_to_remove.append(ids[i])
else:
if any(element in list_before for element in address_dict[address_dict_reverse[ids[i]]]):
stops_to_remove.append(ids[i])
return stops_to_remove
def generate_gmap_url(final_route):
base_url = "https://www.google.com/maps/dir/"
formatted_addresses = [address.replace(' ', '+') for address in final_route]
locations = '/'.join(formatted_addresses)
return base_url + locations
def get_distance_matrix(src_locations, dest_locations, api_key=vrppd_api_key):
gmaps = googlemaps.Client(key=api_key) # Replace with your Google Maps API key
# Request distance matrix
distance_matrix = gmaps.distance_matrix(src_locations, dest_locations, mode='driving') # Separate sources and destinations
# Create matrix from response
matrix = []
for row in distance_matrix['rows']:
matrix_row = []
for element in row['elements']:
matrix_row.append(element['distance']['value']) # get distance in meters
matrix.append(matrix_row)
return np.array(matrix)
def chunked_distance_matrix(locations, chunk_size, key):
n = len(locations)
final_matrix = np.zeros((n, n))
for i in range(0, n, chunk_size):
for j in range(0, n, chunk_size):
chunk_src = locations[i:i+chunk_size]
chunk_dest = locations[j:j+chunk_size]
chunk_matrix = get_distance_matrix(chunk_src, chunk_dest, key) # Separate the sources and destinations
final_matrix[i:i+len(chunk_src), j:j+len(chunk_dest)] = chunk_matrix
return final_matrix
if __name__ == "__main__":
# replace with your actual file path and
#uhaul_address = "1616 SE Van Skiver Rd, Port Orchard, WA 98367"
#file_path = r"C:\Users\trent\OneDrive\Documents\GitHub\sandbox\vrppd_test.csv"
# read and prepare data
#df = read_data(file_path)
locations = prepare_location_data(df, uhaul_address)
pickups_deliveries, address_dict, address_dict_reverse = extract_pickups_deliveries(df)
try:
st.write("Getting distance matrix...")
# get distance matrix
distance_matrix = chunked_distance_matrix(locations, 10, vrppd_api_key).tolist()
except:
st.write("Error: Distance Matrix API call failed.")
try:
st.write("Building graph...")
# manipulate distance_matrix
for row in distance_matrix:
row.append(row.pop(0))
for i in range(len(distance_matrix)):
distance_matrix[i] = [0] + distance_matrix[i]
distance_matrix.append([0] * len(distance_matrix[0]))
# create directed graph
A = array(distance_matrix, dtype=[("cost", int)])
G_d = from_numpy_matrix(A, create_using=DiGraph())
G = relabel_nodes(G_d, {0: "Source", len(distance_matrix)-1: "Sink"})
# add requests to the graph
for (u, v) in pickups_deliveries:
G.nodes[u]["request"] = v
G.nodes[u]["demand"] = pickups_deliveries[(u, v)]
G.nodes[v]["demand"] = -pickups_deliveries[(u, v)]
except:
st.write("Error: Graph creation failed.")
try:
# solve the problem
st.write("Solving for route. This may take a while, but there will be an error message if it fails...")
prob = VehicleRoutingProblem(G, load_capacity=int(load_capacity), num_stops=int(num_stops), pickup_delivery=True)
prob.solve(cspy=False)
st.write("Raw Solution:")
st.write(prob.best_routes)
st.write(prob.node_load)
# generate final route
final_route = get_final_route(prob, address_dict_reverse, uhaul_address, keg_dropoff_address)
st.write("Final Route:")
for address in final_route:
#print(address)
st.write(address)
# generate Google Maps URL
final_url = generate_gmap_url(final_route)
#print(final_url)
st.write(final_url)
except:
st.write("Error: No solution found.")
else:
st.write("Error: Code not recognized. Please try again.")
st.title('Distributor VRPPD App')
# Initial condition input
init_code = st.text_input("Code")
# UHaul address input
uhaul_address = st.text_input("Start/Finish Address", "1616 SE Van Skiver Rd, Port Orchard, WA 98367")
# Empty keg dropoff address input
keg_dropoff_address = st.text_input("Empty Keg Pickup/Dropoff Address", "120 Harrison Ave, Port Orchard, WA 98366")
# CSV file upload
csv_file = st.file_uploader("Upload CSV", type=['csv'])
if st.button('Run'):
df = read_csv_file(csv_file)
# Calculate the sum of the 'Units' column
units_sum = df['Units'].sum()
# Calculate the total combined count of values in 'Pickup Address' and 'Dropoff Address' columns
total_count = df['Pickup Address'].count() + df['Dropoff Address'].count()
# Calculate the unique combined count of values in 'Pickup Address' and 'Dropoff Address' columns
unique_count = pd.concat([df['Pickup Address'], df['Dropoff Address']]).nunique()
st.write(f"A total of {units_sum} kegs are being delivered to {unique_count} unique locations.")
st.write(f"Route optimization is being run on {total_count} total stops.")
run_app(df,
uhaul_address,
keg_dropoff_address,
init_code,
vrppd_api_key,
units_sum,
total_count,
vrppd_init_code)