urban-meal-delivery/research/08_google_maps.ipynb
Alexander Hess 0f60640bc0
Some checks failed
CI / fast (without R) (push) Has been cancelled
CI / slow (with R) (push) Has been cancelled
Show how the Google Maps API is integrated
- show an example order's path from the restaurant to the customer's
  delivery address as it is travelled by a courier
- explain how to use the Google Maps API directly
- show how the API is integrated into the data model
2021-09-13 11:30:17 +02:00

922 lines
34 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"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%20%20%20%20%20%20%20zoom%3A%2012%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20zoomControl%3A%20true%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20preferCanvas%3A%20false%2C%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%29%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%0A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20tile_layer_77a7f4b44a2f4ddba69990679f4d5d1d%20%3D%20L.tileLayer%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22https%3A//%7Bs%7D.tile.openstreetmap.org/%7Bz%7D/%7Bx%7D/%7By%7D.png%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22attribution%22%3A%20%22Data%20by%20%5Cu0026copy%3B%20%5Cu003ca%20href%3D%5C%22http%3A//openstreetmap.org%5C%22%5Cu003eOpenStreetMap%5Cu003c/a%5Cu003e%2C%20under%20%5Cu003ca%20href%3D%5C%22http%3A//www.openstreetmap.org/copyright%5C%22%5Cu003eODbL%5Cu003c/a%5Cu003e.%22%2C%20%22detectRetina%22%3A%20false%2C%20%22maxNativeZoom%22%3A%2018%2C%20%22maxZoom%22%3A%2018%2C%20%22minZoom%22%3A%200%2C%20%22noWrap%22%3A%20false%2C%20%22opacity%22%3A%201%2C%20%22subdomains%22%3A%20%22abc%22%2C%20%22tms%22%3A%20false%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%29.addTo%28map_f07ee29663cb478595eba61d72228bde%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20poly_line_8f1fc6d060884d5ebae8620d56d30fb8%20%3D%20L.polyline%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B%5B48.85313%2C%202.37461%5D%2C%20%5B48.8530828%2C%202.3745434%5D%2C%20%5B48.8530147%2C%202.374655%5D%2C%20%5B48.8518789%2C%202.3738089%5D%2C%20%5B48.8513033%2C%202.3760858%5D%2C%20%5B48.84962549999999%2C%202.3743054%5D%2C%20%5B48.8532224%2C%202.3702528%5D%2C%20%5B48.85323229999999%2C%202.3696934%5D%2C%20%5B48.8535723%2C%202.369183%5D%2C%20%5B48.8609545%2C%202.3672565%5D%2C%20%5B48.8630801%2C%202.3667255%5D%2C%20%5B48.8653031%2C%202.365423%5D%2C%20%5B48.8638152%2C%202.3636461%5D%2C%20%5B48.863705%2C%202.363815%5D%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22bubblingMouseEvents%22%3A%20true%2C%20%22color%22%3A%20%22black%22%2C%20%22dashArray%22%3A%20null%2C%20%22dashOffset%22%3A%20null%2C%20%22fill%22%3A%20false%2C%20%22fillColor%22%3A%20%22black%22%2C%20%22fillOpacity%22%3A%200.2%2C%20%22fillRule%22%3A%20%22evenodd%22%2C%20%22lineCap%22%3A%20%22round%22%2C%20%22lineJoin%22%3A%20%22round%22%2C%20%22noClip%22%3A%20false%2C%20%22opacity%22%3A%201.0%2C%20%22smoothFactor%22%3A%201.0%2C%20%22stroke%22%3A%20true%2C%20%22weight%22%3A%202%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%29.addTo%28map_f07ee29663cb478595eba61d72228bde%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20circle_b347114a9d6a420fb70b170d9692caea%20%3D%20L.circle%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B48.85313%2C%202.37461%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22bubblingMouseEvents%22%3A%20true%2C%20%22color%22%3A%20%22red%22%2C%20%22dashArray%22%3A%20null%2C%20%22dashOffset%22%3A%20null%2C%20%22fill%22%3A%20true%2C%20%22fillColor%22%3A%20%22red%22%2C%20%22fillOpacity%22%3A%201%2C%20%22fillRule%22%3A%20%22evenodd%22%2C%20%22lineCap%22%3A%20%22round%22%2C%20%22lineJoin%22%3A%20%22round%22%2C%20%22opacity%22%3A%201.0%2C%20%22radius%22%3A%205%2C%20%22stroke%22%3A%20true%2C%20%22weight%22%3A%203%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%29.addTo%28map_f07ee29663cb478595eba61d72228bde%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20var%20popup_7874e58804634dfeacf525a705e56507%20%3D%20L.popup%28%7B%22maxWidth%22%3A%20%22100%25%22%7D%29%3B%0A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20html_77adeb5af6dc4591bf90d9ba63699ca3%20%3D%20%24%28%60%3Cdiv%20id%3D%22html_77adeb5af6dc4591bf90d9ba63699ca3%22%20style%3D%22width%3A%20100.0%25%3B%20height%3A%20100.0%25%3B%22%3E42%20Rue%20De%20Charonne%2C%2075011%20Paris%3C/div%3E%60%29%5B0%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20popup_7874e58804634dfeacf525a705e56507.setContent%28html_77adeb5af6dc4591bf90d9ba63699ca3%29%3B%0A%20%20%20%20%20%20%20%20%0A%0A%20%20%20%20%20%20%20%20circle_b347114a9d6a420fb70b170d9692caea.bindPopup%28popup_7874e58804634dfeacf525a705e56507%29%0A%20%20%20%20%20%20%20%20%3B%0A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20circle_b347114a9d6a420fb70b170d9692caea.bindTooltip%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Starvin%27%20Joe%20%28%23158%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C/div%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22sticky%22%3A%20true%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20circle_e1d7fd62b5f64139b1903f7d234b83a1%20%3D%20L.circle%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B48.863705%2C%202.363815%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22bubblingMouseEvents%22%3A%20true%2C%20%22color%22%3A%20%22blue%22%2C%20%22dashArray%22%3A%20null%2C%20%22dashOffset%22%3A%20null%2C%20%22fill%22%3A%20true%2C%20%22fillColor%22%3A%20%22blue%22%2C%20%22fillOpacity%22%3A%201%2C%20%22fillRule%22%3A%20%22evenodd%22%2C%20%22lineCap%22%3A%20%22round%22%2C%20%22lineJoin%22%3A%20%22round%22%2C%20%22opacity%22%3A%201.0%2C%20%22radius%22%3A%205%2C%20%22stroke%22%3A%20true%2C%20%22weight%22%3A%203%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%29.addTo%28map_f07ee29663cb478595eba61d72228bde%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20var%20popup_5d6991161ce64e3aac1d2deac912f728%20%3D%20L.popup%28%7B%22maxWidth%22%3A%20%22100%25%22%7D%29%3B%0A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20var%20html_b4c4e1112edd4b76ad56912fcb74c9a6%20%3D%20%24%28%60%3Cdiv%20id%3D%22html_b4c4e1112edd4b76ad56912fcb74c9a6%22%20style%3D%22width%3A%20100.0%25%3B%20height%3A%20100.0%25%3B%22%3ERue%20Charlot%2062%2C%2075003%20Paris%3C/div%3E%60%29%5B0%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20popup_5d6991161ce64e3aac1d2deac912f728.setContent%28html_b4c4e1112edd4b76ad56912fcb74c9a6%29%3B%0A%20%20%20%20%20%20%20%20%0A%0A%20%20%20%20%20%20%20%20circle_e1d7fd62b5f64139b1903f7d234b83a1.bindPopup%28popup_5d6991161ce64e3aac1d2deac912f728%29%0A%20%20%20%20%20%20%20%20%3B%0A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20circle_e1d7fd62b5f64139b1903f7d234b83a1.bindTooltip%28%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%60%3Cdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Customer%20%234715%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C/div%3E%60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22sticky%22%3A%20true%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%29%3B%0A%20%20%20%20%20%20%20%20%0A%3C/script%3E onload=\"this.contentDocument.open();this.contentDocument.write( decodeURIComponent(this.getAttribute('data-html')));this.contentDocument.close();\" allowfullscreen webkitallowfullscreen mozallowfullscreen></iframe></div></div>"
],
"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
}