Introduction

This RMarkdown document is part of my Workshop Course in R. The intent is to build Skill in coding in R, and also appreciate R as a way to metaphorically visualize information of various kinds, using predominantly geometric figures and structures.

All RMarkdown/Quarto files combine code, text, web-images, and figures developed using code. Everything is text; code chunks are enclosed in fences (```)

Goals

At the end of this Lab session, we should:
- know the types and structures of spatial data and be able to work with them
- understand the basics of modern spatial packages in R
- be able to specify and download spatial data from the web, using R from sources such as naturalearth and Open Streep Map
- plot static and interactive maps using ggplot, tmap and leaflet packages
- add symbols and markers for places and regions of our own interest in these maps.
- plot maps on a globe using the threejs package

Pedagogical Note

The method followed will be based on PRIMM:

  • PREDICT Inspect the code and guess at what the code might do, write predictions
  • RUN the code provided and check what happens
  • INFER what the parameters of the code do and write comments to explain. What bells and whistles can you see?
  • MODIFY the parameters code provided to understand the options available. Write comments to show what you have aimed for and achieved.
  • MAKE : take an idea/concept of your own, and graph it.

All jargon words will be capitalized and in bold font.

Set Up

The setup code chunk below brings into our coding session R packages that provide specific computational abilities and also datasets which we can use.

To reiterate: Packages and datasets are not the same thing !! Packages are (small) collections of programs. Datasets are just….information.

Setup the Packages

Install all packages that are flagged by RStudio when you open this RMarkdown file!

library(rnaturalearth)
library(rnaturalearthdata)

# Run this in your console first
# devtools::install_github("ropensci/rnaturalearthhires")
library(rnaturalearthhires)

# Plotting Maps
library(tidyverse) # Maps using ggplot + geom_sf
library(tmap) # Thematic Maps, static and interactive
library(osmdata) # Fetch map data from osmdata.org
library(leaflet) # interactive Maps
library(threejs) # Globe maps in R. Part of the htmlwidgets family of packages

# For Spatial Data Frame Processing
library(sf)

Introduction to Maps in R

We will take small steps in making maps using just two of the several map making packages in R.

The steps we will use are:

  1. Search for an area of interest
  2. Learn how to access spatial/map data using osmdata
  3. Plot and dress up our map using ggplot and tmap
  4. Create interactive maps with leaflet using a variety of map data providers. (Note: tmap can also do interactive maps which we will explore also.)

Bas. Onwards and Map-wards!!

Step1 - Specifying an area of interest

In R, we need to specify a “BOUNDING BOX” first, to declare our area of interest. God made me a BengaluR-kaR…I think..Let’s see if we can declare an area of interest. Then we can order on Swiggy and…never mind.

We can declare a BOUNDING BOX in several ways.

  1. Using a longitude latitude info from Bounding Box Tool which gives bounding boxes in many different formats.
  • Locate the place of interest using the search box.
  • click on the “box with arrow” tool on the upper left. This will create a rectangular shape.
  • Move/resize this box and then copy the bounding box from the menu at the bottom. Ensure you copy in CSV format.
# https://boundingbox.klokantech.com
# CSV: 77.574028,12.917262,77.595073,12.939895
bbox_1 <- matrix(
  c(77.574028, 12.917262, 77.595073, 12.939895),
  byrow = FALSE,
  nrow = 2,
  ncol = 2,
  dimnames = list(c('x', 'y'), c('min', 'max'))
)
bbox_1
##        min      max
## x 77.57403 77.59507
## y 12.91726 12.93989
  1. Using a place name to look up a BOUNDING BOX with osmdata::getbb. This may not always work if the place name is know well known.
# Using getbb command from the osmdata package
bbox_2 <- osmdata::getbb("Jayanagar, Bangalore, India")
bbox_2
##        min      max
## x 77.56242 77.60242
## y 12.90927 12.94927

Let us examine both the calculated BOUNDING BOXes:

bbox_1
##        min      max
## x 77.57403 77.59507
## y 12.91726 12.93989
bbox_2
##        min      max
## x 77.56242 77.60242
## y 12.90927 12.94927

Both look similar in size; bbox_2 is slightly bigger.

We will use the bbox_2 from the above, to ensure we have a decent collection of features. If the download becomes too hefty, we can fall back on the smaller bbox!

Step2 - Get Map data

OpenStreetMap (OSM) provides maps of the world mostly created by volunteers. They are completely free to browse and use, with attribution to © OpenStreetMap contributors and adherence to the ODbL license required, and are used by many public and private organisations. OSM data can be downloaded in vector format and used for our own purposes. In this tutorial, we will obtain data from OSM using a query. A query is a request for data from a database. Simple queries can be performed more easily using the osmdata library for R, which automatically constructs the query and imports the data in a convenient format.

Open Street Map features have attributes in key-value pairs. We can use them to download the specific data we need. These features can easily be explored in the web browser, by using the ‘Query features’ button on OpenStreetMap (OSM):

Head off to OSM Street Map to try this out and to get an intuitive understanding of what OSM key-value pairs are, for different types of map features. Look for places of interest to you (features) and see what key-value pairs attach to those features.

NOTE: key-value pairs are also referred to as tags.

Useful key-value pairs / tags include:

KEY VALUEs
building yes (all), house residential, apartments
highway residential, service, track, unclassified, footway, path
amenity parking, parking_space, bench; place_of_worship; restaurant, cafe, fast_food; school, waste_basket, fuel, bank, toilets…
shop convenience, supermarket, clothes, hairdresser, car-repair…
name actual name of the place e.g. Main_Street, McDonald’s, Pizza Hut, Subway
waterway
natural
boundary

For more information see:OSM Tags for a nice visual description of popular key-value pairs that we can use. See what the highway tag looks like tag : highway

The osmdata commands available_features and available_tags can help also us get the associated *key-value** pairs to retrieve data from OSM.

osmdata::available_features() %>% as_tibble()
available_tags(feature = "highway")
available_tags("amenity")
available_tags("natural")

We can use these key-value pairs to download different types of map data. Within our bbox for Jayanagar, Bangalore, we want to download diverse kinds of FEATURE data. Remember that a FEATURE is any object that can be “seen” on a map. This is done using the OPQ query in the osmdata package. The main parameters for this command are:

  • bbox
  • KEY / VALUE pairs (“TAGS”) to specify the kind of feature you need

The query returns a list data structure, with all geometries and features within the bounding box, and we can use any or all of them. Now we know the map features we are interested in. We also know what key-value pairs will be used to get this info from OSM.

Data Downloads from OSM

Do not run these commands too many times. Re-run this ONLY if you have changed your BOUNDING BOX.. We will get our map data from OSM and then save it avoid repeated downloads. So, please copy/paste and run the following commands in your console. The chunk below is set to eval:false so it will not run when you render!

# Eval is set to false here
# This code is for reference
# Run these commands ONCE in your Console
# Or run this chunk manually one time

# Get all restaurants, atms, colleges within my bbox
locations <- 
  osmdata::opq(bbox = bbox_2) %>% 
  osmdata::add_osm_feature(key = "amenity", 
                           value = c("restaurant", "atm", "college")) %>% 
  osmdata_sf() %>%  # Convert to Simple Features format
  purrr::pluck("osm_points") # Pull out the data frame of interest

# Get all buildings within my bbox
dat_buildings <-
  osmdata::opq(bbox = bbox_2) %>% 
  osmdata::add_osm_feature(key = "building") %>% 
  osmdata_sf() %>% 
  purrr::pluck("osm_polygons") 

# Get all residential roads within my bbox
dat_roads <- 
  osmdata::opq(bbox = bbox_2) %>% 
  osmdata::add_osm_feature(key = "highway", 
                           value = c("residential")) %>% 
  osmdata_sf() %>% 
  purrr::pluck("osm_lines") 

# Get all parks / natural /greenery areas and spots within my bbox
dat_natural <-   
  osmdata::opq(bbox = bbox_2) %>% 
  osmdata::add_osm_feature(key = "natural",
                           value = c("tree", "water", "wood")) %>% 
  osmdata_sf()
dat_natural

dat_trees <- 
  dat_natural %>% 
  purrr::pluck("osm_points") 

dat_greenery <- 
  dat_natural %>% pluck("osm_polygons")

Let us save this data, so we don’t need to download all this again! We will store the downloaded data as .gpkg files on our local hard drives to use when we run this file again later. We will name our stored files as buildings, roads, and greenery, and trees, each with the .gpkg file extension, e.g. trees.gpkg.

Check your local project folder for these files after executing these commands.

# Eval is set to false here
# This code is for reference
# Run these commands ONCE in your Console
# Or manually run this chunk once

st_write(dat_roads, dsn = "roads.gpkg", 
         append = FALSE, quiet = FALSE)

st_write(dat_buildings, 
         dsn = "buildings.gpkg", 
         append = FALSE, 
         quiet = FALSE)

st_write(dat_greenery, dsn = "greenery.gpkg", 
         append = FALSE,quiet = FALSE)

st_write(dat_trees, dsn = "trees.gpkg", 
         append = FALSE,quiet = FALSE)

Always work from here to avoid repeated downloads from OSM. Start from the top ONLY if you intend to map new locations and need to modify your Bounding Box.

Let us now read back the saved Data:

buildings <- st_read("./buildings.gpkg")
## Reading layer `buildings' from data source 
##   `/Users/arvindv/RWork/MyWebsites/the-foundation-series/static/labs/R-for-Artists/06-spatial/buildings.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 34766 features and 89 fields
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: 77.56221 ymin: 12.90906 xmax: 77.60373 ymax: 12.9497
## Geodetic CRS:  WGS 84
greenery <- st_read("./greenery.gpkg")
## Reading layer `greenery' from data source 
##   `/Users/arvindv/RWork/MyWebsites/the-foundation-series/static/labs/R-for-Artists/06-spatial/greenery.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 2 features and 2 fields
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: 77.56776 ymin: 12.91751 xmax: 77.57392 ymax: 12.94811
## Geodetic CRS:  WGS 84
trees <- st_read("./trees.gpkg")
## Reading layer `trees' from data source 
##   `/Users/arvindv/RWork/MyWebsites/the-foundation-series/static/labs/R-for-Artists/06-spatial/trees.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 153 features and 9 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: 77.56566 ymin: 12.90806 xmax: 77.60096 ymax: 12.94914
## Geodetic CRS:  WGS 84
roads <- st_read("./roads.gpkg")
## Reading layer `roads' from data source 
##   `/Users/arvindv/RWork/MyWebsites/the-foundation-series/static/labs/R-for-Artists/06-spatial/roads.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 2242 features and 28 fields
## Geometry type: LINESTRING
## Dimension:     XY
## Bounding box:  xmin: 77.55895 ymin: 12.90635 xmax: 77.60603 ymax: 12.95636
## Geodetic CRS:  WGS 84

How many rows? ( Rows -> Features ) What kind of geom column in each data set?

# How many buildings?
nrow(buildings)
## [1] 34766
buildings$geom
## Geometry set for 34766 features 
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: 77.56221 ymin: 12.90906 xmax: 77.60373 ymax: 12.9497
## Geodetic CRS:  WGS 84
## First 5 geometries:
## POLYGON ((77.58405 12.93005, 77.5845 12.93005, ...
## POLYGON ((77.57568 12.9199, 77.57592 12.9199, 7...
## POLYGON ((77.59592 12.94016, 77.59676 12.94022,...
## POLYGON ((77.5937 12.94011, 77.59458 12.94015, ...
## POLYGON ((77.59321 12.94042, 77.59321 12.94035,...
class(buildings$geom)
## [1] "sfc_POLYGON" "sfc"

So the buildings dataset has 34766 buildings and their geometry is naturally a POLYGON type of geometry column.

Do this check for all the other spatial data, in the code chunk below. What kind of geom column does each dataset have?

My first Map in R

There are two ways of plotting maps that we will learn:

ggplot and geom_sf()

First we will plot with ggplot and geom_sf() : recall that our data is stored in 5 files: buildings, parks, roads, trees, and greenery.

ggplot() +
  geom_sf(data = buildings, fill = "gold", color = "grey", linewidth = 0.025) +    # POLYGONS
  geom_sf(data = roads, color = '#ff9999', linewidth = 0.5) +        # LINES
  geom_sf(data = greenery, col = "darkseagreen") +  # POLYGONS
  geom_sf(data = trees, col = "darkgreen")  +       # POINTS
  
  # Set plot limits to exactly the bbox_2
  coord_sf(xlim = c(bbox_2[1,1], bbox_2[1,2]),
           ylim = c(bbox_2[2,1], bbox_2[2,2]),
           expand = FALSE) + 
  theme_minimal()

Note how geom_sf is capable of handling any geometry in the sfc column !!

geom_sf() is an unusual geom because it will draw different geometric objects depending on what simple features are present in the data: you can get points, lines, or polygons.

So there, we have our first map!

Map using tmap package

We can also create a map using a package called tmap. Here we also have the option of making the map interactive. tmap plots are made with code in “groups”: each group starts with a tm_shape() command.

# Group-1
tm_shape(buildings) +
  tm_fill(col = "burlywood") +

#Group-2
tm_shape(roads) +
  tm_lines(col = "grey20") +

#Group-3  
tm_shape(greenery) +
  tm_polygons(col = "limegreen") +
  

#Group-4
tm_shape(trees) +
  tm_dots(col = "darkgreen")

How do we make this map interactive? One more line of code !! Add this line in your console and then run the above chunk again

tmap_mode("view")

Using data from tmap

Like many other packages ( e.g. ggplot ) tmap also has a few built-in spatial datasets: World and metro, rivers, land and a few others. Check help on these. Let’s plot a first map using datasets built into tmap.

data("World")
head(World, n = 3)

We have several 14 attribute variables in World. Attribute variables such as gdp_cap_est, HPI are numeric. Others such as income_grp appear to be factors. iso_a3 is the standard three letter name for the country. name is of course, the name for each country!

data("metro")
head(metro, n = 3)
## old-style crs object detected; please recreate object with a recent sf::st_crs()
## old-style crs object detected; please recreate object with a recent sf::st_crs()

Here too we have attribute variables for the metros, and they seem predominantly numeric. Again iso_a3 is the three letter name for the city.

tmap_mode("plot") # Making this a static plot
## tmap mode set to plotting
# Group 1
tm_shape(World) + # dataset = World. 
    tm_polygons("HPI") + # Colour polygons by HPI numeric variable

  # Note the "+" sign continuation
  
# Group 2
tm_shape(metro) + # dataset = metro
  tm_bubbles(size = "pop2030", 
             col = "red") 
## old-style crs object detected; please recreate object with a recent sf::st_crs()
## old-style crs object detected; please recreate object with a recent sf::st_crs()
## old-style crs object detected; please recreate object with a recent sf::st_crs()

# Plot cities as bubbles
# Size proportional to numeric variable `pop2030`
tmap_mode("view") # Change to Interactive
## tmap mode set to interactive viewing
# Let's use WaterColor Map this time!!
tm_tiles("Stamen.Watercolor") + # Watercolor map only with interactive
tm_shape(World) +
    tm_polygons("HPI") + # Color by Happiness Index
  
  
tm_shape(metro) + 
  tm_bubbles(size = "pop2030", # Size City Markers by Population in 2020
             col = "red") 
## old-style crs object detected; please recreate object with a recent sf::st_crs()
## old-style crs object detected; please recreate object with a recent sf::st_crs()
## old-style crs object detected; please recreate object with a recent sf::st_crs()
## Warning: basemap Stamen.Watercolordoes not exist in the providers list nor does
## it seem a valid url
## Legend for symbol sizes not available in view mode.

Using data from rnaturalearth

The rnaturalearth package allows us to download shapes of countries. We can use it to get borders and also internal state/district boundaries.

india <- 
  ne_states(country =  "india", 
            returnclass = "sf") # gives a ready sf dataframe !

india_neighbours <- 
  ne_states(country = (c("sri lanka", "pakistan",
                         "afghanistan", "nepal","bangladesh", "bhutan")
                       ),
            returnclass = "sf")

Let’s look at the attribute variable columns to colour our graph and to shape our symbols:

names(india)
##   [1] "featurecla" "scalerank"  "adm1_code"  "diss_me"    "iso_3166_2"
##   [6] "wikipedia"  "iso_a2"     "adm0_sr"    "name"       "name_alt"  
##  [11] "name_local" "type"       "type_en"    "code_local" "code_hasc" 
##  [16] "note"       "hasc_maybe" "region"     "region_cod" "provnum_ne"
##  [21] "gadm_level" "check_me"   "datarank"   "abbrev"     "postal"    
##  [26] "area_sqkm"  "sameascity" "labelrank"  "name_len"   "mapcolor9" 
##  [31] "mapcolor13" "fips"       "fips_alt"   "woe_id"     "woe_label" 
##  [36] "woe_name"   "latitude"   "longitude"  "sov_a3"     "adm0_a3"   
##  [41] "adm0_label" "admin"      "geonunit"   "gu_a3"      "gn_id"     
##  [46] "gn_name"    "gns_id"     "gns_name"   "gn_level"   "gn_region" 
##  [51] "gn_a1_code" "region_sub" "sub_code"   "gns_level"  "gns_lang"  
##  [56] "gns_adm1"   "gns_region" "min_label"  "max_label"  "min_zoom"  
##  [61] "wikidataid" "name_ar"    "name_bn"    "name_de"    "name_en"   
##  [66] "name_es"    "name_fr"    "name_el"    "name_hi"    "name_hu"   
##  [71] "name_id"    "name_it"    "name_ja"    "name_ko"    "name_nl"   
##  [76] "name_pl"    "name_pt"    "name_ru"    "name_sv"    "name_tr"   
##  [81] "name_vi"    "name_zh"    "ne_id"      "name_he"    "name_uk"   
##  [86] "name_ur"    "name_fa"    "name_zht"   "FCLASS_ISO" "FCLASS_US" 
##  [91] "FCLASS_FR"  "FCLASS_RU"  "FCLASS_ES"  "FCLASS_CN"  "FCLASS_TW" 
##  [96] "FCLASS_IN"  "FCLASS_NP"  "FCLASS_PK"  "FCLASS_DE"  "FCLASS_GB" 
## [101] "FCLASS_BR"  "FCLASS_IL"  "FCLASS_PS"  "FCLASS_SA"  "FCLASS_EG" 
## [106] "FCLASS_MA"  "FCLASS_PT"  "FCLASS_AR"  "FCLASS_JP"  "FCLASS_KO" 
## [111] "FCLASS_VN"  "FCLASS_TR"  "FCLASS_ID"  "FCLASS_PL"  "FCLASS_GR" 
## [116] "FCLASS_IT"  "FCLASS_NL"  "FCLASS_SE"  "FCLASS_BD"  "FCLASS_UA" 
## [121] "FCLASS_TLC" "geometry"
names(india_neighbours)
##   [1] "featurecla" "scalerank"  "adm1_code"  "diss_me"    "iso_3166_2"
##   [6] "wikipedia"  "iso_a2"     "adm0_sr"    "name"       "name_alt"  
##  [11] "name_local" "type"       "type_en"    "code_local" "code_hasc" 
##  [16] "note"       "hasc_maybe" "region"     "region_cod" "provnum_ne"
##  [21] "gadm_level" "check_me"   "datarank"   "abbrev"     "postal"    
##  [26] "area_sqkm"  "sameascity" "labelrank"  "name_len"   "mapcolor9" 
##  [31] "mapcolor13" "fips"       "fips_alt"   "woe_id"     "woe_label" 
##  [36] "woe_name"   "latitude"   "longitude"  "sov_a3"     "adm0_a3"   
##  [41] "adm0_label" "admin"      "geonunit"   "gu_a3"      "gn_id"     
##  [46] "gn_name"    "gns_id"     "gns_name"   "gn_level"   "gn_region" 
##  [51] "gn_a1_code" "region_sub" "sub_code"   "gns_level"  "gns_lang"  
##  [56] "gns_adm1"   "gns_region" "min_label"  "max_label"  "min_zoom"  
##  [61] "wikidataid" "name_ar"    "name_bn"    "name_de"    "name_en"   
##  [66] "name_es"    "name_fr"    "name_el"    "name_hi"    "name_hu"   
##  [71] "name_id"    "name_it"    "name_ja"    "name_ko"    "name_nl"   
##  [76] "name_pl"    "name_pt"    "name_ru"    "name_sv"    "name_tr"   
##  [81] "name_vi"    "name_zh"    "ne_id"      "name_he"    "name_uk"   
##  [86] "name_ur"    "name_fa"    "name_zht"   "FCLASS_ISO" "FCLASS_US" 
##  [91] "FCLASS_FR"  "FCLASS_RU"  "FCLASS_ES"  "FCLASS_CN"  "FCLASS_TW" 
##  [96] "FCLASS_IN"  "FCLASS_NP"  "FCLASS_PK"  "FCLASS_DE"  "FCLASS_GB" 
## [101] "FCLASS_BR"  "FCLASS_IL"  "FCLASS_PS"  "FCLASS_SA"  "FCLASS_EG" 
## [106] "FCLASS_MA"  "FCLASS_PT"  "FCLASS_AR"  "FCLASS_JP"  "FCLASS_KO" 
## [111] "FCLASS_VN"  "FCLASS_TR"  "FCLASS_ID"  "FCLASS_PL"  "FCLASS_GR" 
## [116] "FCLASS_IT"  "FCLASS_NL"  "FCLASS_SE"  "FCLASS_BD"  "FCLASS_UA" 
## [121] "FCLASS_TLC" "geometry"
# Look only at attributes
india %>% st_drop_geometry() %>% head()
india_neighbours %>% st_drop_geometry() %>% head()

In the india data frame:
- Column iso_a2 contains the country name.
- Column name contains the name of the state

In the india_neighbours data frame:
- Column gu_a3 contains the country abbreviation
- Column name contains the name of the state
- Column iso_3166_2 contains the abbreviation of the state within each neighbouring country.

tmap_mode("view")
## tmap mode set to interactive viewing
# Plot India
  tm_shape(india) +
  tm_polygons("name", # Colour by States in India
              legend.show = FALSE) +
  
# Plot Neighbours
  tm_shape(india_neighbours) +
  tm_fill(col = "gu_a3") +  # Colour by Country Name
  
# Plot the cities in India alone
  tm_shape(metro %>% dplyr::filter(iso_a3 == "IND")) +
    
  tm_dots(size = "pop2020",legend.size.show = FALSE) +
    # size by population in 2020
    
  tm_layout(legend.show = FALSE) +
  tm_credits("Geographical Boundaries are not accurate",
             size = 0.5,
             position = "right") +
  tm_compass(position = c("right", "top")) +
  tm_scale_bar(position = "left") +
  tmap_style(style = "classic") 
## old-style crs object detected; please recreate object with a recent sf::st_crs()
## tmap style set to "classic"
## other available styles are: "white", "gray", "natural", "cobalt", "col_blind", "albatross", "beaver", "bw", "watercolor"
## Credits not supported in view mode.
## Compass not supported in view mode.
## Warning: Number of levels of the variable "name" is 36, which is larger than
## max.categories (which is 30), so levels are combined. Set
## tmap_options(max.categories = 36) in the layer function to show all levels.
#Try other map styles
#cobalt #gray #white #watercolor #beaver #classic #watercolor #albatross #bw #col_blind

Your Turn 2

Can you try to download a map area of your home town and plot it as we have above?

Adding my favourite Restaurants to the map

Is it time to order on Swiggy…

Let us adding interesting places to our map: say based on your favourite restaurants etc. We need restaurant data: lat/long + name + maybe type of restaurant. This can be manually created ( like all of OSMdata ) or if it is already there we can download using key-value pairs in our OSM data query.

Restaurants can be downloaded using key= "amenity", value = "restaurant" or "cafe" etc. There are also other tags to explore!Searching for McDonalds for instance…( key = "name", value = "McDonalds"). Since we want JUST their location, and not the restaurant BUILDINGs, we extract osm_points.

# Again, run these commands in your Console
dat_R <-
  osmdata::opq(bbox = bbox_2) %>% 
  osmdata::add_osm_feature(key = "amenity", 
                           value = c("restaurant")) %>% 
  osmdata_sf() %>% 
  purrr::pluck("osm_points") 

# Save the data for future use
write_sf(dat_R, dsn = "restaurants.gpkg",append = FALSE, quiet = FALSE)

Now reading the saved Restaurant Data

restaurants <- st_read("./restaurants.gpkg")
## Reading layer `restaurants' from data source 
##   `/Users/arvindv/RWork/MyWebsites/the-foundation-series/static/labs/R-for-Artists/06-spatial/restaurants.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 203 features and 33 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: 77.56373 ymin: 12.9105 xmax: 77.60104 ymax: 12.94917
## Geodetic CRS:  WGS 84

How many restaurants have we got?

restaurants %>% nrow()
## [1] 203

So the restaurants dataset has 203 restaurants and their geometry is naturally a POINT type of geometry column.

These are the names of columns in the Restaurant Data: Note the cuisine column.

glimpse(restaurants)
## Rows: 203
## Columns: 34
## $ osm_id             <chr> "595408703", "595409635", "595409636", "595409790",…
## $ name               <chr> "Ganesh Darshan", "Upahara Darshini", "Nagarjuna Ch…
## $ addr.city          <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ addr.housename     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ addr.housenumber   <chr> NA, NA, NA, NA, NA, NA, NA, "19/2", NA, NA, NA, NA,…
## $ addr.postcode      <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ addr.street        <chr> NA, NA, NA, NA, NA, NA, NA, "South End Main Road", …
## $ alt_name           <chr> NA, NA, NA, NA, NA, NA, NA, "Upahara Darshini", NA,…
## $ amenity            <chr> "restaurant", "restaurant", "restaurant", "restaura…
## $ building           <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ capacity           <chr> NA, "150", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ cuisine            <chr> NA, NA, "indian", "italian", NA, "indian", "indian"…
## $ delivery           <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ description        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ diet.vegetarian    <chr> NA, "only", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ email              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ food               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ internet_access    <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ level              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ name.en            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ name.kn            <chr> "ಗಣೇಶ ದರ್ಶಿನಿ", "ಉಪಹಾರ ದರ್ಶಿನಿ", "ನಾಗಾರ್ಜುನ ಚಿಮಿನಿ", "ಲಾ ಕಾಸಾ…
## $ note               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ opening_hours      <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ operator           <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ phone              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ smoking            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ source             <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ takeaway           <chr> NA, "yes", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ toilets.wheelchair <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "no…
## $ website            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ wheelchair         <chr> NA, NA, NA, NA, "no", NA, NA, NA, NA, NA, NA, "no",…
## $ wikidata           <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ wikipedia          <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ geom               <POINT [°]> POINT (77.58403 12.93092), POINT (77.58442 12…

So let us plot the restaurants as POINTs using the restaurants data we have downloaded. The cuisine attribute looks interesting; let us colour the POINT based on the cuisine offered at that restaurant.

So Let’s look therefore at the cuisine column!

# ( I want pizza...)
restaurants$cuisine %>% unique()
##  [1] NA                                       
##  [2] "indian"                                 
##  [3] "italian"                                
##  [4] "regional"                               
##  [5] "pizza"                                  
##  [6] "ice_cream"                              
##  [7] "chinese"                                
##  [8] "South_Indian"                           
##  [9] "Multi-cuisne"                           
## [10] "South_India"                            
## [11] "chicken;regional"                       
## [12] "arab"                                   
## [13] "indian;seafood;fine_dining"             
## [14] "fast_food"                              
## [15] "kebab;grill"                            
## [16] "chicken"                                
## [17] "chinese;sandwich;tea;indian;coffee_shop"
## [18] "indian,_japanese"                       
## [19] "indian;regional"

Big mess…many NAs, some double entries, separated by commas and semicolons….

The cuisine attribute:

Note: The cuisine variable has more than one entry for a given restaurant. We use tidyr::separate() to make multiple columns out of the cuisine column and retain the first one only. Since the entries are badly entered using both “;” and “,” we need to do this twice ;-() Bad Data entry!!

Let’s get one cuisine entry per restaurant, and drop off the ones that do not mention a cuisine at all:

restaurants <- restaurants %>% 
  drop_na(cuisine) %>% # Knock off nondescript restaurants
  
  # Some have more than one classification ;-()
  # Separated by semicolon or comma, so....
  separate_wider_delim(cols = cuisine, 
                       names = c("cuisine", NA, NA), 
                       delim = ";", 
                       too_few = "align_start",
                       too_many = "drop") %>% 
  separate_wider_delim(cols = cuisine, 
                       names = c("cuisine", NA, NA), 
                       delim = ",",
                       too_few = "align_start",
                       too_many = "drop")

# Finally good food?
restaurants$cuisine
##   [1] "indian"       "italian"      "indian"       "indian"       "regional"    
##   [6] "indian"       "pizza"        "regional"     "ice_cream"    "ice_cream"   
##  [11] "indian"       "chinese"      "chinese"      "indian"       "italian"     
##  [16] "regional"     "indian"       "indian"       "italian"      "regional"    
##  [21] "indian"       "chinese"      "indian"       "indian"       "indian"      
##  [26] "indian"       "indian"       "ice_cream"    "pizza"        "South_Indian"
##  [31] "regional"     "regional"     "Multi-cuisne" "South_India"  "indian"      
##  [36] "indian"       "chicken"      "arab"         "indian"       "regional"    
##  [41] "regional"     "regional"     "regional"     "regional"     "indian"      
##  [46] "indian"       "indian"       "indian"       "regional"     "regional"    
##  [51] "italian"      "regional"     "regional"     "regional"     "regional"    
##  [56] "regional"     "regional"     "regional"     "regional"     "regional"    
##  [61] "regional"     "regional"     "regional"     "fast_food"    "indian"      
##  [66] "regional"     "italian"      "regional"     "regional"     "regional"    
##  [71] "regional"     "regional"     "regional"     "italian"      "fast_food"   
##  [76] "regional"     "fast_food"    "regional"     "chinese"      "regional"    
##  [81] "regional"     "regional"     "regional"     "regional"     "regional"    
##  [86] "regional"     "regional"     "regional"     "regional"     "regional"    
##  [91] "regional"     "regional"     "regional"     "regional"     "regional"    
##  [96] "regional"     "regional"     "regional"     "regional"     "kebab"       
## [101] "chicken"      "chinese"      "indian"       "italian"      "indian"      
## [106] "indian"       "indian"

Looks clean! Each entry is only ONE and not multiple any more. Now let’s plot the Restaurants as POINTs:

# http://www.stat.columbia.edu/~tzheng/files/Rcolor.pdf
# 
ggplot() +
  geom_sf(data = buildings, colour = "burlywood1") +
  geom_sf(data = roads, colour = "gray80") +
  geom_sf(
    data = restaurants %>% drop_na(cuisine),
    aes(fill = cuisine, geometry = geom),
    colour = "black",
    shape = 21,
    size = 3
  ) +  
  # Set plot limits to exactly the bbox_2
  coord_sf(xlim = c(bbox_2[1,1], bbox_2[1,2]),
           ylim = c(bbox_2[2,1], bbox_2[2,2]),
           expand = FALSE) + 
  theme_minimal() + 
  theme(legend.position = "right") +
  labs(title = "Restaurants in South Central Bangalore",
       caption = "Based on osmdata")

We could have done a (much!) better job, by combining cuisines into simpler and fewer categories, ( South_India and South_Indian ), but that is for another day!!

By now we know that we can use geom_sf() multiple number of times with different datasets to create layered maps in R.

Some fancy stuff

Let us try making glob based maps with the package threejs. This package is one of the family of packages in the htmlwidgets group of packages. It allows the use of some ( famous!) JavaScript graphing libraries directly and natively in R.

globejs usage

The globejs command from the package threejs allows one to plot points, arcs and images on a globe in 3D. The globe can be rotated and and zoomed. Great Circles and historical routes are a good idea for this perhaps.

Refer to this page for more ideas http://bwlewis.github.io/rthreejs/globejs.html

We will generate some random locations and plot them on the 3D globe.

# Random Lats and Longs
lat <- rpois(10, 60) + rnorm(10, 80)
long <- rpois(10, 60) + rnorm(10, 10)

# Random "Spike" heights for each location. Population? Tourists? GDP?
value <- rpois(10, lambda = 80)
 
globejs(lat = lat, long = long)

As seen, “spikes” are created at the random lat-lon locations. We can control the height/width/colour of the spikes, as well as the initial view of the globe itself: zoom, location and so on

globejs(
  lat = lat,
  long = long,
  
  # random heights of the Spikes (!!) at lat-long combo
  value = value,
  color = "red",
  # Zoom factor, default is 35
  fov = 50
)
globejs(
  lat = lat,
  long = long,
  value = value,
  color = "red",
  pointsize = 4, # width of the columns
  # Zoom position
  fov = 35,
  # initial position of the globe
  rotationlat = 0.6, #  in RADIANS !!! Good Heavens!!
  rotationlong = 0.2 #  in RADIANS !!! Good Heavens!!
)
globejs(
  lat = lat,
  long = long,
  value = value,
  color = "red",
  pointsize = 4,
  fov = 35,
  rotationlat = 0.6,
  rotationlong = 0.2,
  lightcolor = "#aaeeff",
  emissive = "#0000ee",
  bodycolor = "#ffffff",
  bg = "grey"
)

Scope and Packages for Exploration!!

sfnetworks

mapsf

ggspatial

Assignments

  1. Draw a map of your home-town with your favourite restaurants shown. Pop-ups for each restaurant will win bonus points.

  2. Download bird migration data from movebank.org. Import these into R and plot a migration map using tmap. Include the graticule, compass, legend, and credits.

Inspiration

  1. Burkhart, Christian. n.d. “Streetmaps.” StreetMaps

  1. Making Vector Maps, Computing for the Social Sciences, Univ. of Chicago
LS0tCnRpdGxlOiAiTGFiLTA2OiBUaGUgR3JhbW1hciBvZiBNYXBzIgpzdWJ0aXRsZTogIldoZXJlIGlzIHRoZSBTZWNyZXQgR2FyZGVuPyIKYXV0aG9yOiAiQXJ2aW5kIFZlbmthdGFkcmkiCmRhdGU6IDIyL0FwcmlsLzIwMjEKbGFzdG1vZDogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCiAgICB0b2NfZGVwdGg6IDIKICAgIG51bWJlcl9zZWN0aW9uczogRkFMU0UKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCmFic3RyYWN0OiBQYXJ0IG9mIG15IFdvcmtzaG9wIGNvdXJzZSBvbiBSCi0tLQoKIyMgSW50cm9kdWN0aW9uCgpUaGlzIFJNYXJrZG93biBkb2N1bWVudCBpcyBwYXJ0IG9mIG15IFdvcmtzaG9wIENvdXJzZSBpbiBSLiBUaGUgaW50ZW50CmlzIHRvIGJ1aWxkIFNraWxsIGluIGNvZGluZyBpbiBSLCBhbmQgYWxzbyBhcHByZWNpYXRlIFIgYXMgYSB3YXkgdG8KbWV0YXBob3JpY2FsbHkgdmlzdWFsaXplIGluZm9ybWF0aW9uIG9mIHZhcmlvdXMga2luZHMsIHVzaW5nCnByZWRvbWluYW50bHkgZ2VvbWV0cmljIGZpZ3VyZXMgYW5kIHN0cnVjdHVyZXMuCgpBbGwgUk1hcmtkb3duL1F1YXJ0byBmaWxlcyBjb21iaW5lIGNvZGUsIHRleHQsIHdlYi1pbWFnZXMsIGFuZCBmaWd1cmVzCmRldmVsb3BlZCB1c2luZyBjb2RlLiBFdmVyeXRoaW5nIGlzIHRleHQ7IGNvZGUgY2h1bmtzIGFyZSBlbmNsb3NlZCBpbgoqKmZlbmNlcyoqIChcYFxgXGApCgojIyBHb2FscwoKQXQgdGhlIGVuZCBvZiB0aGlzIExhYiBzZXNzaW9uLCB3ZSBzaG91bGQ6XAotIGtub3cgdGhlIHR5cGVzIGFuZCBzdHJ1Y3R1cmVzIG9mIGBzcGF0aWFsIGRhdGFgIGFuZCBiZSBhYmxlIHRvIHdvcmsKd2l0aCB0aGVtXAotIHVuZGVyc3RhbmQgdGhlIGJhc2ljcyBvZiBtb2Rlcm4gc3BhdGlhbCBwYWNrYWdlcyBpbiBSXAotIGJlIGFibGUgdG8gc3BlY2lmeSBhbmQgZG93bmxvYWQgc3BhdGlhbCBkYXRhIGZyb20gdGhlIHdlYiwgdXNpbmcgUgpmcm9tIHNvdXJjZXMgc3VjaCBhcyBgbmF0dXJhbGVhcnRoYCBhbmQgYE9wZW4gU3RyZWVwIE1hcGBcCi0gcGxvdCAqc3RhdGljKiBhbmQgKmludGVyYWN0aXZlKiBtYXBzIHVzaW5nIGBnZ3Bsb3RgLCBgdG1hcGAgYW5kCmBsZWFmbGV0YCBwYWNrYWdlc1wKLSBhZGQgc3ltYm9scyBhbmQgbWFya2VycyBmb3IgcGxhY2VzIGFuZCByZWdpb25zIG9mIG91ciBvd24gaW50ZXJlc3QgaW4KdGhlc2UgbWFwcy5cCi0gcGxvdCBtYXBzIG9uIGEgZ2xvYmUgdXNpbmcgdGhlIGB0aHJlZWpzYCBwYWNrYWdlCgojIyBQZWRhZ29naWNhbCBOb3RlCgpUaGUgbWV0aG9kIGZvbGxvd2VkIHdpbGwgYmUgYmFzZWQgb24KW1BSSU1NXShodHRwczovL2Jsb2dzLmtjbC5hYy51ay9jc2VyLzIwMTcvMDkvMDEvcHJpbW0tYS1zdHJ1Y3R1cmVkLWFwcHJvYWNoLXRvLXRlYWNoaW5nLXByb2dyYW1taW5nLyk6CgotICAgKipQUkVESUNUKiogSW5zcGVjdCB0aGUgY29kZSBhbmQgZ3Vlc3MgYXQgd2hhdCB0aGUgY29kZSBtaWdodCBkbywKICAgICoqd3JpdGUgcHJlZGljdGlvbnMqKgotICAgKipSVU4qKiB0aGUgY29kZSBwcm92aWRlZCBhbmQgY2hlY2sgd2hhdCBoYXBwZW5zCi0gICAqKklORkVSKiogd2hhdCB0aGUgYHBhcmFtZXRlcnNgIG9mIHRoZSBjb2RlIGRvIGFuZCAqKndyaXRlIGNvbW1lbnRzCiAgICB0byBleHBsYWluKiouIFdoYXQgYmVsbHMgYW5kIHdoaXN0bGVzIGNhbiB5b3Ugc2VlPwotICAgKipNT0RJRlkqKiB0aGUgYHBhcmFtZXRlcnNgIGNvZGUgcHJvdmlkZWQgdG8gdW5kZXJzdGFuZCB0aGUKICAgIGBvcHRpb25zYCBhdmFpbGFibGUuICoqV3JpdGUgY29tbWVudHMqKiB0byBzaG93IHdoYXQgeW91IGhhdmUgYWltZWQKICAgIGZvciBhbmQgYWNoaWV2ZWQuCi0gICAqKk1BS0UqKiA6IHRha2UgYW4gaWRlYS9jb25jZXB0IG9mIHlvdXIgb3duLCBhbmQgZ3JhcGggaXQuCgpBbGwgamFyZ29uIHdvcmRzIHdpbGwgYmUgY2FwaXRhbGl6ZWQgYW5kIGluICoqYm9sZCoqIGZvbnQuCgojIyBTZXQgVXAKClRoZSBgc2V0dXBgIGNvZGUgKipjaHVuayoqIGJlbG93IGJyaW5ncyBpbnRvIG91ciBjb2Rpbmcgc2Vzc2lvbiAqKlIKcGFja2FnZXMqKiB0aGF0IHByb3ZpZGUgc3BlY2lmaWMgY29tcHV0YXRpb25hbCBhYmlsaXRpZXMgYW5kIGFsc28KKipkYXRhc2V0cyoqIHdoaWNoIHdlIGNhbiB1c2UuCgpUbyByZWl0ZXJhdGU6IFBhY2thZ2VzIGFuZCBkYXRhc2V0cyBhcmUgKipub3QqKiB0aGUgc2FtZSB0aGluZyAhIQpQYWNrYWdlcyBhcmUgKHNtYWxsKSBjb2xsZWN0aW9ucyBvZiBwcm9ncmFtcy4gRGF0YXNldHMgYXJlCmp1c3QuLi4uaW5mb3JtYXRpb24uCgojIyBTZXR1cCB0aGUgUGFja2FnZXMKSW5zdGFsbCBhbGwgcGFja2FnZXMgdGhhdCBhcmUgZmxhZ2dlZCBieSBSU3R1ZGlvIHdoZW4geW91IG9wZW4gdGhpcyBSTWFya2Rvd24gZmlsZSEKCmBgYHtyfQojfCBsYWJlbDogc2V0dXAKI3wgbWVzc2FnZTogZmFsc2UKbGlicmFyeShybmF0dXJhbGVhcnRoKQpsaWJyYXJ5KHJuYXR1cmFsZWFydGhkYXRhKQoKIyBSdW4gdGhpcyBpbiB5b3VyIGNvbnNvbGUgZmlyc3QKIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInJvcGVuc2NpL3JuYXR1cmFsZWFydGhoaXJlcyIpCmxpYnJhcnkocm5hdHVyYWxlYXJ0aGhpcmVzKQoKIyBQbG90dGluZyBNYXBzCmxpYnJhcnkodGlkeXZlcnNlKSAjIE1hcHMgdXNpbmcgZ2dwbG90ICsgZ2VvbV9zZgpsaWJyYXJ5KHRtYXApICMgVGhlbWF0aWMgTWFwcywgc3RhdGljIGFuZCBpbnRlcmFjdGl2ZQpsaWJyYXJ5KG9zbWRhdGEpICMgRmV0Y2ggbWFwIGRhdGEgZnJvbSBvc21kYXRhLm9yZwpsaWJyYXJ5KGxlYWZsZXQpICMgaW50ZXJhY3RpdmUgTWFwcwpsaWJyYXJ5KHRocmVlanMpICMgR2xvYmUgbWFwcyBpbiBSLiBQYXJ0IG9mIHRoZSBodG1sd2lkZ2V0cyBmYW1pbHkgb2YgcGFja2FnZXMKCiMgRm9yIFNwYXRpYWwgRGF0YSBGcmFtZSBQcm9jZXNzaW5nCmxpYnJhcnkoc2YpCgpgYGAKCiMjIEludHJvZHVjdGlvbiB0byBNYXBzIGluIFIKCldlIHdpbGwgdGFrZSBzbWFsbCBzdGVwcyBpbiBtYWtpbmcgbWFwcyB1c2luZyBqdXN0IHR3byBvZiB0aGUgc2V2ZXJhbAptYXAgbWFraW5nIHBhY2thZ2VzIGluIFIuCgpUaGUgc3RlcHMgd2Ugd2lsbCB1c2UgYXJlOgoKMS4gIFNlYXJjaCBmb3IgYW4gYXJlYSBvZiBpbnRlcmVzdAoyLiAgTGVhcm4gaG93IHRvIGFjY2VzcyBzcGF0aWFsL21hcCBkYXRhIHVzaW5nIGBvc21kYXRhYAozLiAgUGxvdCBhbmQgZHJlc3MgdXAgb3VyIG1hcCB1c2luZyBgZ2dwbG90YCBhbmQgYHRtYXBgCjQuICBDcmVhdGUgaW50ZXJhY3RpdmUgbWFwcyB3aXRoIGBsZWFmbGV0YCB1c2luZyBhIHZhcmlldHkgb2YgbWFwIGRhdGEKICAgIHByb3ZpZGVycy4gKE5vdGU6IGB0bWFwYCBjYW4gYWxzbyBkbyBpbnRlcmFjdGl2ZSBtYXBzIHdoaWNoIHdlIHdpbGwKICAgIGV4cGxvcmUgYWxzby4pCgpCYXMuIE9ud2FyZHMgYW5kIE1hcC13YXJkcyEhCgojIyBTdGVwMSAtIFNwZWNpZnlpbmcgYW4gYXJlYSBvZiBpbnRlcmVzdAoKSW4gUiwgd2UgbmVlZCB0byBzcGVjaWZ5IGEgIioqQk9VTkRJTkcgQk9YKioiIGZpcnN0LCB0byBkZWNsYXJlIG91ciBhcmVhIG9mIGludGVyZXN0LiBHb2QgbWFkZSBtZSBhIEJlbmdhbHVSLWthUi4uLkkgdGhpbmsuLkxldCdzIHNlZSBpZiB3ZSBjYW4gZGVjbGFyZSBhbiBhcmVhIG9mIGludGVyZXN0LiBUaGVuIHdlIGNhbiBvcmRlciBvbiBTd2lnZ3kgYW5kLi4ubmV2ZXIgbWluZC4KCldlIGNhbiBkZWNsYXJlIGEgKipCT1VORElORyBCT1gqKiBpbiBzZXZlcmFsIHdheXMuCgphKSAgVXNpbmcgYSBsb25naXR1ZGUgbGF0aXR1ZGUgaW5mbyBmcm9tIFtCb3VuZGluZyBCb3ggVG9vbF0oaHR0cHM6Ly9ib3VuZGluZ2JveC5rbG9rYW50ZWNoLmNvbSkgd2hpY2ggZ2l2ZXMgYm91bmRpbmcgYm94ZXMgaW4gbWFueSBkaWZmZXJlbnQgZm9ybWF0cy4gCgotIExvY2F0ZSB0aGUgcGxhY2Ugb2YgaW50ZXJlc3QgdXNpbmcgdGhlIHNlYXJjaCBib3guIAotIGNsaWNrIG9uIHRoZSAiYm94IHdpdGggYXJyb3ciIHRvb2wgb24gdGhlIHVwcGVyIGxlZnQuIFRoaXMgd2lsbCBjcmVhdGUgYSByZWN0YW5ndWxhciBzaGFwZS4gICAKLSBNb3ZlL3Jlc2l6ZSB0aGlzIGJveCBhbmQgdGhlbiBjb3B5IHRoZSBib3VuZGluZyBib3ggZnJvbSB0aGUgbWVudSBhdCB0aGUgYm90dG9tLiBFbnN1cmUgeW91IGNvcHkgaW4gKipDU1YqKiBmb3JtYXQuICAgCgpgYGB7cn0KI3wgbGFiZWw6ICBJLWFtLWdvaW5nLWhvbWUgMQoKIyBodHRwczovL2JvdW5kaW5nYm94Lmtsb2thbnRlY2guY29tCiMgQ1NWOiA3Ny41NzQwMjgsMTIuOTE3MjYyLDc3LjU5NTA3MywxMi45Mzk4OTUKYmJveF8xIDwtIG1hdHJpeCgKICBjKDc3LjU3NDAyOCwgMTIuOTE3MjYyLCA3Ny41OTUwNzMsIDEyLjkzOTg5NSksCiAgYnlyb3cgPSBGQUxTRSwKICBucm93ID0gMiwKICBuY29sID0gMiwKICBkaW1uYW1lcyA9IGxpc3QoYygneCcsICd5JyksIGMoJ21pbicsICdtYXgnKSkKKQpiYm94XzEKCmBgYAoKYikgIFVzaW5nIGEgcGxhY2UgbmFtZSB0byBsb29rIHVwIGEgQk9VTkRJTkcgQk9YIHdpdGggYG9zbWRhdGE6OmdldGJiYC4gVGhpcyBtYXkgbm90IGFsd2F5cyB3b3JrIGlmIHRoZSBwbGFjZSBuYW1lIGlzIGtub3cgd2VsbCBrbm93bi4KCmBgYHtyIEktYW0tZ29pbmctaG9tZSAyfQojIFVzaW5nIGdldGJiIGNvbW1hbmQgZnJvbSB0aGUgb3NtZGF0YSBwYWNrYWdlCmJib3hfMiA8LSBvc21kYXRhOjpnZXRiYigiSmF5YW5hZ2FyLCBCYW5nYWxvcmUsIEluZGlhIikKYmJveF8yCgpgYGAKCkxldCB1cyBleGFtaW5lIGJvdGggdGhlIGNhbGN1bGF0ZWQgQk9VTkRJTkcgQk9YZXM6CgpgYGB7cn0KYmJveF8xCmJib3hfMgoKYGBgCgpCb3RoIGxvb2sgc2ltaWxhciBpbiBzaXplOyBgYmJveF8yYCBpcyBzbGlnaHRseSBiaWdnZXIuCgpXZSB3aWxsIHVzZSB0aGUgYGJib3hfMmAgZnJvbSB0aGUgYWJvdmUsIHRvIGVuc3VyZSB3ZSBoYXZlIGEgZGVjZW50IGNvbGxlY3Rpb24gb2YgZmVhdHVyZXMuIElmIHRoZSBkb3dubG9hZCBiZWNvbWVzIHRvbyBoZWZ0eSwgd2UgY2FuIGZhbGwgYmFjayBvbiB0aGUgc21hbGxlciBiYm94IQoKIyMgU3RlcDIgLSBHZXQgTWFwIGRhdGEKCj4gW09wZW5TdHJlZXRNYXAgKE9TTSldKGh0dHBzOi8vd3d3Lm9wZW5zdHJlZXRtYXAub3JnLykgcHJvdmlkZXMgbWFwcyBvZgo+IHRoZSB3b3JsZCBtb3N0bHkgY3JlYXRlZCBieSB2b2x1bnRlZXJzLiBUaGV5IGFyZSBjb21wbGV0ZWx5IGZyZWUgdG8KPiBicm93c2UgYW5kIHVzZSwgd2l0aCBhdHRyaWJ1dGlvbiB0byDCqSBPcGVuU3RyZWV0TWFwIGNvbnRyaWJ1dG9ycyBhbmQKPiBhZGhlcmVuY2UgdG8gdGhlIE9EYkwgbGljZW5zZSByZXF1aXJlZCwgYW5kIGFyZSB1c2VkIGJ5IG1hbnkgcHVibGljCj4gYW5kIHByaXZhdGUgb3JnYW5pc2F0aW9ucy4gT1NNIGRhdGEgY2FuIGJlIGRvd25sb2FkZWQgaW4gdmVjdG9yIGZvcm1hdAo+IGFuZCB1c2VkIGZvciBvdXIgb3duIHB1cnBvc2VzLiBJbiB0aGlzIHR1dG9yaWFsLCB3ZSB3aWxsIG9idGFpbiBkYXRhCj4gZnJvbSBPU00gdXNpbmcgYSBgcXVlcnlgLiBBIHF1ZXJ5IGlzIGEgcmVxdWVzdCBmb3IgZGF0YSBmcm9tIGEKPiBkYXRhYmFzZS4gU2ltcGxlIHF1ZXJpZXMgY2FuIGJlIHBlcmZvcm1lZCBtb3JlIGVhc2lseSB1c2luZyB0aGUKPiBgb3NtZGF0YWAgbGlicmFyeSBmb3IgUiwgd2hpY2ggYXV0b21hdGljYWxseSBjb25zdHJ1Y3RzIHRoZSBxdWVyeSBhbmQKPiBpbXBvcnRzIHRoZSBkYXRhIGluIGEgY29udmVuaWVudCBmb3JtYXQuCgpPcGVuIFN0cmVldCBNYXAgKipmZWF0dXJlcyoqIGhhdmUgYXR0cmlidXRlcyBpbiAqKmtleS12YWx1ZSBwYWlycyoqLiBXZQpjYW4gdXNlIHRoZW0gdG8gZG93bmxvYWQgdGhlIHNwZWNpZmljIGRhdGEgd2UgbmVlZC4gVGhlc2UgZmVhdHVyZXMgY2FuCmVhc2lseSBiZSBleHBsb3JlZCBpbiB0aGUgd2ViIGJyb3dzZXIsIGJ5IHVzaW5nIHRoZSAnUXVlcnkgZmVhdHVyZXMnCmJ1dHRvbiBvbiBbT3BlblN0cmVldE1hcCAoT1NNKV0oaHR0cHM6Ly93d3cub3BlbnN0cmVldG1hcC5vcmcvKToKCiFbXShvc20tb25saW5lLWZlYXR1cmVzLmdpZikKCkhlYWQgb2ZmIHRvIFtPU00gU3RyZWV0IE1hcF0oaHR0cHM6Ly93d3cub3BlbnN0cmVldG1hcC5vcmcvKSB0byB0cnkgdGhpcwpvdXQgYW5kIHRvIGdldCBhbiBpbnR1aXRpdmUgdW5kZXJzdGFuZGluZyBvZiB3aGF0IE9TTSAqKmtleS12YWx1ZQpwYWlycyoqIGFyZSwgZm9yIGRpZmZlcmVudCB0eXBlcyBvZiBtYXAgKipmZWF0dXJlcyoqLiBMb29rIGZvciBwbGFjZXMgb2YKaW50ZXJlc3QgdG8geW91ICgqKmZlYXR1cmVzKiopIGFuZCBzZWUgd2hhdCAqKmtleS12YWx1ZSoqIHBhaXJzIGF0dGFjaAp0byB0aG9zZSBmZWF0dXJlcy4KCk5PVEU6ICoqa2V5LXZhbHVlIHBhaXJzKiogYXJlIGFsc28gcmVmZXJyZWQgdG8gYXMgKip0YWdzKiouCgpVc2VmdWwgKiprZXktdmFsdWUgcGFpcnMqKiAvICoqdGFncyoqIGluY2x1ZGU6Cgp8IEtFWSAgICAgIHwgVkFMVUVzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwKfCBidWlsZGluZyB8IHllcyAoYWxsKSwgaG91c2UgcmVzaWRlbnRpYWwsIGFwYXJ0bWVudHMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBoaWdod2F5ICB8IHJlc2lkZW50aWFsLCBzZXJ2aWNlLCB0cmFjaywgdW5jbGFzc2lmaWVkLCBmb290d2F5LCBwYXRoICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBhbWVuaXR5ICB8IHBhcmtpbmcsIHBhcmtpbmdfc3BhY2UsIGJlbmNoOyBwbGFjZV9vZl93b3JzaGlwOyByZXN0YXVyYW50LCBjYWZlLCBmYXN0X2Zvb2Q7IHNjaG9vbCwgd2FzdGVfYmFza2V0LCBmdWVsLCBiYW5rLCB0b2lsZXRzLi4uIHwKfCBzaG9wICAgICB8IGNvbnZlbmllbmNlLCBzdXBlcm1hcmtldCwgY2xvdGhlcywgaGFpcmRyZXNzZXIsIGNhci1yZXBhaXIuLi4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBuYW1lICAgICB8IGFjdHVhbCBuYW1lIG9mIHRoZSBwbGFjZSBlLmcuIE1haW5fU3RyZWV0LCAqTWNEb25hbGQncywgUGl6emEgSHV0LCBTdWJ3YXkqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCB3YXRlcndheSB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBuYXR1cmFsICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBib3VuZGFyeSB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKCkZvciBtb3JlIGluZm9ybWF0aW9uIHNlZTpbT1NNIFRhZ3NdKGh0dHBzOi8vdGFnaW5mby5vcGVuc3RyZWV0bWFwLm9yZy8pIGZvciBhIG5pY2UgdmlzdWFsIGRlc2NyaXB0aW9uIG9mIHBvcHVsYXIgKiprZXktdmFsdWUgcGFpcnMqKiB0aGF0IHdlIGNhbiB1c2UuIFNlZSB3aGF0IHRoZSBgaGlnaHdheWAgdGFnIGxvb2tzIGxpa2UgW3RhZyA6IGhpZ2h3YXldKGh0dHBzOi8vdGFnaW5mby5vcGVuc3RyZWV0bWFwLm9yZy9rZXlzL2hpZ2h3YXkjdmFsdWVzKQoKVGhlIGBvc21kYXRhYCBjb21tYW5kcyBgYXZhaWxhYmxlX2ZlYXR1cmVzYCBhbmQgYGF2YWlsYWJsZV90YWdzYCBjYW4gaGVscCBhbHNvIHVzIGdldCB0aGUgYXNzb2NpYXRlZCAqa2V5LXZhbHVlKiogcGFpcnMgdG8gcmV0cmlldmUgZGF0YSBmcm9tIE9TTS4KCmBgYHtyfQojfCBsYXlvdXQtbmNvbDogMgpvc21kYXRhOjphdmFpbGFibGVfZmVhdHVyZXMoKSAlPiUgYXNfdGliYmxlKCkKYXZhaWxhYmxlX3RhZ3MoZmVhdHVyZSA9ICJoaWdod2F5IikKYXZhaWxhYmxlX3RhZ3MoImFtZW5pdHkiKQphdmFpbGFibGVfdGFncygibmF0dXJhbCIpCgpgYGAKCldlIGNhbiB1c2UgdGhlc2UgKiprZXktdmFsdWUqKiBwYWlycyB0byBkb3dubG9hZCBkaWZmZXJlbnQgdHlwZXMgb2YgbWFwIGRhdGEuIFdpdGhpbiBvdXIgYGJib3hgIGZvciBKYXlhbmFnYXIsIEJhbmdhbG9yZSwgd2Ugd2FudCB0byBkb3dubG9hZCBkaXZlcnNlICBraW5kcyBvZiAqKkZFQVRVUkUqKiBkYXRhLiBSZW1lbWJlciB0aGF0IGEgKipGRUFUVVJFKiogaXMgYW55IG9iamVjdCB0aGF0IGNhbiBiZSAic2VlbiIgb24gYSBtYXAuIFRoaXMgaXMgZG9uZSB1c2luZyB0aGUgT1BRIHF1ZXJ5IGluIHRoZSBgb3NtZGF0YWAgcGFja2FnZS4gVGhlIG1haW4gcGFyYW1ldGVycyBmb3IgdGhpcyBjb21tYW5kIGFyZToKCi0gICBiYm94Ci0gICAqKktFWSAvIFZBTFVFKiogcGFpcnMgKCoqIlRBR1MiKiopIHRvIHNwZWNpZnkgdGhlIGtpbmQgb2YgZmVhdHVyZQogICAgeW91IG5lZWQKICAgIApUaGUgcXVlcnkgcmV0dXJucyBhICoqbGlzdCoqIGRhdGEgc3RydWN0dXJlLCB3aXRoICoqYWxsIGdlb21ldHJpZXMqKiBhbmQgZmVhdHVyZXMgd2l0aGluIHRoZSBib3VuZGluZyBib3gsIGFuZCB3ZSBjYW4gdXNlIGFueSBvciBhbGwgb2YgdGhlbS4gTm93IHdlIGtub3cgdGhlIG1hcCBmZWF0dXJlcyB3ZSBhcmUgaW50ZXJlc3RlZCBpbi4gV2UgYWxzbyBrbm93IAp3aGF0IGtleS12YWx1ZSBwYWlycyB3aWxsIGJlIHVzZWQgdG8gZ2V0IHRoaXMgaW5mbyBmcm9tIE9TTS4gCgo6OjogY2FsbG91dC1kYW5nZXIKIyMjIERhdGEgRG93bmxvYWRzIGZyb20gT1NNCioqRG8gbm90IHJ1biB0aGVzZSBjb21tYW5kcyB0b28gbWFueSB0aW1lcyoqLiAqKlJlLXJ1biB0aGlzIE9OTFkgaWYgeW91IGhhdmUgY2hhbmdlZCB5b3VyIEJPVU5ESU5HIEJPWC4qKi4gV2Ugd2lsbCBnZXQgb3VyIG1hcCBkYXRhIGZyb20gT1NNIGFuZCB0aGVuIHNhdmUgaXQgYXZvaWQgcmVwZWF0ZWQgZG93bmxvYWRzLiBTbywgcGxlYXNlIGNvcHkvcGFzdGUgYW5kIHJ1biB0aGUgZm9sbG93aW5nIGNvbW1hbmRzIGluIHlvdXIgY29uc29sZS4gVGhlIGNodW5rIGJlbG93IGlzIHNldCB0byBldmFsOmZhbHNlIHNvIGl0IHdpbGwgKm5vdCogcnVuIHdoZW4geW91IHJlbmRlciEKOjo6CgpgYGB7cn0KI3wgZXZhbDogZmFsc2UKIyBFdmFsIGlzIHNldCB0byBmYWxzZSBoZXJlCiMgVGhpcyBjb2RlIGlzIGZvciByZWZlcmVuY2UKIyBSdW4gdGhlc2UgY29tbWFuZHMgT05DRSBpbiB5b3VyIENvbnNvbGUKIyBPciBydW4gdGhpcyBjaHVuayBtYW51YWxseSBvbmUgdGltZQoKIyBHZXQgYWxsIHJlc3RhdXJhbnRzLCBhdG1zLCBjb2xsZWdlcyB3aXRoaW4gbXkgYmJveApsb2NhdGlvbnMgPC0gCiAgb3NtZGF0YTo6b3BxKGJib3ggPSBiYm94XzIpICU+JSAKICBvc21kYXRhOjphZGRfb3NtX2ZlYXR1cmUoa2V5ID0gImFtZW5pdHkiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBjKCJyZXN0YXVyYW50IiwgImF0bSIsICJjb2xsZWdlIikpICU+JSAKICBvc21kYXRhX3NmKCkgJT4lICAjIENvbnZlcnQgdG8gU2ltcGxlIEZlYXR1cmVzIGZvcm1hdAogIHB1cnJyOjpwbHVjaygib3NtX3BvaW50cyIpICMgUHVsbCBvdXQgdGhlIGRhdGEgZnJhbWUgb2YgaW50ZXJlc3QKCiMgR2V0IGFsbCBidWlsZGluZ3Mgd2l0aGluIG15IGJib3gKZGF0X2J1aWxkaW5ncyA8LQogIG9zbWRhdGE6Om9wcShiYm94ID0gYmJveF8yKSAlPiUgCiAgb3NtZGF0YTo6YWRkX29zbV9mZWF0dXJlKGtleSA9ICJidWlsZGluZyIpICU+JSAKICBvc21kYXRhX3NmKCkgJT4lIAogIHB1cnJyOjpwbHVjaygib3NtX3BvbHlnb25zIikgCgojIEdldCBhbGwgcmVzaWRlbnRpYWwgcm9hZHMgd2l0aGluIG15IGJib3gKZGF0X3JvYWRzIDwtIAogIG9zbWRhdGE6Om9wcShiYm94ID0gYmJveF8yKSAlPiUgCiAgb3NtZGF0YTo6YWRkX29zbV9mZWF0dXJlKGtleSA9ICJoaWdod2F5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gYygicmVzaWRlbnRpYWwiKSkgJT4lIAogIG9zbWRhdGFfc2YoKSAlPiUgCiAgcHVycnI6OnBsdWNrKCJvc21fbGluZXMiKSAKCiMgR2V0IGFsbCBwYXJrcyAvIG5hdHVyYWwgL2dyZWVuZXJ5IGFyZWFzIGFuZCBzcG90cyB3aXRoaW4gbXkgYmJveApkYXRfbmF0dXJhbCA8LSAgIAogIG9zbWRhdGE6Om9wcShiYm94ID0gYmJveF8yKSAlPiUgCiAgb3NtZGF0YTo6YWRkX29zbV9mZWF0dXJlKGtleSA9ICJuYXR1cmFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBjKCJ0cmVlIiwgIndhdGVyIiwgIndvb2QiKSkgJT4lIAogIG9zbWRhdGFfc2YoKQpkYXRfbmF0dXJhbAoKZGF0X3RyZWVzIDwtIAogIGRhdF9uYXR1cmFsICU+JSAKICBwdXJycjo6cGx1Y2soIm9zbV9wb2ludHMiKSAKCmRhdF9ncmVlbmVyeSA8LSAKICBkYXRfbmF0dXJhbCAlPiUgcGx1Y2soIm9zbV9wb2x5Z29ucyIpCiAKCmBgYAoKTGV0IHVzIHNhdmUgdGhpcyBkYXRhLCBzbyB3ZSBkb24ndCBuZWVkIHRvIGRvd25sb2FkIGFsbCB0aGlzIGFnYWluISBXZSB3aWxsIHN0b3JlIHRoZSBkb3dubG9hZGVkIGRhdGEgYXMgYC5ncGtnYCBmaWxlcyBvbiBvdXIgbG9jYWwgaGFyZApkcml2ZXMgdG8gdXNlIHdoZW4gd2UgcnVuIHRoaXMgZmlsZSBhZ2FpbiBsYXRlci4gV2Ugd2lsbCBuYW1lIG91ciBzdG9yZWQKZmlsZXMgYXMgYGJ1aWxkaW5nc2AsIGByb2Fkc2AsIGFuZCBgZ3JlZW5lcnlgLCBhbmQgYHRyZWVzYCwgZWFjaCB3aXRoIHRoZQpgLmdwa2dgIGZpbGUgZXh0ZW5zaW9uLCBlLmcuIGB0cmVlcy5ncGtnYC4KCkNoZWNrIHlvdXIgbG9jYWwgcHJvamVjdCBmb2xkZXIgZm9yIHRoZXNlIGZpbGVzIGFmdGVyIGV4ZWN1dGluZyB0aGVzZQpjb21tYW5kcy4KCmBgYHtyfQojfCBldmFsOiBmYWxzZQojIEV2YWwgaXMgc2V0IHRvIGZhbHNlIGhlcmUKIyBUaGlzIGNvZGUgaXMgZm9yIHJlZmVyZW5jZQojIFJ1biB0aGVzZSBjb21tYW5kcyBPTkNFIGluIHlvdXIgQ29uc29sZQojIE9yIG1hbnVhbGx5IHJ1biB0aGlzIGNodW5rIG9uY2UKCnN0X3dyaXRlKGRhdF9yb2FkcywgZHNuID0gInJvYWRzLmdwa2ciLCAKICAgICAgICAgYXBwZW5kID0gRkFMU0UsIHF1aWV0ID0gRkFMU0UpCgpzdF93cml0ZShkYXRfYnVpbGRpbmdzLCAKICAgICAgICAgZHNuID0gImJ1aWxkaW5ncy5ncGtnIiwgCiAgICAgICAgIGFwcGVuZCA9IEZBTFNFLCAKICAgICAgICAgcXVpZXQgPSBGQUxTRSkKCnN0X3dyaXRlKGRhdF9ncmVlbmVyeSwgZHNuID0gImdyZWVuZXJ5Lmdwa2ciLCAKICAgICAgICAgYXBwZW5kID0gRkFMU0UscXVpZXQgPSBGQUxTRSkKCnN0X3dyaXRlKGRhdF90cmVlcywgZHNuID0gInRyZWVzLmdwa2ciLCAKICAgICAgICAgYXBwZW5kID0gRkFMU0UscXVpZXQgPSBGQUxTRSkKCmBgYAoKOjo6IGNhbGxvdXQtZGFuZ2VyCioqQWx3YXlzIHdvcmsgZnJvbSBoZXJlIHRvIGF2b2lkIHJlcGVhdGVkIGRvd25sb2FkcyBmcm9tIE9TTS4gU3RhcnQgZnJvbSB0aGUgdG9wIE9OTFkgaWYgeW91IGludGVuZCB0byBtYXAgbmV3IGxvY2F0aW9ucyBhbmQgbmVlZCB0byBtb2RpZnkgeW91ciBCb3VuZGluZyBCb3guKioKOjo6CgpMZXQgdXMgbm93IHJlYWQgYmFjayB0aGUgc2F2ZWQgRGF0YToKCmBgYHtyfQojfCBsYWJlbDogcmVzdW1lLWZyb20taGVyZQpidWlsZGluZ3MgPC0gc3RfcmVhZCgiLi9idWlsZGluZ3MuZ3BrZyIpCmdyZWVuZXJ5IDwtIHN0X3JlYWQoIi4vZ3JlZW5lcnkuZ3BrZyIpCnRyZWVzIDwtIHN0X3JlYWQoIi4vdHJlZXMuZ3BrZyIpCnJvYWRzIDwtIHN0X3JlYWQoIi4vcm9hZHMuZ3BrZyIpCgpgYGAKSG93IG1hbnkgcm93cz8gKCBSb3dzIC1cPiBGZWF0dXJlcyApIFdoYXQga2luZCBvZiBgZ2VvbWAgY29sdW1uIGluIGVhY2ggZGF0YSBzZXQ/CgpgYGB7cn0KIyBIb3cgbWFueSBidWlsZGluZ3M/Cm5yb3coYnVpbGRpbmdzKQpidWlsZGluZ3MkZ2VvbQpjbGFzcyhidWlsZGluZ3MkZ2VvbSkKCmBgYAoKU28gdGhlIGBidWlsZGluZ3NgIGRhdGFzZXQgaGFzIGByIG5yb3coYnVpbGRpbmdzKWAgYnVpbGRpbmdzIGFuZCB0aGVpcgpnZW9tZXRyeSBpcyBuYXR1cmFsbHkgYSBQT0xZR09OIHR5cGUgb2YgZ2VvbWV0cnkgY29sdW1uLgoKRG8gdGhpcyBjaGVjayBmb3IgYWxsIHRoZSBvdGhlciBzcGF0aWFsIGRhdGEsIGluIHRoZSBjb2RlIGNodW5rIGJlbG93LgpXaGF0IGtpbmQgb2YgYGdlb21gIGNvbHVtbiBkb2VzIGVhY2ggZGF0YXNldCBoYXZlPwoKYGBge3IgWU9VUi1UVVJOLTEsIGluY2x1ZGU9VFJVRX0KCgoKYGBgCgoKIyMgTXkgZmlyc3QgTWFwIGluIFIKClRoZXJlIGFyZSB0d28gd2F5cyBvZiBwbG90dGluZyBtYXBzIHRoYXQgd2Ugd2lsbCBsZWFybjoKCiMjIyBnZ3Bsb3QgYW5kIGdlb21fc2YoKQoKRmlyc3Qgd2Ugd2lsbCBwbG90IHdpdGggYGdncGxvdGAgYW5kIGBnZW9tX3NmKClgIDogcmVjYWxsIHRoYXQgb3VyIGRhdGEKaXMgc3RvcmVkIGluIDUgZmlsZXM6IGBidWlsZGluZ3NgLCBgcGFya3NgLCBgcm9hZHNgLCBgdHJlZXNgLCBhbmQKYGdyZWVuZXJ5YC4KCmBgYHtyfQoKZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IGJ1aWxkaW5ncywgZmlsbCA9ICJnb2xkIiwgY29sb3IgPSAiZ3JleSIsIGxpbmV3aWR0aCA9IDAuMDI1KSArICAgICMgUE9MWUdPTlMKICBnZW9tX3NmKGRhdGEgPSByb2FkcywgY29sb3IgPSAnI2ZmOTk5OScsIGxpbmV3aWR0aCA9IDAuNSkgKyAgICAgICAgIyBMSU5FUwogIGdlb21fc2YoZGF0YSA9IGdyZWVuZXJ5LCBjb2wgPSAiZGFya3NlYWdyZWVuIikgKyAgIyBQT0xZR09OUwogIGdlb21fc2YoZGF0YSA9IHRyZWVzLCBjb2wgPSAiZGFya2dyZWVuIikgICsgICAgICAgIyBQT0lOVFMKICAKICAjIFNldCBwbG90IGxpbWl0cyB0byBleGFjdGx5IHRoZSBiYm94XzIKICBjb29yZF9zZih4bGltID0gYyhiYm94XzJbMSwxXSwgYmJveF8yWzEsMl0pLAogICAgICAgICAgIHlsaW0gPSBjKGJib3hfMlsyLDFdLCBiYm94XzJbMiwyXSksCiAgICAgICAgICAgZXhwYW5kID0gRkFMU0UpICsgCiAgdGhlbWVfbWluaW1hbCgpCgpgYGAKCk5vdGUgaG93IGBnZW9tX3NmYCBpcyBjYXBhYmxlIG9mIGhhbmRsaW5nICphbnkqIGdlb21ldHJ5IGluIHRoZSBgc2ZjYApjb2x1bW4gISEKCj4gYGdlb21fc2YoKWAgaXMgYW4gdW51c3VhbCBnZW9tIGJlY2F1c2UgaXQgd2lsbCBkcmF3IGRpZmZlcmVudAo+IGdlb21ldHJpYyBvYmplY3RzIGRlcGVuZGluZyBvbiB3aGF0IHNpbXBsZSBmZWF0dXJlcyBhcmUgcHJlc2VudCBpbiB0aGUKPiBkYXRhOiB5b3UgY2FuIGdldCBwb2ludHMsIGxpbmVzLCBvciBwb2x5Z29ucy4KClNvIHRoZXJlLCB3ZSBoYXZlIG91ciBmaXJzdCBtYXAhCgojIyBNYXAgdXNpbmcgYHRtYXBgIHBhY2thZ2UKCldlIGNhbiBhbHNvIGNyZWF0ZSBhIG1hcCB1c2luZyBhIHBhY2thZ2UgY2FsbGVkIGB0bWFwYC4gSGVyZSB3ZSBhbHNvCmhhdmUgdGhlIG9wdGlvbiBvZiBtYWtpbmcgdGhlIG1hcCAqaW50ZXJhY3RpdmUqLiBgdG1hcGAgcGxvdHMgYXJlIG1hZGUKd2l0aCBjb2RlIGluICJncm91cHMiOiBlYWNoIGdyb3VwIHN0YXJ0cyB3aXRoIGEgYHRtX3NoYXBlKClgIGNvbW1hbmQuCgpgYGB7cn0KI3wgbGFiZWw6IHVzaW5nLXRtYXAKIyBHcm91cC0xCnRtX3NoYXBlKGJ1aWxkaW5ncykgKwogIHRtX2ZpbGwoY29sID0gImJ1cmx5d29vZCIpICsKCiNHcm91cC0yCnRtX3NoYXBlKHJvYWRzKSArCiAgdG1fbGluZXMoY29sID0gImdyZXkyMCIpICsKCiNHcm91cC0zICAKdG1fc2hhcGUoZ3JlZW5lcnkpICsKICB0bV9wb2x5Z29ucyhjb2wgPSAibGltZWdyZWVuIikgKwogIAoKI0dyb3VwLTQKdG1fc2hhcGUodHJlZXMpICsKICB0bV9kb3RzKGNvbCA9ICJkYXJrZ3JlZW4iKQoKYGBgCgpIb3cgZG8gd2UgbWFrZSB0aGlzIG1hcCBpbnRlcmFjdGl2ZT8gT25lIG1vcmUgbGluZSBvZiBjb2RlICEhIEFkZCB0aGlzCmxpbmUgaW4geW91ciBjb25zb2xlIGFuZCB0aGVuIHJ1biB0aGUgYWJvdmUgY2h1bmsgYWdhaW4KCmB0bWFwX21vZGUoInZpZXciKWAKCiMjIFVzaW5nIGRhdGEgZnJvbSBgdG1hcGAKCkxpa2UgbWFueSBvdGhlciBwYWNrYWdlcyAoIGUuZy4gZ2dwbG90ICkgYHRtYXBgIGFsc28gaGFzIGEgZmV3IGJ1aWx0LWluCnNwYXRpYWwgZGF0YXNldHM6IGBXb3JsZGAgYW5kIGBtZXRyb2AsIGByaXZlcnNgLCBgbGFuZGAgYW5kIGEgZmV3Cm90aGVycy4gQ2hlY2sgaGVscCBvbiB0aGVzZS4gTGV0J3MgcGxvdCBhIGZpcnN0IG1hcCB1c2luZyBkYXRhc2V0cyBidWlsdAppbnRvIGB0bWFwYC4KCmBgYHtyIFdvcmxkLURhdGEtdG1hcH0KZGF0YSgiV29ybGQiKQpoZWFkKFdvcmxkLCBuID0gMykKCmBgYAoKV2UgaGF2ZSBzZXZlcmFsIDE0IGF0dHJpYnV0ZSB2YXJpYWJsZXMgaW4gYFdvcmxkYC4gQXR0cmlidXRlIHZhcmlhYmxlcwpzdWNoIGFzIGBnZHBfY2FwX2VzdGAsIGBIUElgIGFyZSBudW1lcmljLiBPdGhlcnMgc3VjaCBhcyBgaW5jb21lX2dycGAKYXBwZWFyIHRvIGJlIGZhY3RvcnMuIGBpc29fYTNgIGlzIHRoZSBzdGFuZGFyZCB0aHJlZSBsZXR0ZXIgbmFtZSBmb3IgdGhlCmNvdW50cnkuIGBuYW1lYCBpcyBvZiBjb3Vyc2UsIHRoZSBuYW1lIGZvciBlYWNoIGNvdW50cnkhCgpgYGB7ciBXb3JsZC1tZXRyby1jaXRpZXMtdG1hcH0KZGF0YSgibWV0cm8iKQpoZWFkKG1ldHJvLCBuID0gMykKCmBgYAoKSGVyZSB0b28gd2UgaGF2ZSBhdHRyaWJ1dGUgdmFyaWFibGVzIGZvciB0aGUgbWV0cm9zLCBhbmQgdGhleSBzZWVtCnByZWRvbWluYW50bHkgbnVtZXJpYy4gQWdhaW4gYGlzb19hM2AgaXMgdGhlIHRocmVlIGxldHRlciBuYW1lIGZvciB0aGUKY2l0eS4KCmBgYHtyfQojfCBsYWJlbDogTXktU3RhdGljLVdvcmxkCnRtYXBfbW9kZSgicGxvdCIpICMgTWFraW5nIHRoaXMgYSBzdGF0aWMgcGxvdAoKIyBHcm91cCAxCnRtX3NoYXBlKFdvcmxkKSArICMgZGF0YXNldCA9IFdvcmxkLiAKICAgIHRtX3BvbHlnb25zKCJIUEkiKSArICMgQ29sb3VyIHBvbHlnb25zIGJ5IEhQSSBudW1lcmljIHZhcmlhYmxlCgogICMgTm90ZSB0aGUgIisiIHNpZ24gY29udGludWF0aW9uCiAgCiMgR3JvdXAgMgp0bV9zaGFwZShtZXRybykgKyAjIGRhdGFzZXQgPSBtZXRybwogIHRtX2J1YmJsZXMoc2l6ZSA9ICJwb3AyMDMwIiwgCiAgICAgICAgICAgICBjb2wgPSAicmVkIikgCiMgUGxvdCBjaXRpZXMgYXMgYnViYmxlcwojIFNpemUgcHJvcG9ydGlvbmFsIHRvIG51bWVyaWMgdmFyaWFibGUgYHBvcDIwMzBgCmBgYAoKYGBge3J9CiN8IGxhYmVsOiAgTXkgSW50ZXJhY3RpdmUgV2F0ZXIgQ29sb3VyIFdvcmxkCnRtYXBfbW9kZSgidmlldyIpICMgQ2hhbmdlIHRvIEludGVyYWN0aXZlCgoKIyBMZXQncyB1c2UgV2F0ZXJDb2xvciBNYXAgdGhpcyB0aW1lISEKdG1fdGlsZXMoIlN0YW1lbi5XYXRlcmNvbG9yIikgKyAjIFdhdGVyY29sb3IgbWFwIG9ubHkgd2l0aCBpbnRlcmFjdGl2ZQp0bV9zaGFwZShXb3JsZCkgKwogICAgdG1fcG9seWdvbnMoIkhQSSIpICsgIyBDb2xvciBieSBIYXBwaW5lc3MgSW5kZXgKICAKICAKdG1fc2hhcGUobWV0cm8pICsgCiAgdG1fYnViYmxlcyhzaXplID0gInBvcDIwMzAiLCAjIFNpemUgQ2l0eSBNYXJrZXJzIGJ5IFBvcHVsYXRpb24gaW4gMjAyMAogICAgICAgICAgICAgY29sID0gInJlZCIpIApgYGAKCiMjIFVzaW5nIGRhdGEgZnJvbSBgcm5hdHVyYWxlYXJ0aGAKClRoZSBgcm5hdHVyYWxlYXJ0aGAgcGFja2FnZSBhbGxvd3MgdXMgdG8gZG93bmxvYWQgc2hhcGVzIG9mIGNvdW50cmllcy4KV2UgY2FuIHVzZSBpdCB0byBnZXQgYm9yZGVycyBhbmQgYWxzbyBpbnRlcm5hbCBzdGF0ZS9kaXN0cmljdApib3VuZGFyaWVzLgoKYGBge3J9CiN8IGxhYmVsOiAgc3BhdGlhbF9kYXRhIGZyb20gbmF0dXJhbCBlYXJ0aAppbmRpYSA8LSAKICBuZV9zdGF0ZXMoY291bnRyeSA9ICAiaW5kaWEiLCAKICAgICAgICAgICAgcmV0dXJuY2xhc3MgPSAic2YiKSAjIGdpdmVzIGEgcmVhZHkgc2YgZGF0YWZyYW1lICEKCmluZGlhX25laWdoYm91cnMgPC0gCiAgbmVfc3RhdGVzKGNvdW50cnkgPSAoYygic3JpIGxhbmthIiwgInBha2lzdGFuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJhZmdoYW5pc3RhbiIsICJuZXBhbCIsImJhbmdsYWRlc2giLCAiYmh1dGFuIikKICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICByZXR1cm5jbGFzcyA9ICJzZiIpCgpgYGAKCkxldCdzIGxvb2sgYXQgdGhlIGF0dHJpYnV0ZSB2YXJpYWJsZSBjb2x1bW5zIHRvIGNvbG91ciBvdXIgZ3JhcGggYW5kIHRvCnNoYXBlIG91ciBzeW1ib2xzOgoKYGBge3J9Cm5hbWVzKGluZGlhKQpuYW1lcyhpbmRpYV9uZWlnaGJvdXJzKQoKIyBMb29rIG9ubHkgYXQgYXR0cmlidXRlcwppbmRpYSAlPiUgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSBoZWFkKCkKaW5kaWFfbmVpZ2hib3VycyAlPiUgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSBoZWFkKCkKCmBgYAoKSW4gdGhlIGBpbmRpYWAgZGF0YSBmcmFtZTpcCi0gQ29sdW1uIGBpc29fYTJgIGNvbnRhaW5zIHRoZSBjb3VudHJ5IG5hbWUuXAotIENvbHVtbiBgbmFtZWAgY29udGFpbnMgdGhlIG5hbWUgb2YgdGhlIHN0YXRlCgpJbiB0aGUgYGluZGlhX25laWdoYm91cnNgIGRhdGEgZnJhbWU6XAotIENvbHVtbiBgZ3VfYTNgIGNvbnRhaW5zIHRoZSBjb3VudHJ5IGFiYnJldmlhdGlvblwKLSBDb2x1bW4gYG5hbWVgIGNvbnRhaW5zIHRoZSBuYW1lIG9mIHRoZSBzdGF0ZVwKLSBDb2x1bW4gYGlzb18zMTY2XzJgIGNvbnRhaW5zIHRoZSBhYmJyZXZpYXRpb24gb2YgdGhlIHN0YXRlIHdpdGhpbiBlYWNoCm5laWdoYm91cmluZyBjb3VudHJ5LgoKCmBgYHtyfQojfCBsYWJlbDogIE1hcF8xCnRtYXBfbW9kZSgidmlldyIpCgojIFBsb3QgSW5kaWEKICB0bV9zaGFwZShpbmRpYSkgKwogIHRtX3BvbHlnb25zKCJuYW1lIiwgIyBDb2xvdXIgYnkgU3RhdGVzIGluIEluZGlhCiAgICAgICAgICAgICAgbGVnZW5kLnNob3cgPSBGQUxTRSkgKwogIAojIFBsb3QgTmVpZ2hib3VycwogIHRtX3NoYXBlKGluZGlhX25laWdoYm91cnMpICsKICB0bV9maWxsKGNvbCA9ICJndV9hMyIpICsgICMgQ29sb3VyIGJ5IENvdW50cnkgTmFtZQogIAojIFBsb3QgdGhlIGNpdGllcyBpbiBJbmRpYSBhbG9uZQogIHRtX3NoYXBlKG1ldHJvICU+JSBkcGx5cjo6ZmlsdGVyKGlzb19hMyA9PSAiSU5EIikpICsKICAgIAogIHRtX2RvdHMoc2l6ZSA9ICJwb3AyMDIwIixsZWdlbmQuc2l6ZS5zaG93ID0gRkFMU0UpICsKICAgICMgc2l6ZSBieSBwb3B1bGF0aW9uIGluIDIwMjAKICAgIAogIHRtX2xheW91dChsZWdlbmQuc2hvdyA9IEZBTFNFKSArCiAgdG1fY3JlZGl0cygiR2VvZ3JhcGhpY2FsIEJvdW5kYXJpZXMgYXJlIG5vdCBhY2N1cmF0ZSIsCiAgICAgICAgICAgICBzaXplID0gMC41LAogICAgICAgICAgICAgcG9zaXRpb24gPSAicmlnaHQiKSArCiAgdG1fY29tcGFzcyhwb3NpdGlvbiA9IGMoInJpZ2h0IiwgInRvcCIpKSArCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gImxlZnQiKSArCiAgdG1hcF9zdHlsZShzdHlsZSA9ICJjbGFzc2ljIikgCgojVHJ5IG90aGVyIG1hcCBzdHlsZXMKI2NvYmFsdCAjZ3JheSAjd2hpdGUgI3dhdGVyY29sb3IgI2JlYXZlciAjY2xhc3NpYyAjd2F0ZXJjb2xvciAjYWxiYXRyb3NzICNidyAjY29sX2JsaW5kCgpgYGAKCiMjIFlvdXIgVHVybiAyCgpDYW4geW91IHRyeSB0byBkb3dubG9hZCBhIG1hcCBhcmVhIG9mIHlvdXIgaG9tZSB0b3duIGFuZCBwbG90IGl0IGFzIHdlCmhhdmUgYWJvdmU/CgpgYGB7ciBZb3VyLVR1cm4tMiwgaW5jbHVkZT1UUlVFLCBldmFsPUZBTFNFfQoKYGBgCgoKCiMjIEFkZGluZyBteSBmYXZvdXJpdGUgUmVzdGF1cmFudHMgdG8gdGhlIG1hcAoKSXMgaXQgdGltZSB0byBvcmRlciBvbiBTd2lnZ3kuLi4KCkxldCB1cyBhZGRpbmcgaW50ZXJlc3RpbmcgcGxhY2VzIHRvIG91ciBtYXA6IHNheSBiYXNlZCBvbiB5b3VyIGZhdm91cml0ZQpyZXN0YXVyYW50cyBldGMuIFdlIG5lZWQgcmVzdGF1cmFudCBkYXRhOiBsYXQvbG9uZyArIG5hbWUgKyBtYXliZSB0eXBlCm9mIHJlc3RhdXJhbnQuIFRoaXMgY2FuIGJlIG1hbnVhbGx5IGNyZWF0ZWQgKCBsaWtlIGFsbCBvZiBPU01kYXRhICkgb3IKaWYgaXQgaXMgYWxyZWFkeSB0aGVyZSB3ZSBjYW4gZG93bmxvYWQgdXNpbmcgKmtleS12YWx1ZSogcGFpcnMgaW4gb3VyCk9TTSBkYXRhIHF1ZXJ5LgoKUmVzdGF1cmFudHMgY2FuIGJlIGRvd25sb2FkZWQgdXNpbmcKYGtleT0gImFtZW5pdHkiLCB2YWx1ZSA9ICJyZXN0YXVyYW50ImAgb3IgYCJjYWZlImAgZXRjLiBUaGVyZSBhcmUgYWxzbyBvdGhlciB0YWdzIHRvIGV4cGxvcmUhU2VhcmNoaW5nIGZvciBNY0RvbmFsZHMgZm9yIGluc3RhbmNlLi4uKCBga2V5ID0gIm5hbWUiLCB2YWx1ZSA9ICJNY0RvbmFsZHMiYCkuIFNpbmNlIHdlIHdhbnQgSlVTVCB0aGVpciBsb2NhdGlvbiwgYW5kIG5vdCB0aGUgcmVzdGF1cmFudCBCVUlMRElOR3MsIHdlIGV4dHJhY3QgYG9zbV9wb2ludHNgLiAKCgpgYGB7ciByZXN0YXVyYW50LWRhdGEtMSwgZXZhbD1GQUxTRX0KCiMgQWdhaW4sIHJ1biB0aGVzZSBjb21tYW5kcyBpbiB5b3VyIENvbnNvbGUKZGF0X1IgPC0KICBvc21kYXRhOjpvcHEoYmJveCA9IGJib3hfMikgJT4lIAogIG9zbWRhdGE6OmFkZF9vc21fZmVhdHVyZShrZXkgPSAiYW1lbml0eSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGMoInJlc3RhdXJhbnQiKSkgJT4lIAogIG9zbWRhdGFfc2YoKSAlPiUgCiAgcHVycnI6OnBsdWNrKCJvc21fcG9pbnRzIikgCgojIFNhdmUgdGhlIGRhdGEgZm9yIGZ1dHVyZSB1c2UKd3JpdGVfc2YoZGF0X1IsIGRzbiA9ICJyZXN0YXVyYW50cy5ncGtnIixhcHBlbmQgPSBGQUxTRSwgcXVpZXQgPSBGQUxTRSkKCmBgYAoKCk5vdyByZWFkaW5nIHRoZSBzYXZlZCBSZXN0YXVyYW50IERhdGEKCmBgYHtyfQpyZXN0YXVyYW50cyA8LSBzdF9yZWFkKCIuL3Jlc3RhdXJhbnRzLmdwa2ciKQpgYGAKCkhvdyBtYW55IHJlc3RhdXJhbnRzIGhhdmUgd2UgZ290PwoKYGBge3IgcmVzdGF1cmFudC1kYXRhLTJ9CgpyZXN0YXVyYW50cyAlPiUgbnJvdygpCgpgYGAKClNvIHRoZSBgcmVzdGF1cmFudHNgIGRhdGFzZXQgaGFzIGByIG5yb3cocmVzdGF1cmFudHMpYCByZXN0YXVyYW50cyBhbmQKdGhlaXIgZ2VvbWV0cnkgaXMgbmF0dXJhbGx5IGEgUE9JTlQgdHlwZSBvZiBnZW9tZXRyeSBjb2x1bW4uCgpUaGVzZSBhcmUgdGhlIG5hbWVzIG9mIGNvbHVtbnMgaW4gdGhlIFJlc3RhdXJhbnQgRGF0YTogTm90ZSB0aGUKYGN1aXNpbmVgIGNvbHVtbi4KCmBgYHtyIHJlc3RhdXJhbnQtZGF0YS0zfQoKZ2xpbXBzZShyZXN0YXVyYW50cykKCmBgYAoKU28gbGV0IHVzIHBsb3QgdGhlIHJlc3RhdXJhbnRzIGFzIFBPSU5UcyB1c2luZyB0aGUgYHJlc3RhdXJhbnRzYCBkYXRhIHdlCmhhdmUgZG93bmxvYWRlZC4gVGhlIGBjdWlzaW5lYCBhdHRyaWJ1dGUgbG9va3MgaW50ZXJlc3Rpbmc7IGxldCB1cwpjb2xvdXIgdGhlIFBPSU5UIGJhc2VkIG9uIHRoZSBgY3Vpc2luZWAgb2ZmZXJlZCBhdCB0aGF0IHJlc3RhdXJhbnQuCgpTbyBMZXQncyBsb29rIHRoZXJlZm9yZSBhdCB0aGUgYGN1aXNpbmVgIGNvbHVtbiEKCmBgYHtyIHJlc3RhdXJhbnQtZGF0YS00fQojICggSSB3YW50IHBpenphLi4uKQpyZXN0YXVyYW50cyRjdWlzaW5lICU+JSB1bmlxdWUoKQoKYGBgCgpCaWcgbWVzcy4uLm1hbnkgTkFzLCBzb21lIGRvdWJsZSBlbnRyaWVzLCBzZXBhcmF0ZWQgYnkgY29tbWFzIGFuZApzZW1pY29sb25zLi4uLgoKOjo6IHsucGFuZWwgLnBhbmVsLXN1Y2Nlc3N9Cjo6OiBwYW5lbC1oZWFkaW5nClRoZSBgY3Vpc2luZWAgYXR0cmlidXRlOgo6OjoKCjo6OiBwYW5lbC1ib2R5Ck5vdGU6IFRoZSBgY3Vpc2luZWAgdmFyaWFibGUgaGFzIG1vcmUgdGhhbiBvbmUgZW50cnkgZm9yIGEgZ2l2ZW4KcmVzdGF1cmFudC4gV2UgdXNlIGB0aWR5cjo6c2VwYXJhdGUoKWAgdG8gbWFrZSBtdWx0aXBsZSBjb2x1bW5zIG91dCBvZgp0aGUgY3Vpc2luZSBjb2x1bW4gYW5kIHJldGFpbiB0aGUgZmlyc3Qgb25lIG9ubHkuIFNpbmNlIHRoZSBlbnRyaWVzIGFyZQpiYWRseSBlbnRlcmVkIHVzaW5nIGJvdGggIjsiIGFuZCAiLCIgd2UgbmVlZCB0byBkbyB0aGlzIHR3aWNlIDstKCkgQmFkCkRhdGEgZW50cnkhIQo6OjoKOjo6CgpMZXQncyBnZXQgb25lIGN1aXNpbmUgZW50cnkgcGVyIHJlc3RhdXJhbnQsIGFuZCBkcm9wIG9mZiB0aGUgb25lcyB0aGF0CmRvIG5vdCBtZW50aW9uIGEgY3Vpc2luZSBhdCBhbGw6CgpgYGB7cn0KcmVzdGF1cmFudHMgPC0gcmVzdGF1cmFudHMgJT4lIAogIGRyb3BfbmEoY3Vpc2luZSkgJT4lICMgS25vY2sgb2ZmIG5vbmRlc2NyaXB0IHJlc3RhdXJhbnRzCiAgCiAgIyBTb21lIGhhdmUgbW9yZSB0aGFuIG9uZSBjbGFzc2lmaWNhdGlvbiA7LSgpCiAgIyBTZXBhcmF0ZWQgYnkgc2VtaWNvbG9uIG9yIGNvbW1hLCBzby4uLi4KICBzZXBhcmF0ZV93aWRlcl9kZWxpbShjb2xzID0gY3Vpc2luZSwgCiAgICAgICAgICAgICAgICAgICAgICAgbmFtZXMgPSBjKCJjdWlzaW5lIiwgTkEsIE5BKSwgCiAgICAgICAgICAgICAgICAgICAgICAgZGVsaW0gPSAiOyIsIAogICAgICAgICAgICAgICAgICAgICAgIHRvb19mZXcgPSAiYWxpZ25fc3RhcnQiLAogICAgICAgICAgICAgICAgICAgICAgIHRvb19tYW55ID0gImRyb3AiKSAlPiUgCiAgc2VwYXJhdGVfd2lkZXJfZGVsaW0oY29scyA9IGN1aXNpbmUsIAogICAgICAgICAgICAgICAgICAgICAgIG5hbWVzID0gYygiY3Vpc2luZSIsIE5BLCBOQSksIAogICAgICAgICAgICAgICAgICAgICAgIGRlbGltID0gIiwiLAogICAgICAgICAgICAgICAgICAgICAgIHRvb19mZXcgPSAiYWxpZ25fc3RhcnQiLAogICAgICAgICAgICAgICAgICAgICAgIHRvb19tYW55ID0gImRyb3AiKQoKIyBGaW5hbGx5IGdvb2QgZm9vZD8KcmVzdGF1cmFudHMkY3Vpc2luZQpgYGAKCkxvb2tzIGNsZWFuISBFYWNoIGVudHJ5IGlzIG9ubHkgT05FIGFuZCBub3QgbXVsdGlwbGUgYW55IG1vcmUuIE5vdyBsZXQncwpwbG90IHRoZSBSZXN0YXVyYW50cyBhcyBQT0lOVHM6CgpgYGB7cn0KIyBodHRwOi8vd3d3LnN0YXQuY29sdW1iaWEuZWR1L350emhlbmcvZmlsZXMvUmNvbG9yLnBkZgojIApnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gYnVpbGRpbmdzLCBjb2xvdXIgPSAiYnVybHl3b29kMSIpICsKICBnZW9tX3NmKGRhdGEgPSByb2FkcywgY29sb3VyID0gImdyYXk4MCIpICsKICBnZW9tX3NmKAogICAgZGF0YSA9IHJlc3RhdXJhbnRzICU+JSBkcm9wX25hKGN1aXNpbmUpLAogICAgYWVzKGZpbGwgPSBjdWlzaW5lLCBnZW9tZXRyeSA9IGdlb20pLAogICAgY29sb3VyID0gImJsYWNrIiwKICAgIHNoYXBlID0gMjEsCiAgICBzaXplID0gMwogICkgKyAgCiAgIyBTZXQgcGxvdCBsaW1pdHMgdG8gZXhhY3RseSB0aGUgYmJveF8yCiAgY29vcmRfc2YoeGxpbSA9IGMoYmJveF8yWzEsMV0sIGJib3hfMlsxLDJdKSwKICAgICAgICAgICB5bGltID0gYyhiYm94XzJbMiwxXSwgYmJveF8yWzIsMl0pLAogICAgICAgICAgIGV4cGFuZCA9IEZBTFNFKSArIAogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsKICBsYWJzKHRpdGxlID0gIlJlc3RhdXJhbnRzIGluIFNvdXRoIENlbnRyYWwgQmFuZ2Fsb3JlIiwKICAgICAgIGNhcHRpb24gPSAiQmFzZWQgb24gb3NtZGF0YSIpCmBgYAoKV2UgY291bGQgaGF2ZSBkb25lIGEgKG11Y2ghKSBiZXR0ZXIgam9iLCBieSBjb21iaW5pbmcgY3Vpc2luZXMgaW50bwpzaW1wbGVyIGFuZCBmZXdlciBjYXRlZ29yaWVzLCAoIFNvdXRoX0luZGlhIGFuZCBTb3V0aF9JbmRpYW4gKSwgYnV0IHRoYXQKaXMgZm9yIGFub3RoZXIgZGF5ISEKCkJ5IG5vdyB3ZSBrbm93IHRoYXQgd2UgY2FuIHVzZSBgZ2VvbV9zZigpYCBtdWx0aXBsZSBudW1iZXIgb2YgdGltZXMgd2l0aApkaWZmZXJlbnQgZGF0YXNldHMgdG8gY3JlYXRlIGxheWVyZWQgbWFwcyBpbiBSLgoKCiMgU29tZSBmYW5jeSBzdHVmZgoKTGV0IHVzIHRyeSBtYWtpbmcgZ2xvYiBiYXNlZCBtYXBzIHdpdGggdGhlIHBhY2thZ2UgYHRocmVlanNgLiBUaGlzCnBhY2thZ2UgaXMgb25lIG9mIHRoZSBmYW1pbHkgb2YgcGFja2FnZXMgaW4gdGhlIGBodG1sd2lkZ2V0c2AgZ3JvdXAgb2YKcGFja2FnZXMuIEl0IGFsbG93cyB0aGUgdXNlIG9mIHNvbWUgKCBmYW1vdXMhKSBKYXZhU2NyaXB0IGdyYXBoaW5nCmxpYnJhcmllcyBkaXJlY3RseSBhbmQgbmF0aXZlbHkgaW4gUi4KCiMjIGBnbG9iZWpzYCB1c2FnZQoKVGhlIGBnbG9iZWpzYCBjb21tYW5kIGZyb20gdGhlIHBhY2thZ2UgYHRocmVlanNgIGFsbG93cyBvbmUgdG8gcGxvdApwb2ludHMsIGFyY3MgYW5kIGltYWdlcyBvbiBhIGdsb2JlIGluIDNELiBUaGUgZ2xvYmUgY2FuIGJlIHJvdGF0ZWQgYW5kCmFuZCB6b29tZWQuIEdyZWF0IENpcmNsZXMgYW5kIGhpc3RvcmljYWwgcm91dGVzIGFyZSBhIGdvb2QgaWRlYSBmb3IgdGhpcwpwZXJoYXBzLgoKUmVmZXIgdG8gdGhpcyBwYWdlIGZvciBtb3JlIGlkZWFzCjxodHRwOi8vYndsZXdpcy5naXRodWIuaW8vcnRocmVlanMvZ2xvYmVqcy5odG1sPgoKV2Ugd2lsbCBnZW5lcmF0ZSBzb21lIHJhbmRvbSBsb2NhdGlvbnMgYW5kIHBsb3QgdGhlbSBvbiB0aGUgM0QgZ2xvYmUuCgpgYGB7cn0KIyBSYW5kb20gTGF0cyBhbmQgTG9uZ3MKbGF0IDwtIHJwb2lzKDEwLCA2MCkgKyBybm9ybSgxMCwgODApCmxvbmcgPC0gcnBvaXMoMTAsIDYwKSArIHJub3JtKDEwLCAxMCkKCiMgUmFuZG9tICJTcGlrZSIgaGVpZ2h0cyBmb3IgZWFjaCBsb2NhdGlvbi4gUG9wdWxhdGlvbj8gVG91cmlzdHM/IEdEUD8KdmFsdWUgPC0gcnBvaXMoMTAsIGxhbWJkYSA9IDgwKQogCmdsb2JlanMobGF0ID0gbGF0LCBsb25nID0gbG9uZykKCmBgYAoKQXMgc2VlbiwgInNwaWtlcyIgYXJlIGNyZWF0ZWQgYXQgdGhlIHJhbmRvbSBsYXQtbG9uIGxvY2F0aW9ucy4gV2UgY2FuCmNvbnRyb2wgdGhlIGhlaWdodC93aWR0aC9jb2xvdXIgb2YgdGhlIHNwaWtlcywgYXMgd2VsbCBhcyB0aGUgaW5pdGlhbAp2aWV3IG9mIHRoZSBnbG9iZSBpdHNlbGY6IHpvb20sIGxvY2F0aW9uIGFuZCBzbyBvbgoKYGBge3J9Cmdsb2JlanMoCiAgbGF0ID0gbGF0LAogIGxvbmcgPSBsb25nLAogIAogICMgcmFuZG9tIGhlaWdodHMgb2YgdGhlIFNwaWtlcyAoISEpIGF0IGxhdC1sb25nIGNvbWJvCiAgdmFsdWUgPSB2YWx1ZSwKICBjb2xvciA9ICJyZWQiLAogICMgWm9vbSBmYWN0b3IsIGRlZmF1bHQgaXMgMzUKICBmb3YgPSA1MAopCmBgYAoKYGBge3J9Cmdsb2JlanMoCiAgbGF0ID0gbGF0LAogIGxvbmcgPSBsb25nLAogIHZhbHVlID0gdmFsdWUsCiAgY29sb3IgPSAicmVkIiwKICBwb2ludHNpemUgPSA0LCAjIHdpZHRoIG9mIHRoZSBjb2x1bW5zCiAgIyBab29tIHBvc2l0aW9uCiAgZm92ID0gMzUsCiAgIyBpbml0aWFsIHBvc2l0aW9uIG9mIHRoZSBnbG9iZQogIHJvdGF0aW9ubGF0ID0gMC42LCAjICBpbiBSQURJQU5TICEhISBHb29kIEhlYXZlbnMhIQogIHJvdGF0aW9ubG9uZyA9IDAuMiAjICBpbiBSQURJQU5TICEhISBHb29kIEhlYXZlbnMhIQopCmBgYAoKYGBge3J9Cmdsb2JlanMoCiAgbGF0ID0gbGF0LAogIGxvbmcgPSBsb25nLAogIHZhbHVlID0gdmFsdWUsCiAgY29sb3IgPSAicmVkIiwKICBwb2ludHNpemUgPSA0LAogIGZvdiA9IDM1LAogIHJvdGF0aW9ubGF0ID0gMC42LAogIHJvdGF0aW9ubG9uZyA9IDAuMiwKICBsaWdodGNvbG9yID0gIiNhYWVlZmYiLAogIGVtaXNzaXZlID0gIiMwMDAwZWUiLAogIGJvZHljb2xvciA9ICIjZmZmZmZmIiwKICBiZyA9ICJncmV5IgopCgpgYGAKCiMgU2NvcGUgYW5kIFBhY2thZ2VzIGZvciBFeHBsb3JhdGlvbiEhCgojIyMgc2ZuZXR3b3JrcwoKIyMjIG1hcHNmCgojIyMgZ2dzcGF0aWFsCgojIFJlc291cmNlcwoKMS4gIEVtaW5lIEZpZGFuLCBbR3VpZGUgdG8gQ3JlYXRpbmcgSW50ZXJhY3RpdmUgTWFwcyBpbgogICAgUl0oaHR0cHM6Ly9ib29rZG93bi5vcmcvZW5lbWluZWYvRFJSX0Jvb2tkb3duLykKCjIuICBOaWtpdGEgVm9ldm9kaW4sW1IsIE5vdCB0aGUgQmVzdAogICAgUHJhY3RpY2VzXShodHRwczovL2Jvb2tkb3duLm9yZy92b2V2b2Rpbl9udi9SX05vdF90aGVfQmVzdF9QcmFjdGljZXMvbWFwcy5odG1sKQoKIyBBc3NpZ25tZW50cwoKMS4gIERyYXcgYSBtYXAgb2YgKip5b3VyIGhvbWUtdG93bioqIHdpdGggeW91ciBmYXZvdXJpdGUgcmVzdGF1cmFudHMKICAgIHNob3duLiBQb3AtdXBzIGZvciBlYWNoIHJlc3RhdXJhbnQgd2lsbCB3aW4gYm9udXMgcG9pbnRzLgoKMi4gIERvd25sb2FkIGJpcmQgbWlncmF0aW9uIGRhdGEgZnJvbSBgbW92ZWJhbmsub3JnYC4gSW1wb3J0IHRoZXNlIGludG8KICAgIFIgYW5kIHBsb3QgYSBtaWdyYXRpb24gbWFwIHVzaW5nIGB0bWFwYC4gSW5jbHVkZSB0aGUgZ3JhdGljdWxlLAogICAgY29tcGFzcywgbGVnZW5kLCBhbmQgY3JlZGl0cy4KCiMjIyBJbnNwaXJhdGlvbgoKMS4gIEJ1cmtoYXJ0LCBDaHJpc3RpYW4uIG4uZC4gIlN0cmVldG1hcHMuIgogICAgW1N0cmVldE1hcHNdKGh0dHBzOi8vZ2dwbG90MnR1dG9yLmNvbS90dXRvcmlhbHMvc3RyZWV0bWFwcykKCiFbXShodHRwczovL2dncGxvdDJ0dXRvci5jb20vc3RhdGljLzA5MzQ2NmEwZjk0ZjA0ZjM2ZTJjMDI4ZmU3YWUzZjIzL2ZkODRlL21hcHMucG5nKQoKMi4gICpNYWtpbmcgVmVjdG9yIE1hcHMqLCBDb21wdXRpbmcgZm9yIHRoZSBTb2NpYWwgU2NpZW5jZXMsIFtVbml2LiBvZgogICAgQ2hpY2Fnb10oaHR0cHM6Ly9jZnNzLnVjaGljYWdvLmVkdS9ub3Rlcy92ZWN0b3ItbWFwcy8pCg==