Skip to content

Instantly share code, notes, and snippets.

@rquintel
Created July 14, 2019 23:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rquintel/ed5c60c10a750edf6fd0b538cbb46085 to your computer and use it in GitHub Desktop.
Save rquintel/ed5c60c10a750edf6fd0b538cbb46085 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": 39,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"\n",
"# Import data\n",
"link = \"https://en.wikipedia.org/wiki/List_of_postal_codes_of_Canada:_M\"\n",
"html_table = pd.read_html(link,header=0)\n",
"postal_codes = pd.DataFrame(html_table[0])"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [],
"source": [
"# Cleaning\n",
"postal_codes.replace(\"Not assigned\", np.nan, inplace = True)\n",
"postal_codes.dropna(subset=[\"Borough\"], axis=0, inplace = True)\n",
"postal_codes.Neighbourhood.fillna(postal_codes.Borough, inplace = True)"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [],
"source": [
"# Grouping\n",
"postal_codes = postal_codes.groupby(['Postcode','Borough'])['Neighbourhood'].apply(lambda x: ', '.join(x)).reset_index()"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(103, 3)"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"postal_codes.shape"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [],
"source": [
"# Import Geo Spatial Data\n",
"geo_spatial_data = pd.read_csv(\"http://cocl.us/Geospatial_data\")"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {},
"outputs": [],
"source": [
"toronto_locations = postal_codes\n",
"\n",
"# merge postal_codes with geo_spatial_data to add latitude/longitude for each postal code\n",
"toronto_locations = toronto_locations.join(geo_spatial_data.set_index('Postal Code'), on='Postcode')"
]
},
{
"cell_type": "code",
"execution_count": 102,
"metadata": {},
"outputs": [],
"source": [
"# Import necessary libraries\n",
"import json # library to handle JSON files\n",
"import requests # library to handle requests\n",
"from pandas.io.json import json_normalize # tranform JSON file into a pandas dataframe\n",
"\n",
"# import k-means from clustering stage\n",
"from sklearn.cluster import KMeans\n",
"\n",
"#!conda install -c conda-forge geopy --yes # uncomment this line if you haven't completed the Foursquare API lab\n",
"from geopy.geocoders import Nominatim # convert an address into latitude and longitude values\n",
"\n",
"#!conda install -c conda-forge folium=0.5.0 --yes # uncomment this line if you haven't completed the Foursquare API lab\n",
"import folium # map rendering library\n",
"\n",
"# Matplotlib and associated plotting modules\n",
"import matplotlib.cm as cm\n",
"import matplotlib.colors as colors"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {},
"outputs": [],
"source": [
"# FourSquare parameters\n",
"CLIENT_ID = 'HKB1ULVBQPKLEH30AUOZ2V2I4YXF5EDOL1WIWT0EMECCQ1CW' # your Foursquare ID\n",
"CLIENT_SECRET = 'FFIUIMUUBC42AIDQ5UG4OYH54234PDCT4OANAHB1SNCJE0HB' # your Foursquare Secret\n",
"VERSION = '20180604'\n",
"\n",
"LIMIT = 100"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {},
"outputs": [],
"source": [
"# Function to retrieve nearby venues for each postal code area\n",
"def getNearbyVenues(names, latitudes, longitudes, radius=500):\n",
" \n",
" venues_list=[]\n",
" for name, lat, lng in zip(names, latitudes, longitudes):\n",
" print(name)\n",
" \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",
" 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 = ['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": 49,
"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>Postcode</th>\n",
" <th>Borough</th>\n",
" <th>Neighbourhood</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",
" <tr>\n",
" <th>45</th>\n",
" <td>M4P</td>\n",
" <td>Central Toronto</td>\n",
" <td>Davisville North</td>\n",
" <td>43.712751</td>\n",
" <td>-79.390197</td>\n",
" </tr>\n",
" <tr>\n",
" <th>46</th>\n",
" <td>M4R</td>\n",
" <td>Central Toronto</td>\n",
" <td>North Toronto West</td>\n",
" <td>43.715383</td>\n",
" <td>-79.405678</td>\n",
" </tr>\n",
" <tr>\n",
" <th>47</th>\n",
" <td>M4S</td>\n",
" <td>Central Toronto</td>\n",
" <td>Davisville</td>\n",
" <td>43.704324</td>\n",
" <td>-79.388790</td>\n",
" </tr>\n",
" <tr>\n",
" <th>48</th>\n",
" <td>M4T</td>\n",
" <td>Central Toronto</td>\n",
" <td>Moore Park, Summerhill East</td>\n",
" <td>43.689574</td>\n",
" <td>-79.383160</td>\n",
" </tr>\n",
" <tr>\n",
" <th>49</th>\n",
" <td>M4V</td>\n",
" <td>Central Toronto</td>\n",
" <td>Deer Park, Forest Hill SE, Rathnelly, South Hi...</td>\n",
" <td>43.686412</td>\n",
" <td>-79.400049</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",
" </tr>\n",
" <tr>\n",
" <th>51</th>\n",
" <td>M4X</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Cabbagetown, St. James Town</td>\n",
" <td>43.667967</td>\n",
" <td>-79.367675</td>\n",
" </tr>\n",
" <tr>\n",
" <th>52</th>\n",
" <td>M4Y</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Church and Wellesley</td>\n",
" <td>43.665860</td>\n",
" <td>-79.383160</td>\n",
" </tr>\n",
" <tr>\n",
" <th>53</th>\n",
" <td>M5A</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Harbourfront, Regent Park</td>\n",
" <td>43.654260</td>\n",
" <td>-79.360636</td>\n",
" </tr>\n",
" <tr>\n",
" <th>54</th>\n",
" <td>M5B</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Ryerson, Garden District</td>\n",
" <td>43.657162</td>\n",
" <td>-79.378937</td>\n",
" </tr>\n",
" <tr>\n",
" <th>55</th>\n",
" <td>M5C</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>St. James Town</td>\n",
" <td>43.651494</td>\n",
" <td>-79.375418</td>\n",
" </tr>\n",
" <tr>\n",
" <th>56</th>\n",
" <td>M5E</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Berczy Park</td>\n",
" <td>43.644771</td>\n",
" <td>-79.373306</td>\n",
" </tr>\n",
" <tr>\n",
" <th>57</th>\n",
" <td>M5G</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Central Bay Street</td>\n",
" <td>43.657952</td>\n",
" <td>-79.387383</td>\n",
" </tr>\n",
" <tr>\n",
" <th>58</th>\n",
" <td>M5H</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Adelaide, King, Richmond</td>\n",
" <td>43.650571</td>\n",
" <td>-79.384568</td>\n",
" </tr>\n",
" <tr>\n",
" <th>59</th>\n",
" <td>M5J</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Harbourfront East, Toronto Islands, Union Station</td>\n",
" <td>43.640816</td>\n",
" <td>-79.381752</td>\n",
" </tr>\n",
" <tr>\n",
" <th>60</th>\n",
" <td>M5K</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Design Exchange, Toronto Dominion Centre</td>\n",
" <td>43.647177</td>\n",
" <td>-79.381576</td>\n",
" </tr>\n",
" <tr>\n",
" <th>61</th>\n",
" <td>M5L</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Commerce Court, Victoria Hotel</td>\n",
" <td>43.648198</td>\n",
" <td>-79.379817</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",
" </tr>\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",
" </tr>\n",
" <tr>\n",
" <th>65</th>\n",
" <td>M5R</td>\n",
" <td>Central Toronto</td>\n",
" <td>The Annex, North Midtown, Yorkville</td>\n",
" <td>43.672710</td>\n",
" <td>-79.405678</td>\n",
" </tr>\n",
" <tr>\n",
" <th>66</th>\n",
" <td>M5S</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Harbord, University of Toronto</td>\n",
" <td>43.662696</td>\n",
" <td>-79.400049</td>\n",
" </tr>\n",
" <tr>\n",
" <th>67</th>\n",
" <td>M5T</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Chinatown, Grange Park, Kensington Market</td>\n",
" <td>43.653206</td>\n",
" <td>-79.400049</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, Harbo...</td>\n",
" <td>43.628947</td>\n",
" <td>-79.394420</td>\n",
" </tr>\n",
" <tr>\n",
" <th>69</th>\n",
" <td>M5W</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Stn A PO Boxes 25 The Esplanade</td>\n",
" <td>43.646435</td>\n",
" <td>-79.374846</td>\n",
" </tr>\n",
" <tr>\n",
" <th>70</th>\n",
" <td>M5X</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>First Canadian Place, Underground city</td>\n",
" <td>43.648429</td>\n",
" <td>-79.382280</td>\n",
" </tr>\n",
" <tr>\n",
" <th>75</th>\n",
" <td>M6G</td>\n",
" <td>Downtown Toronto</td>\n",
" <td>Christie</td>\n",
" <td>43.669542</td>\n",
" <td>-79.422564</td>\n",
" </tr>\n",
" <tr>\n",
" <th>76</th>\n",
" <td>M6H</td>\n",
" <td>West Toronto</td>\n",
" <td>Dovercourt Village, Dufferin</td>\n",
" <td>43.669005</td>\n",
" <td>-79.442259</td>\n",
" </tr>\n",
" <tr>\n",
" <th>77</th>\n",
" <td>M6J</td>\n",
" <td>West Toronto</td>\n",
" <td>Little Portugal, Trinity</td>\n",
" <td>43.647927</td>\n",
" <td>-79.419750</td>\n",
" </tr>\n",
" <tr>\n",
" <th>78</th>\n",
" <td>M6K</td>\n",
" <td>West Toronto</td>\n",
" <td>Brockton, Exhibition Place, Parkdale Village</td>\n",
" <td>43.636847</td>\n",
" <td>-79.428191</td>\n",
" </tr>\n",
" <tr>\n",
" <th>82</th>\n",
" <td>M6P</td>\n",
" <td>West Toronto</td>\n",
" <td>High Park, The Junction South</td>\n",
" <td>43.661608</td>\n",
" <td>-79.464763</td>\n",
" </tr>\n",
" <tr>\n",
" <th>83</th>\n",
" <td>M6R</td>\n",
" <td>West Toronto</td>\n",
" <td>Parkdale, Roncesvalles</td>\n",
" <td>43.648960</td>\n",
" <td>-79.456325</td>\n",
" </tr>\n",
" <tr>\n",
" <th>84</th>\n",
" <td>M6S</td>\n",
" <td>West Toronto</td>\n",
" <td>Runnymede, Swansea</td>\n",
" <td>43.651571</td>\n",
" <td>-79.484450</td>\n",
" </tr>\n",
" <tr>\n",
" <th>87</th>\n",
" <td>M7Y</td>\n",
" <td>East Toronto</td>\n",
" <td>Business Reply Mail Processing Centre 969 Eastern</td>\n",
" <td>43.662744</td>\n",
" <td>-79.321558</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postcode Borough \\\n",
"37 M4E East Toronto \n",
"41 M4K East Toronto \n",
"42 M4L East Toronto \n",
"43 M4M East Toronto \n",
"44 M4N Central Toronto \n",
"45 M4P Central Toronto \n",
"46 M4R Central Toronto \n",
"47 M4S Central Toronto \n",
"48 M4T Central Toronto \n",
"49 M4V Central Toronto \n",
"50 M4W Downtown Toronto \n",
"51 M4X Downtown Toronto \n",
"52 M4Y Downtown Toronto \n",
"53 M5A Downtown Toronto \n",
"54 M5B Downtown Toronto \n",
"55 M5C Downtown Toronto \n",
"56 M5E Downtown Toronto \n",
"57 M5G Downtown Toronto \n",
"58 M5H Downtown Toronto \n",
"59 M5J Downtown Toronto \n",
"60 M5K Downtown Toronto \n",
"61 M5L Downtown Toronto \n",
"63 M5N Central Toronto \n",
"64 M5P Central Toronto \n",
"65 M5R Central Toronto \n",
"66 M5S Downtown Toronto \n",
"67 M5T Downtown Toronto \n",
"68 M5V Downtown Toronto \n",
"69 M5W Downtown Toronto \n",
"70 M5X Downtown Toronto \n",
"75 M6G Downtown Toronto \n",
"76 M6H West Toronto \n",
"77 M6J West Toronto \n",
"78 M6K West Toronto \n",
"82 M6P West Toronto \n",
"83 M6R West Toronto \n",
"84 M6S West Toronto \n",
"87 M7Y East Toronto \n",
"\n",
" Neighbourhood Latitude Longitude \n",
"37 The Beaches 43.676357 -79.293031 \n",
"41 The Danforth West, Riverdale 43.679557 -79.352188 \n",
"42 The Beaches West, India Bazaar 43.668999 -79.315572 \n",
"43 Studio District 43.659526 -79.340923 \n",
"44 Lawrence Park 43.728020 -79.388790 \n",
"45 Davisville North 43.712751 -79.390197 \n",
"46 North Toronto West 43.715383 -79.405678 \n",
"47 Davisville 43.704324 -79.388790 \n",
"48 Moore Park, Summerhill East 43.689574 -79.383160 \n",
"49 Deer Park, Forest Hill SE, Rathnelly, South Hi... 43.686412 -79.400049 \n",
"50 Rosedale 43.679563 -79.377529 \n",
"51 Cabbagetown, St. James Town 43.667967 -79.367675 \n",
"52 Church and Wellesley 43.665860 -79.383160 \n",
"53 Harbourfront, Regent Park 43.654260 -79.360636 \n",
"54 Ryerson, Garden District 43.657162 -79.378937 \n",
"55 St. James Town 43.651494 -79.375418 \n",
"56 Berczy Park 43.644771 -79.373306 \n",
"57 Central Bay Street 43.657952 -79.387383 \n",
"58 Adelaide, King, Richmond 43.650571 -79.384568 \n",
"59 Harbourfront East, Toronto Islands, Union Station 43.640816 -79.381752 \n",
"60 Design Exchange, Toronto Dominion Centre 43.647177 -79.381576 \n",
"61 Commerce Court, Victoria Hotel 43.648198 -79.379817 \n",
"63 Roselawn 43.711695 -79.416936 \n",
"64 Forest Hill North, Forest Hill West 43.696948 -79.411307 \n",
"65 The Annex, North Midtown, Yorkville 43.672710 -79.405678 \n",
"66 Harbord, University of Toronto 43.662696 -79.400049 \n",
"67 Chinatown, Grange Park, Kensington Market 43.653206 -79.400049 \n",
"68 CN Tower, Bathurst Quay, Island airport, Harbo... 43.628947 -79.394420 \n",
"69 Stn A PO Boxes 25 The Esplanade 43.646435 -79.374846 \n",
"70 First Canadian Place, Underground city 43.648429 -79.382280 \n",
"75 Christie 43.669542 -79.422564 \n",
"76 Dovercourt Village, Dufferin 43.669005 -79.442259 \n",
"77 Little Portugal, Trinity 43.647927 -79.419750 \n",
"78 Brockton, Exhibition Place, Parkdale Village 43.636847 -79.428191 \n",
"82 High Park, The Junction South 43.661608 -79.464763 \n",
"83 Parkdale, Roncesvalles 43.648960 -79.456325 \n",
"84 Runnymede, Swansea 43.651571 -79.484450 \n",
"87 Business Reply Mail Processing Centre 969 Eastern 43.662744 -79.321558 "
]
},
"execution_count": 49,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Working with boroughs that contain the word \"Toronto\"\n",
"toronto_data = toronto_locations[toronto_locations['Borough'].str.contains(\"Toronto\")]\n",
"toronto_data"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The Beaches\n",
"The Danforth West, Riverdale\n",
"The Beaches West, India Bazaar\n",
"Studio District\n",
"Lawrence Park\n",
"Davisville North\n",
"North Toronto West\n",
"Davisville\n",
"Moore Park, Summerhill East\n",
"Deer Park, Forest Hill SE, Rathnelly, South Hill, Summerhill West\n",
"Rosedale\n",
"Cabbagetown, St. James Town\n",
"Church and Wellesley\n",
"Harbourfront, Regent Park\n",
"Ryerson, Garden District\n",
"St. James Town\n",
"Berczy Park\n",
"Central Bay Street\n",
"Adelaide, King, Richmond\n",
"Harbourfront East, Toronto Islands, Union Station\n",
"Design Exchange, Toronto Dominion Centre\n",
"Commerce Court, Victoria Hotel\n",
"Roselawn\n",
"Forest Hill North, Forest Hill West\n",
"The Annex, North Midtown, Yorkville\n",
"Harbord, University of Toronto\n",
"Chinatown, Grange Park, Kensington Market\n",
"CN Tower, Bathurst Quay, Island airport, Harbourfront West, King and Spadina, Railway Lands, South Niagara\n",
"Stn A PO Boxes 25 The Esplanade\n",
"First Canadian Place, Underground city\n",
"Christie\n",
"Dovercourt Village, Dufferin\n",
"Little Portugal, Trinity\n",
"Brockton, Exhibition Place, Parkdale Village\n",
"High Park, The Junction South\n",
"Parkdale, Roncesvalles\n",
"Runnymede, Swansea\n",
"Business Reply Mail Processing Centre 969 Eastern\n",
"(1707, 7)\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>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>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>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>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>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>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": [
" Neighborhood Neighborhood Latitude Neighborhood Longitude \\\n",
"0 The Beaches 43.676357 -79.293031 \n",
"1 The Beaches 43.676357 -79.293031 \n",
"2 The Beaches 43.676357 -79.293031 \n",
"3 The Beaches 43.676357 -79.293031 \n",
"4 The Beaches 43.676357 -79.293031 \n",
"\n",
" Venue Venue Latitude Venue Longitude \\\n",
"0 Glen Manor Ravine 43.676821 -79.293942 \n",
"1 The Big Carrot Natural Food Market 43.678879 -79.297734 \n",
"2 Grover Pub and Grub 43.679181 -79.297215 \n",
"3 Glen Stewart Ravine 43.676300 -79.294784 \n",
"4 Upper Beaches 43.680563 -79.292869 \n",
"\n",
" Venue Category \n",
"0 Trail \n",
"1 Health Food Store \n",
"2 Pub \n",
"3 Other Great Outdoors \n",
"4 Neighborhood "
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# getNearbyVenues parameters\n",
"names=toronto_data['Neighbourhood']\n",
"latitudes=toronto_data['Latitude']\n",
"longitudes=toronto_data['Longitude']\n",
"\n",
"toronto_venues = getNearbyVenues(names,latitudes,longitudes)\n",
"\n",
"print(toronto_venues.shape)\n",
"toronto_venues.head()"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"There are 238 uniques categories.\n"
]
}
],
"source": [
"# Counting unique categories\n",
"toronto_venues.groupby('Neighborhood').count()\n",
"\n",
"print('There are {} uniques categories.'.format(len(toronto_venues['Venue Category'].unique())))"
]
},
{
"cell_type": "code",
"execution_count": 52,
"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>Yoga Studio</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>Airport Terminal</th>\n",
" <th>American Restaurant</th>\n",
" <th>Antique Shop</th>\n",
" <th>...</th>\n",
" <th>Theme Restaurant</th>\n",
" <th>Thrift / Vintage Store</th>\n",
" <th>Toy / Game Store</th>\n",
" <th>Trail</th>\n",
" <th>Train Station</th>\n",
" <th>Vegetarian / Vegan Restaurant</th>\n",
" <th>Video Game Store</th>\n",
" <th>Vietnamese Restaurant</th>\n",
" <th>Wine Bar</th>\n",
" <th>Wings Joint</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\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",
" <td>...</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</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",
" </tr>\n",
" <tr>\n",
" <th>1</th>\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",
" <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>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",
" <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>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",
" <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>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",
" <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 × 238 columns</p>\n",
"</div>"
],
"text/plain": [
" Yoga Studio Afghan Restaurant Airport Airport Food Court Airport Gate \\\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",
" Airport Lounge Airport Service Airport Terminal American Restaurant \\\n",
"0 0 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",
" Antique Shop ... Theme Restaurant Thrift / Vintage Store \\\n",
"0 0 ... 0 0 \n",
"1 0 ... 0 0 \n",
"2 0 ... 0 0 \n",
"3 0 ... 0 0 \n",
"4 0 ... 0 0 \n",
"\n",
" Toy / Game Store Trail Train Station Vegetarian / Vegan Restaurant \\\n",
"0 0 1 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 Game Store Vietnamese Restaurant Wine Bar Wings Joint \n",
"0 0 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",
"[5 rows x 238 columns]"
]
},
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# one hot encoding\n",
"toronto_onehot = pd.get_dummies(toronto_venues[['Venue Category']], prefix=\"\", prefix_sep=\"\")\n",
"\n",
"# add neighborhood column back to dataframe\n",
"toronto_onehot['Neighborhood'] = toronto_venues['Neighborhood'] \n",
"\n",
"# move neighborhood column to the first column\n",
"fixed_columns = [toronto_onehot.columns[-1]] + list(toronto_onehot.columns[:-1])\n",
"toronto_onehot = toronto_onehot[fixed_columns]\n",
"\n",
"toronto_onehot.head()"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"----Adelaide, King, Richmond----\n",
" venue freq\n",
"0 Coffee Shop 0.07\n",
"1 Café 0.05\n",
"2 Steakhouse 0.04\n",
"3 Bar 0.04\n",
"4 Thai Restaurant 0.04\n",
"\n",
"\n",
"----Berczy Park----\n",
" venue freq\n",
"0 Coffee Shop 0.09\n",
"1 Cocktail Bar 0.05\n",
"2 Seafood Restaurant 0.03\n",
"3 Steakhouse 0.03\n",
"4 Beer Bar 0.03\n",
"\n",
"\n",
"----Brockton, Exhibition Place, Parkdale Village----\n",
" venue freq\n",
"0 Breakfast Spot 0.10\n",
"1 Café 0.10\n",
"2 Coffee Shop 0.10\n",
"3 Intersection 0.05\n",
"4 Burrito Place 0.05\n",
"\n",
"\n",
"----Business Reply Mail Processing Centre 969 Eastern----\n",
" venue freq\n",
"0 Light Rail Station 0.11\n",
"1 Park 0.06\n",
"2 Recording Studio 0.06\n",
"3 Skate Park 0.06\n",
"4 Fast Food Restaurant 0.06\n",
"\n",
"\n",
"----CN Tower, Bathurst Quay, Island airport, Harbourfront West, King and Spadina, Railway Lands, South Niagara----\n",
" venue freq\n",
"0 Airport Service 0.18\n",
"1 Airport Lounge 0.12\n",
"2 Airport Terminal 0.12\n",
"3 Boutique 0.06\n",
"4 Harbor / Marina 0.06\n",
"\n",
"\n",
"----Cabbagetown, St. James Town----\n",
" venue freq\n",
"0 Coffee Shop 0.07\n",
"1 Restaurant 0.05\n",
"2 Pizza Place 0.05\n",
"3 Park 0.05\n",
"4 Bakery 0.05\n",
"\n",
"\n",
"----Central Bay Street----\n",
" venue freq\n",
"0 Coffee Shop 0.15\n",
"1 Café 0.05\n",
"2 Italian Restaurant 0.05\n",
"3 Ice Cream Shop 0.04\n",
"4 Sandwich Place 0.04\n",
"\n",
"\n",
"----Chinatown, Grange Park, Kensington Market----\n",
" venue freq\n",
"0 Café 0.07\n",
"1 Vegetarian / Vegan Restaurant 0.06\n",
"2 Bar 0.05\n",
"3 Vietnamese Restaurant 0.05\n",
"4 Chinese Restaurant 0.04\n",
"\n",
"\n",
"----Christie----\n",
" venue freq\n",
"0 Grocery Store 0.19\n",
"1 Café 0.19\n",
"2 Park 0.12\n",
"3 Nightclub 0.06\n",
"4 Restaurant 0.06\n",
"\n",
"\n",
"----Church and Wellesley----\n",
" venue freq\n",
"0 Coffee Shop 0.08\n",
"1 Japanese Restaurant 0.06\n",
"2 Gay Bar 0.05\n",
"3 Sushi Restaurant 0.05\n",
"4 Restaurant 0.03\n",
"\n",
"\n",
"----Commerce Court, Victoria Hotel----\n",
" venue freq\n",
"0 Coffee Shop 0.10\n",
"1 Hotel 0.06\n",
"2 Café 0.06\n",
"3 Restaurant 0.04\n",
"4 American Restaurant 0.04\n",
"\n",
"\n",
"----Davisville----\n",
" venue freq\n",
"0 Pizza Place 0.09\n",
"1 Dessert Shop 0.09\n",
"2 Sandwich Place 0.09\n",
"3 Italian Restaurant 0.06\n",
"4 Coffee Shop 0.06\n",
"\n",
"\n",
"----Davisville North----\n",
" venue freq\n",
"0 Food & Drink Shop 0.12\n",
"1 Gym 0.12\n",
"2 Sandwich Place 0.12\n",
"3 Clothing Store 0.12\n",
"4 Hotel 0.12\n",
"\n",
"\n",
"----Deer Park, Forest Hill SE, Rathnelly, South Hill, Summerhill West----\n",
" venue freq\n",
"0 Pub 0.13\n",
"1 Coffee Shop 0.13\n",
"2 Liquor Store 0.07\n",
"3 Light Rail Station 0.07\n",
"4 Bagel Shop 0.07\n",
"\n",
"\n",
"----Design Exchange, Toronto Dominion Centre----\n",
" venue freq\n",
"0 Coffee Shop 0.15\n",
"1 Café 0.07\n",
"2 Hotel 0.06\n",
"3 Restaurant 0.04\n",
"4 Italian Restaurant 0.04\n",
"\n",
"\n",
"----Dovercourt Village, Dufferin----\n",
" venue freq\n",
"0 Bakery 0.10\n",
"1 Pharmacy 0.10\n",
"2 Supermarket 0.10\n",
"3 Portuguese Restaurant 0.05\n",
"4 Park 0.05\n",
"\n",
"\n",
"----First Canadian Place, Underground city----\n",
" venue freq\n",
"0 Coffee Shop 0.09\n",
"1 Café 0.07\n",
"2 Hotel 0.04\n",
"3 Steakhouse 0.04\n",
"4 Asian Restaurant 0.03\n",
"\n",
"\n",
"----Forest Hill North, Forest Hill West----\n",
" venue freq\n",
"0 Sushi Restaurant 0.2\n",
"1 Jewelry Store 0.2\n",
"2 Home Service 0.2\n",
"3 Park 0.2\n",
"4 Trail 0.2\n",
"\n",
"\n",
"----Harbord, University of Toronto----\n",
" venue freq\n",
"0 Café 0.12\n",
"1 Bookstore 0.09\n",
"2 Bakery 0.06\n",
"3 Bar 0.06\n",
"4 Japanese Restaurant 0.06\n",
"\n",
"\n",
"----Harbourfront East, Toronto Islands, Union Station----\n",
" venue freq\n",
"0 Coffee Shop 0.11\n",
"1 Hotel 0.05\n",
"2 Aquarium 0.05\n",
"3 Italian Restaurant 0.04\n",
"4 Café 0.04\n",
"\n",
"\n",
"----Harbourfront, Regent Park----\n",
" venue freq\n",
"0 Coffee Shop 0.18\n",
"1 Bakery 0.06\n",
"2 Park 0.06\n",
"3 Theater 0.04\n",
"4 Breakfast Spot 0.04\n",
"\n",
"\n",
"----High Park, The Junction South----\n",
" venue freq\n",
"0 Café 0.09\n",
"1 Bar 0.09\n",
"2 Mexican Restaurant 0.09\n",
"3 Speakeasy 0.04\n",
"4 Arts & Crafts Store 0.04\n",
"\n",
"\n",
"----Lawrence Park----\n",
" venue freq\n",
"0 Park 0.33\n",
"1 Swim School 0.33\n",
"2 Bus Line 0.33\n",
"3 Yoga Studio 0.00\n",
"4 Museum 0.00\n",
"\n",
"\n",
"----Little Portugal, Trinity----\n",
" venue freq\n",
"0 Bar 0.14\n",
"1 Coffee Shop 0.06\n",
"2 Asian Restaurant 0.05\n",
"3 Boutique 0.03\n",
"4 Restaurant 0.03\n",
"\n",
"\n",
"----Moore Park, Summerhill East----\n",
" venue freq\n",
"0 Playground 0.33\n",
"1 Trail 0.33\n",
"2 Restaurant 0.33\n",
"3 Yoga Studio 0.00\n",
"4 Music Store 0.00\n",
"\n",
"\n",
"----North Toronto West----\n",
" venue freq\n",
"0 Coffee Shop 0.13\n",
"1 Yoga Studio 0.07\n",
"2 Chinese Restaurant 0.07\n",
"3 Sporting Goods Shop 0.07\n",
"4 Spa 0.07\n",
"\n",
"\n",
"----Parkdale, Roncesvalles----\n",
" venue freq\n",
"0 Breakfast Spot 0.13\n",
"1 Gift Shop 0.13\n",
"2 Bookstore 0.07\n",
"3 Italian Restaurant 0.07\n",
"4 Movie Theater 0.07\n",
"\n",
"\n",
"----Rosedale----\n",
" venue freq\n",
"0 Park 0.4\n",
"1 Playground 0.2\n",
"2 Trail 0.2\n",
"3 Building 0.2\n",
"4 Music Store 0.0\n",
"\n",
"\n",
"----Roselawn----\n",
" venue freq\n",
"0 Garden 0.5\n",
"1 Pool 0.5\n",
"2 Music Venue 0.0\n",
"3 Martial Arts Dojo 0.0\n",
"4 Mediterranean Restaurant 0.0\n",
"\n",
"\n",
"----Runnymede, Swansea----\n",
" venue freq\n",
"0 Café 0.08\n",
"1 Coffee Shop 0.08\n",
"2 Pizza Place 0.08\n",
"3 Sushi Restaurant 0.05\n",
"4 Italian Restaurant 0.05\n",
"\n",
"\n",
"----Ryerson, Garden District----\n",
" venue freq\n",
"0 Coffee Shop 0.09\n",
"1 Clothing Store 0.07\n",
"2 Cosmetics Shop 0.04\n",
"3 Café 0.03\n",
"4 Fast Food Restaurant 0.03\n",
"\n",
"\n",
"----St. James Town----\n",
" venue freq\n",
"0 Coffee Shop 0.08\n",
"1 Café 0.05\n",
"2 Hotel 0.05\n",
"3 Restaurant 0.05\n",
"4 Italian Restaurant 0.04\n",
"\n",
"\n",
"----Stn A PO Boxes 25 The Esplanade----\n",
" venue freq\n",
"0 Coffee Shop 0.10\n",
"1 Restaurant 0.04\n",
"2 Café 0.04\n",
"3 Hotel 0.03\n",
"4 Beer Bar 0.03\n",
"\n",
"\n",
"----Studio District----\n",
" venue freq\n",
"0 Café 0.10\n",
"1 Coffee Shop 0.07\n",
"2 Italian Restaurant 0.05\n",
"3 Gastropub 0.05\n",
"4 American Restaurant 0.05\n",
"\n",
"\n",
"----The Annex, North Midtown, Yorkville----\n",
" venue freq\n",
"0 Coffee Shop 0.12\n",
"1 Sandwich Place 0.12\n",
"2 Café 0.12\n",
"3 Pizza Place 0.08\n",
"4 BBQ Joint 0.04\n",
"\n",
"\n",
"----The Beaches----\n",
" venue freq\n",
"0 Health Food Store 0.2\n",
"1 Other Great Outdoors 0.2\n",
"2 Trail 0.2\n",
"3 Pub 0.2\n",
"4 Music Store 0.0\n",
"\n",
"\n",
"----The Beaches West, India Bazaar----\n",
" venue freq\n",
"0 Pizza Place 0.05\n",
"1 Italian Restaurant 0.05\n",
"2 Fish & Chips Shop 0.05\n",
"3 Burger Joint 0.05\n",
"4 Burrito Place 0.05\n",
"\n",
"\n",
"----The Danforth West, Riverdale----\n",
" venue freq\n",
"0 Greek Restaurant 0.22\n",
"1 Coffee Shop 0.10\n",
"2 Italian Restaurant 0.07\n",
"3 Furniture / Home Store 0.05\n",
"4 Ice Cream Shop 0.05\n",
"\n",
"\n"
]
}
],
"source": [
"# Top 5 venues per Neighborhood\n",
"toronto_grouped = toronto_onehot.groupby('Neighborhood').mean().reset_index()\n",
"\n",
"num_top_venues = 5\n",
"\n",
"for hood in toronto_grouped['Neighborhood']:\n",
" print(\"----\"+hood+\"----\")\n",
" temp = toronto_grouped[toronto_grouped['Neighborhood'] == hood].T.reset_index()\n",
" temp.columns = ['venue','freq']\n",
" temp = temp.iloc[1:]\n",
" temp['freq'] = temp['freq'].astype(float)\n",
" temp = temp.round({'freq': 2})\n",
" print(temp.sort_values('freq', ascending=False).reset_index(drop=True).head(num_top_venues))\n",
" print('\\n')"
]
},
{
"cell_type": "code",
"execution_count": 54,
"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>Neighborhood</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",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>Adelaide, King, Richmond</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Café</td>\n",
" <td>American Restaurant</td>\n",
" <td>Bar</td>\n",
" <td>Steakhouse</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>Berczy Park</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Cocktail Bar</td>\n",
" <td>Seafood Restaurant</td>\n",
" <td>Beer Bar</td>\n",
" <td>Bakery</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>Brockton, Exhibition Place, Parkdale Village</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Café</td>\n",
" <td>Breakfast Spot</td>\n",
" <td>Yoga Studio</td>\n",
" <td>Bar</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>Business Reply Mail Processing Centre 969 Eastern</td>\n",
" <td>Light Rail Station</td>\n",
" <td>Auto Workshop</td>\n",
" <td>Smoke Shop</td>\n",
" <td>Brewery</td>\n",
" <td>Spa</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>CN Tower, Bathurst Quay, Island airport, Harbo...</td>\n",
" <td>Airport Service</td>\n",
" <td>Airport Terminal</td>\n",
" <td>Airport Lounge</td>\n",
" <td>Harbor / Marina</td>\n",
" <td>Plane</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Neighborhood 1st Most Common Venue \\\n",
"0 Adelaide, King, Richmond Coffee Shop \n",
"1 Berczy Park Coffee Shop \n",
"2 Brockton, Exhibition Place, Parkdale Village Coffee Shop \n",
"3 Business Reply Mail Processing Centre 969 Eastern Light Rail Station \n",
"4 CN Tower, Bathurst Quay, Island airport, Harbo... Airport Service \n",
"\n",
" 2nd Most Common Venue 3rd Most Common Venue 4th Most Common Venue \\\n",
"0 Café American Restaurant Bar \n",
"1 Cocktail Bar Seafood Restaurant Beer Bar \n",
"2 Café Breakfast Spot Yoga Studio \n",
"3 Auto Workshop Smoke Shop Brewery \n",
"4 Airport Terminal Airport Lounge Harbor / Marina \n",
"\n",
" 5th Most Common Venue \n",
"0 Steakhouse \n",
"1 Bakery \n",
"2 Bar \n",
"3 Spa \n",
"4 Plane "
]
},
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Most common venues per Neighborhood\n",
"def return_most_common_venues(row, num_top_venues):\n",
" row_categories = row.iloc[1:]\n",
" row_categories_sorted = row_categories.sort_values(ascending=False)\n",
" \n",
" return row_categories_sorted.index.values[0:num_top_venues]\n",
"\n",
"num_top_venues = 5\n",
"\n",
"indicators = ['st', 'nd', 'rd']\n",
"\n",
"# create columns according to number of top venues\n",
"columns = ['Neighborhood']\n",
"for ind in np.arange(num_top_venues):\n",
" try:\n",
" columns.append('{}{} Most Common Venue'.format(ind+1, indicators[ind]))\n",
" except:\n",
" columns.append('{}th Most Common Venue'.format(ind+1))\n",
"\n",
"# create a new dataframe\n",
"neighborhoods_venues_sorted = pd.DataFrame(columns=columns)\n",
"neighborhoods_venues_sorted['Neighborhood'] = toronto_grouped['Neighborhood']\n",
"\n",
"for ind in np.arange(toronto_grouped.shape[0]):\n",
" neighborhoods_venues_sorted.iloc[ind, 1:] = return_most_common_venues(toronto_grouped.iloc[ind, :], num_top_venues)\n",
"\n",
"neighborhoods_venues_sorted.head()"
]
},
{
"cell_type": "code",
"execution_count": 114,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 2, 2,\n",
" 4, 2, 3, 2, 2, 1, 0, 2, 2, 2, 2, 2, 2, 1, 2, 2], dtype=int32)"
]
},
"execution_count": 114,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# set number of clusters\n",
"kclusters = 5\n",
"\n",
"toronto_grouped_clustering = toronto_grouped.drop('Neighborhood', 1)\n",
"\n",
"# run k-means clustering\n",
"kmeans = KMeans(n_clusters=kclusters, random_state=1).fit(toronto_grouped_clustering)\n",
"\n",
"# check cluster labels generated for each row in the dataframe\n",
"kmeans.labels_"
]
},
{
"cell_type": "code",
"execution_count": 115,
"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>Postcode</th>\n",
" <th>Borough</th>\n",
" <th>Neighbourhood</th>\n",
" <th>Latitude</th>\n",
" <th>Longitude</th>\n",
" <th>Cluster Labels</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",
" </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",
" <td>0</td>\n",
" <td>Health Food Store</td>\n",
" <td>Other Great Outdoors</td>\n",
" <td>Trail</td>\n",
" <td>Pub</td>\n",
" <td>Wings Joint</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",
" <td>0</td>\n",
" <td>Greek Restaurant</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Italian Restaurant</td>\n",
" <td>Ice Cream Shop</td>\n",
" <td>Furniture / Home Store</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",
" <td>0</td>\n",
" <td>Sushi Restaurant</td>\n",
" <td>Movie Theater</td>\n",
" <td>Brewery</td>\n",
" <td>Sandwich Place</td>\n",
" <td>Board Shop</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",
" <td>0</td>\n",
" <td>Café</td>\n",
" <td>Coffee Shop</td>\n",
" <td>Bakery</td>\n",
" <td>Gastropub</td>\n",
" <td>Italian Restaurant</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>1</td>\n",
" <td>Bus Line</td>\n",
" <td>Park</td>\n",
" <td>Swim School</td>\n",
" <td>Wings Joint</td>\n",
" <td>Ethiopian Restaurant</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Postcode Borough Neighbourhood 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 Cluster Labels 1st Most Common Venue 2nd Most Common Venue \\\n",
"37 -79.293031 0 Health Food Store Other Great Outdoors \n",
"41 -79.352188 0 Greek Restaurant Coffee Shop \n",
"42 -79.315572 0 Sushi Restaurant Movie Theater \n",
"43 -79.340923 0 Café Coffee Shop \n",
"44 -79.388790 1 Bus Line Park \n",
"\n",
" 3rd Most Common Venue 4th Most Common Venue 5th Most Common Venue \n",
"37 Trail Pub Wings Joint \n",
"41 Italian Restaurant Ice Cream Shop Furniture / Home Store \n",
"42 Brewery Sandwich Place Board Shop \n",
"43 Bakery Gastropub Italian Restaurant \n",
"44 Swim School Wings Joint Ethiopian Restaurant "
]
},
"execution_count": 115,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# add clustering labels\n",
"neighborhoods_venues_sorted.insert(0, 'Cluster Labels', kmeans.labels_)\n",
"\n",
"toronto_merged = toronto_data\n",
"\n",
"# merge toronto_grouped with toronto_data to add latitude/longitude for each neighborhood\n",
"toronto_merged = toronto_merged.join(neighborhoods_venues_sorted.set_index('Neighborhood'), on='Neighbourhood')\n",
"\n",
"toronto_merged.head()"
]
},
{
"cell_type": "code",
"execution_count": 116,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The geograpical coordinates of Toronto are 43.653963, -79.387207.\n"
]
}
],
"source": [
"# Defining location\n",
"address = 'Toronto, Ontario'\n",
"\n",
"geolocator = Nominatim(user_agent=\"can_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": "code",
"execution_count": 119,
"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+ICNtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcgewogICAgICAgICAgICAgICAgcG9zaXRpb24gOiByZWxhdGl2ZTsKICAgICAgICAgICAgICAgIHdpZHRoIDogMTAwLjAlOwogICAgICAgICAgICAgICAgaGVpZ2h0OiAxMDAuMCU7CiAgICAgICAgICAgICAgICBsZWZ0OiAwLjAlOwogICAgICAgICAgICAgICAgdG9wOiAwLjAlOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICA8L3N0eWxlPgogICAgICAgIAo8L2hlYWQ+Cjxib2R5PiAgICAKICAgIAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJmb2xpdW0tbWFwIiBpZD0ibWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3IiA+PC9kaXY+CiAgICAgICAgCjwvYm9keT4KPHNjcmlwdD4gICAgCiAgICAKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGJvdW5kcyA9IG51bGw7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgdmFyIG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5NyA9IEwubWFwKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5NycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7Y2VudGVyOiBbNDMuNjUzOTYzLC03OS4zODcyMDddLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgem9vbTogMTEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhCb3VuZHM6IGJvdW5kcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxheWVyczogW10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3b3JsZENvcHlKdW1wOiBmYWxzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNyczogTC5DUlMuRVBTRzM4NTcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciB0aWxlX2xheWVyXzg1NGY3N2NjMGQ2YzQzYTBhNDQzYzVlMDM1OGIwMmFlID0gTC50aWxlTGF5ZXIoCiAgICAgICAgICAgICAgICAnaHR0cHM6Ly97c30udGlsZS5vcGVuc3RyZWV0bWFwLm9yZy97en0ve3h9L3t5fS5wbmcnLAogICAgICAgICAgICAgICAgewogICJhdHRyaWJ1dGlvbiI6IG51bGwsCiAgImRldGVjdFJldGluYSI6IGZhbHNlLAogICJtYXhab29tIjogMTgsCiAgIm1pblpvb20iOiAxLAogICJub1dyYXAiOiBmYWxzZSwKICAic3ViZG9tYWlucyI6ICJhYmMiCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8xOGFjMjI1N2IxMTU0YTMyYjBmZjcyMThmZjNiODg0NSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY3NjM1NzM5OTk5OTk5LC03OS4yOTMwMzEyXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2ZjZDZkYTY2YjQwOTQyNDBhNTJlMTM5ODhhZGI2ZDY0ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzJjYjY2NjhhN2E5MzQyMDFiNzBlMzllYmU1OGI3YjU5ID0gJCgnPGRpdiBpZD0iaHRtbF8yY2I2NjY4YTdhOTM0MjAxYjcwZTM5ZWJlNThiN2I1OSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+VGhlIEJlYWNoZXMgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9mY2Q2ZGE2NmI0MDk0MjQwYTUyZTEzOTg4YWRiNmQ2NC5zZXRDb250ZW50KGh0bWxfMmNiNjY2OGE3YTkzNDIwMWI3MGUzOWViZTU4YjdiNTkpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMThhYzIyNTdiMTE1NGEzMmIwZmY3MjE4ZmYzYjg4NDUuYmluZFBvcHVwKHBvcHVwX2ZjZDZkYTY2YjQwOTQyNDBhNTJlMTM5ODhhZGI2ZDY0KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzgwZmJhNWY0MzNiYTQ1OWRhYTg1OWM4ZTM3YjI0MTRhID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjc5NTU3MSwtNzkuMzUyMTg4XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2ExZjYwMTUzZjFhZjRlOWNhODEyMzY0YzMwZTkwNDZhID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2I1YjkyMTI4ZDY4MDRjZDY4M2U5MGM3YmU0NzQyZGFmID0gJCgnPGRpdiBpZD0iaHRtbF9iNWI5MjEyOGQ2ODA0Y2Q2ODNlOTBjN2JlNDc0MmRhZiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+VGhlIERhbmZvcnRoIFdlc3QsIFJpdmVyZGFsZSBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2ExZjYwMTUzZjFhZjRlOWNhODEyMzY0YzMwZTkwNDZhLnNldENvbnRlbnQoaHRtbF9iNWI5MjEyOGQ2ODA0Y2Q2ODNlOTBjN2JlNDc0MmRhZik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl84MGZiYTVmNDMzYmE0NTlkYWE4NTljOGUzN2IyNDE0YS5iaW5kUG9wdXAocG9wdXBfYTFmNjAxNTNmMWFmNGU5Y2E4MTIzNjRjMzBlOTA0NmEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNmYwYWI5OThiMjU5NGEzZTgxMmQwZmRiNzBhZDVlMzAgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Njg5OTg1LC03OS4zMTU1NzE1OTk5OTk5OF0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9hNDY5OGMyYWE1YTQ0YzVkOGU0MDA1MWE1M2ZiNDgzNCA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9mODI1NDE5NTVkZGU0MjhkYTY4YWYxMzA1NGZlMzBiZiA9ICQoJzxkaXYgaWQ9Imh0bWxfZjgyNTQxOTU1ZGRlNDI4ZGE2OGFmMTMwNTRmZTMwYmYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlRoZSBCZWFjaGVzIFdlc3QsIEluZGlhIEJhemFhciBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2E0Njk4YzJhYTVhNDRjNWQ4ZTQwMDUxYTUzZmI0ODM0LnNldENvbnRlbnQoaHRtbF9mODI1NDE5NTVkZGU0MjhkYTY4YWYxMzA1NGZlMzBiZik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl82ZjBhYjk5OGIyNTk0YTNlODEyZDBmZGI3MGFkNWUzMC5iaW5kUG9wdXAocG9wdXBfYTQ2OThjMmFhNWE0NGM1ZDhlNDAwNTFhNTNmYjQ4MzQpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfYTBmMjY5OTQ2ZTZiNDkyNWI1NmE4YzkzMDUwNTQxNWQgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTk1MjU1LC03OS4zNDA5MjNdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZGZmOWExMTNiYjEzNDMwM2E2NjA4NjU1ZDI2ZGEzNjcgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfY2JjOTA3ZmUxOGM1NGVlYjlhYWI3ZDQ4NzU4MTc0NmMgPSAkKCc8ZGl2IGlkPSJodG1sX2NiYzkwN2ZlMThjNTRlZWI5YWFiN2Q0ODc1ODE3NDZjIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5TdHVkaW8gRGlzdHJpY3QgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9kZmY5YTExM2JiMTM0MzAzYTY2MDg2NTVkMjZkYTM2Ny5zZXRDb250ZW50KGh0bWxfY2JjOTA3ZmUxOGM1NGVlYjlhYWI3ZDQ4NzU4MTc0NmMpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYTBmMjY5OTQ2ZTZiNDkyNWI1NmE4YzkzMDUwNTQxNWQuYmluZFBvcHVwKHBvcHVwX2RmZjlhMTEzYmIxMzQzMDNhNjYwODY1NWQyNmRhMzY3KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzU1ZGI0NGI3YTkxMTQ1NGI5MGI0NmYwZDUwZGQwYmNjID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzI4MDIwNSwtNzkuMzg4NzkwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODAwMGZmIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwMDBmZiIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF81MTk4MzljNmE1NjE0ZjY4YWNhMWIzNGIxNDY3OWNhNSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF82NmU3N2UzYTljMGU0YjM3ODA3MTkzNzhhYTJkMjA4NSA9ICQoJzxkaXYgaWQ9Imh0bWxfNjZlNzdlM2E5YzBlNGIzNzgwNzE5Mzc4YWEyZDIwODUiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkxhd3JlbmNlIFBhcmsgQ2x1c3RlciAxPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF81MTk4MzljNmE1NjE0ZjY4YWNhMWIzNGIxNDY3OWNhNS5zZXRDb250ZW50KGh0bWxfNjZlNzdlM2E5YzBlNGIzNzgwNzE5Mzc4YWEyZDIwODUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNTVkYjQ0YjdhOTExNDU0YjkwYjQ2ZjBkNTBkZDBiY2MuYmluZFBvcHVwKHBvcHVwXzUxOTgzOWM2YTU2MTRmNjhhY2ExYjM0YjE0Njc5Y2E1KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzQzOTk3MTkwZTBhZTRkZTY5OGY5YTdjM2JhMGI2ZTMxID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzEyNzUxMSwtNzkuMzkwMTk3NV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF83NDljMWY1NzY4M2I0NGRmYjRkZTBiZGI1ZTY5OTk4NSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF82NGRmNWIyNDUyMmY0YmQ0YjZmMDhjMjgxMjI5MWY3NiA9ICQoJzxkaXYgaWQ9Imh0bWxfNjRkZjViMjQ1MjJmNGJkNGI2ZjA4YzI4MTIyOTFmNzYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRhdmlzdmlsbGUgTm9ydGggQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF83NDljMWY1NzY4M2I0NGRmYjRkZTBiZGI1ZTY5OTk4NS5zZXRDb250ZW50KGh0bWxfNjRkZjViMjQ1MjJmNGJkNGI2ZjA4YzI4MTIyOTFmNzYpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNDM5OTcxOTBlMGFlNGRlNjk4ZjlhN2MzYmEwYjZlMzEuYmluZFBvcHVwKHBvcHVwXzc0OWMxZjU3NjgzYjQ0ZGZiNGRlMGJkYjVlNjk5OTg1KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzBhN2RkMGQwNzZlMzQ0ZWZhNmEwMjI5MjM0NzA4ODZmID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzE1MzgzNCwtNzkuNDA1Njc4NDAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZGZmMzFlYTI5ZGMwNDc5YWJhZDM3ODhlMTdmYWE3OGYgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYjg1OTU4ZTBlYjU1NDlmNDk5ZGEwZDgzYTg1ZjI3YjIgPSAkKCc8ZGl2IGlkPSJodG1sX2I4NTk1OGUwZWI1NTQ5ZjQ5OWRhMGQ4M2E4NWYyN2IyIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Ob3J0aCBUb3JvbnRvIFdlc3QgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9kZmYzMWVhMjlkYzA0NzlhYmFkMzc4OGUxN2ZhYTc4Zi5zZXRDb250ZW50KGh0bWxfYjg1OTU4ZTBlYjU1NDlmNDk5ZGEwZDgzYTg1ZjI3YjIpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMGE3ZGQwZDA3NmUzNDRlZmE2YTAyMjkyMzQ3MDg4NmYuYmluZFBvcHVwKHBvcHVwX2RmZjMxZWEyOWRjMDQ3OWFiYWQzNzg4ZTE3ZmFhNzhmKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzFhNTA3NmJiODk0MDQ2NzI4YmM0NzM5ZDgyYTBiMzA1ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzA0MzI0NCwtNzkuMzg4NzkwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9mZjlmMzc4MTYzODA0MjQwODNkYTY4NjM5NmJkZmZlNSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF82ODBmMWNiZjk2NTg0MmFhYmU0ODQ1NmU3NjkxMTFlZCA9ICQoJzxkaXYgaWQ9Imh0bWxfNjgwZjFjYmY5NjU4NDJhYWJlNDg0NTZlNzY5MTExZWQiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRhdmlzdmlsbGUgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9mZjlmMzc4MTYzODA0MjQwODNkYTY4NjM5NmJkZmZlNS5zZXRDb250ZW50KGh0bWxfNjgwZjFjYmY5NjU4NDJhYWJlNDg0NTZlNzY5MTExZWQpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMWE1MDc2YmI4OTQwNDY3MjhiYzQ3MzlkODJhMGIzMDUuYmluZFBvcHVwKHBvcHVwX2ZmOWYzNzgxNjM4MDQyNDA4M2RhNjg2Mzk2YmRmZmU1KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzQwNDkwZjJjZGU2YzQ4MmQ5ZjcyNGZmOGEwMzBmOTk4ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjg5NTc0MywtNzkuMzgzMTU5OTAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmYjM2MCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZmIzNjAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfODFkMjI5YmUwY2NiNDc4Y2IyMTk0ODRlMDNkOTIyNjQgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfN2QxZTgyZmFkYTNhNDZkYjkzODM1M2JhNGNiZmFmNDAgPSAkKCc8ZGl2IGlkPSJodG1sXzdkMWU4MmZhZGEzYTQ2ZGI5MzgzNTNiYTRjYmZhZjQwIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Nb29yZSBQYXJrLCBTdW1tZXJoaWxsIEVhc3QgQ2x1c3RlciA0PC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF84MWQyMjliZTBjY2I0NzhjYjIxOTQ4NGUwM2Q5MjI2NC5zZXRDb250ZW50KGh0bWxfN2QxZTgyZmFkYTNhNDZkYjkzODM1M2JhNGNiZmFmNDApOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNDA0OTBmMmNkZTZjNDgyZDlmNzI0ZmY4YTAzMGY5OTguYmluZFBvcHVwKHBvcHVwXzgxZDIyOWJlMGNjYjQ3OGNiMjE5NDg0ZTAzZDkyMjY0KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzRlOGE2ZjdiMzA4ZjQ0YWViNjNiMGQyOTkyNTUwMWI0ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjg2NDEyMjk5OTk5OTksLTc5LjQwMDA0OTNdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNDUzMWE1Mzg0Y2I2NDgwN2E4YmY3MDVkYWE3YzM2ZDQgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMDM0ZjNmN2ZkNTg5NDQzMWE3YTk3YTczMGQxNjU4MjYgPSAkKCc8ZGl2IGlkPSJodG1sXzAzNGYzZjdmZDU4OTQ0MzFhN2E5N2E3MzBkMTY1ODI2IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5EZWVyIFBhcmssIEZvcmVzdCBIaWxsIFNFLCBSYXRobmVsbHksIFNvdXRoIEhpbGwsIFN1bW1lcmhpbGwgV2VzdCBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzQ1MzFhNTM4NGNiNjQ4MDdhOGJmNzA1ZGFhN2MzNmQ0LnNldENvbnRlbnQoaHRtbF8wMzRmM2Y3ZmQ1ODk0NDMxYTdhOTdhNzMwZDE2NTgyNik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl80ZThhNmY3YjMwOGY0NGFlYjYzYjBkMjk5MjU1MDFiNC5iaW5kUG9wdXAocG9wdXBfNDUzMWE1Mzg0Y2I2NDgwN2E4YmY3MDVkYWE3YzM2ZDQpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMTcxZjRiNzMzZDkyNGQ4M2JlMWMzY2FkNDVjMDUwNTggPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Nzk1NjI2LC03OS4zNzc1Mjk0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9mOWJkMTYyZTY0Zjk0YWFiYmZmNzVjODg0YjhjOGQyYSA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9hN2VkMmU0MmY3ZWM0YmExYTk2ODEzODY3ZjVlZDRhYyA9ICQoJzxkaXYgaWQ9Imh0bWxfYTdlZDJlNDJmN2VjNGJhMWE5NjgxMzg2N2Y1ZWQ0YWMiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPlJvc2VkYWxlIENsdXN0ZXIgMzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfZjliZDE2MmU2NGY5NGFhYmJmZjc1Yzg4NGI4YzhkMmEuc2V0Q29udGVudChodG1sX2E3ZWQyZTQyZjdlYzRiYTFhOTY4MTM4NjdmNWVkNGFjKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzE3MWY0YjczM2Q5MjRkODNiZTFjM2NhZDQ1YzA1MDU4LmJpbmRQb3B1cChwb3B1cF9mOWJkMTYyZTY0Zjk0YWFiYmZmNzVjODg0YjhjOGQyYSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9lODc1NjhkYzZiZDI0NDdhODg4YzhkNTEyZDRjNGRjNSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY2Nzk2NywtNzkuMzY3Njc1M10sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9hZWM4MzNmOGVkMjY0ZmJmYjAyMWQyMTg2OTUzMzA4YyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8xN2ZiMzNmMmU4MzM0ZmEzYmI2MDRlM2RlODZhZTM3YSA9ICQoJzxkaXYgaWQ9Imh0bWxfMTdmYjMzZjJlODMzNGZhM2JiNjA0ZTNkZTg2YWUzN2EiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNhYmJhZ2V0b3duLCBTdC4gSmFtZXMgVG93biBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2FlYzgzM2Y4ZWQyNjRmYmZiMDIxZDIxODY5NTMzMDhjLnNldENvbnRlbnQoaHRtbF8xN2ZiMzNmMmU4MzM0ZmEzYmI2MDRlM2RlODZhZTM3YSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9lODc1NjhkYzZiZDI0NDdhODg4YzhkNTEyZDRjNGRjNS5iaW5kUG9wdXAocG9wdXBfYWVjODMzZjhlZDI2NGZiZmIwMjFkMjE4Njk1MzMwOGMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNDQ4NmUwODQzMWYyNGZmMTk5YWE4M2MyNzliODY5YTMgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjU4NTk5LC03OS4zODMxNTk5MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8xNDQwMGM2ZDQ0OTA0MGVhYmFiZTJiN2IwODgyOWJhZiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF81NmJhZjUwOGE5NmU0YjU1YTY0NjllMWMxODgzOWRiMiA9ICQoJzxkaXYgaWQ9Imh0bWxfNTZiYWY1MDhhOTZlNGI1NWE2NDY5ZTFjMTg4MzlkYjIiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNodXJjaCBhbmQgV2VsbGVzbGV5IENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMTQ0MDBjNmQ0NDkwNDBlYWJhYmUyYjdiMDg4MjliYWYuc2V0Q29udGVudChodG1sXzU2YmFmNTA4YTk2ZTRiNTVhNjQ2OWUxYzE4ODM5ZGIyKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzQ0ODZlMDg0MzFmMjRmZjE5OWFhODNjMjc5Yjg2OWEzLmJpbmRQb3B1cChwb3B1cF8xNDQwMGM2ZDQ0OTA0MGVhYmFiZTJiN2IwODgyOWJhZik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl81NWE4MWYxMjUzMzQ0NTU4YjQwYjIxMDM3NjVlNzk1MyA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1NDI1OTksLTc5LjM2MDYzNTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfODU1MzBiMTZiMzk3NDJkYTk4Mzg0NjBiYTIxNDA1MTMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfOTA4ZDEyZTNlZGQ0NDcxNmEyNzkwNmNjY2MwZTljZWYgPSAkKCc8ZGl2IGlkPSJodG1sXzkwOGQxMmUzZWRkNDQ3MTZhMjc5MDZjY2NjMGU5Y2VmIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5IYXJib3VyZnJvbnQsIFJlZ2VudCBQYXJrIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfODU1MzBiMTZiMzk3NDJkYTk4Mzg0NjBiYTIxNDA1MTMuc2V0Q29udGVudChodG1sXzkwOGQxMmUzZWRkNDQ3MTZhMjc5MDZjY2NjMGU5Y2VmKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzU1YTgxZjEyNTMzNDQ1NThiNDBiMjEwMzc2NWU3OTUzLmJpbmRQb3B1cChwb3B1cF84NTUzMGIxNmIzOTc0MmRhOTgzODQ2MGJhMjE0MDUxMyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9kOTc3YWE5Yjk3ZTY0ZWU3YmE1ZTMwNWE4N2VjMDRkOSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1NzE2MTgsLTc5LjM3ODkzNzA5OTk5OTk5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzU1YjNkN2E2OTEyNTQ0MTliOGY5ODg0YzFjNWQ3ZmU5ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzg5YzVhNTdlMTAyNDQwYjI4MzI3NWY1YmQ4ZWM0ZDJkID0gJCgnPGRpdiBpZD0iaHRtbF84OWM1YTU3ZTEwMjQ0MGIyODMyNzVmNWJkOGVjNGQyZCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+UnllcnNvbiwgR2FyZGVuIERpc3RyaWN0IENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfNTViM2Q3YTY5MTI1NDQxOWI4Zjk4ODRjMWM1ZDdmZTkuc2V0Q29udGVudChodG1sXzg5YzVhNTdlMTAyNDQwYjI4MzI3NWY1YmQ4ZWM0ZDJkKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2Q5NzdhYTliOTdlNjRlZTdiYTVlMzA1YTg3ZWMwNGQ5LmJpbmRQb3B1cChwb3B1cF81NWIzZDdhNjkxMjU0NDE5YjhmOTg4NGMxYzVkN2ZlOSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8zMGJhNjU0ZDIxOGM0OTNjOWJkNTc0M2JjMjJiYjJhYyA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1MTQ5MzksLTc5LjM3NTQxNzldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMWUzOGFkZmQ5NTc1NDAzOTljYzBkNzgzZGUzOGQyYTEgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfZmU4M2JjNWFiNjFkNGM1NWIzNmMyN2E1ZjRjYmQ0MjIgPSAkKCc8ZGl2IGlkPSJodG1sX2ZlODNiYzVhYjYxZDRjNTViMzZjMjdhNWY0Y2JkNDIyIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5TdC4gSmFtZXMgVG93biBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzFlMzhhZGZkOTU3NTQwMzk5Y2MwZDc4M2RlMzhkMmExLnNldENvbnRlbnQoaHRtbF9mZTgzYmM1YWI2MWQ0YzU1YjM2YzI3YTVmNGNiZDQyMik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8zMGJhNjU0ZDIxOGM0OTNjOWJkNTc0M2JjMjJiYjJhYy5iaW5kUG9wdXAocG9wdXBfMWUzOGFkZmQ5NTc1NDAzOTljYzBkNzgzZGUzOGQyYTEpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfNjcwMjY1OTI3OTkyNDI0NWI5NzI0ZjA4NmM0OTg0NDMgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDQ3NzA3OTk5OTk5OTYsLTc5LjM3MzMwNjRdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZTJkYjg4NWRiYWIwNDUzZjk4NjE0OTJhNWEyYzgyN2EgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMWM5ZDJmZDVhNWNhNDhjMzljY2JmMDJkNjhiZTMyOGUgPSAkKCc8ZGl2IGlkPSJodG1sXzFjOWQyZmQ1YTVjYTQ4YzM5Y2NiZjAyZDY4YmUzMjhlIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5CZXJjenkgUGFyayBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwX2UyZGI4ODVkYmFiMDQ1M2Y5ODYxNDkyYTVhMmM4MjdhLnNldENvbnRlbnQoaHRtbF8xYzlkMmZkNWE1Y2E0OGMzOWNjYmYwMmQ2OGJlMzI4ZSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl82NzAyNjU5Mjc5OTI0MjQ1Yjk3MjRmMDg2YzQ5ODQ0My5iaW5kUG9wdXAocG9wdXBfZTJkYjg4NWRiYWIwNDUzZjk4NjE0OTJhNWEyYzgyN2EpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfOTUyM2Y4ZmY4NzU4NGZlMmJlMjc4MmVmNzM1MjRiZDMgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTc5NTI0LC03OS4zODczODI2XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2EwODQyNTM1YmYzNzRjMzVhZDlhNGVjMzRmYTkzODk4ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2I4ZWU3OWZlMmViMzQ0ZjA4MzUyMjg5ZGEwOTZiNmFjID0gJCgnPGRpdiBpZD0iaHRtbF9iOGVlNzlmZTJlYjM0NGYwODM1MjI4OWRhMDk2YjZhYyIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2VudHJhbCBCYXkgU3RyZWV0IENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfYTA4NDI1MzViZjM3NGMzNWFkOWE0ZWMzNGZhOTM4OTguc2V0Q29udGVudChodG1sX2I4ZWU3OWZlMmViMzQ0ZjA4MzUyMjg5ZGEwOTZiNmFjKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzk1MjNmOGZmODc1ODRmZTJiZTI3ODJlZjczNTI0YmQzLmJpbmRQb3B1cChwb3B1cF9hMDg0MjUzNWJmMzc0YzM1YWQ5YTRlYzM0ZmE5Mzg5OCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9mYWJlNTc0OTFkODE0ZDYwYmZkMDM2ZGI4MWUwODU4OSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1MDU3MTIwMDAwMDAxLC03OS4zODQ1Njc1XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2NkNTNiMjk4OWYyMzQxNmNhZmExN2U3YzQ5NzE0ZjExID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sX2E2ZjVhNDBhOWJlYjRjNDRhYmNjM2I3YTk5MGUyZGM1ID0gJCgnPGRpdiBpZD0iaHRtbF9hNmY1YTQwYTliZWI0YzQ0YWJjYzNiN2E5OTBlMmRjNSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+QWRlbGFpZGUsIEtpbmcsIFJpY2htb25kIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfY2Q1M2IyOTg5ZjIzNDE2Y2FmYTE3ZTdjNDk3MTRmMTEuc2V0Q29udGVudChodG1sX2E2ZjVhNDBhOWJlYjRjNDRhYmNjM2I3YTk5MGUyZGM1KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2ZhYmU1NzQ5MWQ4MTRkNjBiZmQwMzZkYjgxZTA4NTg5LmJpbmRQb3B1cChwb3B1cF9jZDUzYjI5ODlmMjM0MTZjYWZhMTdlN2M0OTcxNGYxMSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9kZTg5YzRkMGJjNDg0NjlkYWY4ODk1NmRlZThiZWJmZiA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY0MDgxNTcsLTc5LjM4MTc1MjI5OTk5OTk5XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzQxODEyMzY3ODk4YjQ5YzdhY2YxNTI4ODlkNjY3NDgyID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzA5MjRjM2I5MGMyZTRmMWRiNzFjZjY3YzhkMmUxNTlkID0gJCgnPGRpdiBpZD0iaHRtbF8wOTI0YzNiOTBjMmU0ZjFkYjcxY2Y2N2M4ZDJlMTU5ZCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+SGFyYm91cmZyb250IEVhc3QsIFRvcm9udG8gSXNsYW5kcywgVW5pb24gU3RhdGlvbiBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzQxODEyMzY3ODk4YjQ5YzdhY2YxNTI4ODlkNjY3NDgyLnNldENvbnRlbnQoaHRtbF8wOTI0YzNiOTBjMmU0ZjFkYjcxY2Y2N2M4ZDJlMTU5ZCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9kZTg5YzRkMGJjNDg0NjlkYWY4ODk1NmRlZThiZWJmZi5iaW5kUG9wdXAocG9wdXBfNDE4MTIzNjc4OThiNDljN2FjZjE1Mjg4OWQ2Njc0ODIpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfN2Y1YTljYTEzMmNmNDRmZWFmZjUwOWI5YjI0OGNmZDAgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDcxNzY4LC03OS4zODE1NzY0MDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF81Mjc1NzBkMzE2NGI0YTk4ODBmNWMxYWNkYzVjMmE2NiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9iNGI4MjE3MGU5Y2I0OTJhOGQ1ZWU4M2U2Y2JmODI2NiA9ICQoJzxkaXYgaWQ9Imh0bWxfYjRiODIxNzBlOWNiNDkyYThkNWVlODNlNmNiZjgyNjYiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRlc2lnbiBFeGNoYW5nZSwgVG9yb250byBEb21pbmlvbiBDZW50cmUgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF81Mjc1NzBkMzE2NGI0YTk4ODBmNWMxYWNkYzVjMmE2Ni5zZXRDb250ZW50KGh0bWxfYjRiODIxNzBlOWNiNDkyYThkNWVlODNlNmNiZjgyNjYpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfN2Y1YTljYTEzMmNmNDRmZWFmZjUwOWI5YjI0OGNmZDAuYmluZFBvcHVwKHBvcHVwXzUyNzU3MGQzMTY0YjRhOTg4MGY1YzFhY2RjNWMyYTY2KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2NkMzgzYzBkYThkMjRkM2I5NjAzOWRhMjNhMmJmM2E5ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ4MTk4NSwtNzkuMzc5ODE2OTAwMDAwMDFdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZDIxNWE5ZjA3Y2Y5NDFhZjk3NGI0ODQ0YWE5MDk2MzIgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMDA1ZmQ4ZjU3YzdiNDg1Njg4MjNkZThmYWM1ZmU4N2UgPSAkKCc8ZGl2IGlkPSJodG1sXzAwNWZkOGY1N2M3YjQ4NTY4ODIzZGU4ZmFjNWZlODdlIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Db21tZXJjZSBDb3VydCwgVmljdG9yaWEgSG90ZWwgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9kMjE1YTlmMDdjZjk0MWFmOTc0YjQ4NDRhYTkwOTYzMi5zZXRDb250ZW50KGh0bWxfMDA1ZmQ4ZjU3YzdiNDg1Njg4MjNkZThmYWM1ZmU4N2UpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfY2QzODNjMGRhOGQyNGQzYjk2MDM5ZGEyM2EyYmYzYTkuYmluZFBvcHVwKHBvcHVwX2QyMTVhOWYwN2NmOTQxYWY5NzRiNDg0NGFhOTA5NjMyKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzdmMzEzNGRkNTc3MzQzNDU4YWFiMTU1MzRkNWJkNjJiID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNzExNjk0OCwtNzkuNDE2OTM1NTk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiIzAwYjVlYiIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiMwMGI1ZWIiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNTY0MDFmYjgwMzZlNGY4OThlZWU5NjJlMTQyYTNhYTMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfMjM5NjFjMTk4YzQ3NGI2MzhiZDAxOGI5ZDZhMjFkY2YgPSAkKCc8ZGl2IGlkPSJodG1sXzIzOTYxYzE5OGM0NzRiNjM4YmQwMThiOWQ2YTIxZGNmIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5Sb3NlbGF3biBDbHVzdGVyIDI8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzU2NDAxZmI4MDM2ZTRmODk4ZWVlOTYyZTE0MmEzYWEzLnNldENvbnRlbnQoaHRtbF8yMzk2MWMxOThjNDc0YjYzOGJkMDE4YjlkNmEyMWRjZik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl83ZjMxMzRkZDU3NzM0MzQ1OGFhYjE1NTM0ZDViZDYyYi5iaW5kUG9wdXAocG9wdXBfNTY0MDFmYjgwMzZlNGY4OThlZWU5NjJlMTQyYTNhYTMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMDA5OTIwYzczY2Y3NDBiMWIxZjViODNjMDE1NTdhNzQgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42OTY5NDc2LC03OS40MTEzMDcyMDAwMDAwMV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjODBmZmI0IiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiIzgwZmZiNCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF8xYTdhN2UxYTNjMDE0OWNlODQyZjQ1Y2JmMDA4MDE1NyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8wNjlmNzM3ZDg2ZmI0MTJmYjg1MzRmOTVmYjIzYmMzMSA9ICQoJzxkaXYgaWQ9Imh0bWxfMDY5ZjczN2Q4NmZiNDEyZmI4NTM0Zjk1ZmIyM2JjMzEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkZvcmVzdCBIaWxsIE5vcnRoLCBGb3Jlc3QgSGlsbCBXZXN0IENsdXN0ZXIgMzwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfMWE3YTdlMWEzYzAxNDljZTg0MmY0NWNiZjAwODAxNTcuc2V0Q29udGVudChodG1sXzA2OWY3MzdkODZmYjQxMmZiODUzNGY5NWZiMjNiYzMxKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzAwOTkyMGM3M2NmNzQwYjFiMWY1YjgzYzAxNTU3YTc0LmJpbmRQb3B1cChwb3B1cF8xYTdhN2UxYTNjMDE0OWNlODQyZjQ1Y2JmMDA4MDE1Nyk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9mZTFiYmE3MTBjZjE0NTVlYWZiYmYyMjMwZWU1YjNkZCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY3MjcwOTcsLTc5LjQwNTY3ODQwMDAwMDAxXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2Y2ODJmOWNjMTlhMTQxMjZhYzczNWI5ZmQ2MTg4OTVjID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzc1ZWFmMGU0NTQxNjQwZGY4MzRhNDA4NDhiMDBhMGI3ID0gJCgnPGRpdiBpZD0iaHRtbF83NWVhZjBlNDU0MTY0MGRmODM0YTQwODQ4YjAwYTBiNyIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+VGhlIEFubmV4LCBOb3J0aCBNaWR0b3duLCBZb3JrdmlsbGUgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9mNjgyZjljYzE5YTE0MTI2YWM3MzViOWZkNjE4ODk1Yy5zZXRDb250ZW50KGh0bWxfNzVlYWYwZTQ1NDE2NDBkZjgzNGE0MDg0OGIwMGEwYjcpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfZmUxYmJhNzEwY2YxNDU1ZWFmYmJmMjIzMGVlNWIzZGQuYmluZFBvcHVwKHBvcHVwX2Y2ODJmOWNjMTlhMTQxMjZhYzczNWI5ZmQ2MTg4OTVjKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzlmNjU3MmUyYjRkOTQ2MWFiMzk0NTRlOWM2Zjk3ODU3ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjYyNjk1NiwtNzkuNDAwMDQ5M10sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF85ZTk2NzJlNmNjZDE0MmE5OTg2MjFjNzJmZmRhNDg2MyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF9lNDM4MWQ0YjQ5YTg0NDQzOWMzYTAwNmFjNjhmMjE3ZiA9ICQoJzxkaXYgaWQ9Imh0bWxfZTQzODFkNGI0OWE4NDQ0MzljM2EwMDZhYzY4ZjIxN2YiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkhhcmJvcmQsIFVuaXZlcnNpdHkgb2YgVG9yb250byBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzllOTY3MmU2Y2NkMTQyYTk5ODYyMWM3MmZmZGE0ODYzLnNldENvbnRlbnQoaHRtbF9lNDM4MWQ0YjQ5YTg0NDQzOWMzYTAwNmFjNjhmMjE3Zik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl85ZjY1NzJlMmI0ZDk0NjFhYjM5NDU0ZTljNmY5Nzg1Ny5iaW5kUG9wdXAocG9wdXBfOWU5NjcyZTZjY2QxNDJhOTk4NjIxYzcyZmZkYTQ4NjMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfMDQxMWQyM2Q4Yzk5NDU3NmJlYjViOGU3MTJiYzAyY2MgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NTMyMDU3LC03OS40MDAwNDkzXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzdiYjBhZGE3MmJmMzRkOTE5MmMzYjcyYTY4NDdkZTdkID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzVmOTYwMDc2YTNhOTQ5OTNhNjBhZTU3ZTFmYmUwYWE1ID0gJCgnPGRpdiBpZD0iaHRtbF81Zjk2MDA3NmEzYTk0OTkzYTYwYWU1N2UxZmJlMGFhNSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+Q2hpbmF0b3duLCBHcmFuZ2UgUGFyaywgS2Vuc2luZ3RvbiBNYXJrZXQgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF83YmIwYWRhNzJiZjM0ZDkxOTJjM2I3MmE2ODQ3ZGU3ZC5zZXRDb250ZW50KGh0bWxfNWY5NjAwNzZhM2E5NDk5M2E2MGFlNTdlMWZiZTBhYTUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMDQxMWQyM2Q4Yzk5NDU3NmJlYjViOGU3MTJiYzAyY2MuYmluZFBvcHVwKHBvcHVwXzdiYjBhZGE3MmJmMzRkOTE5MmMzYjcyYTY4NDdkZTdkKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzc3ZTQxMzMyNmM0MTRiMDk5MmZlNGVhZWQ5YmI2MTE5ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjI4OTQ2NywtNzkuMzk0NDE5OV0sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9iNzM3ODVkNzZmM2Q0N2MxYWY0YTNiN2JjNzhiYmFlYiA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF81NTBjMGQ4ZDRjNDk0ZDM0Yjg2ZDc5ZTY2NWNkY2IzNSA9ICQoJzxkaXYgaWQ9Imh0bWxfNTUwYzBkOGQ0YzQ5NGQzNGI4NmQ3OWU2NjVjZGNiMzUiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkNOIFRvd2VyLCBCYXRodXJzdCBRdWF5LCBJc2xhbmQgYWlycG9ydCwgSGFyYm91cmZyb250IFdlc3QsIEtpbmcgYW5kIFNwYWRpbmEsIFJhaWx3YXkgTGFuZHMsIFNvdXRoIE5pYWdhcmEgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9iNzM3ODVkNzZmM2Q0N2MxYWY0YTNiN2JjNzhiYmFlYi5zZXRDb250ZW50KGh0bWxfNTUwYzBkOGQ0YzQ5NGQzNGI4NmQ3OWU2NjVjZGNiMzUpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfNzdlNDEzMzI2YzQxNGIwOTkyZmU0ZWFlZDliYjYxMTkuYmluZFBvcHVwKHBvcHVwX2I3Mzc4NWQ3NmYzZDQ3YzFhZjRhM2I3YmM3OGJiYWViKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzA2MDAwZmEwMzVjNTQ1ZDE4OWU2ZmYxNzVlNmFjM2ZiID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ2NDM1MiwtNzkuMzc0ODQ1OTk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfOGI5MTAyMTNlNWVjNGI2MmIwNmRhYzg2MDZkMzQ2ZGYgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYmFjMWFiZDBjOTVjNDdlNGE1MGQ1ZGIyNmU5Mjg3YjAgPSAkKCc8ZGl2IGlkPSJodG1sX2JhYzFhYmQwYzk1YzQ3ZTRhNTBkNWRiMjZlOTI4N2IwIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5TdG4gQSBQTyBCb3hlcyAyNSBUaGUgRXNwbGFuYWRlIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfOGI5MTAyMTNlNWVjNGI2MmIwNmRhYzg2MDZkMzQ2ZGYuc2V0Q29udGVudChodG1sX2JhYzFhYmQwYzk1YzQ3ZTRhNTBkNWRiMjZlOTI4N2IwKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzA2MDAwZmEwMzVjNTQ1ZDE4OWU2ZmYxNzVlNmFjM2ZiLmJpbmRQb3B1cChwb3B1cF84YjkxMDIxM2U1ZWM0YjYyYjA2ZGFjODYwNmQzNDZkZik7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl80Y2U5NWZmZThlZTU0YjMzOWY1NGZhOTdkY2JhOGI2YSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY0ODQyOTIsLTc5LjM4MjI4MDJdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMmI4MWFjNWEzNDhjNDIyMjgyZGMyNTE4Y2EwNGI0MzMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfNWM2NjIxNDQ2ODQ3NDYxN2JmNjlhODY0M2Y3NDVlNzcgPSAkKCc8ZGl2IGlkPSJodG1sXzVjNjYyMTQ0Njg0NzQ2MTdiZjY5YTg2NDNmNzQ1ZTc3IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5GaXJzdCBDYW5hZGlhbiBQbGFjZSwgVW5kZXJncm91bmQgY2l0eSBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzJiODFhYzVhMzQ4YzQyMjI4MmRjMjUxOGNhMDRiNDMzLnNldENvbnRlbnQoaHRtbF81YzY2MjE0NDY4NDc0NjE3YmY2OWE4NjQzZjc0NWU3Nyk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl80Y2U5NWZmZThlZTU0YjMzOWY1NGZhOTdkY2JhOGI2YS5iaW5kUG9wdXAocG9wdXBfMmI4MWFjNWEzNDhjNDIyMjgyZGMyNTE4Y2EwNGI0MzMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfOWE5ZmIzZDM0ODlmNDlhMjgzZmYzNTU0NjM2ZmMwZDkgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42Njk1NDIsLTc5LjQyMjU2MzddLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfNmJhZTViZmYyMDhhNGFmNTg3N2VmMjM5MjEzNzA3NzMgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYzljN2Q4ODc0NGJhNDMzMzg0ODVhODEyZmJhYjlhNzIgPSAkKCc8ZGl2IGlkPSJodG1sX2M5YzdkODg3NDRiYTQzMzM4NDg1YTgxMmZiYWI5YTcyIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5DaHJpc3RpZSBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzZiYWU1YmZmMjA4YTRhZjU4NzdlZjIzOTIxMzcwNzczLnNldENvbnRlbnQoaHRtbF9jOWM3ZDg4NzQ0YmE0MzMzODQ4NWE4MTJmYmFiOWE3Mik7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl85YTlmYjNkMzQ4OWY0OWEyODNmZjM1NTQ2MzZmYzBkOS5iaW5kUG9wdXAocG9wdXBfNmJhZTViZmYyMDhhNGFmNTg3N2VmMjM5MjEzNzA3NzMpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfYjk0Y2M0OWZkYzQ1NGUxZWJhYjI3NWMxMjQ4NWNkMDQgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NjkwMDUxMDAwMDAwMSwtNzkuNDQyMjU5M10sCiAgICAgICAgICAgICAgICB7CiAgImJ1YmJsaW5nTW91c2VFdmVudHMiOiB0cnVlLAogICJjb2xvciI6ICIjZmYwMDAwIiwKICAiZGFzaEFycmF5IjogbnVsbCwKICAiZGFzaE9mZnNldCI6IG51bGwsCiAgImZpbGwiOiB0cnVlLAogICJmaWxsQ29sb3IiOiAiI2ZmMDAwMCIsCiAgImZpbGxPcGFjaXR5IjogMC43LAogICJmaWxsUnVsZSI6ICJldmVub2RkIiwKICAibGluZUNhcCI6ICJyb3VuZCIsCiAgImxpbmVKb2luIjogInJvdW5kIiwKICAib3BhY2l0eSI6IDEuMCwKICAicmFkaXVzIjogNSwKICAic3Ryb2tlIjogdHJ1ZSwKICAid2VpZ2h0IjogMwp9CiAgICAgICAgICAgICAgICApLmFkZFRvKG1hcF80ZmVkYTczOTU1Mzg0MmI0YjkwN2QzMjdlNDVjN2M5Nyk7CiAgICAgICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBwb3B1cF9lMzI3NTMwYmUxZDg0MjM3YjJlMTdmNGRmN2Y4YjNlNyA9IEwucG9wdXAoe21heFdpZHRoOiAnMzAwJ30pOwoKICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB2YXIgaHRtbF8yYzMyZjJkZmYyNDg0ZTYxODA1NzFlNzEyZWU2NDQyMSA9ICQoJzxkaXYgaWQ9Imh0bWxfMmMzMmYyZGZmMjQ4NGU2MTgwNTcxZTcxMmVlNjQ0MjEiIHN0eWxlPSJ3aWR0aDogMTAwLjAlOyBoZWlnaHQ6IDEwMC4wJTsiPkRvdmVyY291cnQgVmlsbGFnZSwgRHVmZmVyaW4gQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF9lMzI3NTMwYmUxZDg0MjM3YjJlMTdmNGRmN2Y4YjNlNy5zZXRDb250ZW50KGh0bWxfMmMzMmYyZGZmMjQ4NGU2MTgwNTcxZTcxMmVlNjQ0MjEpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYjk0Y2M0OWZkYzQ1NGUxZWJhYjI3NWMxMjQ4NWNkMDQuYmluZFBvcHVwKHBvcHVwX2UzMjc1MzBiZTFkODQyMzdiMmUxN2Y0ZGY3ZjhiM2U3KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzE3MDRiMzc4MzFjZTRkMWZiZGViZGFlMTExMzhkMWVmID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjQ3OTI2NzAwMDAwMDA2LC03OS40MTk3NDk3XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwX2JmZDNhMDJlM2JmZTRkYzJiOGU2OWQ3YzMzNTIxZmUwID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzk2ZTMyZTQ3ZDUzMjRjYzQ4OTIwNjE5YjkyNTFhNjEyID0gJCgnPGRpdiBpZD0iaHRtbF85NmUzMmU0N2Q1MzI0Y2M0ODkyMDYxOWI5MjUxYTYxMiIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+TGl0dGxlIFBvcnR1Z2FsLCBUcmluaXR5IENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfYmZkM2EwMmUzYmZlNGRjMmI4ZTY5ZDdjMzM1MjFmZTAuc2V0Q29udGVudChodG1sXzk2ZTMyZTQ3ZDUzMjRjYzQ4OTIwNjE5YjkyNTFhNjEyKTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyXzE3MDRiMzc4MzFjZTRkMWZiZGViZGFlMTExMzhkMWVmLmJpbmRQb3B1cChwb3B1cF9iZmQzYTAyZTNiZmU0ZGMyYjhlNjlkN2MzMzUyMWZlMCk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl8wYThhZTZjNTBhNzE0NDJlYjc1YTc5MmI2MmYxYjhiMCA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjYzNjg0NzIsLTc5LjQyODE5MTQwMDAwMDAyXSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzg5Y2VlOGQ0NmIxMDRkOTRhZjM5MGQwODJjYmUyNWY5ID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzE4YTQ0NzU0NGRjZDRkYTM4ZWM5MzFkYThlN2JjMmEwID0gJCgnPGRpdiBpZD0iaHRtbF8xOGE0NDc1NDRkY2Q0ZGEzOGVjOTMxZGE4ZTdiYzJhMCIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+QnJvY2t0b24sIEV4aGliaXRpb24gUGxhY2UsIFBhcmtkYWxlIFZpbGxhZ2UgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF84OWNlZThkNDZiMTA0ZDk0YWYzOTBkMDgyY2JlMjVmOS5zZXRDb250ZW50KGh0bWxfMThhNDQ3NTQ0ZGNkNGRhMzhlYzkzMWRhOGU3YmMyYTApOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfMGE4YWU2YzUwYTcxNDQyZWI3NWE3OTJiNjJmMWI4YjAuYmluZFBvcHVwKHBvcHVwXzg5Y2VlOGQ0NmIxMDRkOTRhZjM5MGQwODJjYmUyNWY5KTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyXzM2ZmI2NWE4ZTZmMDRjODk4MTIxYTQwYjNlODFkZmU5ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjYxNjA4MywtNzkuNDY0NzYzMjk5OTk5OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfMmViYTM1N2Q5MmYxNDYxMWFmNDZkOWZkZDg1MDg2YWUgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfYmNmNGRlMmM1M2U1NDRkN2FhMTZjYTkxNGJmZDA4YzQgPSAkKCc8ZGl2IGlkPSJodG1sX2JjZjRkZTJjNTNlNTQ0ZDdhYTE2Y2E5MTRiZmQwOGM0IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5IaWdoIFBhcmssIFRoZSBKdW5jdGlvbiBTb3V0aCBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzJlYmEzNTdkOTJmMTQ2MTFhZjQ2ZDlmZGQ4NTA4NmFlLnNldENvbnRlbnQoaHRtbF9iY2Y0ZGUyYzUzZTU0NGQ3YWExNmNhOTE0YmZkMDhjNCk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl8zNmZiNjVhOGU2ZjA0Yzg5ODEyMWE0MGIzZTgxZGZlOS5iaW5kUG9wdXAocG9wdXBfMmViYTM1N2Q5MmYxNDYxMWFmNDZkOWZkZDg1MDg2YWUpOwoKICAgICAgICAgICAgCiAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIGNpcmNsZV9tYXJrZXJfYjExNjA2ZDU4OGExNDRlMjgyMzk1NDM5NDZlYTFkNGYgPSBMLmNpcmNsZU1hcmtlcigKICAgICAgICAgICAgICAgIFs0My42NDg5NTk3LC03OS40NTYzMjVdLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfZTM1ZmFjOTdhNTE1NDUzY2FiMDQzNTc0MzQ0NjE2MjkgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfZGNkYzZkOGZkNjNhNGI3NTg0YWIyNWFmMDViMThhYjQgPSAkKCc8ZGl2IGlkPSJodG1sX2RjZGM2ZDhmZDYzYTRiNzU4NGFiMjVhZjA1YjE4YWI0IiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5QYXJrZGFsZSwgUm9uY2VzdmFsbGVzIENsdXN0ZXIgMDwvZGl2PicpWzBdOwogICAgICAgICAgICAgICAgcG9wdXBfZTM1ZmFjOTdhNTE1NDUzY2FiMDQzNTc0MzQ0NjE2Mjkuc2V0Q29udGVudChodG1sX2RjZGM2ZDhmZDYzYTRiNzU4NGFiMjVhZjA1YjE4YWI0KTsKICAgICAgICAgICAgCgogICAgICAgICAgICBjaXJjbGVfbWFya2VyX2IxMTYwNmQ1ODhhMTQ0ZTI4MjM5NTQzOTQ2ZWExZDRmLmJpbmRQb3B1cChwb3B1cF9lMzVmYWM5N2E1MTU0NTNjYWIwNDM1NzQzNDQ2MTYyOSk7CgogICAgICAgICAgICAKICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgY2lyY2xlX21hcmtlcl9jNGUyZGJiNDlkZGI0ZjExYTkxZDM1ZGE4M2VjNmVkOSA9IEwuY2lyY2xlTWFya2VyKAogICAgICAgICAgICAgICAgWzQzLjY1MTU3MDYsLTc5LjQ4NDQ0OTldLAogICAgICAgICAgICAgICAgewogICJidWJibGluZ01vdXNlRXZlbnRzIjogdHJ1ZSwKICAiY29sb3IiOiAiI2ZmMDAwMCIsCiAgImRhc2hBcnJheSI6IG51bGwsCiAgImRhc2hPZmZzZXQiOiBudWxsLAogICJmaWxsIjogdHJ1ZSwKICAiZmlsbENvbG9yIjogIiNmZjAwMDAiLAogICJmaWxsT3BhY2l0eSI6IDAuNywKICAiZmlsbFJ1bGUiOiAiZXZlbm9kZCIsCiAgImxpbmVDYXAiOiAicm91bmQiLAogICJsaW5lSm9pbiI6ICJyb3VuZCIsCiAgIm9wYWNpdHkiOiAxLjAsCiAgInJhZGl1cyI6IDUsCiAgInN0cm9rZSI6IHRydWUsCiAgIndlaWdodCI6IDMKfQogICAgICAgICAgICAgICAgKS5hZGRUbyhtYXBfNGZlZGE3Mzk1NTM4NDJiNGI5MDdkMzI3ZTQ1YzdjOTcpOwogICAgICAgICAgICAKICAgIAogICAgICAgICAgICB2YXIgcG9wdXBfODg3OTA4NGYwNzU2NDU4ZWFhYTc3ZGVkMzc2MmZjYjIgPSBMLnBvcHVwKHttYXhXaWR0aDogJzMwMCd9KTsKCiAgICAgICAgICAgIAogICAgICAgICAgICAgICAgdmFyIGh0bWxfZmFhOWU5NjQzZjY0NDQxNjllNjUxM2JiNWVlNzEyOWEgPSAkKCc8ZGl2IGlkPSJodG1sX2ZhYTllOTY0M2Y2NDQ0MTY5ZTY1MTNiYjVlZTcxMjlhIiBzdHlsZT0id2lkdGg6IDEwMC4wJTsgaGVpZ2h0OiAxMDAuMCU7Ij5SdW5ueW1lZGUsIFN3YW5zZWEgQ2x1c3RlciAwPC9kaXY+JylbMF07CiAgICAgICAgICAgICAgICBwb3B1cF84ODc5MDg0ZjA3NTY0NThlYWFhNzdkZWQzNzYyZmNiMi5zZXRDb250ZW50KGh0bWxfZmFhOWU5NjQzZjY0NDQxNjllNjUxM2JiNWVlNzEyOWEpOwogICAgICAgICAgICAKCiAgICAgICAgICAgIGNpcmNsZV9tYXJrZXJfYzRlMmRiYjQ5ZGRiNGYxMWE5MWQzNWRhODNlYzZlZDkuYmluZFBvcHVwKHBvcHVwXzg4NzkwODRmMDc1NjQ1OGVhYWE3N2RlZDM3NjJmY2IyKTsKCiAgICAgICAgICAgIAogICAgICAgIAogICAgCiAgICAgICAgICAgIHZhciBjaXJjbGVfbWFya2VyX2I1MDQyNzNjYzlhNjQ0YmY5Nzk3YTZiOWJjZDc0YWU0ID0gTC5jaXJjbGVNYXJrZXIoCiAgICAgICAgICAgICAgICBbNDMuNjYyNzQzOSwtNzkuMzIxNTU4XSwKICAgICAgICAgICAgICAgIHsKICAiYnViYmxpbmdNb3VzZUV2ZW50cyI6IHRydWUsCiAgImNvbG9yIjogIiNmZjAwMDAiLAogICJkYXNoQXJyYXkiOiBudWxsLAogICJkYXNoT2Zmc2V0IjogbnVsbCwKICAiZmlsbCI6IHRydWUsCiAgImZpbGxDb2xvciI6ICIjZmYwMDAwIiwKICAiZmlsbE9wYWNpdHkiOiAwLjcsCiAgImZpbGxSdWxlIjogImV2ZW5vZGQiLAogICJsaW5lQ2FwIjogInJvdW5kIiwKICAibGluZUpvaW4iOiAicm91bmQiLAogICJvcGFjaXR5IjogMS4wLAogICJyYWRpdXMiOiA1LAogICJzdHJva2UiOiB0cnVlLAogICJ3ZWlnaHQiOiAzCn0KICAgICAgICAgICAgICAgICkuYWRkVG8obWFwXzRmZWRhNzM5NTUzODQyYjRiOTA3ZDMyN2U0NWM3Yzk3KTsKICAgICAgICAgICAgCiAgICAKICAgICAgICAgICAgdmFyIHBvcHVwXzhmNzliZGVjZTMyNDQ1MmNhMWRiMTMyZDc1OTFmNjdlID0gTC5wb3B1cCh7bWF4V2lkdGg6ICczMDAnfSk7CgogICAgICAgICAgICAKICAgICAgICAgICAgICAgIHZhciBodG1sXzVmNzliZjc0N2E1OTQ2MzY5Mjc4MGE4OGU0NmNlNGI1ID0gJCgnPGRpdiBpZD0iaHRtbF81Zjc5YmY3NDdhNTk0NjM2OTI3ODBhODhlNDZjZTRiNSIgc3R5bGU9IndpZHRoOiAxMDAuMCU7IGhlaWdodDogMTAwLjAlOyI+QnVzaW5lc3MgUmVwbHkgTWFpbCBQcm9jZXNzaW5nIENlbnRyZSA5NjkgRWFzdGVybiBDbHVzdGVyIDA8L2Rpdj4nKVswXTsKICAgICAgICAgICAgICAgIHBvcHVwXzhmNzliZGVjZTMyNDQ1MmNhMWRiMTMyZDc1OTFmNjdlLnNldENvbnRlbnQoaHRtbF81Zjc5YmY3NDdhNTk0NjM2OTI3ODBhODhlNDZjZTRiNSk7CiAgICAgICAgICAgIAoKICAgICAgICAgICAgY2lyY2xlX21hcmtlcl9iNTA0MjczY2M5YTY0NGJmOTc5N2E2YjliY2Q3NGFlNC5iaW5kUG9wdXAocG9wdXBfOGY3OWJkZWNlMzI0NDUyY2ExZGIxMzJkNzU5MWY2N2UpOwoKICAgICAgICAgICAgCiAgICAgICAgCjwvc2NyaXB0Pg==\" 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 0x7fc2b4aea860>"
]
},
"execution_count": 119,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# create map\n",
"map_clusters = folium.Map(location=[latitude, longitude], zoom_start=11)\n",
"\n",
"# set color scheme for the clusters\n",
"x = np.arange(kclusters)\n",
"ys = [i + x + (i*x)**2 for i in range(kclusters)]\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, poi, cluster in zip(toronto_merged['Latitude'], toronto_merged['Longitude'], toronto_merged['Neighbourhood'], toronto_merged['Cluster Labels']):\n",
" label = folium.Popup(str(poi) + ' Cluster ' + str(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": "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.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment