Skip to content

Instantly share code, notes, and snippets.

@connectwithprakash
Created June 26, 2019 12:50
Show Gist options
  • Select an option

  • Save connectwithprakash/5ba75b109bee3284618373d5c8152846 to your computer and use it in GitHub Desktop.

Select an option

Save connectwithprakash/5ba75b109bee3284618373d5c8152846 to your computer and use it in GitHub Desktop.
Created on Cognitive Class Labs
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Collecting package metadata: done\n",
"Solving environment: | \n",
"The environment is inconsistent, please check the package plan carefully\n",
"The following packages are causing the inconsistency:\n",
"\n",
" - defaults/linux-64::anaconda==5.3.1=py37_0\n",
" - defaults/linux-64::astropy==3.0.4=py37h14c3975_0\n",
" - defaults/linux-64::bkcharts==0.2=py37_0\n",
" - defaults/linux-64::blaze==0.11.3=py37_0\n",
" - defaults/linux-64::bokeh==0.13.0=py37_0\n",
" - defaults/linux-64::bottleneck==1.2.1=py37h035aef0_1\n",
" - defaults/linux-64::dask==0.19.1=py37_0\n",
" - defaults/linux-64::datashape==0.5.4=py37_1\n",
" - defaults/linux-64::mkl-service==1.1.2=py37h90e4bf4_5\n",
" - defaults/linux-64::numba==0.39.0=py37h04863e7_0\n",
" - defaults/linux-64::numexpr==2.6.8=py37hd89afb7_0\n",
" - defaults/linux-64::odo==0.5.1=py37_0\n",
" - defaults/linux-64::pytables==3.4.4=py37ha205bf6_0\n",
" - defaults/linux-64::pytest-arraydiff==0.2=py37h39e3cac_0\n",
" - defaults/linux-64::pytest-astropy==0.4.0=py37_0\n",
" - defaults/linux-64::pytest-doctestplus==0.1.3=py37_0\n",
" - defaults/linux-64::pywavelets==1.0.0=py37hdd07704_0\n",
" - defaults/linux-64::scikit-image==0.14.0=py37hf484d3e_1\n",
"done\n",
"\n",
"# All requested packages already installed.\n",
"\n",
"Collecting package metadata: done\n",
"Solving environment: / \n",
"The environment is inconsistent, please check the package plan carefully\n",
"The following packages are causing the inconsistency:\n",
"\n",
" - defaults/linux-64::anaconda==5.3.1=py37_0\n",
" - defaults/linux-64::astropy==3.0.4=py37h14c3975_0\n",
" - defaults/linux-64::bkcharts==0.2=py37_0\n",
" - defaults/linux-64::blaze==0.11.3=py37_0\n",
" - defaults/linux-64::bokeh==0.13.0=py37_0\n",
" - defaults/linux-64::bottleneck==1.2.1=py37h035aef0_1\n",
" - defaults/linux-64::dask==0.19.1=py37_0\n",
" - defaults/linux-64::datashape==0.5.4=py37_1\n",
" - defaults/linux-64::mkl-service==1.1.2=py37h90e4bf4_5\n",
" - defaults/linux-64::numba==0.39.0=py37h04863e7_0\n",
" - defaults/linux-64::numexpr==2.6.8=py37hd89afb7_0\n",
" - defaults/linux-64::odo==0.5.1=py37_0\n",
" - defaults/linux-64::pytables==3.4.4=py37ha205bf6_0\n",
" - defaults/linux-64::pytest-arraydiff==0.2=py37h39e3cac_0\n",
" - defaults/linux-64::pytest-astropy==0.4.0=py37_0\n",
" - defaults/linux-64::pytest-doctestplus==0.1.3=py37_0\n",
" - defaults/linux-64::pywavelets==1.0.0=py37hdd07704_0\n",
" - defaults/linux-64::scikit-image==0.14.0=py37hf484d3e_1\n",
"done\n",
"\n",
"## Package Plan ##\n",
"\n",
" environment location: /home/jupyterlab/conda\n",
"\n",
" added / updated specs:\n",
" - geopy\n",
"\n",
"\n",
"The following packages will be downloaded:\n",
"\n",
" package | build\n",
" ---------------------------|-----------------\n",
" ca-certificates-2019.6.16 | hecc5488_0 145 KB conda-forge\n",
" certifi-2019.6.16 | py36_0 148 KB conda-forge\n",
" conda-4.7.5 | py36_0 3.0 MB conda-forge\n",
" conda-package-handling-1.3.10| py36_0 257 KB conda-forge\n",
" geographiclib-1.49 | py_0 32 KB conda-forge\n",
" geopy-1.20.0 | py_0 57 KB conda-forge\n",
" ------------------------------------------------------------\n",
" Total: 3.6 MB\n",
"\n",
"The following NEW packages will be INSTALLED:\n",
"\n",
" conda-package-han~ conda-forge/linux-64::conda-package-handling-1.3.10-py36_0\n",
" geographiclib conda-forge/noarch::geographiclib-1.49-py_0\n",
"\n",
"The following packages will be UPDATED:\n",
"\n",
" ca-certificates anaconda::ca-certificates-2019.5.15-0 --> conda-forge::ca-certificates-2019.6.16-hecc5488_0\n",
" conda anaconda::conda-4.6.14-py36_0 --> conda-forge::conda-4.7.5-py36_0\n",
" geopy conda-forge/linux-64::geopy-1.11.0-py~ --> conda-forge/noarch::geopy-1.20.0-py_0\n",
"\n",
"The following packages will be SUPERSEDED by a higher-priority channel:\n",
"\n",
" certifi anaconda --> conda-forge\n",
" openssl anaconda::openssl-1.1.1-h7b6447c_0 --> conda-forge::openssl-1.1.1b-h14c3975_1\n",
"\n",
"\n",
"\n",
"Downloading and Extracting Packages\n",
"conda-package-handli | 257 KB | ##################################### | 100% \n",
"conda-4.7.5 | 3.0 MB | ##################################### | 100% \n",
"ca-certificates-2019 | 145 KB | ##################################### | 100% \n",
"geopy-1.20.0 | 57 KB | ##################################### | 100% \n",
"certifi-2019.6.16 | 148 KB | ##################################### | 100% \n",
"geographiclib-1.49 | 32 KB | ##################################### | 100% \n",
"Preparing transaction: done\n",
"Verifying transaction: done\n",
"Executing transaction: done\n",
"WARNING conda.base.context:use_only_tar_bz2(632): Conda is constrained to only using the old .tar.bz2 file format because you have conda-build installed, and it is <3.18.3. Update or remove conda-build to get smaller downloads and faster extractions.\n",
"Collecting package metadata (repodata.json): done\n",
"Solving environment: / \n",
"The environment is inconsistent, please check the package plan carefully\n",
"The following packages are causing the inconsistency:\n",
"\n",
" - defaults/linux-64::anaconda==5.3.1=py37_0\n",
" - defaults/linux-64::astropy==3.0.4=py37h14c3975_0\n",
" - defaults/linux-64::bkcharts==0.2=py37_0\n",
" - defaults/linux-64::blaze==0.11.3=py37_0\n",
" - defaults/linux-64::bokeh==0.13.0=py37_0\n",
" - defaults/linux-64::bottleneck==1.2.1=py37h035aef0_1\n",
" - defaults/linux-64::dask==0.19.1=py37_0\n",
" - defaults/linux-64::datashape==0.5.4=py37_1\n",
" - defaults/linux-64::mkl-service==1.1.2=py37h90e4bf4_5\n",
" - defaults/linux-64::numba==0.39.0=py37h04863e7_0\n",
" - defaults/linux-64::numexpr==2.6.8=py37hd89afb7_0\n",
" - defaults/linux-64::odo==0.5.1=py37_0\n",
" - defaults/linux-64::pytables==3.4.4=py37ha205bf6_0\n",
" - defaults/linux-64::pytest-arraydiff==0.2=py37h39e3cac_0\n",
" - defaults/linux-64::pytest-astropy==0.4.0=py37_0\n",
" - defaults/linux-64::pytest-doctestplus==0.1.3=py37_0\n",
" - defaults/linux-64::pywavelets==1.0.0=py37hdd07704_0\n",
" - defaults/linux-64::scikit-image==0.14.0=py37hf484d3e_1\n",
"failed\n",
"\n",
"UnsatisfiableError: The following specifications were found to be incompatible with each other:\n",
"\n",
" - anaconda/linux-64::anaconda-navigator==1.9.7=py36_0\n",
" - anaconda/linux-64::graphviz==2.40.1=h21bd128_2 -> pango[version='>=1.42.1,<2.0a0']\n",
" - anaconda/linux-64::importlib_metadata==0.8=py36_0\n",
" - anaconda/linux-64::lxml==4.3.0=py36hefd8a0e_0\n",
" - anaconda/linux-64::mkl_fft==1.0.6=py36h7dd41cf_0 -> mkl[version='>=2018.0.3']\n",
" - anaconda/linux-64::mkl_random==1.0.1=py36h4414c95_1 -> mkl[version='>=2018.0.3']\n",
" - anaconda/linux-64::navigator-updater==0.2.1=py36_0\n",
" - anaconda/linux-64::numpy-base==1.15.4=py36h81de0dd_0 -> mkl[version='>=2018.0.3']\n",
" - anaconda/linux-64::numpy==1.15.4=py36h1d66e8a_0 -> mkl[version='>=2018.0.3']\n",
" - anaconda/linux-64::pytorch==0.4.1=py36ha74772b_0 -> mkl[version='>=2018.0.3']\n",
" - anaconda/linux-64::scikit-learn==0.20.1=py36h4989274_0 -> mkl[version='>=2018.0.3']\n",
" - anaconda/linux-64::scipy==1.1.0=py36hfa4b5c9_1 -> mkl[version='>=2018.0.3']\n",
" - anaconda/linux-64::spyder==3.3.4=py36_0\n",
" - anaconda/linux-64::sympy==1.4=py36_0\n",
" - anaconda/linux-64::torchvision==0.2.1=py36_0 -> pytorch[version='>=0.4'] -> mkl[version='>=2019.1,<2020.0a0']\n",
" - anaconda/noarch::openpyxl==2.6.2=py_0\n",
" - anaconda/noarch::path.py==12.0.1=py_0 -> importlib_metadata[version='>=0.5']\n",
" - anaconda/noarch::xlsxwriter==1.1.6=py_0\n",
" - mkl-service -> mkl[version='>=2019.4,<2020.0a0']\n",
" - pkgs/main/linux-64::mkl==2019.0=118\n",
" - pkgs/main/linux-64::pango==1.42.4=h049681c_0\n",
"\n",
"\n",
"\n"
]
}
],
"source": [
"!conda install -c anaconda beautifulsoup4 --yes\n",
"!conda install -c conda-forge geopy --yes\n",
"!conda install -c conda-forge folium=0.5.0 --yes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Installing required Libraries"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"import requests\n",
"\n",
"from bs4 import BeautifulSoup\n",
"\n",
"from geopy.geocoders import Nominatim\n",
"\n",
"import folium\n",
"import matplotlib.cm as cm\n",
"import matplotlib.colors as colors\n",
"\n",
"from sklearn.cluster import KMeans\n",
"\n",
"from tqdm import tqdm"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Libraries Import"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"url = 'https://en.wikipedia.org/wiki/List_of_postal_codes_of_Canada:_M'\n",
"html_data = requests.get(url).text\n",
"soup = BeautifulSoup(html_data, 'html.parser')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Scrapping web for HTML data"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 289/289 [00:00<00:00, 22171.38it/s]\n"
]
}
],
"source": [
"post_code = []\n",
"borough = []\n",
"neighborhood = []\n",
"for row in tqdm(soup.find('table', {'class' : 'wikitable sortable'}).find_all('tr')):\n",
" columns = row.find_all('td')\n",
" if(len(columns) > 0):\n",
" post_code.append(columns[0].text)\n",
" borough.append(columns[1].text)\n",
" neighborhood.append(columns[2].text.rstrip('\\n'))\n",
" \n",
" "
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>PostalCode</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M1A</td>\n",
" <td>Not assigned</td>\n",
" <td>Not assigned</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M2A</td>\n",
" <td>Not assigned</td>\n",
" <td>Not assigned</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M3A</td>\n",
" <td>North York</td>\n",
" <td>Parkwoods</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M4A</td>\n",
" <td>North York</td>\n",
" <td>Victoria Village</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M5A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Harbourfront</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" PostalCode Borough Neighborhood\n",
"0 M1A Not assigned Not assigned\n",
"1 M2A Not assigned Not assigned\n",
"2 M3A North York Parkwoods\n",
"3 M4A North York Victoria Village\n",
"4 M5A Downtown Toronto Harbourfront"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = pd.DataFrame(data=[post_code, borough, neighborhood])\n",
"df = df.T\n",
"df.columns = ['PostalCode', 'Borough', 'Neighborhood']\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Converting scrapped data to DataFrame"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>PostalCode</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M3A</td>\n",
" <td>North York</td>\n",
" <td>Parkwoods</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M4A</td>\n",
" <td>North York</td>\n",
" <td>Victoria Village</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M5A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Harbourfront</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M5A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Regent Park</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M6A</td>\n",
" <td>North York</td>\n",
" <td>Lawrence Heights</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" PostalCode Borough Neighborhood\n",
"0 M3A North York Parkwoods\n",
"1 M4A North York Victoria Village\n",
"2 M5A Downtown Toronto Harbourfront\n",
"3 M5A Downtown Toronto Regent Park\n",
"4 M6A North York Lawrence Heights"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_dropna = df[df.Borough != 'Not assigned'].reset_index(drop=True) \n",
"df_dropna.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Removing the Borough with values as 'Not assigned'"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>PostalCode</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M1B</td>\n",
" <td>Scarborough</td>\n",
" <td>Rouge,Malvern</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M1C</td>\n",
" <td>Scarborough</td>\n",
" <td>Highland Creek,Rouge Hill,Port Union</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M1E</td>\n",
" <td>Scarborough</td>\n",
" <td>Guildwood,Morningside,West Hill</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M1G</td>\n",
" <td>Scarborough</td>\n",
" <td>Woburn</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M1H</td>\n",
" <td>Scarborough</td>\n",
" <td>Cedarbrae</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" PostalCode Borough Neighborhood\n",
"0 M1B Scarborough Rouge,Malvern\n",
"1 M1C Scarborough Highland Creek,Rouge Hill,Port Union\n",
"2 M1E Scarborough Guildwood,Morningside,West Hill\n",
"3 M1G Scarborough Woburn\n",
"4 M1H Scarborough Cedarbrae"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_grouped =df_dropna.groupby(['PostalCode', 'Borough'], as_index=False).agg(lambda x:','.join(x))\n",
"df_grouped.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Grouping neighborhood by postal and borough"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>PostalCode</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>85</th>\n",
" <td>M7A</td>\n",
" <td>Queen's Park</td>\n",
" <td>Not assigned</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" PostalCode Borough Neighborhood\n",
"85 M7A Queen's Park Not assigned"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_grouped.loc[df_grouped.Neighborhood == 'Not assigned']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Dealing with 'Not assigned' neighborhood"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"PostalCode M7A\n",
"Borough Queen's Park\n",
"Neighborhood Queen's Park\n",
"Name: 85, dtype: object"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_grouped.loc[df_grouped.Neighborhood == 'Not assigned', 'Neighborhood'] = df_grouped.loc[df_grouped.Neighborhood == 'Not assigned', 'Borough']\n",
"df_grouped.iloc[85]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Clean DataFrame"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(103, 3)"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_clean = df_grouped\n",
"df_clean.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Get location data fro borough"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Read CSV"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"!wget -q -O \"toronto_coordinates.csv\" http://cocl.us/Geospatial_data"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal Code</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M1B</td>\n",
" <td>43.806686</td>\n",
" <td>-79.194353</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M1C</td>\n",
" <td>43.784535</td>\n",
" <td>-79.160497</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M1E</td>\n",
" <td>43.763573</td>\n",
" <td>-79.188711</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M1G</td>\n",
" <td>43.770992</td>\n",
" <td>-79.216917</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M1H</td>\n",
" <td>43.773136</td>\n",
" <td>-79.239476</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postal Code Latitude Longitude\n",
"0 M1B 43.806686 -79.194353\n",
"1 M1C 43.784535 -79.160497\n",
"2 M1E 43.763573 -79.188711\n",
"3 M1G 43.770992 -79.216917\n",
"4 M1H 43.773136 -79.239476"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"coordinates = pd.read_csv('toronto_coordinates.csv')\n",
"coordinates.head()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal Code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M1B</td>\n",
" <td>Scarborough</td>\n",
" <td>Rouge,Malvern</td>\n",
" <td>43.806686</td>\n",
" <td>-79.194353</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M1C</td>\n",
" <td>Scarborough</td>\n",
" <td>Highland Creek,Rouge Hill,Port Union</td>\n",
" <td>43.784535</td>\n",
" <td>-79.160497</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M1E</td>\n",
" <td>Scarborough</td>\n",
" <td>Guildwood,Morningside,West Hill</td>\n",
" <td>43.763573</td>\n",
" <td>-79.188711</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M1G</td>\n",
" <td>Scarborough</td>\n",
" <td>Woburn</td>\n",
" <td>43.770992</td>\n",
" <td>-79.216917</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M1H</td>\n",
" <td>Scarborough</td>\n",
" <td>Cedarbrae</td>\n",
" <td>43.773136</td>\n",
" <td>-79.239476</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postal Code Borough Neighborhood Latitude \\\n",
"0 M1B Scarborough Rouge,Malvern 43.806686 \n",
"1 M1C Scarborough Highland Creek,Rouge Hill,Port Union 43.784535 \n",
"2 M1E Scarborough Guildwood,Morningside,West Hill 43.763573 \n",
"3 M1G Scarborough Woburn 43.770992 \n",
"4 M1H Scarborough Cedarbrae 43.773136 \n",
"\n",
" Longitude \n",
"0 -79.194353 \n",
"1 -79.160497 \n",
"2 -79.188711 \n",
"3 -79.216917 \n",
"4 -79.239476 "
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_clean_temp = df_clean.set_index('PostalCode')\n",
"coordinates_temp = coordinates.set_index('Postal Code')\n",
"coordinates_df = pd.concat([df_clean_temp, coordinates_temp], axis=1, join='inner')\n",
"coordinates_df.index.name = 'Postal Code'\n",
"coordinates_df.reset_index(inplace=True)\n",
"coordinates_df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Explore only those Borough that have Toronto in their name"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal Code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>37</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>43.676357</td>\n",
" <td>-79.293031</td>\n",
" </tr>\n",
" <tr>\n",
" <th>41</th>\n",
" <td>M4K</td>\n",
" <td>East Toronto</td>\n",
" <td>The Danforth West,Riverdale</td>\n",
" <td>43.679557</td>\n",
" <td>-79.352188</td>\n",
" </tr>\n",
" <tr>\n",
" <th>42</th>\n",
" <td>M4L</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches West,India Bazaar</td>\n",
" <td>43.668999</td>\n",
" <td>-79.315572</td>\n",
" </tr>\n",
" <tr>\n",
" <th>43</th>\n",
" <td>M4M</td>\n",
" <td>East Toronto</td>\n",
" <td>Studio District</td>\n",
" <td>43.659526</td>\n",
" <td>-79.340923</td>\n",
" </tr>\n",
" <tr>\n",
" <th>44</th>\n",
" <td>M4N</td>\n",
" <td>Central Toronto</td>\n",
" <td>Lawrence Park</td>\n",
" <td>43.728020</td>\n",
" <td>-79.388790</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postal Code Borough Neighborhood Latitude \\\n",
"37 M4E East Toronto The Beaches 43.676357 \n",
"41 M4K East Toronto The Danforth West,Riverdale 43.679557 \n",
"42 M4L East Toronto The Beaches West,India Bazaar 43.668999 \n",
"43 M4M East Toronto Studio District 43.659526 \n",
"44 M4N Central Toronto Lawrence Park 43.728020 \n",
"\n",
" Longitude \n",
"37 -79.293031 \n",
"41 -79.352188 \n",
"42 -79.315572 \n",
"43 -79.340923 \n",
"44 -79.388790 "
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"toronto_df = coordinates_df[coordinates_df.Borough.str.contains('Toronto')]\n",
"toronto_df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Use geopy library to get the latitude and longitude values of Toronto."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The geograpical coordinates of Toronto are 43.653963, -79.387207.\n"
]
}
],
"source": [
"address = 'Toronto, Ontario'\n",
"\n",
"geolocator = Nominatim(user_agent=\"toronto_explorer\")\n",
"location = geolocator.geocode(address)\n",
"latitude = location.latitude\n",
"longitude = location.longitude\n",
"print('The geograpical coordinates of Toronto are {}, {}.'.format(latitude, longitude))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Create a map of Toronto with neighborhoods superimposed on top"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+CjxoZWFkPiAgICAKICAgIDxtZXRhIGh0dHAtZXF1aXY9ImNvbnRlbnQtdHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PVVURi04IiAvPgogICAgPHNjcmlwdD5MX1BSRUZFUl9DQU5WQVMgPSBmYWxzZTsgTF9OT19UT1VDSCA9IGZhbHNlOyBMX0RJU0FCTEVfM0QgPSBmYWxzZTs8L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2xlYWZsZXRAMS4yLjAvZGlzdC9sZWFmbGV0LmpzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2FqYXguZ29vZ2xlYXBpcy5jb20vYWpheC9saWJzL2pxdWVyeS8xLjExLjEvanF1ZXJ5Lm1pbi5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS9ib290c3RyYXAvMy4yLjAvanMvYm9vdHN0cmFwLm1pbi5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS9hamF4L2xpYnMvTGVhZmxldC5hd2Vzb21lLW1hcmtlcnMvMi4wLjIvbGVhZmxldC5hd2Vzb21lLW1hcmtlcnMuanMiPjwvc2NyaXB0PgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2xlYWZsZXRAMS4yLjAvZGlzdC9sZWFmbGV0LmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL21heGNkbi5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC8zLjIuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiLz4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS9ib290c3RyYXAvMy4yLjAvY3NzL2Jvb3RzdHJhcC10aGVtZS5taW4uY3NzIi8+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vbWF4Y2RuLmJvb3RzdHJhcGNkbi5jb20vZm9udC1hd2Vzb21lLzQuNi4zL2Nzcy9mb250LWF3ZXNvbWUubWluLmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9MZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy8yLjAuMi9sZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy5jc3MiLz4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9yYXdnaXQuY29tL3B5dGhvbi12aXN1YWxpemF0aW9uL2ZvbGl1bS9tYXN0ZXIvZm9saXVtL3RlbXBsYXRlcy9sZWFmbGV0LmF3ZXNvbWUucm90YXRlLmNzcyIvPgogICAgPHN0eWxlPmh0bWwsIGJvZHkge3dpZHRoOiAxMDAlO2hlaWdodDogMTAwJTttYXJnaW46IDA7cGFkZGluZzogMDt9PC9zdHlsZT4KICAgIDxzdHlsZT4jbWFwIHtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MDtib3R0b206MDtyaWdodDowO2xlZnQ6MDt9PC9zdHlsZT4KICAgIAogICAgICAgICAgICA8c3R5bGU+ICNtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UgewogICAgICAgICAgICAgICAgcG9zaXRpb24gOiByZWxhdGl2ZTsKICAgICAgICAgICAgICAgIHdpZHRoIDogMTAwLjAlOwogICAgICAgICAgICAgICAgaGVpZ2h0OiAxMDAuMCU7CiAgICAgICAgICAgICAgICBsZWZ0OiAwLjAlOwogICAgICAgICAgICAgICAgdG9wOiAwLjAlOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICA8L3N0eWxlPgogICAgICAgIAo8L2hlYWQ+Cjxib2R5PiAgICAKICAgIAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJmb2xpdW0tbWFwIiBpZD0ibWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlIiA+PC9kaXY+CiAgICAgICAgCjwvYm9keT4KPHNjcmlwdD4gICAgCiAgICAKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGJvdW5kcyA9IG51bGw7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgdmFyIG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSA9IEwubWFwKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7Y2VudGVyOiBbNDMuNjUzOTYzLC03OS4zODcyMDddLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgem9vbTogMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhCb3VuZHM6IGJvdW5kcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxheWVyczogW10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3b3JsZENvcHlKdW1wOiBmYWxzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNyczogTC5DUlMuRVBTRzM4NTcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciB0aWxlX2xheWVyX2QxZDIyN2NhZjdmMTQ3NTM4M2M4YmY0ZDY4YTIxYTA5ID0gTC50aWxlTGF5ZXIoCiAgICAgICAgICAgICAgICAnaHR0cHM6Ly97c30udGlsZS5vcGVuc3RyZWV0bWFwLm9yZy97en0ve3h9L3t5fS5wbmcnLAogICAgICAgICAgICAgICAgewogICJhdHRyaWJ1dGlvbiI6IG51bGwsCiAgImRldGVjdFJldGluYSI6IGZhbHNlLAogICJtYXhab29tIjogMTgsCiAgIm1pblpvb20iOiAxLAogICJub1dyYXAiOiBmYWxzZSwKICAic3ViZG9tYWlucyI6ICJhYmMiCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9iMGI2NWRmZDU3ZmU0MjViYTI1MmQyYmExZTRkNzA2MSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY3NjM1NzM5OTk5OTk5LC03OS4yOTMwMzEyXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzExYmQ0MjRlMDVlMDRhMmZhMTA1NmIxNjZkOWI1MTNhID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzE3N2YwYjNiNDA2MDQ4NTE5NDQ0OWFiMWRiMTYzMjlkID0gJCgnPGRpdiBpZD0iaHRtbF8xNzdmMGIzYjQwNjA0ODUxOTQ0NDlhYjFkYjE2MzI5ZCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+VGhlIEJlYWNoZXMsIEVhc3QgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMTFiZDQyNGUwNWUwNGEyZmExMDU2YjE2NmQ5YjUxM2Euc2V0Q29udGVudChodG1sXzE3N2YwYjNiNDA2MDQ4NTE5NDQ0OWFiMWRiMTYzMjlkKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2IwYjY1ZGZkNTdmZTQyNWJhMjUyZDJiYTFlNGQ3MDYxLmJpbmRQb3B1cChwb3B1cF8xMWJkNDI0ZTA1ZTA0YTJmYTEwNTZiMTY2ZDliNTEzYSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl82ZTU0NTliODc5Y2U0MWZkOWU4OWExMjAwOGE1OWRmNyA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY3OTU1NzEsLTc5LjM1MjE4OF0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8yNjUzMTc2ZWZkYzQ0OWE1OWViN2FhNGFjYmVkY2NkMyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8yNzAwNzVkNDJhYTU0OWJkOTNhN2I2ODZiMzI1Zjg2ZCA9ICQoJzxkaXYgaWQ9Imh0bWxfMjcwMDc1ZDQyYWE1NDliZDkzYTdiNjg2YjMyNWY4NmQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlRoZSBEYW5mb3J0aCBXZXN0LFJpdmVyZGFsZSwgRWFzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8yNjUzMTc2ZWZkYzQ0OWE1OWViN2FhNGFjYmVkY2NkMy5zZXRDb250ZW50KGh0bWxfMjcwMDc1ZDQyYWE1NDliZDkzYTdiNjg2YjMyNWY4NmQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNmU1NDU5Yjg3OWNlNDFmZDllODlhMTIwMDhhNTlkZjcuYmluZFBvcHVwKHBvcHVwXzI2NTMxNzZlZmRjNDQ5YTU5ZWI3YWE0YWNiZWRjY2QzKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2Q3ZmYxNjA0ZGE2MzQ4MzQ5Mjc4NmJjOGRlMjJiNmNiID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY4OTk4NSwtNzkuMzE1NTcxNTk5OTk5OThdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfOTI5ZjdiYmU4NDdkNGM0ZDg3MmI2NGM0ZDUzOWIxMDAgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMmRiZjdhZDUwYzJlNGNjZWI3MGUxOTNlZDY2N2RhY2EgPSAkKCc8ZGl2IGlkPSJodG1sXzJkYmY3YWQ1MGMyZTRjY2ViNzBlMTkzZWQ2NjdkYWNhIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5UaGUgQmVhY2hlcyBXZXN0LEluZGlhIEJhemFhciwgRWFzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF85MjlmN2JiZTg0N2Q0YzRkODcyYjY0YzRkNTM5YjEwMC5zZXRDb250ZW50KGh0bWxfMmRiZjdhZDUwYzJlNGNjZWI3MGUxOTNlZDY2N2RhY2EpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfZDdmZjE2MDRkYTYzNDgzNDkyNzg2YmM4ZGUyMmI2Y2IuYmluZFBvcHVwKHBvcHVwXzkyOWY3YmJlODQ3ZDRjNGQ4NzJiNjRjNGQ1MzliMTAwKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2FkNzVmZTU5ZjllMDRkYzlhODZmZTY0MzAzZDg2MzgxID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjU5NTI1NSwtNzkuMzQwOTIzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2IxOTk4YzU2ODRhYTQ5ZTlhMTJjNGE1NDlhN2MyMzE3ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzNlNmM1MDc1ZjQ1YTRlMDA4NTk2NGFhYzY2YzI3NDQ5ID0gJCgnPGRpdiBpZD0iaHRtbF8zZTZjNTA3NWY0NWE0ZTAwODU5NjRhYWM2NmMyNzQ0OSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+U3R1ZGlvIERpc3RyaWN0LCBFYXN0IFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2IxOTk4YzU2ODRhYTQ5ZTlhMTJjNGE1NDlhN2MyMzE3LnNldENvbnRlbnQoaHRtbF8zZTZjNTA3NWY0NWE0ZTAwODU5NjRhYWM2NmMyNzQ0OSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9hZDc1ZmU1OWY5ZTA0ZGM5YTg2ZmU2NDMwM2Q4NjM4MS5iaW5kUG9wdXAocG9wdXBfYjE5OThjNTY4NGFhNDllOWExMmM0YTU0OWE3YzIzMTcpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNjE4OGMyZDNmNzhkNDIwODkxMDQ4ZGY5N2I2YjU4NGUgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MjgwMjA1LC03OS4zODg3OTAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2NmMDNiNGJkYjZmZjQ1MDVhYTljNzk4N2ZkMTU1ZTVhID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2MzYWJhN2U4YzJhNTQ2NGE5NDI3MmI4ZjI4MWEyZDkwID0gJCgnPGRpdiBpZD0iaHRtbF9jM2FiYTdlOGMyYTU0NjRhOTQyNzJiOGYyODFhMmQ5MCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+TGF3cmVuY2UgUGFyaywgQ2VudHJhbCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9jZjAzYjRiZGI2ZmY0NTA1YWE5Yzc5ODdmZDE1NWU1YS5zZXRDb250ZW50KGh0bWxfYzNhYmE3ZThjMmE1NDY0YTk0MjcyYjhmMjgxYTJkOTApOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNjE4OGMyZDNmNzhkNDIwODkxMDQ4ZGY5N2I2YjU4NGUuYmluZFBvcHVwKHBvcHVwX2NmMDNiNGJkYjZmZjQ1MDVhYTljNzk4N2ZkMTU1ZTVhKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzJkNDA4ZTU0YzY2ODQ4YTE4ZDI5ZjEyM2I2ZjI3NTlkID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzEyNzUxMSwtNzkuMzkwMTk3NV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9mNmExNDNjMTc1NzU0ZmIxYWJiZjhiZjMxNzJjNTlkMyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9lYjFhMjQwYTYxMzg0ZGNiYTY1Njg3OGUwNDM3MjY5ZiA9ICQoJzxkaXYgaWQ9Imh0bWxfZWIxYTI0MGE2MTM4NGRjYmE2NTY4NzhlMDQzNzI2OWYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRhdmlzdmlsbGUgTm9ydGgsIENlbnRyYWwgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfZjZhMTQzYzE3NTc1NGZiMWFiYmY4YmYzMTcyYzU5ZDMuc2V0Q29udGVudChodG1sX2ViMWEyNDBhNjEzODRkY2JhNjU2ODc4ZTA0MzcyNjlmKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzJkNDA4ZTU0YzY2ODQ4YTE4ZDI5ZjEyM2I2ZjI3NTlkLmJpbmRQb3B1cChwb3B1cF9mNmExNDNjMTc1NzU0ZmIxYWJiZjhiZjMxNzJjNTlkMyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9iNTJjNjUyMzk3ODI0ZmFhOGNkMjdmODdmYmI3NjFiNSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjcxNTM4MzQsLTc5LjQwNTY3ODQwMDAwMDAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzQ5OWZmNzA1ZTFkYTQyMzFiZGYzZmNkYjJlNDZmZDliID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzRiNDdkOTJmNGNjNzRhZDliNGM0MmZjOTE0YWU3NThmID0gJCgnPGRpdiBpZD0iaHRtbF80YjQ3ZDkyZjRjYzc0YWQ5YjRjNDJmYzkxNGFlNzU4ZiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Tm9ydGggVG9yb250byBXZXN0LCBDZW50cmFsIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzQ5OWZmNzA1ZTFkYTQyMzFiZGYzZmNkYjJlNDZmZDliLnNldENvbnRlbnQoaHRtbF80YjQ3ZDkyZjRjYzc0YWQ5YjRjNDJmYzkxNGFlNzU4Zik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9iNTJjNjUyMzk3ODI0ZmFhOGNkMjdmODdmYmI3NjFiNS5iaW5kUG9wdXAocG9wdXBfNDk5ZmY3MDVlMWRhNDIzMWJkZjNmY2RiMmU0NmZkOWIpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMWYzZDAwMzgzYjNiNDEyYzk5NDRiMDJiMzk0YmE4MWIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MDQzMjQ0LC03OS4zODg3OTAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2NkODBkOTUyYzFmOTQ0ZGU4OGNhZjM5YzYxYjZjOGEyID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzYwZmU1ZjlhY2UyNDQyN2FhMGQwM2ZhN2Q3ODg1YjZhID0gJCgnPGRpdiBpZD0iaHRtbF82MGZlNWY5YWNlMjQ0MjdhYTBkMDNmYTdkNzg4NWI2YSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RGF2aXN2aWxsZSwgQ2VudHJhbCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9jZDgwZDk1MmMxZjk0NGRlODhjYWYzOWM2MWI2YzhhMi5zZXRDb250ZW50KGh0bWxfNjBmZTVmOWFjZTI0NDI3YWEwZDAzZmE3ZDc4ODViNmEpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMWYzZDAwMzgzYjNiNDEyYzk5NDRiMDJiMzk0YmE4MWIuYmluZFBvcHVwKHBvcHVwX2NkODBkOTUyYzFmOTQ0ZGU4OGNhZjM5YzYxYjZjOGEyKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2MxOGNjOTBlMjJjYzQwZTg4YTgxMGE3ZjA5OGEyMzY2ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjg5NTc0MywtNzkuMzgzMTU5OTAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMTYwYmNmMGM0NjVlNDcxNGFkOWU5ZWFkNWQyMDlkMzUgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNDU5ODU4MjM4YzBlNDFmZTlmNGEzNzFiM2MxMDQxNTYgPSAkKCc8ZGl2IGlkPSJodG1sXzQ1OTg1ODIzOGMwZTQxZmU5ZjRhMzcxYjNjMTA0MTU2IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Nb29yZSBQYXJrLFN1bW1lcmhpbGwgRWFzdCwgQ2VudHJhbCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8xNjBiY2YwYzQ2NWU0NzE0YWQ5ZTllYWQ1ZDIwOWQzNS5zZXRDb250ZW50KGh0bWxfNDU5ODU4MjM4YzBlNDFmZTlmNGEzNzFiM2MxMDQxNTYpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYzE4Y2M5MGUyMmNjNDBlODhhODEwYTdmMDk4YTIzNjYuYmluZFBvcHVwKHBvcHVwXzE2MGJjZjBjNDY1ZTQ3MTRhZDllOWVhZDVkMjA5ZDM1KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2UzMTRjMTg3OTFmYjRjOTY5NWJkNmNhOGVmM2E2YmZjID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjg2NDEyMjk5OTk5OTksLTc5LjQwMDA0OTNdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfYzU5YjY3NWQ5NjMzNDJkMTllNGRhNDVmMGM1YTA3NDAgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfOTBiNjcxYzc3NzQzNGU3Yzk0ODQwYjZlZGVmZDkxODggPSAkKCc8ZGl2IGlkPSJodG1sXzkwYjY3MWM3Nzc0MzRlN2M5NDg0MGI2ZWRlZmQ5MTg4IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5EZWVyIFBhcmssRm9yZXN0IEhpbGwgU0UsUmF0aG5lbGx5LFNvdXRoIEhpbGwsU3VtbWVyaGlsbCBXZXN0LCBDZW50cmFsIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2M1OWI2NzVkOTYzMzQyZDE5ZTRkYTQ1ZjBjNWEwNzQwLnNldENvbnRlbnQoaHRtbF85MGI2NzFjNzc3NDM0ZTdjOTQ4NDBiNmVkZWZkOTE4OCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9lMzE0YzE4NzkxZmI0Yzk2OTViZDZjYThlZjNhNmJmYy5iaW5kUG9wdXAocG9wdXBfYzU5YjY3NWQ5NjMzNDJkMTllNGRhNDVmMGM1YTA3NDApOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNWU1YWVhZjI2ZDJiNGIyZWE2YWNkN2E0NTYzNWFmYjUgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Nzk1NjI2LC03OS4zNzc1Mjk0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9jZGY1ZGY0NDc0NDU0OGNiOTdlOWFkYmMwZmIxODBhYiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9lNzM0NGQ1NGI1NTk0YjEzYTAyNGY0MzM5MjZmYTA4ZSA9ICQoJzxkaXYgaWQ9Imh0bWxfZTczNDRkNTRiNTU5NGIxM2EwMjRmNDMzOTI2ZmEwOGUiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJvc2VkYWxlLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9jZGY1ZGY0NDc0NDU0OGNiOTdlOWFkYmMwZmIxODBhYi5zZXRDb250ZW50KGh0bWxfZTczNDRkNTRiNTU5NGIxM2EwMjRmNDMzOTI2ZmEwOGUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNWU1YWVhZjI2ZDJiNGIyZWE2YWNkN2E0NTYzNWFmYjUuYmluZFBvcHVwKHBvcHVwX2NkZjVkZjQ0NzQ0NTQ4Y2I5N2U5YWRiYzBmYjE4MGFiKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2U0MDYwMGRjZDc2OTQ2YTZiYzBjNjVhYTM0MTE1MTdlID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY3OTY3LC03OS4zNjc2NzUzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzUwMjk4NDY3NDUyNTRhMjU4MGExOTgyYTI2NWQ1MWFkID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2M3NzJhMmRhNDk1MjQ0NGI4YzM4OWNmZDZlNjAyOGYwID0gJCgnPGRpdiBpZD0iaHRtbF9jNzcyYTJkYTQ5NTI0NDRiOGMzODljZmQ2ZTYwMjhmMCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2FiYmFnZXRvd24sU3QuIEphbWVzIFRvd24sIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzUwMjk4NDY3NDUyNTRhMjU4MGExOTgyYTI2NWQ1MWFkLnNldENvbnRlbnQoaHRtbF9jNzcyYTJkYTQ5NTI0NDRiOGMzODljZmQ2ZTYwMjhmMCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9lNDA2MDBkY2Q3Njk0NmE2YmMwYzY1YWEzNDExNTE3ZS5iaW5kUG9wdXAocG9wdXBfNTAyOTg0Njc0NTI1NGEyNTgwYTE5ODJhMjY1ZDUxYWQpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfYWQ2MWQ1YzY5NDJhNDQxYmI3NzBlM2NjZDQ2MGFmYzIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjU4NTk5LC03OS4zODMxNTk5MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9kYjJlN2RmMWNiMTQ0OTdkYmE5YWEzZTViZjI1MGU4NCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF80NmNiY2QwNTQ2YTk0YzA5OTA3ZWM2MTY5Y2NjNmI1NSA9ICQoJzxkaXYgaWQ9Imh0bWxfNDZjYmNkMDU0NmE5NGMwOTkwN2VjNjE2OWNjYzZiNTUiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNodXJjaCBhbmQgV2VsbGVzbGV5LCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9kYjJlN2RmMWNiMTQ0OTdkYmE5YWEzZTViZjI1MGU4NC5zZXRDb250ZW50KGh0bWxfNDZjYmNkMDU0NmE5NGMwOTkwN2VjNjE2OWNjYzZiNTUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYWQ2MWQ1YzY5NDJhNDQxYmI3NzBlM2NjZDQ2MGFmYzIuYmluZFBvcHVwKHBvcHVwX2RiMmU3ZGYxY2IxNDQ5N2RiYTlhYTNlNWJmMjUwZTg0KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2Y4Y2MzYmQ1N2JhMzQ2Yjg5Yzg5ZDA0MzM1Y2JkZTU3ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjU0MjU5OSwtNzkuMzYwNjM1OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF82NDZkZWFhNDAyYWY0Mzc2OWUxYmNhM2I0YTBmNmNmNSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9jNTRiOGMzYjkxZWQ0NTEyYjYyNmRkMWI1ODJhZmEwZSA9ICQoJzxkaXYgaWQ9Imh0bWxfYzU0YjhjM2I5MWVkNDUxMmI2MjZkZDFiNTgyYWZhMGUiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkhhcmJvdXJmcm9udCxSZWdlbnQgUGFyaywgRG93bnRvd24gVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNjQ2ZGVhYTQwMmFmNDM3NjllMWJjYTNiNGEwZjZjZjUuc2V0Q29udGVudChodG1sX2M1NGI4YzNiOTFlZDQ1MTJiNjI2ZGQxYjU4MmFmYTBlKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2Y4Y2MzYmQ1N2JhMzQ2Yjg5Yzg5ZDA0MzM1Y2JkZTU3LmJpbmRQb3B1cChwb3B1cF82NDZkZWFhNDAyYWY0Mzc2OWUxYmNhM2I0YTBmNmNmNSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl82MTZjOWQ1ZTg3MGU0NzI0OWRkM2EyZjk3N2NlYTk1YiA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1NzE2MTgsLTc5LjM3ODkzNzA5OTk5OTk5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2I5Y2E0OTEwNjdlNDRjNDc4MzkxZmIwYTBlOTE3NTg3ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzA3MjNmMmU4MDU2MDQxZGM5MWJlMGJlZTYzMzY2MGY4ID0gJCgnPGRpdiBpZD0iaHRtbF8wNzIzZjJlODA1NjA0MWRjOTFiZTBiZWU2MzM2NjBmOCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+UnllcnNvbixHYXJkZW4gRGlzdHJpY3QsIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2I5Y2E0OTEwNjdlNDRjNDc4MzkxZmIwYTBlOTE3NTg3LnNldENvbnRlbnQoaHRtbF8wNzIzZjJlODA1NjA0MWRjOTFiZTBiZWU2MzM2NjBmOCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl82MTZjOWQ1ZTg3MGU0NzI0OWRkM2EyZjk3N2NlYTk1Yi5iaW5kUG9wdXAocG9wdXBfYjljYTQ5MTA2N2U0NGM0NzgzOTFmYjBhMGU5MTc1ODcpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfYjFlY2FjMDc4OWE0NGYyMDk1YWVlNDYyYzM2MTBhMDggPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTE0OTM5LC03OS4zNzU0MTc5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzM1YWIxNTVkZTFkYzQ0MDA5YWIxMDNkZDc1NzM2YjA5ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzU1Y2QwZWE4YzgyODQ0NmM4MmRhMTJkMmE4MTI4ZmU5ID0gJCgnPGRpdiBpZD0iaHRtbF81NWNkMGVhOGM4Mjg0NDZjODJkYTEyZDJhODEyOGZlOSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+U3QuIEphbWVzIFRvd24sIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzM1YWIxNTVkZTFkYzQ0MDA5YWIxMDNkZDc1NzM2YjA5LnNldENvbnRlbnQoaHRtbF81NWNkMGVhOGM4Mjg0NDZjODJkYTEyZDJhODEyOGZlOSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9iMWVjYWMwNzg5YTQ0ZjIwOTVhZWU0NjJjMzYxMGEwOC5iaW5kUG9wdXAocG9wdXBfMzVhYjE1NWRlMWRjNDQwMDlhYjEwM2RkNzU3MzZiMDkpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNzhiMmU1OWNhNmRkNDY3NmFlOTBiZDIzMzU1NTg1NGMgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDQ3NzA3OTk5OTk5OTYsLTc5LjM3MzMwNjRdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfYjczZDg5ZTVmY2QwNGU0MDkyNzRhNjA2NmVkNWFmNDkgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfOWJjNmQ4Y2I0YmU2NDg1ZTkxOGIzMjY5YTEzNDdjZjQgPSAkKCc8ZGl2IGlkPSJodG1sXzliYzZkOGNiNGJlNjQ4NWU5MThiMzI2OWExMzQ3Y2Y0IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5CZXJjenkgUGFyaywgRG93bnRvd24gVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfYjczZDg5ZTVmY2QwNGU0MDkyNzRhNjA2NmVkNWFmNDkuc2V0Q29udGVudChodG1sXzliYzZkOGNiNGJlNjQ4NWU5MThiMzI2OWExMzQ3Y2Y0KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzc4YjJlNTljYTZkZDQ2NzZhZTkwYmQyMzM1NTU4NTRjLmJpbmRQb3B1cChwb3B1cF9iNzNkODllNWZjZDA0ZTQwOTI3NGE2MDY2ZWQ1YWY0OSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl84NWYxN2Y0YTI2MTI0OGFiYTUwZGQyODFhM2RlYTRmOCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1Nzk1MjQsLTc5LjM4NzM4MjZdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZDliMzdiZjgxYjNmNGJlMThkNmJmYjRjMGQzMTFhNDcgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNTc5YTM5NGI2ODUwNDkzZWJjZjFhMGFiYTUzOTYwYzYgPSAkKCc8ZGl2IGlkPSJodG1sXzU3OWEzOTRiNjg1MDQ5M2ViY2YxYTBhYmE1Mzk2MGM2IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5DZW50cmFsIEJheSBTdHJlZXQsIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2Q5YjM3YmY4MWIzZjRiZTE4ZDZiZmI0YzBkMzExYTQ3LnNldENvbnRlbnQoaHRtbF81NzlhMzk0YjY4NTA0OTNlYmNmMWEwYWJhNTM5NjBjNik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl84NWYxN2Y0YTI2MTI0OGFiYTUwZGQyODFhM2RlYTRmOC5iaW5kUG9wdXAocG9wdXBfZDliMzdiZjgxYjNmNGJlMThkNmJmYjRjMGQzMTFhNDcpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNTU3NTVlMzFiYTI1NDc3MTgwZDlmYThjZDU0MzMyMWMgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTA1NzEyMDAwMDAwMSwtNzkuMzg0NTY3NV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF81MDVlMDgzYjEwMmE0NWRjYjYzZTNjYTNiOTNmMTg4ZSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8xNjNjYjg5NjZkNWU0ZTA2OTU5ODEwNTYzY2UyNDdmOCA9ICQoJzxkaXYgaWQ9Imh0bWxfMTYzY2I4OTY2ZDVlNGUwNjk1OTgxMDU2M2NlMjQ3ZjgiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkFkZWxhaWRlLEtpbmcsUmljaG1vbmQsIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzUwNWUwODNiMTAyYTQ1ZGNiNjNlM2NhM2I5M2YxODhlLnNldENvbnRlbnQoaHRtbF8xNjNjYjg5NjZkNWU0ZTA2OTU5ODEwNTYzY2UyNDdmOCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl81NTc1NWUzMWJhMjU0NzcxODBkOWZhOGNkNTQzMzIxYy5iaW5kUG9wdXAocG9wdXBfNTA1ZTA4M2IxMDJhNDVkY2I2M2UzY2EzYjkzZjE4OGUpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfYjRmZGY3ZmY5YTM3NDk1NGFhOTI1Y2MxOTg1ZTA0YTcgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDA4MTU3LC03OS4zODE3NTIyOTk5OTk5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF84MWVkZDU4OTMzYzc0M2I2YjY1YjczNGQ0NDQxMzFmZCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF82ZTUxZWY5YTM5ZDk0Yjg3OWJkMTU1ZThjMzM4Y2MyOSA9ICQoJzxkaXYgaWQ9Imh0bWxfNmU1MWVmOWEzOWQ5NGI4NzliZDE1NWU4YzMzOGNjMjkiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkhhcmJvdXJmcm9udCBFYXN0LFRvcm9udG8gSXNsYW5kcyxVbmlvbiBTdGF0aW9uLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF84MWVkZDU4OTMzYzc0M2I2YjY1YjczNGQ0NDQxMzFmZC5zZXRDb250ZW50KGh0bWxfNmU1MWVmOWEzOWQ5NGI4NzliZDE1NWU4YzMzOGNjMjkpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYjRmZGY3ZmY5YTM3NDk1NGFhOTI1Y2MxOTg1ZTA0YTcuYmluZFBvcHVwKHBvcHVwXzgxZWRkNTg5MzNjNzQzYjZiNjViNzM0ZDQ0NDEzMWZkKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2I3NzFhNTRmOGVlMDQ1MTU5YmUxMTkwOGU4NWIwMWJjID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ3MTc2OCwtNzkuMzgxNTc2NDAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMGEwMzc4NmQ0NTczNGVjOGJkN2JlZTc2ZjBkNjRmNmMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYzlmOTc3Y2U4ZGQyNDVlNDg0OWNjOGQ4Mjc1NGI5MzIgPSAkKCc8ZGl2IGlkPSJodG1sX2M5Zjk3N2NlOGRkMjQ1ZTQ4NDljYzhkODI3NTRiOTMyIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5EZXNpZ24gRXhjaGFuZ2UsVG9yb250byBEb21pbmlvbiBDZW50cmUsIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzBhMDM3ODZkNDU3MzRlYzhiZDdiZWU3NmYwZDY0ZjZjLnNldENvbnRlbnQoaHRtbF9jOWY5NzdjZThkZDI0NWU0ODQ5Y2M4ZDgyNzU0YjkzMik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9iNzcxYTU0ZjhlZTA0NTE1OWJlMTE5MDhlODViMDFiYy5iaW5kUG9wdXAocG9wdXBfMGEwMzc4NmQ0NTczNGVjOGJkN2JlZTc2ZjBkNjRmNmMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNDMyMTBhOTA0Nzc2NGI3MmI0YWE0MWJmZTliNGFhYjkgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDgxOTg1LC03OS4zNzk4MTY5MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8xN2IwYTdiNTU0Njk0ZjMzYjljNDE2Nzk0NmZiOWQ0NSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8wMmExZjc5NjNhMTk0OWE5YjM0MWJjNzY0N2MwMDE4OCA9ICQoJzxkaXYgaWQ9Imh0bWxfMDJhMWY3OTYzYTE5NDlhOWIzNDFiYzc2NDdjMDAxODgiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNvbW1lcmNlIENvdXJ0LFZpY3RvcmlhIEhvdGVsLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8xN2IwYTdiNTU0Njk0ZjMzYjljNDE2Nzk0NmZiOWQ0NS5zZXRDb250ZW50KGh0bWxfMDJhMWY3OTYzYTE5NDlhOWIzNDFiYzc2NDdjMDAxODgpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNDMyMTBhOTA0Nzc2NGI3MmI0YWE0MWJmZTliNGFhYjkuYmluZFBvcHVwKHBvcHVwXzE3YjBhN2I1NTQ2OTRmMzNiOWM0MTY3OTQ2ZmI5ZDQ1KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2EwOWM1MmMwZDM4ZTQ3NjU4Y2IwZWRiYjgzOGZmMzEwID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzExNjk0OCwtNzkuNDE2OTM1NTk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNjdlMzRjNTA5YTg0NGZhYmJlZTEzZDAwOGUxOTg1ZGQgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfODg4ZWIwZDkwNTI1NDk5MGJiMTc3ZWQzNGE5MDgxN2QgPSAkKCc8ZGl2IGlkPSJodG1sXzg4OGViMGQ5MDUyNTQ5OTBiYjE3N2VkMzRhOTA4MTdkIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Sb3NlbGF3biwgQ2VudHJhbCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF82N2UzNGM1MDlhODQ0ZmFiYmVlMTNkMDA4ZTE5ODVkZC5zZXRDb250ZW50KGh0bWxfODg4ZWIwZDkwNTI1NDk5MGJiMTc3ZWQzNGE5MDgxN2QpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYTA5YzUyYzBkMzhlNDc2NThjYjBlZGJiODM4ZmYzMTAuYmluZFBvcHVwKHBvcHVwXzY3ZTM0YzUwOWE4NDRmYWJiZWUxM2QwMDhlMTk4NWRkKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzUyZjc3MTI4ZTNiZjQ4NmE5ZjRlMDhjYmRiOGY1NzQ2ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjk2OTQ3NiwtNzkuNDExMzA3MjAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNzJhNTYyOGM1ZGIzNDIyOTgxNmYxYTYwOTZkNjBkOTcgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMjc4OGVhZjIzODdjNDBmYTgzNzczOTVkZGM1ZDA3ODUgPSAkKCc8ZGl2IGlkPSJodG1sXzI3ODhlYWYyMzg3YzQwZmE4Mzc3Mzk1ZGRjNWQwNzg1IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Gb3Jlc3QgSGlsbCBOb3J0aCxGb3Jlc3QgSGlsbCBXZXN0LCBDZW50cmFsIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzcyYTU2MjhjNWRiMzQyMjk4MTZmMWE2MDk2ZDYwZDk3LnNldENvbnRlbnQoaHRtbF8yNzg4ZWFmMjM4N2M0MGZhODM3NzM5NWRkYzVkMDc4NSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl81MmY3NzEyOGUzYmY0ODZhOWY0ZTA4Y2JkYjhmNTc0Ni5iaW5kUG9wdXAocG9wdXBfNzJhNTYyOGM1ZGIzNDIyOTgxNmYxYTYwOTZkNjBkOTcpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMGRjZDY1OWRhNDcwNDVhMDgyMGE2YjQzOGEyNDY2ODEgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NzI3MDk3LC03OS40MDU2Nzg0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9jMDIxN2E3MTY1NDY0Mzk1YjI5MzI0MmNlYWQ3MGIwZiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9lMzE5OTBjYzQ0ZWE0ODg0OTYyY2NhZjBhNGIwNmFkNyA9ICQoJzxkaXYgaWQ9Imh0bWxfZTMxOTkwY2M0NGVhNDg4NDk2MmNjYWYwYTRiMDZhZDciIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlRoZSBBbm5leCxOb3J0aCBNaWR0b3duLFlvcmt2aWxsZSwgQ2VudHJhbCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9jMDIxN2E3MTY1NDY0Mzk1YjI5MzI0MmNlYWQ3MGIwZi5zZXRDb250ZW50KGh0bWxfZTMxOTkwY2M0NGVhNDg4NDk2MmNjYWYwYTRiMDZhZDcpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMGRjZDY1OWRhNDcwNDVhMDgyMGE2YjQzOGEyNDY2ODEuYmluZFBvcHVwKHBvcHVwX2MwMjE3YTcxNjU0NjQzOTViMjkzMjQyY2VhZDcwYjBmKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2E0ZjA3MTA4YTk4MzQ3ZTg5ODI0NDU5Y2NhMjA3OThjID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjYyNjk1NiwtNzkuNDAwMDQ5M10sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8yZjhjZDU5ZTNlNzU0NDBkODJkYjgwOTU2ZjhkOWY3NSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9kNGE1ZTYxOTk3YzE0NWI5YTNkMTQwNjkyYTk1NWZiZiA9ICQoJzxkaXYgaWQ9Imh0bWxfZDRhNWU2MTk5N2MxNDViOWEzZDE0MDY5MmE5NTVmYmYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkhhcmJvcmQsVW5pdmVyc2l0eSBvZiBUb3JvbnRvLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8yZjhjZDU5ZTNlNzU0NDBkODJkYjgwOTU2ZjhkOWY3NS5zZXRDb250ZW50KGh0bWxfZDRhNWU2MTk5N2MxNDViOWEzZDE0MDY5MmE5NTVmYmYpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYTRmMDcxMDhhOTgzNDdlODk4MjQ0NTljY2EyMDc5OGMuYmluZFBvcHVwKHBvcHVwXzJmOGNkNTllM2U3NTQ0MGQ4MmRiODA5NTZmOGQ5Zjc1KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2U0MTZjMDg4ZDhlNjRjMDRiMGQzOWQ4OTczN2JjYzY0ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjUzMjA1NywtNzkuNDAwMDQ5M10sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8xOWYwOGE0ZTE0ZjY0MDQ5YmUxNDU3ZDM5MWI0MDIxYyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8xNGM2NjQwZmFkODM0ZjFmYmQ1MGZlZWZlYzM4OWI4MSA9ICQoJzxkaXYgaWQ9Imh0bWxfMTRjNjY0MGZhZDgzNGYxZmJkNTBmZWVmZWMzODliODEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNoaW5hdG93bixHcmFuZ2UgUGFyayxLZW5zaW5ndG9uIE1hcmtldCwgRG93bnRvd24gVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMTlmMDhhNGUxNGY2NDA0OWJlMTQ1N2QzOTFiNDAyMWMuc2V0Q29udGVudChodG1sXzE0YzY2NDBmYWQ4MzRmMWZiZDUwZmVlZmVjMzg5YjgxKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2U0MTZjMDg4ZDhlNjRjMDRiMGQzOWQ4OTczN2JjYzY0LmJpbmRQb3B1cChwb3B1cF8xOWYwOGE0ZTE0ZjY0MDQ5YmUxNDU3ZDM5MWI0MDIxYyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl80OWYzZGRjZDAwNzc0N2EyODNmNjdmMDczNTY2ODFhZSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjYyODk0NjcsLTc5LjM5NDQxOTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfN2Q3ZWI3NWViY2QxNDM5ZGJhOWRkZjViMjA1NjkyNDYgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNzNmMTRkNThlZjM2NDJkYzkzNzQ1OWM3OGYxNmY2MzQgPSAkKCc8ZGl2IGlkPSJodG1sXzczZjE0ZDU4ZWYzNjQyZGM5Mzc0NTljNzhmMTZmNjM0IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5DTiBUb3dlcixCYXRodXJzdCBRdWF5LElzbGFuZCBhaXJwb3J0LEhhcmJvdXJmcm9udCBXZXN0LEtpbmcgYW5kIFNwYWRpbmEsUmFpbHdheSBMYW5kcyxTb3V0aCBOaWFnYXJhLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF83ZDdlYjc1ZWJjZDE0MzlkYmE5ZGRmNWIyMDU2OTI0Ni5zZXRDb250ZW50KGh0bWxfNzNmMTRkNThlZjM2NDJkYzkzNzQ1OWM3OGYxNmY2MzQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNDlmM2RkY2QwMDc3NDdhMjgzZjY3ZjA3MzU2NjgxYWUuYmluZFBvcHVwKHBvcHVwXzdkN2ViNzVlYmNkMTQzOWRiYTlkZGY1YjIwNTY5MjQ2KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzAxY2Q1ZmZmODUyMTQ1YjZhMzAxYmRkZGE4NzdmZDUyID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ2NDM1MiwtNzkuMzc0ODQ1OTk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfY2ZhODdiNzI4YTVlNDQxNDkxZmVhNGFiNjdiMzdmNDkgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNmU1YzgyYTRiODI2NDlhYTk4MWVjOGFiNzAxOTc1YTcgPSAkKCc8ZGl2IGlkPSJodG1sXzZlNWM4MmE0YjgyNjQ5YWE5ODFlYzhhYjcwMTk3NWE3IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5TdG4gQSBQTyBCb3hlcyAyNSBUaGUgRXNwbGFuYWRlLCBEb3dudG93biBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9jZmE4N2I3MjhhNWU0NDE0OTFmZWE0YWI2N2IzN2Y0OS5zZXRDb250ZW50KGh0bWxfNmU1YzgyYTRiODI2NDlhYTk4MWVjOGFiNzAxOTc1YTcpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMDFjZDVmZmY4NTIxNDViNmEzMDFiZGRkYTg3N2ZkNTIuYmluZFBvcHVwKHBvcHVwX2NmYTg3YjcyOGE1ZTQ0MTQ5MWZlYTRhYjY3YjM3ZjQ5KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2NlNzVhY2UxMzE3MzQyMzg4YmRkMjBkMmJiNDRlNGU0ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ4NDI5MiwtNzkuMzgyMjgwMl0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF83NjNjNzc0MDg1ZjI0NWQ3YmRhMjUzZDViOGU0OGU2YyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8yZWMyZTI2N2VlNjM0YzE1YmJkYTUxMzg0YjJhMTNkNCA9ICQoJzxkaXYgaWQ9Imh0bWxfMmVjMmUyNjdlZTYzNGMxNWJiZGE1MTM4NGIyYTEzZDQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkZpcnN0IENhbmFkaWFuIFBsYWNlLFVuZGVyZ3JvdW5kIGNpdHksIERvd250b3duIFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzc2M2M3NzQwODVmMjQ1ZDdiZGEyNTNkNWI4ZTQ4ZTZjLnNldENvbnRlbnQoaHRtbF8yZWMyZTI2N2VlNjM0YzE1YmJkYTUxMzg0YjJhMTNkNCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9jZTc1YWNlMTMxNzM0MjM4OGJkZDIwZDJiYjQ0ZTRlNC5iaW5kUG9wdXAocG9wdXBfNzYzYzc3NDA4NWYyNDVkN2JkYTI1M2Q1YjhlNDhlNmMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMzNhZTM2M2UxNTZlNDJlZGIzNzQ0OWIwNTQzZDdmMDYgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Njk1NDIsLTc5LjQyMjU2MzddLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfOGU3ZDExMjZiZDAzNDc2ZmE1MzE5ZTVkMjJkMzZhYTkgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfZDAwYzBhYTdkYjJmNDQ2M2I2YTA1NGU5NGViZjRiNmQgPSAkKCc8ZGl2IGlkPSJodG1sX2QwMGMwYWE3ZGIyZjQ0NjNiNmEwNTRlOTRlYmY0YjZkIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5DaHJpc3RpZSwgRG93bnRvd24gVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfOGU3ZDExMjZiZDAzNDc2ZmE1MzE5ZTVkMjJkMzZhYTkuc2V0Q29udGVudChodG1sX2QwMGMwYWE3ZGIyZjQ0NjNiNmEwNTRlOTRlYmY0YjZkKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzMzYWUzNjNlMTU2ZTQyZWRiMzc0NDliMDU0M2Q3ZjA2LmJpbmRQb3B1cChwb3B1cF84ZTdkMTEyNmJkMDM0NzZmYTUzMTllNWQyMmQzNmFhOSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl82MTI1NDJiNjEyMmU0MTRhYWQ5N2U3NzI3Mjk2YTIzZSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY2OTAwNTEwMDAwMDAxLC03OS40NDIyNTkzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2M3OTUyMGM5MTMwYzQ5YzE4Y2IwMDAyNjRlN2QwYmM4ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2M3ZjVmNjUyOTFiZDRjZTU4MmFkZDI1ODAwYWI5YWZlID0gJCgnPGRpdiBpZD0iaHRtbF9jN2Y1ZjY1MjkxYmQ0Y2U1ODJhZGQyNTgwMGFiOWFmZSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RG92ZXJjb3VydCBWaWxsYWdlLER1ZmZlcmluLCBXZXN0IFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2M3OTUyMGM5MTMwYzQ5YzE4Y2IwMDAyNjRlN2QwYmM4LnNldENvbnRlbnQoaHRtbF9jN2Y1ZjY1MjkxYmQ0Y2U1ODJhZGQyNTgwMGFiOWFmZSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl82MTI1NDJiNjEyMmU0MTRhYWQ5N2U3NzI3Mjk2YTIzZS5iaW5kUG9wdXAocG9wdXBfYzc5NTIwYzkxMzBjNDljMThjYjAwMDI2NGU3ZDBiYzgpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfYTYyNDc5NjBjYTBiNDI5ZGI0Njk4NDc4MWIzMmZhNmMgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDc5MjY3MDAwMDAwMDYsLTc5LjQxOTc0OTddLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNzRmMzZhYTI5OTE2NDYxNTlmYWU1OGI3Y2E1NjlmNDAgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNzU3YmUzNTljZDFmNDExZjgyNWQzYzJjYTM1N2VjNzEgPSAkKCc8ZGl2IGlkPSJodG1sXzc1N2JlMzU5Y2QxZjQxMWY4MjVkM2MyY2EzNTdlYzcxIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5MaXR0bGUgUG9ydHVnYWwsVHJpbml0eSwgV2VzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF83NGYzNmFhMjk5MTY0NjE1OWZhZTU4YjdjYTU2OWY0MC5zZXRDb250ZW50KGh0bWxfNzU3YmUzNTljZDFmNDExZjgyNWQzYzJjYTM1N2VjNzEpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYTYyNDc5NjBjYTBiNDI5ZGI0Njk4NDc4MWIzMmZhNmMuYmluZFBvcHVwKHBvcHVwXzc0ZjM2YWEyOTkxNjQ2MTU5ZmFlNThiN2NhNTY5ZjQwKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzBkNDYwYWQ2ZjU2NjQ0ZTI4ZDgxZDdkZjUzOTY5Nzk3ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjM2ODQ3MiwtNzkuNDI4MTkxNDAwMDAwMDJdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiYmx1ZSIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMzMTg2Y2MiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfZTJlMjQ0OGUxYjUwNGQxYmI4ZjA4YjVkNTBmNWRiN2UpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMDA2NWZhYjhjMWRjNGI1MGE4ZDk4MThlZmM3MjZlYTggPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYjc0NWM5YWI3YjZiNDRkODg1ZWZkODNkZDFhODVkMGIgPSAkKCc8ZGl2IGlkPSJodG1sX2I3NDVjOWFiN2I2YjQ0ZDg4NWVmZDgzZGQxYTg1ZDBiIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Ccm9ja3RvbixFeGhpYml0aW9uIFBsYWNlLFBhcmtkYWxlIFZpbGxhZ2UsIFdlc3QgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMDA2NWZhYjhjMWRjNGI1MGE4ZDk4MThlZmM3MjZlYTguc2V0Q29udGVudChodG1sX2I3NDVjOWFiN2I2YjQ0ZDg4NWVmZDgzZGQxYTg1ZDBiKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzBkNDYwYWQ2ZjU2NjQ0ZTI4ZDgxZDdkZjUzOTY5Nzk3LmJpbmRQb3B1cChwb3B1cF8wMDY1ZmFiOGMxZGM0YjUwYThkOTgxOGVmYzcyNmVhOCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl81MThkMWI0NTk0M2Y0NjU4YWMyMDg1MmZkODM4OGVhOSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY2MTYwODMsLTc5LjQ2NDc2MzI5OTk5OTk5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2YwYTg0Njc5MzhkNzQ4Zjc4MzY3MDUyMjE3YTY1ZDM2ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzg4MGE2N2IzNmY5YzQyNjg5ZTgyY2VmY2NjNjA0YWE1ID0gJCgnPGRpdiBpZD0iaHRtbF84ODBhNjdiMzZmOWM0MjY4OWU4MmNlZmNjYzYwNGFhNSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+SGlnaCBQYXJrLFRoZSBKdW5jdGlvbiBTb3V0aCwgV2VzdCBUb3JvbnRvPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9mMGE4NDY3OTM4ZDc0OGY3ODM2NzA1MjIxN2E2NWQzNi5zZXRDb250ZW50KGh0bWxfODgwYTY3YjM2ZjljNDI2ODllODJjZWZjY2M2MDRhYTUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNTE4ZDFiNDU5NDNmNDY1OGFjMjA4NTJmZDgzODhlYTkuYmluZFBvcHVwKHBvcHVwX2YwYTg0Njc5MzhkNzQ4Zjc4MzY3MDUyMjE3YTY1ZDM2KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzQ0MTZjNGJkNmEzYzQyY2ZhYjk2ZWFmOTE0ZmQyYmJkID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ4OTU5NywtNzkuNDU2MzI1XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzc4ZjhhMGMwYzEzZDQ2ZDU4YjUwOTY4OTU2Y2YxMjhhID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2M3MzViM2QwOTU2NzQ4Mzc5NmRmYmI2ZGUxNTdlNGQyID0gJCgnPGRpdiBpZD0iaHRtbF9jNzM1YjNkMDk1Njc0ODM3OTZkZmJiNmRlMTU3ZTRkMiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+UGFya2RhbGUsUm9uY2VzdmFsbGVzLCBXZXN0IFRvcm9udG88L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzc4ZjhhMGMwYzEzZDQ2ZDU4YjUwOTY4OTU2Y2YxMjhhLnNldENvbnRlbnQoaHRtbF9jNzM1YjNkMDk1Njc0ODM3OTZkZmJiNmRlMTU3ZTRkMik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl80NDE2YzRiZDZhM2M0MmNmYWI5NmVhZjkxNGZkMmJiZC5iaW5kUG9wdXAocG9wdXBfNzhmOGEwYzBjMTNkNDZkNThiNTA5Njg5NTZjZjEyOGEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfYWI1MzgxNDk4NjQwNDJiODlmMGE2NjJhNGE4ODc2NTggPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTE1NzA2LC03OS40ODQ0NDk5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogImJsdWUiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjMzE4NmNjIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwX2UyZTI0NDhlMWI1MDRkMWJiOGYwOGI1ZDUwZjVkYjdlKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzk3M2IzZWUwZjNkYjRjM2ZhOWE2MDJkYTc2OThlMDU3ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzA4OTA2OWVmYTFkODRkZjU4MDIzNTI1OTIxODFmMmI5ID0gJCgnPGRpdiBpZD0iaHRtbF8wODkwNjllZmExZDg0ZGY1ODAyMzUyNTkyMTgxZjJiOSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+UnVubnltZWRlLFN3YW5zZWEsIFdlc3QgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfOTczYjNlZTBmM2RiNGMzZmE5YTYwMmRhNzY5OGUwNTcuc2V0Q29udGVudChodG1sXzA4OTA2OWVmYTFkODRkZjU4MDIzNTI1OTIxODFmMmI5KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2FiNTM4MTQ5ODY0MDQyYjg5ZjBhNjYyYTRhODg3NjU4LmJpbmRQb3B1cChwb3B1cF85NzNiM2VlMGYzZGI0YzNmYTlhNjAyZGE3Njk4ZTA1Nyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8zODEzOGY5MTIzNDU0MDQwYTYwM2JlZTJkZGIxODA3OSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY2Mjc0MzksLTc5LjMyMTU1OF0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICJibHVlIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzMxODZjYyIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF9lMmUyNDQ4ZTFiNTA0ZDFiYjhmMDhiNWQ1MGY1ZGI3ZSk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8zOGZmYTJjZWFjNmI0YTE1ODk3YTM3ZDAyMTUwYWVmYyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF84OTUwZmM2MmNlYjY0NDZiODA2OTQ3NDUwMzY3MDdhYyA9ICQoJzxkaXYgaWQ9Imh0bWxfODk1MGZjNjJjZWI2NDQ2YjgwNjk0NzQ1MDM2NzA3YWMiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkJ1c2luZXNzIFJlcGx5IE1haWwgUHJvY2Vzc2luZyBDZW50cmUgOTY5IEVhc3Rlcm4sIEVhc3QgVG9yb250bzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMzhmZmEyY2VhYzZiNGExNTg5N2EzN2QwMjE1MGFlZmMuc2V0Q29udGVudChodG1sXzg5NTBmYzYyY2ViNjQ0NmI4MDY5NDc0NTAzNjcwN2FjKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzM4MTM4ZjkxMjM0NTQwNDBhNjAzYmVlMmRkYjE4MDc5LmJpbmRQb3B1cChwb3B1cF8zOGZmYTJjZWFjNmI0YTE1ODk3YTM3ZDAyMTUwYWVmYyk7CgogICAgICAgICAgICAKICAgICAgICAKPC9zY3JpcHQ+\" style=\"position:absolute;width:100%;height:100%;left:0;top:0;border:none !important;\" allowfullscreen webkitallowfullscreen mozallowfullscreen></iframe></div></div>"
],
"text/plain": [
"<folium.folium.Map at 0x7f50cb33f0f0>"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# create map of New York using latitude and longitude values\n",
"map_toronto = folium.Map(location=[latitude, longitude], zoom_start=10)\n",
"\n",
"# add markers to map\n",
"for lat, lng, borough, neighborhood in zip(toronto_df['Latitude'], toronto_df['Longitude'], toronto_df['Borough'], toronto_df['Neighborhood']):\n",
" label = '{}, {}'.format(neighborhood, borough)\n",
" label = folium.Popup(label, parse_html=True)\n",
" folium.CircleMarker(\n",
" [lat, lng],\n",
" radius=5,\n",
" popup=label,\n",
" color='blue',\n",
" fill=True,\n",
" fill_color='#3186cc',\n",
" fill_opacity=0.7,\n",
" parse_html=False).add_to(map_toronto) \n",
" \n",
"map_toronto"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Define Foursquare Credentials and Version"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Your credentails:\n",
"CLIENT_ID: UGHFMNO1HCOWOZTT0W5MXN0CKUIFZZU2OXV1KIM1CUL1KX31\n",
"CLIENT_SECRET:O0WTNIHI0W2Q1GUUJ34U0KCB3GBRD4OXCESMXMTVBB1SRCJV\n"
]
}
],
"source": [
"CLIENT_ID = 'UGHFMNO1HCOWOZTT0W5MXN0CKUIFZZU2OXV1KIM1CUL1KX31' # your Foursquare ID\n",
"CLIENT_SECRET = 'O0WTNIHI0W2Q1GUUJ34U0KCB3GBRD4OXCESMXMTVBB1SRCJV' # your Foursquare Secret\n",
"VERSION = '201906020' #Foursquare API version\n",
"\n",
"print('Your credentails:')\n",
"print('CLIENT_ID: ' + CLIENT_ID)\n",
"print('CLIENT_SECRET:' + CLIENT_SECRET)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Get recommend places inside or near each Borough in Toronto Central"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"def getNearbyVenues(names, latitudes, longitudes, postal_code, borough, radius=500, LIMIT=100):\n",
" \n",
" venues_list=[]\n",
" for name, lat, lng, post, borough in zip(names, latitudes, longitudes, postal_code, borough): \n",
" # create the API request URL\n",
" url = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&v={}&ll={},{}&radius={}&limit={}'.format(\n",
" CLIENT_ID, \n",
" CLIENT_SECRET, \n",
" VERSION, \n",
" lat, \n",
" lng, \n",
" radius, \n",
" LIMIT)\n",
" \n",
" # make the GET request\n",
" results = requests.get(url).json()[\"response\"]['groups'][0]['items']\n",
" \n",
" # return only relevant information for each nearby venue\n",
" venues_list.append([(\n",
" post,\n",
" borough,\n",
" name, \n",
" lat, \n",
" lng, \n",
" v['venue']['name'], \n",
" v['venue']['location']['lat'], \n",
" v['venue']['location']['lng'], \n",
" v['venue']['categories'][0]['name']) for v in results])\n",
"\n",
" nearby_venues = pd.DataFrame([item for venue_list in venues_list for item in venue_list])\n",
" nearby_venues.columns = ['Postal Code',\n",
" 'Borough',\n",
" 'Neighborhood', \n",
" 'Neighborhood Latitude', \n",
" 'Neighborhood Longitude', \n",
" 'Venue', \n",
" 'Venue Latitude', \n",
" 'Venue Longitude', \n",
" 'Venue Category']\n",
" \n",
" return(nearby_venues)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"venues_df = getNearbyVenues(names=toronto_df['Neighborhood'],\n",
" latitudes=toronto_df['Latitude'],\n",
" longitudes=toronto_df['Longitude'],\n",
" postal_code=toronto_df['Postal Code'],\n",
" borough=toronto_df['Borough']\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal Code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" <th>Neighborhood Latitude</th>\n",
" <th>Neighborhood Longitude</th>\n",
" <th>Venue</th>\n",
" <th>Venue Latitude</th>\n",
" <th>Venue Longitude</th>\n",
" <th>Venue Category</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>43.676357</td>\n",
" <td>-79.293031</td>\n",
" <td>Glen Manor Ravine</td>\n",
" <td>43.676821</td>\n",
" <td>-79.293942</td>\n",
" <td>Trail</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>43.676357</td>\n",
" <td>-79.293031</td>\n",
" <td>The Big Carrot Natural Food Market</td>\n",
" <td>43.678879</td>\n",
" <td>-79.297734</td>\n",
" <td>Health Food Store</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>43.676357</td>\n",
" <td>-79.293031</td>\n",
" <td>Grover Pub and Grub</td>\n",
" <td>43.679181</td>\n",
" <td>-79.297215</td>\n",
" <td>Pub</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>43.676357</td>\n",
" <td>-79.293031</td>\n",
" <td>Glen Stewart Ravine</td>\n",
" <td>43.676300</td>\n",
" <td>-79.294784</td>\n",
" <td>Other Great Outdoors</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>43.676357</td>\n",
" <td>-79.293031</td>\n",
" <td>Upper Beaches</td>\n",
" <td>43.680563</td>\n",
" <td>-79.292869</td>\n",
" <td>Neighborhood</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postal Code Borough Neighborhood Neighborhood Latitude \\\n",
"0 M4E East Toronto The Beaches 43.676357 \n",
"1 M4E East Toronto The Beaches 43.676357 \n",
"2 M4E East Toronto The Beaches 43.676357 \n",
"3 M4E East Toronto The Beaches 43.676357 \n",
"4 M4E East Toronto The Beaches 43.676357 \n",
"\n",
" Neighborhood Longitude Venue Venue Latitude \\\n",
"0 -79.293031 Glen Manor Ravine 43.676821 \n",
"1 -79.293031 The Big Carrot Natural Food Market 43.678879 \n",
"2 -79.293031 Grover Pub and Grub 43.679181 \n",
"3 -79.293031 Glen Stewart Ravine 43.676300 \n",
"4 -79.293031 Upper Beaches 43.680563 \n",
"\n",
" Venue Longitude Venue Category \n",
"0 -79.293942 Trail \n",
"1 -79.297734 Health Food Store \n",
"2 -79.297215 Pub \n",
"3 -79.294784 Other Great Outdoors \n",
"4 -79.292869 Neighborhood "
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"venues_df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Let's find out how many unique categories can be curated from all the returned venues"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"There are 238 uniques categories.\n"
]
}
],
"source": [
"print('There are {} uniques categories.'.format(len(venues_df['Venue Category'].unique())))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Analyze Each Neighborhood"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(1700, 241)\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal Code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhoods</th>\n",
" <th>Adult Boutique</th>\n",
" <th>Afghan Restaurant</th>\n",
" <th>Airport</th>\n",
" <th>Airport Food Court</th>\n",
" <th>Airport Gate</th>\n",
" <th>Airport Lounge</th>\n",
" <th>Airport Service</th>\n",
" <th>...</th>\n",
" <th>Trail</th>\n",
" <th>Train Station</th>\n",
" <th>Vegetarian / Vegan Restaurant</th>\n",
" <th>Video Game Store</th>\n",
" <th>Video Store</th>\n",
" <th>Vietnamese Restaurant</th>\n",
" <th>Wine Bar</th>\n",
" <th>Wings Joint</th>\n",
" <th>Women's Store</th>\n",
" <th>Yoga Studio</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>1</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>5 rows × 241 columns</p>\n",
"</div>"
],
"text/plain": [
" Postal Code Borough Neighborhoods Adult Boutique Afghan Restaurant \\\n",
"0 M4E East Toronto The Beaches 0 0 \n",
"1 M4E East Toronto The Beaches 0 0 \n",
"2 M4E East Toronto The Beaches 0 0 \n",
"3 M4E East Toronto The Beaches 0 0 \n",
"4 M4E East Toronto The Beaches 0 0 \n",
"\n",
" Airport Airport Food Court Airport Gate Airport Lounge Airport Service \\\n",
"0 0 0 0 0 0 \n",
"1 0 0 0 0 0 \n",
"2 0 0 0 0 0 \n",
"3 0 0 0 0 0 \n",
"4 0 0 0 0 0 \n",
"\n",
" ... Trail Train Station Vegetarian / Vegan Restaurant Video Game Store \\\n",
"0 ... 1 0 0 0 \n",
"1 ... 0 0 0 0 \n",
"2 ... 0 0 0 0 \n",
"3 ... 0 0 0 0 \n",
"4 ... 0 0 0 0 \n",
"\n",
" Video Store Vietnamese Restaurant Wine Bar Wings Joint Women's Store \\\n",
"0 0 0 0 0 0 \n",
"1 0 0 0 0 0 \n",
"2 0 0 0 0 0 \n",
"3 0 0 0 0 0 \n",
"4 0 0 0 0 0 \n",
"\n",
" Yoga Studio \n",
"0 0 \n",
"1 0 \n",
"2 0 \n",
"3 0 \n",
"4 0 \n",
"\n",
"[5 rows x 241 columns]"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# one hot encoding\n",
"toronto_central_onehot = pd.get_dummies(venues_df[['Venue Category']], prefix=\"\", prefix_sep=\"\")\n",
"\n",
"# add postal, borough and neighborhood column back to dataframe\n",
"toronto_central_onehot['Postal Code'] = venues_df['Postal Code'] \n",
"toronto_central_onehot['Borough'] = venues_df['Borough'] \n",
"toronto_central_onehot['Neighborhoods'] = venues_df['Neighborhood'] \n",
"\n",
"# move postal, borough and neighborhood column to the first column\n",
"fixed_columns = list(toronto_central_onehot.columns[-3:]) + list(toronto_central_onehot.columns[:-3])\n",
"toronto_central_onehot = toronto_central_onehot[fixed_columns]\n",
"\n",
"print(toronto_central_onehot.shape)\n",
"toronto_central_onehot.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Get the frequency of occurance of each category in an area"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(38, 241)\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal Code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhoods</th>\n",
" <th>Adult Boutique</th>\n",
" <th>Afghan Restaurant</th>\n",
" <th>Airport</th>\n",
" <th>Airport Food Court</th>\n",
" <th>Airport Gate</th>\n",
" <th>Airport Lounge</th>\n",
" <th>Airport Service</th>\n",
" <th>...</th>\n",
" <th>Trail</th>\n",
" <th>Train Station</th>\n",
" <th>Vegetarian / Vegan Restaurant</th>\n",
" <th>Video Game Store</th>\n",
" <th>Video Store</th>\n",
" <th>Vietnamese Restaurant</th>\n",
" <th>Wine Bar</th>\n",
" <th>Wings Joint</th>\n",
" <th>Women's Store</th>\n",
" <th>Yoga Studio</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>M4E</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.20000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>M4K</td>\n",
" <td>East Toronto</td>\n",
" <td>The Danforth West,Riverdale</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.02381</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.023810</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>M4L</td>\n",
" <td>East Toronto</td>\n",
" <td>The Beaches West,India Bazaar</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.00000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>M4M</td>\n",
" <td>East Toronto</td>\n",
" <td>Studio District</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.00000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.026316</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>M4N</td>\n",
" <td>Central Toronto</td>\n",
" <td>Lawrence Park</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>...</td>\n",
" <td>0.00000</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.000000</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>5 rows × 241 columns</p>\n",
"</div>"
],
"text/plain": [
" Postal Code Borough Neighborhoods Adult Boutique \\\n",
"0 M4E East Toronto The Beaches 0.0 \n",
"1 M4K East Toronto The Danforth West,Riverdale 0.0 \n",
"2 M4L East Toronto The Beaches West,India Bazaar 0.0 \n",
"3 M4M East Toronto Studio District 0.0 \n",
"4 M4N Central Toronto Lawrence Park 0.0 \n",
"\n",
" Afghan Restaurant Airport Airport Food Court Airport Gate \\\n",
"0 0.0 0.0 0.0 0.0 \n",
"1 0.0 0.0 0.0 0.0 \n",
"2 0.0 0.0 0.0 0.0 \n",
"3 0.0 0.0 0.0 0.0 \n",
"4 0.0 0.0 0.0 0.0 \n",
"\n",
" Airport Lounge Airport Service ... Trail Train Station \\\n",
"0 0.0 0.0 ... 0.20000 0.0 \n",
"1 0.0 0.0 ... 0.02381 0.0 \n",
"2 0.0 0.0 ... 0.00000 0.0 \n",
"3 0.0 0.0 ... 0.00000 0.0 \n",
"4 0.0 0.0 ... 0.00000 0.0 \n",
"\n",
" Vegetarian / Vegan Restaurant Video Game Store Video Store \\\n",
"0 0.0 0.0 0.0 \n",
"1 0.0 0.0 0.0 \n",
"2 0.0 0.0 0.0 \n",
"3 0.0 0.0 0.0 \n",
"4 0.0 0.0 0.0 \n",
"\n",
" Vietnamese Restaurant Wine Bar Wings Joint Women's Store Yoga Studio \n",
"0 0.0 0.0 0.0 0.0 0.000000 \n",
"1 0.0 0.0 0.0 0.0 0.023810 \n",
"2 0.0 0.0 0.0 0.0 0.000000 \n",
"3 0.0 0.0 0.0 0.0 0.026316 \n",
"4 0.0 0.0 0.0 0.0 0.000000 \n",
"\n",
"[5 rows x 241 columns]"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"toronto_central_venues_freq = toronto_central_onehot.groupby(['Postal Code', 'Borough', 'Neighborhoods']).mean().reset_index()\n",
"print(toronto_central_venues_freq.shape)\n",
"toronto_central_venues_freq.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Get 10 most occurance venue types in each area"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal Code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhoods</th>\n",
" <th>1st Most Common Venue</th>\n",
" <th>2nd Most Common Venue</th>\n",
" <th>3rd Most Common Venue</th>\n",
" <th>4th Most Common Venue</th>\n",
" <th>5th Most Common Venue</th>\n",
" <th>6th Most Common Venue</th>\n",
" <th>7th Most Common Venue</th>\n",
" <th>8th Most Common Venue</th>\n",
" <th>9th Most Common Venue</th>\n",
" <th>10th Most Common Venue</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>27</th>\n",
" <td>M5V</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>CN Tower,Bathurst Quay,Island airport,Harbourf...</td>\n",
" <td>Airport Terminal</td>\n",
" <td>Airport Lounge</td>\n",
" <td>Airport Service</td>\n",
" <td>Boutique</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Boat or Ferry</td>\n",
" <td>Sculpture Garden</td>\n",
" <td>Harbor / Marina</td>\n",
" <td>Plane</td>\n",
" <td>Airport Gate</td>\n",
" </tr>\n",
" <tr>\n",
" <th>32</th>\n",
" <td>M6J</td>\n",
" <td>West Toronto</td>\n",
" <td>Little Portugal,Trinity</td>\n",
" <td>Bar</td>\n",
" <td>Asian Restaurant</td>\n",
" <td>Men's Store</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Pizza Place</td>\n",
" <td>New American Restaurant</td>\n",
" <td>Restaurant</td>\n",
" <td>Vietnamese Restaurant</td>\n",
" <td>Café</td>\n",
" <td>Cocktail Bar</td>\n",
" </tr>\n",
" <tr>\n",
" <th>34</th>\n",
" <td>M6P</td>\n",
" <td>West Toronto</td>\n",
" <td>High Park,The Junction South</td>\n",
" <td>Bar</td>\n",
" <td>Mexican Restaurant</td>\n",
" <td>Café</td>\n",
" <td>Fast Food Restaurant</td>\n",
" <td>Fried Chicken Joint</td>\n",
" <td>Bakery</td>\n",
" <td>Italian Restaurant</td>\n",
" <td>Gastropub</td>\n",
" <td>Music Venue</td>\n",
" <td>Arts &amp; Crafts Store</td>\n",
" </tr>\n",
" <tr>\n",
" <th>33</th>\n",
" <td>M6K</td>\n",
" <td>West Toronto</td>\n",
" <td>Brockton,Exhibition Place,Parkdale Village</td>\n",
" <td>Breakfast Spot</td>\n",
" <td>Café</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Yoga Studio</td>\n",
" <td>Intersection</td>\n",
" <td>Performing Arts Venue</td>\n",
" <td>Caribbean Restaurant</td>\n",
" <td>Stadium</td>\n",
" <td>Restaurant</td>\n",
" <td>Bar</td>\n",
" </tr>\n",
" <tr>\n",
" <th>35</th>\n",
" <td>M6R</td>\n",
" <td>West Toronto</td>\n",
" <td>Parkdale,Roncesvalles</td>\n",
" <td>Breakfast Spot</td>\n",
" <td>Gift Shop</td>\n",
" <td>Bookstore</td>\n",
" <td>Bank</td>\n",
" <td>Bar</td>\n",
" <td>Movie Theater</td>\n",
" <td>Restaurant</td>\n",
" <td>Dog Run</td>\n",
" <td>Italian Restaurant</td>\n",
" <td>Dessert Shop</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postal Code Borough \\\n",
"27 M5V Downtown Toronto \n",
"32 M6J West Toronto \n",
"34 M6P West Toronto \n",
"33 M6K West Toronto \n",
"35 M6R West Toronto \n",
"\n",
" Neighborhoods 1st Most Common Venue \\\n",
"27 CN Tower,Bathurst Quay,Island airport,Harbourf... Airport Terminal \n",
"32 Little Portugal,Trinity Bar \n",
"34 High Park,The Junction South Bar \n",
"33 Brockton,Exhibition Place,Parkdale Village Breakfast Spot \n",
"35 Parkdale,Roncesvalles Breakfast Spot \n",
"\n",
" 2nd Most Common Venue 3rd Most Common Venue 4th Most Common Venue \\\n",
"27 Airport Lounge Airport Service Boutique \n",
"32 Asian Restaurant Men's Store Coffee Shop \n",
"34 Mexican Restaurant Café Fast Food Restaurant \n",
"33 Café Coffee Shop Yoga Studio \n",
"35 Gift Shop Bookstore Bank \n",
"\n",
" 5th Most Common Venue 6th Most Common Venue 7th Most Common Venue \\\n",
"27 Coffee Shop Boat or Ferry Sculpture Garden \n",
"32 Pizza Place New American Restaurant Restaurant \n",
"34 Fried Chicken Joint Bakery Italian Restaurant \n",
"33 Intersection Performing Arts Venue Caribbean Restaurant \n",
"35 Bar Movie Theater Restaurant \n",
"\n",
" 8th Most Common Venue 9th Most Common Venue 10th Most Common Venue \n",
"27 Harbor / Marina Plane Airport Gate \n",
"32 Vietnamese Restaurant Café Cocktail Bar \n",
"34 Gastropub Music Venue Arts & Crafts Store \n",
"33 Stadium Restaurant Bar \n",
"35 Dog Run Italian Restaurant Dessert Shop "
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"num_top_venues = 10\n",
"\n",
"indicators = ['st', 'nd', 'rd']\n",
"\n",
"# create columns according to number of top venues\n",
"area_columns = ['Postal Code', 'Borough', 'Neighborhoods']\n",
"freq_columns = []\n",
"for ind in np.arange(num_top_venues):\n",
" try:\n",
" freq_columns.append('{}{} Most Common Venue'.format(ind+1, indicators[ind]))\n",
" except:\n",
" freq_columns.append('{}th Most Common Venue'.format(ind+1))\n",
"columns = area_columns+freq_columns\n",
"# create a new dataframe\n",
"neighborhoods_venues_sorted = pd.DataFrame(columns=columns)\n",
"neighborhoods_venues_sorted['Postal Code'] = toronto_central_venues_freq['Postal Code']\n",
"neighborhoods_venues_sorted['Borough'] = toronto_central_venues_freq['Borough']\n",
"neighborhoods_venues_sorted['Neighborhoods'] = toronto_central_venues_freq['Neighborhoods']\n",
"\n",
"for ind in np.arange(toronto_central_venues_freq.shape[0]):\n",
" row_categories = toronto_central_venues_freq.iloc[ind, :].iloc[3:]\n",
" row_categories_sorted = row_categories.sort_values(ascending=False)\n",
" neighborhoods_venues_sorted.iloc[ind, 3:] = row_categories_sorted.index.values[0:num_top_venues]\n",
"\n",
"neighborhoods_venues_sorted.sort_values(freq_columns, inplace=True)\n",
"neighborhoods_venues_sorted.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Clustering Neighborhoods"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/jupyterlab/conda/lib/python3.6/site-packages/ipykernel_launcher.py:8: SettingWithCopyWarning: \n",
"A value is trying to be set on a copy of a slice from a DataFrame.\n",
"Try using .loc[row_indexer,col_indexer] = value instead\n",
"\n",
"See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy\n",
" \n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Postal Code</th>\n",
" <th>Borough</th>\n",
" <th>Neighborhood</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" <th>Cluster</th>\n",
" <th>1st Most Common Venue</th>\n",
" <th>2nd Most Common Venue</th>\n",
" <th>3rd Most Common Venue</th>\n",
" <th>4th Most Common Venue</th>\n",
" <th>5th Most Common Venue</th>\n",
" <th>6th Most Common Venue</th>\n",
" <th>7th Most Common Venue</th>\n",
" <th>8th Most Common Venue</th>\n",
" <th>9th Most Common Venue</th>\n",
" <th>10th Most Common Venue</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>64</th>\n",
" <td>M5P</td>\n",
" <td>Central Toronto</td>\n",
" <td>Forest Hill North,Forest Hill West</td>\n",
" <td>43.696948</td>\n",
" <td>-79.411307</td>\n",
" <td>0</td>\n",
" <td>Jewelry Store</td>\n",
" <td>Trail</td>\n",
" <td>Park</td>\n",
" <td>Sushi Restaurant</td>\n",
" <td>Donut Shop</td>\n",
" <td>Dumpling Restaurant</td>\n",
" <td>Eastern European Restaurant</td>\n",
" <td>Electronics Store</td>\n",
" <td>Ethiopian Restaurant</td>\n",
" <td>Yoga Studio</td>\n",
" </tr>\n",
" <tr>\n",
" <th>50</th>\n",
" <td>M4W</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Rosedale</td>\n",
" <td>43.679563</td>\n",
" <td>-79.377529</td>\n",
" <td>0</td>\n",
" <td>Park</td>\n",
" <td>Playground</td>\n",
" <td>Trail</td>\n",
" <td>Dog Run</td>\n",
" <td>Fish &amp; Chips Shop</td>\n",
" <td>Filipino Restaurant</td>\n",
" <td>Fast Food Restaurant</td>\n",
" <td>Farmers Market</td>\n",
" <td>Falafel Restaurant</td>\n",
" <td>Event Space</td>\n",
" </tr>\n",
" <tr>\n",
" <th>44</th>\n",
" <td>M4N</td>\n",
" <td>Central Toronto</td>\n",
" <td>Lawrence Park</td>\n",
" <td>43.728020</td>\n",
" <td>-79.388790</td>\n",
" <td>0</td>\n",
" <td>Park</td>\n",
" <td>Swim School</td>\n",
" <td>Bus Line</td>\n",
" <td>Yoga Studio</td>\n",
" <td>Doner Restaurant</td>\n",
" <td>Fish &amp; Chips Shop</td>\n",
" <td>Filipino Restaurant</td>\n",
" <td>Fast Food Restaurant</td>\n",
" <td>Farmers Market</td>\n",
" <td>Falafel Restaurant</td>\n",
" </tr>\n",
" <tr>\n",
" <th>63</th>\n",
" <td>M5N</td>\n",
" <td>Central Toronto</td>\n",
" <td>Roselawn</td>\n",
" <td>43.711695</td>\n",
" <td>-79.416936</td>\n",
" <td>1</td>\n",
" <td>Ice Cream Shop</td>\n",
" <td>Garden</td>\n",
" <td>Yoga Studio</td>\n",
" <td>Doner Restaurant</td>\n",
" <td>Fish &amp; Chips Shop</td>\n",
" <td>Filipino Restaurant</td>\n",
" <td>Fast Food Restaurant</td>\n",
" <td>Farmers Market</td>\n",
" <td>Falafel Restaurant</td>\n",
" <td>Event Space</td>\n",
" </tr>\n",
" <tr>\n",
" <th>68</th>\n",
" <td>M5V</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>CN Tower,Bathurst Quay,Island airport,Harbourf...</td>\n",
" <td>43.628947</td>\n",
" <td>-79.394420</td>\n",
" <td>2</td>\n",
" <td>Airport Terminal</td>\n",
" <td>Airport Lounge</td>\n",
" <td>Airport Service</td>\n",
" <td>Boutique</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Boat or Ferry</td>\n",
" <td>Sculpture Garden</td>\n",
" <td>Harbor / Marina</td>\n",
" <td>Plane</td>\n",
" <td>Airport Gate</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postal Code Borough \\\n",
"64 M5P Central Toronto \n",
"50 M4W Downtown Toronto \n",
"44 M4N Central Toronto \n",
"63 M5N Central Toronto \n",
"68 M5V Downtown Toronto \n",
"\n",
" Neighborhood Latitude Longitude \\\n",
"64 Forest Hill North,Forest Hill West 43.696948 -79.411307 \n",
"50 Rosedale 43.679563 -79.377529 \n",
"44 Lawrence Park 43.728020 -79.388790 \n",
"63 Roselawn 43.711695 -79.416936 \n",
"68 CN Tower,Bathurst Quay,Island airport,Harbourf... 43.628947 -79.394420 \n",
"\n",
" Cluster 1st Most Common Venue 2nd Most Common Venue 3rd Most Common Venue \\\n",
"64 0 Jewelry Store Trail Park \n",
"50 0 Park Playground Trail \n",
"44 0 Park Swim School Bus Line \n",
"63 1 Ice Cream Shop Garden Yoga Studio \n",
"68 2 Airport Terminal Airport Lounge Airport Service \n",
"\n",
" 4th Most Common Venue 5th Most Common Venue 6th Most Common Venue \\\n",
"64 Sushi Restaurant Donut Shop Dumpling Restaurant \n",
"50 Dog Run Fish & Chips Shop Filipino Restaurant \n",
"44 Yoga Studio Doner Restaurant Fish & Chips Shop \n",
"63 Doner Restaurant Fish & Chips Shop Filipino Restaurant \n",
"68 Boutique Coffee Shop Boat or Ferry \n",
"\n",
" 7th Most Common Venue 8th Most Common Venue 9th Most Common Venue \\\n",
"64 Eastern European Restaurant Electronics Store Ethiopian Restaurant \n",
"50 Fast Food Restaurant Farmers Market Falafel Restaurant \n",
"44 Filipino Restaurant Fast Food Restaurant Farmers Market \n",
"63 Fast Food Restaurant Farmers Market Falafel Restaurant \n",
"68 Sculpture Garden Harbor / Marina Plane \n",
"\n",
" 10th Most Common Venue \n",
"64 Yoga Studio \n",
"50 Event Space \n",
"44 Falafel Restaurant \n",
"63 Event Space \n",
"68 Airport Gate "
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"k_clusters = 3\n",
"\n",
"toronto_central_venues_freq_clustering = toronto_central_venues_freq.drop(['Postal Code', 'Borough', 'Neighborhoods'], 1)\n",
"\n",
"kmeans = KMeans(n_clusters=k_clusters, random_state=0, n_jobs=-1).fit(toronto_central_venues_freq_clustering)\n",
"\n",
"toronto_central_clustered_df = toronto_df\n",
"toronto_central_clustered_df['Cluster'] = kmeans.labels_\n",
"\n",
"toronto_central_clustered_df = toronto_central_clustered_df.join(neighborhoods_venues_sorted.drop(['Borough', 'Neighborhoods'], 1).set_index('Postal Code'), on='Postal Code')\n",
"toronto_central_clustered_df.sort_values(['Cluster'] + freq_columns, inplace=True)\n",
"toronto_central_clustered_df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Cluster Map"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div style=\"width:100%;\"><div style=\"position:relative;width:100%;height:0;padding-bottom:60%;\"><iframe src=\"data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+CjxoZWFkPiAgICAKICAgIDxtZXRhIGh0dHAtZXF1aXY9ImNvbnRlbnQtdHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PVVURi04IiAvPgogICAgPHNjcmlwdD5MX1BSRUZFUl9DQU5WQVMgPSBmYWxzZTsgTF9OT19UT1VDSCA9IGZhbHNlOyBMX0RJU0FCTEVfM0QgPSBmYWxzZTs8L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2xlYWZsZXRAMS4yLjAvZGlzdC9sZWFmbGV0LmpzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2FqYXguZ29vZ2xlYXBpcy5jb20vYWpheC9saWJzL2pxdWVyeS8xLjExLjEvanF1ZXJ5Lm1pbi5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS9ib290c3RyYXAvMy4yLjAvanMvYm9vdHN0cmFwLm1pbi5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS9hamF4L2xpYnMvTGVhZmxldC5hd2Vzb21lLW1hcmtlcnMvMi4wLjIvbGVhZmxldC5hd2Vzb21lLW1hcmtlcnMuanMiPjwvc2NyaXB0PgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2xlYWZsZXRAMS4yLjAvZGlzdC9sZWFmbGV0LmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL21heGNkbi5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC8zLjIuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiLz4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS9ib290c3RyYXAvMy4yLjAvY3NzL2Jvb3RzdHJhcC10aGVtZS5taW4uY3NzIi8+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vbWF4Y2RuLmJvb3RzdHJhcGNkbi5jb20vZm9udC1hd2Vzb21lLzQuNi4zL2Nzcy9mb250LWF3ZXNvbWUubWluLmNzcyIvPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9MZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy8yLjAuMi9sZWFmbGV0LmF3ZXNvbWUtbWFya2Vycy5jc3MiLz4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9yYXdnaXQuY29tL3B5dGhvbi12aXN1YWxpemF0aW9uL2ZvbGl1bS9tYXN0ZXIvZm9saXVtL3RlbXBsYXRlcy9sZWFmbGV0LmF3ZXNvbWUucm90YXRlLmNzcyIvPgogICAgPHN0eWxlPmh0bWwsIGJvZHkge3dpZHRoOiAxMDAlO2hlaWdodDogMTAwJTttYXJnaW46IDA7cGFkZGluZzogMDt9PC9zdHlsZT4KICAgIDxzdHlsZT4jbWFwIHtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MDtib3R0b206MDtyaWdodDowO2xlZnQ6MDt9PC9zdHlsZT4KICAgIAogICAgICAgICAgICA8c3R5bGU+ICNtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYgewogICAgICAgICAgICAgICAgcG9zaXRpb24gOiByZWxhdGl2ZTsKICAgICAgICAgICAgICAgIHdpZHRoIDogMTAwLjAlOwogICAgICAgICAgICAgICAgaGVpZ2h0OiAxMDAuMCU7CiAgICAgICAgICAgICAgICBsZWZ0OiAwLjAlOwogICAgICAgICAgICAgICAgdG9wOiAwLjAlOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICA8L3N0eWxlPgogICAgICAgIAo8L2hlYWQ+Cjxib2R5PiAgICAKICAgIAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJmb2xpdW0tbWFwIiBpZD0ibWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmIiA+PC9kaXY+CiAgICAgICAgCjwvYm9keT4KPHNjcmlwdD4gICAgCiAgICAKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGJvdW5kcyA9IG51bGw7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgdmFyIG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZiA9IEwubWFwKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7Y2VudGVyOiBbNDMuNjUzOTYzLC03OS4zODcyMDddLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgem9vbTogMTIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhCb3VuZHM6IGJvdW5kcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxheWVyczogW10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3b3JsZENvcHlKdW1wOiBmYWxzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNyczogTC5DUlMuRVBTRzM4NTcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciB0aWxlX2xheWVyX2UxNjVmZWVmMDgyZjRlNjFiOGM3NjgzZDU0ZjFkMDRjID0gTC50aWxlTGF5ZXIoCiAgICAgICAgICAgICAgICAnaHR0cHM6Ly97c30udGlsZS5vcGVuc3RyZWV0bWFwLm9yZy97en0ve3h9L3t5fS5wbmcnLAogICAgICAgICAgICAgICAgewogICJhdHRyaWJ1dGlvbiI6IG51bGwsCiAgImRldGVjdFJldGluYSI6IGZhbHNlLAogICJtYXhab29tIjogMTgsCiAgIm1pblpvb20iOiAxLAogICJub1dyYXAiOiBmYWxzZSwKICAic3ViZG9tYWlucyI6ICJhYmMiCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl80M2E3YTlkMTNjMDE0ZGFhYjQ3N2ZhNDExNzEzNjc2ZSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY5Njk0NzYsLTc5LjQxMTMwNzIwMDAwMDAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzVmYWJkZTFlMWRkNTRkZGNhNmUzOGRkMzVjZGU3N2YxID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzU1MTNiNDY3MjYyZTQwNGU4NDIxNjEwYzQ4N2I4OTJlID0gJCgnPGRpdiBpZD0iaHRtbF81NTEzYjQ2NzI2MmU0MDRlODQyMTYxMGM0ODdiODkyZSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2VudHJhbCBUb3JvbnRvIChNNVApOiBGb3Jlc3QgSGlsbCBOb3J0aCxGb3Jlc3QgSGlsbCBXZXN0IC0gQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF81ZmFiZGUxZTFkZDU0ZGRjYTZlMzhkZDM1Y2RlNzdmMS5zZXRDb250ZW50KGh0bWxfNTUxM2I0NjcyNjJlNDA0ZTg0MjE2MTBjNDg3Yjg5MmUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNDNhN2E5ZDEzYzAxNGRhYWI0NzdmYTQxMTcxMzY3NmUuYmluZFBvcHVwKHBvcHVwXzVmYWJkZTFlMWRkNTRkZGNhNmUzOGRkMzVjZGU3N2YxKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2YxODVlMjA1NDI5MjRmODRiNWE3YTI5OThhZjJkNzVlID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjc5NTYyNiwtNzkuMzc3NTI5NDAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNTBiOGY0YzQzZDA2NGExNmIxMmI0MDZlMTFhMDYxNTMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfOGRhMDZjZjU1OWU4NGM5ODg0Mzk0N2Y0YTk4NjVkODEgPSAkKCc8ZGl2IGlkPSJodG1sXzhkYTA2Y2Y1NTllODRjOTg4NDM5NDdmNGE5ODY1ZDgxIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Eb3dudG93biBUb3JvbnRvIChNNFcpOiBSb3NlZGFsZSAtIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNTBiOGY0YzQzZDA2NGExNmIxMmI0MDZlMTFhMDYxNTMuc2V0Q29udGVudChodG1sXzhkYTA2Y2Y1NTllODRjOTg4NDM5NDdmNGE5ODY1ZDgxKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2YxODVlMjA1NDI5MjRmODRiNWE3YTI5OThhZjJkNzVlLmJpbmRQb3B1cChwb3B1cF81MGI4ZjRjNDNkMDY0YTE2YjEyYjQwNmUxMWEwNjE1Myk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9mNzdjMzBlMWQzNzQ0ZTNkODU5ZWY1YWNkZWE1OGViYiA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjcyODAyMDUsLTc5LjM4ODc5MDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZDY2MjYxZDZkNDdiNDM2MGJlNjc5N2E1YTAxMmNhNWIgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYzU0ZGZkMjBmMjBlNDg3MGJlMTcwODBhYjBiMWUwM2UgPSAkKCc8ZGl2IGlkPSJodG1sX2M1NGRmZDIwZjIwZTQ4NzBiZTE3MDgwYWIwYjFlMDNlIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5DZW50cmFsIFRvcm9udG8gKE00Tik6IExhd3JlbmNlIFBhcmsgLSBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2Q2NjI2MWQ2ZDQ3YjQzNjBiZTY3OTdhNWEwMTJjYTViLnNldENvbnRlbnQoaHRtbF9jNTRkZmQyMGYyMGU0ODcwYmUxNzA4MGFiMGIxZTAzZSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9mNzdjMzBlMWQzNzQ0ZTNkODU5ZWY1YWNkZWE1OGViYi5iaW5kUG9wdXAocG9wdXBfZDY2MjYxZDZkNDdiNDM2MGJlNjc5N2E1YTAxMmNhNWIpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfN2RjN2RmZTE1ZWQ5NGNhZmE0NzRjMDQ1NTRhYjdhOGUgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MTE2OTQ4LC03OS40MTY5MzU1OTk5OTk5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODAwMGZmIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwMDBmZiIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9lZTgzMDBhMjJmYjM0MDI5ODViYWQwZDc4OWEzYTE0OSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9hMzBlYTYxYjVhODU0MDMzYmM5YWIwOWE2MjIwZGFhOSA9ICQoJzxkaXYgaWQ9Imh0bWxfYTMwZWE2MWI1YTg1NDAzM2JjOWFiMDlhNjIyMGRhYTkiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNlbnRyYWwgVG9yb250byAoTTVOKTogUm9zZWxhd24gLSBDbHVzdGVyIDE8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2VlODMwMGEyMmZiMzQwMjk4NWJhZDBkNzg5YTNhMTQ5LnNldENvbnRlbnQoaHRtbF9hMzBlYTYxYjVhODU0MDMzYmM5YWIwOWE2MjIwZGFhOSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl83ZGM3ZGZlMTVlZDk0Y2FmYTQ3NGMwNDU1NGFiN2E4ZS5iaW5kUG9wdXAocG9wdXBfZWU4MzAwYTIyZmIzNDAyOTg1YmFkMGQ3ODlhM2ExNDkpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMWIzNmUzNDYxN2QwNGE3OGEzZjZmNGQxZDdjODkxYWQgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Mjg5NDY3LC03OS4zOTQ0MTk5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzQyNTlhZGU1YmZiMjQ1Yzk5MTk0OGU4MTk3ZjQzODUzID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzQxZGIzNTNhMzQxNjQ0YjZhZGI2YzFhNDk3N2E0NTUwID0gJCgnPGRpdiBpZD0iaHRtbF80MWRiMzUzYTM0MTY0NGI2YWRiNmMxYTQ5NzdhNDU1MCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RG93bnRvd24gVG9yb250byAoTTVWKTogQ04gVG93ZXIsQmF0aHVyc3QgUXVheSxJc2xhbmQgYWlycG9ydCxIYXJib3VyZnJvbnQgV2VzdCxLaW5nIGFuZCBTcGFkaW5hLFJhaWx3YXkgTGFuZHMsU291dGggTmlhZ2FyYSAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNDI1OWFkZTViZmIyNDVjOTkxOTQ4ZTgxOTdmNDM4NTMuc2V0Q29udGVudChodG1sXzQxZGIzNTNhMzQxNjQ0YjZhZGI2YzFhNDk3N2E0NTUwKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzFiMzZlMzQ2MTdkMDRhNzhhM2Y2ZjRkMWQ3Yzg5MWFkLmJpbmRQb3B1cChwb3B1cF80MjU5YWRlNWJmYjI0NWM5OTE5NDhlODE5N2Y0Mzg1Myk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9iY2FiMDJkNzUyZGQ0NjM5YmIyMTIxNzg3OTExNjVkMyA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY0NzkyNjcwMDAwMDAwNiwtNzkuNDE5NzQ5N10sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8yMmE3NDZlZjU1MjM0Zjc5ODk1MTM3ODVmZjk1NzQ0NiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9jMTA1M2E5OGVkNTc0ZTQ3OWJlZWU4ODJmMzEwYjgxNiA9ICQoJzxkaXYgaWQ9Imh0bWxfYzEwNTNhOThlZDU3NGU0NzliZWVlODgyZjMxMGI4MTYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPldlc3QgVG9yb250byAoTTZKKTogTGl0dGxlIFBvcnR1Z2FsLFRyaW5pdHkgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzIyYTc0NmVmNTUyMzRmNzk4OTUxMzc4NWZmOTU3NDQ2LnNldENvbnRlbnQoaHRtbF9jMTA1M2E5OGVkNTc0ZTQ3OWJlZWU4ODJmMzEwYjgxNik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9iY2FiMDJkNzUyZGQ0NjM5YmIyMTIxNzg3OTExNjVkMy5iaW5kUG9wdXAocG9wdXBfMjJhNzQ2ZWY1NTIzNGY3OTg5NTEzNzg1ZmY5NTc0NDYpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNTU2NDZmYTBjYzRlNDIxNWFlZTEzZDg0YTRjOTM5MmQgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjE2MDgzLC03OS40NjQ3NjMyOTk5OTk5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8zMmNhN2NkYmExMGQ0ZmVkYmUwYzk3ZTFiOTVhMTA5MSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF84ZmQ5ZDYzMzQ4ZWY0NTU4OTg2YTZhNzg5ZDc4YmJkZCA9ICQoJzxkaXYgaWQ9Imh0bWxfOGZkOWQ2MzM0OGVmNDU1ODk4NmE2YTc4OWQ3OGJiZGQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPldlc3QgVG9yb250byAoTTZQKTogSGlnaCBQYXJrLFRoZSBKdW5jdGlvbiBTb3V0aCAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMzJjYTdjZGJhMTBkNGZlZGJlMGM5N2UxYjk1YTEwOTEuc2V0Q29udGVudChodG1sXzhmZDlkNjMzNDhlZjQ1NTg5ODZhNmE3ODlkNzhiYmRkKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzU1NjQ2ZmEwY2M0ZTQyMTVhZWUxM2Q4NGE0YzkzOTJkLmJpbmRQb3B1cChwb3B1cF8zMmNhN2NkYmExMGQ0ZmVkYmUwYzk3ZTFiOTVhMTA5MSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl85NGQ4ZmJmYzEwMjU0ZjVhOTQxZjY3YzU4YWI0NjViNCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjYzNjg0NzIsLTc5LjQyODE5MTQwMDAwMDAyXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2RmYzZlMjZhYjdmYzQ2MjViMDBmMjRiOGI1YWQxYzlhID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzBkMWFiMGI4ZjdhNjQ2ZGE5NDlmYTIxYzkxNDk0ZGMwID0gJCgnPGRpdiBpZD0iaHRtbF8wZDFhYjBiOGY3YTY0NmRhOTQ5ZmEyMWM5MTQ5NGRjMCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+V2VzdCBUb3JvbnRvIChNNkspOiBCcm9ja3RvbixFeGhpYml0aW9uIFBsYWNlLFBhcmtkYWxlIFZpbGxhZ2UgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2RmYzZlMjZhYjdmYzQ2MjViMDBmMjRiOGI1YWQxYzlhLnNldENvbnRlbnQoaHRtbF8wZDFhYjBiOGY3YTY0NmRhOTQ5ZmEyMWM5MTQ5NGRjMCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl85NGQ4ZmJmYzEwMjU0ZjVhOTQxZjY3YzU4YWI0NjViNC5iaW5kUG9wdXAocG9wdXBfZGZjNmUyNmFiN2ZjNDYyNWIwMGYyNGI4YjVhZDFjOWEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMTg4OGFjZTcyNWJkNDlhZWE1MmI3YWFmNmU1Y2Y3NmUgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDg5NTk3LC03OS40NTYzMjVdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzgwZmZiNCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiM4MGZmYjQiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfOWNjODg4NjI0MWFjNDJiOTg4ZDhjZGIwYjZjOTA5M2UgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfZTA1N2E2ZDMxMDU2NDgwNjk0ZTVkYTY2NmJlZWFkMGEgPSAkKCc8ZGl2IGlkPSJodG1sX2UwNTdhNmQzMTA1NjQ4MDY5NGU1ZGE2NjZiZWVhZDBhIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5XZXN0IFRvcm9udG8gKE02Uik6IFBhcmtkYWxlLFJvbmNlc3ZhbGxlcyAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfOWNjODg4NjI0MWFjNDJiOTg4ZDhjZGIwYjZjOTA5M2Uuc2V0Q29udGVudChodG1sX2UwNTdhNmQzMTA1NjQ4MDY5NGU1ZGE2NjZiZWVhZDBhKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzE4ODhhY2U3MjViZDQ5YWVhNTJiN2FhZjZlNWNmNzZlLmJpbmRQb3B1cChwb3B1cF85Y2M4ODg2MjQxYWM0MmI5ODhkOGNkYjBiNmM5MDkzZSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8xZmU4YjkzNzY0NzM0YWVmODEyZjYxYWQ5ZGQ1NDkwMyA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1OTUyNTUsLTc5LjM0MDkyM10sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF80MDVkOTQzYzc3N2E0ZTQxYTc2MWE5MjczZDk4MDI2ZCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9lYTRkNzUxNTAyZDU0OTlmOWM1NmM5Y2YwYWViZjA3MSA9ICQoJzxkaXYgaWQ9Imh0bWxfZWE0ZDc1MTUwMmQ1NDk5ZjljNTZjOWNmMGFlYmYwNzEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkVhc3QgVG9yb250byAoTTRNKTogU3R1ZGlvIERpc3RyaWN0IC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF80MDVkOTQzYzc3N2E0ZTQxYTc2MWE5MjczZDk4MDI2ZC5zZXRDb250ZW50KGh0bWxfZWE0ZDc1MTUwMmQ1NDk5ZjljNTZjOWNmMGFlYmYwNzEpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMWZlOGI5Mzc2NDczNGFlZjgxMmY2MWFkOWRkNTQ5MDMuYmluZFBvcHVwKHBvcHVwXzQwNWQ5NDNjNzc3YTRlNDFhNzYxYTkyNzNkOTgwMjZkKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzRjNjdjNmI0ZTdjNjRjNTE4NWE3ZGNmNWE2N2I5NjhhID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY5NTQyLC03OS40MjI1NjM3XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2VlYTQ2ZDhjNzM2ODRjNzc4NDNmNWFmMWNmMzgxZjMzID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzhiNzViNDM4ODllZjQ3MGM4Zjk4NDQwNTg5YzNlOGYxID0gJCgnPGRpdiBpZD0iaHRtbF84Yjc1YjQzODg5ZWY0NzBjOGY5ODQ0MDU4OWMzZThmMSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RG93bnRvd24gVG9yb250byAoTTZHKTogQ2hyaXN0aWUgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2VlYTQ2ZDhjNzM2ODRjNzc4NDNmNWFmMWNmMzgxZjMzLnNldENvbnRlbnQoaHRtbF84Yjc1YjQzODg5ZWY0NzBjOGY5ODQ0MDU4OWMzZThmMSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl80YzY3YzZiNGU3YzY0YzUxODVhN2RjZjVhNjdiOTY4YS5iaW5kUG9wdXAocG9wdXBfZWVhNDZkOGM3MzY4NGM3Nzg0M2Y1YWYxY2YzODFmMzMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMTc2YzcwYjk1M2UyNDdjNzgxYTRkNzkxZTA4OWIzNDIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjI2OTU2LC03OS40MDAwNDkzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzBkOGI3NjQ1NmUzNTQ1N2U4OWI1MDA0ZmQ1YjIyOTdhID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2ViYjM1YzFiODQ0MjQwODVhM2FjMWJkNzcyMGYwOWRmID0gJCgnPGRpdiBpZD0iaHRtbF9lYmIzNWMxYjg0NDI0MDg1YTNhYzFiZDc3MjBmMDlkZiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RG93bnRvd24gVG9yb250byAoTTVTKTogSGFyYm9yZCxVbml2ZXJzaXR5IG9mIFRvcm9udG8gLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzBkOGI3NjQ1NmUzNTQ1N2U4OWI1MDA0ZmQ1YjIyOTdhLnNldENvbnRlbnQoaHRtbF9lYmIzNWMxYjg0NDI0MDg1YTNhYzFiZDc3MjBmMDlkZik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8xNzZjNzBiOTUzZTI0N2M3ODFhNGQ3OTFlMDg5YjM0Mi5iaW5kUG9wdXAocG9wdXBfMGQ4Yjc2NDU2ZTM1NDU3ZTg5YjUwMDRmZDViMjI5N2EpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNGQ1OTc5OTg0ODkxNGY1Yzk0ZGZmYjRiYTZkN2M4MjIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTMyMDU3LC03OS40MDAwNDkzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzM5Y2FhMmMyYmQ3ZjRhNjBhODM5ZjRhOWQ2NTlkMTUzID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2QwZTI2NzM2ZmNmYzRlMGViZmJlNTcxNDhlNDBhMDU5ID0gJCgnPGRpdiBpZD0iaHRtbF9kMGUyNjczNmZjZmM0ZTBlYmZiZTU3MTQ4ZTQwYTA1OSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RG93bnRvd24gVG9yb250byAoTTVUKTogQ2hpbmF0b3duLEdyYW5nZSBQYXJrLEtlbnNpbmd0b24gTWFya2V0IC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8zOWNhYTJjMmJkN2Y0YTYwYTgzOWY0YTlkNjU5ZDE1My5zZXRDb250ZW50KGh0bWxfZDBlMjY3MzZmY2ZjNGUwZWJmYmU1NzE0OGU0MGEwNTkpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNGQ1OTc5OTg0ODkxNGY1Yzk0ZGZmYjRiYTZkN2M4MjIuYmluZFBvcHVwKHBvcHVwXzM5Y2FhMmMyYmQ3ZjRhNjBhODM5ZjRhOWQ2NTlkMTUzKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzIxNTdkMTk0MmYwZTQwZjJiYWFiMTA3M2MzZDVjYjcyID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQwODE1NywtNzkuMzgxNzUyMjk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzgwZmZiNCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiM4MGZmYjQiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZGNhNGNlNjQyMzViNGI2Yjk0ZjU2NzQwMjEwZjhhOGYgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMjZiMmI0MzVjODJjNDA3MGJmMTc4ODAzZjE2NDU0YjIgPSAkKCc8ZGl2IGlkPSJodG1sXzI2YjJiNDM1YzgyYzQwNzBiZjE3ODgwM2YxNjQ1NGIyIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Eb3dudG93biBUb3JvbnRvIChNNUopOiBIYXJib3VyZnJvbnQgRWFzdCxUb3JvbnRvIElzbGFuZHMsVW5pb24gU3RhdGlvbiAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfZGNhNGNlNjQyMzViNGI2Yjk0ZjU2NzQwMjEwZjhhOGYuc2V0Q29udGVudChodG1sXzI2YjJiNDM1YzgyYzQwNzBiZjE3ODgwM2YxNjQ1NGIyKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzIxNTdkMTk0MmYwZTQwZjJiYWFiMTA3M2MzZDVjYjcyLmJpbmRQb3B1cChwb3B1cF9kY2E0Y2U2NDIzNWI0YjZiOTRmNTY3NDAyMTBmOGE4Zik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8wYzJhZmU3MGViYmE0YzM0YjA2NGFlZDFhOTlhNmI1NCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1NDI1OTksLTc5LjM2MDYzNTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzgwZmZiNCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiM4MGZmYjQiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfYTQzODY0MWVlYzg3NDM5YTllMzU0MzhkYThkYzVmMTEgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNDhmMjliNzJkZGZkNDBiZWI2NTdjZjQxZGRiYTc1ZTEgPSAkKCc8ZGl2IGlkPSJodG1sXzQ4ZjI5YjcyZGRmZDQwYmViNjU3Y2Y0MWRkYmE3NWUxIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Eb3dudG93biBUb3JvbnRvIChNNUEpOiBIYXJib3VyZnJvbnQsUmVnZW50IFBhcmsgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2E0Mzg2NDFlZWM4NzQzOWE5ZTM1NDM4ZGE4ZGM1ZjExLnNldENvbnRlbnQoaHRtbF80OGYyOWI3MmRkZmQ0MGJlYjY1N2NmNDFkZGJhNzVlMSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8wYzJhZmU3MGViYmE0YzM0YjA2NGFlZDFhOTlhNmI1NC5iaW5kUG9wdXAocG9wdXBfYTQzODY0MWVlYzg3NDM5YTllMzU0MzhkYThkYzVmMTEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfZGNjMGQ5MDM5NTA3NDIxNmE3NzE0ZTA5NjlkMGU5Y2UgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTE0OTM5LC03OS4zNzU0MTc5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2ZkNWU5MDU2NjdkMzQ0OGI5MjM4NGNmMWJkMTVkMjAwID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2NmNzhiMThiMmYwMDRmOGRhODM2ODAyNDhiZTg3ZmYwID0gJCgnPGRpdiBpZD0iaHRtbF9jZjc4YjE4YjJmMDA0ZjhkYTgzNjgwMjQ4YmU4N2ZmMCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RG93bnRvd24gVG9yb250byAoTTVDKTogU3QuIEphbWVzIFRvd24gLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2ZkNWU5MDU2NjdkMzQ0OGI5MjM4NGNmMWJkMTVkMjAwLnNldENvbnRlbnQoaHRtbF9jZjc4YjE4YjJmMDA0ZjhkYTgzNjgwMjQ4YmU4N2ZmMCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9kY2MwZDkwMzk1MDc0MjE2YTc3MTRlMDk2OWQwZTljZS5iaW5kUG9wdXAocG9wdXBfZmQ1ZTkwNTY2N2QzNDQ4YjkyMzg0Y2YxYmQxNWQyMDApOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMWMyN2JlYjMzMzJiNDgyNzhiMTM4YmJhNDY3ZjJiNjMgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDcxNzY4LC03OS4zODE1NzY0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8yYTFiZTRlZDYwOWU0MDE1YTJjNzllY2U2ZmNiNzlmYiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9iYmIzZDQ1ZWQ2MDE0YWVlYWM5ZDI3N2U0NzdlNDZhYyA9ICQoJzxkaXYgaWQ9Imh0bWxfYmJiM2Q0NWVkNjAxNGFlZWFjOWQyNzdlNDc3ZTQ2YWMiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRvd250b3duIFRvcm9udG8gKE01Syk6IERlc2lnbiBFeGNoYW5nZSxUb3JvbnRvIERvbWluaW9uIENlbnRyZSAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMmExYmU0ZWQ2MDllNDAxNWEyYzc5ZWNlNmZjYjc5ZmIuc2V0Q29udGVudChodG1sX2JiYjNkNDVlZDYwMTRhZWVhYzlkMjc3ZTQ3N2U0NmFjKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzFjMjdiZWIzMzMyYjQ4Mjc4YjEzOGJiYTQ2N2YyYjYzLmJpbmRQb3B1cChwb3B1cF8yYTFiZTRlZDYwOWU0MDE1YTJjNzllY2U2ZmNiNzlmYik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9mOGM5MDMzYTk2NzM0MmZhOGE2MzEzMDY5ODQwOTk5MiA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY0ODE5ODUsLTc5LjM3OTgxNjkwMDAwMDAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzJlYzZmZjU0OTY5YTRkNjU4M2Q0NzIyNThkNDg3MGQ1ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzQyZGFlMmNhNGQyODRkMzBhYTJlNjllZjJiZDQ4ZjJhID0gJCgnPGRpdiBpZD0iaHRtbF80MmRhZTJjYTRkMjg0ZDMwYWEyZTY5ZWYyYmQ0OGYyYSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RG93bnRvd24gVG9yb250byAoTTVMKTogQ29tbWVyY2UgQ291cnQsVmljdG9yaWEgSG90ZWwgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzJlYzZmZjU0OTY5YTRkNjU4M2Q0NzIyNThkNDg3MGQ1LnNldENvbnRlbnQoaHRtbF80MmRhZTJjYTRkMjg0ZDMwYWEyZTY5ZWYyYmQ0OGYyYSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9mOGM5MDMzYTk2NzM0MmZhOGE2MzEzMDY5ODQwOTk5Mi5iaW5kUG9wdXAocG9wdXBfMmVjNmZmNTQ5NjlhNGQ2NTgzZDQ3MjI1OGQ0ODcwZDUpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNDg0N2EzZmVhODRjNDA4NzkwYzczMGNkOGYyMjQ5OTIgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDg0MjkyLC03OS4zODIyODAyXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzBiYzY4NDE3NDU5MDQxYTRhNDNhOWNkZGU0MDc4Mzc2ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzNhMWNhOThhMDQ0NDQ5OTBhNGNjMDQxYWVlNDkzMTZiID0gJCgnPGRpdiBpZD0iaHRtbF8zYTFjYTk4YTA0NDQ0OTkwYTRjYzA0MWFlZTQ5MzE2YiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RG93bnRvd24gVG9yb250byAoTTVYKTogRmlyc3QgQ2FuYWRpYW4gUGxhY2UsVW5kZXJncm91bmQgY2l0eSAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMGJjNjg0MTc0NTkwNDFhNGE0M2E5Y2RkZTQwNzgzNzYuc2V0Q29udGVudChodG1sXzNhMWNhOThhMDQ0NDQ5OTBhNGNjMDQxYWVlNDkzMTZiKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzQ4NDdhM2ZlYTg0YzQwODc5MGM3MzBjZDhmMjI0OTkyLmJpbmRQb3B1cChwb3B1cF8wYmM2ODQxNzQ1OTA0MWE0YTQzYTljZGRlNDA3ODM3Nik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9jZDlkYTc4YTI3NDQ0NDk1OTFhZTVlYTA3ZWZhOWZjZCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1Nzk1MjQsLTc5LjM4NzM4MjZdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzgwZmZiNCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiM4MGZmYjQiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZDNlZWE0ZDhjNWYxNDhmZGI2YTE1MWZiMDNjNmYyZmMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYWI2ZWUwNjI5YjczNDMwNjkyZjhmY2RjZWE0MzM5OWYgPSAkKCc8ZGl2IGlkPSJodG1sX2FiNmVlMDYyOWI3MzQzMDY5MmY4ZmNkY2VhNDMzOTlmIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Eb3dudG93biBUb3JvbnRvIChNNUcpOiBDZW50cmFsIEJheSBTdHJlZXQgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2QzZWVhNGQ4YzVmMTQ4ZmRiNmExNTFmYjAzYzZmMmZjLnNldENvbnRlbnQoaHRtbF9hYjZlZTA2MjliNzM0MzA2OTJmOGZjZGNlYTQzMzk5Zik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9jZDlkYTc4YTI3NDQ0NDk1OTFhZTVlYTA3ZWZhOWZjZC5iaW5kUG9wdXAocG9wdXBfZDNlZWE0ZDhjNWYxNDhmZGI2YTE1MWZiMDNjNmYyZmMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMjBkNjE0M2I0YjI2NDExNDkyNmE5NjcyYzFjYjg3ZjYgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDY0MzUyLC03OS4zNzQ4NDU5OTk5OTk5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8wY2UwM2M3NGRmZTI0NTNlOTdkOGMyOWI0MDdkM2NiZiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF81Y2E2MTEyMzAwMzQ0ZjRmYjljMTEyYTI0NDEwM2M5MCA9ICQoJzxkaXYgaWQ9Imh0bWxfNWNhNjExMjMwMDM0NGY0ZmI5YzExMmEyNDQxMDNjOTAiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRvd250b3duIFRvcm9udG8gKE01Vyk6IFN0biBBIFBPIEJveGVzIDI1IFRoZSBFc3BsYW5hZGUgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzBjZTAzYzc0ZGZlMjQ1M2U5N2Q4YzI5YjQwN2QzY2JmLnNldENvbnRlbnQoaHRtbF81Y2E2MTEyMzAwMzQ0ZjRmYjljMTEyYTI0NDEwM2M5MCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8yMGQ2MTQzYjRiMjY0MTE0OTI2YTk2NzJjMWNiODdmNi5iaW5kUG9wdXAocG9wdXBfMGNlMDNjNzRkZmUyNDUzZTk3ZDhjMjliNDA3ZDNjYmYpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNTU5NDEwODM3ZmQ5NDEyZGJhOTFlNzUyYWQzZDc3ZjkgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NzI3MDk3LC03OS40MDU2Nzg0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF85NTczYmZlN2RhNDY0ODNkYjNhODNlZDA5NDJjNzFkZCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF82YjkyZjM2MzhlNGY0MTNkYmVkYWRkMGU1YTQ2ZmRlMCA9ICQoJzxkaXYgaWQ9Imh0bWxfNmI5MmYzNjM4ZTRmNDEzZGJlZGFkZDBlNWE0NmZkZTAiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNlbnRyYWwgVG9yb250byAoTTVSKTogVGhlIEFubmV4LE5vcnRoIE1pZHRvd24sWW9ya3ZpbGxlIC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF85NTczYmZlN2RhNDY0ODNkYjNhODNlZDA5NDJjNzFkZC5zZXRDb250ZW50KGh0bWxfNmI5MmYzNjM4ZTRmNDEzZGJlZGFkZDBlNWE0NmZkZTApOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNTU5NDEwODM3ZmQ5NDEyZGJhOTFlNzUyYWQzZDc3ZjkuYmluZFBvcHVwKHBvcHVwXzk1NzNiZmU3ZGE0NjQ4M2RiM2E4M2VkMDk0MmM3MWRkKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzRmM2M1YWQ1ZjdhZTQyYTM4MjM1YTA1Zjg2NmEzNDEyID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjUwNTcxMjAwMDAwMDEsLTc5LjM4NDU2NzVdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzgwZmZiNCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiM4MGZmYjQiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMTZkYTRkMzZkMmVmNGE3OGFmMGMwNTYzZTc0Y2Y0Y2MgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMjg3NjQwMzc0OTRkNDkyN2I0ZTdhOWNhNmFjYTk4NTYgPSAkKCc8ZGl2IGlkPSJodG1sXzI4NzY0MDM3NDk0ZDQ5MjdiNGU3YTljYTZhY2E5ODU2IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Eb3dudG93biBUb3JvbnRvIChNNUgpOiBBZGVsYWlkZSxLaW5nLFJpY2htb25kIC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8xNmRhNGQzNmQyZWY0YTc4YWYwYzA1NjNlNzRjZjRjYy5zZXRDb250ZW50KGh0bWxfMjg3NjQwMzc0OTRkNDkyN2I0ZTdhOWNhNmFjYTk4NTYpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNGYzYzVhZDVmN2FlNDJhMzgyMzVhMDVmODY2YTM0MTIuYmluZFBvcHVwKHBvcHVwXzE2ZGE0ZDM2ZDJlZjRhNzhhZjBjMDU2M2U3NGNmNGNjKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzNlM2Q4YzExYjcxNzRmOGQ4NDgyOTA2MDdhYTJjYzJkID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjU3MTYxOCwtNzkuMzc4OTM3MDk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzgwZmZiNCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiM4MGZmYjQiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfODEyYmZjNTgwOWFjNDE4Njk2OTdhMmM4ODU5ZmQ2NTkgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMTc5ZDU0NGM4ZjhiNGYyMzk0NDc2ZWQzOTU5MmNmMGEgPSAkKCc8ZGl2IGlkPSJodG1sXzE3OWQ1NDRjOGY4YjRmMjM5NDQ3NmVkMzk1OTJjZjBhIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Eb3dudG93biBUb3JvbnRvIChNNUIpOiBSeWVyc29uLEdhcmRlbiBEaXN0cmljdCAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfODEyYmZjNTgwOWFjNDE4Njk2OTdhMmM4ODU5ZmQ2NTkuc2V0Q29udGVudChodG1sXzE3OWQ1NDRjOGY4YjRmMjM5NDQ3NmVkMzk1OTJjZjBhKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzNlM2Q4YzExYjcxNzRmOGQ4NDgyOTA2MDdhYTJjYzJkLmJpbmRQb3B1cChwb3B1cF84MTJiZmM1ODA5YWM0MTg2OTY5N2EyYzg4NTlmZDY1OSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8wNTYzODU5MThkZjg0NTljYmU0NTNkYzI4NDM5ZmE5YiA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY0NDc3MDc5OTk5OTk5NiwtNzkuMzczMzA2NF0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF83MWI0MTdmMGIyNTM0MDI1OGM2MGIxMjEyYzJmZDM1MyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF81NzU5NGU3MWVkY2U0NTdmYjljYzMyMmU3MTVkMThlNiA9ICQoJzxkaXYgaWQ9Imh0bWxfNTc1OTRlNzFlZGNlNDU3ZmI5Y2MzMjJlNzE1ZDE4ZTYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRvd250b3duIFRvcm9udG8gKE01RSk6IEJlcmN6eSBQYXJrIC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF83MWI0MTdmMGIyNTM0MDI1OGM2MGIxMjEyYzJmZDM1My5zZXRDb250ZW50KGh0bWxfNTc1OTRlNzFlZGNlNDU3ZmI5Y2MzMjJlNzE1ZDE4ZTYpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMDU2Mzg1OTE4ZGY4NDU5Y2JlNDUzZGMyODQzOWZhOWIuYmluZFBvcHVwKHBvcHVwXzcxYjQxN2YwYjI1MzQwMjU4YzYwYjEyMTJjMmZkMzUzKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2UzNTFhYjk5YjdiYzQwMGE5Yzk3MzU2OTk1YjQyOGU3ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY3OTY3LC03OS4zNjc2NzUzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzI3OWQzOWNiMTgyNzRjZTQ5MTcwY2E3MjI1ZDA2NTAxID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzRhZmI4ZGZiMWJhOTRkNDA4Y2M1YjAyMjU5MDAxYzQxID0gJCgnPGRpdiBpZD0iaHRtbF80YWZiOGRmYjFiYTk0ZDQwOGNjNWIwMjI1OTAwMWM0MSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RG93bnRvd24gVG9yb250byAoTTRYKTogQ2FiYmFnZXRvd24sU3QuIEphbWVzIFRvd24gLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzI3OWQzOWNiMTgyNzRjZTQ5MTcwY2E3MjI1ZDA2NTAxLnNldENvbnRlbnQoaHRtbF80YWZiOGRmYjFiYTk0ZDQwOGNjNWIwMjI1OTAwMWM0MSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9lMzUxYWI5OWI3YmM0MDBhOWM5NzM1Njk5NWI0MjhlNy5iaW5kUG9wdXAocG9wdXBfMjc5ZDM5Y2IxODI3NGNlNDkxNzBjYTcyMjVkMDY1MDEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfYmEzNTJhMWQ1ZmJjNGZmMjhlOTE5ZWViOTg2NjNkYjkgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MTUzODM0LC03OS40MDU2Nzg0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF83ZWNkZjg4MmRmNjE0MGY3ODBjY2UxYWJiZjI2MWE5OSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8yYWY0NWNjMzdjOGU0MWIwODljNzE1YzNmOGM1ZDc0ZSA9ICQoJzxkaXYgaWQ9Imh0bWxfMmFmNDVjYzM3YzhlNDFiMDg5YzcxNWMzZjhjNWQ3NGUiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNlbnRyYWwgVG9yb250byAoTTRSKTogTm9ydGggVG9yb250byBXZXN0IC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF83ZWNkZjg4MmRmNjE0MGY3ODBjY2UxYWJiZjI2MWE5OS5zZXRDb250ZW50KGh0bWxfMmFmNDVjYzM3YzhlNDFiMDg5YzcxNWMzZjhjNWQ3NGUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYmEzNTJhMWQ1ZmJjNGZmMjhlOTE5ZWViOTg2NjNkYjkuYmluZFBvcHVwKHBvcHVwXzdlY2RmODgyZGY2MTQwZjc4MGNjZTFhYmJmMjYxYTk5KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzcxNDU3YWZiMDc4NTRlMTJhMDU2Mjk5MzY0OWU5M2ZjID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjc5NTU3MSwtNzkuMzUyMTg4XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzgwYTAwYzllNmI5ZDRlZGNhODhhNDk2NDFlMTM0NDgxID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzhkYWViNTA2NTg4MTRhNjJiOTRkMGEzY2FjZTdhMDQxID0gJCgnPGRpdiBpZD0iaHRtbF84ZGFlYjUwNjU4ODE0YTYyYjk0ZDBhM2NhY2U3YTA0MSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+RWFzdCBUb3JvbnRvIChNNEspOiBUaGUgRGFuZm9ydGggV2VzdCxSaXZlcmRhbGUgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzgwYTAwYzllNmI5ZDRlZGNhODhhNDk2NDFlMTM0NDgxLnNldENvbnRlbnQoaHRtbF84ZGFlYjUwNjU4ODE0YTYyYjk0ZDBhM2NhY2U3YTA0MSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl83MTQ1N2FmYjA3ODU0ZTEyYTA1NjI5OTM2NDllOTNmYy5iaW5kUG9wdXAocG9wdXBfODBhMDBjOWU2YjlkNGVkY2E4OGE0OTY0MWUxMzQ0ODEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfZmM5ZDliNzUwNmU0NDc2ZTg0ZWVjZGM0YWZhZmEzMzkgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NzYzNTczOTk5OTk5OSwtNzkuMjkzMDMxMl0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9iYjgwYWZkOWIzZDE0N2FlYjdjZTY2N2FkYzcyNDRiZCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8yNGJmYmU3YTc1Y2U0ZTMxOGQ5MjFkYWZmMjI1N2FhNyA9ICQoJzxkaXYgaWQ9Imh0bWxfMjRiZmJlN2E3NWNlNGUzMThkOTIxZGFmZjIyNTdhYTciIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkVhc3QgVG9yb250byAoTTRFKTogVGhlIEJlYWNoZXMgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2JiODBhZmQ5YjNkMTQ3YWViN2NlNjY3YWRjNzI0NGJkLnNldENvbnRlbnQoaHRtbF8yNGJmYmU3YTc1Y2U0ZTMxOGQ5MjFkYWZmMjI1N2FhNyk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9mYzlkOWI3NTA2ZTQ0NzZlODRlZWNkYzRhZmFmYTMzOS5iaW5kUG9wdXAocG9wdXBfYmI4MGFmZDliM2QxNDdhZWI3Y2U2NjdhZGM3MjQ0YmQpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfZjI2ZjFiMDE4MjNjNGJhYWExY2FlZjYzM2YyNmRiYzAgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjU4NTk5LC03OS4zODMxNTk5MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8zMWU3NWU3MTkxNmU0OWQyOTQwNDgxNDQ1YzU4Y2Y4OSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9kODU2ZTNjODQxMTY0M2FkYmEwNDI5NWFiZmM0ODRiOCA9ICQoJzxkaXYgaWQ9Imh0bWxfZDg1NmUzYzg0MTE2NDNhZGJhMDQyOTVhYmZjNDg0YjgiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRvd250b3duIFRvcm9udG8gKE00WSk6IENodXJjaCBhbmQgV2VsbGVzbGV5IC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8zMWU3NWU3MTkxNmU0OWQyOTQwNDgxNDQ1YzU4Y2Y4OS5zZXRDb250ZW50KGh0bWxfZDg1NmUzYzg0MTE2NDNhZGJhMDQyOTVhYmZjNDg0YjgpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfZjI2ZjFiMDE4MjNjNGJhYWExY2FlZjYzM2YyNmRiYzAuYmluZFBvcHVwKHBvcHVwXzMxZTc1ZTcxOTE2ZTQ5ZDI5NDA0ODE0NDVjNThjZjg5KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzMxYjE4MmE5OGNiMzRhOWM5MjJmOTNlYTA5ODkxMGZiID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY4OTk4NSwtNzkuMzE1NTcxNTk5OTk5OThdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzgwZmZiNCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiM4MGZmYjQiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMzY4ZGVjMzEwMzJiNDQ5YWE1YWZiYzQ1YTg2NDBlZTAgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYWEzZDQzMDc4ZjdmNGRhYjg4YzM4MGE1N2ExZjIzNDIgPSAkKCc8ZGl2IGlkPSJodG1sX2FhM2Q0MzA3OGY3ZjRkYWI4OGMzODBhNTdhMWYyMzQyIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5FYXN0IFRvcm9udG8gKE00TCk6IFRoZSBCZWFjaGVzIFdlc3QsSW5kaWEgQmF6YWFyIC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF8zNjhkZWMzMTAzMmI0NDlhYTVhZmJjNDVhODY0MGVlMC5zZXRDb250ZW50KGh0bWxfYWEzZDQzMDc4ZjdmNGRhYjg4YzM4MGE1N2ExZjIzNDIpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMzFiMTgyYTk4Y2IzNGE5YzkyMmY5M2VhMDk4OTEwZmIuYmluZFBvcHVwKHBvcHVwXzM2OGRlYzMxMDMyYjQ0OWFhNWFmYmM0NWE4NjQwZWUwKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzY4ZDU4MmM2N2M2YzRjMzg4OGM5NzFiZmU5OTBiNWY4ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjUxNTcwNiwtNzkuNDg0NDQ5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9hOTlhZjkyNzRiY2Q0NGYxYmZlNjEwYmFiYTBiZGRiMSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9lZWRkYWIyNmUyN2M0MWFlYTYzNmRhYWYyZWI3NGZjYiA9ICQoJzxkaXYgaWQ9Imh0bWxfZWVkZGFiMjZlMjdjNDFhZWE2MzZkYWFmMmViNzRmY2IiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPldlc3QgVG9yb250byAoTTZTKTogUnVubnltZWRlLFN3YW5zZWEgLSBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2E5OWFmOTI3NGJjZDQ0ZjFiZmU2MTBiYWJhMGJkZGIxLnNldENvbnRlbnQoaHRtbF9lZWRkYWIyNmUyN2M0MWFlYTYzNmRhYWYyZWI3NGZjYik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl82OGQ1ODJjNjdjNmM0YzM4ODhjOTcxYmZlOTkwYjVmOC5iaW5kUG9wdXAocG9wdXBfYTk5YWY5Mjc0YmNkNDRmMWJmZTYxMGJhYmEwYmRkYjEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNzYzYTlhZDY3NWI5NDQ0NjhmZWY1OTVlMjU5MmNiNzggPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My43MDQzMjQ0LC03OS4zODg3OTAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzczMzcyNjgzMDdmNTQzZDRhYTExNmE3NjA3MGJkMDNkID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzhmZDJlNzdkNzMwMTRiMjg5YzE4ZjczZjI1NDVkMWIwID0gJCgnPGRpdiBpZD0iaHRtbF84ZmQyZTc3ZDczMDE0YjI4OWMxOGY3M2YyNTQ1ZDFiMCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2VudHJhbCBUb3JvbnRvIChNNFMpOiBEYXZpc3ZpbGxlIC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF83MzM3MjY4MzA3ZjU0M2Q0YWExMTZhNzYwNzBiZDAzZC5zZXRDb250ZW50KGh0bWxfOGZkMmU3N2Q3MzAxNGIyODljMThmNzNmMjU0NWQxYjApOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNzYzYTlhZDY3NWI5NDQ0NjhmZWY1OTVlMjU5MmNiNzguYmluZFBvcHVwKHBvcHVwXzczMzcyNjgzMDdmNTQzZDRhYTExNmE3NjA3MGJkMDNkKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2E3MmZjYjliZDJlZDQ3ZWVhNTk3NzkwYTNiNzNlYTk5ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzEyNzUxMSwtNzkuMzkwMTk3NV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9hZGU2NjQwMzM1M2U0NWNhYTY0OGFkNDkxMjkxMDQ5NyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF83MGVjYzc1YjBjMzY0NzNkOTUyOTliYTdmYzhiM2U1NCA9ICQoJzxkaXYgaWQ9Imh0bWxfNzBlY2M3NWIwYzM2NDczZDk1Mjk5YmE3ZmM4YjNlNTQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNlbnRyYWwgVG9yb250byAoTTRQKTogRGF2aXN2aWxsZSBOb3J0aCAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfYWRlNjY0MDMzNTNlNDVjYWE2NDhhZDQ5MTI5MTA0OTcuc2V0Q29udGVudChodG1sXzcwZWNjNzViMGMzNjQ3M2Q5NTI5OWJhN2ZjOGIzZTU0KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2E3MmZjYjliZDJlZDQ3ZWVhNTk3NzkwYTNiNzNlYTk5LmJpbmRQb3B1cChwb3B1cF9hZGU2NjQwMzM1M2U0NWNhYTY0OGFkNDkxMjkxMDQ5Nyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9mYWFiMjMzYWU2ZDA0YWExOTcwZWI0M2UyZTMyNTg1NyA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY4NjQxMjI5OTk5OTk5LC03OS40MDAwNDkzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2M4MDM5YTFiOTZhNzQ5YTk5ZTQwMzdlMDMyMDkzMmMyID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzkzMDY5NjI2NzI5YTQ0YzQ5Y2JkMjQwOGZkY2JmNTNjID0gJCgnPGRpdiBpZD0iaHRtbF85MzA2OTYyNjcyOWE0NGM0OWNiZDI0MDhmZGNiZjUzYyIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2VudHJhbCBUb3JvbnRvIChNNFYpOiBEZWVyIFBhcmssRm9yZXN0IEhpbGwgU0UsUmF0aG5lbGx5LFNvdXRoIEhpbGwsU3VtbWVyaGlsbCBXZXN0IC0gQ2x1c3RlciAyPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9jODAzOWExYjk2YTc0OWE5OWU0MDM3ZTAzMjA5MzJjMi5zZXRDb250ZW50KGh0bWxfOTMwNjk2MjY3MjlhNDRjNDljYmQyNDA4ZmRjYmY1M2MpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfZmFhYjIzM2FlNmQwNGFhMTk3MGViNDNlMmUzMjU4NTcuYmluZFBvcHVwKHBvcHVwX2M4MDM5YTFiOTZhNzQ5YTk5ZTQwMzdlMDMyMDkzMmMyKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzBjMDAwZGQ1YjEyMjQwMDI4MzJjNDQ5ZDY2ZjA1MjIzID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjY5MDA1MTAwMDAwMDEsLTc5LjQ0MjI1OTNdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzgwZmZiNCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiM4MGZmYjQiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNmI5MTAyMGM1NjRkNDY0ZmJmMmY3ZDdlZmY5ZTMyZWYpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfY2FkNjU3Yjk0OTgyNGI5ODg4NTU4N2ExYzZmMWJiYTkgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMTk2ODdkMDg4ZGZjNDkxZDhjZjU3ODNmOGUxNTg5OGUgPSAkKCc8ZGl2IGlkPSJodG1sXzE5Njg3ZDA4OGRmYzQ5MWQ4Y2Y1NzgzZjhlMTU4OThlIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5XZXN0IFRvcm9udG8gKE02SCk6IERvdmVyY291cnQgVmlsbGFnZSxEdWZmZXJpbiAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfY2FkNjU3Yjk0OTgyNGI5ODg4NTU4N2ExYzZmMWJiYTkuc2V0Q29udGVudChodG1sXzE5Njg3ZDA4OGRmYzQ5MWQ4Y2Y1NzgzZjhlMTU4OThlKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzBjMDAwZGQ1YjEyMjQwMDI4MzJjNDQ5ZDY2ZjA1MjIzLmJpbmRQb3B1cChwb3B1cF9jYWQ2NTdiOTQ5ODI0Yjk4ODg1NTg3YTFjNmYxYmJhOSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl85MjQyMDMwODMxZjA0OTFiOGNmNWIxYzZmMmZiMzIyNSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY4OTU3NDMsLTc5LjM4MzE1OTkwMDAwMDAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiM4MGZmYjQiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjODBmZmI0IiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzZiOTEwMjBjNTY0ZDQ2NGZiZjJmN2Q3ZWZmOWUzMmVmKTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2VhOGVmY2FhMzA4YzQ4NDM4ODhiMmVjMmFkYWE0ZmIyID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2QxY2QwM2VkYzdhMTQyN2I4NDNhNzRlM2FiZGQ4NDE1ID0gJCgnPGRpdiBpZD0iaHRtbF9kMWNkMDNlZGM3YTE0MjdiODQzYTc0ZTNhYmRkODQxNSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2VudHJhbCBUb3JvbnRvIChNNFQpOiBNb29yZSBQYXJrLFN1bW1lcmhpbGwgRWFzdCAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfZWE4ZWZjYWEzMDhjNDg0Mzg4OGIyZWMyYWRhYTRmYjIuc2V0Q29udGVudChodG1sX2QxY2QwM2VkYzdhMTQyN2I4NDNhNzRlM2FiZGQ4NDE1KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzkyNDIwMzA4MzFmMDQ5MWI4Y2Y1YjFjNmYyZmIzMjI1LmJpbmRQb3B1cChwb3B1cF9lYThlZmNhYTMwOGM0ODQzODg4YjJlYzJhZGFhNGZiMik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl84YTk4NDQ2OGQwMzE0MWM2YTQ4ZTJiYzIwZGYyOTZhNyA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY2Mjc0MzksLTc5LjMyMTU1OF0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF82YjkxMDIwYzU2NGQ0NjRmYmYyZjdkN2VmZjllMzJlZik7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9hMWVlODlmYzY2NmU0ODA3ODMyNDRkZjdjZTBkMTY4MCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9hOWY3Yzc1Y2NlMDA0M2FhYTg3NWEyNDI3NTJmNzE2NCA9ICQoJzxkaXYgaWQ9Imh0bWxfYTlmN2M3NWNjZTAwNDNhYWE4NzVhMjQyNzUyZjcxNjQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkVhc3QgVG9yb250byAoTTdZKTogQnVzaW5lc3MgUmVwbHkgTWFpbCBQcm9jZXNzaW5nIENlbnRyZSA5NjkgRWFzdGVybiAtIENsdXN0ZXIgMjwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfYTFlZTg5ZmM2NjZlNDgwNzgzMjQ0ZGY3Y2UwZDE2ODAuc2V0Q29udGVudChodG1sX2E5ZjdjNzVjY2UwMDQzYWFhODc1YTI0Mjc1MmY3MTY0KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzhhOTg0NDY4ZDAzMTQxYzZhNDhlMmJjMjBkZjI5NmE3LmJpbmRQb3B1cChwb3B1cF9hMWVlODlmYzY2NmU0ODA3ODMyNDRkZjdjZTBkMTY4MCk7CgogICAgICAgICAgICAKICAgICAgICAKPC9zY3JpcHQ+\" style=\"position:absolute;width:100%;height:100%;left:0;top:0;border:none !important;\" allowfullscreen webkitallowfullscreen mozallowfullscreen></iframe></div></div>"
],
"text/plain": [
"<folium.folium.Map at 0x7f50cb9d69e8>"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# create map\n",
"map_clusters = folium.Map(location=[latitude, longitude], zoom_start=12)\n",
"\n",
"# set color scheme for the clusters\n",
"x = np.arange(k_clusters)\n",
"ys = [i+x+(i*x)**2 for i in range(k_clusters)]\n",
"colors_array = cm.rainbow(np.linspace(0, 1, len(ys)))\n",
"rainbow = [colors.rgb2hex(i) for i in colors_array]\n",
"\n",
"# add markers to the map\n",
"markers_colors = []\n",
"for lat, lon, post, bor, poi, cluster in zip(toronto_central_clustered_df['Latitude'], toronto_central_clustered_df['Longitude'], toronto_central_clustered_df['Postal Code'], toronto_central_clustered_df['Borough'], toronto_central_clustered_df['Neighborhood'], toronto_central_clustered_df['Cluster']):\n",
" label = folium.Popup('{} ({}): {} - Cluster {}'.format(bor, post, poi, cluster), parse_html=True)\n",
" folium.CircleMarker(\n",
" [lat, lon],\n",
" radius=5,\n",
" popup=label,\n",
" color=rainbow[cluster-1],\n",
" fill=True,\n",
" fill_color=rainbow[cluster-1],\n",
" fill_opacity=0.7).add_to(map_clusters)\n",
" \n",
"map_clusters"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Peeking at the result shows\n",
"- Cluster 0: Park, hill, rosedale\n",
"- Cluster 1: Central Toronto (Roselawn)\n",
"- Cluster 2: Business area (with lots of business venues and some parks)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"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.6.8"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment