Preliminaries

Make sure to move to the proper working directory. We continue to work with the NYC_Sub_borough_Area data.

Packages Needed

We will be using four packages: tidyverse, sf, tmap, and RColorBrewer. You may need to install some of these if you don’t have them yet.

library(tidyverse)
── Attaching packages ────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.0.0     ✔ purrr   0.2.5
✔ tibble  1.4.2     ✔ dplyr   0.7.6
✔ tidyr   0.8.1     ✔ stringr 1.3.1
✔ readr   1.1.1     ✔ forcats 0.3.0
── Conflicts ───────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(sf)
Linking to GEOS 3.6.1, GDAL 2.1.3, proj.4 4.9.3
library(tmap)
library(RColorBrewer)

Spatial Data - Shape Files

Spatial data are characterized by the combination of locational information (the precise definition of points, lines or areas) and so-called attribute information (variables). So far, we have only worked with attribute information, stored in the file NYC_Sub_borough_Area.dbf and turned into a data frame (or tibble). In order to make maps and carry out spatial analysis we need the locational information as well. There are many formats to store this information. To keep things simple we will be using a so-called shape file, a standard supported by ESRI, one of the major commercial GIS vendors.

It is a bit confusing, since there is no such thing as one shape file, but there is instead a collection of three (or four) files. One file has the extension .shp, one .shx, one .dbf, and one .prj (with the projection information). The first three are required, the fourth one is optional, but highly recommended. The files should all be in the same directory and have the same file name.

There are a number of different ways to read shape files, but we will use the one associated with the package sf. The command is st_read and you pass the file name for the .shp file (with the shp file extension). In our example, that would be st_read("NYC_Sub_borough_Area.shp"). We assign this to the nyc data frame.

nyc <- st_read("Nyc_Sub_borough_Area.shp")
Reading layer `NYC_Sub_borough_Area' from data source `/Users/luc/Dropbox/z_Courses/uc_spatial_analysis_1_2018/R_stuff/NYC_Sub_borough_Area.shp' using driver `ESRI Shapefile'
Simple feature collection with 55 features and 34 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 913037.2 ymin: 120117 xmax: 1067549 ymax: 272751.4
epsg (SRID):    NA
proj4string:    +proj=lcc +lat_1=40.66666666666666 +lat_2=41.03333333333333 +lat_0=40.16666666666666 +lon_0=-74 +x_0=300000.0000000001 +y_0=0 +datum=NAD83 +units=us-ft +no_defs

Upon successful completion, some summary characteristics of the layer are listed that describe its spatial characteristics. When you compute a summary of the contents of the data frame, you will notice that in addition to the by now familiar variable names, there is also a column named geometry. This contains the spatial information. You will be exploring this further next quarter, but for now it suffices to know that the attributes are combined with the spatial properties.

summary(nyc)
    bor_subb                        name         code                     subborough    forhis06        forhis07        forhis08         forhis09    
 Min.   :101.0   Astoria              : 1   Min.   :101.0   Astoria            : 1   Min.   :10.70   Min.   :10.37   Min.   : 9.689   Min.   :14.66  
 1st Qu.:204.5   Bay Ridge            : 1   1st Qu.:204.5   Bay Ridge          : 1   1st Qu.:29.61   1st Qu.:28.92   1st Qu.:28.182   1st Qu.:28.28  
 Median :218.0   Bayside / Little Neck: 1   Median :218.0   Bayside/Little Neck: 1   Median :39.72   Median :39.21   Median :39.567   Median :35.99  
 Mean   :274.4   Bedford Stuyvesant   : 1   Mean   :274.4   Bedford Stuyvesant : 1   Mean   :39.22   Mean   :38.33   Mean   :37.764   Mean   :37.46  
 3rd Qu.:403.5   Bellerose / Rosedale : 1   3rd Qu.:403.5   Bensonhurst        : 1   3rd Qu.:44.61   3rd Qu.:45.30   3rd Qu.:45.496   3rd Qu.:45.14  
 Max.   :503.0   Bensonhurst          : 1   Max.   :503.0   Borough Park       : 1   Max.   :69.52   Max.   :69.40   Max.   :69.341   Max.   :69.16  
                 (Other)              :49                   (Other)            :49                                                                   
    forwh06         forwh07         forwh08         forwh09        hhsiz1990        hhsiz00         hhsiz02         hhsiz05         hhsiz08     
 Min.   : 0.00   Min.   : 0.00   Min.   : 0.00   Min.   : 0.00   Min.   :1.560   Min.   :1.574   Min.   :1.532   Min.   :1.544   Min.   :1.544  
 1st Qu.:14.26   1st Qu.:14.56   1st Qu.:14.30   1st Qu.:13.57   1st Qu.:2.428   1st Qu.:2.445   1st Qu.:2.234   1st Qu.:2.271   1st Qu.:2.203  
 Median :21.18   Median :21.10   Median :21.39   Median :19.69   Median :2.716   Median :2.718   Median :2.568   Median :2.567   Median :2.488  
 Mean   :24.36   Mean   :23.54   Mean   :22.67   Mean   :21.98   Mean   :2.635   Mean   :2.639   Mean   :2.505   Mean   :2.471   Mean   :2.424  
 3rd Qu.:31.53   3rd Qu.:29.78   3rd Qu.:31.38   3rd Qu.:27.98   3rd Qu.:2.937   3rd Qu.:2.929   3rd Qu.:2.812   3rd Qu.:2.682   3rd Qu.:2.665  
 Max.   :60.36   Max.   :59.64   Max.   :59.11   Max.   :56.89   Max.   :3.376   Max.   :3.202   Max.   :3.087   Max.   :3.106   Max.   :3.222  
                                                                                                                                                
    kids2000         kids2005         kids2006         kids2007         kids2008         kids2009        rent2002         rent2005         rent2008   
 Min.   : 8.382   Min.   : 8.708   Min.   : 8.683   Min.   : 8.067   Min.   : 8.004   Min.   : 0.00   Min.   :   0.0   Min.   :   0.0   Min.   :   0  
 1st Qu.:30.301   1st Qu.:27.871   1st Qu.:26.215   1st Qu.:27.366   1st Qu.:27.843   1st Qu.:26.69   1st Qu.: 750.0   1st Qu.: 897.5   1st Qu.:1000  
 Median :38.228   Median :35.763   Median :36.524   Median :35.414   Median :33.883   Median :33.53   Median : 800.0   Median : 990.0   Median :1100  
 Mean   :36.040   Mean   :34.353   Mean   :33.847   Mean   :33.801   Mean   :33.212   Mean   :32.07   Mean   : 944.4   Mean   :1045.9   Mean   :1257  
 3rd Qu.:42.773   3rd Qu.:42.152   3rd Qu.:41.174   3rd Qu.:40.290   3rd Qu.:40.610   3rd Qu.:39.68   3rd Qu.:1000.0   3rd Qu.:1100.0   3rd Qu.:1362  
 Max.   :55.367   Max.   :50.872   Max.   :51.911   Max.   :51.502   Max.   :49.716   Max.   :48.13   Max.   :2500.0   Max.   :2500.0   Max.   :2900  
                                                                                                                                                      
   rentpct02       rentpct05       rentpct08        pubast90        pubast00          yrhom02          yrhom05          yrhom08      
 Min.   : 0.00   Min.   : 0.00   Min.   : 0.00   Min.   :23.89   Min.   : 0.8981   Min.   : 8.216   Min.   : 8.583   Min.   : 8.278  
 1st Qu.:13.27   1st Qu.:13.04   1st Qu.:15.48   1st Qu.:62.38   1st Qu.: 3.3860   1st Qu.:11.264   1st Qu.:11.268   1st Qu.:10.846  
 Median :21.11   Median :22.59   Median :23.70   Median :75.44   Median : 6.8781   Median :12.391   Median :12.217   Median :12.274  
 Mean   :22.01   Mean   :22.08   Mean   :23.41   Mean   :71.53   Mean   : 8.4372   Mean   :12.251   Mean   :12.273   Mean   :12.058  
 3rd Qu.:29.49   3rd Qu.:30.51   3rd Qu.:31.50   3rd Qu.:84.59   3rd Qu.:11.5369   3rd Qu.:13.160   3rd Qu.:13.288   3rd Qu.:13.001  
 Max.   :43.38   Max.   :40.88   Max.   :47.38   Max.   :96.65   Max.   :23.4318   Max.   :16.124   Max.   :16.627   Max.   :15.425  
                                                                                                                                     
          geometry 
 MULTIPOLYGON :55  
 epsg:NA      : 0  
 +proj=lcc ...: 0  
                   
                   
                   
                   

Checking the class of the nyc data frame, we see that it is of type sf, which stands for simple features, the data structure used to deal with spatial information.

class(nyc)
[1] "sf"         "data.frame"

We can also make a quick plot, but this is not exactly what we want. A small map is created for each of the variables. It just confirms that we indeed have both the attributes and the spatial information.

plot(nyc)
plotting the first 9 out of 34 attributes; use max.plot = 34 to plot all

Variables

We will again need variables to do some conditioning. If you did not save these with the dbf file, we can quickly re-create them and add them to the nyc data frame.

The manbronx variable:

nyc <- nyc %>% mutate(manbronx = if_else((code > 300 & code < 311) | (code > 100 & code < 111),"Select","Rest"))

The cut-off by household size in 2000:

nyc$cut.hhsiz <- cut_number(nyc$hhsiz00,n=2)
nyc$cut.hhsiz
 [1] (2.72,3.2]  [1.57,2.72] (2.72,3.2]  [1.57,2.72] [1.57,2.72] (2.72,3.2]  (2.72,3.2]  [1.57,2.72] [1.57,2.72] [1.57,2.72] [1.57,2.72] (2.72,3.2] 
[13] (2.72,3.2]  [1.57,2.72] (2.72,3.2]  (2.72,3.2]  [1.57,2.72] [1.57,2.72] [1.57,2.72] [1.57,2.72] [1.57,2.72] [1.57,2.72] [1.57,2.72] [1.57,2.72]
[25] [1.57,2.72] [1.57,2.72] (2.72,3.2]  (2.72,3.2]  (2.72,3.2]  (2.72,3.2]  (2.72,3.2]  (2.72,3.2]  [1.57,2.72] (2.72,3.2]  [1.57,2.72] [1.57,2.72]
[37] (2.72,3.2]  (2.72,3.2]  (2.72,3.2]  (2.72,3.2]  [1.57,2.72] [1.57,2.72] [1.57,2.72] (2.72,3.2]  (2.72,3.2]  (2.72,3.2]  (2.72,3.2]  (2.72,3.2] 
[49] (2.72,3.2]  [1.57,2.72] (2.72,3.2]  (2.72,3.2]  [1.57,2.72] [1.57,2.72] [1.57,2.72]
Levels: [1.57,2.72] (2.72,3.2]

A final check on all the variables using summary:

summary(nyc)
    bor_subb                        name         code                     subborough    forhis06        forhis07        forhis08         forhis09    
 Min.   :101.0   Astoria              : 1   Min.   :101.0   Astoria            : 1   Min.   :10.70   Min.   :10.37   Min.   : 9.689   Min.   :14.66  
 1st Qu.:204.5   Bay Ridge            : 1   1st Qu.:204.5   Bay Ridge          : 1   1st Qu.:29.61   1st Qu.:28.92   1st Qu.:28.182   1st Qu.:28.28  
 Median :218.0   Bayside / Little Neck: 1   Median :218.0   Bayside/Little Neck: 1   Median :39.72   Median :39.21   Median :39.567   Median :35.99  
 Mean   :274.4   Bedford Stuyvesant   : 1   Mean   :274.4   Bedford Stuyvesant : 1   Mean   :39.22   Mean   :38.33   Mean   :37.764   Mean   :37.46  
 3rd Qu.:403.5   Bellerose / Rosedale : 1   3rd Qu.:403.5   Bensonhurst        : 1   3rd Qu.:44.61   3rd Qu.:45.30   3rd Qu.:45.496   3rd Qu.:45.14  
 Max.   :503.0   Bensonhurst          : 1   Max.   :503.0   Borough Park       : 1   Max.   :69.52   Max.   :69.40   Max.   :69.341   Max.   :69.16  
                 (Other)              :49                   (Other)            :49                                                                   
    forwh06         forwh07         forwh08         forwh09        hhsiz1990        hhsiz00         hhsiz02         hhsiz05         hhsiz08     
 Min.   : 0.00   Min.   : 0.00   Min.   : 0.00   Min.   : 0.00   Min.   :1.560   Min.   :1.574   Min.   :1.532   Min.   :1.544   Min.   :1.544  
 1st Qu.:14.26   1st Qu.:14.56   1st Qu.:14.30   1st Qu.:13.57   1st Qu.:2.428   1st Qu.:2.445   1st Qu.:2.234   1st Qu.:2.271   1st Qu.:2.203  
 Median :21.18   Median :21.10   Median :21.39   Median :19.69   Median :2.716   Median :2.718   Median :2.568   Median :2.567   Median :2.488  
 Mean   :24.36   Mean   :23.54   Mean   :22.67   Mean   :21.98   Mean   :2.635   Mean   :2.639   Mean   :2.505   Mean   :2.471   Mean   :2.424  
 3rd Qu.:31.53   3rd Qu.:29.78   3rd Qu.:31.38   3rd Qu.:27.98   3rd Qu.:2.937   3rd Qu.:2.929   3rd Qu.:2.812   3rd Qu.:2.682   3rd Qu.:2.665  
 Max.   :60.36   Max.   :59.64   Max.   :59.11   Max.   :56.89   Max.   :3.376   Max.   :3.202   Max.   :3.087   Max.   :3.106   Max.   :3.222  
                                                                                                                                                
    kids2000         kids2005         kids2006         kids2007         kids2008         kids2009        rent2002         rent2005         rent2008   
 Min.   : 8.382   Min.   : 8.708   Min.   : 8.683   Min.   : 8.067   Min.   : 8.004   Min.   : 0.00   Min.   :   0.0   Min.   :   0.0   Min.   :   0  
 1st Qu.:30.301   1st Qu.:27.871   1st Qu.:26.215   1st Qu.:27.366   1st Qu.:27.843   1st Qu.:26.69   1st Qu.: 750.0   1st Qu.: 897.5   1st Qu.:1000  
 Median :38.228   Median :35.763   Median :36.524   Median :35.414   Median :33.883   Median :33.53   Median : 800.0   Median : 990.0   Median :1100  
 Mean   :36.040   Mean   :34.353   Mean   :33.847   Mean   :33.801   Mean   :33.212   Mean   :32.07   Mean   : 944.4   Mean   :1045.9   Mean   :1257  
 3rd Qu.:42.773   3rd Qu.:42.152   3rd Qu.:41.174   3rd Qu.:40.290   3rd Qu.:40.610   3rd Qu.:39.68   3rd Qu.:1000.0   3rd Qu.:1100.0   3rd Qu.:1362  
 Max.   :55.367   Max.   :50.872   Max.   :51.911   Max.   :51.502   Max.   :49.716   Max.   :48.13   Max.   :2500.0   Max.   :2500.0   Max.   :2900  
                                                                                                                                                      
   rentpct02       rentpct05       rentpct08        pubast90        pubast00          yrhom02          yrhom05          yrhom08      
 Min.   : 0.00   Min.   : 0.00   Min.   : 0.00   Min.   :23.89   Min.   : 0.8981   Min.   : 8.216   Min.   : 8.583   Min.   : 8.278  
 1st Qu.:13.27   1st Qu.:13.04   1st Qu.:15.48   1st Qu.:62.38   1st Qu.: 3.3860   1st Qu.:11.264   1st Qu.:11.268   1st Qu.:10.846  
 Median :21.11   Median :22.59   Median :23.70   Median :75.44   Median : 6.8781   Median :12.391   Median :12.217   Median :12.274  
 Mean   :22.01   Mean   :22.08   Mean   :23.41   Mean   :71.53   Mean   : 8.4372   Mean   :12.251   Mean   :12.273   Mean   :12.058  
 3rd Qu.:29.49   3rd Qu.:30.51   3rd Qu.:31.50   3rd Qu.:84.59   3rd Qu.:11.5369   3rd Qu.:13.160   3rd Qu.:13.288   3rd Qu.:13.001  
 Max.   :43.38   Max.   :40.88   Max.   :47.38   Max.   :96.65   Max.   :23.4318   Max.   :16.124   Max.   :16.627   Max.   :15.425  
                                                                                                                                     
   manbronx                  geometry        cut.hhsiz 
 Length:55          MULTIPOLYGON :55   [1.57,2.72]:28  
 Class :character   epsg:NA      : 0   (2.72,3.2] :27  
 Mode  :character   +proj=lcc ...: 0                   
                                                       
                                                       
                                                       
                                                       

Writing the shape file

If you want to keep the new variables for future use, you need to write them out as a new shape file. Without further details, the command is st_write. You must specify the data frame, a name for the shape file (without the file extension) and the driver, e.g. st_write(nyc,"new_nyc",driver="ESRI Shapefile"). This will create a folder containing the four new files.

st_write(nyc,"new_nyc",driver="ESRI Shapefile")

Basic Choropleth Mapping

We now turn to the choropleth mapping functionality included in the tmap package. This package is extremely powerful, and there are many features that we do not cover. Detailed information on tmap functionality can be found at tmap documentation. In addition, several extensive examples are given in the chapter on mapping in Lovelace, Nowosad, and Muenchow’s Geocomputation with R book.

Default settings

The package tmap uses the same layered logic for graphics as ggplot. The initial command is tm_shape, which specifies the geography to which the mapping is applied. This is followed by a number of tm_* options that select the type of map and several optional customizations.

We start with a basic choropleth map of the rent2008 variable.

In tmap there are two ways to create a choropleth map. The simplest one is to use the tm_polygons command with the variable name as the argument (under the hood, this is the value for the col parameter, i.e., the color of the polygons).

In our example, the map is created with two commands, tm_shape(nyc) and tm_polygons("rent2008") (note the quotes in “rent2008”, unlike what we did for ggplot).

The color coding corresponds to style = "pretty", which is the default when the classification breaks are not set explicitly, and the default values are used (see the discussion of map classifications below).

tm_shape(nyc) +
  tm_polygons("rent2008")

As it turns out, the tm_polygons command is a wrapper around two other functions, tm_fill and tm_borders. tm_fill controls the contents of the polygons (color, classification, etc.), while tm_borders does the same for the polygon outlines.

For example, using the same shape (but no variable), whe obtain the outlines of the sub-boroughs from the tm_borders( ) command.

tm_shape(nyc) +
  tm_borders()

Similarly, we obtain a choropleth map without the polygon outlines when we just use the tm_fill("rent2008") command.

tm_shape(nyc) + 
  tm_fill("rent2008")

When we combine the two commands, we obtain the same map as with tm_polygons (this illustrates how in R one can often obtain the same result in a number of different ways).

tm_shape(nyc) +
  tm_fill("rent2008") +
  tm_borders()

An extensive set of options is available to customize the appearance of the map. A full list is given in the documentation page for tm_fill. In what follows, we briefly consider the most common ones.

Color palette

The range of colors used to depict the spatial distribution of a variable is determined by the palette. The palette is an argument to the tm_fill function. Several built-in palettes are contained in tmap. For example, using palette = "Reds" (again, note the quotes) would yield the following map for our example.

tm_shape(nyc) +
  tm_fill("rent2008",palette="Reds") +
  tm_borders()

Under the hood, “Reds” refers to one of the color schemes supported by the RColorBrewer package, discussed next.

ColorBrewer

The preferred approach to select a color palette is to choose one of the schemes contained in the RColorBrewer package. These are based on the research of cartographer Cynthia Brewer (see the colorbrewer2 web site for details). ColorBrewer makes a distinction between sequential scales (for a scale that goes from low to high), diverging scales (to highlight how values differ from a central tendency), and qualitative scales (for categorical variables). For each scale, a series of single hue and multi-hue scales are suggested. In the RColorBrewer package, these are referred to by a name (e.g., the “Reds” palette we used above is an example). The full list is contained in the RColorBrewer documentation.

There are two very useful commands in this package. One sets a color palette by specifying its name and the number of desired categories. The result is a character vector with the hex codes of the corresponding colors.

For example, we select a sequential color scheme going from blue to green, as BuGn, by means of the command brewer.pal, with the number of categories (6) and the scheme as arguments. The resulting vector contains the HEX codes for the colors.

pal <- brewer.pal(6,"BuGn")
pal
[1] "#EDF8FB" "#CCECE6" "#99D8C9" "#66C2A4" "#2CA25F" "#006D2C"

Using this palette in our map yields the following result.

tm_shape(nyc) +
  tm_fill("rent2008",palette=pal) +
  tm_borders()

The command display.brewer.pal allows us to explore different color schemes before applying them to a map. Continuing with our “BuGn” example, that would be invoked with display.brewer.pal(6,"BuGn").

display.brewer.pal(6,"BuGn")

Legend

There are many options to change the formatting of the legend entries through the legend.format argument. We refer to the tm_fill documentation for specific details.

Often, the automatic title for the legend is not that attractive, since it is simply the variable name. This can be customized by setting the title argument to tm_fill. For example, keeping all the other settings to the default, we change the legend title to Rent in 2008 by specifying title="Rent in 2008" as an option to tm_fill.

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008") +
  tm_borders()

Another important aspect of the legend is its positioning. This is handled through the tm_layout function. This function has a vast number of options, as detailed in the documentation. There are also specialized subsets of layout functions, focused on specific aspects of the map, such as tm_legend, tm_style and tm_format. We illustrate the positioning of the legend.

The default is to position the legend inside the map. Often, this default solution is appropriate, but sometimes further control is needed. The legend.position argument to the tm_layout function takes a vector of two string variables that determine both the horizontal position (“left”, “right”, or “center”) and the vertical position (“top”, “bottom”, or “center”).

For example, if we would want to move the legend to the lower-right position (clearly inferior to the default solution), we would specify the legend.position = c("right","bottom") to the tm_layout function.

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008") +
  tm_borders() +
  tm_layout(legend.position = c("right", "bottom"))

There is also the option to position the legend outside the frame of the map. This is accomplished by setting legend.outside to TRUE (the default is FALSE), and optionally also specify its position by means of legend.outside.position. The latter can take the values “top”, “bottom”, “right”, and “left”.

For example, to position the legend outside and on the right, would be accomplished by passing the option legend.outside = TRUE, legend.outside.position = "right" to the tm_layout function.

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008") + 
  tm_borders() +
  tm_layout(legend.outside = TRUE, legend.outside.position = "right")

We can also customize the size of the legend, its alignment, font, etc. We refer to the documentation for specifics.

A final interesting option for the legend is to match it with a histogram that visualizes the number of observations in each map category. This is accomplished by setting legend.hist=TRUE in the tm_fill command. Further customization is possible, but is not covered here.

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008",legend.hist=TRUE) + 
  tm_borders() +
  tm_layout(legend.outside = TRUE, legend.outside.position = "right")

Title

Another functionality of the tm_layout function is to set a title for the map, and specify its position, size, etc. For example, we can set title = "Rent 2008 NYC Sub-Boroughs", title.size = 0.8, title.position = c("right","bottom") (for details, see the tm_layout documentation). We made the font size a bit smaller (0.8) in order not to overwhelm the map, and positioned it in the bottom right-hand corner.

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008")  +
  tm_borders() +
  tm_layout(title = "Rent 2008 NYC Sub-Boroughs", title.size = 0.8, title.position = c("right","bottom"))

Finally, in order to have a title appear on top (or on the bottom) of the map, rather than inside (the default), we need to set the main.title argument of the tm_layout function. For example, main.title = "Rent 2008 NYC Sub-Boroughs", title.size = 1.5,main.title.position="center" has the title centered on top and with the size set to 1.5 to have a larger font).

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008")  +
  tm_borders() +
  tm_layout(main.title = "Rent 2008 NYC Sub-Boroughs", title.size = 1.5,main.title.position="center")

Interactive base map

The default mode in tmap is plot, i.e., the standard plotting environment in R to draw a map on the screen or to a device (e.g., a pdf file). There is also an interactive mode, which builds upon leaflet to add a basemap and interact with the map through zooming and identification of individual observations. The interactive mode is referred to as view. We switch between modes by means of the tmap_mode command. For example, to switch to the interactive mode, we set tmap_mode("view").

tmap_mode("view")
tmap mode set to interactive viewing

There are a number of different ways in which a basemap can be added. The current preferred approach is through the tm_basemap command. This takes two important arguments. One is the name of the server. A number of basemaps are supported (and no doubt, that number will increase over time), but we will illustrate the OpenStreetMap option. A second argument is the degree of transparency, or alpha level. This can also be set for the map itself through tm_fill. In practice, one typically needs to experiment a bit to find the right balance between the information in the choropleth map and the background.

In the example below, we set the alpha level for the main map to 0.7 in tm_fill, and for the base layer to 0.5 in tm_basemap, as tm_basemap(server="OpenStreetMap",alpha=0.5).

When we move the pointer over the polygons, their ID value is shown. A click on a location also gives the value for the variable that is being mapped. Zooming and panning are supported as well. In addition, it is possible to stack several layers, but we won’t pursue that here.

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008",alpha=0.7) +
  tm_borders() +
  tm_basemap(server="OpenStreetMap",alpha=0.5)

Before we proceed, we turn the mode back to plot.

tmap_mode("plot")
tmap mode set to plotting

Common Map Classifications

The statistical aspects of a choropleth map are expressed through the map classification that is used to convert observations for a continuous variable into a small number of bins, similar to what is the case for a histogram. Different classifications reveal different aspects of the spatial distribution of the variable.

In tmap, the classification is selected by means of the style option in tm_fill. So far, we have used the default, which is set to pretty. The latter is a base R function to compute a sequence of roughly equally spaced values.

There are many classification types available in tmap, which each correspond to either a base R function, or to a custom expression contained in the classIntervals functionality.

In this section, we review the quantile map, natural breaks map, and equal intervals map, the most commonly used map classifications. We continue to use the same example for rent2008.

Quantile map

A quantile map shows the spatial distribution of a variable by classifying the observations into categories according to the quantile to which they below. For example, for a quintile map (with 5 categories), the observations are sorted, and the first 20% are given the color for the first quintile, the second for the second quintile, etc.

A quantile map is obtained by setting style="quantile" in the tm_fill command. The number of categories is taken from the n argument, which has a default value of 5. So, for example, using this default will yield a quintile map with five categories (we specify the type of map in the title through tm_layout), e.g., title= "Quintile Map", with title.size = 0.9 for somewhat better proportions.

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008",style="quantile")  +
  tm_borders() +
  tm_layout(title = "Quintile Map", title.size = 0.9, title.position = c("right","bottom"))

For a quartile map, with four categories, we need to specify the argument n=4 explicitly (five is the default, so we did not have to specify that in the quintile map). The rest of the commands are the same as before (but now with title = "Quartile Map".

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008",n=4,style="quantile")  +
  tm_borders() +
  tm_layout(title = "Quartile Map", title.size = 0.9, title.position = c("right","bottom"))

Equal intervals map

An equal intervals map is the counterpart of a histogram. You specify the number of bins that each cover the same range from beginning to end for the variable, hence the name equal intervals. For example, we can check the range for rent2008 using the summary command (remember the data frame and the $ symbol).

summary(nyc$rent2008)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      0    1000    1100    1257    1362    2900 

The minimum is 0 (which is suspicious, but we ignore that aspect for now) and the maximum is 2900. If we wanted 4 intervals, each would cover a range of 2900/4 = 725. So, the first interval would be from 0 to 725, the second from 725 to 1450, etc.

Again, we specify this type of classification by setting the proper style as style = "equal", and by setting the number of bins as n = 4. We adjust the title to give title="Equal Intervals Map". Note the difference with the quantile map.

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008",n=4,style="equal")  +
  tm_borders() +
  tm_layout(title = "Equal Intervals Map", title.size = 0.9, title.position = c("right","bottom"))

Natural breaks map

A natural breaks, or Jenks classification is based on an algorithm that creates groups of observations such that they are most similar within the group, and dissimilar between groups. This is a clustering logic for the variable we want to map. The algorithm is fairly complex, but it does result in (mostly) intuitive breaks in the data.

A natural breaks map is obtained by specifying the style = “jenks” in tm_fill. All the other options are as before. We illustrate this for six categories, with n=6, but using the standard palette (feel free to customize). We set the title = "Natural Breaks Map".

Note how the classification nicely separates the observations with rent zero, as well as the highest rent neighborhoods in Manhattan.

tm_shape(nyc) +
  tm_fill("rent2008",title="Rent in 2008",n=6,style="jenks")  +
  tm_borders() +
  tm_layout(title = "Natural Breaks Map", title.size = 0.9, title.position = c("right","bottom"))

Custom breaks

For all the built-in styles, the category breaks are computed internally. In most cases this is fine, but what if we want to compare the rent over different years? The classifications are all relative to the values observed in that year. For example, for a quartile map, we could track whether an observations (neighborhood) moves from one quartile to another, but that doesn’t tell us anything about the actual rent. It would be more informative to set fixed break points for all the years to illustrate how the rent changes over time.

In order to override the standard defaults, the breakpoints can be set explicitly by means of the breaks argument to the tm_fill function. For example, say we wanted to track the rent between 2002, 2005 and 2008 (i.e., rent2002, rent2005, and rent2008). First, we check the descriptive statistics we computed earlier with the summary command. From that, we will use the quartiles for 2005 as the break points (just to illustrate how to go about this). These are 898, 990 and 1100 (rounded). We also need to set a minimum (0) and a maximum, the largest value in 2008, or 2900.

In tmap, the custom break points are set in the breaks option. They must include a minimum and a maximum. So, in our example, we need to set the breakpoints to 0 (minimum), 898, 990, 1100, and 2900 (maximum). This is passed as a vector, using the c( ) notation, as breaks = c(0,898,990,1100,2900). Or, alternatively, we first set a vector as custom.breaks = c(0,898,990,1100,2900), and then pass that vector to the breaks option.

custom.breaks <- c(0,898,990,1100,2900)

Now, we can make our three maps using the custom classification. We use the palette="YlOrBr" from RColorBrewer. First, for 2002 (with an appropriate title, resized to 0.8)

tm_shape(nyc) +
  tm_fill("rent2002",title="Median Rent",breaks=custom.breaks,palette="YlOrBr")  +
  tm_borders() +
  tm_layout(title = "Custom Breaks Map - 2002", title.size = 0.9, title.position = c("right","bottom"))

For 2005:

tm_shape(nyc) +
  tm_fill("rent2005",title="Median Rent",breaks=custom.breaks,palette="YlOrBr")  +
  tm_borders() +
  tm_layout(title = "Custom Breaks Map - 2005", title.size = 0.9, title.position = c("right","bottom"))

And for 2008:

tm_shape(nyc) +
  tm_fill("rent2008",title="Median Rent",breaks=custom.breaks,palette="YlOrBr")  +
  tm_borders() +
  tm_layout(title = "Custom Breaks Map - 2008", title.size = 0.9, title.position = c("right","bottom"))

The maps clearly show how the rent increases spread throughout the neighborhoods.

Conditional Map

A conditional map, or facet map, or small multiples, is created by the tm_facets command. This largely follows the logic of the facet_grid command in ggplot that we covered earlier. An extensive set of options is available to customize the facet maps. An in-depth coverage of all the subtleties is beyond our scope (details can be found on the tm_facets documentation page)

We will use the same conditioning variables as before (which we recreated above), i.e., manbronx and cut.hhsiz. These two factor variables are used as conditioning variables in the by argument of the tm_facets command. The first variable conditions the rows (i.e., is the y-axis), the second variable conditions the columns (i.e., is the x-axis), in the same way as we saw for facet_grid in ggplot.

We illustrate this with the rent2008 variable, using the default map type. Two important options that affect the look of the conditional map are free.coords and drop.units. The default is that each facet map has its own scaling, focused on the relevant observations. Typically, one wants the same spatial layout in each facet, i.e., all the sub-boroughs in our example. This is ensured by setting free.coords=FALSE. In addition, we want all the spatial units to be shown in each facet (the default is to only show those that match the conditions). This is accomplished by setting drop.units=FALSE.

One “gotcha” is that manbronx is not a factor which is what tmap requires for the conditioning variable. So, first we need to create an additional variable that is a factor, based on the values of manbronx, i.e., using nyc$mb <- as.factor(nyc$manbronx)

nyc$mb <- as.factor(nyc$manbronx)

Now, we can proceed with the command tm_facets(by = c("cut.hhsiz", "mb"),free.coords = FALSE,drop.units=FALSE). We just keep the defaults (titles, labels, etc. can be added later).

tm_shape(nyc) +
  tm_fill("rent2008") +
  tm_borders() +
  tm_facets(by = c("cut.hhsiz", "mb"),free.coords = FALSE,drop.units=FALSE)

LS0tCnRpdGxlOiAiQmFzaWMgTWFwcGluZyIKYXV0aG9yOiAiTHVjIEFuc2VsaW4iCmRhdGU6ICIxMS8wOC8yMDE4IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBQcmVsaW1pbmFyaWVzCgpNYWtlIHN1cmUgdG8gbW92ZSB0byB0aGUgcHJvcGVyIHdvcmtpbmcgZGlyZWN0b3J5LiBXZSBjb250aW51ZSB0byB3b3JrIHdpdGggdGhlIAoqKk5ZQ19TdWJfYm9yb3VnaF9BcmVhKiogZGF0YS4KCiMjIyBQYWNrYWdlcyBOZWVkZWQKCldlIHdpbGwgYmUgdXNpbmcgZm91ciBwYWNrYWdlczogYHRpZHl2ZXJzZWAsIGBzZmAsIGB0bWFwYCwgYW5kIGBSQ29sb3JCcmV3ZXJgLiBZb3UgbWF5IG5lZWQKdG8gaW5zdGFsbCBzb21lIG9mIHRoZXNlIGlmIHlvdSBkb24ndCBoYXZlIHRoZW0geWV0LgoKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzZikKbGlicmFyeSh0bWFwKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKYGBgCgojIyMgU3BhdGlhbCBEYXRhIC0gU2hhcGUgRmlsZXMKClNwYXRpYWwgZGF0YSBhcmUgY2hhcmFjdGVyaXplZCBieSB0aGUgY29tYmluYXRpb24gb2YgbG9jYXRpb25hbCBpbmZvcm1hdGlvbiAodGhlIHByZWNpc2UKZGVmaW5pdGlvbiBvZiBwb2ludHMsIGxpbmVzIG9yIGFyZWFzKSBhbmQgc28tY2FsbGVkIGF0dHJpYnV0ZSBpbmZvcm1hdGlvbiAodmFyaWFibGVzKS4gU28gZmFyLCB3ZQpoYXZlIG9ubHkgd29ya2VkIHdpdGggYXR0cmlidXRlIGluZm9ybWF0aW9uLCBzdG9yZWQgaW4gdGhlIGZpbGUgKipOWUNfU3ViX2Jvcm91Z2hfQXJlYS5kYmYqKiBhbmQgCnR1cm5lZCBpbnRvIGEgZGF0YSBmcmFtZSAob3IgdGliYmxlKS4gSW4gb3JkZXIgdG8gbWFrZSBtYXBzIGFuZCBjYXJyeSBvdXQgc3BhdGlhbCBhbmFseXNpcyB3ZQpuZWVkIHRoZSBsb2NhdGlvbmFsIGluZm9ybWF0aW9uIGFzIHdlbGwuIFRoZXJlIGFyZSBtYW55IGZvcm1hdHMgdG8gc3RvcmUgdGhpcyBpbmZvcm1hdGlvbi4gVG8ga2VlcAp0aGluZ3Mgc2ltcGxlIHdlIHdpbGwgYmUgdXNpbmcgYSBzby1jYWxsZWQgKnNoYXBlIGZpbGUqLCBhIHN0YW5kYXJkIHN1cHBvcnRlZCBieSBFU1JJLCBvbmUgb2YgdGhlIAptYWpvciBjb21tZXJjaWFsIEdJUyB2ZW5kb3JzLgoKSXQgaXMgYSBiaXQgY29uZnVzaW5nLCBzaW5jZSB0aGVyZSBpcyBubyBzdWNoIHRoaW5nIGFzIG9uZSBzaGFwZSBmaWxlLCBidXQgdGhlcmUgaXMgaW5zdGVhZAphIGNvbGxlY3Rpb24gb2YgdGhyZWUgKG9yIGZvdXIpIGZpbGVzLiBPbmUgZmlsZSBoYXMgdGhlIGV4dGVuc2lvbiAqLnNocCosIG9uZSAqLnNoeCosIG9uZSAqLmRiZiosIGFuZApvbmUgKi5wcmoqICh3aXRoIHRoZSBwcm9qZWN0aW9uIGluZm9ybWF0aW9uKS4gVGhlIGZpcnN0IHRocmVlIGFyZSByZXF1aXJlZCwgdGhlIGZvdXJ0aCBvbmUgaXMgb3B0aW9uYWwsCmJ1dCBoaWdobHkgcmVjb21tZW5kZWQuIFRoZSBmaWxlcyBzaG91bGQgYWxsIGJlIGluIHRoZSBzYW1lIGRpcmVjdG9yeSBhbmQgaGF2ZSB0aGUgc2FtZSBmaWxlIG5hbWUuCgpUaGVyZSBhcmUgYSBudW1iZXIgb2YgZGlmZmVyZW50IHdheXMgdG8gcmVhZCBzaGFwZSBmaWxlcywgYnV0IHdlIHdpbGwgdXNlIHRoZSBvbmUgYXNzb2NpYXRlZCB3aXRoCnRoZSBwYWNrYWdlIGBzZmAuIFRoZSBjb21tYW5kIGlzIGBzdF9yZWFkYCBhbmQgeW91IHBhc3MgdGhlIGZpbGUgbmFtZSBmb3IgdGhlICouc2hwKiBmaWxlICh3aXRoIHRoZQpzaHAgZmlsZSBleHRlbnNpb24pLiBJbiBvdXIgZXhhbXBsZSwgdGhhdCB3b3VsZCBiZSBgc3RfcmVhZCgiTllDX1N1Yl9ib3JvdWdoX0FyZWEuc2hwIilgLiBXZSBhc3NpZ24KdGhpcyB0byB0aGUgKipueWMqKiBkYXRhIGZyYW1lLgoKYGBge3J9Cm55YyA8LSBzdF9yZWFkKCJOeWNfU3ViX2Jvcm91Z2hfQXJlYS5zaHAiKQpgYGAKClVwb24gc3VjY2Vzc2Z1bCBjb21wbGV0aW9uLCBzb21lIHN1bW1hcnkgY2hhcmFjdGVyaXN0aWNzIG9mIHRoZSAqbGF5ZXIqIGFyZSBsaXN0ZWQgdGhhdCBkZXNjcmliZSBpdHMgc3BhdGlhbApjaGFyYWN0ZXJpc3RpY3MuIFdoZW4geW91IGNvbXB1dGUgYSBgc3VtbWFyeWAgb2YgdGhlIGNvbnRlbnRzIG9mIHRoZSBkYXRhIGZyYW1lLCB5b3Ugd2lsbCBub3RpY2UgdGhhdCBpbiBhZGRpdGlvbgp0byB0aGUgYnkgbm93IGZhbWlsaWFyIHZhcmlhYmxlIG5hbWVzLCB0aGVyZSBpcyBhbHNvIGEgY29sdW1uIG5hbWVkICoqZ2VvbWV0cnkqKi4gVGhpcyBjb250YWlucyB0aGUgc3BhdGlhbAppbmZvcm1hdGlvbi4gWW91IHdpbGwgYmUgZXhwbG9yaW5nIHRoaXMgZnVydGhlciBuZXh0IHF1YXJ0ZXIsIGJ1dCBmb3Igbm93IGl0IHN1ZmZpY2VzIHRvIGtub3cgdGhhdCB0aGUKYXR0cmlidXRlcyBhcmUgY29tYmluZWQgd2l0aCB0aGUgc3BhdGlhbCBwcm9wZXJ0aWVzLgoKYGBge3J9CnN1bW1hcnkobnljKQpgYGAKCkNoZWNraW5nIHRoZSBgY2xhc3NgIG9mIHRoZSAqKm55YyoqIGRhdGEgZnJhbWUsIHdlIHNlZSB0aGF0IGl0IGlzIG9mIHR5cGUgKipzZioqLCB3aGljaCBzdGFuZHMgZm9yICpzaW1wbGUgZmVhdHVyZXMqLCB0aGUgZGF0YSBzdHJ1Y3R1cmUgdXNlZCB0byBkZWFsIHdpdGggc3BhdGlhbCBpbmZvcm1hdGlvbi4KCgpgYGB7cn0KY2xhc3MobnljKQpgYGAKCldlIGNhbiBhbHNvIG1ha2UgYSBxdWljayBgcGxvdGAsIGJ1dCB0aGlzIGlzIG5vdCBleGFjdGx5IHdoYXQgd2Ugd2FudC4gQSBzbWFsbCBtYXAgaXMgY3JlYXRlZCBmb3IgZWFjaCBvZiB0aGUgdmFyaWFibGVzLgpJdCBqdXN0IGNvbmZpcm1zIHRoYXQgd2UgaW5kZWVkIGhhdmUgYm90aCB0aGUgYXR0cmlidXRlcyBhbmQgdGhlIHNwYXRpYWwgaW5mb3JtYXRpb24uCgoKYGBge3J9CnBsb3QobnljKQpgYGAKCiMjIyBWYXJpYWJsZXMKCldlIHdpbGwgYWdhaW4gbmVlZCB2YXJpYWJsZXMgdG8gZG8gc29tZSBjb25kaXRpb25pbmcuIElmIHlvdSBkaWQgbm90IHNhdmUgdGhlc2Ugd2l0aCB0aGUgKipkYmYqKiBmaWxlLCB3ZQpjYW4gcXVpY2tseSByZS1jcmVhdGUgdGhlbSBhbmQgYWRkIHRoZW0gdG8gdGhlICoqbnljKiogZGF0YSBmcmFtZS4KClRoZSAqKm1hbmJyb254KiogdmFyaWFibGU6CgpgYGB7cn0KbnljIDwtIG55YyAlPiUgbXV0YXRlKG1hbmJyb254ID0gaWZfZWxzZSgoY29kZSA+IDMwMCAmIGNvZGUgPCAzMTEpIHwgKGNvZGUgPiAxMDAgJiBjb2RlIDwgMTExKSwiU2VsZWN0IiwiUmVzdCIpKQpgYGAKCgpUaGUgY3V0LW9mZiBieSBob3VzZWhvbGQgc2l6ZSBpbiAyMDAwOgoKYGBge3J9Cm55YyRjdXQuaGhzaXogPC0gY3V0X251bWJlcihueWMkaGhzaXowMCxuPTIpCm55YyRjdXQuaGhzaXoKYGBgCgpBIGZpbmFsIGNoZWNrIG9uIGFsbCB0aGUgdmFyaWFibGVzIHVzaW5nIGBzdW1tYXJ5YDoKCgpgYGB7cn0Kc3VtbWFyeShueWMpCmBgYAoKIyMjIyBXcml0aW5nIHRoZSBzaGFwZSBmaWxlCgpJZiB5b3Ugd2FudCB0byBrZWVwIHRoZSBuZXcgdmFyaWFibGVzIGZvciBmdXR1cmUgdXNlLCB5b3UgbmVlZCB0byB3cml0ZSB0aGVtIG91dCBhcyBhIG5ldyBzaGFwZSBmaWxlLgpXaXRob3V0IGZ1cnRoZXIgZGV0YWlscywgdGhlIGNvbW1hbmQgaXMgYHN0X3dyaXRlYC4gWW91IG11c3Qgc3BlY2lmeSB0aGUgZGF0YSBmcmFtZSwgYSBuYW1lIGZvciB0aGUKc2hhcGUgZmlsZSAod2l0aG91dCB0aGUgZmlsZSBleHRlbnNpb24pIGFuZCB0aGUgYGRyaXZlcmAsIGUuZy4KYHN0X3dyaXRlKG55YywibmV3X255YyIsZHJpdmVyPSJFU1JJIFNoYXBlZmlsZSIpYC4gVGhpcyB3aWxsIGNyZWF0ZSBhIGZvbGRlciBjb250YWluaW5nIHRoZSBmb3VyIG5ldyBmaWxlcy4KCgpgYGB7cn0Kc3Rfd3JpdGUobnljLCJuZXdfbnljIixkcml2ZXI9IkVTUkkgU2hhcGVmaWxlIikKYGBgCgoKIyMgQmFzaWMgQ2hvcm9wbGV0aCBNYXBwaW5nCgpXZSBub3cgdHVybiB0byB0aGUgY2hvcm9wbGV0aCBtYXBwaW5nIGZ1bmN0aW9uYWxpdHkgaW5jbHVkZWQgaW4gdGhlICoqdG1hcCoqIHBhY2thZ2UuClRoaXMgcGFja2FnZSBpcyBleHRyZW1lbHkgcG93ZXJmdWwsCmFuZCB0aGVyZSBhcmUgbWFueSBmZWF0dXJlcyB0aGF0IHdlIGRvIG5vdCBjb3Zlci4gRGV0YWlsZWQgaW5mb3JtYXRpb24gb24gKip0bWFwKiogZnVuY3Rpb25hbGl0eSBjYW4KYmUgZm91bmQgYXQgW3RtYXAgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RtYXAvdG1hcC5wZGYpLiBJbiBhZGRpdGlvbiwgc2V2ZXJhbApleHRlbnNpdmUgZXhhbXBsZXMgYXJlIGdpdmVuIGluIHRoZSBbY2hhcHRlciBvbiBtYXBwaW5nXShodHRwczovL2dlb2NvbXByLnJvYmlubG92ZWxhY2UubmV0L2Fkdi1tYXAuaHRtbCkgaW4gTG92ZWxhY2UsIE5vd29zYWQsCmFuZCBNdWVuY2hvdydzICpHZW9jb21wdXRhdGlvbiB3aXRoIFIqIGJvb2suCgojIyMgRGVmYXVsdCBzZXR0aW5ncwoKVGhlIHBhY2thZ2UgKip0bWFwKiogdXNlcyB0aGUgc2FtZSBsYXllcmVkCmxvZ2ljIGZvciBncmFwaGljcyBhcyAqKmdncGxvdCoqLiBUaGUgaW5pdGlhbCBjb21tYW5kIGlzIGB0bV9zaGFwZWAsIHdoaWNoIHNwZWNpZmllcyB0aGUgZ2VvZ3JhcGh5IHRvIHdoaWNoIHRoZQptYXBwaW5nIGlzIGFwcGxpZWQuIFRoaXMgaXMgZm9sbG93ZWQgYnkgYSBudW1iZXIgb2YgYHRtXypgIG9wdGlvbnMgdGhhdCBzZWxlY3QgdGhlIHR5cGUgb2YgbWFwCmFuZCBzZXZlcmFsIG9wdGlvbmFsIGN1c3RvbWl6YXRpb25zLgoKV2Ugc3RhcnQgd2l0aCBhIGJhc2ljIGNob3JvcGxldGggbWFwIG9mIHRoZSAqKnJlbnQyMDA4KioKdmFyaWFibGUuIAoKSW4gKip0bWFwKiogdGhlcmUgYXJlIHR3byB3YXlzIHRvIGNyZWF0ZSBhIGNob3JvcGxldGggbWFwLiBUaGUgc2ltcGxlc3Qgb25lCmlzIHRvIHVzZSB0aGUgYHRtX3BvbHlnb25zYCBjb21tYW5kIHdpdGggdGhlIHZhcmlhYmxlIG5hbWUgYXMgdGhlIGFyZ3VtZW50Cih1bmRlciB0aGUgaG9vZCwgdGhpcyBpcyB0aGUgdmFsdWUgZm9yIHRoZSAqKmNvbCoqIHBhcmFtZXRlciwgaS5lLiwgdGhlIGNvbG9yIG9mIHRoZSBwb2x5Z29ucykuIAoKSW4gb3VyIGV4YW1wbGUsIHRoZSBtYXAKaXMgY3JlYXRlZCB3aXRoIHR3byBjb21tYW5kcywgYHRtX3NoYXBlKG55YylgIGFuZCBgdG1fcG9seWdvbnMoInJlbnQyMDA4IilgIChub3RlIHRoZSBxdW90ZXMKaW4gInJlbnQyMDA4IiwgdW5saWtlIHdoYXQgd2UgZGlkIGZvciAqKmdncGxvdCoqKS4KClRoZSBjb2xvciBjb2RpbmcgY29ycmVzcG9uZHMgdG8gYHN0eWxlID0gInByZXR0eSJgLCAKd2hpY2ggaXMgdGhlIGRlZmF1bHQgd2hlbiB0aGUgY2xhc3NpZmljYXRpb24gYnJlYWtzIGFyZSBub3Qgc2V0IGV4cGxpY2l0bHksIGFuZCB0aGUgZGVmYXVsdAp2YWx1ZXMgYXJlIHVzZWQgKHNlZSB0aGUgZGlzY3Vzc2lvbiBvZiBtYXAgY2xhc3NpZmljYXRpb25zIGJlbG93KS4KCmBgYHtyfQp0bV9zaGFwZShueWMpICsKICB0bV9wb2x5Z29ucygicmVudDIwMDgiKQpgYGAKCkFzIGl0IHR1cm5zIG91dCwgdGhlIGB0bV9wb2x5Z29uc2AgY29tbWFuZCBpcyBhIHdyYXBwZXIgYXJvdW5kIHR3byBvdGhlciBmdW5jdGlvbnMsIGB0bV9maWxsYCBhbmQgYHRtX2JvcmRlcnNgLgpgdG1fZmlsbGAgY29udHJvbHMgdGhlIGNvbnRlbnRzIG9mIHRoZSBwb2x5Z29ucyAoY29sb3IsIGNsYXNzaWZpY2F0aW9uLCBldGMuKSwgd2hpbGUKYHRtX2JvcmRlcnNgIGRvZXMgdGhlIHNhbWUgZm9yIHRoZSBwb2x5Z29uIG91dGxpbmVzLgoKRm9yIGV4YW1wbGUsIHVzaW5nIHRoZSBzYW1lIHNoYXBlIChidXQgbm8gdmFyaWFibGUpLCB3aGUgb2J0YWluIHRoZSBvdXRsaW5lcyBvZiB0aGUKc3ViLWJvcm91Z2hzIGZyb20gdGhlIGB0bV9ib3JkZXJzKCApYCBjb21tYW5kLgoKYGBge3J9CnRtX3NoYXBlKG55YykgKwogIHRtX2JvcmRlcnMoKQpgYGAKClNpbWlsYXJseSwgd2Ugb2J0YWluIGEgY2hvcm9wbGV0aCBtYXAgd2l0aG91dCB0aGUgcG9seWdvbiBvdXRsaW5lcyB3aGVuIHdlIGp1c3QKdXNlIHRoZSBgdG1fZmlsbCgicmVudDIwMDgiKWAgY29tbWFuZC4KCmBgYHtyfQp0bV9zaGFwZShueWMpICsgCiAgdG1fZmlsbCgicmVudDIwMDgiKQpgYGAKCldoZW4gd2UgY29tYmluZSB0aGUgdHdvIGNvbW1hbmRzLCB3ZSBvYnRhaW4gdGhlIHNhbWUgbWFwIGFzIHdpdGggYHRtX3BvbHlnb25zYCAodGhpcwppbGx1c3RyYXRlcyBob3cgaW4gUiBvbmUgY2FuIG9mdGVuIG9idGFpbiB0aGUgc2FtZSByZXN1bHQgaW4gYSBudW1iZXIgb2YgZGlmZmVyZW50IHdheXMpLgoKYGBge3J9CnRtX3NoYXBlKG55YykgKwogIHRtX2ZpbGwoInJlbnQyMDA4IikgKwogIHRtX2JvcmRlcnMoKQpgYGAKCkFuIGV4dGVuc2l2ZSBzZXQgb2Ygb3B0aW9ucyBpcyBhdmFpbGFibGUgdG8gY3VzdG9taXplIHRoZSBhcHBlYXJhbmNlIG9mIHRoZSBtYXAuCkEgZnVsbCBsaXN0IGlzIGdpdmVuIGluIHRoZSBbZG9jdW1lbnRhdGlvbiBwYWdlXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvdG1hcC92ZXJzaW9ucy8yLjEtMS90b3BpY3MvdG1fZmlsbCkgZm9yIGB0bV9maWxsYC4KSW4gd2hhdCBmb2xsb3dzLCB3ZSBicmllZmx5IGNvbnNpZGVyIHRoZSBtb3N0IGNvbW1vbiBvbmVzLgoKIyMjIENvbG9yIHBhbGV0dGUKClRoZSByYW5nZSBvZiBjb2xvcnMgdXNlZCB0byBkZXBpY3QgdGhlIHNwYXRpYWwgZGlzdHJpYnV0aW9uIG9mIGEgdmFyaWFibGUgaXMgCmRldGVybWluZWQgYnkgdGhlICoqcGFsZXR0ZSoqLiBUaGUgcGFsZXR0ZSBpcyBhbiBhcmd1bWVudCB0byB0aGUgYHRtX2ZpbGxgIGZ1bmN0aW9uLgpTZXZlcmFsIGJ1aWx0LWluIHBhbGV0dGVzIGFyZSBjb250YWluZWQgaW4gKip0bWFwKiouIEZvciBleGFtcGxlLCB1c2luZyAKYHBhbGV0dGUgPSAiUmVkcyJgIChhZ2Fpbiwgbm90ZSB0aGUgcXVvdGVzKSB3b3VsZCB5aWVsZCB0aGUgZm9sbG93aW5nIG1hcCBmb3Igb3VyIGV4YW1wbGUuCgpgYGB7cn0KdG1fc2hhcGUobnljKSArCiAgdG1fZmlsbCgicmVudDIwMDgiLHBhbGV0dGU9IlJlZHMiKSArCiAgdG1fYm9yZGVycygpCmBgYAoKVW5kZXIgdGhlIGhvb2QsICoqIlJlZHMiKiogcmVmZXJzIHRvIG9uZSBvZiB0aGUgY29sb3Igc2NoZW1lcyBzdXBwb3J0ZWQgYnkgdGhlCioqUkNvbG9yQnJld2VyKiogcGFja2FnZSwgZGlzY3Vzc2VkIG5leHQuCgojIyMjIENvbG9yQnJld2VyCgpUaGUgcHJlZmVycmVkIGFwcHJvYWNoIHRvIHNlbGVjdCBhIGNvbG9yIHBhbGV0dGUgaXMgdG8gY2hvb3NlIG9uZSBvZiB0aGUgc2NoZW1lcyBjb250YWluZWQKaW4gdGhlICoqUkNvbG9yQnJld2VyKiogcGFja2FnZS4gVGhlc2UgYXJlIGJhc2VkIG9uIHRoZSByZXNlYXJjaCBvZiBjYXJ0b2dyYXBoZXIgQ3ludGhpYSBCcmV3ZXIKKFtzZWUgdGhlIGNvbG9yYnJld2VyMiB3ZWIgc2l0ZSBmb3IgZGV0YWlsc10oaHR0cDovL2NvbG9yYnJld2VyMi5vcmcpKS4gQ29sb3JCcmV3ZXIgbWFrZXMKYSBkaXN0aW5jdGlvbiBiZXR3ZWVuICpzZXF1ZW50aWFsKiBzY2FsZXMgKGZvciBhIHNjYWxlIHRoYXQgZ29lcyBmcm9tIGxvdyB0byBoaWdoKSwKKmRpdmVyZ2luZyogc2NhbGVzICh0byBoaWdobGlnaHQgaG93IHZhbHVlcyBkaWZmZXIgZnJvbSBhIGNlbnRyYWwgdGVuZGVuY3kpLCBhbmQKKnF1YWxpdGF0aXZlKiBzY2FsZXMgKGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMpLiBGb3IgZWFjaCBzY2FsZSwgYSBzZXJpZXMgb2Ygc2luZ2xlIGh1ZSBhbmQKbXVsdGktaHVlIHNjYWxlcyBhcmUgc3VnZ2VzdGVkLiBJbiB0aGUgKipSQ29sb3JCcmV3ZXIqKiBwYWNrYWdlLCB0aGVzZSBhcmUgcmVmZXJyZWQgdG8gYnkgYQpuYW1lIChlLmcuLCB0aGUgIlJlZHMiIHBhbGV0dGUgd2UgdXNlZCBhYm92ZSBpcyBhbiBleGFtcGxlKS4gVGhlIGZ1bGwgbGlzdCBpcyBjb250YWluZWQgaW4gdGhlIApbUkNvbG9yQnJld2VyIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9SQ29sb3JCcmV3ZXIvdmVyc2lvbnMvMS4xLTIvdG9waWNzL1JDb2xvckJyZXdlcikuCgpUaGVyZSBhcmUgdHdvIHZlcnkgdXNlZnVsIGNvbW1hbmRzIGluIHRoaXMgcGFja2FnZS4gT25lIHNldHMgYSBjb2xvciBwYWxldHRlIGJ5IHNwZWNpZnlpbmcgaXRzCm5hbWUgYW5kIHRoZSBudW1iZXIgb2YgZGVzaXJlZCBjYXRlZ29yaWVzLiBUaGUgcmVzdWx0IGlzIGEgY2hhcmFjdGVyIHZlY3RvciB3aXRoIHRoZSBoZXggY29kZXMgb2YgdGhlIApjb3JyZXNwb25kaW5nIGNvbG9ycy4KCkZvciBleGFtcGxlLCB3ZSBzZWxlY3QgYSBzZXF1ZW50aWFsIGNvbG9yIHNjaGVtZSBnb2luZyBmcm9tIGJsdWUgdG8gZ3JlZW4sIGFzICoqQnVHbioqLCBieQptZWFucyBvZiB0aGUgY29tbWFuZCBgYnJld2VyLnBhbGAsIHdpdGggdGhlIG51bWJlciBvZiBjYXRlZ29yaWVzICg2KSBhbmQgdGhlIHNjaGVtZSBhcyAKYXJndW1lbnRzLiBUaGUgcmVzdWx0aW5nIHZlY3RvciBjb250YWlucyB0aGUgSEVYIGNvZGVzIGZvciB0aGUgY29sb3JzLgoKYGBge3J9CnBhbCA8LSBicmV3ZXIucGFsKDYsIkJ1R24iKQpwYWwKYGBgCgpVc2luZyB0aGlzIHBhbGV0dGUgaW4gb3VyIG1hcCB5aWVsZHMgdGhlIGZvbGxvd2luZyByZXN1bHQuCgpgYGB7cn0KdG1fc2hhcGUobnljKSArCiAgdG1fZmlsbCgicmVudDIwMDgiLHBhbGV0dGU9cGFsKSArCiAgdG1fYm9yZGVycygpCmBgYAoKVGhlIGNvbW1hbmQgYGRpc3BsYXkuYnJld2VyLnBhbGAgYWxsb3dzIHVzIHRvIGV4cGxvcmUgZGlmZmVyZW50IGNvbG9yIHNjaGVtZXMgYmVmb3JlCmFwcGx5aW5nIHRoZW0gdG8gYSBtYXAuIENvbnRpbnVpbmcgd2l0aCBvdXIgIkJ1R24iIGV4YW1wbGUsIHRoYXQgd291bGQgYmUgaW52b2tlZAp3aXRoIGBkaXNwbGF5LmJyZXdlci5wYWwoNiwiQnVHbiIpYC4KCgpgYGB7cn0KZGlzcGxheS5icmV3ZXIucGFsKDYsIkJ1R24iKQpgYGAKCiMjIyBMZWdlbmQKClRoZXJlIGFyZSBtYW55IG9wdGlvbnMgdG8gY2hhbmdlIHRoZSBmb3JtYXR0aW5nIG9mIHRoZSBsZWdlbmQgZW50cmllcyB0aHJvdWdoIHRoZSAqKmxlZ2VuZC5mb3JtYXQqKgphcmd1bWVudC4gV2UgcmVmZXIgdG8gdGhlIGB0bV9maWxsYCBkb2N1bWVudGF0aW9uIGZvciBzcGVjaWZpYyBkZXRhaWxzLiAKCk9mdGVuLCB0aGUgYXV0b21hdGljIHRpdGxlIGZvciB0aGUgbGVnZW5kIGlzIG5vdCB0aGF0IGF0dHJhY3RpdmUsIHNpbmNlIGl0IGlzIHNpbXBseQp0aGUgdmFyaWFibGUgbmFtZS4gVGhpcyBjYW4gYmUgY3VzdG9taXplZCBieSBzZXR0aW5nIHRoZSAqKnRpdGxlKiogYXJndW1lbnQgdG8gYHRtX2ZpbGxgLgpGb3IgZXhhbXBsZSwga2VlcGluZyBhbGwgdGhlIG90aGVyIHNldHRpbmdzIHRvIHRoZSBkZWZhdWx0LCB3ZSBjaGFuZ2UgdGhlIGxlZ2VuZCB0aXRsZSB0bwoqKlJlbnQgaW4gMjAwOCoqIGJ5IHNwZWNpZnlpbmcgYHRpdGxlPSJSZW50IGluIDIwMDgiYCBhcyBhbiBvcHRpb24gdG8gYHRtX2ZpbGxgLgoKYGBge3J9CnRtX3NoYXBlKG55YykgKwogIHRtX2ZpbGwoInJlbnQyMDA4Iix0aXRsZT0iUmVudCBpbiAyMDA4IikgKwogIHRtX2JvcmRlcnMoKQpgYGAKCkFub3RoZXIgaW1wb3J0YW50CmFzcGVjdCBvZiB0aGUgbGVnZW5kIGlzIGl0cyBwb3NpdGlvbmluZy4gVGhpcyBpcyBoYW5kbGVkIHRocm91Z2ggdGhlIGB0bV9sYXlvdXRgIGZ1bmN0aW9uLgpUaGlzIGZ1bmN0aW9uIGhhcyBhIHZhc3QgbnVtYmVyIG9mIG9wdGlvbnMsIFthcyBkZXRhaWxlZCBpbiB0aGUgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3RtYXAvdmVyc2lvbnMvMi4xLTEvdG9waWNzL3RtX2xheW91dCkuClRoZXJlIGFyZSBhbHNvIHNwZWNpYWxpemVkIHN1YnNldHMgb2YgbGF5b3V0IGZ1bmN0aW9ucywgZm9jdXNlZCBvbiBzcGVjaWZpYyBhc3BlY3RzCm9mIHRoZSBtYXAsIHN1Y2ggYXMgYHRtX2xlZ2VuZGAsIGB0bV9zdHlsZWAgYW5kIGB0bV9mb3JtYXRgLiBXZSBpbGx1c3RyYXRlIHRoZSBwb3NpdGlvbmluZyBvZgp0aGUgbGVnZW5kLgoKVGhlIGRlZmF1bHQgaXMgdG8gcG9zaXRpb24gdGhlIGxlZ2VuZCBpbnNpZGUgdGhlIG1hcC4gT2Z0ZW4sIHRoaXMgZGVmYXVsdCBzb2x1dGlvbiBpcyBhcHByb3ByaWF0ZSwKYnV0IHNvbWV0aW1lcyBmdXJ0aGVyIGNvbnRyb2wgaXMgbmVlZGVkLiBUaGUgKipsZWdlbmQucG9zaXRpb24qKiBhcmd1bWVudCB0byB0aGUgYHRtX2xheW91dGAKZnVuY3Rpb24gdGFrZXMgYSB2ZWN0b3Igb2YgdHdvIHN0cmluZyB2YXJpYWJsZXMgdGhhdCBkZXRlcm1pbmUgYm90aCB0aGUgaG9yaXpvbnRhbApwb3NpdGlvbiAoImxlZnQiLCAicmlnaHQiLCBvciAiY2VudGVyIikgYW5kIHRoZSB2ZXJ0aWNhbCBwb3NpdGlvbiAoInRvcCIsICJib3R0b20iLApvciAiY2VudGVyIikuCgpGb3IgZXhhbXBsZSwgaWYgd2Ugd291bGQgd2FudCB0byBtb3ZlIHRoZSBsZWdlbmQgdG8gdGhlIGxvd2VyLXJpZ2h0IHBvc2l0aW9uIChjbGVhcmx5CmluZmVyaW9yIHRvIHRoZSBkZWZhdWx0IHNvbHV0aW9uKSwgd2Ugd291bGQgc3BlY2lmeSB0aGUgYGxlZ2VuZC5wb3NpdGlvbiA9IGMoInJpZ2h0IiwiYm90dG9tIilgCnRvIHRoZSBgdG1fbGF5b3V0YCBmdW5jdGlvbi4KCmBgYHtyfQp0bV9zaGFwZShueWMpICsKICB0bV9maWxsKCJyZW50MjAwOCIsdGl0bGU9IlJlbnQgaW4gMjAwOCIpICsKICB0bV9ib3JkZXJzKCkgKwogIHRtX2xheW91dChsZWdlbmQucG9zaXRpb24gPSBjKCJyaWdodCIsICJib3R0b20iKSkKYGBgCgoKVGhlcmUgaXMgYWxzbyB0aGUgb3B0aW9uIHRvIHBvc2l0aW9uIHRoZSBsZWdlbmQgb3V0c2lkZSB0aGUgZnJhbWUgb2YgdGhlIG1hcC4gVGhpcwppcyBhY2NvbXBsaXNoZWQgYnkgc2V0dGluZyAqKmxlZ2VuZC5vdXRzaWRlKiogdG8gVFJVRSAodGhlIGRlZmF1bHQgaXMgRkFMU0UpLCBhbmQKb3B0aW9uYWxseSBhbHNvIHNwZWNpZnkgaXRzIHBvc2l0aW9uIGJ5IG1lYW5zIG9mICoqbGVnZW5kLm91dHNpZGUucG9zaXRpb24qKi4KVGhlIGxhdHRlciBjYW4gdGFrZSB0aGUgdmFsdWVzICJ0b3AiLCAiYm90dG9tIiwgInJpZ2h0IiwgYW5kICJsZWZ0Ii4KCkZvciBleGFtcGxlLCB0byBwb3NpdGlvbiB0aGUgbGVnZW5kIG91dHNpZGUgYW5kIG9uIHRoZSByaWdodCwgd291bGQgYmUgYWNjb21wbGlzaGVkCmJ5IHBhc3NpbmcgdGhlIG9wdGlvbiBgbGVnZW5kLm91dHNpZGUgPSBUUlVFLCBsZWdlbmQub3V0c2lkZS5wb3NpdGlvbiA9ICJyaWdodCJgIHRvCnRoZSBgdG1fbGF5b3V0YCBmdW5jdGlvbi4KCgpgYGB7cn0KdG1fc2hhcGUobnljKSArCiAgdG1fZmlsbCgicmVudDIwMDgiLHRpdGxlPSJSZW50IGluIDIwMDgiKSArIAogIHRtX2JvcmRlcnMoKSArCiAgdG1fbGF5b3V0KGxlZ2VuZC5vdXRzaWRlID0gVFJVRSwgbGVnZW5kLm91dHNpZGUucG9zaXRpb24gPSAicmlnaHQiKQpgYGAKCldlIGNhbiBhbHNvIGN1c3RvbWl6ZSB0aGUgc2l6ZSBvZiB0aGUgbGVnZW5kLCBpdHMgYWxpZ25tZW50LCBmb250LCBldGMuIFdlIHJlZmVyCnRvIHRoZSBkb2N1bWVudGF0aW9uIGZvciBzcGVjaWZpY3MuCgpBIGZpbmFsIGludGVyZXN0aW5nIG9wdGlvbiBmb3IgdGhlIGxlZ2VuZCBpcyB0byBtYXRjaCBpdCB3aXRoIGEgaGlzdG9ncmFtCnRoYXQgdmlzdWFsaXplcyB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBlYWNoIG1hcCBjYXRlZ29yeS4KVGhpcyBpcyBhY2NvbXBsaXNoZWQgYnkgc2V0dGluZyBgbGVnZW5kLmhpc3Q9VFJVRWAgaW4gdGhlCmB0bV9maWxsYCBjb21tYW5kLiBGdXJ0aGVyIGN1c3RvbWl6YXRpb24gaXMgcG9zc2libGUsIGJ1dCBpcyBub3QgY292ZXJlZCBoZXJlLgoKYGBge3J9CnRtX3NoYXBlKG55YykgKwogIHRtX2ZpbGwoInJlbnQyMDA4Iix0aXRsZT0iUmVudCBpbiAyMDA4IixsZWdlbmQuaGlzdD1UUlVFKSArIAogIHRtX2JvcmRlcnMoKSArCiAgdG1fbGF5b3V0KGxlZ2VuZC5vdXRzaWRlID0gVFJVRSwgbGVnZW5kLm91dHNpZGUucG9zaXRpb24gPSAicmlnaHQiKQpgYGAKCgojIyMgVGl0bGUKCkFub3RoZXIgZnVuY3Rpb25hbGl0eSBvZiB0aGUgYHRtX2xheW91dGAgZnVuY3Rpb24gaXMgdG8gc2V0IGEgdGl0bGUgZm9yCnRoZSBtYXAsIGFuZCBzcGVjaWZ5IGl0cyBwb3NpdGlvbiwgc2l6ZSwgZXRjLiBGb3IgZXhhbXBsZSwgd2UgY2FuIApzZXQgYHRpdGxlID0gIlJlbnQgMjAwOCBOWUMgU3ViLUJvcm91Z2hzIiwgdGl0bGUuc2l6ZSA9IDAuOCwgdGl0bGUucG9zaXRpb24gPSBjKCJyaWdodCIsImJvdHRvbSIpYCAoZm9yIGRldGFpbHMsIHNlZSAKW3RoZSBgdG1fbGF5b3V0YCBkb2N1bWVudGF0aW9uXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvdG1hcC92ZXJzaW9ucy8yLjEtMS90b3BpY3MvdG1fbGF5b3V0KSkuCldlIG1hZGUgdGhlIGZvbnQgc2l6ZSBhIGJpdCBzbWFsbGVyICgwLjgpIGluIG9yZGVyIG5vdCB0byBvdmVyd2hlbG0gdGhlIG1hcCwgYW5kIHBvc2l0aW9uZWQgaXQgaW4gdGhlCmJvdHRvbSByaWdodC1oYW5kIGNvcm5lci4KCgpgYGB7cn0KdG1fc2hhcGUobnljKSArCiAgdG1fZmlsbCgicmVudDIwMDgiLHRpdGxlPSJSZW50IGluIDIwMDgiKSAgKwogIHRtX2JvcmRlcnMoKSArCiAgdG1fbGF5b3V0KHRpdGxlID0gIlJlbnQgMjAwOCBOWUMgU3ViLUJvcm91Z2hzIiwgdGl0bGUuc2l6ZSA9IDAuOCwgdGl0bGUucG9zaXRpb24gPSBjKCJyaWdodCIsImJvdHRvbSIpKQpgYGAKCkZpbmFsbHksIGluIG9yZGVyIHRvIGhhdmUgYSB0aXRsZSBhcHBlYXIgb24gdG9wIChvciBvbiB0aGUgYm90dG9tKSBvZiB0aGUgbWFwLCByYXRoZXIgdGhhbiBpbnNpZGUKKHRoZSBkZWZhdWx0KSwgd2UgbmVlZCB0byBzZXQgdGhlICoqbWFpbi50aXRsZSoqCmFyZ3VtZW50IG9mIHRoZSBgdG1fbGF5b3V0YCBmdW5jdGlvbi4gRm9yIGV4YW1wbGUsIApgbWFpbi50aXRsZSA9ICJSZW50IDIwMDggTllDIFN1Yi1Cb3JvdWdocyIsIHRpdGxlLnNpemUgPSAxLjUsbWFpbi50aXRsZS5wb3NpdGlvbj0iY2VudGVyImAgaGFzCnRoZSB0aXRsZSBjZW50ZXJlZCBvbiB0b3AgYW5kIHdpdGggdGhlIHNpemUgc2V0IHRvIDEuNSB0byBoYXZlIGEgbGFyZ2VyIGZvbnQpLgoKYGBge3J9CnRtX3NoYXBlKG55YykgKwogIHRtX2ZpbGwoInJlbnQyMDA4Iix0aXRsZT0iUmVudCBpbiAyMDA4IikgICsKICB0bV9ib3JkZXJzKCkgKwogIHRtX2xheW91dChtYWluLnRpdGxlID0gIlJlbnQgMjAwOCBOWUMgU3ViLUJvcm91Z2hzIiwgdGl0bGUuc2l6ZSA9IDEuNSxtYWluLnRpdGxlLnBvc2l0aW9uPSJjZW50ZXIiKQpgYGAKCiMjIyBJbnRlcmFjdGl2ZSBiYXNlIG1hcAoKVGhlIGRlZmF1bHQgKiptb2RlKiogaW4gKip0bWFwKiogaXMgKipwbG90KiosIGkuZS4sIHRoZSBzdGFuZGFyZCBwbG90dGluZyBlbnZpcm9ubWVudCBpbgpSIHRvIGRyYXcgYSBtYXAgb24gdGhlIHNjcmVlbiBvciB0byBhIGRldmljZSAoZS5nLiwgYSBwZGYgZmlsZSkuIFRoZXJlIGlzIGFsc28gYW4KaW50ZXJhY3RpdmUgbW9kZSwgd2hpY2ggYnVpbGRzIHVwb24gKipsZWFmbGV0KiogdG8gYWRkIGEgYmFzZW1hcCBhbmQgaW50ZXJhY3Qgd2l0aCB0aGUKbWFwIHRocm91Z2ggem9vbWluZyBhbmQgaWRlbnRpZmljYXRpb24gb2YgaW5kaXZpZHVhbCBvYnNlcnZhdGlvbnMuIFRoZSBpbnRlcmFjdGl2ZQptb2RlIGlzIHJlZmVycmVkIHRvIGFzICoqdmlldyoqLiBXZSBzd2l0Y2ggYmV0d2VlbiBtb2RlcyBieSBtZWFucyBvZiB0aGUgYHRtYXBfbW9kZWAKY29tbWFuZC4gRm9yIGV4YW1wbGUsIHRvIHN3aXRjaCB0byB0aGUgaW50ZXJhY3RpdmUgbW9kZSwgd2Ugc2V0IGB0bWFwX21vZGUoInZpZXciKWAuCgpgYGB7cn0KdG1hcF9tb2RlKCJ2aWV3IikKYGBgCgpUaGVyZSBhcmUgYSBudW1iZXIgb2YgZGlmZmVyZW50IHdheXMgaW4gd2hpY2ggYSBiYXNlbWFwIGNhbiBiZSBhZGRlZC4gVGhlIGN1cnJlbnQgcHJlZmVycmVkCmFwcHJvYWNoIGlzIHRocm91Z2ggdGhlIGB0bV9iYXNlbWFwYCBjb21tYW5kLiBUaGlzIHRha2VzIHR3byBpbXBvcnRhbnQgYXJndW1lbnRzLiBPbmUgaXMKdGhlIG5hbWUgb2YgdGhlIHNlcnZlci4gQSBudW1iZXIgb2YgYmFzZW1hcHMgYXJlIHN1cHBvcnRlZCAoYW5kIG5vIGRvdWJ0LCB0aGF0IG51bWJlciB3aWxsCmluY3JlYXNlIG92ZXIgdGltZSksIGJ1dCB3ZSB3aWxsIGlsbHVzdHJhdGUgdGhlICoqT3BlblN0cmVldE1hcCoqIG9wdGlvbi4gQSBzZWNvbmQgYXJndW1lbnQKaXMgdGhlIGRlZ3JlZSBvZiB0cmFuc3BhcmVuY3ksIG9yICoqYWxwaGEqKiBsZXZlbC4gVGhpcyBjYW4gYWxzbyBiZSBzZXQgZm9yIHRoZSBtYXAKaXRzZWxmIHRocm91Z2ggYHRtX2ZpbGxgLiBJbiBwcmFjdGljZSwgb25lIHR5cGljYWxseSBuZWVkcyB0byBleHBlcmltZW50IGEgYml0IHRvIGZpbmQgdGhlCnJpZ2h0IGJhbGFuY2UgYmV0d2VlbiB0aGUgaW5mb3JtYXRpb24gaW4gdGhlIGNob3JvcGxldGggbWFwIGFuZCB0aGUgYmFja2dyb3VuZC4KCkluIHRoZSBleGFtcGxlIGJlbG93LCB3ZSBzZXQgdGhlICoqYWxwaGEqKiBsZXZlbCBmb3IgdGhlIG1haW4gbWFwIHRvIDAuNyBpbiAgYHRtX2ZpbGxgLCBhbmQKZm9yIHRoZSBiYXNlIGxheWVyIHRvIDAuNSBpbiBgdG1fYmFzZW1hcGAsIGFzIGB0bV9iYXNlbWFwKHNlcnZlcj0iT3BlblN0cmVldE1hcCIsYWxwaGE9MC41KWAuCgpXaGVuIHdlIG1vdmUgdGhlIHBvaW50ZXIgb3ZlciB0aGUgcG9seWdvbnMsIHRoZWlyCklEIHZhbHVlIGlzIHNob3duLiBBIGNsaWNrIG9uIGEgbG9jYXRpb24gYWxzbyBnaXZlcyB0aGUgdmFsdWUgZm9yIHRoZSB2YXJpYWJsZQp0aGF0IGlzIGJlaW5nIG1hcHBlZC4gWm9vbWluZyBhbmQgcGFubmluZyBhcmUgc3VwcG9ydGVkIGFzIHdlbGwuIEluIGFkZGl0aW9uLCBpdAppcyBwb3NzaWJsZSB0byBzdGFjayBzZXZlcmFsIGxheWVycywgYnV0IHdlIHdvbid0IHB1cnN1ZSB0aGF0IGhlcmUuCgpgYGB7cn0KdG1fc2hhcGUobnljKSArCiAgdG1fZmlsbCgicmVudDIwMDgiLHRpdGxlPSJSZW50IGluIDIwMDgiLGFscGhhPTAuNykgKwogIHRtX2JvcmRlcnMoKSArCiAgdG1fYmFzZW1hcChzZXJ2ZXI9Ik9wZW5TdHJlZXRNYXAiLGFscGhhPTAuNSkKYGBgCgpCZWZvcmUgd2UgcHJvY2VlZCwgd2UgdHVybiB0aGUgbW9kZSBiYWNrIHRvICoqcGxvdCoqLgoKYGBge3J9CnRtYXBfbW9kZSgicGxvdCIpCmBgYAoKIyMgQ29tbW9uIE1hcCBDbGFzc2lmaWNhdGlvbnMKClRoZSBzdGF0aXN0aWNhbCBhc3BlY3RzIG9mIGEgY2hvcm9wbGV0aCBtYXAgYXJlIGV4cHJlc3NlZCB0aHJvdWdoIHRoZQptYXAgY2xhc3NpZmljYXRpb24gdGhhdCBpcyB1c2VkIHRvIGNvbnZlcnQgb2JzZXJ2YXRpb25zIGZvciBhIGNvbnRpbnVvdXMgdmFyaWFibGUgaW50bwphIHNtYWxsIG51bWJlciBvZiBiaW5zLCBzaW1pbGFyIHRvIHdoYXQgaXMgdGhlIGNhc2UgZm9yIGEgaGlzdG9ncmFtLiBEaWZmZXJlbnQgCmNsYXNzaWZpY2F0aW9ucyByZXZlYWwgZGlmZmVyZW50IGFzcGVjdHMgb2YgdGhlIHNwYXRpYWwgZGlzdHJpYnV0aW9uIG9mIHRoZSB2YXJpYWJsZS4KCkluICoqdG1hcCoqLCB0aGUgY2xhc3NpZmljYXRpb24gaXMgc2VsZWN0ZWQgYnkgbWVhbnMgb2YgdGhlICoqc3R5bGUqKiBvcHRpb24gaW4KYHRtX2ZpbGxgLiBTbyBmYXIsIHdlIGhhdmUgdXNlZCB0aGUgZGVmYXVsdCwgd2hpY2ggaXMgc2V0IHRvICoqcHJldHR5KiouIFRoZSBsYXR0ZXIgaXMKYSBbYmFzZSBSIGZ1bmN0aW9uXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvYmFzZS92ZXJzaW9ucy8zLjUuMS90b3BpY3MvcHJldHR5KSB0bwpjb21wdXRlIGEgc2VxdWVuY2Ugb2Ygcm91Z2hseSBlcXVhbGx5IHNwYWNlZCB2YWx1ZXMuIAoKVGhlcmUgYXJlIG1hbnkKY2xhc3NpZmljYXRpb24gdHlwZXMgYXZhaWxhYmxlIGluICoqdG1hcCoqLCB3aGljaCBlYWNoIGNvcnJlc3BvbmQgdG8gZWl0aGVyIGEgYmFzZSBSIGZ1bmN0aW9uLCBvciB0byBhCmN1c3RvbSBleHByZXNzaW9uIGNvbnRhaW5lZCBpbiB0aGUgCltjbGFzc0ludGVydmFscyBmdW5jdGlvbmFsaXR5XShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvY2xhc3NJbnQvdmVyc2lvbnMvMC4xLTcvdG9waWNzL2NsYXNzSW50ZXJ2YWxzKS4KCkluIHRoaXMgc2VjdGlvbiwgd2UgcmV2aWV3IHRoZSBxdWFudGlsZSBtYXAsIG5hdHVyYWwgYnJlYWtzIG1hcCwgYW5kIGVxdWFsIGludGVydmFscyBtYXAsIHRoZSBtb3N0CmNvbW1vbmx5IHVzZWQgbWFwIGNsYXNzaWZpY2F0aW9ucy4KV2UgY29udGludWUgdG8gdXNlIHRoZSBzYW1lIGV4YW1wbGUgZm9yICoqcmVudDIwMDgqKi4KCgojIyMgUXVhbnRpbGUgbWFwCgpBIHF1YW50aWxlIG1hcCBzaG93cyB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgYSB2YXJpYWJsZSBieSBjbGFzc2lmeWluZyB0aGUKb2JzZXJ2YXRpb25zIGludG8gY2F0ZWdvcmllcyBhY2NvcmRpbmcgdG8gdGhlIHF1YW50aWxlIHRvIHdoaWNoIHRoZXkgYmVsb3cuIEZvciAKZXhhbXBsZSwgZm9yIGEgKnF1aW50aWxlKiBtYXAgKHdpdGggNSBjYXRlZ29yaWVzKSwgdGhlIG9ic2VydmF0aW9ucyBhcmUgc29ydGVkLAphbmQgdGhlIGZpcnN0IDIwJSBhcmUgZ2l2ZW4gdGhlIGNvbG9yIGZvciB0aGUgZmlyc3QgcXVpbnRpbGUsIHRoZSBzZWNvbmQKZm9yIHRoZSBzZWNvbmQgcXVpbnRpbGUsIGV0Yy4KCkEgcXVhbnRpbGUgbWFwIGlzIG9idGFpbmVkIGJ5IHNldHRpbmcgYHN0eWxlPSJxdWFudGlsZSJgIGluIHRoZSBgdG1fZmlsbGAgY29tbWFuZC4KVGhlIG51bWJlciBvZiBjYXRlZ29yaWVzIGlzIHRha2VuIGZyb20gdGhlICoqbioqIGFyZ3VtZW50LCB3aGljaCBoYXMgYSBkZWZhdWx0IHZhbHVlIG9mIDUuClNvLCBmb3IgZXhhbXBsZSwgdXNpbmcgdGhpcyBkZWZhdWx0IHdpbGwgeWllbGQgYSBxdWludGlsZSBtYXAgd2l0aCBmaXZlIGNhdGVnb3JpZXMKKHdlIHNwZWNpZnkgdGhlIHR5cGUgb2YgbWFwIGluIHRoZSB0aXRsZSB0aHJvdWdoIGB0bV9sYXlvdXRgKSwgZS5nLiwgYHRpdGxlPSAiUXVpbnRpbGUgTWFwImAsCndpdGggYHRpdGxlLnNpemUgPSAwLjlgIGZvciBzb21ld2hhdCBiZXR0ZXIgcHJvcG9ydGlvbnMuCgpgYGB7cn0KdG1fc2hhcGUobnljKSArCiAgdG1fZmlsbCgicmVudDIwMDgiLHRpdGxlPSJSZW50IGluIDIwMDgiLHN0eWxlPSJxdWFudGlsZSIpICArCiAgdG1fYm9yZGVycygpICsKICB0bV9sYXlvdXQodGl0bGUgPSAiUXVpbnRpbGUgTWFwIiwgdGl0bGUuc2l6ZSA9IDAuOSwgdGl0bGUucG9zaXRpb24gPSBjKCJyaWdodCIsImJvdHRvbSIpKQpgYGAKCkZvciBhIHF1YXJ0aWxlIG1hcCwgd2l0aCBmb3VyIGNhdGVnb3JpZXMsIHdlIG5lZWQgdG8gc3BlY2lmeSB0aGUgYXJndW1lbnQgYG49NGAKZXhwbGljaXRseSAoZml2ZSBpcyB0aGUgZGVmYXVsdCwgc28gd2UgZGlkIG5vdCBoYXZlIHRvIHNwZWNpZnkgdGhhdCBpbiB0aGUgcXVpbnRpbGUKbWFwKS4gVGhlCnJlc3Qgb2YgdGhlIGNvbW1hbmRzIGFyZSB0aGUgc2FtZSBhcyBiZWZvcmUgKGJ1dCBub3cgd2l0aCBgdGl0bGUgPSAiUXVhcnRpbGUgTWFwImAuCgoKYGBge3J9CnRtX3NoYXBlKG55YykgKwogIHRtX2ZpbGwoInJlbnQyMDA4Iix0aXRsZT0iUmVudCBpbiAyMDA4IixuPTQsc3R5bGU9InF1YW50aWxlIikgICsKICB0bV9ib3JkZXJzKCkgKwogIHRtX2xheW91dCh0aXRsZSA9ICJRdWFydGlsZSBNYXAiLCB0aXRsZS5zaXplID0gMC45LCB0aXRsZS5wb3NpdGlvbiA9IGMoInJpZ2h0IiwiYm90dG9tIikpCmBgYAoKIyMjIEVxdWFsIGludGVydmFscyBtYXAKCkFuIGVxdWFsIGludGVydmFscyBtYXAgaXMgdGhlIGNvdW50ZXJwYXJ0IG9mIGEgaGlzdG9ncmFtLiAgWW91IHNwZWNpZnkgdGhlIG51bWJlcgpvZiAqYmlucyogdGhhdCBlYWNoIGNvdmVyIHRoZSBzYW1lIHJhbmdlIGZyb20gYmVnaW5uaW5nIHRvIGVuZCBmb3IgdGhlIHZhcmlhYmxlLApoZW5jZSB0aGUgbmFtZSAqZXF1YWwgaW50ZXJ2YWxzKi4gRm9yIGV4YW1wbGUsIHdlIGNhbiBjaGVjayB0aGUgcmFuZ2UgZm9yCioqcmVudDIwMDgqKiB1c2luZyB0aGUgYHN1bW1hcnlgIGNvbW1hbmQgKHJlbWVtYmVyIHRoZSBkYXRhIGZyYW1lIGFuZCB0aGUgJCBzeW1ib2wpLgoKYGBge3J9CnN1bW1hcnkobnljJHJlbnQyMDA4KQpgYGAKClRoZSBtaW5pbXVtIGlzIDAgKHdoaWNoIGlzIHN1c3BpY2lvdXMsIGJ1dCB3ZSBpZ25vcmUgdGhhdCBhc3BlY3QgZm9yIG5vdykgYW5kIHRoZSBtYXhpbXVtCmlzIDI5MDAuIElmIHdlIHdhbnRlZCA0IGludGVydmFscywgZWFjaCB3b3VsZCBjb3ZlciBhIHJhbmdlIG9mIDI5MDAvNCA9IDcyNS4gU28sIHRoZQpmaXJzdCBpbnRlcnZhbCB3b3VsZCBiZSBmcm9tIDAgdG8gNzI1LCB0aGUgc2Vjb25kIGZyb20gNzI1IHRvIDE0NTAsIGV0Yy4KCkFnYWluLCB3ZSBzcGVjaWZ5IHRoaXMgdHlwZSBvZiBjbGFzc2lmaWNhdGlvbiBieSBzZXR0aW5nIHRoZSBwcm9wZXIgKnN0eWxlKiBhcyBgc3R5bGUgPSAiZXF1YWwiYCwKYW5kIGJ5IHNldHRpbmcgdGhlIG51bWJlciBvZiBiaW5zIGFzIGBuID0gNGAuIFdlIGFkanVzdCB0aGUgdGl0bGUgdG8gZ2l2ZSBgdGl0bGU9IkVxdWFsIEludGVydmFscyBNYXAiYC4KTm90ZSB0aGUgZGlmZmVyZW5jZSB3aXRoIHRoZSBxdWFudGlsZSBtYXAuCgoKYGBge3J9CnRtX3NoYXBlKG55YykgKwogIHRtX2ZpbGwoInJlbnQyMDA4Iix0aXRsZT0iUmVudCBpbiAyMDA4IixuPTQsc3R5bGU9ImVxdWFsIikgICsKICB0bV9ib3JkZXJzKCkgKwogIHRtX2xheW91dCh0aXRsZSA9ICJFcXVhbCBJbnRlcnZhbHMgTWFwIiwgdGl0bGUuc2l6ZSA9IDAuOSwgdGl0bGUucG9zaXRpb24gPSBjKCJyaWdodCIsImJvdHRvbSIpKQpgYGAKCiMjIyBOYXR1cmFsIGJyZWFrcyBtYXAKCkEgbmF0dXJhbCBicmVha3MsIG9yICpKZW5rcyogY2xhc3NpZmljYXRpb24gaXMgYmFzZWQgb24gYW4gYWxnb3JpdGhtIHRoYXQgY3JlYXRlcwpncm91cHMgb2Ygb2JzZXJ2YXRpb25zIHN1Y2ggdGhhdCB0aGV5IGFyZSBtb3N0IHNpbWlsYXIgd2l0aGluIHRoZSBncm91cCwgYW5kCmRpc3NpbWlsYXIgYmV0d2VlbiBncm91cHMuIFRoaXMgaXMgYSAqY2x1c3RlcmluZyogbG9naWMgZm9yIHRoZSB2YXJpYWJsZSB3ZSB3YW50IHRvCm1hcC4gVGhlIGFsZ29yaXRobSBpcyBmYWlybHkgY29tcGxleCwgYnV0IGl0IGRvZXMgcmVzdWx0IGluIChtb3N0bHkpIGludHVpdGl2ZQpicmVha3MgaW4gdGhlIGRhdGEuCgpBIG5hdHVyYWwgYnJlYWtzIG1hcCBpcyBvYnRhaW5lZCBieSBzcGVjaWZ5aW5nIHRoZSAqKnN0eWxlID0gImplbmtzIioqIGluIGB0bV9maWxsYC4gQWxsCnRoZSBvdGhlciBvcHRpb25zIGFyZSBhcyBiZWZvcmUuIFdlIGlsbHVzdHJhdGUgdGhpcyBmb3Igc2l4IGNhdGVnb3JpZXMsCndpdGggYG49NmAsIGJ1dCB1c2luZyB0aGUgc3RhbmRhcmQgcGFsZXR0ZSAoZmVlbCBmcmVlIHRvIGN1c3RvbWl6ZSkuIFdlIHNldAp0aGUgYHRpdGxlID0gIk5hdHVyYWwgQnJlYWtzIE1hcCJgLgoKTm90ZSBob3cgdGhlIGNsYXNzaWZpY2F0aW9uIG5pY2VseSBzZXBhcmF0ZXMgdGhlIG9ic2VydmF0aW9ucyB3aXRoIHJlbnQgemVybywgYXMgCndlbGwgYXMgdGhlIGhpZ2hlc3QgcmVudCBuZWlnaGJvcmhvb2RzIGluIE1hbmhhdHRhbi4KCmBgYHtyfQp0bV9zaGFwZShueWMpICsKICB0bV9maWxsKCJyZW50MjAwOCIsdGl0bGU9IlJlbnQgaW4gMjAwOCIsbj02LHN0eWxlPSJqZW5rcyIpICArCiAgdG1fYm9yZGVycygpICsKICB0bV9sYXlvdXQodGl0bGUgPSAiTmF0dXJhbCBCcmVha3MgTWFwIiwgdGl0bGUuc2l6ZSA9IDAuOSwgdGl0bGUucG9zaXRpb24gPSBjKCJyaWdodCIsImJvdHRvbSIpKQpgYGAKCiMjIyBDdXN0b20gYnJlYWtzCgpGb3IgYWxsIHRoZSBidWlsdC1pbiBzdHlsZXMsIHRoZSBjYXRlZ29yeSBicmVha3MgYXJlIGNvbXB1dGVkIGludGVybmFsbHkuIEluIG1vc3QKY2FzZXMgdGhpcyBpcyBmaW5lLCBidXQgd2hhdCBpZiB3ZSB3YW50IHRvIGNvbXBhcmUgdGhlIHJlbnQgb3ZlciBkaWZmZXJlbnQgeWVhcnM/ClRoZSBjbGFzc2lmaWNhdGlvbnMgYXJlIGFsbCAqcmVsYXRpdmUqIHRvIHRoZSB2YWx1ZXMgb2JzZXJ2ZWQgaW4gdGhhdCB5ZWFyLiBGb3IKZXhhbXBsZSwgZm9yIGEgcXVhcnRpbGUgbWFwLCB3ZSBjb3VsZCB0cmFjayB3aGV0aGVyIGFuIG9ic2VydmF0aW9ucyAobmVpZ2hib3Job29kKQptb3ZlcyBmcm9tIG9uZSBxdWFydGlsZSB0byBhbm90aGVyLCBidXQgdGhhdCBkb2Vzbid0IHRlbGwgdXMgYW55dGhpbmcgYWJvdXQKdGhlIGFjdHVhbCByZW50LiBJdCB3b3VsZCBiZSBtb3JlIGluZm9ybWF0aXZlIHRvIHNldCBmaXhlZCBicmVhayBwb2ludHMgZm9yIGFsbCB0aGUKeWVhcnMgdG8gaWxsdXN0cmF0ZSBob3cgdGhlIHJlbnQgY2hhbmdlcyBvdmVyIHRpbWUuCgpJbiBvcmRlciB0bwpvdmVycmlkZSB0aGUgc3RhbmRhcmQgZGVmYXVsdHMsIHRoZSBicmVha3BvaW50cyBjYW4gYmUgc2V0IGV4cGxpY2l0bHkgYnkgbWVhbnMgb2YgdGhlIGBicmVha3NgCmFyZ3VtZW50IHRvIHRoZSBgdG1fZmlsbGAgZnVuY3Rpb24uIEZvciBleGFtcGxlLCBzYXkgd2Ugd2FudGVkIHRvIHRyYWNrIHRoZSByZW50IGJldHdlZW4KMjAwMiwgMjAwNSBhbmQgMjAwOCAoaS5lLiwgKipyZW50MjAwMioqLCAqKnJlbnQyMDA1KiosICBhbmQgKipyZW50MjAwOCoqKS4gRmlyc3QsIAp3ZSBjaGVjayB0aGUgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyB3ZSBjb21wdXRlZCBlYXJsaWVyIHdpdGggdGhlIGBzdW1tYXJ5YCBjb21tYW5kLgpGcm9tIHRoYXQsIHdlIHdpbGwgdXNlIHRoZSBxdWFydGlsZXMgZm9yIDIwMDUgYXMgdGhlIGJyZWFrIHBvaW50cyAoanVzdCB0byBpbGx1c3RyYXRlCmhvdyB0byBnbyBhYm91dCB0aGlzKS4gVGhlc2UgYXJlIDg5OCwgOTkwIGFuZCAxMTAwIChyb3VuZGVkKS4gV2UgYWxzbyBuZWVkIHRvIHNldAphIG1pbmltdW0gKDApIGFuZCBhIG1heGltdW0sIHRoZSBsYXJnZXN0IHZhbHVlIGluIDIwMDgsIG9yIDI5MDAuCgpJbiAqKnRtYXAqKiwgdGhlIGN1c3RvbSBicmVhayBwb2ludHMgYXJlIHNldCBpbiB0aGUgYGJyZWFrc2Agb3B0aW9uLiBUaGV5IG11c3QgaW5jbHVkZQphIG1pbmltdW0gYW5kIGEgbWF4aW11bS4gU28sIGluIG91ciBleGFtcGxlLCB3ZSBuZWVkIHRvIHNldCB0aGUgYnJlYWtwb2ludHMKdG8gMCAobWluaW11bSksIDg5OCwgOTkwLCAxMTAwLCBhbmQgMjkwMCAobWF4aW11bSkuIFRoaXMgaXMgcGFzc2VkIGFzIGEgdmVjdG9yLAp1c2luZyB0aGUgYGMoIClgIG5vdGF0aW9uLCBhcyBgYnJlYWtzID0gYygwLDg5OCw5OTAsMTEwMCwyOTAwKWAuIE9yLCBhbHRlcm5hdGl2ZWx5LAp3ZSBmaXJzdCBzZXQgYSB2ZWN0b3IgYXMgYGN1c3RvbS5icmVha3MgPSBjKDAsODk4LDk5MCwxMTAwLDI5MDApYCwgYW5kIHRoZW4gcGFzcyB0aGF0CnZlY3RvciB0byB0aGUgYGJyZWFrc2Agb3B0aW9uLgoKYGBge3J9CmN1c3RvbS5icmVha3MgPC0gYygwLDg5OCw5OTAsMTEwMCwyOTAwKQpgYGAKCk5vdywgd2UgY2FuIG1ha2Ugb3VyIHRocmVlIG1hcHMgdXNpbmcgdGhlIGN1c3RvbSBjbGFzc2lmaWNhdGlvbi4gV2UgdXNlIHRoZSBgcGFsZXR0ZT0iWWxPckJyImAgZnJvbQoqKlJDb2xvckJyZXdlcioqLiBGaXJzdCwgZm9yIDIwMDIgKHdpdGggYW4gYXBwcm9wcmlhdGUgdGl0bGUsIHJlc2l6ZWQgdG8gMC44KQoKCmBgYHtyfQp0bV9zaGFwZShueWMpICsKICB0bV9maWxsKCJyZW50MjAwMiIsdGl0bGU9Ik1lZGlhbiBSZW50IixicmVha3M9Y3VzdG9tLmJyZWFrcyxwYWxldHRlPSJZbE9yQnIiKSAgKwogIHRtX2JvcmRlcnMoKSArCiAgdG1fbGF5b3V0KHRpdGxlID0gIkN1c3RvbSBCcmVha3MgTWFwIC0gMjAwMiIsIHRpdGxlLnNpemUgPSAwLjksIHRpdGxlLnBvc2l0aW9uID0gYygicmlnaHQiLCJib3R0b20iKSkKYGBgCgpGb3IgMjAwNToKCmBgYHtyfQp0bV9zaGFwZShueWMpICsKICB0bV9maWxsKCJyZW50MjAwNSIsdGl0bGU9Ik1lZGlhbiBSZW50IixicmVha3M9Y3VzdG9tLmJyZWFrcyxwYWxldHRlPSJZbE9yQnIiKSAgKwogIHRtX2JvcmRlcnMoKSArCiAgdG1fbGF5b3V0KHRpdGxlID0gIkN1c3RvbSBCcmVha3MgTWFwIC0gMjAwNSIsIHRpdGxlLnNpemUgPSAwLjksIHRpdGxlLnBvc2l0aW9uID0gYygicmlnaHQiLCJib3R0b20iKSkKYGBgCgpBbmQgZm9yIDIwMDg6CgpgYGB7cn0KdG1fc2hhcGUobnljKSArCiAgdG1fZmlsbCgicmVudDIwMDgiLHRpdGxlPSJNZWRpYW4gUmVudCIsYnJlYWtzPWN1c3RvbS5icmVha3MscGFsZXR0ZT0iWWxPckJyIikgICsKICB0bV9ib3JkZXJzKCkgKwogIHRtX2xheW91dCh0aXRsZSA9ICJDdXN0b20gQnJlYWtzIE1hcCAtIDIwMDgiLCB0aXRsZS5zaXplID0gMC45LCB0aXRsZS5wb3NpdGlvbiA9IGMoInJpZ2h0IiwiYm90dG9tIikpCmBgYAoKVGhlIG1hcHMgY2xlYXJseSBzaG93IGhvdyB0aGUgcmVudCBpbmNyZWFzZXMgc3ByZWFkIHRocm91Z2hvdXQgdGhlIG5laWdoYm9yaG9vZHMuCgoKIyMgQ29uZGl0aW9uYWwgTWFwCgpBIGNvbmRpdGlvbmFsIG1hcCwgb3IgZmFjZXQgbWFwLCBvciBzbWFsbCBtdWx0aXBsZXMsIGlzIGNyZWF0ZWQgYnkgdGhlIGB0bV9mYWNldHNgIGNvbW1hbmQuClRoaXMgbGFyZ2VseSBmb2xsb3dzIHRoZSBsb2dpYyBvZiB0aGUgYGZhY2V0X2dyaWRgIGNvbW1hbmQgaW4gKipnZ3Bsb3QqKiB0aGF0IHdlIGNvdmVyZWQgZWFybGllci4gCkFuIGV4dGVuc2l2ZSBzZXQgb2Ygb3B0aW9ucyBpcyBhdmFpbGFibGUgdG8gY3VzdG9taXplIHRoZSBmYWNldCBtYXBzLiBBbiBpbi1kZXB0aApjb3ZlcmFnZSBvZiBhbGwgdGhlIHN1YnRsZXRpZXMgaXMgYmV5b25kIG91ciBzY29wZSAKKGRldGFpbHMgY2FuIGJlIGZvdW5kIG9uIHRoZSBbYHRtX2ZhY2V0c2AgZG9jdW1lbnRhdGlvbiBwYWdlXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvdG1hcC92ZXJzaW9ucy8yLjEtMS90b3BpY3MvdG1fZmFjZXRzKSkKCldlIHdpbGwgdXNlIHRoZSBzYW1lIGNvbmRpdGlvbmluZyB2YXJpYWJsZXMgYXMgYmVmb3JlICh3aGljaCB3ZSByZWNyZWF0ZWQgYWJvdmUpLCBpLmUuLAoqKm1hbmJyb254KiogYW5kICoqY3V0Lmhoc2l6KiouClRoZXNlIHR3byBmYWN0b3IgdmFyaWFibGVzIGFyZSB1c2VkIGFzIGNvbmRpdGlvbmluZyB2YXJpYWJsZXMgaW4gdGhlIGBieWAgYXJndW1lbnQKb2YgdGhlIGB0bV9mYWNldHNgIGNvbW1hbmQuIFRoZSBmaXJzdCB2YXJpYWJsZSBjb25kaXRpb25zIHRoZSByb3dzIChpLmUuLCBpcyB0aGUgeS1heGlzKSwKdGhlIHNlY29uZCB2YXJpYWJsZSBjb25kaXRpb25zIHRoZSBjb2x1bW5zIChpLmUuLCBpcyB0aGUgeC1heGlzKSwgaW4gdGhlIHNhbWUgd2F5CmFzIHdlIHNhdyBmb3IgYGZhY2V0X2dyaWRgIGluICoqZ2dwbG90KiouCgpXZSBpbGx1c3RyYXRlIHRoaXMgd2l0aCB0aGUgKipyZW50MjAwOCoqIHZhcmlhYmxlLCB1c2luZyB0aGUgZGVmYXVsdCBtYXAgdHlwZS4gVHdvIGltcG9ydGFudApvcHRpb25zIHRoYXQgYWZmZWN0IHRoZSBsb29rIG9mIHRoZSBjb25kaXRpb25hbCBtYXAgYXJlIGBmcmVlLmNvb3Jkc2AgYW5kIGBkcm9wLnVuaXRzYC4KVGhlIGRlZmF1bHQgaXMgdGhhdCBlYWNoIGZhY2V0IG1hcCBoYXMgaXRzIG93biBzY2FsaW5nLCBmb2N1c2VkIG9uIHRoZSByZWxldmFudCBvYnNlcnZhdGlvbnMuClR5cGljYWxseSwgb25lIHdhbnRzIHRoZSBzYW1lIHNwYXRpYWwgbGF5b3V0IGluIGVhY2ggZmFjZXQsIGkuZS4sIGFsbCB0aGUgc3ViLWJvcm91Z2hzIGluIG91cgpleGFtcGxlLiBUaGlzIGlzIGVuc3VyZWQgYnkgc2V0dGluZyBgZnJlZS5jb29yZHM9RkFMU0VgLiBJbiBhZGRpdGlvbiwgd2Ugd2FudCBhbGwgdGhlIApzcGF0aWFsIHVuaXRzIHRvIGJlIHNob3duIGluIGVhY2ggZmFjZXQgKHRoZSBkZWZhdWx0IGlzIHRvIG9ubHkgc2hvdyB0aG9zZSB0aGF0IG1hdGNoIHRoZQpjb25kaXRpb25zKS4gVGhpcyBpcyBhY2NvbXBsaXNoZWQgYnkgc2V0dGluZyBgZHJvcC51bml0cz1GQUxTRWAuIAoKT25lICJnb3RjaGEiIGlzIHRoYXQgKiptYW5icm9ueCoqIGlzIG5vdCBhICpmYWN0b3IqIHdoaWNoIGlzIHdoYXQgKip0bWFwKiogcmVxdWlyZXMgZm9yCnRoZSBjb25kaXRpb25pbmcgdmFyaWFibGUuIFNvLCBmaXJzdCB3ZSBuZWVkIHRvIGNyZWF0ZSBhbiBhZGRpdGlvbmFsIHZhcmlhYmxlIHRoYXQKaXMgYSBmYWN0b3IsIGJhc2VkIG9uIHRoZSB2YWx1ZXMgb2YgKiptYW5icm9ueCoqLCBpLmUuLCB1c2luZyBgbnljJG1iIDwtIGFzLmZhY3RvcihueWMkbWFuYnJvbngpYAoKYGBge3J9Cm55YyRtYiA8LSBhcy5mYWN0b3IobnljJG1hbmJyb254KQpgYGAKCk5vdywgd2UgY2FuIHByb2NlZWQgd2l0aCB0aGUKY29tbWFuZCBgdG1fZmFjZXRzKGJ5ID0gYygiY3V0Lmhoc2l6IiwgIm1iIiksZnJlZS5jb29yZHMgPSBGQUxTRSxkcm9wLnVuaXRzPUZBTFNFKWAuCldlIGp1c3Qga2VlcCB0aGUgZGVmYXVsdHMgKHRpdGxlcywgbGFiZWxzLCBldGMuIGNhbiBiZSBhZGRlZCBsYXRlcikuCgoKYGBge3J9CnRtX3NoYXBlKG55YykgKwogIHRtX2ZpbGwoInJlbnQyMDA4IikgKwogIHRtX2JvcmRlcnMoKSArCiAgdG1fZmFjZXRzKGJ5ID0gYygiY3V0Lmhoc2l6IiwgIm1iIiksZnJlZS5jb29yZHMgPSBGQUxTRSxkcm9wLnVuaXRzPUZBTFNFKQpgYGAKCgoKCgoKCgoKCgo=