In this section we will look at some libraries and commands that allow us to process vector data in R and perform a few exemplary operations.
Libraries needed:
Data needed: RSpatialDataOps.zip
1. Attribute Join
An attribute join brings tabular data into a geographic context. It refers to the process of joining data in tabular format to data in a format that holds the geometries (polygon, line, or point).
If you have done attribute joins of shapefiles in GIS software like ArcGIS or QGis you know that you need a unique identifier in both the attribute table of the shapefile and the table to be joined.
In order to combine a Spatial*Dataframe
with another table (which would be a dataframe in R) we do exactly the same. We have a Spatial*Dataframe
that contains the geometries and an identifying index variable for each. We combine it with a dataframe, that includes the same index variable with additional variables.
Attribute joins in R can very simply be done with the merge
command. Since an sf
object is just an extension of the data frame, it works exactly as we would expect.
The sp
package has a merge
command which extends the one from the base package and works with Spatial* objects.
Assume we have:
- a shape file named worldCountries and
- a dataframe called countryData with the attribute data to join
where:
- “id-number” is the colum that contains the unique identifier in worldCountries, and
- “countryID” is the column that contains the unique identifier in countryData.
We would then say:
# for sf
worldCountries <- merge(worldCountries, countryData, by.x = "id-number", by.y = "countryID"
# for sp
require(sp) # just to make sure it is loaded
worldCountries <- merge(worldCountries, countryData, by.x = "id-number", by.y = "countryID")
If the names of the ID columns match, we can omit them.
Other R packages also may have convenience functions to do attribute joins. For example, the geo_join()
command from the tigris
package provides a convenient way to merge a data frame to a spatial data frame.
Exercise 1
Download and unzip RSpatialDataOps.zip
Load the CSV table PhillyEducAttainment.csv
(in the nhgisPhilly_csv
folder) into a dataframe in R and name it edu
.
Read the PhillyTotalPopHHinc
shapefile into an object named philly_sp
or philly_sf
, depending on which route you go.
Check out the column names of philly_sp/philly_sf
and of edu
to find the column with the unique identifier. (If you are interested in what those data are, you can take a look at the codebook PhillyEducAttainment_nhgis2010_tract_codebook.txt)
Join the edu
data frame with philly_sp/philly_sf
using merge
as described above. Use the names()
command to see if the join was successful.
Try before you peek!
edu <- read.csv("~/Desktop/RSpatialDataOps/nhgisPhilly_csv/PhillyEducAttainment.csv")
names(edu)
## sf ##
library(sf)
philly_sf <- st_read("~/Desktop/RSpatialDataOps/Philly/")
names(philly_sf)
philly_sf_merged <- merge(philly, edu)
names(philly_sf_merged) # note the geometry column
## sp ##
library(rgdal)
library(sp)
philly_sp <- readOGR("/Users/cengel/Desktop/RSpatialDataOps/Philly/", "PhillyTotalPopHHinc")
# this is sp::merge() -- what happens if we use base::merge()?
philly_sp_merged <- merge(philly_sp, edu)
names(philly_sp_merged) # no geometry column here
2. Reprojecting
Not unfrequently you may have to reproject spatial objects that you perhaps have acquired from differnet sources and that you need to be in the same Coordinate Reference System (CRS). Both sf
and sp
packages have a simple function for this.
The sp
package has a function called spTransform()
that will do this for you. The function takes as a minimum the following two arguments:
- the
Spatial*
object to reproject
- a CRS object (created using the
CRS()
command), with the new projection definition
If for, example, we have an object called world_countries
and we want to reproject this into a new projection my_new_projection
, we would say:
my_new_projection <- CRS("definition of projection goes here as string")
spTransform(world_countries, my_new_projection)
The perhaps trickiest part here is to determine the definition of the projection, which needs to be a character string in proj4 format. You can look it up online. For example for UTM zone 33N (EPSG:32633) the string would be:
+proj=utm +zone=33 +ellps=WGS84 +datum=WGS84 +units=m +no_defs
You can retrieve the CRS from an existing Spatial*
object with the proj4string()
command.
The sf
package equivalent is - you guessed it! - st_transform()
. The function takes the following two arguments:
- the
sf
object to reproject
- the CRS, which can either be a string as above, or an integer, if you know the EPSG code.
Note that you can transform any object of class sf
, sfc
or sfg
.
To retrieve the CRS of an sf
object use st_crs()
Exercise 2
From the files downloaded earlier read the PhillyHomicides
shapefile into R and name it ph_homicides_sf
or ph_homicides_sp
.
What is the CRS of philly_*
? What is the CRS of ph_homicides_*
?
Reproject ph_homicides_*
so it matches the projection of philly_*
and assign it to a new object called ph_homicides_aea
.
Use range()
and coordinates()
/ st_coorinates()
to compare the coordinates before and after repojection.
Try before you peek!
## sf ##
ph_homicides_sf <- st_read("~/Desktop/RSpatialDataOps/PhillyCrime/")
st_crs(philly_sf)
st_crs(ph_homicides_sf)
ph_homicides_aea_sf <- st_transform(ph_homicides_sf, st_crs(philly_sf))
range(st_coordinates(ph_homicides_aea_sf))
range(st_coordinates(ph_homicides_sf))
## sp ##
ph_homicides_sp <- readOGR("/Users/cengel/Desktop/RSpatialDataOps/PhillyCrime/", "PhillyHomicides")
proj4string(philly_sp)
proj4string(ph_homicides_sp)
ph_homicides_aea_sp <- spTransform(ph_homicides_sp, CRS(proj4string(philly_sp)))
range(coordinates(ph_homicides_aea_sp))
range(coordinates(ph_homicides_sp))
If you plotted the sp
object with
par(mfrow=c(1,2), cex=0.7)
plot(ph_homic, pch=20, axes=TRUE)
plot(ph_homic_aea, pch=20, axes=TRUE)
you should see something like this:
3. Spatial aggregation: Points in Polygons
For the next exercise we want to calculate the homicide ratio for each census tract in Philadelphia as
homicides per tract / total population per tract.
For this we need to count all the homicides for each census tract in Philadelphia. To achieve this this we join the points of homicide incidence to the census tract polygon. You might be familiar with this operation from other GIS packages.
For sp
objects we can use the aggregate()
function. Here are the arguments that it needs:
- the
SpatialPointDataframe
with the homicide incidents as point locations,
- the
SpatialPolygonDataframe
with the census tract polygons to aggregate on, and
- an aggregate function. Since we are interested in counting the points (i.e. the rows of all the points that belong to a certain polygon), we can use
length
(of the respective vectors of the aggregated data).
Exercise 3
Count homicides per census tract. Use the OBJECTID
field from ph_homicides_aea*
for homicide incidents and philly_*
to aggregate on and save the result as ph_homicides_agg
. Use length
as aggregate function. (Look up ?sp::aggregate
if you need help.)
Out of couriosity try to use ph_homicides
for homicide incidents. What happens?
What type of object is ph_homicides_agg
?
Does it have an attribute table and if so, what does it contain?
How might you go about calculating the homicide ratio (i.e. normalized over the total population) per census tract?
Try before you peek!
ph_homicides_agg_sp <- aggregate(x = ph_homicides_aea_sp["OBJECTID"], by = philly_sp, FUN = length)
# make sure you understand this error message:
aggregate(x = ph_homicides, by = philly_sp, FUN = length)
class(ph_homicides_agg)
names(ph_homicides_agg)
head(ph_homicides_agg)
ph_homicides_agg_sp$hom_ratio <- ph_homicides_agg_sp$OBJECTID/philly_sp$totalPop
hist(ph_homicides_agg_sp$hom_ratio, nclass=100)
There might be other instances where we don’t want to aggregate, but might only want to know which polygon a point falls into. In that case we can use over()
. In fact, the aggregate()
function used above makes use of over()
. See https://cran.r-project.org/web/packages/sp/vignettes/over.pdf for more details on the over-methods. point.in.poly()
from the spatialEco
package intersects point and polygons and adds polygon attributes to points. There is also point.in.polygon()
from the sp
package which tests if a point or set of points fall in a given polygon.
For sf
objects we need to add one more step. We first use st_within()
to determine the polygon which points falls into. We can then use the result to aggregate by.
point_in_poly <- st_within(ph_homicides_aea_sf, philly_sf) # determine which poly each point falls into
pp <- as.numeric(as.character(point_in_poly)) # we need a vector
ph_homicides_agg_sf <- aggregate(ph_homicides_aea_sf["OBJECTID"], by = pp, length)
hist(unlist(ph_homicides_agg_sf$OBJECTID)/philly_sf$totalPop, nclass=100)
4. Select Polygons by Location
For the next example our goal is to select all Philadelphia census tracts within a range of 2 kilometers from the city center.
Think about this for a moment – what might be the steps you’d follow?
## How about:
# 1. Get the census tract polygons.
# 2. Find the Philadelphia city center coordinates.
# 3. Create a buffer around the city center point.
# 4. Select all census tract polygons that intersect with the center buffer
Spatial operations with sp
In order to perform those operations on an sp
object we will need to make use of an additional another library, called rgeos
. Make sure you have it loaded.
Exercise 4
Get the census tract polygons.
We got those.
We will reuse philly_sp
for the census tract polygons.
Find the Philadelphia city center coordinates.
Ok. I will tell you:
Lat is 39.95258 and Lon is -75.16522. This is in WGS84.
With this information, create a SpatialPoints
object named philly_ctr
.
coords <- data.frame(x = -75.16522, y = 39.95258) # set the coordinates
prj <- CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs") # the projection string for WGS84
philly_ctr <- SpatialPoints(coords, proj4string = prj) # create the spatialPoints
Create a buffer around the city center point.
Here is where we will use the gBuffer()
function from the rgeos
package. For this purpose we will need to provide two arguments: the sp object and the width of the buffer, which is assumed to be in map units. The function returns a SpatialPolygons
object to you with the buffer - name it philly_buf
.
So your command would look something like
philly_buf <- gBuffer(the_spatial_point_object, width = a_number_here)
Now – before you create this buffer, think about what you need to do to philly_ctr
before you proceed.
library(rgeos)
philly_ctr_aea <- spTransform(philly_ctr, CRS(proj4string(philly_sp))) # reproject!!
philly_buf <- gBuffer(philly_ctr_aea, width=2000) # create buffer around center
- Select all census tract polygons that intersect with the center buffer
We will use the gIntersects()
function from the rgeos
package for this. The function tests if two geometries (let’s name them spgeom1 and spgeom2) have points in common or not. gIntersects
returns TRUE if spgeom1 and spgeom2 have at least one point in common.
Here is where we determine if the census tracts fall within the buffer. In addition to our two sp objects (philly_buf
and philly_sp
) we need to provide one more argument, byid
. It determines if the function should be applied across ids (TRUE) or the entire object (FALSE) for spgeom1 and spgeom2. The default setting is FALSE. Since we want to compare every single census tract polygon in our philly_sp
object we need to set it to TRUE.
What class of object does gIntersects()
return and what is its structure?
How can you use it to select the desired polygons?
philly_buf_intersects <- gIntersects (philly_buf, philly_sp, byid=TRUE) # determine which census tracts intersect with the buffer
class(philly_buf_intersects)
# subset
philly_sel <- philly_sp[as.vector(philly_buf_intersects),]
- Plot philly, the selected polygons and the buffer. Below you can see it all put together.
philly_sp <- readOGR("/Users/cengel/Desktop/RSpatialDataOps/Philly/", "PhillyTotalPopHHinc", verbose = F)
coords <- data.frame(x = -75.16522, y = 39.95258)
philly_ctr <- SpatialPoints(coords, proj4string = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"))
philly_ctr_aea <- spTransform(philly_ctr, CRS(proj4string(philly_sp)))
philly_buf <- gBuffer(philly_ctr_aea, width=2000)
philly_buf_intersects <- gIntersects (philly_buf, philly_sp, byid=TRUE)
philly_sel <- philly_sp[as.vector(philly_buf_intersects),]
plot (philly_sp, border="#aaaaaa")
plot (philly_sel, add=T, col="red")
plot (philly_buf, add=T, lwd = 2)
Spatial operations with sf
To give you a sense of how this might be done using the sf
package we will reproduce here the same example as above.
For the spatial operations we can recur to the suite of geometric operations that come with the sf
package , in particular we will use st_buffer()
and st_intersects()
library(sf)
philly_sf <- st_read("~/Desktop/RSpatialDataOps/Philly/", quiet = T)
# make a simple feature point with CRS
philly_ctr_sfc <- st_sfc(st_point(c(-75.16522, 39.95258)), crs = 4326)
# reproject
philly_ctr_aea_sf <- st_transform(philly_ctr_sfc, st_crs(philly_sf))
# buffer
philly_buf_sf <- st_buffer(philly_ctr_aea_sf, 2000)
# intersection
philly_buf_intersects <- st_intersects(philly_buf_sf, philly_sf)
# subsetting
philly_sel_sf <- philly_sf[unlist(philly_buf_intersects),]
# plot
plot(st_geometry(philly_sf), border="#aaaaaa")
plot(st_geometry(philly_sel_sf), add=T, col="red")
plot(st_geometry(philly_buf_sf), add=T, lwd = 2)
6. sp
- sf
comparison
join attributes |
sp::merge() |
base::merge() |
reproject |
spTransform() |
st_transform() |
retrieve (or assign) CRS |
proj4string() |
st_crs() |
count points in polygons |
over() |
st_within and aggregate() |
buffer |
rgeos::gBuffer() (separate package) |
st_buffer() |
select by location |
g* functions from rgeos |
geos functions in sf |
LS0tCnRpdGxlOiAiT3BlcmF0aW9ucyB3aXRoIFNwYXRpYWwgVmVjdG9yIERhdGEgaW4gUiIKYXV0aG9yOiAiY2xhdWRpYSBhIGVuZ2VsIgpkYXRlOiAiTGFzdCB1cGRhdGVkOiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBmaWdfY2FwdGlvbjogbm8KICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDQKLS0tCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIyBsaWJyYXJpZXMgbmVlZGVkIGZvciBSIGNvZGUgZXhhbXBsZXMKbGlicmFyeShzcCkKbGlicmFyeShyZ2RhbCkKbGlicmFyeShyZ2VvcykKbGlicmFyeShzZikKYGBgCioqKgoKSW4gdGhpcyBzZWN0aW9uIHdlIHdpbGwgbG9vayBhdCBzb21lIGxpYnJhcmllcyBhbmQgY29tbWFuZHMgdGhhdCBhbGxvdyB1cyB0byBwcm9jZXNzIHZlY3RvciBkYXRhIGluIFIgYW5kIHBlcmZvcm0gYSBmZXcgZXhlbXBsYXJ5IG9wZXJhdGlvbnMuCgpMaWJyYXJpZXMgbmVlZGVkOgoKKiBgc3BgCiogYHJnZGFsYAoqIGByZ2Vvc2AgCiogYHNmYAoKRGF0YSBuZWVkZWQ6CltgUlNwYXRpYWxEYXRhT3BzLnppcGBdKGh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvbnJjODlhZ3RpaXdmMG0zL1JTcGF0aWFsRGF0YU9wcy56aXA/ZGw9MSkKCiMgMS4gQXR0cmlidXRlIEpvaW4KCkFuIGF0dHJpYnV0ZSBqb2luIGJyaW5ncyB0YWJ1bGFyIGRhdGEgaW50byBhIGdlb2dyYXBoaWMgY29udGV4dC4gSXQgcmVmZXJzIHRvIHRoZSBwcm9jZXNzIG9mIGpvaW5pbmcgZGF0YSBpbiB0YWJ1bGFyIGZvcm1hdCB0byBkYXRhIGluIGEgZm9ybWF0IHRoYXQgaG9sZHMgdGhlIGdlb21ldHJpZXMgKHBvbHlnb24sIGxpbmUsIG9yIHBvaW50KS4gCgpJZiB5b3UgaGF2ZSBkb25lIGF0dHJpYnV0ZSBqb2lucyBvZiBzaGFwZWZpbGVzIGluIEdJUyBzb2Z0d2FyZSBsaWtlIF9BcmNHSVNfIG9yIF9RR2lzXyB5b3Uga25vdyB0aGF0IHlvdSBuZWVkIGEgX191bmlxdWUgaWRlbnRpZmllcl9fIGluIGJvdGggdGhlIGF0dHJpYnV0ZSB0YWJsZSBvZiB0aGUgc2hhcGVmaWxlIGFuZCB0aGUgdGFibGUgdG8gYmUgam9pbmVkLiAKCkluIG9yZGVyIHRvIGNvbWJpbmUgYSBgU3BhdGlhbCpEYXRhZnJhbWVgIHdpdGggYW5vdGhlciB0YWJsZSAod2hpY2ggd291bGQgYmUgYSBkYXRhZnJhbWUgaW4gUikgd2UgZG8gZXhhY3RseSB0aGUgc2FtZS4gV2UgaGF2ZSBhIGBTcGF0aWFsKkRhdGFmcmFtZWBbXjFdIHRoYXQgY29udGFpbnMgdGhlIGdlb21ldHJpZXMgYW5kIGFuIGlkZW50aWZ5aW5nIGluZGV4IHZhcmlhYmxlIGZvciBlYWNoLiBXZSBjb21iaW5lIGl0IHdpdGggYSBkYXRhZnJhbWUsIHRoYXQgaW5jbHVkZXMgdGhlIHNhbWUgaW5kZXggdmFyaWFibGUgd2l0aCBhZGRpdGlvbmFsIHZhcmlhYmxlcy4KClteMV06IFBlciB0aGUgW0VTUkkgc3BlY2lmaWNhdGlvbl0oaHR0cDovL3d3dy5lc3JpLmNvbS9saWJyYXJ5L3doaXRlcGFwZXJzL3BkZnMvc2hhcGVmaWxlLnBkZikgYSBzaGFwZWZpbGUgYWx3YXlzIGhhcyBhbiBhdHRyaWJ1dGUgdGFibGUsIHNvIHdoZW4gd2UgcmVhZCBpdCBpbnRvIFIgd2l0aCB0aGUgYHJlYWRPR1JgIGNvbW1hbmQgZnJvbSB0aGUgYHNwYCBwYWNrYWdlIGl0IGF1dG9tYXRpY2FsbHkgYmVjb21lcyBhIGBTcGF0aWFsKkRhdGFmcmFtZWAgYW5kIHRoZSBhdHRyaWJ1dGUgdGFibGUgYmVjb21lcyB0aGUgZGF0YWZyYW1lLgoKIVtBdHRyaWJ1dGUgSm9pbiBvZiBjb3VudHJ5RGF0YSB0YWJsZSB0byB3b3JsZENvdW50cmllcyB1c2luZyB1bmlxdWUgSUQgdmFyaWFibGVzXShpbWFnZXMvYXR0ckpvaW4ucG5nKQoKQXR0cmlidXRlIGpvaW5zIGluIFIgY2FuIHZlcnkgc2ltcGx5IGJlIGRvbmUgd2l0aCB0aGUgYG1lcmdlYCBjb21tYW5kLiBTaW5jZSBhbiBgc2ZgIG9iamVjdCBpcyBqdXN0IGFuIGV4dGVuc2lvbiBvZiB0aGUgZGF0YSBmcmFtZSwgaXQgd29ya3MgZXhhY3RseSBhcyB3ZSB3b3VsZCBleHBlY3QuCgpUaGUgYHNwYCBwYWNrYWdlIGhhcyBhIGBtZXJnZWAgY29tbWFuZCB3aGljaCBleHRlbmRzIHRoZSBvbmUgZnJvbSB0aGUgYmFzZSBwYWNrYWdlIGFuZCB3b3JrcyB3aXRoIFNwYXRpYWwqIG9iamVjdHMuCgpBc3N1bWUgd2UgaGF2ZToKCiogYSBzaGFwZSBmaWxlIG5hbWVkIF93b3JsZENvdW50cmllc18gYW5kCiogYSBkYXRhZnJhbWUgY2FsbGVkIF9jb3VudHJ5RGF0YV8gd2l0aCB0aGUgYXR0cmlidXRlIGRhdGEgdG8gam9pbgoKd2hlcmU6CgoqIF8iaWQtbnVtYmVyIl8gaXMgdGhlIGNvbHVtIHRoYXQgY29udGFpbnMgdGhlIHVuaXF1ZSBpZGVudGlmaWVyIGluIF93b3JsZENvdW50cmllc18sIGFuZCAKKiBfImNvdW50cnlJRCJfIGlzIHRoZSBjb2x1bW4gdGhhdCBjb250YWlucyB0aGUgdW5pcXVlIGlkZW50aWZpZXIgaW4gX2NvdW50cnlEYXRhXy4gCgpXZSB3b3VsZCB0aGVuIHNheToKCmBgYAojIGZvciBzZgp3b3JsZENvdW50cmllcyA8LSBtZXJnZSh3b3JsZENvdW50cmllcywgY291bnRyeURhdGEsIGJ5LnggPSAiaWQtbnVtYmVyIiwgYnkueSA9ICJjb3VudHJ5SUQiCgojIGZvciBzcApyZXF1aXJlKHNwKSAjIGp1c3QgdG8gbWFrZSBzdXJlIGl0IGlzIGxvYWRlZAp3b3JsZENvdW50cmllcyA8LSBtZXJnZSh3b3JsZENvdW50cmllcywgY291bnRyeURhdGEsIGJ5LnggPSAiaWQtbnVtYmVyIiwgYnkueSA9ICJjb3VudHJ5SUQiKQpgYGAKSWYgdGhlIG5hbWVzIG9mIHRoZSBJRCBjb2x1bW5zIG1hdGNoLCB3ZSBjYW4gb21pdCB0aGVtLgoKT3RoZXIgUiBwYWNrYWdlcyBhbHNvIG1heSBoYXZlIGNvbnZlbmllbmNlIGZ1bmN0aW9ucyB0byBkbyBhdHRyaWJ1dGUgam9pbnMuIEZvciBleGFtcGxlLCB0aGUgYGdlb19qb2luKClgIGNvbW1hbmQgZnJvbSB0aGUgW2B0aWdyaXNgIHBhY2thZ2VdKGh0dHBzOi8vQ1JBTi5SLXByb2plY3Qub3JnL3BhY2thZ2U9dGlncmlzKSBwcm92aWRlcyBhIGNvbnZlbmllbnQgd2F5IHRvIG1lcmdlIGEgZGF0YSBmcmFtZSB0byBhIHNwYXRpYWwgZGF0YSBmcmFtZS4KCioqKgojIyBFeGVyY2lzZSAxCgojLiBEb3dubG9hZCBhbmQgdW56aXAgW2BSU3BhdGlhbERhdGFPcHMuemlwYF0oaHR0cHM6Ly93d3cuZHJvcGJveC5jb20vcy9ucmM4OWFndGlpd2YwbTMvUlNwYXRpYWxEYXRhT3BzLnppcD9kbD0xKQoKIy4gTG9hZCB0aGUgQ1NWIHRhYmxlIGBQaGlsbHlFZHVjQXR0YWlubWVudC5jc3ZgIChpbiB0aGUgYG5oZ2lzUGhpbGx5X2NzdmAgZm9sZGVyKSBpbnRvIGEgZGF0YWZyYW1lIGluIFIgYW5kIG5hbWUgaXQgYGVkdWAuCgojLiBSZWFkIHRoZSBgUGhpbGx5VG90YWxQb3BISGluY2Agc2hhcGVmaWxlIGludG8gYW4gb2JqZWN0IG5hbWVkIGBwaGlsbHlfc3BgIG9yIGBwaGlsbHlfc2ZgLCBkZXBlbmRpbmcgb24gd2hpY2ggcm91dGUgeW91IGdvLiAKCiMuIENoZWNrIG91dCB0aGUgY29sdW1uIG5hbWVzIG9mIGBwaGlsbHlfc3AvcGhpbGx5X3NmYCBhbmQgb2YgYGVkdWAgdG8gZmluZCB0aGUgY29sdW1uIHdpdGggdGhlIHVuaXF1ZSBpZGVudGlmaWVyLiAoSWYgeW91IGFyZSBpbnRlcmVzdGVkIGluIHdoYXQgdGhvc2UgZGF0YSBhcmUsIHlvdSBjYW4gdGFrZSBhIGxvb2sgYXQgdGhlIGNvZGVib29rIF9QaGlsbHlFZHVjQXR0YWlubWVudF9uaGdpczIwMTBfdHJhY3RfY29kZWJvb2sudHh0XykKCiMuIEpvaW4gdGhlIGBlZHVgIGRhdGEgZnJhbWUgd2l0aCBgcGhpbGx5X3NwL3BoaWxseV9zZmAgdXNpbmcgYG1lcmdlYCBhcyBkZXNjcmliZWQgYWJvdmUuIFVzZSB0aGUgYG5hbWVzKClgIGNvbW1hbmQgdG8gc2VlIGlmIHRoZSBqb2luIHdhcyBzdWNjZXNzZnVsLgoKPiBUcnkgYmVmb3JlIHlvdSBwZWVrIQoKYGBge3IgZXZhbD1GQUxTRX0KZWR1IDwtIHJlYWQuY3N2KCJ+L0Rlc2t0b3AvUlNwYXRpYWxEYXRhT3BzL25oZ2lzUGhpbGx5X2Nzdi9QaGlsbHlFZHVjQXR0YWlubWVudC5jc3YiKQpuYW1lcyhlZHUpCgojIyBzZiAjIwpsaWJyYXJ5KHNmKQpwaGlsbHlfc2YgPC0gc3RfcmVhZCgifi9EZXNrdG9wL1JTcGF0aWFsRGF0YU9wcy9QaGlsbHkvIikKbmFtZXMocGhpbGx5X3NmKQoKcGhpbGx5X3NmX21lcmdlZCA8LSBtZXJnZShwaGlsbHksIGVkdSkKbmFtZXMocGhpbGx5X3NmX21lcmdlZCkgIyBub3RlIHRoZSBnZW9tZXRyeSBjb2x1bW4KCiMjIHNwICMjCmxpYnJhcnkocmdkYWwpCmxpYnJhcnkoc3ApCnBoaWxseV9zcCA8LSByZWFkT0dSKCIvVXNlcnMvY2VuZ2VsL0Rlc2t0b3AvUlNwYXRpYWxEYXRhT3BzL1BoaWxseS8iLCAiUGhpbGx5VG90YWxQb3BISGluYyIpIAoKIyB0aGlzIGlzIHNwOjptZXJnZSgpIC0tIHdoYXQgaGFwcGVucyBpZiB3ZSB1c2UgYmFzZTo6bWVyZ2UoKT8KcGhpbGx5X3NwX21lcmdlZCA8LSBtZXJnZShwaGlsbHlfc3AsIGVkdSkgCgpuYW1lcyhwaGlsbHlfc3BfbWVyZ2VkKSAjIG5vIGdlb21ldHJ5IGNvbHVtbiBoZXJlCmBgYAoKKioqCgoKIyAyLiBSZXByb2plY3RpbmcKCk5vdCB1bmZyZXF1ZW50bHkgeW91IG1heSBoYXZlIHRvIHJlcHJvamVjdCBzcGF0aWFsIG9iamVjdHMgdGhhdCB5b3UgcGVyaGFwcyBoYXZlIGFjcXVpcmVkIGZyb20gZGlmZmVybmV0IHNvdXJjZXMgYW5kIHRoYXQgeW91IG5lZWQgdG8gYmUgaW4gdGhlIHNhbWUgQ29vcmRpbmF0ZSBSZWZlcmVuY2UgU3lzdGVtIChDUlMpLiBCb3RoIGBzZmAgYW5kIGBzcGAgcGFja2FnZXMgaGF2ZSBhIHNpbXBsZSBmdW5jdGlvbiBmb3IgdGhpcy4KClRoZSBgc3BgIHBhY2thZ2UgaGFzIGEgZnVuY3Rpb24gY2FsbGVkIGBzcFRyYW5zZm9ybSgpYCB0aGF0IHdpbGwgZG8gdGhpcyBmb3IgeW91LiBUaGUgZnVuY3Rpb24gdGFrZXMgYXMgYSBtaW5pbXVtIHRoZSBmb2xsb3dpbmcgdHdvIGFyZ3VtZW50czoKCiogdGhlIGBTcGF0aWFsKmAgb2JqZWN0IHRvIHJlcHJvamVjdAoqIGEgX19DUlMgb2JqZWN0X18gKGNyZWF0ZWQgdXNpbmcgdGhlIGBDUlMoKWAgY29tbWFuZCksIHdpdGggdGhlIG5ldyBwcm9qZWN0aW9uIGRlZmluaXRpb24KCklmIGZvciwgZXhhbXBsZSwgd2UgaGF2ZSBhbiBvYmplY3QgY2FsbGVkIGB3b3JsZF9jb3VudHJpZXNgIGFuZCB3ZSB3YW50IHRvIHJlcHJvamVjdCB0aGlzIGludG8gYSBuZXcgcHJvamVjdGlvbiBgbXlfbmV3X3Byb2plY3Rpb25gLCB3ZSB3b3VsZCBzYXk6CgpgYGAKbXlfbmV3X3Byb2plY3Rpb24gPC0gQ1JTKCJkZWZpbml0aW9uIG9mIHByb2plY3Rpb24gZ29lcyBoZXJlIGFzIHN0cmluZyIpCnNwVHJhbnNmb3JtKHdvcmxkX2NvdW50cmllcywgbXlfbmV3X3Byb2plY3Rpb24pCmBgYAoKVGhlIHBlcmhhcHMgdHJpY2tpZXN0IHBhcnQgaGVyZSBpcyB0byBkZXRlcm1pbmUgdGhlIGRlZmluaXRpb24gb2YgdGhlIHByb2plY3Rpb24sIHdoaWNoIG5lZWRzIHRvIGJlIGEgY2hhcmFjdGVyIHN0cmluZyBpbiBbcHJvajRdKGh0dHA6Ly90cmFjLm9zZ2VvLm9yZy9wcm9qLykgZm9ybWF0LiBZb3UgY2FuIFtsb29rIGl0IHVwIG9ubGluZV0oaHR0cDovL3d3dy5zcGF0aWFscmVmZXJlbmNlLm9yZykuIEZvciBleGFtcGxlIGZvciBbVVRNIHpvbmUgMzNOIChFUFNHOjMyNjMzKV0oaHR0cDovL3NwYXRpYWxyZWZlcmVuY2Uub3JnL3JlZi9lcHNnL3dncy04NC11dG0tem9uZS0zM24vKSB0aGUgc3RyaW5nIHdvdWxkIGJlOgoKW2ArcHJvaj11dG0gK3pvbmU9MzMgK2VsbHBzPVdHUzg0ICtkYXR1bT1XR1M4NCArdW5pdHM9bSArbm9fZGVmc2BdKGh0dHA6Ly9zcGF0aWFscmVmZXJlbmNlLm9yZy9yZWYvZXBzZy93Z3MtODQtdXRtLXpvbmUtMzNuL3Byb2o0anMvKQoKWW91IGNhbiByZXRyaWV2ZSB0aGUgQ1JTIGZyb20gYW4gZXhpc3RpbmcgYFNwYXRpYWwqYCBvYmplY3Qgd2l0aCB0aGUgYHByb2o0c3RyaW5nKClgIGNvbW1hbmQuIAoKVGhlIGBzZmAgcGFja2FnZSBlcXVpdmFsZW50IGlzIC0geW91IGd1ZXNzZWQgaXQhIC0gYHN0X3RyYW5zZm9ybSgpYC4gVGhlIGZ1bmN0aW9uIHRha2VzIHRoZSBmb2xsb3dpbmcgdHdvIGFyZ3VtZW50czoKCiogdGhlIGBzZmAgb2JqZWN0IHRvIHJlcHJvamVjdAoqIHRoZSBDUlMsIHdoaWNoIGNhbiBlaXRoZXIgYmUgYSBzdHJpbmcgYXMgYWJvdmUsIG9yIGFuIGludGVnZXIsIGlmIHlvdSBrbm93IHRoZSBFUFNHIGNvZGUuCgpOb3RlIHRoYXQgeW91IGNhbiB0cmFuc2Zvcm0gYW55IG9iamVjdCBvZiBjbGFzcyBgc2ZgLCBgc2ZjYCBvciBgc2ZnYC4KClRvIHJldHJpZXZlIHRoZSBDUlMgb2YgYW4gYHNmYCBvYmplY3QgdXNlIGBzdF9jcnMoKWAKCioqKgoKIyMjIEV4ZXJjaXNlIDIKCiMuIEZyb20gdGhlIGZpbGVzIGRvd25sb2FkZWQgZWFybGllciByZWFkIHRoZSBgUGhpbGx5SG9taWNpZGVzYCBzaGFwZWZpbGUgaW50byBSIGFuZCBuYW1lIGl0IGBwaF9ob21pY2lkZXNfc2ZgIG9yIGBwaF9ob21pY2lkZXNfc3BgLgoKIy4gV2hhdCBpcyB0aGUgQ1JTIG9mIGBwaGlsbHlfKmA/ICAgV2hhdCBpcyB0aGUgQ1JTIG9mIGBwaF9ob21pY2lkZXNfKmA/CgojLiBSZXByb2plY3QgYHBoX2hvbWljaWRlc18qYCBzbyBpdCBtYXRjaGVzIHRoZSBwcm9qZWN0aW9uIG9mIGBwaGlsbHlfKmAgYW5kIGFzc2lnbiBpdCB0byBhIG5ldyBvYmplY3QgY2FsbGVkIGBwaF9ob21pY2lkZXNfYWVhYC4KCiMuIFVzZSBgcmFuZ2UoKWAgYW5kIGBjb29yZGluYXRlcygpYCAvIGBzdF9jb29yaW5hdGVzKClgIHRvIGNvbXBhcmUgdGhlIGNvb3JkaW5hdGVzIGJlZm9yZSBhbmQgYWZ0ZXIgcmVwb2plY3Rpb24uICAgCgo+IFRyeSBiZWZvcmUgeW91IHBlZWshCgpgYGB7ciBldmFsPUZBTFNFfQojIyBzZiAjIwpwaF9ob21pY2lkZXNfc2YgPC0gc3RfcmVhZCgifi9EZXNrdG9wL1JTcGF0aWFsRGF0YU9wcy9QaGlsbHlDcmltZS8iKQpzdF9jcnMocGhpbGx5X3NmKQpzdF9jcnMocGhfaG9taWNpZGVzX3NmKQpwaF9ob21pY2lkZXNfYWVhX3NmIDwtIHN0X3RyYW5zZm9ybShwaF9ob21pY2lkZXNfc2YsIHN0X2NycyhwaGlsbHlfc2YpKQoKcmFuZ2Uoc3RfY29vcmRpbmF0ZXMocGhfaG9taWNpZGVzX2FlYV9zZikpCnJhbmdlKHN0X2Nvb3JkaW5hdGVzKHBoX2hvbWljaWRlc19zZikpCgojIyBzcCAjIwpwaF9ob21pY2lkZXNfc3AgPC0gcmVhZE9HUigiL1VzZXJzL2NlbmdlbC9EZXNrdG9wL1JTcGF0aWFsRGF0YU9wcy9QaGlsbHlDcmltZS8iLCAiUGhpbGx5SG9taWNpZGVzIikKcHJvajRzdHJpbmcocGhpbGx5X3NwKQpwcm9qNHN0cmluZyhwaF9ob21pY2lkZXNfc3ApCnBoX2hvbWljaWRlc19hZWFfc3AgPC0gc3BUcmFuc2Zvcm0ocGhfaG9taWNpZGVzX3NwLCBDUlMocHJvajRzdHJpbmcocGhpbGx5X3NwKSkpCgpyYW5nZShjb29yZGluYXRlcyhwaF9ob21pY2lkZXNfYWVhX3NwKSkKcmFuZ2UoY29vcmRpbmF0ZXMocGhfaG9taWNpZGVzX3NwKSkKYGBgCgpJZiB5b3UgcGxvdHRlZCB0aGUgYHNwYCBvYmplY3Qgd2l0aCAKCiAgICBwYXIobWZyb3c9YygxLDIpLCBjZXg9MC43KSAKICAgIHBsb3QocGhfaG9taWMsIHBjaD0yMCwgYXhlcz1UUlVFKQogICAgcGxvdChwaF9ob21pY19hZWEsIHBjaD0yMCwgYXhlcz1UUlVFKQoKeW91IHNob3VsZCBzZWUgc29tZXRoaW5nIGxpa2UgdGhpczoKCmBgYHtyIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTEwfQpwaCA8LSByZWFkT0dSKCIvVXNlcnMvY2VuZ2VsL0Rlc2t0b3AvUlNwYXRpYWxEYXRhT3BzL1BoaWxseS8iLCAiUGhpbGx5VG90YWxQb3BISGluYyIsIHZlcmJvc2UgPSBGKSAKcGhfaG9taWMgPC0gcmVhZE9HUigiL1VzZXJzL2NlbmdlbC9EZXNrdG9wL1JTcGF0aWFsRGF0YU9wcy9QaGlsbHlDcmltZS8iLCAiUGhpbGx5SG9taWNpZGVzIiwgdmVyYm9zZSA9IEYpCnBoX2hvbWljX2FlYSA8LSBzcFRyYW5zZm9ybShwaF9ob21pYywgQ1JTKHByb2o0c3RyaW5nKHBoKSkpCnBhcihtZnJvdz1jKDEsMiksIGNleD0wLjcpIApwbG90KHBoX2hvbWljLCBwY2g9MjAsIGF4ZXM9VFJVRSkKcGxvdChwaF9ob21pY19hZWEsIHBjaD0yMCwgYXhlcz1UUlVFKQpgYGAKCioqKgoKIyAzLiBTcGF0aWFsIGFnZ3JlZ2F0aW9uOiBQb2ludHMgaW4gUG9seWdvbnMKCkZvciB0aGUgbmV4dCBleGVyY2lzZSB3ZSB3YW50IHRvIGNhbGN1bGF0ZSB0aGUgaG9taWNpZGUgcmF0aW8gZm9yIGVhY2ggY2Vuc3VzIHRyYWN0IGluIFBoaWxhZGVscGhpYSBhcyAKCiAgICBob21pY2lkZXMgcGVyIHRyYWN0IC8gdG90YWwgcG9wdWxhdGlvbiBwZXIgdHJhY3QuCgpGb3IgdGhpcyB3ZSBuZWVkIHRvIGNvdW50IGFsbCB0aGUgaG9taWNpZGVzIGZvciBlYWNoIGNlbnN1cyB0cmFjdCBpbiBQaGlsYWRlbHBoaWEuIFRvIGFjaGlldmUgdGhpcyB0aGlzIHdlIGpvaW4gdGhlIHBvaW50cyBvZiBob21pY2lkZSBpbmNpZGVuY2UgdG8gdGhlIGNlbnN1cyB0cmFjdCBwb2x5Z29uLiBZb3UgbWlnaHQgYmUgZmFtaWxpYXIgd2l0aCB0aGlzIG9wZXJhdGlvbiBmcm9tIG90aGVyIEdJUyBwYWNrYWdlcy4KCkZvciBgc3BgIG9iamVjdHMgd2UgY2FuIHVzZSB0aGUgYGFnZ3JlZ2F0ZSgpYCBmdW5jdGlvblteM10uIEhlcmUgYXJlIHRoZSBhcmd1bWVudHMgdGhhdCBpdCBuZWVkczoKCiogdGhlIGBTcGF0aWFsUG9pbnREYXRhZnJhbWVgIHdpdGggdGhlIGhvbWljaWRlIGluY2lkZW50cyBhcyBwb2ludCBsb2NhdGlvbnMsIAoqIHRoZSBgU3BhdGlhbFBvbHlnb25EYXRhZnJhbWVgIHdpdGggdGhlIGNlbnN1cyB0cmFjdCBwb2x5Z29ucyB0byBhZ2dyZWdhdGUgb24sIGFuZCAgCiogYW4gYWdncmVnYXRlIGZ1bmN0aW9uLiBTaW5jZSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBjb3VudGluZyB0aGUgcG9pbnRzIChpLmUuIHRoZSByb3dzIG9mIGFsbCB0aGUgcG9pbnRzIHRoYXQgYmVsb25nIHRvIGEgY2VydGFpbiBwb2x5Z29uKSwgd2UgY2FuIHVzZSBgbGVuZ3RoYCAob2YgdGhlIHJlc3BlY3RpdmUgdmVjdG9ycyBvZiB0aGUgYWdncmVnYXRlZCBkYXRhKS4gCgpbXjNdOiBUaGVyZSBpcyBhbHNvIGFuIGBhZ2dyZWdhdGUoKWAgZnVuY3Rpb24gaW4gdGhlIGBzdGF0c2AgcGFja2FnZSB0aGF0IGNvbWVzIHdpdGggdGhlIFIgc3RhbmRhcmQgaW5zdGFsbC4gTm90ZSB0aGF0IGBzcGAgZXh0ZW5kcyB0aGlzIGZ1bmN0aW9uIHNvIGl0IGNhbiB0YWtlIGBTcGF0aWFsKmAgb2JqZWN0cyBhbmQgYWdncmVnYXRlIG92ZXIgdGhlIGdlb21ldHJpYyBmZWF0dXJlcy4KCioqKgojIyMgRXhlcmNpc2UgMwoKIy4gQ291bnQgaG9taWNpZGVzIHBlciBjZW5zdXMgdHJhY3QuIFVzZSB0aGUgYE9CSkVDVElEYCBmaWVsZCBmcm9tIGBwaF9ob21pY2lkZXNfYWVhKmAgZm9yIGhvbWljaWRlIGluY2lkZW50cyBhbmQgYHBoaWxseV8qYCB0byBhZ2dyZWdhdGUgb24gYW5kIHNhdmUgdGhlIHJlc3VsdCBhcyBgcGhfaG9taWNpZGVzX2FnZ2AuIFVzZSBgbGVuZ3RoYCBhcyBhZ2dyZWdhdGUgZnVuY3Rpb24uIChMb29rIHVwIGA/c3A6OmFnZ3JlZ2F0ZWAgaWYgeW91IG5lZWQgaGVscC4pCgojLiBPdXQgb2YgY291cmlvc2l0eSB0cnkgdG8gdXNlIGBwaF9ob21pY2lkZXNgIGZvciBob21pY2lkZSBpbmNpZGVudHMuIFdoYXQgaGFwcGVucz8KCiMuIFdoYXQgdHlwZSBvZiBvYmplY3QgaXMgYHBoX2hvbWljaWRlc19hZ2dgPwoKIy4gRG9lcyBpdCBoYXZlIGFuIGF0dHJpYnV0ZSB0YWJsZSBhbmQgaWYgc28sIHdoYXQgZG9lcyBpdCBjb250YWluPwoKIy4gSG93IG1pZ2h0IHlvdSBnbyBhYm91dCBjYWxjdWxhdGluZyB0aGUgaG9taWNpZGUgcmF0aW8gKGkuZS4gbm9ybWFsaXplZCBvdmVyIHRoZSB0b3RhbCBwb3B1bGF0aW9uKSBwZXIgY2Vuc3VzIHRyYWN0PwoKPiBUcnkgYmVmb3JlIHlvdSBwZWVrIQoKYGBge3IgZXZhbD1GQUxTRX0KcGhfaG9taWNpZGVzX2FnZ19zcCA8LSBhZ2dyZWdhdGUoeCA9IHBoX2hvbWljaWRlc19hZWFfc3BbIk9CSkVDVElEIl0sIGJ5ID0gcGhpbGx5X3NwLCBGVU4gPSBsZW5ndGgpCiMgbWFrZSBzdXJlIHlvdSB1bmRlcnN0YW5kIHRoaXMgZXJyb3IgbWVzc2FnZToKYWdncmVnYXRlKHggPSBwaF9ob21pY2lkZXMsIGJ5ID0gcGhpbGx5X3NwLCBGVU4gPSBsZW5ndGgpICAKCmNsYXNzKHBoX2hvbWljaWRlc19hZ2cpCm5hbWVzKHBoX2hvbWljaWRlc19hZ2cpCmhlYWQocGhfaG9taWNpZGVzX2FnZykKCnBoX2hvbWljaWRlc19hZ2dfc3AkaG9tX3JhdGlvIDwtIHBoX2hvbWljaWRlc19hZ2dfc3AkT0JKRUNUSUQvcGhpbGx5X3NwJHRvdGFsUG9wCmhpc3QocGhfaG9taWNpZGVzX2FnZ19zcCRob21fcmF0aW8sIG5jbGFzcz0xMDApCmBgYAoKVGhlcmUgbWlnaHQgYmUgb3RoZXIgaW5zdGFuY2VzIHdoZXJlIHdlIGRvbid0IHdhbnQgdG8gYWdncmVnYXRlLCBidXQgbWlnaHQgb25seSB3YW50IHRvIGtub3cgd2hpY2ggcG9seWdvbiBhIHBvaW50IGZhbGxzIGludG8uIEluIHRoYXQgY2FzZSB3ZSBjYW4gdXNlIGBvdmVyKClgLiBJbiBmYWN0LCB0aGUgYGFnZ3JlZ2F0ZSgpYCBmdW5jdGlvbiB1c2VkIGFib3ZlIG1ha2VzIHVzZSBvZiBgb3ZlcigpYC4gU2VlIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zcC92aWduZXR0ZXMvb3Zlci5wZGYgZm9yIG1vcmUgZGV0YWlscyBvbiB0aGUgb3Zlci1tZXRob2RzLiBgcG9pbnQuaW4ucG9seSgpYCBmcm9tIHRoZSBbYHNwYXRpYWxFY29gXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPXNwYXRpYWxFY28pIHBhY2thZ2UgaW50ZXJzZWN0cyBwb2ludCBhbmQgcG9seWdvbnMgYW5kIGFkZHMgcG9seWdvbiBhdHRyaWJ1dGVzIHRvIHBvaW50cy4gVGhlcmUgaXMgYWxzbyBgcG9pbnQuaW4ucG9seWdvbigpYCBmcm9tIHRoZSBgc3BgIHBhY2thZ2Ugd2hpY2ggdGVzdHMgaWYgYSBwb2ludCBvciBzZXQgb2YgcG9pbnRzIGZhbGwgaW4gYSBnaXZlbiBwb2x5Z29uLgoKRm9yIGBzZmAgb2JqZWN0cyB3ZSBuZWVkIHRvIGFkZCBvbmUgbW9yZSBzdGVwLiBXZSBmaXJzdCB1c2UgYHN0X3dpdGhpbigpYCB0byBkZXRlcm1pbmUgdGhlIHBvbHlnb24gd2hpY2ggcG9pbnRzIGZhbGxzIGludG8uIFdlIGNhbiB0aGVuIHVzZSB0aGUgcmVzdWx0IHRvIGFnZ3JlZ2F0ZSBieS4KCmBgYHtyIGV2YWw9Rn0KcG9pbnRfaW5fcG9seSA8LSBzdF93aXRoaW4ocGhfaG9taWNpZGVzX2FlYV9zZiwgcGhpbGx5X3NmKSAjIGRldGVybWluZSB3aGljaCBwb2x5IGVhY2ggcG9pbnQgZmFsbHMgaW50bwpwcCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcihwb2ludF9pbl9wb2x5KSkgIyB3ZSBuZWVkIGEgdmVjdG9yCnBoX2hvbWljaWRlc19hZ2dfc2YgPC0gYWdncmVnYXRlKHBoX2hvbWljaWRlc19hZWFfc2ZbIk9CSkVDVElEIl0sIGJ5ID0gcHAsIGxlbmd0aCkKaGlzdCh1bmxpc3QocGhfaG9taWNpZGVzX2FnZ19zZiRPQkpFQ1RJRCkvcGhpbGx5X3NmJHRvdGFsUG9wLCBuY2xhc3M9MTAwKQpgYGAKCioqKgoKCiMgNC4gU2VsZWN0IFBvbHlnb25zIGJ5IExvY2F0aW9uCgpGb3IgdGhlIG5leHQgZXhhbXBsZSBvdXIgZ29hbCBpcyB0byBzZWxlY3QgYWxsIFBoaWxhZGVscGhpYSBjZW5zdXMgdHJhY3RzIHdpdGhpbiBhIHJhbmdlIG9mIDIga2lsb21ldGVycyBmcm9tIHRoZSBjaXR5IGNlbnRlci4KCj4gVGhpbmsgYWJvdXQgdGhpcyBmb3IgYSBtb21lbnQgLS0gd2hhdCBtaWdodCBiZSB0aGUgc3RlcHMgeW91J2QgZm9sbG93PwoKYGBge3IgZXZhbD1GQUxTRX0KIyMgSG93IGFib3V0OgoKIyAxLiBHZXQgdGhlIGNlbnN1cyB0cmFjdCBwb2x5Z29ucy4KIyAyLiBGaW5kIHRoZSBQaGlsYWRlbHBoaWEgY2l0eSBjZW50ZXIgY29vcmRpbmF0ZXMuCiMgMy4gQ3JlYXRlIGEgYnVmZmVyIGFyb3VuZCB0aGUgY2l0eSBjZW50ZXIgcG9pbnQuCiMgNC4gU2VsZWN0IGFsbCBjZW5zdXMgdHJhY3QgcG9seWdvbnMgdGhhdCBpbnRlcnNlY3Qgd2l0aCB0aGUgY2VudGVyIGJ1ZmZlcgoKYGBgCgojIyBTcGF0aWFsIG9wZXJhdGlvbnMgd2l0aCBgc3BgCgpJbiBvcmRlciB0byBwZXJmb3JtIHRob3NlIG9wZXJhdGlvbnMgb24gYW4gYHNwYCBvYmplY3Qgd2Ugd2lsbCBuZWVkIHRvIG1ha2UgdXNlIG9mIGFuIGFkZGl0aW9uYWwgYW5vdGhlciBsaWJyYXJ5LCBjYWxsZWQgYHJnZW9zYC4gTWFrZSBzdXJlIHlvdSBoYXZlIGl0IGxvYWRlZC4KCiMjIyBFeGVyY2lzZSA0CgoxLiBHZXQgdGhlIGNlbnN1cyB0cmFjdCBwb2x5Z29ucy4gIApXZSBnb3QgdGhvc2UuICAKV2Ugd2lsbCByZXVzZSBgcGhpbGx5X3NwYCBmb3IgdGhlIGNlbnN1cyB0cmFjdCBwb2x5Z29ucy4KCjIuIEZpbmQgdGhlIFBoaWxhZGVscGhpYSBjaXR5IGNlbnRlciBjb29yZGluYXRlcy4gIApPay4gSSB3aWxsIHRlbGwgeW91OiAgCkxhdCBpcyAzOS45NTI1OCBhbmQgTG9uIGlzIC03NS4xNjUyMi4gVGhpcyBpcyBpbiBXR1M4NC4gIApXaXRoIHRoaXMgaW5mb3JtYXRpb24sIGNyZWF0ZSBhIGBTcGF0aWFsUG9pbnRzYCBvYmplY3QgbmFtZWQgYHBoaWxseV9jdHJgLiAgCgpgYGB7ciBldmFsPUZBTFNFfQpjb29yZHMgPC0gZGF0YS5mcmFtZSh4ID0gLTc1LjE2NTIyLCB5ID0gMzkuOTUyNTgpICMgc2V0IHRoZSBjb29yZGluYXRlcwpwcmogPC0gQ1JTKCIrcHJvaj1sb25nbGF0ICtlbGxwcz1XR1M4NCArZGF0dW09V0dTODQgK25vX2RlZnMiKSAjIHRoZSBwcm9qZWN0aW9uIHN0cmluZyBmb3IgV0dTODQKcGhpbGx5X2N0ciA8LSBTcGF0aWFsUG9pbnRzKGNvb3JkcywgcHJvajRzdHJpbmcgPSBwcmopICMgY3JlYXRlIHRoZSBzcGF0aWFsUG9pbnRzCmBgYAoKMy4gQ3JlYXRlIGEgYnVmZmVyIGFyb3VuZCB0aGUgY2l0eSBjZW50ZXIgcG9pbnQuICAKSGVyZSBpcyB3aGVyZSB3ZSB3aWxsIHVzZSB0aGUgYGdCdWZmZXIoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYHJnZW9zYCBwYWNrYWdlLiBGb3IgdGhpcyBwdXJwb3NlIHdlIHdpbGwgbmVlZCB0byBwcm92aWRlIHR3byBhcmd1bWVudHM6IHRoZSBfX3NwIG9iamVjdF9fIGFuZCB0aGUgX193aWR0aF9fIG9mIHRoZSBidWZmZXIsIHdoaWNoIGlzIGFzc3VtZWQgdG8gYmUgaW4gbWFwIHVuaXRzLiBUaGUgZnVuY3Rpb24gcmV0dXJucyBhIGBTcGF0aWFsUG9seWdvbnNgIG9iamVjdCB0byB5b3Ugd2l0aCB0aGUgYnVmZmVyIC0gbmFtZSBpdCBgcGhpbGx5X2J1ZmAuICAKU28geW91ciBjb21tYW5kIHdvdWxkIGxvb2sgc29tZXRoaW5nIGxpa2UgIApgYGAKcGhpbGx5X2J1ZiA8LSBnQnVmZmVyKHRoZV9zcGF0aWFsX3BvaW50X29iamVjdCwgd2lkdGggPSBhX251bWJlcl9oZXJlKQpgYGAgIApfX05vdyAtLSBiZWZvcmUgeW91IGNyZWF0ZSB0aGlzIGJ1ZmZlciwgdGhpbmsgYWJvdXQgd2hhdCB5b3UgbmVlZCB0byBkbyB0byBgcGhpbGx5X2N0cmAgYmVmb3JlIHlvdSBwcm9jZWVkLl9fCgpgYGB7ciBldmFsPUZBTFNFfQpsaWJyYXJ5KHJnZW9zKQpwaGlsbHlfY3RyX2FlYSA8LSBzcFRyYW5zZm9ybShwaGlsbHlfY3RyLCBDUlMocHJvajRzdHJpbmcocGhpbGx5X3NwKSkpICMgcmVwcm9qZWN0ISEKcGhpbGx5X2J1ZiA8LSBnQnVmZmVyKHBoaWxseV9jdHJfYWVhLCB3aWR0aD0yMDAwKSAgIyBjcmVhdGUgYnVmZmVyIGFyb3VuZCBjZW50ZXIKYGBgCgo0LiBTZWxlY3QgYWxsIGNlbnN1cyB0cmFjdCBwb2x5Z29ucyB0aGF0IGludGVyc2VjdCB3aXRoIHRoZSBjZW50ZXIgYnVmZmVyICAKV2Ugd2lsbCB1c2UgdGhlIGBnSW50ZXJzZWN0cygpYCBmdW5jdGlvbiBmcm9tIHRoZSBgcmdlb3NgIHBhY2thZ2UgZm9yIHRoaXMuIFRoZSBmdW5jdGlvbiB0ZXN0cyBpZiB0d28gZ2VvbWV0cmllcyAobGV0J3MgbmFtZSB0aGVtIF9zcGdlb20xXyBhbmQgX3NwZ2VvbTJfKSBoYXZlIHBvaW50cyBpbiBjb21tb24gb3Igbm90LiBgZ0ludGVyc2VjdHNgIHJldHVybnMgVFJVRSBpZiBfc3BnZW9tMV8gYW5kIF9zcGdlb20yXyBoYXZlIGF0IGxlYXN0IG9uZSBwb2ludCBpbiBjb21tb24uICAKSGVyZSBpcyB3aGVyZSB3ZSBkZXRlcm1pbmUgaWYgdGhlIGNlbnN1cyB0cmFjdHMgZmFsbCB3aXRoaW4gdGhlIGJ1ZmZlci4gSW4gYWRkaXRpb24gdG8gb3VyIHR3byBzcCBvYmplY3RzIChgcGhpbGx5X2J1ZmAgYW5kIGBwaGlsbHlfc3BgKSB3ZSBuZWVkIHRvIHByb3ZpZGUgb25lIG1vcmUgYXJndW1lbnQsIGBieWlkYC4gSXQgZGV0ZXJtaW5lcyBpZiB0aGUgZnVuY3Rpb24gc2hvdWxkIGJlIGFwcGxpZWQgYWNyb3NzIGlkcyAoVFJVRSkgb3IgdGhlIGVudGlyZSBvYmplY3QgKEZBTFNFKSBmb3IgX3NwZ2VvbTFfIGFuZCBfc3BnZW9tMl8uIFRoZSBkZWZhdWx0IHNldHRpbmcgaXMgRkFMU0UuIFNpbmNlIHdlIHdhbnQgdG8gY29tcGFyZSBfZXZlcnkgc2luZ2xlXyBjZW5zdXMgdHJhY3QgcG9seWdvbiBpbiBvdXIgYHBoaWxseV9zcGAgb2JqZWN0IHdlIG5lZWQgdG8gc2V0IGl0IHRvIFRSVUUuICAKV2hhdCBjbGFzcyBvZiBvYmplY3QgZG9lcyBgZ0ludGVyc2VjdHMoKWAgcmV0dXJuIGFuZCB3aGF0IGlzIGl0cyBzdHJ1Y3R1cmU/ICAKSG93IGNhbiB5b3UgdXNlIGl0IHRvIHNlbGVjdCB0aGUgZGVzaXJlZCBwb2x5Z29ucz8KCmBgYHtyIGV2YWw9RkFMU0V9CnBoaWxseV9idWZfaW50ZXJzZWN0cyA8LSAgZ0ludGVyc2VjdHMgKHBoaWxseV9idWYsIHBoaWxseV9zcCwgYnlpZD1UUlVFKSAjIGRldGVybWluZSB3aGljaCBjZW5zdXMgdHJhY3RzIGludGVyc2VjdCB3aXRoIHRoZSBidWZmZXIKY2xhc3MocGhpbGx5X2J1Zl9pbnRlcnNlY3RzKQoKIyBzdWJzZXQKcGhpbGx5X3NlbCA8LSBwaGlsbHlfc3BbYXMudmVjdG9yKHBoaWxseV9idWZfaW50ZXJzZWN0cyksXQpgYGAKNS4gUGxvdCBwaGlsbHksIHRoZSBzZWxlY3RlZCBwb2x5Z29ucyBhbmQgdGhlIGJ1ZmZlci4gQmVsb3cgeW91IGNhbiBzZWUgaXQgYWxsIHB1dCB0b2dldGhlci4KCmBgYHtyfQpwaGlsbHlfc3AgPC0gcmVhZE9HUigiL1VzZXJzL2NlbmdlbC9EZXNrdG9wL1JTcGF0aWFsRGF0YU9wcy9QaGlsbHkvIiwgIlBoaWxseVRvdGFsUG9wSEhpbmMiLCB2ZXJib3NlID0gRikgCmNvb3JkcyA8LSBkYXRhLmZyYW1lKHggPSAtNzUuMTY1MjIsIHkgPSAzOS45NTI1OCkKcGhpbGx5X2N0ciA8LSBTcGF0aWFsUG9pbnRzKGNvb3JkcywgcHJvajRzdHJpbmcgPSBDUlMoIitwcm9qPWxvbmdsYXQgK2VsbHBzPVdHUzg0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyIpKSAKcGhpbGx5X2N0cl9hZWEgPC0gc3BUcmFuc2Zvcm0ocGhpbGx5X2N0ciwgQ1JTKHByb2o0c3RyaW5nKHBoaWxseV9zcCkpKSAKcGhpbGx5X2J1ZiA8LSBnQnVmZmVyKHBoaWxseV9jdHJfYWVhLCB3aWR0aD0yMDAwKQpwaGlsbHlfYnVmX2ludGVyc2VjdHMgPC0gIGdJbnRlcnNlY3RzIChwaGlsbHlfYnVmLCBwaGlsbHlfc3AsIGJ5aWQ9VFJVRSkKcGhpbGx5X3NlbCA8LSBwaGlsbHlfc3BbYXMudmVjdG9yKHBoaWxseV9idWZfaW50ZXJzZWN0cyksXQpwbG90IChwaGlsbHlfc3AsIGJvcmRlcj0iI2FhYWFhYSIpCnBsb3QgKHBoaWxseV9zZWwsIGFkZD1ULCBjb2w9InJlZCIpIApwbG90IChwaGlsbHlfYnVmLCBhZGQ9VCwgbHdkID0gMikKYGBgCgojIyBTcGF0aWFsIG9wZXJhdGlvbnMgd2l0aCBgc2ZgCgpUbyBnaXZlIHlvdSBhIHNlbnNlIG9mIGhvdyB0aGlzIG1pZ2h0IGJlIGRvbmUgdXNpbmcgdGhlIGBzZmAgcGFja2FnZSB3ZSB3aWxsIHJlcHJvZHVjZSBoZXJlIHRoZSBzYW1lIGV4YW1wbGUgYXMgYWJvdmUuIAoKRm9yIHRoZSBzcGF0aWFsIG9wZXJhdGlvbnMgd2UgY2FuIHJlY3VyIHRvIHRoZSBzdWl0ZSBvZiBnZW9tZXRyaWMgb3BlcmF0aW9ucyB0aGF0IGNvbWUgd2l0aCB0aGUgYHNmYCBwYWNrYWdlICwgaW4gcGFydGljdWxhciB3ZSB3aWxsIHVzZSBgc3RfYnVmZmVyKClgIGFuZCBgc3RfaW50ZXJzZWN0cygpYAoKYGBge3J9CmxpYnJhcnkoc2YpCnBoaWxseV9zZiA8LSBzdF9yZWFkKCJ+L0Rlc2t0b3AvUlNwYXRpYWxEYXRhT3BzL1BoaWxseS8iLCBxdWlldCA9IFQpCgojIG1ha2UgYSBzaW1wbGUgZmVhdHVyZSBwb2ludCB3aXRoIENSUwpwaGlsbHlfY3RyX3NmYyA8LSBzdF9zZmMoc3RfcG9pbnQoYygtNzUuMTY1MjIsIDM5Ljk1MjU4KSksIGNycyA9IDQzMjYpCgojIHJlcHJvamVjdApwaGlsbHlfY3RyX2FlYV9zZiA8LSBzdF90cmFuc2Zvcm0ocGhpbGx5X2N0cl9zZmMsIHN0X2NycyhwaGlsbHlfc2YpKQoKIyBidWZmZXIKcGhpbGx5X2J1Zl9zZiA8LSBzdF9idWZmZXIocGhpbGx5X2N0cl9hZWFfc2YsIDIwMDApCgojIGludGVyc2VjdGlvbgpwaGlsbHlfYnVmX2ludGVyc2VjdHMgPC0gc3RfaW50ZXJzZWN0cyhwaGlsbHlfYnVmX3NmLCBwaGlsbHlfc2YpCgojIHN1YnNldHRpbmcKcGhpbGx5X3NlbF9zZiA8LSBwaGlsbHlfc2ZbdW5saXN0KHBoaWxseV9idWZfaW50ZXJzZWN0cyksXQoKIyBwbG90CnBsb3Qoc3RfZ2VvbWV0cnkocGhpbGx5X3NmKSwgYm9yZGVyPSIjYWFhYWFhIikKcGxvdChzdF9nZW9tZXRyeShwaGlsbHlfc2VsX3NmKSwgYWRkPVQsIGNvbD0icmVkIikKcGxvdChzdF9nZW9tZXRyeShwaGlsbHlfYnVmX3NmKSwgYWRkPVQsIGx3ZCA9IDIpCmBgYAoKIyA2LiBgc3BgIC0gYHNmYCBjb21wYXJpc29uCnxob3cgdG8uLiB8IGZvciBgc3BgIG9iamVjdHMgfCBmb3IgYHNmYCBvYmplY3RzIHwKfC0tLXwtLS18LS0tfAp8am9pbiBhdHRyaWJ1dGVzfCBgc3A6Om1lcmdlKClgIHwgYGJhc2U6Om1lcmdlKClgIHwKfHJlcHJvamVjdCB8IGBzcFRyYW5zZm9ybSgpYCB8IGBzdF90cmFuc2Zvcm0oKWB8CnxyZXRyaWV2ZSAob3IgYXNzaWduKSBDUlMgfCBgcHJvajRzdHJpbmcoKWAgfCBgc3RfY3JzKClgIHwKfGNvdW50IHBvaW50cyBpbiBwb2x5Z29uc3wgYG92ZXIoKWAgfCBgc3Rfd2l0aGluYCBhbmQgYGFnZ3JlZ2F0ZSgpYCB8CnxidWZmZXJ8ICBgcmdlb3M6OmdCdWZmZXIoKWAgKHNlcGFyYXRlIHBhY2thZ2UpIHwgYHN0X2J1ZmZlcigpYCB8CnxzZWxlY3QgYnkgbG9jYXRpb24gfCBbYGcqYCBmdW5jdGlvbnNdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9yZ2Vvcy92ZXJzaW9ucy8wLjMtMjIpIGZyb20gYHJnZW9zYCB8IFtnZW9zIGZ1bmN0aW9uc10oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3NmL3ZlcnNpb25zLzAuMy00L3RvcGljcy9nZW9zKSBpbiBgc2ZgIHwKCg==