urban-meal-delivery/research/08_google_maps.ipynb

923 lines
34 KiB
Text
Raw Normal View History

{
"cells": [
{
"cell_type": "markdown",
"id": "3d0f2ad3",
"metadata": {},
"source": [
"# Google Maps Integration"
]
},
{
"cell_type": "markdown",
"id": "e3e91187",
"metadata": {},
"source": [
"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\"`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "5c6fc1f0",
"metadata": {},
"outputs": [],
"source": [
"city_name = \"Paris\""
]
},
{
"cell_type": "markdown",
"id": "435f8cfb",
"metadata": {},
"source": [
"### Load the Data"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "ac59dc73",
"metadata": {},
"outputs": [],
"source": [
"city = db.session.query(db.City).filter_by(name=city_name).one()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "b15b36f2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<City(Paris)>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"city"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "6f76bece",
"metadata": {},
"outputs": [],
"source": [
"restaurants = (\n",
" db.session.query(db.Restaurant)\n",
" .join(db.Address)\n",
" .filter(db.Address.city == city)\n",
" .all()\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "7c3968be",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1153"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(restaurants)"
]
},
{
"cell_type": "markdown",
"id": "baaf60b1",
"metadata": {},
"source": [
"## Visualization"
]
},
{
"cell_type": "markdown",
"id": "16176dc0",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "4168a03c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'address_components': [{'long_name': '2',\n",
" 'short_name': '2',\n",
" 'types': ['street_number']},\n",
" {'long_name': 'Burgplatz', 'short_name': 'Burgpl.', 'types': ['route']},\n",
" {'long_name': 'Vallendar',\n",
" 'short_name': 'Vallendar',\n",
" 'types': ['locality', 'political']},\n",
" {'long_name': 'Mayen-Koblenz',\n",
" 'short_name': 'Mayen-Koblenz',\n",
" 'types': ['administrative_area_level_3', 'political']},\n",
" {'long_name': 'Rheinland-Pfalz',\n",
" 'short_name': 'RP',\n",
" 'types': ['administrative_area_level_1', 'political']},\n",
" {'long_name': 'Germany',\n",
" 'short_name': 'DE',\n",
" 'types': ['country', 'political']},\n",
" {'long_name': '56179', 'short_name': '56179', 'types': ['postal_code']}],\n",
" 'formatted_address': 'Burgpl. 2, 56179 Vallendar, Germany',\n",
" 'geometry': {'bounds': {'northeast': {'lat': 50.40070650000001,\n",
" 'lng': 7.6142806},\n",
" 'southwest': {'lat': 50.4001466, 'lng': 7.6128609}},\n",
" 'location': {'lat': 50.40035899999999, 'lng': 7.613506600000001},\n",
" 'location_type': 'ROOFTOP',\n",
" 'viewport': {'northeast': {'lat': 50.4017755302915,\n",
" 'lng': 7.614919730291503},\n",
" 'southwest': {'lat': 50.3990775697085, 'lng': 7.612221769708499}}},\n",
" 'place_id': 'ChIJjcydwDx9vkcR6sSdsYNtPOo',\n",
" 'types': ['premise']}]"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"client.geocode(\"Burgplatz 2, Vallendar\")"
]
},
{
"cell_type": "markdown",
"id": "dc2c1fe4",
"metadata": {},
"source": [
"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)>"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"restaurants[0].orders[100].delivery_address"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "f3c4674c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(48.864269, 2.385568)"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"restaurants[0].orders[100].delivery_address.location.lat_lng"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "6a1dfee8",
"metadata": {},
"outputs": [],
"source": [
"destinations = [\n",
" restaurants[0].orders[100].delivery_address.location.lat_lng,\n",
" restaurants[1].orders[100].delivery_address.location.lat_lng,\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "20dfd317",
"metadata": {},
"source": [
"The [Google Maps API](https://developers.google.com/maps/) provides a `.distance_matrix()` method for that."
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "25575bfa",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'destination_addresses': ['83 Bd de Ménilmontant, 75011 Paris, France',\n",
" '7 Rue des Abbesses, 75018 Paris, France'],\n",
" 'origin_addresses': ['19 Rue de Charonne, 75011 Paris, France',\n",
" '64 Rue Saint-Lazare, 75009 Paris, France'],\n",
" 'rows': [{'elements': [{'distance': {'text': '2.4 km', 'value': 2389},\n",
" 'duration': {'text': '12 mins', 'value': 701},\n",
" 'status': 'OK'},\n",
" {'distance': {'text': '5.8 km', 'value': 5798},\n",
" 'duration': {'text': '24 mins', 'value': 1454},\n",
" 'status': 'OK'}]},\n",
" {'elements': [{'distance': {'text': '5.7 km', 'value': 5735},\n",
" 'duration': {'text': '22 mins', 'value': 1332},\n",
" 'status': 'OK'},\n",
" {'distance': {'text': '1.2 km', 'value': 1211},\n",
" 'duration': {'text': '10 mins', 'value': 574},\n",
" 'status': 'OK'}]}],\n",
" 'status': 'OK'}"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"client.distance_matrix(\n",
" origins=origins,\n",
" destinations=destinations,\n",
" mode=\"bicycling\", # Choose an appropriate travelling mode\n",
")"
]
},
{
"cell_type": "markdown",
"id": "be52a0f3",
"metadata": {},
"source": [
"The `.directions()` method provides the **legs** (i.e., **waypoints**) of a **route** from *one* location to another."
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "5a5e31b5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'bounds': {'northeast': {'lat': 48.8643175, 'lng': 2.3887503},\n",
" 'southwest': {'lat': 48.8530147, 'lng': 2.3745434}},\n",
" 'copyrights': 'Map data ©2021',\n",
" 'legs': [{'distance': {'text': '2.0 km', 'value': 2008},\n",
" 'duration': {'text': '6 mins', 'value': 358},\n",
" 'end_address': '19 Rue de Charonne, 75011 Paris, France',\n",
" 'end_location': {'lat': 48.8530828, 'lng': 2.3745434},\n",
" 'start_address': '83 Bd de Ménilmontant, 75011 Paris, France',\n",
" 'start_location': {'lat': 48.8643175, 'lng': 2.3856677},\n",
" 'steps': [{'distance': {'text': '0.6 km', 'value': 554},\n",
" 'duration': {'text': '2 mins', 'value': 98},\n",
" 'end_location': {'lat': 48.85983239999999, 'lng': 2.3887503},\n",
" 'html_instructions': 'Head <b>southeast</b> on <b>Bd de Ménilmontant</b> toward <b>Rue de Tlemcen</b>/<wbr/><b>Rue Spinoza</b>',\n",
" 'polyline': {'points': '_yfiHm}pMt@{@`D{DJMBCh@m@HGDCLEjJsD`@MPGB?`@QjCaA'},\n",
" 'start_location': {'lat': 48.8643175, 'lng': 2.3856677},\n",
" 'travel_mode': 'BICYCLING'},\n",
" {'distance': {'text': '0.8 km', 'value': 750},\n",
" 'duration': {'text': '2 mins', 'value': 127},\n",
" 'end_location': {'lat': 48.8573962, 'lng': 2.379628},\n",
" 'html_instructions': 'Turn <b>right</b> onto <b>Rue de la Roquette</b><div style=\"font-size:0.9em\">Go through 1 roundabout</div>',\n",
" 'maneuver': 'turn-right',\n",
" 'polyline': {'points': '}|eiHupqMTrA\\\\pBHd@TtADZ@NBN\\\\xBHp@BJ`@~B`@~BNz@^~B@FRjAf@xCP|@Fd@RhA@N@JHh@@NBNBNBV@N?H?TA^A@A??@A@A@?@A@EJ?BAB?B?B?B?@?B@J@@?B@B?@@B@@@B@@@@@@@@@?@@@@@?@?@?@?@?@?@??A@?@?@ADFBFBF?BL`@BBBB\\\\P'},\n",
" 'start_location': {'lat': 48.85983239999999, 'lng': 2.3887503},\n",
" 'travel_mode': 'BICYCLING'},\n",
" {'distance': {'text': '0.5 km', 'value': 488},\n",
" 'duration': {'text': '2 mins', 'value': 96},\n",
" 'end_location': {'lat': 48.853281, 'lng': 2.3773315},\n",
" 'html_instructions': 'Continue onto <b>Av. Ledru Rollin</b>',\n",
" 'polyline': {'points': 'wmeiHuwoMh@XPFRJB@B?DAn@XHDl@Z`ChAHDFDxBdA`@R`@Rd@V`DxAz@^ZJ'},\n",
" 'start_location': {'lat': 48.8573962, 'lng': 2.379628},\n",
" 'travel_mode': 'BICYCLING'},\n",
" {'distance': {'text': '0.2 km', 'value': 205},\n",
" 'duration': {'text': '1 min', 'value': 35},\n",
" 'end_location': {'lat': 48.8530147, 'lng': 2.374655},\n",
" 'html_instructions': 'Turn <b>right</b> onto <b>Rue de Charonne</b>',\n",
" 'maneuver': 'turn-right',\n",
" 'polyline': {'points': '_tdiHiioM?~@@~@Av@ALAhAAn@?T?L?H@N@H@NJd@BLFNDNFPHNDH'},\n",
" 'start_location': {'lat': 48.853281, 'lng': 2.3773315},\n",
" 'travel_mode': 'BICYCLING'},\n",
" {'distance': {'text': '11 m', 'value': 11},\n",
" 'duration': {'text': '1 min', 'value': 2},\n",
" 'end_location': {'lat': 48.8530828, 'lng': 2.3745434},\n",
" 'html_instructions': 'Turn <b>right</b> onto <b>Cr du Panier Fleuri</b><div style=\"font-size:0.9em\">Destination will be on the right</div>',\n",
" 'maneuver': 'turn-right',\n",
" 'polyline': {'points': 'irdiHqxnMMT'},\n",
" 'start_location': {'lat': 48.8530147, 'lng': 2.374655},\n",
" 'travel_mode': 'BICYCLING'}],\n",
" 'traffic_speed_entry': [],\n",
" 'via_waypoint': []}],\n",
" 'overview_polyline': {'points': '_yfiHm}pMfFiGr@u@RIjJsD`@MTGlDsAr@dEf@fDn@fEbEfVZnBBZRxADf@?^C`@A@EFGVDb@LRLBDAB?FDTt@d@XrAn@HAx@^`EpBbFdC|ExBZJ?~@?vBCvAA|ABXLt@X~@NXMT'},\n",
" 'summary': 'Bd de Ménilmontant and Rue de la Roquette',\n",
" 'warnings': [\"Bicycling directions are in beta. Use caution – This route may contain streets that aren't suited for bicycling.\"],\n",
" 'waypoint_order': []}]"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"client.directions(\n",
" destination=restaurants[0].address.location.lat_lng,\n",
" origin=restaurants[0].orders[100].delivery_address.location.lat_lng,\n",
" mode=\"bicycling\", # Choose an appropriate travelling mode\n",
")"
]
},
{
"cell_type": "markdown",
"id": "e22404f4",
"metadata": {},
"source": [
"### Abstraction as `Path` Objects"
]
},
{
"cell_type": "markdown",
"id": "fb6a035d",
"metadata": {},
"source": [
"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/main/src/urban_meal_delivery/db/addresses_addresses.py) in the code).\n",
"\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."
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "15b16871",
"metadata": {},
"outputs": [],
"source": [
"paths = db.Path.from_addresses(\n",
" restaurants[0].address, restaurants[0].orders[100].delivery_address\n",
")\n",
"\n",
"path = paths[0]"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "9554fd8e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<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."
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "1745b42e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[<Location: 31U 454118 5411312>,\n",
" <Location: 31U 454126 5411304>,\n",
" <Location: 31U 454063 5411179>,\n",
" <Location: 31U 454238 5411110>,\n",
" <Location: 31U 454501 5411786>,\n",
" <Location: 31U 455184 5412061>,\n",
" <Location: 31U 454979 5412537>,\n",
" <Location: 31U 454966 5412524>,\n",
" <Location: 31U 454944 5412554>]"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"path.waypoints"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}