Eco-efficient Delivery#

Montmartre is known to be large hill in Paris where there are some streets with more than 120 metres elevation above sea level. Passing through up hills may be the shortest path between the depot pick-up location and the customer’s drop-off location but is not necessarily the fastest or the most eco-efficient route.

Authors: Mohamed Donia and Samar AbdelFattah Supervisor: Dr. Alaa Khamis

import osmnx as ox
import networkx as nx
import time 
from ipyleaflet import Map
import numpy as np
from math import *
import math
import matplotlib.pyplot as plt
import json
import requests
import pandas as pd
from pandas import json_normalize
import folium 
from folium.plugins import HeatMap

Case 1:#

Map With OSM:#

# Create a graph for Montmartre region in Paris
location_point = (48.885496458, 2.337998648)
G = ox.graph_from_point(location_point,
                        dist=1000,
                        clean_periphery=True,
                        simplify=True,
                        network_type = "drive"
                       )
highlighted = [94239462,94225980]
# marking both the source and destination node
nc = ['r' if node in highlighted else '#336699' for node in G.nodes()]
ns = [60 if node in highlighted else 8 for node in G.nodes()]
fig, ax = ox.plot_graph(G, node_size=ns, node_color=nc, node_zorder=2)
ox.speed.add_edge_speeds(G, hwy_speeds=None, fallback=None, precision=1)
ox.speed.add_edge_travel_times(G, precision=1)
../../_images/d2c168aba29159e72982b07ab09c877882ed81852d97b8a1e5f36fbe2d9f2ebc.png
<networkx.classes.multidigraph.MultiDiGraph at 0x18de13b74c0>

Obtaining Node height#

website = r'https://api.opentopodata.org/v1/'
dataset = 'eudem25m?'
# dictionary to save node numbr and height 
elevation = {}    
# place to save location data to pass it to the wesite 
locations = ""
# place to save node index for sake of elevation dictionary
nodes_index = []
i = 0 
# get last element in itertor 
for last in G.nodes:
    pass
# loop to get elevation 
for n in G.nodes:
    locations = locations + str(G.nodes[n]['y']) + ',' + str(G.nodes[n]['x']) + '|'
    nodes_index.append(n)
    i += 1
    if i % 100 == 0  or n == last :
        url = website + dataset + 'locations=' + locations[:-1]
        r = requests.get(url)
        for segment,k in zip(r.json()['results'], range(len(r.json()['results']))):
            elevation[nodes_index[k]] = segment['elevation']
        locations = ""
        i = 0 
        nodes_index = []

Plot Height Contours#

x = []
y = []
z = []
for node in G.nodes:
    x.append(G.nodes[node]['x'])
    y.append(G.nodes[node]['y'])
    z.append(elevation[node])

#ax = plt.axes(projection='3d')    
#ax.scatter(x, y, z)
#plt.show()
x = np.array(x)
y = np.array(y)
x = (x - np.min(x)) * 111000     #111000
y = (y - np.min(y)) * 180000
ax = plt.figure(figsize=(6, 6), dpi=120).add_subplot(projection='3d')
ax.plot_trisurf(x, y, z, linewidth=0.3, antialiased=True, cmap=plt.cm.viridis)
ax.set_box_aspect((np.max(x), np.max(y), np.max(z)*2.5))
plt.axis('off')
#ax.view_init(elev=50, azim=20)
plt.show()
../../_images/ac05ff4585fa4da13175c7744f2e29929ac7c407c3a7f8f60e7c03aa82c54a5d.png
from IPython.core.display import display, HTML
import json
import numpy as np

def plot3D(X, Y, Z, height=600, xlabel = "X", ylabel = "Y", zlabel = "Z", initialCamera = None):

    options = {
        "width": "100%",
        "style": "surface",
        "showPerspective": True,
        "showGrid": True,
        "showShadow": False,
        "keepAspectRatio": True,
        "height": str(height) + "px"
    }

    if initialCamera:
        options["cameraPosition"] = initialCamera
        
    data = [ {"x": X[y,x], "y": Y[y,x], "z": Z[y,x]} for y in range(X.shape[0]) for x in range(X.shape[1]) ]
    #data = [ {"x": X[i], "y": Y[i], "z": Z[i]} for i in range(X.shape[0]) ]
    visCode = r"""
       <link href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.css" type="text/css" rel="stylesheet" />
       <script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
       <div id="pos" style="top:0px;left:0px;position:absolute;"></div>
       <div id="visualization"></div>
       <script type="text/javascript">
        var data = new vis.DataSet();
        data.add(""" + json.dumps(data) + """);
        var options = """ + json.dumps(options) + """;
        var container = document.getElementById("visualization");
        var graph3d = new vis.Graph3d(container, data, options);
        graph3d.on("cameraPositionChange", function(evt)
        {
            elem = document.getElementById("pos");
            elem.innerHTML = "H: " + evt.horizontal + "<br>V: " + evt.vertical + "<br>D: " + evt.distance;
        });
       </script>
    """
    htmlCode = "<iframe srcdoc='"+visCode+"' width='100%' height='" + str(height) + "px' style='border:0;' scrolling='no'> </iframe>"
    display(HTML(htmlCode))
C:\Users\Alaa\AppData\Local\Temp\ipykernel_9264\1633600677.py:1: DeprecationWarning: Importing display from IPython.core.display is deprecated since IPython 7.14, please import from IPython display
  from IPython.core.display import display, HTML
X, Y = np.meshgrid(np.linspace(-3,3,50),np.linspace(-3,3,50))
Z = np.sin(X**2 + Y**2)**2/(X**2+Y**2)
plot3D(X,Y,Z)
C:\Users\Alaa\.conda\envs\uoft\lib\site-packages\IPython\core\display.py:419: UserWarning: Consider using IPython.display.IFrame instead
  warnings.warn("Consider using IPython.display.IFrame instead")

Co2 Emissions Estimator:#

Second by Second Model#

In practice, it is usually difficult to know all the variables, such as 𝜂, \(𝑝_{𝑖𝑑𝑙𝑒}\), μ, etc. To simplify the fuel consumption model and make it applicable to the eco-routing problem, we only keep the easy-to-measure variables, i.e., \(𝑣_𝑡\) , \(𝑎_𝑡\) , M, and 𝜃 in the model. Additionally, the difficult-to-measure variables will be substituted by parameters to be estimated. Hence, the fuel rate can be represented as follows:

\(f_t=\beta_1 Mgcos(\theta)v_t+ \beta_2Mgsin(\theta)v_t+ \beta_3v_t^3+ \beta_4Ma_tv_t+\beta_5a_tv_t+\beta_6a_t+\beta_7\)

where :

Coeff

Value

\(\beta_1\)

0.0181

\(\beta_2\)

0.0409

\(\beta_3\)

0.0000232

\(\beta_4\)

0.0779

\(\beta_5\)

0.0131

\(\beta_6\)

0.342

\(\beta_7\)

0.101

What we need :

  • car mass M in kg

  • gravitational acceleration g

  • \(\theta\), \(v_{t}\), \(a_{t}\) is function in the road

def estimate_co2_model1(theta=0, v=15, a=0):
    '''
        theta in radian
        v m/s
        a m/s^2
        fuel rate (ml/s)
    ''' 
    # car parameters:
    mass = 1.555
    g = 9.81
    beta=[0, 0.0181,0.0409,0.0000232,0.0779,0.0131,0.342,0.101]
    ft = beta[1] * mass * g * cos(theta) * v + beta[2] * mass * g * sin(theta) * v +\
         beta[3] * v ** 3 + beta[4] * mass * a * v +\
         beta[5] * a * v + beta[6] * a + beta[7]
    return ft

Distance, Time, Fuel Cost Functions:#

# this function to get fuel consumption for specified path
def get_distance_cost(G, route):
    route = list(route)
    weight = 0
    for u,v in zip(route[:-1], route[1:]):
        leng = G[u][v][0]['length']
        weight += leng
    return weight
def get_time_cost(G, route):
    route = list(route)
    weight = 0
    for u,v in zip(route[:-1], route[1:]):
        leng = G[u][v][0]['travel_time']
        weight += leng
    return weight
def get_fuel_cost(G, route):
    route = list(route)
    weight = 0
    for u,v in zip(route[:-1], route[1:]):
        point1_h = elevation[u]
        point2_h = elevation[v]
        leng = G[u][v][0]['length']
        grad = np.max([(point2_h -point1_h) / leng * 100, 0])
        speed = G[u][v][0]['speed_kph'] * 0.277777778 * 3
        fuel = Estimate_Co2_Model2(grad, speed, leng)
        weight += fuel
    return weight
        

Dijkstra Algorithm Implementation:#

'''
    This function is refine of the Dijsktra algorithm. 
    Here, we will terminate the searching after reaching the destination.
'''
def Dijkstra_fine(G,origin,destination, criteria = 'Distance'):
    # convert map nodes into index from 0 to length(nodes) to simplify our algorithm 
    n = len(G.nodes)
    map_nodes = list(G.nodes)
    # initial defination of the distance list with infinity for all nodes and zero for source node
    dist = [math.inf] * n
    dist[map_nodes.index(origin)] = 0
    # mark all nodes as unvisited 
    visited = [False] * n
    parent  = [None] * n
    while sum(visited) <= n:
        # index of the node of the minimum dist with condition that it is not visited
        current_node = dist.index(min(dist[at] for at in range(len(dist)) if visited[at]==False))
        # here, we will  terminate the searching after reaching the the required destination 
        if current_node == map_nodes.index(destination) :
            break
        # iterate over all neighbors of the current node
        for child in nx.neighbors(G,map_nodes[current_node]):
            # get distance between currrent node and child node
            distance = dist[current_node] + func(G,map_nodes[current_node],child, criteria)
            # update minimum distance if the calculated distnace is less than previous distance
            if distance < dist[map_nodes.index(child)]:
                dist[map_nodes.index(child)] = distance
                parent[map_nodes.index(child)] = current_node
        visited[current_node] = True
        #print(str(sum(visited)/len(visited)*100)+'--'+str(current_node))

    # here we can define our path back from the destination   
    path = []            
    path.append(map_nodes.index(destination))
    while path[-1] != None:
        path.append(parent[path[-1]])
    path.pop()
    path.reverse()
    return [map_nodes[i] for i in path]

'''
    this function represent the cost function we will define 
'''
# here we can put our required cost function :
def func(G, node1, node2, criteria):
    if criteria =='Distance':
        distance = G[node1][node2][0]['length'] # length between the nodes
    elif criteria == 'Time':
        distance = G[node1][node2][0]['travel_time'] # time between the nodes
    return distance
class Node:
    # using __slots__ for optimization
    __slots__ = ['node', 'distance', 'parent', 'osmid', 'G']
    # constructor for each node
    def __init__(self ,graph , osmid, distance = 0, parent = None):
        # the dictionary of each node as in networkx graph --- still needed for internal usage
        self.node = graph[osmid]
        # the distance from the parent node --- edge length
        self.distance = distance
        # the parent node
        self.parent = parent
        # unique identifier for each node so we don't use the dictionary returned from osmnx
        self.osmid = osmid
        # the graph
        self.G = graph
    # returning all the nodes adjacent to the node
    #def expand(self):
    #     children = [Node(graph = self.G, osmid = child, distance = self.node[child][0]['length'], parent = self) \
    #                    for child in self.node]
    def expand(self, criteria):
        children = []
        for child in self.node:
            if criteria == 'Time':
                dist = self.node[child][0]['travel_time']
            elif criteria == 'Distance':
                dist = self.node[child][0]['length']
            elif criteria == 'Fuel':
                point1_h = elevation[self.osmid]
                point2_h = elevation[child]
                leng = self.node[child][0]['length']
                grad = np.max([(point2_h -point1_h) / leng * 100, 0])
                speed = self.node[child][0]['speed_kph'] * 0.277777778 * 3
                dist = Estimate_Co2_Model2(grad, speed, leng)
        
            Node_ = Node(graph = self.G, 
                         osmid = child,
                         distance = dist,
                         parent = self)
            children.append(Node_)
        return children 
    # returns the path from that node to the origin as a list and the length of that path
    def path(self):
        node = self
        path = []
        while node:
            path.append(node.osmid)
            node = node.parent
        return path[::-1]
    # the following two methods are for dictating how comparison works
    def __eq__(self, other):
        try:
            return self.osmid == other.osmid
        except:
            return self.osmid == other
    def __hash__(self):
        return hash(self.osmid)
def Dijkstra(G,origin,destination,criteria = 'Distance'):
    seen = set()         # for dealing with self loops
    shortest_dist = {osmid: math.inf for osmid in G.nodes()}
    unrelaxed_nodes = [Node(graph = G, osmid = osmid) for osmid in G.nodes()]

    shortest_dist[origin.osmid] = 0
    found = False

    while len(unrelaxed_nodes) > 0 and not found:
        node = min(unrelaxed_nodes, key = lambda node : shortest_dist[node.osmid])  
        # relaxing the node, so this node's value in shortest_dist
        # is the shortest distance between the origin and destination
        unrelaxed_nodes.remove(node)
        seen.add(node.osmid)  
        # if the destination node has been relaxed
        # then that is the route we want
        if node == destination:
            route = node.path()
            cost = shortest_dist[node.osmid]
            found = True
            continue
        # otherwise, let's relax edges of its neighbours
        for child in node.expand(criteria):
            # skip self-loops
            if child.osmid in seen: continue
            # this doesn't look pretty because Node is just an object
            # so retrieving it is a bit verbose -- if you have nicer 
            # way to do that, please open an issue
            child_obj = next((node for node in unrelaxed_nodes if node.osmid == child.osmid), None)
            child_obj.distance = child.distance
            distance = shortest_dist[node.osmid] + child.distance
            if distance < shortest_dist[child_obj.osmid]:
                shortest_dist[child_obj.osmid] = distance
                child_obj.parent = node
    return route

Get Path With Minimum Distance#

origin =  highlighted[0]
destination = highlighted[1]
route1 = Dijkstra_fine(G,origin,destination, 'Distance')
#route2 = Dijkstra_fine(G,origin,destination, 'Time')
ox.plot_graph_route(G, route1, route_color='r', route_linewidth=4, route_alpha=0.6)
../../_images/85548ea9323fba9459d62c01dc879741609b2e59e3ada83081d99f74746c6fac.png
(<Figure size 800x800 with 1 Axes>, <AxesSubplot:>)

Routes With Minimum Distance, Time and Fuel Criteria#

# first define the origin/source nodes as Node
origin = Node(graph = G, osmid = highlighted[0])
destination = Node(graph = G, osmid = highlighted[1])
route1 = Dijkstra(G,origin,destination, 'Distance')
route2 = Dijkstra(G,origin,destination, 'Time')
route3 = Dijkstra(G,origin,destination, 'Fuel')
ox.plot_graph_routes(G, 
                     [route1, route2, route3], 
                     route_colors=['r', 'b', 'g'], 
                     route_linewidths=6, 
                     node_size=0)
../../_images/cb4abd62c3fadd2751d37410403d51c0383e8ef6a378c987b6b64a2ed11b1460.png
(<Figure size 800x800 with 1 Axes>, <AxesSubplot:>)
print("For route with minimum distance : ")
print("Distance cost : ", get_distance_cost(G, route1), ' m')
print("Time cost :", get_time_cost(G, route1), 'sec')
print("Fuel cost :", get_fuel_cost(G, route1), 'gram')
# ------------------------------------- #
print("")
print("For route with minimum time : ")
print("Distance cost : ", get_distance_cost(G, route2), ' m')
print("Time cost :", get_time_cost(G, route2), 'sec')
print("Fuel cost :", get_fuel_cost(G, route2), 'gram')
# -------------------------------------- #
print("")
print("For route with minimum fuel : ")
print("Distance cost : ", get_distance_cost(G, route3), ' m')
print("Time cost :", get_time_cost(G, route3), 'sec')
print("Fuel cost :", get_fuel_cost(G, route3), 'gram')
For route with minimum distance : 
Distance cost :  1583.34  m
Time cost : 190.1 sec
Fuel cost : 171.1186893295503 gram

For route with minimum time : 
Distance cost :  1583.34  m
Time cost : 190.1 sec
Fuel cost : 171.1186893295503 gram

For route with minimum fuel : 
Distance cost :  1683.3060000000003  m
Time cost : 202.2 sec
Fuel cost : 163.499101709951 gram

Show Routes on Folium Map:#

# generate a new map
folium_map = folium.Map(location=location_point,
                        zoom_start=15,
                        tiles="OpenStreetMap")
paths = [route1, route2, route3]
color_list = ['#FF3333', '#0F3FE5', '#55DF15']
for path,color in zip(paths,color_list):
    path_coordinates = []
    for i in path:
        path_coordinates.append([G.nodes[i]['y'],G.nodes[i]['x']])
    line = folium.PolyLine(path_coordinates,
                           weight=4,
                           color=color
                          ).add_to(folium_map)
    
folium_map
Make this Notebook Trusted to load map: File -> Trust Notebook

Case 2:#

location_point = (48.885496458, 2.337998648)
G = ox.graph_from_point(location_point,
                        dist=3000,
                        clean_periphery=True,
                        simplify=True,
                        network_type = "drive"
                       )
highlighted = [1430111689,25625242]
# marking both the source and destination node
nc = ['r' if node in highlighted else '#336699' for node in G.nodes()]
ns = [60 if node in highlighted else 8 for node in G.nodes()]
fig, ax = ox.plot_graph(G, node_size=ns, node_color=nc, node_zorder=2)
# add edge speeds :
ox.speed.add_edge_speeds(G, hwy_speeds=None, fallback=None, precision=1)
# add edge travel times :
ox.speed.add_edge_travel_times(G, precision=1)
# add edge bearings :
ox.bearing.add_edge_bearings(G, precision=1)
# add elevation from raster file
# the raster file of the area of interest is obtained from https://srtm.csi.cgiar.org/srtmdata/
ox.elevation.add_node_elevations_raster(G, "srtm_37_03.tif", band=1, cpus=None) 
# add edge grades :
ox.elevation.add_edge_grades(G, add_absolute=True, precision=3)
../../_images/3650d3d97ec3e2c15cc3736d09e75ba2c1f2c80058bed0adfe8240845ff0fa9a.png
<networkx.classes.multidigraph.MultiDiGraph at 0x18decb71180>

Link-Level Model#

def Estimate_Co2_Model2(gl=0, v=15, length= 0):
    '''
    format of received paremeters :
        gl -----------> (in percentage)
        length -------> m
        v ------------> m/s
        
    '''
    
    length /= 1609.344
    v *= 2.23693629
    '''
    model parameters : 
        length -------> miles
        v ------------> (in miles per hour)
        fuel ---------> grams per mile
    ''' 
    
    # car parameters:
    alpha=[6.8, -0.14, 0.00392, -0.000052, 0.000000257, 0.137 * 1.5]
    # fuel consumption gram per mile :
    ft = exp(alpha[0] + alpha[1] * v + alpha[2] * v**2 + alpha[3] * v**3 + alpha[4] * v**4 + alpha[5] * gl)
    # fuel consumption gram :
    fuel = ft * length
    return fuel

Distance, Time, Fuel Cost Functions:#

# this function to get fuel consumption for specified path
def get_distance_cost(G, route):
    route = list(route)
    weight = 0
    for u,v in zip(route[:-1], route[1:]):
        leng = G[u][v][0]['length']
        weight += leng
    return weight
def get_time_cost(G, route):
    route = list(route)
    weight = 0
    for u,v in zip(route[:-1], route[1:]):
        leng = G[u][v][0]['travel_time']
        weight += leng
    return weight
def get_fuel_cost(G, route):
    route = list(route)
    weight = 0
    for u,v in zip(route[:-1], route[1:]):
        '''
        point1_h = elevation[u]
        point2_h = elevation[v]
        leng = G[u][v][0]['length']
        grad = np.max([(point2_h -point1_h) / leng * 100, 0])
        speed = G[u][v][0]['speed_kph'] * 0.277777778 * 2
        '''
        leng = G[u][v][0]['length']
        speed = G[u][v][0]['speed_kph'] * 0.277777778 * 2
        grad = np.max([G[u][v][0]['grade'] * 100 , 0])
        fuel = Estimate_Co2_Model2(grad, speed, leng)
        weight += fuel
    return weight
def get_turns(G, route):
    edge_bearings = []
    route = list(route)
    for u,v in zip(route[:-1], route[1:]):
        edge_bearings.append(G[u][v][0]['bearing'])
    # get the difference :
    right_turns = 0
    left_turns = 0
    for u_bearing,v_bearing in zip(edge_bearings[1:], edge_bearings[:-1]):
        diff = u_bearing - v_bearing
        if diff > 60:
            right_turns +=1
        elif diff < -60:
            left_turns +=1
    return right_turns, left_turns

Dijkstra Algorithm Implementation:#

class Node:
    # using __slots__ for optimization
    __slots__ = ['node', 'distance', 'parent', 'osmid', 'G']
    # constructor for each node
    def __init__(self ,graph , osmid, distance = 0, parent = None):
        # the dictionary of each node as in networkx graph --- still needed for internal usage
        self.node = graph[osmid]
        # the distance from the parent node --- edge length
        self.distance = distance
        # the parent node
        self.parent = parent
        # unique identifier for each node so we don't use the dictionary returned from osmnx
        self.osmid = osmid
        # the graph
        self.G = graph
    # returning all the nodes adjacent to the node
    #def expand(self):
    #     children = [Node(graph = self.G, osmid = child, distance = self.node[child][0]['length'], parent = self) \
    #                    for child in self.node]
    def expand(self, criteria):
        children = []
        for child in self.node:
            if criteria == 'Time':
                dist = self.node[child][0]['travel_time']
            elif criteria == 'Distance':
                dist = self.node[child][0]['length']
            elif criteria == 'Fuel':
                '''
                point1_h = elevation[self.osmid]
                point2_h = elevation[child]
                leng = self.node[child][0]['length']
                grad = np.max([(point2_h -point1_h) / leng * 100, 0])
                speed = self.node[child][0]['speed_kph'] * 0.277777778 * 2
                dist = Estimate_Co2_Model2(grad, speed, leng)
                '''
                leng = self.node[child][0]['length']
                speed = self.node[child][0]['speed_kph'] * 0.277777778 * 2
                grad = np.max([self.node[child][0]['grade'] * 100 , 0])
                dist = Estimate_Co2_Model2(grad, speed, leng)
        
            Node_ = Node(graph = self.G, 
                         osmid = child,
                         distance = dist,
                         parent = self)
            children.append(Node_)
        return children 
    # returns the path from that node to the origin as a list and the length of that path
    def path(self):
        node = self
        path = []
        while node:
            path.append(node.osmid)
            node = node.parent
        return path[::-1]
    # the following two methods are for dictating how comparison works
    def __eq__(self, other):
        try:
            return self.osmid == other.osmid
        except:
            return self.osmid == other
    def __hash__(self):
        return hash(self.osmid)
def Dijkstra(G,origin,destination,criteria = 'Distance'):
    seen = set()         # for dealing with self loops
    shortest_dist = {osmid: math.inf for osmid in G.nodes()}
    unrelaxed_nodes = [Node(graph = G, osmid = osmid) for osmid in G.nodes()]

    shortest_dist[origin.osmid] = 0
    found = False

    while len(unrelaxed_nodes) > 0 and not found:
        node = min(unrelaxed_nodes, key = lambda node : shortest_dist[node.osmid])  
        # relaxing the node, so this node's value in shortest_dist
        # is the shortest distance between the origin and destination
        unrelaxed_nodes.remove(node)
        seen.add(node.osmid)  
        # if the destination node has been relaxed
        # then that is the route we want
        if node == destination:
            route = node.path()
            cost = shortest_dist[node.osmid]
            found = True
            continue
        # otherwise, let's relax edges of its neighbours
        for child in node.expand(criteria):
            # skip self-loops
            if child.osmid in seen: continue
            # this doesn't look pretty because Node is just an object
            # so retrieving it is a bit verbose -- if you have nicer 
            # way to do that, please open an issue
            child_obj = next((node for node in unrelaxed_nodes if node.osmid == child.osmid), None)
            child_obj.distance = child.distance
            distance = shortest_dist[node.osmid] + child.distance
            if distance < shortest_dist[child_obj.osmid]:
                shortest_dist[child_obj.osmid] = distance
                child_obj.parent = node
    return route

Routes With Minimum Distance, Time and Fuel Criteria#

# first define the origin/source nodes as Node
origin = Node(graph = G, osmid = highlighted[0])
destination = Node(graph = G, osmid = highlighted[1])
route1 = Dijkstra(G,origin,destination, 'Distance')
route2 = Dijkstra(G,origin,destination, 'Time')
route3 = Dijkstra(G,origin,destination, 'Fuel')
ox.plot_graph_routes(G, 
                     [route1, route2, route3], 
                     route_colors=['r', 'b', 'g'], 
                     route_linewidths=6, 
                     node_size=0)
../../_images/bcaa5599c9f297caccbb522d2154310c0ae744a13a32f8b9a319eeee32d40fbe.png
(<Figure size 800x800 with 1 Axes>, <AxesSubplot:>)
print("For route with minimum distance : ")
dist_route1 = round(get_distance_cost(G, route1))
time_route1 = round(get_time_cost(G, route1))
fuel_route1 = round(get_fuel_cost(G, route1))
print("Distance cost : ", dist_route1, ' m')
print("Time cost :", time_route1, 'sec')
print("Fuel cost :", fuel_route1, 'gram')
# ------------------------------------- #
print("")
print("For route with minimum time : ")
dist_route2 = round(get_distance_cost(G, route2))
time_route2 = round(get_time_cost(G, route2))
fuel_route2 = round(get_fuel_cost(G, route2))
print("Distance cost : ", dist_route2, ' m')
print("Time cost :", time_route2, 'sec')
print("Fuel cost :", fuel_route2, 'gram')
# -------------------------------------- #
print("")
print("For route with minimum fuel : ")
dist_route3 = round(get_distance_cost(G, route3))
time_route3 = round(get_time_cost(G, route3))
fuel_route3 = round(get_fuel_cost(G, route3))
print("Distance cost : ", dist_route3, ' m')
print("Time cost :", time_route3, 'sec')
print("Fuel cost :", fuel_route3, 'gram')
# --------------------------------------- #
right_turns_route1, left_turns_route1 = get_turns(G, route1)
right_turns_route2, left_turns_route2 = get_turns(G, route2)
right_turns_route3, left_turns_route3 = get_turns(G, route3)
For route with minimum distance : 
Distance cost :  9044  m
Time cost : 1033 sec
Fuel cost : 103908 gram

For route with minimum time : 
Distance cost :  10283  m
Time cost : 847 sec
Fuel cost : 6189 gram

For route with minimum fuel : 
Distance cost :  9577  m
Time cost : 1021 sec
Fuel cost : 857 gram

Show Routes on Folium Map:#

x = []
y = []
z = []
for node in G.nodes:
    x.append(G.nodes[node]['x'])
    y.append(G.nodes[node]['y'])
    z.append(G.nodes[node]['elevation'])
x = np.array(x)
y = np.array(y)
z = np.array(z)
data=np.array([y,x,z]).T
# generate a new map
folium_map = folium.Map(location=location_point,
                        zoom_start=13,
                        tiles="OpenStreetMap")
paths = [route1, route2, route3]
color_list = ['#FF3333', '#0F3FE5', '#55DF15']    # red, blue, green
for path,color in zip(paths,color_list):
    path_coordinates = []
    for i in path:
        path_coordinates.append([G.nodes[i]['y'],G.nodes[i]['x']])
    line = folium.PolyLine(path_coordinates,
                           weight=4,
                           color=color
                          ).add_to(folium_map)
# add folium marker
folium.Marker(location=[G.nodes[highlighted[0]]['y'],
                        G.nodes[highlighted[0]]['x']],
              icon= folium.Icon(color='blue',icon='map-marker'),
              popup = 'Origin').add_to(folium_map)

folium.Marker(location=[G.nodes[highlighted[1]]['y'],
                        G.nodes[highlighted[1]]['x']],
              icon= folium.Icon(color='red',icon='map-marker'),
              popup = 'Destination').add_to(folium_map)


HeatMap(data, radius=15, opacity = 1).add_to(folium.FeatureGroup(name='Heat Map').add_to(folium_map))
folium.LayerControl().add_to(folium_map)
# show the map layers:
folium_map
Make this Notebook Trusted to load map: File -> Trust Notebook
df = pd.DataFrame([[dist_route1, time_route1, fuel_route1, right_turns_route1, left_turns_route1],
                   [dist_route2, time_route2, fuel_route2, right_turns_route2, left_turns_route2],
                   [dist_route3, time_route3, fuel_route3, right_turns_route3, left_turns_route3]],
                  index=pd.Index(['Min Dist Criteria','Min Time Criteria', 'Min Fuel Criteria']),
                  columns=pd.Index(['Distance (metre)','Time (sec)','Fuel (gram)', 'Right turns', 'Left turns']))

df.style.highlight_min(color = 'lightgreen', 
                       axis = 0)
  Distance (metre) Time (sec) Fuel (gram) Right turns Left turns
Min Dist Criteria 9044 1033 103908 7 7
Min Time Criteria 10283 847 6189 5 4
Min Fuel Criteria 9577 1021 857 3 4