diff --git a/research/08_google_maps.ipynb b/research/08_google_maps.ipynb deleted file mode 100644 index 9c6e0aa..0000000 --- a/research/08_google_maps.ipynb +++ /dev/null @@ -1,922 +0,0 @@ -{ - "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": [ - "" - ] - }, - "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": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" - ], - "text/plain": [ - "" - ] - }, - "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": [ - "" - ] - }, - "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": [ - "" - ] - }, - "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 southeast on Bd de Ménilmontant toward Rue de Tlemcen/Rue Spinoza',\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 right onto Rue de la Roquette
Go through 1 roundabout
',\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 Av. Ledru Rollin',\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 right onto Rue de Charonne',\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 right onto Cr du Panier Fleuri
Destination will be on the right
',\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": [ - "" - ] - }, - "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": [ - "" - ] - }, - "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": [ - "" - ] - }, - "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": [ - "" - ] - }, - "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": [ - "" - ] - }, - "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": [ - "[,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" - ] - }, - "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 -}