{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Restaurant Quest using TomTom API\n", "\n", "## Where to Open a Chinese Restaurant in Amsterdam?\n", "\n", "#### About this document\n", "* **Scope**: This is the partial story that focuses on using TomTom API to examine the surroundings of a neighborhood.
[Click here line to get the **full story**](https://nbviewer.jupyter.org/github/xding78/Sharing/blob/master/RestaurantQuest/RestaurantQuest_TomTomAPI_Full.ipynb).\n", "* **Version**: 1.0 | Updated on: 14 Jan 2020\n", "* **Author**: [Xinrong Ding](https://www.linkedin.com/in/xding/)\n", "\n", "### Table of contents\n", " \n", "[1. Background](#1)
\n", "[2. Data requirements](#2)
\n", "[3. View candidate neighborhoods on a map](#3)
\n", "[4. Explore the surroundings](#4)
\n", "[5. In-depth Analysis of One Neighborhood](#5)
\n", "[6. Conclusion and future work](#6)\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "

\n", "\n", "# 1. Background\n", "\n", "This is the virtual challenge I choose to complete an online Data Science course. And I have so much fun tackling the challenge using TomTom Maps API, I would like to share the project with others.\n", "\n", "## 1.1 The challenge\n", "\n", "Linda has a dream to open a Chinese restaurant to share the joy of great food with others. Now, everything is ready, she chooses Amsterdam to be the place where her dream takes off. Not only because Amsterdam is one of the most populous and visited cities in Europe but also because the diverse culture the city embraces.\n", "\n", "\n", "## 1.2. The approach\n", "\n", "The big question is, where to open a Chinese restaurant in Amsterdam. I need to collect data from at least two sources to narrow down the selection:\n", "\n", "1. Demographic data (for instance: population density per area in Amsterdam)\n", "2. Data of the surroundings (for instance: density of similar restaurants nearby)\n", "\n", "Since you are here to read about fantastic things you can do with TomTom Maps API, I will be much more focused on the second part: Data of the surroundings.\n", "If you are interested in the complete story, please read: the **[full Story](https://nbviewer.jupyter.org/github/xding78/Sharing/blob/master/RestaurantQuest/RestaurantQuest_TomTomAPI_Full.ipynb)**\n", "\n", "\n", "## 1.3. Business questions\n", "\n", "To find the ideal location for the restaurant, I must first seek answers to a few questions.\n", "\n", "### Question 1: How many restaurants already exist?\n", "If this new restaurant would be the only one in a neighborhood, there will be more profit for Linda. So, the number of existing restaurants in the neighborhood must be taken into consideration. Again, this question can be answered, hopefully, by using TomTom API.\n", "\n", "### Question 2: How popular will Chinese food be in the neighborhood?\n", "For Linda, it’s important to serve traditional Chinese food the way she knows. Even though Chinese food is widely loved, it makes sense to double check how existing Chinese restaurants (or Asian restaurants) are perceived. This question can be answered, hopefully, by using TomTom API.\n", "\n", "### Question 3: Who are the target customers and where do they live?\n", "It is going to be a small restaurant (5 to 7 tables) due to the limited investment. The primary income would be takeout and orders made online. From past experience, Linda knows that people who live alone are more likely to buy takeout or use online food ordering apps such as Uber Eats. They are the ideal target customers for her new restaurant. So, we will look for an area with a relatively high density of one-person household. We need demographic information to answer this question.\n", "\n", "\n", "
\n", "\n", "

\n", "\n", "\n", "# 2. Data requirements\n", "**↑** [Back to top](#top)\n", "\n", "We have the big question, where to open a Chinese restaurant in Amsterdam. Now, we need to collect data that can help us answer the questions. We need to collect data from at least two sources:\n", "\n", "1. Data of the surroundings (density of similar restaurants nearby)\n", "2. Demographic data (per area in Amsterdam)\n", "\n", "## 2.1. Data of surroundings\n", "\n", "### TomTom Search API:\n", "To find all points of interest (POI) per category around a certain location, I will utilize TomTom Search:\n", "\n", "* [**Search API documentations**](https://developer.tomtom.com/search-api/search-api-documentation)\n", "* [**Search API Explorer**](https://developer.tomtom.com/content/search-api-explorer#/Search/get_search__versionNumber__poiSearch__query___ext_)\n", "\n", "\n", "## 2.2 Demographic data\n", "\n", "### Demographic features that are crucial to learn for this project:\n", "\n", "* **Total Households**: Number of households in a neighborhood.\n", " * High number of total households guarantees a solid base of potential customers.\n", "* **Population density**: A more densely populated area means more customers for a restaurant. The unit of population density is **number of people per square kilometer**.\n", " * On top of total households, this feature tells us how many households there are within a given area. Since people living nearby are more likely target customers, the more densely populated neighborhood is a more ideal choice.\n", "* **One-person Households**: Number of the households that with only one person. \n", " * One-person households are perfect target customers, as these individuals are more likely to order takeout and avoid cooking alone.\n", " \n", "Demographic analysis is definitely essential to start narrowing down neighborhoods in Amsterdam. However, it is not the focus of this article. If you are interested in the full story (including demographic data analysis), please read the **[full story](https://nbviewer.jupyter.org/github/xding78/Sharing/blob/master/RestaurantQuest/RestaurantQuest_TomTomAPI_Full.ipynb)**.\n", " \n", "### Conclude demographic analysis\n", "\n", "From studying and analyzing demographic data, I chose 10 out of 65 neighborhoods in Amsterdam. Now, I want to display all of these neighborhoods on a map, to give Linda some visuals so she can make her decision more easily!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "

\n", "\n", "\n", "# 3. View candidate neighborhoods on a map\n", "**↑** [Back to top](#top)\n", "\n", "I will use these tools to visualize information on a map:\n", "* [TomTom Maps API](https://developer.tomtom.com/products/maps-api?gclid=Cj0KCQiA2ITuBRDkARIsAMK9Q7Mra8DrrcLAR20WH9DDHfOud1XSVeaJzWeyLEG7ebjjaL2LUGoGHhkaAn2rEALw_wcB): map data\n", "* [folium](https://python-visualization.github.io/folium/): map rendering library\n", "\n", "\n", "## 3.1 Load the information of the 10 remaining neighborhoods into a dataframe\n", "The CSV file that is loaded below is cleaned up based on analyzing demographic data. In the **[full story](https://nbviewer.jupyter.org/github/xding78/Sharing/blob/master/RestaurantQuest/RestaurantQuest_TomTomAPI_Full.ipynb)**, I explained how to process the data from a larger dataset.\n", "\n", "### Load necessary libraries" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# library to handle data in a vectorized manner\n", "import numpy as np \n", "# library to load dataframe\n", "import pandas as pd\n", "\n", "# Matplotlib and associated plotting modules\n", "import matplotlib.colors as colors\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Load CSV files" ] }, { "cell_type": "code", "execution_count": 2, "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", " \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", "
NeighborhoodTotal ResidencesTotal HouseholdsOne-person HouseholdsPopulation DensityPercentage of One-person HouseholdsLatLon
0Jordaan194351298586252328966.4252.3745004.879491
1Van Lennepbuurt6990453530052800566.2652.3651444.867845
2Nieuwmarkt9765648542851374166.0852.3721604.900096
3Oude Pijp14820987565102335365.9252.3552164.894574
4Nieuwe Pijp12325790550152399863.4452.3518564.897728
5Weesperzijde5535347021801498462.8252.3579004.906300
6Grachtengordel-West6385411025701426162.5352.3708374.885478
7Kinkerbuurt6590395024602613562.2852.3691674.866649
8Helmersbuurt7410458028352212461.9052.3633604.871285
9Frederik Hendrikbuurt8435516031652352061.3452.3769564.874085
\n", "
" ], "text/plain": [ " Neighborhood Total Residences Total Households \\\n", "0 Jordaan 19435 12985 \n", "1 Van Lennepbuurt 6990 4535 \n", "2 Nieuwmarkt 9765 6485 \n", "3 Oude Pijp 14820 9875 \n", "4 Nieuwe Pijp 12325 7905 \n", "5 Weesperzijde 5535 3470 \n", "6 Grachtengordel-West 6385 4110 \n", "7 Kinkerbuurt 6590 3950 \n", "8 Helmersbuurt 7410 4580 \n", "9 Frederik Hendrikbuurt 8435 5160 \n", "\n", " One-person Households Population Density \\\n", "0 8625 23289 \n", "1 3005 28005 \n", "2 4285 13741 \n", "3 6510 23353 \n", "4 5015 23998 \n", "5 2180 14984 \n", "6 2570 14261 \n", "7 2460 26135 \n", "8 2835 22124 \n", "9 3165 23520 \n", "\n", " Percentage of One-person Households Lat Lon \n", "0 66.42 52.374500 4.879491 \n", "1 66.26 52.365144 4.867845 \n", "2 66.08 52.372160 4.900096 \n", "3 65.92 52.355216 4.894574 \n", "4 63.44 52.351856 4.897728 \n", "5 62.82 52.357900 4.906300 \n", "6 62.53 52.370837 4.885478 \n", "7 62.28 52.369167 4.866649 \n", "8 61.90 52.363360 4.871285 \n", "9 61.34 52.376956 4.874085 " ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.read_csv('https://github.com/xding78/Sharing/raw/master/RestaurantQuest/Amsterdam_top10.csv')\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Install folium" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "#-----install folium map----\n", "#pip install folium==0.9.1 #comment it out if folium is already installed\n", "\n", "#----Alterantively, install folium with the following code-------\n", "#!conda install -c conda-forge folium=0.5.0 --yes #install folium" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Import folium" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import folium # map rendering library" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.2 Use TomTom Search API\n", "**↑** [Back to top](#top)\n", "\n", "### Get an API key\n", "\n", "Click the \"Get Your Key\" button in [this page](https://developer.tomtom.com/content/search-api-explorer) to get an API key.\n", "\n", "### Load the TomTom API\n", "\n", "TomTom API offers multiple APIs, including the Search API. There is no need to load each API separately." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import requests\n", "tomtom_api_keys = [\"qTI9oA80m7X6TeWf4qKDjA2UvCy6p5mA\"] # max 2500 calls/day\n", "api_key = tomtom_api_keys[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Establishing the map\n", "\n", "First, I want to define a function using [Geocoding feature in Search API](https://developer.tomtom.com/content/search-api-explorer#/Geocoding/get_search__versionNumber__geocode__query___ext_) to get lat/lon of the center of a city. In this case, I retrieve the center of Amsterdam so that the map is properly aligned in the view." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "# Search for city: \n", "def SearchCity(api_key,City,Country):\n", " \n", " url = 'https://api.tomtom.com/search/2/search/'\n", " url += City + ', ' + Country\n", " url += '.json?limit=1&idxSet=Geo&key=' + api_key\n", " \n", " result = requests.get(url).json()\n", " \n", " GeoID = result['results'][0]['dataSources']['geometry']['id']\n", " position = result['results'][0]['position']\n", " \n", " return GeoID,position" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "Amsterdam_position = SearchCity(api_key, \"Amsterdam\", \"Netherlands\")" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "52.37317 4.89066\n" ] } ], "source": [ "lat_amsterdam = Amsterdam_position[1]['lat']\n", "lon_amsterdam = Amsterdam_position[1]['lon']\n", "print(lat_amsterdam, lon_amsterdam)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### First Impression of the Candidate Neighborhoods\n", "\n", "Now, let’s instantiate the visual component, the TomTom map itself, so I can begin displaying neighborhoods." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "#Define a function to initialize any map using TomTom map.\n", "def init_map(api_key=api_key, latitude=0, longitude=0, zoom=14, layer = \"basic\", style = \"main\"):\n", " \"\"\"\n", " The initialise_map function initializes a clean TomTom map\n", " \"\"\"\n", " \n", " maps_url = \"http://{s}.api.tomtom.com/map/1/tile/\"+layer+\"/\"+style+\"/{z}/{x}/{y}.png?tileSize=512&key=\"\n", " TomTom_map = folium.Map(\n", " location = [latitude, longitude], # on what coordinates [lat, lon] to initialise our map\n", " zoom_start = zoom, # with what zoom level to initialize the map, from 0 to 22\n", " tiles = str(maps_url + api_key),\n", " attr = 'TomTom')\n", " \n", " return TomTom_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.3 Visualize one feature on the map\n", "**↑** [Back to top](#top)\n", "\n", "Let's start from visualizing the number of one-person households on the map to get an impression of the 10 candidate neighborhoods." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Visualize one feature (number of one-person households) to get an impression of the 10 candidate neighborhoods.\n", "TomTom_map = init_map(latitude=lat_amsterdam, longitude=lon_amsterdam, zoom=13, layer = \"basic\")\n", "\n", "# add markers to map\n", "for lat, lon, neighborhood, oph in zip(df['Lat'], df['Lon'], df['Neighborhood'], df['One-person Households']):\n", " label = '{}'.format(neighborhood)\n", " label = folium.Popup(label, parse_html=True)\n", " folium.Circle(\n", " [lat, lon],\n", " radius=oph/25,\n", " popup=label, \n", " color='#FF7F0F', # Orange\n", " fill=True,\n", " fill_color='#FF7F0F',\n", " fill_opacity=0.3).add_to(TomTom_map)\n", "TomTom_map.save('01_demographic.html')\n", "TomTom_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3.4 Visualize more features on the map\n", "**↑** [Back to top](#top)\n", "\n", "The above map gives us an impression of how many one-person households actually exist in each neighborhood.\n", "\n", "Now, let's add two more features to the map, so there are three features in total:\n", "\n", "1. **Orange circles** represent the number of one-person households.\n", "2. **Blue circles** represent the number of households in total.\n", "3. **Green circles** represent the population density.\n", "\n", "Important notes about these circles:\n", "\n", "* The center of the orange, green, and blue circles is the center of the neighborhood. Click the center of the circles to see the name of the neighborhood.\n", "* The radius of each circle represents the number of each feature.\n", "\n", "In order to show a more zoomed in map view, I re-adjust the center of the map.\n", "\n", "### Re-adjust the center of the map using an address\n", "\n", "Based on the previous map visualization, I can see a better center for further analysis is the address: Prinsengracht 745A Amsterdam." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "scrolled": true }, "outputs": [], "source": [ "url = \"https://api.tomtom.com/search/2/geocode/Prinsengracht 745A Amsterdam.json?countrySet=NL&key=\" + api_key\n", "result = requests.get(url).json()" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "52.36425 4.88628\n" ] } ], "source": [ "lat_center = result['results'][0]['position']['lat']\n", "lon_center = result['results'][0]['position']['lon']\n", "print(lat_center, lon_center)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Draw the Map with One-person Households, Total Households, and Population Density." ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "TomTom_map = init_map(latitude=lat_center, longitude=lon_center, zoom=14, layer = \"basic\")\n", "\n", "# add markers that represent one-person households to the map\n", "for lat, lon, neighborhood, oph in zip(df['Lat'], df['Lon'], df['Neighborhood'], df['One-person Households']):\n", " label = '{}'.format(neighborhood)\n", " label = folium.Popup(label, parse_html=True)\n", " folium.Circle(\n", " [lat, lon],\n", " radius=oph/25,\n", " popup=label,\n", " color='#FF7F0F', # Orange\n", " fill=True,\n", " fill_color='#FF7F0F', \n", " fill_opacity=0.3\n", " ).add_to(TomTom_map)\n", "\n", "# add markers that represent total households to the map\n", "for lat, lon, neighborhood, households in zip(df['Lat'], df['Lon'], df['Neighborhood'], df['Total Households']):\n", " label = '{}'.format(neighborhood)\n", " label = folium.Popup(label, parse_html=True)\n", " folium.Circle(\n", " [lat, lon],\n", " radius=households/25,\n", " popup=label,\n", " color='#1E77B4', # Blue\n", " fill=False\n", " ).add_to(TomTom_map)\n", " \n", "# add markers that represent population density to the map\n", "for lat, lon, neighborhood, density in zip(df['Lat'], df['Lon'], df['Neighborhood'], df['Population Density']):\n", " label = '{}'.format(neighborhood)\n", " label = folium.Popup(label, parse_html=True)\n", " folium.Circle(\n", " [lat, lon],\n", " radius=density/100,\n", " popup=label,\n", " color='#2A9E2A', # Green\n", " fill=False\n", " ).add_to(TomTom_map)\n", "TomTom_map.save('02_demographic.html')\n", "TomTom_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Learnings from the above data visualization\n", "\n", "As you can see, when choosing an ideal location to open the Chinese restaurant:\n", "\n", "* **The bigger the green circles the better.**\n", "* **The less difference between the size of the blue circles and the orange circles the better.**\n", "\n", "## 3.5 Conclusion and Next Step\n", "**↑** [Back to top](#top)\n", "\n", "Based the above analysis, I have chosen **10** out of **65** neighborhoods in Amsterdam city proper as the candidate neighborhoods for us to investigate further.\n", "\n", "**The next step**, covered in the next chapter, will be to further analyze the **10** neighborhoods by looking into **the density of Chinese restaurants** in each. This will help me narrow down Linda’s choices for the best exact location for her new restaurant.\n", "\n", "
\n", "\n", "

\n", "\n", "# 4. Explore the surroundings\n", "**↑** [Back to top](#top)\n", "\n", "(Add description of TomTom Search API)\n", "\n", "Now, I know where the remaining 10 neighborhoods locate and their geo-relationship. It's time to explore the surroundings. In the scope of the project, I will focus on only question to demonstrate the methodology:\n", "\n", " * How many Chinese restaurants are already available in each neighborhood?\n", "\n", "## 4.1. How many Chinese restaurants are already available in each neighborhood?\n", "\n", "Use the [Search API explorer](https://developer.tomtom.com/content/search-api-explorer#/Search/get_search__versionNumber__categorySearch__query___ext_) to get the url. I choose to store all search results in a JSON file.\n", "\n", "Some key variables:\n", "\n", "* Search radius: **radius**\n", "* Maximum number of search results: **limit**" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "search_radius = 3000\n", "search_limit = 2000" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "scrolled": true }, "outputs": [], "source": [ "url = ('https://api.tomtom.com/search/2/categorySearch/Chinese restaurant.json?countrySet=NL'\n", " +'&lat=52.364250&lon=4.886280&limit=2000&radius=3000&key=' + api_key)\n", "result = requests.get(url).json()\n", "#result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### One of the search results in the JSON file." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/plain": [ "({'type': 'POI',\n", " 'id': 'NL/POI/p0/109857',\n", " 'score': 5.14904,\n", " 'dist': 150.32529954911772,\n", " 'info': 'search:ta:528009005857203-NL',\n", " 'poi': {'name': 'Taste Of Culture',\n", " 'phone': '+(31)-(20)-4271136',\n", " 'categorySet': [{'id': 7315012}],\n", " 'url': 'www.tasteofculture.net',\n", " 'categories': ['chinese', 'restaurant'],\n", " 'classifications': [{'code': 'RESTAURANT',\n", " 'names': [{'nameLocale': 'en-US', 'name': 'chinese'},\n", " {'nameLocale': 'en-US', 'name': 'restaurant'}]}]},\n", " 'address': {'streetNumber': '139HS',\n", " 'streetName': 'Korte Leidsedwarsstraat',\n", " 'municipalitySubdivision': 'Amsterdam',\n", " 'municipality': 'Amsterdam',\n", " 'countrySubdivision': 'North Holland',\n", " 'postalCode': '1017',\n", " 'extendedPostalCode': '1017PZ',\n", " 'countryCode': 'NL',\n", " 'country': 'Netherlands',\n", " 'countryCodeISO3': 'NLD',\n", " 'freeformAddress': 'Korte Leidsedwarsstraat 139HS, 1017PZ, Amsterdam',\n", " 'localName': 'Amsterdam'},\n", " 'position': {'lat': 52.36311, 'lon': 4.88509},\n", " 'viewport': {'topLeftPoint': {'lat': 52.36401, 'lon': 4.88362},\n", " 'btmRightPoint': {'lat': 52.36221, 'lon': 4.88656}},\n", " 'entryPoints': [{'type': 'main',\n", " 'position': {'lat': 52.36305, 'lon': 4.885}}]},)" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{'type': 'POI',\n", " 'id': 'NL/POI/p0/109857',\n", " 'score': 5.14904,\n", " 'dist': 150.32529954911772,\n", " 'info': 'search:ta:528009005857203-NL',\n", " 'poi': {'name': 'Taste Of Culture',\n", " 'phone': '+(31)-(20)-4271136',\n", " 'categorySet': [{'id': 7315012}],\n", " 'url': 'www.tasteofculture.net',\n", " 'categories': ['chinese', 'restaurant'],\n", " 'classifications': [{'code': 'RESTAURANT',\n", " 'names': [{'nameLocale': 'en-US', 'name': 'chinese'},\n", " {'nameLocale': 'en-US', 'name': 'restaurant'}]}]},\n", " 'address': {'streetNumber': '139HS',\n", " 'streetName': 'Korte Leidsedwarsstraat',\n", " 'municipalitySubdivision': 'Amsterdam',\n", " 'municipality': 'Amsterdam',\n", " 'countrySubdivision': 'North Holland',\n", " 'postalCode': '1017',\n", " 'extendedPostalCode': '1017PZ',\n", " 'countryCode': 'NL',\n", " 'country': 'Netherlands',\n", " 'countryCodeISO3': 'NLD',\n", " 'freeformAddress': 'Korte Leidsedwarsstraat 139HS, 1017PZ, Amsterdam',\n", " 'localName': 'Amsterdam'},\n", " 'position': {'lat': 52.36311, 'lon': 4.88509},\n", " 'viewport': {'topLeftPoint': {'lat': 52.36401, 'lon': 4.88362},\n", " 'btmRightPoint': {'lat': 52.36221, 'lon': 4.88656}},\n", " 'entryPoints': [{'type': 'main',\n", " 'position': {'lat': 52.36305, 'lon': 4.885}}]}," ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I can learn from the above JSON file that the following information is essential to show Chinese Restaurants on the map:\n", "\n", "* Get lat lon from:**'position': {'lat': 52.36311, 'lon': 4.88509},**\n", "* Get name from: **'poi': {'name': 'Taste Of Culture',**\n", "\n", "### Now, let's show these restaurants.\n", "\n", "\n", "## 4.2. Show Chinese restaurants on the map\n", "**↑** [Back to top](#top)\n", "\n", "Use the position and name information extracted from the JSON file to show POIs on the map." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# add a grey circle to represent the search radius\n", "folium.Circle(\n", " [lat_center, lon_center],\n", " radius=search_radius,\n", " color='#004B7F', # Navy\n", " opacity=0.3,\n", " fill = False\n", ").add_to(TomTom_map)\n", "\n", "# Add POIs one by one to the map\n", "for poi in result['results']:\n", " folium.Marker(location=tuple(poi['position'].values()),\n", " popup=str(poi['poi']['name']), \n", " icon=folium.Icon(color='blue', icon='glyphicon-star')\n", " #icon=icon\n", " ).add_to(TomTom_map)\n", "TomTom_map.save('03_ChineseRestaurants.html')\n", "TomTom_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Legends of the above map\n", "\n", "1. **Blue markers**: Chinese restaurants.\n", "2. **Orange circles**: the number of one-person households.\n", "3. **Blue circles**: the number of households in total.\n", "4. **Green circles**: the population density.\n", "5. **Grey circle**: the search radius.\n", "\n", "## 4.3. Cluster the POIs\n", "**↑** [Back to top](#top)\n", "\n", "What I would really like to do is have a more obvious visual as to the number of Chinese restaurants in the area. Clustering the POIs (Point of Interests) might help this." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "from folium.plugins import MarkerCluster" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#------IMPORTANT: Reinitiate the TomTom_map, so that the POI pins won't remain in the map--------\n", "TomTom_map = init_map(latitude=lat_center, longitude=lon_center, zoom=14, layer = \"basic\")\n", "\n", "# add markers that represent one-person households to the map\n", "for lat, lon, neighborhood, oph in zip(df['Lat'], df['Lon'], df['Neighborhood'], df['One-person Households']):\n", " label = '{}'.format(neighborhood)\n", " label = folium.Popup(label, parse_html=True)\n", " folium.Circle(\n", " [lat, lon],\n", " radius=oph/25,\n", " popup=label,\n", " color='#FF7F0F', # Orange\n", " fill=True,\n", " fill_color='#FF7F0F', \n", " fill_opacity=0.3\n", " ).add_to(TomTom_map)\n", "\n", "# add markers that represent total households to the map\n", "for lat, lon, neighborhood, households in zip(df['Lat'], df['Lon'], df['Neighborhood'], df['Total Households']):\n", " label = '{}'.format(neighborhood)\n", " label = folium.Popup(label, parse_html=True)\n", " folium.Circle(\n", " [lat, lon],\n", " radius=households/25,\n", " popup=label,\n", " color='#1E77B4', # Blue\n", " fill=False\n", " ).add_to(TomTom_map)\n", " \n", "# add markers that represent population density to the map\n", "for lat, lon, neighborhood, density in zip(df['Lat'], df['Lon'], df['Neighborhood'], df['Population Density']):\n", " label = '{}'.format(neighborhood)\n", " label = folium.Popup(label, parse_html=True)\n", " folium.Circle(\n", " [lat, lon],\n", " radius=density/100,\n", " popup=label,\n", " color='#2A9E2A', # Green\n", " fill=False\n", " ).add_to(TomTom_map)\n", " \n", "#----------------END of the reinitiation of TomTom_map------------\n", "\n", "\n", "\n", "#--------------Show POIs in Clusters rather than POI pins on the map------------\n", "\n", "# Define the marker cluster\n", "mc = MarkerCluster()\n", "\n", "# add a grey circle to represent the search radius\n", "folium.Circle(\n", " [lat_center, lon_center],\n", " radius=search_radius,\n", " color='#004B7F', # Navy\n", " opacity=0.3,\n", " fill = False\n", ").add_to(TomTom_map)\n", "\n", "# Add POIs one by one to the map\n", "for poi in result['results']:\n", " mc.add_child(\n", " folium.Marker(\n", " location=tuple(poi['position'].values()),\n", " popup=str(poi['poi']['name'])\n", " ))\n", "\n", "TomTom_map.add_child(mc)\n", "TomTom_map.save('04_POI_Clustered.html')\n", "TomTom_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Zoom in and out the map to observe how the clusters react.\n", "\n", "### Key Takeaways\n", "\n", "Now that I have investigated options for Linda through multiple filters and criteria, **I can conclude**:\n", "\n", "* There must be at least one existing Chinese restaurant in or near the neighborhood.
\n", "If there isn’t at least one Chinese restaurant, it might mean that **there is not enough demand**.
\n", "Opening a Chinese restaurant without understanding why there are no any could be a risk for Linda.\n", "* There cannot be more than 10 existing Chinese restaurants in the neighborhood, in order to mitigate competition for her.
\n", "**Linda wants to stand out!!**\n", "\n", "If I apply the criteria, from the above map, I can exclude these neighborhoods:\n", "\n", "* Too many existing Chinese restaurants\n", " * Nieuwmarkt\n", "* No existing Chinese restaurant in or near the neighborhood\n", " * Weesperzijde\n", " * Frederik Hendrikbuurt\n", " * Grachtengordel-West\n", " * Nieuwe Pijp\n", " \n", "### The remaining neighborhoods left for Linda to choose from:\n", "* Jordaan\n", "* Van Lennepbuurt\n", "* Oude Pijp\n", "* Kinkerbuurt\n", "* Helmersbuurt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "

\n", "\n", "# 5. In-depth analysis of one neighborhood\n", "**↑** [Back to top](#top)\n", "\n", "Let's use **_Jordaan_** as an example to show how I look into one particular candidate neighborhood.\n", "\n", "## 5.1. Draw the area of the neighborhood on the map" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "area_name = 'Jordaan'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define a function to get polygon of a given GeoID." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "# get polygon of GeoID: \n", "def getPolygon(api_key,GeoID,zoomLevel):\n", " \n", " url = 'https://api.tomtom.com/search/2/additionalData.json?geometries=' + GeoID\n", " url += '&geometriesZoom=' + str(zoomLevel)\n", " url += '&key=' + api_key\n", " \n", " result = requests.get(url).json() \n", " GeoJson = result['additionalData'][0]['geometryData']\n", " \n", " return GeoJson" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "# Search City:\n", "GeoID, position = SearchCity(api_key, area_name ,'Amsterdam')" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The center of the neighborhood is: ( 52.37329 , 4.87992 )\n" ] } ], "source": [ "lat_area = position['lat']\n", "lon_area = position['lon']\n", "print(\"The center of the neighborhood is: (\", lat_area, \", \", lon_area, \")\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create a polygon and add it to the map" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get Polygon of city:\n", "Polygon = getPolygon(api_key,GeoID,22)\n", "\n", "map_url = 'http://{s}.api.tomtom.com/map/1/tile/basic/main/{z}/{x}/{y}.png?view=Unified&key=' + api_key\n", "\n", "TomTom_map = folium.Map(\n", " location=[lat_area, lon_area],\n", " zoom_start=14,\n", " tiles= map_url,\n", " attr='TomTom')\n", "\n", "# add polygons to a map\n", "folium.GeoJson(\n", " Polygon).add_to(TomTom_map)\n", "\n", "TomTom_map.save('05_Area.html')\n", "TomTom_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5.2. Show Chinese restaurants in the neighborhood\n", "**↑** [Back to top](#top)\n", "\n", "### Search for the Chinese restaurant using the search API\n", "\n", "Set the search radius to 1.2 km to cover the entire neighborhood." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "scrolled": true }, "outputs": [], "source": [ "url = ('https://api.tomtom.com/search/2/categorySearch/Chinese restaurant.json?countrySet=NL'\n", " +'&lat=52.37329&lon=4.87992&limit=2000&radius=1200&key=' + api_key)\n", "result = requests.get(url).json()\n", "#result" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# add a grey circle to represent the search radius\n", "folium.Circle(\n", " [lat_area, lon_area],\n", " radius=1200,\n", " color='#004B7F', # Navy\n", " opacity=0.3,\n", " fill = False\n", ").add_to(TomTom_map)\n", "\n", "# Add POIs one by one to the map\n", "for poi in result['results']:\n", " folium.Marker(location=tuple(poi['position'].values()),\n", " popup=str(poi['poi']['name']), \n", " icon=folium.Icon(color='blue', icon='glyphicon-star')\n", " #icon=icon\n", " ).add_to(TomTom_map)\n", "TomTom_map.save('06_Area_POI.html')\n", "TomTom_map" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Legends of the above map\n", "\n", "1. **Blue markers**: Chinese restaurants.\n", "2. **Blue area**: The shape of the neighborhood\n", "3. **Grey circle**: the search radius.\n", "\n", "### Takeaways from this map\n", "\n", "1.\tAccording to **[TomTom Maps API](https://developer.tomtom.com/products/maps-api?gclid=Cj0KCQiA2ITuBRDkARIsAMK9Q7PaNwgDOLImQcoaRN8mHEJ9bcBuNg_UEH3S5Ybk2tlVj-kQNJILIWAaAkYIEALw_wcB)**, there are more than 20 Chinese Restaurants within the range of 500 meters of the neighborhood Jordaan.\n", "2.\tFrom the map I learn that the west of Jordaan seems to be void of Chinese restaurants. If Linda opens a Chinese restaurant there, she will likely have enough customers.\n", "\n", "\n", "## 5.3. Repeat!\n", "**↑** [Back to top](#top)\n", "\n", "At this point, I would advise Linda to repeat this in-depth examination for each neighborhood she is considering. I could also adjust the details. I consider to include other venues, for example – looking at the number of cafes, snack bars, etc present in an area in addition to regular restaurants.\n", "\n", "
\n", "\n", "

\n", "\n", "# 6. Conclusion and future work\n", "**↑** [Back to top](#top)\n", "\n", "## The limitation of this project\n", "\n", "### Only focus on residential information\n", "\n", "This project is limited by the lack of crucial information. So far I have been focused quite a lot on residence information and one-person households. However, customers can also come from nearby business venues. I am unable to validate any assumption or answer any questions, because the information of business venues in Amsterdam is not as available as demographic information.\n", "\n", "### Rent of a venue is not taken into consideration\n", "\n", "Due to lack of information, I am unable to include rental price as part of the analysis. Cost could be a big factor for Linda. In order to be able to predict the potential profit, however, it is crucial to include potential rental price.\n", "\n", "### Explore more POI categories\n", "\n", "There are other facilities in the neighborhood which may influence the income of the restaurant. For instance:\n", "\n", "* **How easy is it to reach the place via public transportation?**
\n", "Search for nearby bus stops, tram stations, train stations, etc.\n", "* **How easy is it to park your car in the neighborhood?**
\n", "Search for nearby parking garages or open parking places.\n", "\n", "\n", "## Next steps for Linda\n", "\n", "* Continue to perform the same in-depth analysis to all neighborhoods as I did in [In-depth analysis of one neighborhood](#5).\n", "* Include rental price of each neighborhood in future analysis to be informed about her costs to profit ratio.\n", "\n", "
\n", "
" ] } ], "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.3" } }, "nbformat": 4, "nbformat_minor": 2 }