{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# A Route to Remember\n", "\n", "I was eagerly pacing up and down the waiting room of the Honda Car Service Center. I was looking forward to meeting Ujaval Gandhi the owner of Spatial Thoughts, the primier learning platform for modern geospatial technologies. In the next hour we were going to meeting for coffee.\n", "But i had a few errands to do before seeing him. \n", "* Fedex a package\n", "* Fill gas at Quiktrip\n", "* Pick up my prescription medicine at Walgreens\n", "* Pick up some tools at Home Depot\n", "* Some grocery at Schnucks\n", "\n", "I needed to comeup with a plan to do all the errands in the shortest possible time so i could reach Starbucks in time. I came up with a plan.\n", "1. Get address of all the locations via Google\n", "2. Geocode the addresses via \"geopy\" python module\n", "3. Plot it on a map to get an idea using \"folium\" module\n", "4. Find shortest route to visit the locations using OpenRouteService.org api\n", "5. Plot the route on folium module" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "import geopy\n", "from geopy.geocoders import Nominatim\n", "from geopy.extra.rate_limiter import RateLimiter\n", "import pandas as pd\n", "import folium\n", "import requests\n", "import json" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "# ORS_API_KEY = ''" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "# places to visit\n", "places = [['honda','15532 Manchester Rd, Ellisville, MO 63011'],\n", " ['fedex','5 Clarkson Rd, Ellisville, MO 63011'], \n", " ['walgreens', '16105 Manchester Rd, Ellisville, MO 63011'],\n", " ['quiktrip','15902 Manchester Rd, Ellisville, MO 63011'],\n", " ['home depot', '37 Towne Dr, Ellisville, MO 63011'],\n", " ['schnucks', '16580 Manchester Rd, Wildwood, MO 63040'],\n", " ['starbucks', '125 Plaza Dr, Wildwood, MO 63040']\n", " ]" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
placeaddress
0honda15532 Manchester Rd, Ellisville, MO 63011
1fedex5 Clarkson Rd, Ellisville, MO 63011
2walgreens16105 Manchester Rd, Ellisville, MO 63011
3quiktrip15902 Manchester Rd, Ellisville, MO 63011
4home depot37 Towne Dr, Ellisville, MO 63011
5schnucks16580 Manchester Rd, Wildwood, MO 63040
6starbucks125 Plaza Dr, Wildwood, MO 63040
\n", "
" ], "text/plain": [ " place address\n", "0 honda 15532 Manchester Rd, Ellisville, MO 63011\n", "1 fedex 5 Clarkson Rd, Ellisville, MO 63011\n", "2 walgreens 16105 Manchester Rd, Ellisville, MO 63011\n", "3 quiktrip 15902 Manchester Rd, Ellisville, MO 63011\n", "4 home depot 37 Towne Dr, Ellisville, MO 63011\n", "5 schnucks 16580 Manchester Rd, Wildwood, MO 63040\n", "6 starbucks 125 Plaza Dr, Wildwood, MO 63040" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create pandas dataframe\n", "df = pd.DataFrame(places, columns = ['place', 'address'])\n", "df" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "# tip: https://towardsdatascience.com/geocode-with-python-161ec1e62b89\n", "# Using Nominatim Geocoding service\n", "locator = Nominatim(user_agent='myGeocoder') " ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "# function to dalay geocoding calls\n", "geocode_fn = RateLimiter(locator.geocode, min_delay_seconds=2)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "# Geocoding\n", "df['location'] = df['address'].apply(geocode_fn)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "# create longitude, laatitude and altitude from location column (returns tuple)\n", "df['point'] = df['location'].apply(lambda loc: tuple(loc.point) if loc else None)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "# split point column into latitude, longitude and altitude columns\n", "df[['latitude', 'longitude', 'altitude']] = pd.DataFrame(df['point'].tolist(), index=df.index)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
placeaddresslocationpointlatitudelongitudealtitude
0honda15532 Manchester Rd, Ellisville, MO 63011(West County Honda, 15532, Manchester Road, El...(38.59175415, -90.5701563705986, 0.0)38.591754-90.5701560.0
1fedex5 Clarkson Rd, Ellisville, MO 63011(5, Clarkson Road, Ellisville, Saint Louis Cou...(38.5933265, -90.5848154, 0.0)38.593327-90.5848150.0
2walgreens16105 Manchester Rd, Ellisville, MO 63011(16105, Manchester Road, Ellisville, Saint Lou...(38.591023, -90.5964036, 0.0)38.591023-90.5964040.0
3quiktrip15902 Manchester Rd, Ellisville, MO 63011(15902, Manchester Road, Ellisville, Saint Lou...(38.5919868, -90.5866775, 0.0)38.591987-90.5866770.0
4home depot37 Towne Dr, Ellisville, MO 63011(Towne Drive, Ellisville, Saint Louis County, ...(38.5979707, -90.5996285, 0.0)38.597971-90.5996280.0
5schnucks16580 Manchester Rd, Wildwood, MO 63040(Schnucks, 16580, Manchester Road, Wildwood, S...(38.580228399999996, -90.61774964813051, 0.0)38.580228-90.6177500.0
6starbucks125 Plaza Dr, Wildwood, MO 63040(125, Plaza Drive, Wildwood, Saint Louis Count...(38.5822086, -90.628069, 0.0)38.582209-90.6280690.0
\n", "
" ], "text/plain": [ " place address \\\n", "0 honda 15532 Manchester Rd, Ellisville, MO 63011 \n", "1 fedex 5 Clarkson Rd, Ellisville, MO 63011 \n", "2 walgreens 16105 Manchester Rd, Ellisville, MO 63011 \n", "3 quiktrip 15902 Manchester Rd, Ellisville, MO 63011 \n", "4 home depot 37 Towne Dr, Ellisville, MO 63011 \n", "5 schnucks 16580 Manchester Rd, Wildwood, MO 63040 \n", "6 starbucks 125 Plaza Dr, Wildwood, MO 63040 \n", "\n", " location \\\n", "0 (West County Honda, 15532, Manchester Road, El... \n", "1 (5, Clarkson Road, Ellisville, Saint Louis Cou... \n", "2 (16105, Manchester Road, Ellisville, Saint Lou... \n", "3 (15902, Manchester Road, Ellisville, Saint Lou... \n", "4 (Towne Drive, Ellisville, Saint Louis County, ... \n", "5 (Schnucks, 16580, Manchester Road, Wildwood, S... \n", "6 (125, Plaza Drive, Wildwood, Saint Louis Count... \n", "\n", " point latitude longitude \\\n", "0 (38.59175415, -90.5701563705986, 0.0) 38.591754 -90.570156 \n", "1 (38.5933265, -90.5848154, 0.0) 38.593327 -90.584815 \n", "2 (38.591023, -90.5964036, 0.0) 38.591023 -90.596404 \n", "3 (38.5919868, -90.5866775, 0.0) 38.591987 -90.586677 \n", "4 (38.5979707, -90.5996285, 0.0) 38.597971 -90.599628 \n", "5 (38.580228399999996, -90.61774964813051, 0.0) 38.580228 -90.617750 \n", "6 (38.5822086, -90.628069, 0.0) 38.582209 -90.628069 \n", "\n", " altitude \n", "0 0.0 \n", "1 0.0 \n", "2 0.0 \n", "3 0.0 \n", "4 0.0 \n", "5 0.0 \n", "6 0.0 " ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ "" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# tip: https://openrouteservice.org/example-optimize-pub-crawl-with-ors/\n", "# Mapping using folium\n", "my_map = folium.Map(\n", " location=[38.5933265, -90.5848154],\n", " tiles='Stamen Toner',\n", " zoom_start=13)\n", "df.apply(lambda row:folium.Marker(location=[row['latitude'], row['longitude']]).add_to(my_map), axis=1)\n", "\n", "# display map\n", "my_map" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "# Data for finding shortest route\n", "start_location = [df.iloc[0]['longitude'], df.iloc[0]['latitude']]\n", "end_location = [df.iloc[6]['longitude'], df.iloc[0]['latitude']]\n", "vehicle = [{ \"id\":1,\n", " \"profile\":\"driving-car\",\n", " \"start\":start_location,\n", " \"end\":end_location,\n", " \"capacity\":[6] \n", "}]\n", "# stops\n", "stops = []\n", "for i in range(5):\n", " stop = {}\n", " stop['id'] = i + 1\n", " stop['service'] = 300\n", " stop['delivery'] = [1]\n", " stop['location'] = [df.iloc[i + 1]['longitude'], df.iloc[i + 1]['latitude']]\n", " stops.append(stop) " ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "200 OK\n", "{\"code\":0,\"summary\":{\"cost\":993,\"unassigned\":0,\"delivery\":[5],\"amount\":[5],\"pickup\":[0],\"service\":1500,\"duration\":993,\"waiting_time\":0,\"computing_times\":{\"loading\":76,\"solving\":2}},\"unassigned\":[],\"routes\":[{\"vehicle\":1,\"cost\":993,\"delivery\":[5],\"amount\":[5],\"pickup\":[0],\"service\":1500,\"duration\":993,\"waiting_time\":0,\"steps\":[{\"type\":\"start\",\"location\":[-90.5701563705986,38.59175415],\"load\":[5],\"arrival\":0,\"duration\":0},{\"type\":\"job\",\"location\":[-90.5848154,38.5933265],\"id\":1,\"service\":300,\"waiting_time\":0,\"job\":1,\"load\":[4],\"arrival\":135,\"duration\":135},{\"type\":\"job\",\"location\":[-90.5866775,38.5919868],\"id\":3,\"service\":300,\"waiting_time\":0,\"job\":3,\"load\":[3],\"arrival\":495,\"duration\":195},{\"type\":\"job\",\"location\":[-90.5996285,38.5979707],\"id\":4,\"service\":300,\"waiting_time\":0,\"job\":4,\"load\":[2],\"arrival\":992,\"duration\":392},{\"type\":\"job\",\"location\":[-90.5964036,38.591023],\"id\":2,\"service\":300,\"waiting_time\":0,\"job\":2,\"load\":[1],\"arrival\":1414,\"duration\":514},{\"type\":\"job\",\"location\":[-90.61774964813053,38.5802284],\"id\":5,\"service\":300,\"waiting_time\":0,\"job\":5,\"load\":[0],\"arrival\":1931,\"duration\":731},{\"type\":\"end\",\"location\":[-90.628069,38.59175415],\"load\":[0],\"arrival\":2493,\"duration\":993}]}]}\n", "\n" ] } ], "source": [ "# api call to openrouteservice\n", "\n", "body = {'jobs':stops, 'vehicles': vehicle}\n", "headers = {\n", " 'Accept': 'application/json, application/geo+json, application/gpx+xml, img/png; charset=utf-8',\n", " 'Authorization': ORS_API_KEY,\n", " 'Content-Type': 'application/json; charset=utf-8'\n", "}\n", "response = requests.post('https://api.openrouteservice.org/optimization', json=body, headers=headers)\n", "\n", "print(response.status_code, response.reason)\n", "print(response.text)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "# Total Drive Time in minutes\n", "data = response.json()\n", "total_drive_time = data['summary']['cost'] / 60" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[(38.59175415, -90.5701563705986),\n", " (38.5933265, -90.5848154),\n", " (38.5919868, -90.5866775),\n", " (38.5979707, -90.5996285),\n", " (38.591023, -90.5964036),\n", " (38.5802284, -90.61774964813053),\n", " (38.59175415, -90.628069)]" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Shortest Route\n", "steps = data['routes'][0]['steps']\n", "route_points = []\n", "for step in steps:\n", " route_step = (step['location'][1], step['location'][0])\n", " route_points.append(route_step)\n", "route_points" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ "" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# tip: https://deparkes.co.uk/2016/06/03/plot-lines-in-folium/\n", "# Mapping using folium\n", "\n", "ave_lat = sum(p[0] for p in route_points)/len(route_points)\n", "ave_lon = sum(p[1] for p in route_points)/len(route_points)\n", " \n", "# Load map centred on average coordinates\n", "route_map = folium.Map(\n", " location=[ave_lat, ave_lon],\n", " tiles='Stamen Toner',\n", " zoom_start=14)\n", " \n", "#add a markers\n", "for each in route_points: \n", " folium.Marker(each).add_to(route_map)\n", " \n", "#fadd lines\n", "folium.PolyLine(route_points, color=\"red\", weight=2.5, opacity=1).add_to(route_map)\n", "\n", "route_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Conclusion\n", "The route plan worked like a charm. I was able to reach starbucks on time. It was really wonderful meeting Ujaval. Have a wonderful discussion regarding leveraging Python for GeoSpatial Analysis." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.7.7" } }, "nbformat": 4, "nbformat_minor": 4 }