Create a route optimization algorithm with zero costs using google's OR-tools and OSRM Part 4
We continue with our series and we create a function that takes care of reading the output of the algorithm, converting it to a readable form and plotting the routes on the map.
def print_solution(data, manager, routing, assignment):
Route = []
for vehicle_id in range(data['num_vehicles']:
index = routing.Start(vehicle_id)
idx = manager.IndexToNode(index)
plan_output = 'Route for vehicle {} with ID {} and capacity {}:\n'.format(vehicle_id, data['IDs'][idx], data['capacity'][vehicle_id])
Route[Iter].append([data['locations'][idx], data['IDs'][idx]])
prev_index = index
index = assignment.Value(routing.NextVar(index))
route_dur = 0
Route_cost = 0
while not routing.IsEnd(index):
plan_output += 'client ID: {} demand: {} -> '.format(data['IDs'][index],data['demands'][index])
route_cost += routing.GetArcCostForVehicle(prev_index, index, vehicle_id)
prev_idx = manager.IndexToNode(prev_index)
idx = manager.IndexToNode(index)
Route[Iter].append([data['locations'][idx], data['IDs'][idx]])
route_dur += data['durations'][prev_idx][idx]
index = assignment.Value(routing.NextVar(index))
plan_output += 'Route cost: ' + str(route_cost) + '\n'
plan_output += 'Route duration: ' + str(route_dur) +'\n'
return Route
Route = []
for vehicle_id in range(data['num_vehicles']:
index = routing.Start(vehicle_id)
idx = manager.IndexToNode(index)
plan_output = 'Route for vehicle {} with ID {} and capacity {}:\n'.format(vehicle_id, data['IDs'][idx], data['capacity'][vehicle_id])
Route[Iter].append([data['locations'][idx], data['IDs'][idx]])
prev_index = index
index = assignment.Value(routing.NextVar(index))
route_dur = 0
Route_cost = 0
while not routing.IsEnd(index):
plan_output += 'client ID: {} demand: {} -> '.format(data['IDs'][index],data['demands'][index])
route_cost += routing.GetArcCostForVehicle(prev_index, index, vehicle_id)
prev_idx = manager.IndexToNode(prev_index)
idx = manager.IndexToNode(index)
Route[Iter].append([data['locations'][idx], data['IDs'][idx]])
route_dur += data['durations'][prev_idx][idx]
index = assignment.Value(routing.NextVar(index))
plan_output += 'Route cost: ' + str(route_cost) + '\n'
plan_output += 'Route duration: ' + str(route_dur) +'\n'
return Route
So the idea is simple here,
There are as much routes as the number of our vehicles,
so we get the starting node index of each route routing.Start(vehicle_id) then we convert that to our index notation manager.IndexToNode(index) , if you are not following this part then you should read the previous parts of my tutorial first.And we use the index to get the respective IDs and locations of each node [data['locations'][idx], data['IDs'][idx]] on the route to construct a route list.
We can get the total cost of the route which is the distance + duration + slack + all the variables we have added using GetArcCostForVehicle
If you could like to calculate the distance only or the duration, then you can use their respective matrices data['durations'][prev_idx][idx] .
Next we define the plotting function which generates the js file using the locations in the Route list and gives a different color to each route.
def plot(Route, simple = False):
colors = [ '#008000', '#800080', '#FF00FF', '#008000',
'#000000', '#FFFF00', 'gray', 'red',
'purple', '#99a3a4', '#833a2a', '#FA8072']
color_int = len(colors)-1
con = 'a'
fin_con = ''
waypoints = ""
full = "var map = L.map('map');\nL.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap contributors'+"'}).addTo(map);\nfunction calculateRoute(from) {\n var blueIcon = new L.Icon({iconUrl: '/dist/logos/men-silhouette.png',\nshadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.4/images/marker-shadow.png',\niconSize: [45, 51],\niconAnchor: [12, 41],\npopupAnchor: [1, -34],\nshadowSize: [41, 41]});\nvar greenIcon = new L.Icon({iconUrl: '/dist/logos/racing.png',\nshadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.4/images/marker-shadow.png',\niconSize: [45, 51],\niconAnchor: [12, 41],\npopupAnchor: [1, -34],\nshadowSize: [41, 41]});\nvar redIcon = new L.Icon({iconUrl: '/dist/logos/big.png',\nshadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.4/images/marker-shadow.png',\niconSize: [45, 51],\niconAnchor: [12, 41],\npopupAnchor: [1, -34],\nshadowSize: [41, 41]});\n "
for rl_i in range(len(Route)):
way = "L.latLng("
head = "if(from == '%s'){"%(Route[rl_i][0][1]) + "var %s = L.Routing.control(L.extend(window.lrmConfig,\n {waypoints: ["%(fin_con+con)
if ord(con) == 122:
fin_con+='a'
con = 'a'
con = chr(ord(con)+1)
for r_i in range(len(Route[rl_i])):
waypoints += way+ str(Route[rl_i][r_i][0][0])+','+str(Route[rl_i][r_i][0][1])+'),\n'
tail = "],\n"+"createMarker: function(i, wp, nWps) {\nif (i === 0 || i === nWps - 1) {\nif (i === nWps - 1) {return L.marker(wp.latLng, {icon: redIcon})}\nreturn L.marker(wp.latLng, {icon: greenIcon})}\nelse {return L.marker(wp.latLng, {icon: blueIcon})}},\nrouter: new L.Routing.OSRMv1({serviceUrl: 'http://127.0.0.1:5000/route/v1'}),\nlanguage: 'de',\ngeocoder: L.Control.Geocoder.nominatim(),\nrouteWhileDragging: true,\nreverseWaypoints: true,\nshowAlternatives: false,\nlineOptions: {styles: [{color: '%s'"%colors[color_int]+", opacity: 1, weight: 5}]},altLineOptions: {styles: [{color: 'black', opacity: 0.15, weight: 9},{color: 'white', opacity: 0.8, weight: 6},{color: 'blue', opacity: 0.5, weight: 2}]}})).addTo(map);}\n"
full += head + waypoints + tail
if color_int <= 0:
color_int = len(colors)-1
else:
color_int -= 1
waypoints = ''
full += '}$(document).ready(function () {$("#calculate-route").submit(function(event) {event.preventDefault();calculateRoute($("#from").val());\n});\n});'
f = open("localized.js","w+")
f.write(full)
f.close()
Ignore everything in the full variable, if you read the localized.js file within leaflet, you will find that full is just the head of the js file and its useful for the initialization of leaflet but we have no use in controlling it.colors = [ '#008000', '#800080', '#FF00FF', '#008000',
'#000000', '#FFFF00', 'gray', 'red',
'purple', '#99a3a4', '#833a2a', '#FA8072']
color_int = len(colors)-1
con = 'a'
fin_con = ''
waypoints = ""
full = "var map = L.map('map');\nL.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap contributors'+"'}).addTo(map);\nfunction calculateRoute(from) {\n var blueIcon = new L.Icon({iconUrl: '/dist/logos/men-silhouette.png',\nshadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.4/images/marker-shadow.png',\niconSize: [45, 51],\niconAnchor: [12, 41],\npopupAnchor: [1, -34],\nshadowSize: [41, 41]});\nvar greenIcon = new L.Icon({iconUrl: '/dist/logos/racing.png',\nshadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.4/images/marker-shadow.png',\niconSize: [45, 51],\niconAnchor: [12, 41],\npopupAnchor: [1, -34],\nshadowSize: [41, 41]});\nvar redIcon = new L.Icon({iconUrl: '/dist/logos/big.png',\nshadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.4/images/marker-shadow.png',\niconSize: [45, 51],\niconAnchor: [12, 41],\npopupAnchor: [1, -34],\nshadowSize: [41, 41]});\n "
for rl_i in range(len(Route)):
way = "L.latLng("
head = "if(from == '%s'){"%(Route[rl_i][0][1]) + "var %s = L.Routing.control(L.extend(window.lrmConfig,\n {waypoints: ["%(fin_con+con)
if ord(con) == 122:
fin_con+='a'
con = 'a'
con = chr(ord(con)+1)
for r_i in range(len(Route[rl_i])):
waypoints += way+ str(Route[rl_i][r_i][0][0])+','+str(Route[rl_i][r_i][0][1])+'),\n'
tail = "],\n"+"createMarker: function(i, wp, nWps) {\nif (i === 0 || i === nWps - 1) {\nif (i === nWps - 1) {return L.marker(wp.latLng, {icon: redIcon})}\nreturn L.marker(wp.latLng, {icon: greenIcon})}\nelse {return L.marker(wp.latLng, {icon: blueIcon})}},\nrouter: new L.Routing.OSRMv1({serviceUrl: 'http://127.0.0.1:5000/route/v1'}),\nlanguage: 'de',\ngeocoder: L.Control.Geocoder.nominatim(),\nrouteWhileDragging: true,\nreverseWaypoints: true,\nshowAlternatives: false,\nlineOptions: {styles: [{color: '%s'"%colors[color_int]+", opacity: 1, weight: 5}]},altLineOptions: {styles: [{color: 'black', opacity: 0.15, weight: 9},{color: 'white', opacity: 0.8, weight: 6},{color: 'blue', opacity: 0.5, weight: 2}]}})).addTo(map);}\n"
full += head + waypoints + tail
if color_int <= 0:
color_int = len(colors)-1
else:
color_int -= 1
waypoints = ''
full += '}$(document).ready(function () {$("#calculate-route").submit(function(event) {event.preventDefault();calculateRoute($("#from").val());\n});\n});'
f = open("localized.js","w+")
f.write(full)
f.close()
the ord(con) part is just useful for renaming the different routes, ord returns an integer corresponding to the unicode number, basically we give a route a name starting from a to z, if we reach z then we append 'a' to the name to become za and we start incrementing by one again.
The waypoints part is the only part that we need to control, it adds the location in lat, lng so when the html file is opened, it queries for the route between the locations using osrm.
To know more about leaflet, please check the tutorial.
Keep in mind there are the .png icons that you need to put in the same directory and they should have the same name as up here.
Then you can start localized.html to find something like this.
Comments
Post a Comment