Setup

First, install some new packages.

#install.packages("Rtools", version = "4.2")
#install.packages("Rcpp", version = "1.0.10")
if(!require(tmap)){install.packages("tmap", options = options(timeout = 900))}
if(!require(sf)){install.packages("sf", options = options(timeout = 900))}
if(!require(maps)){install.packages("maps")}
if(!require(mapsf)){install.packages("mapsf")}

Then, activate the ones we need.

library(tidyverse)
library(readxl)
library(tmap)               # Geographical plotting
library(gapminder)          # Country level data
library(maps)               # Geographical datasets
library(sf)                 # Shape files (sf) are a common geographical format 
library(plotly)             # Dynamic graphs
library(mapsf)              # A cool new package!

Now, load the data (included in the packages). The World dataset contains geographical data.

load("World.RData")        # From tmap!
names(World)[2] <- "country"                    # Change the column name
str(World)           # structure of World
Classes ‘sf’ and 'data.frame':  177 obs. of  16 variables:
 $ iso_a3      : Factor w/ 177 levels "AFG","AGO","ALB",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ country     : Factor w/ 177 levels "Afghanistan",..: 1 4 2 166 6 7 5 56 8 9 ...
 $ sovereignt  : Factor w/ 171 levels "Afghanistan",..: 1 4 2 159 6 7 5 52 8 9 ...
 $ continent   : Factor w/ 8 levels "Africa","Antarctica",..: 3 1 4 3 8 3 2 7 6 4 ...
 $ area        : Units: [km^2] num  652860 1246700 27400 71252 2736690 ...
 $ pop_est     : num  28400000 12799293 3639453 4798491 40913584 ...
 $ pop_est_dens: num  43.5 10.3 132.8 67.3 15 ...
 $ economy     : Factor w/ 7 levels "1. Developed region: G7",..: 7 7 6 6 5 6 6 6 2 2 ...
 $ income_grp  : Factor w/ 5 levels "1. High income: OECD",..: 5 3 4 2 3 4 2 2 1 1 ...
 $ gdp_cap_est : num  784 8618 5993 38408 14027 ...
 $ life_exp    : num  59.7 NA 77.3 NA 75.9 ...
 $ well_being  : num  3.8 NA 5.5 NA 6.5 4.3 NA NA 7.2 7.4 ...
 $ footprint   : num  0.79 NA 2.21 NA 3.14 2.23 NA NA 9.31 6.06 ...
 $ inequality  : num  0.427 NA 0.165 NA 0.164 ...
 $ HPI         : num  20.2 NA 36.8 NA 35.2 ...
 $ geometry    :sfc_MULTIPOLYGON of length 177; first list element: List of 1
  ..$ :List of 1
  .. ..$ : num [1:69, 1:2] 61.2 62.2 63 63.2 64 ...
  ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
  ..- attr(*, "names")= chr [1:15] "iso_a3" "name" "sovereignt" "continent" ...
data("gapminder")
head(gapminder)

The world data is very rich: it has many attributes (population, economy, etc.). The one column we are interested in is geometry: it contains the data for the maps!

NOTE: there are 2 very useful packages that allow to retrieve data from the World Bank: WID and wbstats: have a look!

We see that a few points are missing for life expectancy, so let’s see if we can complete with the gapminder data (spoiler: not so much!).

This exercise (see below) of grouping two datasets is paramount when plotting geographical content because usually, the geometric properties come from one file and the items we wish to plot come from another file. Thus, the joining step is KEY.

Areas

Maps in which areas are coloured to show some numerical attribute are called choropleths.

With the tmap package

The idea below is to merge the two sources to combine geographical forms and life expectancy. To do that, we need left_join(). But before, we need to create a common variable (i.e., a key) between the two. In World, the term “name” is not so great so let’s change it into “country” (just like in gapminder!). Then we can merge!

gapminder %>% 
  left_join(World, by = "country") %>%          # Join the two datasets
  select(country, lifeExp, pop, geometry)
nrow(gapminder)                                   # Number of instances
[1] 1704

There are missing points. As a rough approximation, let’s just remove them and store the result in data (more on them later).

data <- gapminder %>%                     # Join and save via the arrow operator <- 
  left_join(World, by = "country") %>%
  na.omit()                               # Remove rows with missing data
nrow(data)                                # Number of instances in dataset
[1] 1296

The loss is large because of missing points! We then use the tmap package to create a choropleth.

Some countries are missing => data problem because gapminder is incomplete. Let’s complete it via: https://www.gapminder.org/data/ This yields a much bigger dataset, called “gap2.RData”. Let’s see:

load("gap2.RData")
head(gap2)

Again, we resort to left_join to merge the two data sources.

data <- World %>% 
  left_join(gap2, by = "country") #%>%
#na.omit() 

With the mapsf package

References:
https://riatelab.github.io/mapsf/articles/mapsf.html
https://github.com/riatelab/mapsf

It’s super easy: you just need a shape file with the attribute you want to plot!

sf::sf_use_s2(FALSE)
data %>% 
  filter(year == 2050) %>%    # Keep only one year (problems otherwise)
  sf::st_sf() %>%
  mf_map(x = ., var = "population", type = "choro")

The package does not really use pipes/layers.

map <- data %>% 
  filter(year == 2018) %>%    # Keep only one year (problems otherwise)
  sf::st_sf()

my_theme <- list(
  name = "mytheme", 
  bg = "#113366",        # Light blue
  fg = "#FFFFFF",        # Dark grey
  mar = c(0, 0, 0, 0), 
  tab = TRUE, 
  pos = "center", 
  inner = TRUE, 
  line = 1, 
  cex = .9, 
  font = 3
)

mf_theme(my_theme)
mf_map(x = map, type = "base")
mf_map(x = map,
       add = T,             # After you created a map with mf_map, you need this!
       var = c("inequality", "well_being"),      # Two variables! BUT BE CAREFUL!
       type = "prop_choro",
       inches = 0.1,                             # Scale of circles
       lwd = 1,                                  # Line width
       breaks = "equal", 
       nbreaks = 6,                              # Number of colors breaks
       leg_title_cex = c(1,1),                   # Size of legend title
       leg_val_cex = c(0.5,0.8),                 # Size of legend text
       pal = "Spectral",                         # Palette
       leg_pos = c("bottomleft", "left"),        # Legend position
       leg_title= c("Inequality","Well being"),  # Legend titles
       leg_val_rnd = c(2, 2),                    # Number of decimals in legends
       leg_frame = c(TRUE, TRUE))                # Frames around legend?
mf_layout(title = "Inequality & well-being in the world", 
          credits = paste0("Sources: MISC"), 
          frame = TRUE)

With the leaflet package

Next, we move towards interactive maps with the leaflet package. The advantage of leaflet lies in its multiple options that create dynamic plots. The example below is strongly inspired from the reference page: https://rstudio.github.io/leaflet/choropleths.html Thus, looking at the difference between the two codes helps understand how they work.

We are going two use two features:
- a customized color scale;
- labels that appear when the cursor is on the map.

For the scale, we define it manually, both for the colors and for the separation points (bins).
The labels are defined using html functions.

One word on missing data: in the World data, “Czech Rep.” stands for “Czech Republic”, so we recode manually it to fill in a hole. Since it’s a factor in World, we need to use the recode_factor function. For simple text, recode would be sufficient.

if(!require(leaflet)){install.packages("leaflet")}                  # Install package
library(leaflet)                                                    # Load package
datamap <- World %>% 
  mutate(country = country %>% recode_factor(`Czech Rep.` = "Czech Republic")) %>%
  left_join(gap2, by = "country") %>%
  filter(year == 2018)                                # Keep only one year (recent)

palet <- colorBin("YlGnBu",                                         # Yellow-Green-Blue palette
                  domain = datamap %>% pull(lifeExp), # LifeExp     # Domain of labels: lifeExp
                  bins = c(50, 55, 60, 65, 70, 75, 80, 85))         # Age categories

labels <- sprintf(                                                  # Below we define the labels
  "<strong>%s</strong><br/>%g Years",                               # Adding text to label
  datamap$country,                                                  # We show the country name...
  datamap$lifeExp                                                  # ... and the life expectancy
) %>% lapply(htmltools::HTML)                                       # Embedded all into html language

We can then move forward with the plot.

datamap %>% 
  data.frame() %>%                                # Turn into dataframe (technical)
  sf::st_sf() %>%                                 # Format in sf
  #st_transform("+init=epsg:4326") %>%             # Convert in particular coordinate reference 
  leaflet() %>%                                   # Call leaflet
  addPolygons(fillColor = ~palet(lifeExp),        # Create the map (colored polygons)
              weight = 2,                         # Width of separation line
              opacity = 1,                        # Opacity of separation line
              color = "white",                    # Color of separation line
              dashArray = "3",                    # Dash size of separation line
              fillOpacity = 0.7,                  # Opacity of polygon colors
              highlight = highlightOptions(       # 5 lines below control the cursor impact
                weight = 2,                       # Width of line
                color = "#333333",                # Color of line
                dashArray = "",                   # No dash
                fillOpacity = 0.9,                # Opacity
                bringToFront = TRUE),
              label = labels,                     # LABEL! Defined above!
              labelOptions = labelOptions(        # Label options below...
                style = list("font-weight" = "normal", padding = "3px 8px"),
                textsize = "15px",
                direction = "auto")
  ) %>%
  addLegend(pal = palet,                    # Legend: comes from palet colors defined above
            values = ~lifeExp,              # Values come from lifeExp variable
            opacity = 0.9,                  # Opacity of legend
            title = "Map Legend",           # Title of legend
            position = "bottomright")       # Position of legend

Better, but some African countries are still missing… Data problem!

It is possible to integrate leaflet into shiny: https://rstudio.github.io/leaflet/shiny.html You need renderLeaflet() in the server and leafletOutput() in the UI.

Points (with ggplot!)

Points are more easy to handle because they require less information (two coordinates at least plus other attributes like size or color). This is exactly the kind of data type that fits in the ggplot syntax! Below, we show how to combine ggplot with geographical data.

Country level data

While areas must be defined at the country level (complex!), points are simply characterized by two numbers: latitude & longitude. So a list of these two features suffices to plot points. For countries, it is possible to find data online, ex: https://developers.google.com/public-data/docs/canonical/countries_csv

The latitudes and longitudes correspond to the center of the countries.

Below, we create a new dataset with longitudes & latitutes combined with gapminder (updated version).

if(!require(readxl)){install.packages("readxl")}      # Install package to read excel files
library(readxl)                                       # Activate package
options(scipen=999)                                   # Remove scientific notation
country_LL <- read_excel("country_LL.xlsx")           # Read longitude/latitude data
data2 <- left_join(gap2, country_LL) %>%              # Merge two datasets
  #na.omit() %>%                                    # Remove missing points
  mutate(latitude = as.numeric(latitude),           # 'Numerize' latitude
         longitude = as.numeric(longitude),         # 'Numerize' longitude
         population = round(population / 10^6,1))   # Scaling population into millions
data2 %>% head()

Be careful, the code below is computationally demanding (be patient). We use the viridis color palette. In ggplot, a small list of maps is available: world, France, Italy, New Zealand, US (and states).

if(!require(scales)){install.packages("scales")}
library(scales) 
world <- map_data("world")                                # From ggplot
g <- ggplot(data = world) +                               # Data source
  geom_polygon(data = world, aes(x = long,                # Dark background of the world
                                 y = lat,
                                 group = group)) +   
  geom_point(data = data2 %>% filter(year == 2018),       # Most recent data points
             aes(x = longitude,                           # Classical ggplot syntax!
                 y = latitude, 
                 size = gdpPercap, 
                 color = lifeExp, 
                 label = country)) +                      # Label (for plotly)
  scale_color_viridis_c(option = "magma") + ylim(-70,80)    # Color scale
ggplotly(g)                                               # Plotly integration

City level data

You can find longitudes & latitudes by searching on Google. Here’s one example for France: https://simplemaps.com/data/fr-cities Worldwide data can be accessed here: https://simplemaps.com/data/world-cities

cities <- read_excel("worldcities.xlsx")

That’s too big. Let’s focus on Germany, Italy, Austria, Slovenia and Switzerland (arbitrarily) - or other choices.

cities_short <- cities %>% 
  filter(#country %in% c("Germany", "Italy", "Switzerland", "Austria", "Slovenia"),
    country %in% c("China", "India", "Nepal", "Bangladesh", "Thailand"),
    population > 350000) %>%
  na.omit()
head(cities_short)
world <- map_data("world")
g <- ggplot(data = world) +                                     # Data source
  geom_polygon(data = world,                                    # Dark background of the world
               aes(x = long, y = lat, group = group),
               size = 0.3, color = "grey") +                    # Outer lines of polygons
  geom_point(data = cities_short,                               # The points
             aes(x = lng, 
                 y = lat, 
                 size = population, 
                 color = country, 
                 label = city)) +
  #scale_colour_gradient(low = "#FFFB00", high = "#FF1B00") +  # This line is for color = population
  scale_colour_manual(values = c("#FFEC00", "#66E234", "#1BCBF7", "#CC44F8", "#FC2361")) +
  coord_sf(xlim = c(70, 125), ylim = c(6, 52), expand = FALSE) + theme_bw() # Zoom on Europe
# coord_sf(xlim = c(5, 18), ylim = c(36, 57), expand = FALSE) + theme_bw() # Zoom on Europe
ggplotly(g)

Other maps

For points, it’s easy: just use the same coordinate system via longitude & latitude. It’s much more tricky for areas.

The key is to find shape files (one alternative format is GeoJSON, but it is not treated here).
Example: http://datapages.com/gis-map-publishing-program/gis-open-files/global-framework/ Be very careful: some maps are EXTREMELY precise (up to 1m precision). Thus, the files are heavy and the plotting takes a LOT of time. Below, we use 100m precision, which is largely enough.

The full description of all shape file items can be accessed on wikipedia:

  • .shp — shape format; the feature geometry itself
  • .shx — shape index format; a positional index of the feature geometry to allow seeking forwards and backwards quickly
  • .dbf — attribute format; columnar attributes for each shape, in dBase IV format

For instance, it’s possible to fetch data for French regions at: https://www.data.gouv.fr/fr/datasets/contours-geographiques-des-nouvelles-regions-metropole/

regions <- sf::st_read("regions-20140306-100m.shp")
Reading layer `regions-20140306-100m' from data source 
  `/Users/coqueret/Documents/IT/COURS/R4DS/S7_extensions/Geocomputing/regions-20140306-100m.shp' using driver `ESRI Shapefile'
Simple feature collection with 27 features and 10 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -61.80976 ymin: -21.38973 xmax: 55.83665 ymax: 51.08984
Geodetic CRS:  WGS 84
str(regions)    # Structure of the variable region
Classes ‘sf’ and 'data.frame':  27 obs. of  11 variables:
 $ code_insee: chr  "42" "72" "83" "25" ...
 $ nom       : chr  "Alsace" "Aquitaine" "Auvergne" "Basse-Normandie" ...
 $ nom_cl    : chr  "Strasbourg" "Bordeaux" "Clermont-Ferrand" "Caen" ...
 $ insee_cl  : chr  "67482" "33063" "63113" "14118" ...
 $ nuts2     : chr  "FR42" "FR61" "FR72" "FR25" ...
 $ iso3166_2 : chr  NA NA NA NA ...
 $ wikipedia : chr  "fr:Alsace" "fr:Aquitaine" "fr:Auvergne" "fr:Basse-Normandie" ...
 $ nb_dep    : num  2 5 4 3 4 4 6 4 2 4 ...
 $ nb_comm   : num  904 2296 1310 1812 2046 ...
 $ surf_km2  : num  8328 41818 26172 17786 31752 ...
 $ geometry  :sfc_MULTIPOLYGON of length 27; first list element: List of 1
  ..$ :List of 1
  .. ..$ : num [1:760, 1:2] 7.43 7.43 7.42 7.42 7.4 ...
  ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA
  ..- attr(*, "names")= chr [1:10] "code_insee" "nom" "nom_cl" "insee_cl" ...

That’s complicated. The only important column for now is the name of the region which is easy to recognize. Below, we plot a choropleth where the color is link to the area of the region (bigger = darker).

regions %>% 
  tm_shape() +
  tm_polygons("surf_km2")


regions %>% 
  sf::st_sf() %>%
  mf_map(x = ., var = "surf_km2", type = "choro")

That’s prety ugly. Let’s remove the regions outside the mainland.

regions %>%
  filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>% 
  tm_shape() +
  tm_polygons("surf_km2", border.col = "white") # With the border of polygons


regions %>% 
  filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>% 
  sf::st_sf() %>%
  mf_map(x = ., var = "surf_km2", type = "choro", pal = "Spectral")

Below, we change the color palette and plot the number of French “départements” inside each region.

regions %>%
  filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>% 
  tm_shape() +
  tm_fill("nb_dep", palette = "Spectral")  # Without the border

This is static: leaflet integration would be better.

Highcharts

The material below is inspired from the vignette: https://jkunst.com/highcharter/

The highcharter package creates good looking plots. Let’s install it (and activate it).

if(!require(highcharter)){install.packages("highcharter")}
library(highcharter)

The package has a large library of maps. The list is given here: https://code.highcharts.com/mapdata/

If you click on a link, you get the address of the map:

The last part is what you specify to get access to a map:

mapdata <- get_data_from_map(download_map_data("countries/fr/custom/fr-all-mainland"))
trying URL 'https://code.highcharts.com/mapdata/countries/fr/custom/fr-all-mainland.js'
Content type 'text/javascript' length 23677 bytes (23 KB)
==================================================
downloaded 23 KB
head(mapdata)

The information that will be plotted comes from this file. We need to add the value that we want to use for the plot. There are 21 regions in the variable map_data. This bypasses the merging of external data via the left_join() function.

mapdata <- mapdata %>% mutate(value = 1:nrow(mapdata)) # Completely random values
head(mapdata, 20)

Below, we plot the regions of France and the color code is driven by the column “value” defined above.

hcmap("countries/fr/custom/fr-all-mainland",                      # Source for the map
      data = mapdata,                                             # Source for the color code
      value = "value",                                            # Column for color code
      joinBy = c("name"),                                         # Common name & label
      name = "Random Plot",                                       # Name of plot
      dataLabels = list(enabled = TRUE, format = "{point.name}"), # Allow labels
      borderColor = "#FAFAFA", borderWidth = 0.5,                 # Border info
      tooltip = list(valueDecimals = 1,                           # Label info
                     valuePrefix = "Value = ",                    # Before label
                     valueSuffix = " Units")) %>%                 # After label
  hc_colorAxis(
    stops = color_stops(colors = viridisLite::inferno(10, begin = 0.1)),
    type = "logarithmic"
  ) 
trying URL 'https://code.highcharts.com/mapdata/countries/fr/custom/fr-all-mainland.js'
Content type 'text/javascript' length 23677 bytes (23 KB)
==================================================
downloaded 23 KB

Fine grain local maps

Now let’s turn to smaller scale maps. The data comes from Kaggle, scrapped from tripadvisor. The useful columns: Longitude & Latitude, the MuseumName, the Rating and the LengthOfVisit.

tripadvisor <- read_excel("tripadvisor_museum_US.xlsx") %>%    # Importing the data
  mutate(Longitude = as.numeric(Longitude),                    # Getting numbers back
         Latitude = as.numeric(Latitude),
         Rating = as.numeric(Rating),
         LengthOfVisit = as.factor(LengthOfVisit)) %>%
  filter(Latitude > 40, Latitude < 41, Longitude > (-74.5), Longitude < (-73.5)) # NY museums only
Warning: There were 2 warnings in `mutate()`.
The first warning was:
ℹ In argument: `Longitude = as.numeric(Longitude)`.
Caused by warning:
! NAs introduced by coercion
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 1 remaining warning.
tripadvisor

With leaflet, it’s easy to obtain empty maps as long as you know the longitude & latitude of a particular location. Below: Lyon.

leaflet() %>%                                                  # An empty map: a good start!
  setView(lng = 4.85, lat = 45.75, zoom = 12) %>% 
  addTiles()
pal <- colorFactor(palette = "Spectral", domain = tripadvisor$LengthOfVisit)
tripadvisor %>%
  leaflet() %>%                                                   # A blank sheet...
  setView(lng = -73.9772, lat = 40.7808, zoom = 11) %>% 
  addTiles() %>%
  addCircles(lng = ~Longitude, lat = ~Latitude, 
             radius = ~Rating^4, label =  ~MuseumName,
             fillColor = ~pal(LengthOfVisit), fillOpacity = 0.9,  # Circle colors
             stroke = TRUE, color = "black", weight = 2) %>%      # Circle stroke
  addLegend(pal = pal,                      # Legend: comes from palet colors defined above
            values = ~LengthOfVisit,        # Values come from lifeExp variable
            opacity = 0.7,                  # Opacity of legend
            title = "Map Legend",           # Title of legend
            position = "bottomright")       # Position of legend

Below, you need to specify lon and lat in the source dataframe. Or you can use the hcaes() function.

mapdata2 <- get_data_from_map(download_map_data("countries/us/custom/us-ny-congress-113"))
trying URL 'https://code.highcharts.com/mapdata/countries/us/custom/us-ny-congress-113.js'
Content type 'text/javascript' length 23597 bytes (23 KB)
==================================================
downloaded 23 KB
hcmap("countries/us/custom/us-ny-congress-113") %>%
  hc_mapNavigation(enabled = TRUE,
                   buttonOptions = list(
                     align = "right",
                     verticalAlign = "bottom")) %>%
  hc_add_series(data = tripadvisor %>% mutate(lon = Longitude, 
                                              lat = Latitude, 
                                              name = MuseumName),  # Add correct names
                type = "mappoint", name = "Museums") 
trying URL 'https://code.highcharts.com/mapdata/countries/us/custom/us-ny-congress-113.js'
Content type 'text/javascript' length 23597 bytes (23 KB)
==================================================
downloaded 23 KB

Not as impressive as leaflet, but the map could probably be improved. NOTE: in the example above, the geographical background and the points are unrelated.
Other map, same result below…

mapdata3 <- get_data_from_map(download_map_data("countries/us/us-ny-all"))
trying URL 'https://code.highcharts.com/mapdata/countries/us/us-ny-all.js'
Content type 'text/javascript' length 34989 bytes (34 KB)
==================================================
downloaded 34 KB
hcmap("countries/us/us-ny-all") %>%
  hc_mapNavigation(enabled = TRUE,
                   buttonOptions = list(
                     align = "right",
                     verticalAlign = "bottom")) %>%
  hc_add_series(data = tripadvisor %>% mutate(lon = Longitude, 
                                              lat = Latitude, 
                                              name = MuseumName),  # Add correct names
                type = "mappoint", name = "Museums") 
trying URL 'https://code.highcharts.com/mapdata/countries/us/us-ny-all.js'
Content type 'text/javascript' length 34989 bytes (34 KB)
==================================================
downloaded 34 KB

A test with GeoJSON

GeoJSON are, with shapefiles the other main standard in geocomputing.
It’s easy to obtain files, e.g., via https://geojson-maps.ash.ms Also, you will need the rgdal package.

#library(rgdal)
library(geojsonsf)
#africa <- rgdal::readOGR("africa.geo.json")
#africa <- gdal_read("custom.geo.json")
africa <- geojson_sf("africa.geo.json")

labels <- sprintf(
  "<strong>%s</strong><br/>%g people",
  africa$name, africa$pop_est
) %>% lapply(htmltools::HTML)
pal <- colorBin("YlOrRd", domain = africa$pop_est, 
                bins = c(0, 100, 500, 1000, 5000, 10000, Inf))

leaflet(africa) %>%
  addTiles() %>%
  addPolygons(fillOpacity = 0.7,
              fillColor = ~pal(pop_est),
              weight = 2,
              opacity = 1,
              color = "white",
              # highlight = highlightOptions(       # 5 lines below control the cursor impact
              #   weight = 2,                       # Width of line
              #   color = "#333333",                # Color of line
              #   dashArray = "",                   # No dash
              #   fillOpacity = 0.9,                # Opacity
              #   bringToFront = TRUE),
              label = labels,
              labelOptions = labelOptions(
                style = list("font-weight" = "normal", padding = "3px 8px"),
                textsize = "15px",
                direction = "auto")) %>%
  addLegend(pal = pal, values = ~pop_est, opacity = 0.7, title = NULL,
            position = "bottomleft")
Warning: Some values were outside the color scale and will be treated as NA
LS0tCnRpdGxlOiAiR2VvY29tcHV0aW5nIHdpdGggUiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCgojIFNldHVwCgpGaXJzdCwgaW5zdGFsbCBzb21lIG5ldyBwYWNrYWdlcy4KCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CiNpbnN0YWxsLnBhY2thZ2VzKCJSdG9vbHMiLCB2ZXJzaW9uID0gIjQuMiIpCiNpbnN0YWxsLnBhY2thZ2VzKCJSY3BwIiwgdmVyc2lvbiA9ICIxLjAuMTAiKQppZighcmVxdWlyZSh0bWFwKSl7aW5zdGFsbC5wYWNrYWdlcygidG1hcCIsIG9wdGlvbnMgPSBvcHRpb25zKHRpbWVvdXQgPSA5MDApKX0KaWYoIXJlcXVpcmUoc2YpKXtpbnN0YWxsLnBhY2thZ2VzKCJzZiIsIG9wdGlvbnMgPSBvcHRpb25zKHRpbWVvdXQgPSA5MDApKX0KaWYoIXJlcXVpcmUobWFwcykpe2luc3RhbGwucGFja2FnZXMoIm1hcHMiKX0KaWYoIXJlcXVpcmUobWFwc2YpKXtpbnN0YWxsLnBhY2thZ2VzKCJtYXBzZiIpfQpgYGAKCgpUaGVuLCBhY3RpdmF0ZSB0aGUgb25lcyB3ZSBuZWVkLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkodG1hcCkgICAgICAgICAgICAgICAjIEdlb2dyYXBoaWNhbCBwbG90dGluZwpsaWJyYXJ5KGdhcG1pbmRlcikgICAgICAgICAgIyBDb3VudHJ5IGxldmVsIGRhdGEKbGlicmFyeShtYXBzKSAgICAgICAgICAgICAgICMgR2VvZ3JhcGhpY2FsIGRhdGFzZXRzCmxpYnJhcnkoc2YpICAgICAgICAgICAgICAgICAjIFNoYXBlIGZpbGVzIChzZikgYXJlIGEgY29tbW9uIGdlb2dyYXBoaWNhbCBmb3JtYXQgCmxpYnJhcnkocGxvdGx5KSAgICAgICAgICAgICAjIER5bmFtaWMgZ3JhcGhzCmxpYnJhcnkobWFwc2YpICAgICAgICAgICAgICAjIEEgY29vbCBuZXcgcGFja2FnZSEKYGBgCgoKTm93LCBsb2FkIHRoZSBkYXRhIChpbmNsdWRlZCBpbiB0aGUgcGFja2FnZXMpLiBUaGUgV29ybGQgZGF0YXNldCBjb250YWlucyBnZW9ncmFwaGljYWwgZGF0YS4gCgpgYGB7cn0KbG9hZCgiV29ybGQuUkRhdGEiKSAgICAgICAgIyBGcm9tIHRtYXAhCm5hbWVzKFdvcmxkKVsyXSA8LSAiY291bnRyeSIgICAgICAgICAgICAgICAgICAgICMgQ2hhbmdlIHRoZSBjb2x1bW4gbmFtZQpzdHIoV29ybGQpICAgICAgICAgICAjIHN0cnVjdHVyZSBvZiBXb3JsZApkYXRhKCJnYXBtaW5kZXIiKQpoZWFkKGdhcG1pbmRlcikKYGBgCgpUaGUgd29ybGQgZGF0YSBpcyB2ZXJ5IHJpY2g6IGl0IGhhcyBtYW55IGF0dHJpYnV0ZXMgKHBvcHVsYXRpb24sIGVjb25vbXksIGV0Yy4pLiBUaGUgb25lIGNvbHVtbiB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBpcyAqKmdlb21ldHJ5Kio6IGl0IGNvbnRhaW5zIHRoZSBkYXRhIGZvciB0aGUgbWFwcyEKCioqTk9URSoqOiB0aGVyZSBhcmUgMiB2ZXJ5IHVzZWZ1bCBwYWNrYWdlcyB0aGF0IGFsbG93IHRvIHJldHJpZXZlIGRhdGEgZnJvbSB0aGUgV29ybGQgQmFuazogKipXSUQqKiBhbmQgKip3YnN0YXRzKio6IGhhdmUgYSBsb29rIQoKV2Ugc2VlIHRoYXQgYSBmZXcgcG9pbnRzIGFyZSBtaXNzaW5nIGZvciBsaWZlIGV4cGVjdGFuY3ksIHNvIGxldCdzIHNlZSBpZiB3ZSBjYW4gY29tcGxldGUgd2l0aCB0aGUgZ2FwbWluZGVyIGRhdGEgKHNwb2lsZXI6IG5vdCBzbyBtdWNoISkuCgoqKlRoaXMgZXhlcmNpc2UgKHNlZSBiZWxvdykgb2YgZ3JvdXBpbmcgdHdvIGRhdGFzZXRzIGlzIHBhcmFtb3VudCB3aGVuIHBsb3R0aW5nIGdlb2dyYXBoaWNhbCBjb250ZW50IGJlY2F1c2UgdXN1YWxseSwgdGhlIGdlb21ldHJpYyBwcm9wZXJ0aWVzIGNvbWUgZnJvbSBvbmUgZmlsZSBhbmQgdGhlIGl0ZW1zIHdlIHdpc2ggdG8gcGxvdCBjb21lIGZyb20gYW5vdGhlciBmaWxlLiBUaHVzLCB0aGUgam9pbmluZyBzdGVwIGlzIEtFWS4qKgoKIyBBcmVhcwoKTWFwcyBpbiB3aGljaCBhcmVhcyBhcmUgY29sb3VyZWQgdG8gc2hvdyBzb21lIG51bWVyaWNhbCBhdHRyaWJ1dGUgYXJlIGNhbGxlZCAqY2hvcm9wbGV0aHMqLgoKIyMgV2l0aCB0aGUgdG1hcCBwYWNrYWdlCgpUaGUgaWRlYSBiZWxvdyBpcyB0byBtZXJnZSB0aGUgdHdvIHNvdXJjZXMgdG8gY29tYmluZSBnZW9ncmFwaGljYWwgZm9ybXMgYW5kIGxpZmUgZXhwZWN0YW5jeS4gVG8gZG8gdGhhdCwgd2UgbmVlZCAqKmxlZnRfam9pbioqKCkuIEJ1dCBiZWZvcmUsIHdlIG5lZWQgdG8gY3JlYXRlIGEgY29tbW9uIHZhcmlhYmxlIChpLmUuLCBhICprZXkqKSBiZXR3ZWVuIHRoZSB0d28uIEluIFdvcmxkLCB0aGUgdGVybSAibmFtZSIgaXMgbm90IHNvIGdyZWF0IHNvIGxldCdzIGNoYW5nZSBpdCBpbnRvICJjb3VudHJ5IiAoanVzdCBsaWtlIGluIGdhcG1pbmRlciEpLiBUaGVuIHdlIGNhbiBtZXJnZSEKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KZ2FwbWluZGVyICU+JSAKICBsZWZ0X2pvaW4oV29ybGQsIGJ5ID0gImNvdW50cnkiKSAlPiUgICAgICAgICAgIyBKb2luIHRoZSB0d28gZGF0YXNldHMKICBzZWxlY3QoY291bnRyeSwgbGlmZUV4cCwgcG9wLCBnZW9tZXRyeSkKbnJvdyhnYXBtaW5kZXIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE51bWJlciBvZiBpbnN0YW5jZXMKYGBgCgpUaGVyZSBhcmUgbWlzc2luZyBwb2ludHMuIEFzIGEgcm91Z2ggYXBwcm94aW1hdGlvbiwgbGV0J3MganVzdCByZW1vdmUgdGhlbSBhbmQgc3RvcmUgdGhlIHJlc3VsdCBpbiAqKmRhdGEqKiAobW9yZSBvbiB0aGVtIGxhdGVyKS4gCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmRhdGEgPC0gZ2FwbWluZGVyICU+JSAgICAgICAgICAgICAgICAgICAgICMgSm9pbiBhbmQgc2F2ZSB2aWEgdGhlIGFycm93IG9wZXJhdG9yIDwtIAogIGxlZnRfam9pbihXb3JsZCwgYnkgPSAiY291bnRyeSIpICU+JQogIG5hLm9taXQoKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSByb3dzIHdpdGggbWlzc2luZyBkYXRhCm5yb3coZGF0YSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTnVtYmVyIG9mIGluc3RhbmNlcyBpbiBkYXRhc2V0CmBgYAoKVGhlIGxvc3MgaXMgbGFyZ2UgYmVjYXVzZSBvZiBtaXNzaW5nIHBvaW50cyEgV2UgdGhlbiB1c2UgdGhlICp0bWFwKiBwYWNrYWdlIHRvIGNyZWF0ZSBhICoqY2hvcm9wbGV0aCoqLgoKCmBgYHtyLCBmaWcud2lkdGg9OX0KZGF0YSAlPiUgCiAgZmlsdGVyKHllYXIgPT0gMjAwNykgJT4lICAgICMgS2VlcCBvbmx5IG9uZSB5ZWFyIChwcm9ibGVtcyBvdGhlcndpc2UpCiAgc2Y6OnN0X3NmKCkgJT4lICAgICAgICAgICAgICMgVHJhbnNmb3JtIGRhdGEgaW50byBzaGFwZSBmaWxlIChzZikgZm9ybWF0CiAgdG1fc2hhcGUoKSArICAgICAgICAgICAgICAgICMgVHJhbmZvcm1zIHRoZSBzZiBpbnRvIGEgdG1hcCBvYmplY3QKICB0bV9wb2x5Z29ucygibGlmZUV4cCIsICAgICAgIyBQbG90IQogICAgICAgICAgICAgIGFscGhhID0gMC45LAogICAgICAgICAgICAgIHBhbGV0dGUgPSAiU3BlY3RyYWwiKSAgICAKYGBgCgoKU29tZSBjb3VudHJpZXMgYXJlIG1pc3NpbmcgPT4gZGF0YSBwcm9ibGVtIGJlY2F1c2UgZ2FwbWluZGVyIGlzIGluY29tcGxldGUuCkxldCdzIGNvbXBsZXRlIGl0IHZpYTogaHR0cHM6Ly93d3cuZ2FwbWluZGVyLm9yZy9kYXRhLwpUaGlzIHlpZWxkcyBhIG11Y2ggYmlnZ2VyIGRhdGFzZXQsIGNhbGxlZCAiZ2FwMi5SRGF0YSIuIExldCdzIHNlZToKCmBgYHtyfQpsb2FkKCJnYXAyLlJEYXRhIikKaGVhZChnYXAyKQpgYGAKCgpBZ2Fpbiwgd2UgcmVzb3J0IHRvIGxlZnRfam9pbiB0byBtZXJnZSB0aGUgdHdvIGRhdGEgc291cmNlcy4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KZGF0YSA8LSBXb3JsZCAlPiUgCiAgbGVmdF9qb2luKGdhcDIsIGJ5ID0gImNvdW50cnkiKSAjJT4lCiNuYS5vbWl0KCkgCmBgYAoKCiMjIFdpdGggdGhlICoqbWFwc2YqKiBwYWNrYWdlCgoqKlJlZmVyZW5jZXMqKjogIApodHRwczovL3JpYXRlbGFiLmdpdGh1Yi5pby9tYXBzZi9hcnRpY2xlcy9tYXBzZi5odG1sICAKaHR0cHM6Ly9naXRodWIuY29tL3JpYXRlbGFiL21hcHNmICAKCkl0J3Mgc3VwZXIgZWFzeTogeW91IGp1c3QgbmVlZCBhIHNoYXBlIGZpbGUgd2l0aCB0aGUgYXR0cmlidXRlIHlvdSB3YW50IHRvIHBsb3QhCgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGLCBmaWcud2lkdGg9OH0Kc2Y6OnNmX3VzZV9zMihGQUxTRSkKZGF0YSAlPiUgCiAgZmlsdGVyKHllYXIgPT0gMjA1MCkgJT4lICAgICMgS2VlcCBvbmx5IG9uZSB5ZWFyIChwcm9ibGVtcyBvdGhlcndpc2UpCiAgc2Y6OnN0X3NmKCkgJT4lCiAgbWZfbWFwKHggPSAuLCB2YXIgPSAicG9wdWxhdGlvbiIsIHR5cGUgPSAiY2hvcm8iKQpgYGAKClRoZSBwYWNrYWdlIGRvZXMgbm90IHJlYWxseSB1c2UgcGlwZXMvbGF5ZXJzLgoKYGBge3IsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRiwgZmlnLndpZHRoPTl9Cm1hcCA8LSBkYXRhICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDE4KSAlPiUgICAgIyBLZWVwIG9ubHkgb25lIHllYXIgKHByb2JsZW1zIG90aGVyd2lzZSkKICBzZjo6c3Rfc2YoKQoKbXlfdGhlbWUgPC0gbGlzdCgKICBuYW1lID0gIm15dGhlbWUiLCAKICBiZyA9ICIjMTEzMzY2IiwgICAgICAgICMgTGlnaHQgYmx1ZQogIGZnID0gIiNGRkZGRkYiLCAgICAgICAgIyBEYXJrIGdyZXkKICBtYXIgPSBjKDAsIDAsIDAsIDApLCAKICB0YWIgPSBUUlVFLCAKICBwb3MgPSAiY2VudGVyIiwgCiAgaW5uZXIgPSBUUlVFLCAKICBsaW5lID0gMSwgCiAgY2V4ID0gLjksIAogIGZvbnQgPSAzCikKCm1mX3RoZW1lKG15X3RoZW1lKQptZl9tYXAoeCA9IG1hcCwgdHlwZSA9ICJiYXNlIikKbWZfbWFwKHggPSBtYXAsCiAgICAgICBhZGQgPSBULCAgICAgICAgICAgICAjIEFmdGVyIHlvdSBjcmVhdGVkIGEgbWFwIHdpdGggbWZfbWFwLCB5b3UgbmVlZCB0aGlzIQogICAgICAgdmFyID0gYygiaW5lcXVhbGl0eSIsICJ3ZWxsX2JlaW5nIiksICAgICAgIyBUd28gdmFyaWFibGVzISBCVVQgQkUgQ0FSRUZVTCEKICAgICAgIHR5cGUgPSAicHJvcF9jaG9ybyIsCiAgICAgICBpbmNoZXMgPSAwLjEsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNjYWxlIG9mIGNpcmNsZXMKICAgICAgIGx3ZCA9IDEsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTGluZSB3aWR0aAogICAgICAgYnJlYWtzID0gImVxdWFsIiwgCiAgICAgICBuYnJlYWtzID0gNiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE51bWJlciBvZiBjb2xvcnMgYnJlYWtzCiAgICAgICBsZWdfdGl0bGVfY2V4ID0gYygxLDEpLCAgICAgICAgICAgICAgICAgICAjIFNpemUgb2YgbGVnZW5kIHRpdGxlCiAgICAgICBsZWdfdmFsX2NleCA9IGMoMC41LDAuOCksICAgICAgICAgICAgICAgICAjIFNpemUgb2YgbGVnZW5kIHRleHQKICAgICAgIHBhbCA9ICJTcGVjdHJhbCIsICAgICAgICAgICAgICAgICAgICAgICAgICMgUGFsZXR0ZQogICAgICAgbGVnX3BvcyA9IGMoImJvdHRvbWxlZnQiLCAibGVmdCIpLCAgICAgICAgIyBMZWdlbmQgcG9zaXRpb24KICAgICAgIGxlZ190aXRsZT0gYygiSW5lcXVhbGl0eSIsIldlbGwgYmVpbmciKSwgICMgTGVnZW5kIHRpdGxlcwogICAgICAgbGVnX3ZhbF9ybmQgPSBjKDIsIDIpLCAgICAgICAgICAgICAgICAgICAgIyBOdW1iZXIgb2YgZGVjaW1hbHMgaW4gbGVnZW5kcwogICAgICAgbGVnX2ZyYW1lID0gYyhUUlVFLCBUUlVFKSkgICAgICAgICAgICAgICAgIyBGcmFtZXMgYXJvdW5kIGxlZ2VuZD8KbWZfbGF5b3V0KHRpdGxlID0gIkluZXF1YWxpdHkgJiB3ZWxsLWJlaW5nIGluIHRoZSB3b3JsZCIsIAogICAgICAgICAgY3JlZGl0cyA9IHBhc3RlMCgiU291cmNlczogTUlTQyIpLCAKICAgICAgICAgIGZyYW1lID0gVFJVRSkKYGBgCgoKIyMgV2l0aCB0aGUgKipsZWFmbGV0KiogcGFja2FnZQoKTmV4dCwgd2UgbW92ZSB0b3dhcmRzIGludGVyYWN0aXZlIG1hcHMgd2l0aCB0aGUgKmxlYWZsZXQqIHBhY2thZ2UuIFRoZSBhZHZhbnRhZ2Ugb2YgKmxlYWZsZXQqIGxpZXMgaW4gaXRzIG11bHRpcGxlIG9wdGlvbnMgdGhhdCBjcmVhdGUgZHluYW1pYyBwbG90cy4gVGhlIGV4YW1wbGUgYmVsb3cgaXMgc3Ryb25nbHkgaW5zcGlyZWQgZnJvbSB0aGUgcmVmZXJlbmNlIHBhZ2U6IGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC9jaG9yb3BsZXRocy5odG1sClRodXMsIGxvb2tpbmcgYXQgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIGNvZGVzIGhlbHBzIHVuZGVyc3RhbmQgaG93IHRoZXkgd29yay4gCgpXZSBhcmUgZ29pbmcgdHdvIHVzZSB0d28gZmVhdHVyZXM6ICAgCi0gYSBjdXN0b21pemVkIGNvbG9yIHNjYWxlOyAgICAKLSBsYWJlbHMgdGhhdCBhcHBlYXIgd2hlbiB0aGUgY3Vyc29yIGlzIG9uIHRoZSBtYXAuCgpGb3IgdGhlIHNjYWxlLCB3ZSBkZWZpbmUgaXQgbWFudWFsbHksIGJvdGggZm9yIHRoZSBjb2xvcnMgYW5kIGZvciB0aGUgc2VwYXJhdGlvbiBwb2ludHMgKGJpbnMpLiAgIApUaGUgbGFiZWxzIGFyZSBkZWZpbmVkIHVzaW5nIGh0bWwgZnVuY3Rpb25zLgoKT25lIHdvcmQgb24gbWlzc2luZyBkYXRhOiBpbiB0aGUgKldvcmxkKiBkYXRhLCAiQ3plY2ggUmVwLiIgc3RhbmRzIGZvciAiQ3plY2ggUmVwdWJsaWMiLCBzbyB3ZSByZWNvZGUgbWFudWFsbHkgaXQgdG8gZmlsbCBpbiBhIGhvbGUuIFNpbmNlIGl0J3MgYSBmYWN0b3IgaW4gKldvcmxkKiwgd2UgbmVlZCB0byB1c2UgdGhlICoqcmVjb2RlX2ZhY3RvcioqIGZ1bmN0aW9uLiBGb3Igc2ltcGxlIHRleHQsICoqcmVjb2RlKiogd291bGQgYmUgc3VmZmljaWVudC4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KaWYoIXJlcXVpcmUobGVhZmxldCkpe2luc3RhbGwucGFja2FnZXMoImxlYWZsZXQiKX0gICAgICAgICAgICAgICAgICAjIEluc3RhbGwgcGFja2FnZQpsaWJyYXJ5KGxlYWZsZXQpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTG9hZCBwYWNrYWdlCmRhdGFtYXAgPC0gV29ybGQgJT4lIAogIG11dGF0ZShjb3VudHJ5ID0gY291bnRyeSAlPiUgcmVjb2RlX2ZhY3RvcihgQ3plY2ggUmVwLmAgPSAiQ3plY2ggUmVwdWJsaWMiKSkgJT4lCiAgbGVmdF9qb2luKGdhcDIsIGJ5ID0gImNvdW50cnkiKSAlPiUKICBmaWx0ZXIoeWVhciA9PSAyMDE4KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBLZWVwIG9ubHkgb25lIHllYXIgKHJlY2VudCkKCnBhbGV0IDwtIGNvbG9yQmluKCJZbEduQnUiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBZZWxsb3ctR3JlZW4tQmx1ZSBwYWxldHRlCiAgICAgICAgICAgICAgICAgIGRvbWFpbiA9IGRhdGFtYXAgJT4lIHB1bGwobGlmZUV4cCksICMgTGlmZUV4cCAgICAgIyBEb21haW4gb2YgbGFiZWxzOiBsaWZlRXhwCiAgICAgICAgICAgICAgICAgIGJpbnMgPSBjKDUwLCA1NSwgNjAsIDY1LCA3MCwgNzUsIDgwLCA4NSkpICAgICAgICAgIyBBZ2UgY2F0ZWdvcmllcwoKbGFiZWxzIDwtIHNwcmludGYoICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJlbG93IHdlIGRlZmluZSB0aGUgbGFiZWxzCiAgIjxzdHJvbmc+JXM8L3N0cm9uZz48YnIvPiVnIFllYXJzIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBZGRpbmcgdGV4dCB0byBsYWJlbAogIGRhdGFtYXAkY291bnRyeSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgV2Ugc2hvdyB0aGUgY291bnRyeSBuYW1lLi4uCiAgZGF0YW1hcCRsaWZlRXhwICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIC4uLiBhbmQgdGhlIGxpZmUgZXhwZWN0YW5jeQopICU+JSBsYXBwbHkoaHRtbHRvb2xzOjpIVE1MKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRW1iZWRkZWQgYWxsIGludG8gaHRtbCBsYW5ndWFnZQpgYGAKCldlIGNhbiB0aGVuIG1vdmUgZm9yd2FyZCB3aXRoIHRoZSBwbG90LgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpkYXRhbWFwICU+JSAKICBkYXRhLmZyYW1lKCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFR1cm4gaW50byBkYXRhZnJhbWUgKHRlY2huaWNhbCkKICBzZjo6c3Rfc2YoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEZvcm1hdCBpbiBzZgogICNzdF90cmFuc2Zvcm0oIitpbml0PWVwc2c6NDMyNiIpICU+JSAgICAgICAgICAgICAjIENvbnZlcnQgaW4gcGFydGljdWxhciBjb29yZGluYXRlIHJlZmVyZW5jZSAKICBsZWFmbGV0KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIENhbGwgbGVhZmxldAogIGFkZFBvbHlnb25zKGZpbGxDb2xvciA9IH5wYWxldChsaWZlRXhwKSwgICAgICAgICMgQ3JlYXRlIHRoZSBtYXAgKGNvbG9yZWQgcG9seWdvbnMpCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwgICAgICAgICAgICAgICAgICAgICAgICAgIyBXaWR0aCBvZiBzZXBhcmF0aW9uIGxpbmUKICAgICAgICAgICAgICBvcGFjaXR5ID0gMSwgICAgICAgICAgICAgICAgICAgICAgICAjIE9wYWNpdHkgb2Ygc2VwYXJhdGlvbiBsaW5lCiAgICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLCAgICAgICAgICAgICAgICAgICAgIyBDb2xvciBvZiBzZXBhcmF0aW9uIGxpbmUKICAgICAgICAgICAgICBkYXNoQXJyYXkgPSAiMyIsICAgICAgICAgICAgICAgICAgICAjIERhc2ggc2l6ZSBvZiBzZXBhcmF0aW9uIGxpbmUKICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuNywgICAgICAgICAgICAgICAgICAjIE9wYWNpdHkgb2YgcG9seWdvbiBjb2xvcnMKICAgICAgICAgICAgICBoaWdobGlnaHQgPSBoaWdobGlnaHRPcHRpb25zKCAgICAgICAjIDUgbGluZXMgYmVsb3cgY29udHJvbCB0aGUgY3Vyc29yIGltcGFjdAogICAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwgICAgICAgICAgICAgICAgICAgICAgICMgV2lkdGggb2YgbGluZQogICAgICAgICAgICAgICAgY29sb3IgPSAiIzMzMzMzMyIsICAgICAgICAgICAgICAgICMgQ29sb3Igb2YgbGluZQogICAgICAgICAgICAgICAgZGFzaEFycmF5ID0gIiIsICAgICAgICAgICAgICAgICAgICMgTm8gZGFzaAogICAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjksICAgICAgICAgICAgICAgICMgT3BhY2l0eQogICAgICAgICAgICAgICAgYnJpbmdUb0Zyb250ID0gVFJVRSksCiAgICAgICAgICAgICAgbGFiZWwgPSBsYWJlbHMsICAgICAgICAgICAgICAgICAgICAgIyBMQUJFTCEgRGVmaW5lZCBhYm92ZSEKICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMoICAgICAgICAjIExhYmVsIG9wdGlvbnMgYmVsb3cuLi4KICAgICAgICAgICAgICAgIHN0eWxlID0gbGlzdCgiZm9udC13ZWlnaHQiID0gIm5vcm1hbCIsIHBhZGRpbmcgPSAiM3B4IDhweCIpLAogICAgICAgICAgICAgICAgdGV4dHNpemUgPSAiMTVweCIsCiAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAiYXV0byIpCiAgKSAlPiUKICBhZGRMZWdlbmQocGFsID0gcGFsZXQsICAgICAgICAgICAgICAgICAgICAjIExlZ2VuZDogY29tZXMgZnJvbSBwYWxldCBjb2xvcnMgZGVmaW5lZCBhYm92ZQogICAgICAgICAgICB2YWx1ZXMgPSB+bGlmZUV4cCwgICAgICAgICAgICAgICMgVmFsdWVzIGNvbWUgZnJvbSBsaWZlRXhwIHZhcmlhYmxlCiAgICAgICAgICAgIG9wYWNpdHkgPSAwLjksICAgICAgICAgICAgICAgICAgIyBPcGFjaXR5IG9mIGxlZ2VuZAogICAgICAgICAgICB0aXRsZSA9ICJNYXAgTGVnZW5kIiwgICAgICAgICAgICMgVGl0bGUgb2YgbGVnZW5kCiAgICAgICAgICAgIHBvc2l0aW9uID0gImJvdHRvbXJpZ2h0IikgICAgICAgIyBQb3NpdGlvbiBvZiBsZWdlbmQKYGBgCgoKQmV0dGVyLCBidXQgc29tZSBBZnJpY2FuIGNvdW50cmllcyBhcmUgc3RpbGwgbWlzc2luZy4uLiBEYXRhIHByb2JsZW0hCgpJdCBpcyBwb3NzaWJsZSB0byBpbnRlZ3JhdGUgKmxlYWZsZXQqIGludG8gKnNoaW55KjogaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0L3NoaW55Lmh0bWwKWW91IG5lZWQgKipyZW5kZXJMZWFmbGV0KiooKSBpbiB0aGUgc2VydmVyIGFuZCAqKmxlYWZsZXRPdXRwdXQqKigpIGluIHRoZSBVSS4KCgojIFBvaW50cyAod2l0aCBnZ3Bsb3QhKQoKUG9pbnRzIGFyZSBtb3JlIGVhc3kgdG8gaGFuZGxlIGJlY2F1c2UgdGhleSByZXF1aXJlIGxlc3MgaW5mb3JtYXRpb24gKHR3byBjb29yZGluYXRlcyBhdCBsZWFzdCBwbHVzIG90aGVyIGF0dHJpYnV0ZXMgbGlrZSBzaXplIG9yIGNvbG9yKS4gVGhpcyBpcyBleGFjdGx5IHRoZSBraW5kIG9mIGRhdGEgdHlwZSB0aGF0IGZpdHMgaW4gdGhlICoqZ2dwbG90Kiogc3ludGF4ISBCZWxvdywgd2Ugc2hvdyBob3cgdG8gY29tYmluZSBnZ3Bsb3Qgd2l0aCBnZW9ncmFwaGljYWwgZGF0YS4KCiMjIENvdW50cnkgbGV2ZWwgZGF0YQoKV2hpbGUgYXJlYXMgbXVzdCBiZSBkZWZpbmVkIGF0IHRoZSBjb3VudHJ5IGxldmVsIChjb21wbGV4ISksIHBvaW50cyBhcmUgc2ltcGx5IGNoYXJhY3Rlcml6ZWQgYnkgdHdvIG51bWJlcnM6ICoqbGF0aXR1ZGUqKiAmICoqbG9uZ2l0dWRlKiouIFNvIGEgbGlzdCBvZiB0aGVzZSB0d28gZmVhdHVyZXMgc3VmZmljZXMgdG8gcGxvdCBwb2ludHMuIEZvciBjb3VudHJpZXMsIGl0IGlzIHBvc3NpYmxlIHRvIGZpbmQgZGF0YSBvbmxpbmUsIGV4OiBodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9wdWJsaWMtZGF0YS9kb2NzL2Nhbm9uaWNhbC9jb3VudHJpZXNfY3N2CgpUaGUgbGF0aXR1ZGVzIGFuZCBsb25naXR1ZGVzIGNvcnJlc3BvbmQgdG8gdGhlIGNlbnRlciBvZiB0aGUgY291bnRyaWVzLgoKQmVsb3csIHdlIGNyZWF0ZSBhIG5ldyBkYXRhc2V0IHdpdGggbG9uZ2l0dWRlcyAmIGxhdGl0dXRlcyBjb21iaW5lZCB3aXRoICpnYXBtaW5kZXIqICh1cGRhdGVkIHZlcnNpb24pLgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmlmKCFyZXF1aXJlKHJlYWR4bCkpe2luc3RhbGwucGFja2FnZXMoInJlYWR4bCIpfSAgICAgICMgSW5zdGFsbCBwYWNrYWdlIHRvIHJlYWQgZXhjZWwgZmlsZXMKbGlicmFyeShyZWFkeGwpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBY3RpdmF0ZSBwYWNrYWdlCm9wdGlvbnMoc2NpcGVuPTk5OSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHNjaWVudGlmaWMgbm90YXRpb24KY291bnRyeV9MTCA8LSByZWFkX2V4Y2VsKCJjb3VudHJ5X0xMLnhsc3giKSAgICAgICAgICAgIyBSZWFkIGxvbmdpdHVkZS9sYXRpdHVkZSBkYXRhCmRhdGEyIDwtIGxlZnRfam9pbihnYXAyLCBjb3VudHJ5X0xMKSAlPiUgICAgICAgICAgICAgICMgTWVyZ2UgdHdvIGRhdGFzZXRzCiAgI25hLm9taXQoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBtaXNzaW5nIHBvaW50cwogIG11dGF0ZShsYXRpdHVkZSA9IGFzLm51bWVyaWMobGF0aXR1ZGUpLCAgICAgICAgICAgIyAnTnVtZXJpemUnIGxhdGl0dWRlCiAgICAgICAgIGxvbmdpdHVkZSA9IGFzLm51bWVyaWMobG9uZ2l0dWRlKSwgICAgICAgICAjICdOdW1lcml6ZScgbG9uZ2l0dWRlCiAgICAgICAgIHBvcHVsYXRpb24gPSByb3VuZChwb3B1bGF0aW9uIC8gMTBeNiwxKSkgICAjIFNjYWxpbmcgcG9wdWxhdGlvbiBpbnRvIG1pbGxpb25zCmRhdGEyICU+JSBoZWFkKCkKYGBgCgpCZSBjYXJlZnVsLCB0aGUgY29kZSBiZWxvdyBpcyBjb21wdXRhdGlvbmFsbHkgZGVtYW5kaW5nIChiZSBwYXRpZW50KS4gV2UgdXNlIHRoZSAqdmlyaWRpcyogY29sb3IgcGFsZXR0ZS4gCkluICpnZ3Bsb3QqLCBhIHNtYWxsIGxpc3Qgb2YgbWFwcyBpcyBhdmFpbGFibGU6IHdvcmxkLCBGcmFuY2UsIEl0YWx5LCBOZXcgWmVhbGFuZCwgVVMgKGFuZCBzdGF0ZXMpLiAKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLndpZHRoPTh9CmlmKCFyZXF1aXJlKHNjYWxlcykpe2luc3RhbGwucGFja2FnZXMoInNjYWxlcyIpfQpsaWJyYXJ5KHNjYWxlcykgCndvcmxkIDwtIG1hcF9kYXRhKCJ3b3JsZCIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEZyb20gZ2dwbG90CmcgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgc291cmNlCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSB3b3JsZCwgYWVzKHggPSBsb25nLCAgICAgICAgICAgICAgICAjIERhcmsgYmFja2dyb3VuZCBvZiB0aGUgd29ybGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxhdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBncm91cCkpICsgICAKICBnZW9tX3BvaW50KGRhdGEgPSBkYXRhMiAlPiUgZmlsdGVyKHllYXIgPT0gMjAxOCksICAgICAgICMgTW9zdCByZWNlbnQgZGF0YSBwb2ludHMKICAgICAgICAgICAgIGFlcyh4ID0gbG9uZ2l0dWRlLCAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ2xhc3NpY2FsIGdncGxvdCBzeW50YXghCiAgICAgICAgICAgICAgICAgeSA9IGxhdGl0dWRlLCAKICAgICAgICAgICAgICAgICBzaXplID0gZ2RwUGVyY2FwLCAKICAgICAgICAgICAgICAgICBjb2xvciA9IGxpZmVFeHAsIAogICAgICAgICAgICAgICAgIGxhYmVsID0gY291bnRyeSkpICsgICAgICAgICAgICAgICAgICAgICAgIyBMYWJlbCAoZm9yIHBsb3RseSkKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Mob3B0aW9uID0gIm1hZ21hIikgKyB5bGltKC03MCw4MCkgICAgIyBDb2xvciBzY2FsZQpnZ3Bsb3RseShnKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBQbG90bHkgaW50ZWdyYXRpb24KYGBgCgojIyBDaXR5IGxldmVsIGRhdGEKCllvdSBjYW4gZmluZCBsb25naXR1ZGVzICYgbGF0aXR1ZGVzIGJ5IHNlYXJjaGluZyBvbiBHb29nbGUuIEhlcmUncyBvbmUgZXhhbXBsZSBmb3IgRnJhbmNlOgpodHRwczovL3NpbXBsZW1hcHMuY29tL2RhdGEvZnItY2l0aWVzCldvcmxkd2lkZSBkYXRhIGNhbiBiZSBhY2Nlc3NlZCBoZXJlOiBodHRwczovL3NpbXBsZW1hcHMuY29tL2RhdGEvd29ybGQtY2l0aWVzCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmNpdGllcyA8LSByZWFkX2V4Y2VsKCJ3b3JsZGNpdGllcy54bHN4IikKYGBgCgpUaGF0J3MgdG9vIGJpZy4gTGV0J3MgZm9jdXMgb24gR2VybWFueSwgSXRhbHksIEF1c3RyaWEsIFNsb3ZlbmlhIGFuZCBTd2l0emVybGFuZCAoYXJiaXRyYXJpbHkpIC0gb3Igb3RoZXIgY2hvaWNlcy4KCmBgYHtyfQpjaXRpZXNfc2hvcnQgPC0gY2l0aWVzICU+JSAKICBmaWx0ZXIoI2NvdW50cnkgJWluJSBjKCJHZXJtYW55IiwgIkl0YWx5IiwgIlN3aXR6ZXJsYW5kIiwgIkF1c3RyaWEiLCAiU2xvdmVuaWEiKSwKICAgIGNvdW50cnkgJWluJSBjKCJDaGluYSIsICJJbmRpYSIsICJOZXBhbCIsICJCYW5nbGFkZXNoIiwgIlRoYWlsYW5kIiksCiAgICBwb3B1bGF0aW9uID4gMzUwMDAwKSAlPiUKICBuYS5vbWl0KCkKaGVhZChjaXRpZXNfc2hvcnQpCmBgYAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBmaWcud2lkdGg9OH0Kd29ybGQgPC0gbWFwX2RhdGEoIndvcmxkIikKZyA8LSBnZ3Bsb3QoZGF0YSA9IHdvcmxkKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGF0YSBzb3VyY2UKICBnZW9tX3BvbHlnb24oZGF0YSA9IHdvcmxkLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGFyayBiYWNrZ3JvdW5kIG9mIHRoZSB3b3JsZAogICAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLAogICAgICAgICAgICAgICBzaXplID0gMC4zLCBjb2xvciA9ICJncmV5IikgKyAgICAgICAgICAgICAgICAgICAgIyBPdXRlciBsaW5lcyBvZiBwb2x5Z29ucwogIGdlb21fcG9pbnQoZGF0YSA9IGNpdGllc19zaG9ydCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBUaGUgcG9pbnRzCiAgICAgICAgICAgICBhZXMoeCA9IGxuZywgCiAgICAgICAgICAgICAgICAgeSA9IGxhdCwgCiAgICAgICAgICAgICAgICAgc2l6ZSA9IHBvcHVsYXRpb24sIAogICAgICAgICAgICAgICAgIGNvbG9yID0gY291bnRyeSwgCiAgICAgICAgICAgICAgICAgbGFiZWwgPSBjaXR5KSkgKwogICNzY2FsZV9jb2xvdXJfZ3JhZGllbnQobG93ID0gIiNGRkZCMDAiLCBoaWdoID0gIiNGRjFCMDAiKSArICAjIFRoaXMgbGluZSBpcyBmb3IgY29sb3IgPSBwb3B1bGF0aW9uCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCIjRkZFQzAwIiwgIiM2NkUyMzQiLCAiIzFCQ0JGNyIsICIjQ0M0NEY4IiwgIiNGQzIzNjEiKSkgKwogIGNvb3JkX3NmKHhsaW0gPSBjKDcwLCAxMjUpLCB5bGltID0gYyg2LCA1MiksIGV4cGFuZCA9IEZBTFNFKSArIHRoZW1lX2J3KCkgIyBab29tIG9uIEV1cm9wZQojIGNvb3JkX3NmKHhsaW0gPSBjKDUsIDE4KSwgeWxpbSA9IGMoMzYsIDU3KSwgZXhwYW5kID0gRkFMU0UpICsgdGhlbWVfYncoKSAjIFpvb20gb24gRXVyb3BlCmdncGxvdGx5KGcpCmBgYAoKCiMgT3RoZXIgbWFwcwoKRm9yIHBvaW50cywgaXQncyBlYXN5OiBqdXN0IHVzZSB0aGUgc2FtZSBjb29yZGluYXRlIHN5c3RlbSB2aWEgbG9uZ2l0dWRlICYgbGF0aXR1ZGUuIEl0J3MgbXVjaCBtb3JlIHRyaWNreSBmb3IgYXJlYXMuCgpUaGUga2V5IGlzIHRvIGZpbmQgKipzaGFwZSBmaWxlcyoqIChvbmUgYWx0ZXJuYXRpdmUgZm9ybWF0IGlzIEdlb0pTT04sIGJ1dCBpdCBpcyBub3QgdHJlYXRlZCBoZXJlKS4gIApFeGFtcGxlOiBodHRwOi8vZGF0YXBhZ2VzLmNvbS9naXMtbWFwLXB1Ymxpc2hpbmctcHJvZ3JhbS9naXMtb3Blbi1maWxlcy9nbG9iYWwtZnJhbWV3b3JrLyAKQmUgdmVyeSBjYXJlZnVsOiBzb21lIG1hcHMgYXJlICpFWFRSRU1FTFkqIHByZWNpc2UgKHVwIHRvIDFtIHByZWNpc2lvbikuIFRodXMsIHRoZSBmaWxlcyBhcmUgaGVhdnkgYW5kIHRoZSBwbG90dGluZyB0YWtlcyBhICpMT1QqIG9mIHRpbWUuIEJlbG93LCB3ZSB1c2UgMTAwbSBwcmVjaXNpb24sIHdoaWNoIGlzIGxhcmdlbHkgZW5vdWdoLgoKVGhlIGZ1bGwgZGVzY3JpcHRpb24gb2YgYWxsIHNoYXBlIGZpbGUgaXRlbXMgY2FuIGJlIGFjY2Vzc2VkIG9uIHdpa2lwZWRpYToKCiogLnNocCDigJQgc2hhcGUgZm9ybWF0OyB0aGUgZmVhdHVyZSBnZW9tZXRyeSBpdHNlbGYgICAKKiAuc2h4IOKAlCBzaGFwZSBpbmRleCBmb3JtYXQ7IGEgcG9zaXRpb25hbCBpbmRleCBvZiB0aGUgZmVhdHVyZSBnZW9tZXRyeSB0byBhbGxvdyBzZWVraW5nIGZvcndhcmRzIGFuZCBiYWNrd2FyZHMgcXVpY2tseSAgIAoqIC5kYmYg4oCUIGF0dHJpYnV0ZSBmb3JtYXQ7IGNvbHVtbmFyIGF0dHJpYnV0ZXMgZm9yIGVhY2ggc2hhcGUsIGluIGRCYXNlIElWIGZvcm1hdAoKRm9yIGluc3RhbmNlLCBpdCdzIHBvc3NpYmxlIHRvIGZldGNoIGRhdGEgZm9yIEZyZW5jaCByZWdpb25zIGF0OgpodHRwczovL3d3dy5kYXRhLmdvdXYuZnIvZnIvZGF0YXNldHMvY29udG91cnMtZ2VvZ3JhcGhpcXVlcy1kZXMtbm91dmVsbGVzLXJlZ2lvbnMtbWV0cm9wb2xlLwoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVnaW9ucyA8LSBzZjo6c3RfcmVhZCgicmVnaW9ucy0yMDE0MDMwNi0xMDBtLnNocCIpCnN0cihyZWdpb25zKSAgICAjIFN0cnVjdHVyZSBvZiB0aGUgdmFyaWFibGUgcmVnaW9uCmBgYAoKVGhhdCdzIGNvbXBsaWNhdGVkLiBUaGUgb25seSBpbXBvcnRhbnQgY29sdW1uIGZvciBub3cgaXMgdGhlICpuYW1lKiBvZiB0aGUgcmVnaW9uIHdoaWNoIGlzIGVhc3kgdG8gcmVjb2duaXplLiBCZWxvdywgd2UgcGxvdCBhIGNob3JvcGxldGggd2hlcmUgdGhlIGNvbG9yIGlzIGxpbmsgdG8gdGhlIGFyZWEgb2YgdGhlIHJlZ2lvbiAoYmlnZ2VyID0gZGFya2VyKS4KCmBgYHtyfQpyZWdpb25zICU+JSAKICB0bV9zaGFwZSgpICsKICB0bV9wb2x5Z29ucygic3VyZl9rbTIiKQoKcmVnaW9ucyAlPiUgCiAgc2Y6OnN0X3NmKCkgJT4lCiAgbWZfbWFwKHggPSAuLCB2YXIgPSAic3VyZl9rbTIiLCB0eXBlID0gImNob3JvIikKYGBgCgpUaGF0J3MgcHJldHkgdWdseS4gTGV0J3MgcmVtb3ZlIHRoZSByZWdpb25zIG91dHNpZGUgdGhlIG1haW5sYW5kLiAKCmBgYHtyfQpyZWdpb25zICU+JQogIGZpbHRlcighIChub20gJWluJSBjKCJHdXlhbmUiLCAiTWF5b3R0ZSIsICJNYXJ0aW5pcXVlIiwgIkd1YWRlbG91cGUiLCAiTGEgUsOpdW5pb24iKSkpICU+JSAKICB0bV9zaGFwZSgpICsKICB0bV9wb2x5Z29ucygic3VyZl9rbTIiLCBib3JkZXIuY29sID0gIndoaXRlIikgIyBXaXRoIHRoZSBib3JkZXIgb2YgcG9seWdvbnMKCnJlZ2lvbnMgJT4lIAogIGZpbHRlcighIChub20gJWluJSBjKCJHdXlhbmUiLCAiTWF5b3R0ZSIsICJNYXJ0aW5pcXVlIiwgIkd1YWRlbG91cGUiLCAiTGEgUsOpdW5pb24iKSkpICU+JSAKICBzZjo6c3Rfc2YoKSAlPiUKICBtZl9tYXAoeCA9IC4sIHZhciA9ICJzdXJmX2ttMiIsIHR5cGUgPSAiY2hvcm8iLCBwYWwgPSAiU3BlY3RyYWwiKQpgYGAKCkJlbG93LCB3ZSBjaGFuZ2UgdGhlIGNvbG9yIHBhbGV0dGUgYW5kIHBsb3QgdGhlIG51bWJlciBvZiBGcmVuY2ggImTDqXBhcnRlbWVudHMiIGluc2lkZSBlYWNoIHJlZ2lvbi4KCmBgYHtyfQpyZWdpb25zICU+JQogIGZpbHRlcighIChub20gJWluJSBjKCJHdXlhbmUiLCAiTWF5b3R0ZSIsICJNYXJ0aW5pcXVlIiwgIkd1YWRlbG91cGUiLCAiTGEgUsOpdW5pb24iKSkpICU+JSAKICB0bV9zaGFwZSgpICsKICB0bV9maWxsKCJuYl9kZXAiLCBwYWxldHRlID0gIlNwZWN0cmFsIikgICMgV2l0aG91dCB0aGUgYm9yZGVyCmBgYAoKVGhpcyBpcyBzdGF0aWM6ICoqbGVhZmxldCoqIGludGVncmF0aW9uIHdvdWxkIGJlIGJldHRlci4KCiMgSGlnaGNoYXJ0cwoKVGhlIG1hdGVyaWFsIGJlbG93IGlzIGluc3BpcmVkIGZyb20gdGhlIHZpZ25ldHRlOiBodHRwczovL2prdW5zdC5jb20vaGlnaGNoYXJ0ZXIvCgpUaGUgKmhpZ2hjaGFydGVyKiBwYWNrYWdlIGNyZWF0ZXMgZ29vZCBsb29raW5nIHBsb3RzLiBMZXQncyBpbnN0YWxsIGl0IChhbmQgYWN0aXZhdGUgaXQpLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IFRSVUV9CmlmKCFyZXF1aXJlKGhpZ2hjaGFydGVyKSl7aW5zdGFsbC5wYWNrYWdlcygiaGlnaGNoYXJ0ZXIiKX0KbGlicmFyeShoaWdoY2hhcnRlcikKYGBgCgpUaGUgcGFja2FnZSBoYXMgYSBsYXJnZSBsaWJyYXJ5IG9mIG1hcHMuIFRoZSBsaXN0IGlzIGdpdmVuIGhlcmU6Cmh0dHBzOi8vY29kZS5oaWdoY2hhcnRzLmNvbS9tYXBkYXRhLwoKCklmIHlvdSBjbGljayBvbiBhIGxpbmssIHlvdSBnZXQgdGhlIGFkZHJlc3Mgb2YgdGhlIG1hcDoKCiFbXShoaWdoX2xpc3QucG5nKQoKVGhlIGxhc3QgcGFydCBpcyB3aGF0IHlvdSBzcGVjaWZ5IHRvIGdldCBhY2Nlc3MgdG8gYSBtYXA6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9Cm1hcGRhdGEgPC0gZ2V0X2RhdGFfZnJvbV9tYXAoZG93bmxvYWRfbWFwX2RhdGEoImNvdW50cmllcy9mci9jdXN0b20vZnItYWxsLW1haW5sYW5kIikpCmhlYWQobWFwZGF0YSkKYGBgCgpUaGUgaW5mb3JtYXRpb24gdGhhdCB3aWxsIGJlIHBsb3R0ZWQgY29tZXMgZnJvbSB0aGlzIGZpbGUuIFdlIG5lZWQgdG8gYWRkIHRoZSB2YWx1ZSB0aGF0IHdlIHdhbnQgdG8gdXNlIGZvciB0aGUgcGxvdC4gVGhlcmUgYXJlIDIxIHJlZ2lvbnMgaW4gdGhlIHZhcmlhYmxlICptYXBfZGF0YSouIFRoaXMgYnlwYXNzZXMgdGhlIG1lcmdpbmcgb2YgZXh0ZXJuYWwgZGF0YSB2aWEgdGhlICoqbGVmdF9qb2luKiooKSBmdW5jdGlvbi4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbWFwZGF0YSA8LSBtYXBkYXRhICU+JSBtdXRhdGUodmFsdWUgPSAxOm5yb3cobWFwZGF0YSkpICMgQ29tcGxldGVseSByYW5kb20gdmFsdWVzCmhlYWQobWFwZGF0YSwgMjApCmBgYAoKQmVsb3csIHdlIHBsb3QgdGhlIHJlZ2lvbnMgb2YgRnJhbmNlIGFuZCB0aGUgY29sb3IgY29kZSBpcyBkcml2ZW4gYnkgdGhlIGNvbHVtbiAidmFsdWUiIGRlZmluZWQgYWJvdmUuIAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpoY21hcCgiY291bnRyaWVzL2ZyL2N1c3RvbS9mci1hbGwtbWFpbmxhbmQiLCAgICAgICAgICAgICAgICAgICAgICAjIFNvdXJjZSBmb3IgdGhlIG1hcAogICAgICBkYXRhID0gbWFwZGF0YSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNvdXJjZSBmb3IgdGhlIGNvbG9yIGNvZGUKICAgICAgdmFsdWUgPSAidmFsdWUiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDb2x1bW4gZm9yIGNvbG9yIGNvZGUKICAgICAgam9pbkJ5ID0gYygibmFtZSIpLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDb21tb24gbmFtZSAmIGxhYmVsCiAgICAgIG5hbWUgPSAiUmFuZG9tIFBsb3QiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTmFtZSBvZiBwbG90CiAgICAgIGRhdGFMYWJlbHMgPSBsaXN0KGVuYWJsZWQgPSBUUlVFLCBmb3JtYXQgPSAie3BvaW50Lm5hbWV9IiksICMgQWxsb3cgbGFiZWxzCiAgICAgIGJvcmRlckNvbG9yID0gIiNGQUZBRkEiLCBib3JkZXJXaWR0aCA9IDAuNSwgICAgICAgICAgICAgICAgICMgQm9yZGVyIGluZm8KICAgICAgdG9vbHRpcCA9IGxpc3QodmFsdWVEZWNpbWFscyA9IDEsICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMYWJlbCBpbmZvCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlUHJlZml4ID0gIlZhbHVlID0gIiwgICAgICAgICAgICAgICAgICAgICMgQmVmb3JlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlU3VmZml4ID0gIiBVbml0cyIpKSAlPiUgICAgICAgICAgICAgICAgICMgQWZ0ZXIgbGFiZWwKICBoY19jb2xvckF4aXMoCiAgICBzdG9wcyA9IGNvbG9yX3N0b3BzKGNvbG9ycyA9IHZpcmlkaXNMaXRlOjppbmZlcm5vKDEwLCBiZWdpbiA9IDAuMSkpLAogICAgdHlwZSA9ICJsb2dhcml0aG1pYyIKICApIApgYGAKCgojIEZpbmUgZ3JhaW4gbG9jYWwgbWFwcwoKTm93IGxldCdzIHR1cm4gdG8gc21hbGxlciBzY2FsZSBtYXBzLiBUaGUgZGF0YSBjb21lcyBmcm9tIEthZ2dsZSwgc2NyYXBwZWQgZnJvbSB0cmlwYWR2aXNvci4gVGhlIHVzZWZ1bCBjb2x1bW5zOiAqTG9uZ2l0dWRlKiAmICpMYXRpdHVkZSosIHRoZSAqTXVzZXVtTmFtZSosIHRoZSAqUmF0aW5nKiBhbmQgdGhlICpMZW5ndGhPZlZpc2l0Ki4gCgpgYGB7cn0KdHJpcGFkdmlzb3IgPC0gcmVhZF9leGNlbCgidHJpcGFkdmlzb3JfbXVzZXVtX1VTLnhsc3giKSAlPiUgICAgIyBJbXBvcnRpbmcgdGhlIGRhdGEKICBtdXRhdGUoTG9uZ2l0dWRlID0gYXMubnVtZXJpYyhMb25naXR1ZGUpLCAgICAgICAgICAgICAgICAgICAgIyBHZXR0aW5nIG51bWJlcnMgYmFjawogICAgICAgICBMYXRpdHVkZSA9IGFzLm51bWVyaWMoTGF0aXR1ZGUpLAogICAgICAgICBSYXRpbmcgPSBhcy5udW1lcmljKFJhdGluZyksCiAgICAgICAgIExlbmd0aE9mVmlzaXQgPSBhcy5mYWN0b3IoTGVuZ3RoT2ZWaXNpdCkpICU+JQogIGZpbHRlcihMYXRpdHVkZSA+IDQwLCBMYXRpdHVkZSA8IDQxLCBMb25naXR1ZGUgPiAoLTc0LjUpLCBMb25naXR1ZGUgPCAoLTczLjUpKSAjIE5ZIG11c2V1bXMgb25seQp0cmlwYWR2aXNvcgpgYGAKCgpXaXRoIGxlYWZsZXQsIGl0J3MgZWFzeSB0byBvYnRhaW4gZW1wdHkgbWFwcyBhcyBsb25nIGFzIHlvdSBrbm93IHRoZSBsb25naXR1ZGUgJiBsYXRpdHVkZSBvZiBhIHBhcnRpY3VsYXIgbG9jYXRpb24uIEJlbG93OiAqKkx5b24qKi4KCmBgYHtyLCBmaWcud2lkdGg9OH0KbGVhZmxldCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBbiBlbXB0eSBtYXA6IGEgZ29vZCBzdGFydCEKICBzZXRWaWV3KGxuZyA9IDQuODUsIGxhdCA9IDQ1Ljc1LCB6b29tID0gMTIpICU+JSAKICBhZGRUaWxlcygpCmBgYAoKYGBge3J9CnBhbCA8LSBjb2xvckZhY3RvcihwYWxldHRlID0gIlNwZWN0cmFsIiwgZG9tYWluID0gdHJpcGFkdmlzb3IkTGVuZ3RoT2ZWaXNpdCkKdHJpcGFkdmlzb3IgJT4lCiAgbGVhZmxldCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQSBibGFuayBzaGVldC4uLgogIHNldFZpZXcobG5nID0gLTczLjk3NzIsIGxhdCA9IDQwLjc4MDgsIHpvb20gPSAxMSkgJT4lIAogIGFkZFRpbGVzKCkgJT4lCiAgYWRkQ2lyY2xlcyhsbmcgPSB+TG9uZ2l0dWRlLCBsYXQgPSB+TGF0aXR1ZGUsIAogICAgICAgICAgICAgcmFkaXVzID0gflJhdGluZ140LCBsYWJlbCA9ICB+TXVzZXVtTmFtZSwKICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWwoTGVuZ3RoT2ZWaXNpdCksIGZpbGxPcGFjaXR5ID0gMC45LCAgIyBDaXJjbGUgY29sb3JzCiAgICAgICAgICAgICBzdHJva2UgPSBUUlVFLCBjb2xvciA9ICJibGFjayIsIHdlaWdodCA9IDIpICU+JSAgICAgICMgQ2lyY2xlIHN0cm9rZQogIGFkZExlZ2VuZChwYWwgPSBwYWwsICAgICAgICAgICAgICAgICAgICAgICMgTGVnZW5kOiBjb21lcyBmcm9tIHBhbGV0IGNvbG9ycyBkZWZpbmVkIGFib3ZlCiAgICAgICAgICAgIHZhbHVlcyA9IH5MZW5ndGhPZlZpc2l0LCAgICAgICAgIyBWYWx1ZXMgY29tZSBmcm9tIGxpZmVFeHAgdmFyaWFibGUKICAgICAgICAgICAgb3BhY2l0eSA9IDAuNywgICAgICAgICAgICAgICAgICAjIE9wYWNpdHkgb2YgbGVnZW5kCiAgICAgICAgICAgIHRpdGxlID0gIk1hcCBMZWdlbmQiLCAgICAgICAgICAgIyBUaXRsZSBvZiBsZWdlbmQKICAgICAgICAgICAgcG9zaXRpb24gPSAiYm90dG9tcmlnaHQiKSAgICAgICAjIFBvc2l0aW9uIG9mIGxlZ2VuZApgYGAKCkJlbG93LCB5b3UgbmVlZCB0byBzcGVjaWZ5ICoqbG9uKiogYW5kICoqbGF0KiogaW4gdGhlIHNvdXJjZSBkYXRhZnJhbWUuIE9yIHlvdSBjYW4gdXNlIHRoZSAqaGNhZXMqKCkgZnVuY3Rpb24uCgpgYGB7cn0KbWFwZGF0YTIgPC0gZ2V0X2RhdGFfZnJvbV9tYXAoZG93bmxvYWRfbWFwX2RhdGEoImNvdW50cmllcy91cy9jdXN0b20vdXMtbnktY29uZ3Jlc3MtMTEzIikpCmhjbWFwKCJjb3VudHJpZXMvdXMvY3VzdG9tL3VzLW55LWNvbmdyZXNzLTExMyIpICU+JQogIGhjX21hcE5hdmlnYXRpb24oZW5hYmxlZCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICBidXR0b25PcHRpb25zID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICAgYWxpZ24gPSAicmlnaHQiLAogICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNhbEFsaWduID0gImJvdHRvbSIpKSAlPiUKICBoY19hZGRfc2VyaWVzKGRhdGEgPSB0cmlwYWR2aXNvciAlPiUgbXV0YXRlKGxvbiA9IExvbmdpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXQgPSBMYXRpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gTXVzZXVtTmFtZSksICAjIEFkZCBjb3JyZWN0IG5hbWVzCiAgICAgICAgICAgICAgICB0eXBlID0gIm1hcHBvaW50IiwgbmFtZSA9ICJNdXNldW1zIikgCgpgYGAKCk5vdCBhcyBpbXByZXNzaXZlIGFzIGxlYWZsZXQsIGJ1dCB0aGUgbWFwIGNvdWxkIHByb2JhYmx5IGJlIGltcHJvdmVkLiAKKipOT1RFKio6IGluIHRoZSBleGFtcGxlIGFib3ZlLCB0aGUgZ2VvZ3JhcGhpY2FsIGJhY2tncm91bmQgYW5kIHRoZSBwb2ludHMgYXJlIHVucmVsYXRlZC4gIApPdGhlciBtYXAsIHNhbWUgcmVzdWx0IGJlbG93Li4uCgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQptYXBkYXRhMyA8LSBnZXRfZGF0YV9mcm9tX21hcChkb3dubG9hZF9tYXBfZGF0YSgiY291bnRyaWVzL3VzL3VzLW55LWFsbCIpKQpoY21hcCgiY291bnRyaWVzL3VzL3VzLW55LWFsbCIpICU+JQogIGhjX21hcE5hdmlnYXRpb24oZW5hYmxlZCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICBidXR0b25PcHRpb25zID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICAgYWxpZ24gPSAicmlnaHQiLAogICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNhbEFsaWduID0gImJvdHRvbSIpKSAlPiUKICBoY19hZGRfc2VyaWVzKGRhdGEgPSB0cmlwYWR2aXNvciAlPiUgbXV0YXRlKGxvbiA9IExvbmdpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXQgPSBMYXRpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gTXVzZXVtTmFtZSksICAjIEFkZCBjb3JyZWN0IG5hbWVzCiAgICAgICAgICAgICAgICB0eXBlID0gIm1hcHBvaW50IiwgbmFtZSA9ICJNdXNldW1zIikgCmBgYAoKCiMgUmVzb3VyY2VzCgpCZWxvdywgYSBsaW5rIG9mIGdyZWF0IHBhZ2VzICYgdHV0b3JpYWxzOgoKKipHZW9jb21wdXRhdGlvbiB3aXRoIFIqKiAob25saW5lIGJvb2spOiBodHRwczovL2dlb2NvbXByLnJvYmlubG92ZWxhY2UubmV0ICAgIAoqKmdncGxvdCoqITogaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcF9kYXRhLmh0bWxQdXJlICAgICAKKipzZioqIChzaGFwZSBmaWxlcyk6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zZi92aWduZXR0ZXMvc2YxLmh0bWwgICAKKipzZiAmIFIqKjogaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2FydGljbGVzL3NmMS5odG1sICAgCioqc2YgJiBnZ3Bsb3QqKjogaHR0cHM6Ly93d3cuci1zcGF0aWFsLm9yZy9yLzIwMTgvMTAvMjUvZ2dwbG90Mi1zZi0yLmh0bWwgICAKKipsaXN0IG9mIHNmKio6IGh0dHA6Ly9kYXRhcGFnZXMuY29tL2dpcy1tYXAtcHVibGlzaGluZy1wcm9ncmFtL2dpcy1vcGVuLWZpbGVzL2dsb2JhbC1mcmFtZXdvcmsvZ2xvYmFsLWhlYXQtZmxvdy1kYXRhYmFzZS9zaGFwZWZpbGVzLWxpc3QgIAoqKnRtYXAqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RtYXAvdmlnbmV0dGVzL3RtYXAtZ2V0c3RhcnRlZC5odG1sICAgCioqZ2dtYXAqKjogaHR0cHM6Ly9naXRodWIuY29tL2RrYWhsZS9nZ21hcCAgICAKKipsZWFmbGV0Kio6IGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8gICAgCioqT3JlZ29uKiogKHR1dG9yaWFsKTogaHR0cDovL2dlb2cudW9yZWdvbi5lZHUvYmFydGxlaW4vY291cnNlcy9nZW9nNDk1L2xlYzA2Lmh0bWwgICAgCgoKCgojIEEgdGVzdCB3aXRoIEdlb0pTT04KCkdlb0pTT04gYXJlLCB3aXRoIHNoYXBlZmlsZXMgdGhlIG90aGVyIG1haW4gc3RhbmRhcmQgaW4gZ2VvY29tcHV0aW5nLiAgIApJdCdzIGVhc3kgdG8gb2J0YWluIGZpbGVzLCBlLmcuLCB2aWEgaHR0cHM6Ly9nZW9qc29uLW1hcHMuYXNoLm1zCkFsc28sIHlvdSB3aWxsIG5lZWQgdGhlICoqcmdkYWwqKiBwYWNrYWdlLgoKCmBgYHtyLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CiNsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KGdlb2pzb25zZikKI2FmcmljYSA8LSByZ2RhbDo6cmVhZE9HUigiYWZyaWNhLmdlby5qc29uIikKI2FmcmljYSA8LSBnZGFsX3JlYWQoImN1c3RvbS5nZW8uanNvbiIpCmFmcmljYSA8LSBnZW9qc29uX3NmKCJhZnJpY2EuZ2VvLmpzb24iKQphZnJpY2EgPC0gYWZyaWNhIHw+IG11dGF0ZShwb3BfZXN0ID0gcm91bmQocG9wX2VzdC8xMF42LDEpKSAKCmxhYmVscyA8LSBzcHJpbnRmKAogICI8c3Ryb25nPiVzPC9zdHJvbmc+PGJyLz4lZyBNIGluaGFiaXRhbnRzIiwKICBhZnJpY2EkbmFtZSwgYWZyaWNhJHBvcF9lc3QKKSAlPiUgbGFwcGx5KGh0bWx0b29sczo6SFRNTCkKYGBgCgpgYGB7cn0KcGFsIDwtIGNvbG9yQmluKCJZbE9yUmQiLCBkb21haW4gPSBhZnJpY2EkcG9wX2VzdCwgCiAgICAgICAgICAgICAgICBiaW5zID0gYygwLCAxMDAsIDUwMCwgMTAwMCwgNTAwMCwgMTAwMDAsIEluZikpCgpsZWFmbGV0KGFmcmljYSkgJT4lCiAgYWRkVGlsZXMoKSAlPiUKICBhZGRQb2x5Z29ucyhmaWxsT3BhY2l0eSA9IDAuNywKICAgICAgICAgICAgICBmaWxsQ29sb3IgPSB+cGFsKHBvcF9lc3QpLAogICAgICAgICAgICAgIHdlaWdodCA9IDIsCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDEsCiAgICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLAogICAgICAgICAgICAgICMgaGlnaGxpZ2h0ID0gaGlnaGxpZ2h0T3B0aW9ucyggICAgICAgIyA1IGxpbmVzIGJlbG93IGNvbnRyb2wgdGhlIGN1cnNvciBpbXBhY3QKICAgICAgICAgICAgICAjICAgd2VpZ2h0ID0gMiwgICAgICAgICAgICAgICAgICAgICAgICMgV2lkdGggb2YgbGluZQogICAgICAgICAgICAgICMgICBjb2xvciA9ICIjMzMzMzMzIiwgICAgICAgICAgICAgICAgIyBDb2xvciBvZiBsaW5lCiAgICAgICAgICAgICAgIyAgIGRhc2hBcnJheSA9ICIiLCAgICAgICAgICAgICAgICAgICAjIE5vIGRhc2gKICAgICAgICAgICAgICAjICAgZmlsbE9wYWNpdHkgPSAwLjksICAgICAgICAgICAgICAgICMgT3BhY2l0eQogICAgICAgICAgICAgICMgICBicmluZ1RvRnJvbnQgPSBUUlVFKSwKICAgICAgICAgICAgICBsYWJlbCA9IGxhYmVscywKICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMoCiAgICAgICAgICAgICAgICBzdHlsZSA9IGxpc3QoImZvbnQtd2VpZ2h0IiA9ICJub3JtYWwiLCBwYWRkaW5nID0gIjNweCA4cHgiKSwKICAgICAgICAgICAgICAgIHRleHRzaXplID0gIjE1cHgiLAogICAgICAgICAgICAgICAgZGlyZWN0aW9uID0gImF1dG8iKSkgJT4lCiAgYWRkTGVnZW5kKHBhbCA9IHBhbCwgdmFsdWVzID0gfnBvcF9lc3QsIG9wYWNpdHkgPSAwLjcsIHRpdGxlID0gTlVMTCwKICAgICAgICAgICAgcG9zaXRpb24gPSAiYm90dG9tbGVmdCIpCmBgYAoKCmBgYHtyfQoKYGBgCgpgYGB7cn0KCmBgYAoK