"This notebook shows how the [Google Maps API](https://developers.google.com/maps/) is integrated to plot a courier's path from a restaurant to a customer."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "3bb8099f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[32murban-meal-delivery\u001b[0m, version \u001b[34m0.4.0\u001b[0m\n"
]
}
],
"source": [
"!umd --version"
]
},
{
"cell_type": "markdown",
"id": "5fbc6ccb",
"metadata": {},
"source": [
"### Imports"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "6b5c3483",
"metadata": {},
"outputs": [],
"source": [
"import googlemaps as gm\n",
"\n",
"from urban_meal_delivery import config, db"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "3dd58c43",
"metadata": {},
"outputs": [],
"source": [
"%load_ext lab_black"
]
},
{
"cell_type": "markdown",
"id": "5c7493c8",
"metadata": {},
"source": [
"### Settings"
]
},
{
"cell_type": "markdown",
"id": "2e76e261",
"metadata": {},
"source": [
"Choose `\"Bordeaux\"`, `\"Lyon\"`, or `\"Paris\"`."
"Let's choose a restaurant and then one of its orders."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "baf34d52",
"metadata": {},
"outputs": [],
"source": [
"restaurant = restaurants[0]"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "2e13f48d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3297"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(restaurant.orders)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "9c70a621",
"metadata": {},
"outputs": [],
"source": [
"order = restaurant.orders[10]"
]
},
{
"cell_type": "markdown",
"id": "ff273184",
"metadata": {},
"source": [
"Plot the courier's path from the restaurant to the customer's delivery address."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "d91b07a5",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><span style=\"color:#565656\">Make this Notebook Trusted to load map: File -> Trust Notebook</span><iframe src=\"about:blank\" style=\"position:absolute;width:100%;height:100%;left:0;top:0;border:none !important;\" data-html=%3C%21DOCTYPE%20html%3E%0A%3Chead%3E%20%20%20%20%0A%20%20%20%20%3Cmeta%20http-equiv%3D%22content-type%22%20content%3D%22text/html%3B%20charset%3DUTF-8%22%20/%3E%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%3Cscript%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20L_NO_TOUCH%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20L_DISABLE_3D%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20%3C/script%3E%0A%20%20%20%20%0A%20%20%20%20%3Cstyle%3Ehtml%2C%20body%20%7Bwidth%3A%20100%25%3Bheight%3A%20100%25%3Bmargin%3A%200%3Bpadding%3A%200%3B%7D%3C/style%3E%0A%20%20%20%20%3Cstyle%3E%23map%20%7Bposition%3Aabsolute%3Btop%3A0%3Bbottom%3A0%3Bright%3A0%3Bleft%3A0%3B%7D%3C/style%3E%0A%20%20%20%20%3Cscript%20src%3D%22https%3A//cdn.jsdelivr.net/npm/leaflet%401.6.0/dist/leaflet.js%22%3E%3C/script%3E%0A%20%20%20%20%3Cscript%20src%3D%22https%3A//code.jquery.com/jquery-1.12.4.min.js%22%3E%3C/script%3E%0A%20%20%20%20%3Cscript%20src%3D%22https%3A//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js%22%3E%3C/script%3E%0A%20%20%20%20%3Cscript%20src%3D%22https%3A//cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js%22%3E%3C/script%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//cdn.jsdelivr.net/npm/leaflet%401.6.0/dist/leaflet.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css%22/%3E%0A%20%20%20%20%3Clink%20rel%3D%22stylesheet%22%20href%3D%22https%3A//cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css%22/%3E%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cmeta%20name%3D%22viewport%22%20content%3D%22width%3Ddevice-width%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20initial-scale%3D1.0%2C%20maximum-scale%3D1.0%2C%20user-scalable%3Dno%22%20/%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cstyle%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23map_f07ee29663cb478595eba61d72228bde%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20relative%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20width%3A%20100.0%25%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20height%3A%20100.0%25%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20left%3A%200.0%25%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20top%3A%200.0%25%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C/style%3E%0A%20%20%20%20%20%20%20%20%0A%3C/head%3E%0A%3Cbody%3E%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22folium-map%22%20id%3D%22map_f07ee29663cb478595eba61d72228bde%22%20%3E%3C/div%3E%0A%20%20%20%20%20%20%20%20%0A%3C/body%3E%0A%3Cscript%3E%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20map_f07ee29663cb478595eba61d72228bde%20%3D%20L.map%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22map_f07ee29663cb478595eba61d72228bde%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20center%3A%20%5B48.856614%2C%202.3522219%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20crs%3A%20L.CRS.EPSG3857%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%
],
"text/plain": [
"<folium.folium.Map at 0x7f03e3780550>"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"order.draw()"
]
},
{
"cell_type": "markdown",
"id": "753ee9a5",
"metadata": {},
"source": [
"## Behind the Scenes"
]
},
{
"cell_type": "markdown",
"id": "2ae9598c",
"metadata": {},
"source": [
"The code above integrates the [googlemaps](https://github.com/googlemaps/google-maps-services-python) PyPI package into the application running the UDP's routing optimization.\n",
"\n",
"Let's first look at how we can use the [googlemaps](https://github.com/googlemaps/google-maps-services-python) PyPI package directly to make indiviual API calls.\n",
"\n",
"Then, we'll see how this code is **abstracted** into the **application logic**."
]
},
{
"cell_type": "markdown",
"id": "988ff8c5",
"metadata": {},
"source": [
"### Direct Google Maps API Calls"
]
},
{
"cell_type": "markdown",
"id": "722f9932",
"metadata": {},
"source": [
"With an API key, one can create a new `client` object that handles all communication with the [Google Maps API](https://developers.google.com/maps/)."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "174f6746",
"metadata": {},
"outputs": [],
"source": [
"client = gm.Client(config.GOOGLE_MAPS_API_KEY)"
]
},
{
"cell_type": "markdown",
"id": "a02b9c5a",
"metadata": {},
"source": [
"For example, we can **geocode** an address: This means that we obtain the **latitude-longitude** coordinates for a given *postal* address, and some other structured info."
"For routing applications, we are in particular interested in collecting all the *pair-wise* distances in a **distance matrix** for all involved **origins** and **destinations**."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "961f6fc2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Address(42 Rue De Charonne in Paris)>"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"restaurants[0].address"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "5444d517",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(48.85313, 2.37461)"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"restaurants[0].address.location.lat_lng"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "7ca537e6",
"metadata": {},
"outputs": [],
"source": [
"origins = [\n",
" restaurants[0].address.location.lat_lng,\n",
" restaurants[1].address.location.lat_lng,\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "76eb7cb1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Address(Boulevard De Menilmontant 83 in Paris)>"
"In the application's code base, the above API calls and the related data are modeled as `Path` objects connecting two `Address` objects (cf. [Path class](https://github.com/webartifex/urban-meal-delivery/blob/develop/src/urban_meal_delivery/db/addresses_addresses.py) in the code).\n",
"Let's look at two examples addresses, one from a `Restaurant` and one from a `Customer`."
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "23e5552c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Address(42 Rue De Charonne in Paris)>"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"restaurants[0].address"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "80ff4e0f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Address(Boulevard De Menilmontant 83 in Paris)>"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"restaurants[0].orders[100].delivery_address"
]
},
{
"cell_type": "markdown",
"id": "754b5f16",
"metadata": {},
"source": [
"The `Path.from_addresses()` constructor method takes any number of `Address` objects and creates all entries of a *symmetric* **distance matrix** as `Path` objects.\n",
"\n",
"Here, we only get *one* `Path` object as there are only two `Address` objects."
"<urban_meal_delivery.db.addresses_addresses.Path at 0x7f03e36ac130>"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"path"
]
},
{
"cell_type": "markdown",
"id": "84bf8534",
"metadata": {},
"source": [
"As we assume a *generic* and **symmetric** distance matrix, we call the two `Address` objects \"first\" and \"second\" and not \"restaurant\" and \"customer\". After all, a `Customer` may live in a house that has a `Restaurant` on the ground floor."
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "82f04cf4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Address(42 Rue De Charonne in Paris)>"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"path.first_address"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "fc081362",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Address(Boulevard De Menilmontant 83 in Paris)>"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"path.second_address"
]
},
{
"cell_type": "markdown",
"id": "9af847e4",
"metadata": {},
"source": [
"Because we have `.latitude`-`.longitue` values for each `Address`, we can calculate the path's `.air_distance` even *without* talking to the Google Maps API."
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "b4fbcca9",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1475"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"path.air_distance"
]
},
{
"cell_type": "markdown",
"id": "e99d0883",
"metadata": {},
"source": [
"The `Path.sync_with_google_maps()` method loads all the data needed from Google but makes sure that we do not make another API call if we already have the data."
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "564fa98a",
"metadata": {},
"outputs": [],
"source": [
"path.sync_with_google_maps()"
]
},
{
"cell_type": "markdown",
"id": "d22864f7",
"metadata": {},
"source": [
"Google provides `.bicycle_distance` (in meters) and `.bicylce_duration` (in seconds) approximations for a courier's path from one location to another."
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "c2532b98",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2389"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"path.bicycle_distance"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "b3b69c75",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"702"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"path.bicycle_duration"
]
},
{
"cell_type": "markdown",
"id": "2339190e",
"metadata": {},
"source": [
"In addition, the above `\"legs\"` values are stored as proper UTM coordinates for convenient plotting."