Libraries needed for this section are:
ggmap
readr
sp
rgdal
httr
jsonlite
Data needed:
1. About geocoding
What is Geocoding?
- “Geocoding is the process of transforming a description of a location (such as an address, name of a place, or coordinates) to a location on the earth’s surface.”
- “A geocoder is a piece of software or a (web) service that implements a geocoding process i.e. a set of inter-related components in the form of operations, algorithms, and data sources that work together to produce a spatial representation for descriptive locational references.”
- “Reverse geocoding uses geographic coordinates to find a description of the location, most typically a postal address or place name.” (I rarely have needed to do this.)
How is it done?
There are a number of ways, for example:
- Interpolation for Street adresses
Issues
- Quality of input data or: how specific is your location information?
- Quality of output data: return rate, accuracy and precision
- Regional differences: Geocoding adresses in the US is a different from than geocoding in Nigeria or Germany. Geocoding adresses in suburban Chicago is different from geocoding in rural Alabama.
- Limitations of geocoding services: Bulding and maintaining an accurate global geocoding service is very resource intensive. So is running a geocoding server, particularly if it is hit with millions of requests per second. For that reason geocoding services are usually very limited in what they offer for free, if at all. Secondly, the results returned usually vary from service to service in terms of accuracy and values returned.
2. About APIs
What is an API and what does it have to do with geocoding?
Exercise 1
Go to your browser.
Point it to: http://maps.googleapis.com/maps/api/geocode/xml?address=Stanford+CA
What just happened?
Now write the URL to return the location of Toledo – what do we get here?
Now we want the city in Spain. Extra bonus: Request the results in JSON format.
Here is some documentation: https://developers.google.com/maps/documentation/geocoding/
http://maps.googleapis.com/maps/api/geocode/xml?address=Toledo
http://maps.googleapis.com/maps/api/geocode/json?address=Toledo®ion=ES
# this also works:
http://maps.googleapis.com/maps/api/geocode/json?address=Toledo,Spain
What have we learned?
- We can use an API to access a service (or tools) provided by someone else without knowing the details of how this service or tool is implemented.
- A geocoding API provides a direct way to access these services via an HTTP request (simply speaking: a URL).
- A geocoding service API request must be in a particular form as specified by the service provier.
- Geocoding services responses are returned in a structured format, which is typically XML or JSON, sometimes also KMZ.
Our goal is now to do what we did in a web browser from R. For this we have to take into account also:
Authentication: Using geocoders often may require a valid API key. The key can usually be requested for free by registering at the website of the provider.
Rate Limiting: Geocode service providers typically use a rate limiting mechanism to ensure that the service stays available to all users.
There are many geocoding providers. They vary in terms of format specifications, access and use(!) resrictions, quality of results, and more. So choosing a geocoder for a research project depends on the specifics of your situation.
Generic structure of a geocoding script
If you were to write your own script, here are the basic steps to take.
Load address data. Clean them up if necessary.
Send each address to the geocoding service (typically: create a URL request for each address)
Process results. Extract geogrpahic coordinates and any other values you are interested in and turn into into a convenient format, usually a table.
Save the output.
3. Geocoding with the Google maps API
Interactively
We will start by using the geocode()
command from the ggmap
library.
Exercise 2
Install (if you haven’t) and load the ggmap
library.
Using the geocode
command, how would you search for location of the city of Toledo
library(ggmap)
geocode("Toledo")
- How does your result compare to what you got from when you did the query through the web browser? Check out the
output=
option of the command, what does that do?
geocode("Toledo", ouput = "all")
- Now geocode this address:
"380 New York St, Redlands, CA"
. Compare the results you get with the Google geocoder versus the Data Science Toolkit geocoder.
geocode("380 New York St, Redlands, CA")
geocode("380 New York St, Redlands, CA", source = "dsk")
Batch geocoding
Now let’s write an R script to process an entire list of adresses for geocoding this way. We will use the generic outline above and implement it in R.
Exercise 3
Load address data.
Let’s use the addresses of declared dangerous dogs in Austin, TX, courtesy of the city of Austin’s open data portal.
The direct link to the CSV file of the data is this one:
https://data.austintexas.gov/api/views/ykw4-j3aj/rows.csv?accessType=DOWNLOAD
Read the table into R and call it dogs
.
dogs <- read.csv("https://data.austintexas.gov/api/views/ykw4-j3aj/rows.csv?accessType=DOWNLOAD")
Send each address to the geocoding service.
The nice thing is that geocode()
can take a vector of adresses. So all we have to do is find out where the addresses are in our dogs
data frame and then submit them to the geocode()
function. And of course, catch the results in a variable.
(For the purpose of this exercise we will overlook the fact that the coordinates already come with the table.)
# concatenate the address
dogs_adr <- paste(dogs$Address, "Austin,TX", dogs$Zip.Code)
# geocode - check for warnings
dogs_coords <- geocode(dogs_adr)
Process results.
Most of this is all taken care of already in the geocode()
function, which returns a vector of geographic coordinates. All we need to do is bind it the back to our original dataframe.
dogs <- data.frame(cbind(dogs, dogs_coords))
Save the output as shapefile.
Saving the coordinates out as csv is pretty easy with write.table()
. To save the data frame as a shapefile, we convert the dataframe to a Spatial*Dataframe
first, assign a projection, and then save with writeOGR()
. Remember that you will need both the sp
and the rgdal
libraries for this.
Optional: save the shapefile using sf
.
library(sp)
library(rgdal)
dogs <- dogs[complete.cases(dogs),] # remove NA geocoding results
coordinates(dogs) <- c("lon", "lat")
proj4string(dogs) <- CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
writeOGR(dogs, ".", "dangerous_dogs", driver="ESRI Shapefile")
# If we were to use the `sf` package we would do:
library(sf)
dogs_sf <- st_as_sf(dogs, c("lon", "lat"), crs = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
st_write(dogs_sf, "dangerous_dogs.shp")
- Now let’s use
leaflet()
to plot the dots over a basemap.
leaflet(dogs) %>%
addTiles() %>%
addCircleMarkers(dogs$lon, dogs$lat, popup=dogs$Description.of.Dog)
Something like this:
4. Geocoding with the ArcGIS API (Stanford affiliates only)
Thanks to our fabulous geospatial manager Stace Maples who is tirelessly working to make our GIS lives easier we have our own geolocator at Stanford at
http://locator.stanford.edu/arcgis/rest/services/geocode
The services available here cover the US only. The good news here are that there are no limits as of how many addresses you can throw at this server. However, you should let Stace know if you are intending to run a major job!
You can find more details as of how to use this service from R here.
Exercise 4
- Using the provided function
geocodeSL
we can geocode the adress table from above using the ArcGIS geocoding service:
library(readr)
library(rgdal)
# set your token
myToken <- "XXXXXXXX"
# source the function
source("https://raw.githubusercontent.com/cengel/ArcGIS_geocoding/master/SUL_gcFunctions.R")
# geocode
dogs_coords2 <- do.call("rbind", lapply(dogs_adr, function(x) geocodeSL(x, myToken)))
# add attibutes back
dogs2 <- cbind(dogs, dogs_coords2)
# write out a CSV
write.csv(dogs2, "dangerous_dogs2.csv", row.names=F)
5. More geocoding services and libraries
Other R libraries
ggmap
is certainly not the only R library that includes a geocoding function. The libraries below also do geocoding with different services:
For more see the Geolocation/Geocoding secion on the CRAN Task View for Web Technologies and Services.
6. Putting it all together: Webscraping for maps
Now for fun. We will retrieve a table from the Wikipedia page on Crime statistics U.S. cities with a population of 250,000 or greater. We will use the city names, geocode their locations and plot the cities by population and crime rate.
You should be able to run the code below as is. It might take a little while.
Exercise 5
library(XML)
library(ggmap)
library(dplyr)
library(RCurl)
# read in the data
uri <- "https://en.wikipedia.org/wiki/List_of_United_States_cities_by_crime_rate"
# we want the second table: which=2
citiesCR <- readHTMLTable(getURL(uri), which = 2, stringsAsFactors = FALSE)
# clean up (with mutate_each function from dplyr):
# remove the comma in 1,000 and above and convert numbers from strings to numeric
citiesCRclean <- mutate_each(citiesCR, funs(as.numeric(gsub(",", "", .))), -(State:City))
# geocode loations
latlon <- geocode(paste(citiesCRclean$City, citiesCRclean$State, sep=", "))
# combine into a new dataframe
citiesCRll <- data.frame(citiesCRclean, latlon)
#get basmap
map_us <- get_map (location='United States', zoom=4, color="bw")
# plot
ggmap(map_us, legend='bottomright', extent='device') +
geom_point(data=citiesCRll,
aes(x=lon, y=lat,
color=Violent.Crime,
size=Population),
na.rm=T) +
scale_colour_gradient(low="white", high="red") +
scale_size_continuous(range = c(4,12))
LS0tCnRpdGxlOiAiR2VvY29kaW5nIGluIFIiCmF1dGhvcjogImNsYXVkaWEgYSBlbmdlbCIKZGF0ZTogIkxhc3QgdXBkYXRlZDogYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgZmlnX2NhcHRpb246IG5vCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA0Ci0tLQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KCiMgbG9hZCBhZGRpdGlvbmFsIGxpYnJhcmllcyBmb3IgYmVsb3cKbGlicmFyeShnZ21hcCkKbGlicmFyeShzcCkKbGlicmFyeShyZ2RhbCkKbGlicmFyeShodHRyKQpsaWJyYXJ5KGpzb25saXRlKQpsaWJyYXJ5KGxlYWZsZXQpCmBgYAoKTGlicmFyaWVzIG5lZWRlZCBmb3IgdGhpcyBzZWN0aW9uIGFyZToKCiogYGdnbWFwYAoqIGByZWFkcmAKKiBgc3BgCiogYHJnZGFsYAoqIGBodHRyYAoqIGBqc29ubGl0ZWAKCgpEYXRhIG5lZWRlZDoKCiogW1NvbWUgYWRkcmVzc2VzXShodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL3owZWw2dmZnMXZ0bXh3NS9QaGlsbHlCYW5rc19zbS5jc3Y/ZGw9MSkKCiMgMS4gQWJvdXQgZ2VvY29kaW5nIAojIyBXaGF0IGlzIEdlb2NvZGluZz8KCi0gIioqR2VvY29kaW5nKiogaXMgdGhlIHByb2Nlc3Mgb2YgdHJhbnNmb3JtaW5nIGEgZGVzY3JpcHRpb24gb2YgYSBsb2NhdGlvbiAoc3VjaCBhcyBhbiBhZGRyZXNzLCBuYW1lIG9mIGEgcGxhY2UsIG9yIGNvb3JkaW5hdGVzKSB0byBhIGxvY2F0aW9uIG9uIHRoZSBlYXJ0aCdzIHN1cmZhY2UuIiAKLSAiQSAqKmdlb2NvZGVyKiogaXMgYSBwaWVjZSBvZiBzb2Z0d2FyZSBvciBhICh3ZWIpIHNlcnZpY2UgdGhhdCBpbXBsZW1lbnRzIGEgZ2VvY29kaW5nIHByb2Nlc3MgaS5lLiBhIHNldCBvZiBpbnRlci1yZWxhdGVkIGNvbXBvbmVudHMgaW4gdGhlIGZvcm0gb2Ygb3BlcmF0aW9ucywgYWxnb3JpdGhtcywgYW5kIGRhdGEgc291cmNlcyB0aGF0IHdvcmsgdG9nZXRoZXIgdG8gcHJvZHVjZSBhIHNwYXRpYWwgcmVwcmVzZW50YXRpb24gZm9yIGRlc2NyaXB0aXZlIGxvY2F0aW9uYWwgcmVmZXJlbmNlcy4iCi0gIioqUmV2ZXJzZSBnZW9jb2RpbmcqKiB1c2VzIGdlb2dyYXBoaWMgY29vcmRpbmF0ZXMgdG8gZmluZCBhIGRlc2NyaXB0aW9uIG9mIHRoZSBsb2NhdGlvbiwgbW9zdCB0eXBpY2FsbHkgYSBwb3N0YWwgYWRkcmVzcyBvciBwbGFjZSBuYW1lLiIgKEkgcmFyZWx5IGhhdmUgbmVlZGVkIHRvIGRvIHRoaXMuKVteMV0KClteMV06IEZyb206IGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0dlb2NvZGluZwoKIyMgSG93IGlzIGl0IGRvbmU/CgpUaGVyZSBhcmUgYSBudW1iZXIgb2Ygd2F5cywgZm9yIGV4YW1wbGU6CgotICoqSW50ZXJwb2xhdGlvbioqIGZvciBTdHJlZXQgYWRyZXNzZXMKCiFbSW50ZXJwb2xhdGlvbiBmb3Igc3RyZWV0IGFkcmVzcyBnZW9jb2RpbmddKGltYWdlcy9zdHJlZXRsZXZlbEdlb2NvZGluZy5wbmcpCgotICoqUm9vZnRvcCBMZXZlbCoqIGZvciBTdHJlZXQgYWRyZXNzZXMKCi0gT3BlbiBhY2Nlc3MgKipnYXpldHRlcnMgb3IgZGF0YWJhc2VzKiogZm9yIGxvY2F0aW9uL3BsYWNlIG5hbWVzIChodHRwOi8vZ2VvbmFtZXMub3JnIG9yIGh0dHA6Ly9nZW9uYW1lcy51c2dzLmdvdi9wbHMvZ25pc3B1YmxpYy8pIG9yIElQIGFkcmVzc2VzIChodHRwOi8vZnJlZWdlb2lwLm5ldCkKCiMjIElzc3VlcwotICoqUXVhbGl0eSBvZiBpbnB1dCBkYXRhKiogb3I6IGhvdyBzcGVjaWZpYyBpcyB5b3VyIGxvY2F0aW9uIGluZm9ybWF0aW9uPwotICoqUXVhbGl0eSBvZiBvdXRwdXQgZGF0YSoqOiByZXR1cm4gcmF0ZSwgYWNjdXJhY3kgYW5kIHByZWNpc2lvbgotICoqUmVnaW9uYWwgZGlmZmVyZW5jZXMqKjogR2VvY29kaW5nIGFkcmVzc2VzIGluIHRoZSBVUyBpcyBhIGRpZmZlcmVudCBmcm9tIHRoYW4gZ2VvY29kaW5nIGluIE5pZ2VyaWEgb3IgR2VybWFueS4gR2VvY29kaW5nIGFkcmVzc2VzIGluIHN1YnVyYmFuIENoaWNhZ28gaXMgZGlmZmVyZW50IGZyb20gZ2VvY29kaW5nIGluIHJ1cmFsIEFsYWJhbWEuCi0gKipMaW1pdGF0aW9ucyBvZiBnZW9jb2Rpbmcgc2VydmljZXMqKjogQnVsZGluZyBhbmQgbWFpbnRhaW5pbmcgYW4gYWNjdXJhdGUgZ2xvYmFsIGdlb2NvZGluZyBzZXJ2aWNlIGlzIHZlcnkgcmVzb3VyY2UgaW50ZW5zaXZlLiBTbyBpcyBydW5uaW5nIGEgZ2VvY29kaW5nIHNlcnZlciwgcGFydGljdWxhcmx5IGlmIGl0IGlzIGhpdCB3aXRoIG1pbGxpb25zIG9mIHJlcXVlc3RzIHBlciBzZWNvbmQuIEZvciB0aGF0IHJlYXNvbiBnZW9jb2Rpbmcgc2VydmljZXMgYXJlIHVzdWFsbHkgdmVyeSBsaW1pdGVkIGluIHdoYXQgdGhleSBvZmZlciBmb3IgZnJlZSwgaWYgYXQgYWxsLiBTZWNvbmRseSwgdGhlIHJlc3VsdHMgcmV0dXJuZWQgdXN1YWxseSB2YXJ5IGZyb20gc2VydmljZSB0byBzZXJ2aWNlIGluIHRlcm1zIG9mIGFjY3VyYWN5IGFuZCB2YWx1ZXMgcmV0dXJuZWQuCgojIDIuIEFib3V0IEFQSXMKIyMgV2hhdCBpcyBhbiBBUEkgYW5kIHdoYXQgZG9lcyBpdCBoYXZlIHRvIGRvIHdpdGggZ2VvY29kaW5nPwoKKioqCiMjIyBFeGVyY2lzZSAxCgojLiBHbyB0byB5b3VyIGJyb3dzZXIuCgojLiBQb2ludCBpdCB0bzogaHR0cDovL21hcHMuZ29vZ2xlYXBpcy5jb20vbWFwcy9hcGkvZ2VvY29kZS94bWw/YWRkcmVzcz1TdGFuZm9yZCtDQQoKIy4gV2hhdCBqdXN0IGhhcHBlbmVkPwoKIy4gTm93IHdyaXRlIHRoZSBVUkwgdG8gcmV0dXJuIHRoZSBsb2NhdGlvbiBvZiBUb2xlZG8gLS0gd2hhdCBkbyB3ZSBnZXQgaGVyZT8gCgojLiBOb3cgd2Ugd2FudCB0aGUgY2l0eSBpbiBTcGFpbi4gRXh0cmEgYm9udXM6IFJlcXVlc3QgdGhlIHJlc3VsdHMgaW4gSlNPTiBmb3JtYXQuIAoKSGVyZSBpcyBzb21lIGRvY3VtZW50YXRpb246IGh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL21hcHMvZG9jdW1lbnRhdGlvbi9nZW9jb2RpbmcvCgpgYGB7ciBldmFsPUZ9Cmh0dHA6Ly9tYXBzLmdvb2dsZWFwaXMuY29tL21hcHMvYXBpL2dlb2NvZGUveG1sP2FkZHJlc3M9VG9sZWRvCgpodHRwOi8vbWFwcy5nb29nbGVhcGlzLmNvbS9tYXBzL2FwaS9nZW9jb2RlL2pzb24/YWRkcmVzcz1Ub2xlZG8mcmVnaW9uPUVTCgojIHRoaXMgYWxzbyB3b3JrczoKaHR0cDovL21hcHMuZ29vZ2xlYXBpcy5jb20vbWFwcy9hcGkvZ2VvY29kZS9qc29uP2FkZHJlc3M9VG9sZWRvLFNwYWluCmBgYAoKV2hhdCBoYXZlIHdlIGxlYXJuZWQ/Cgo+IC0gV2UgY2FuIHVzZSBhbiBBUEkgdG8gYWNjZXNzIGEgc2VydmljZSAob3IgdG9vbHMpIHByb3ZpZGVkIGJ5IHNvbWVvbmUgZWxzZSB3aXRob3V0IGtub3dpbmcgdGhlIGRldGFpbHMgb2YgaG93IHRoaXMgc2VydmljZSBvciB0b29sIGlzIGltcGxlbWVudGVkLgogICAgICAKPiAtIEEgZ2VvY29kaW5nIEFQSSBwcm92aWRlcyBhIGRpcmVjdCB3YXkgdG8gYWNjZXNzIHRoZXNlIHNlcnZpY2VzIHZpYSBhbiBIVFRQIHJlcXVlc3QgKHNpbXBseSBzcGVha2luZzogYSBVUkwpLgoKPiAtIEEgZ2VvY29kaW5nIHNlcnZpY2UgQVBJIHJlcXVlc3QgbXVzdCBiZSBpbiBhIHBhcnRpY3VsYXIgZm9ybSBhcyBzcGVjaWZpZWQgYnkgdGhlIHNlcnZpY2UgcHJvdmllci4KCj4gLSBHZW9jb2Rpbmcgc2VydmljZXMgcmVzcG9uc2VzIGFyZSByZXR1cm5lZCBpbiBhIHN0cnVjdHVyZWQgZm9ybWF0LCB3aGljaCBpcyB0eXBpY2FsbHkgWE1MIG9yIEpTT04sIHNvbWV0aW1lcyBhbHNvIEtNWi4gCgpPdXIgZ29hbCBpcyBub3cgdG8gZG8gd2hhdCB3ZSBkaWQgaW4gYSB3ZWIgYnJvd3NlciBmcm9tIFIuIEZvciB0aGlzIHdlIGhhdmUgdG8gdGFrZSBpbnRvIGFjY291bnQgYWxzbzoKCi0gKipBdXRoZW50aWNhdGlvbioqOiBVc2luZyBnZW9jb2RlcnMgb2Z0ZW4gbWF5IHJlcXVpcmUgYSB2YWxpZCBBUEkga2V5LiBUaGUga2V5IGNhbiB1c3VhbGx5IGJlIHJlcXVlc3RlZCBmb3IgZnJlZSBieSByZWdpc3RlcmluZyBhdCB0aGUgd2Vic2l0ZSBvZiB0aGUgcHJvdmlkZXIuCgotICoqUmF0ZSBMaW1pdGluZyoqOiBHZW9jb2RlIHNlcnZpY2UgcHJvdmlkZXJzIHR5cGljYWxseSB1c2UgYSByYXRlIGxpbWl0aW5nIG1lY2hhbmlzbSB0byBlbnN1cmUgdGhhdCB0aGUgc2VydmljZSBzdGF5cyBhdmFpbGFibGUgdG8gYWxsIHVzZXJzLgoKVGhlcmUgYXJlIG1hbnkgZ2VvY29kaW5nIHByb3ZpZGVyc1teMl0uIFRoZXkgdmFyeSBpbiB0ZXJtcyBvZiBmb3JtYXQgc3BlY2lmaWNhdGlvbnMsIGFjY2VzcyBhbmQgdXNlKCEpIHJlc3JpY3Rpb25zLCBxdWFsaXR5IG9mIHJlc3VsdHMsIGFuZCBtb3JlLiBTbyBjaG9vc2luZyBhIGdlb2NvZGVyIGZvciBhIHJlc2VhcmNoIHByb2plY3QgZGVwZW5kcyBvbiB0aGUgc3BlY2lmaWNzIG9mIHlvdXIgc2l0dWF0aW9uLiAKClteMl06IEZvciBhIHF1aXRlIGNvbXByZWhlbnNpdmUgb3ZlcnZpZXcgbG9vayBbaGVyZV0oaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXQvY2NjP2tleT0wQWlkRVd5YV9wNlhGZEd3MVJtWjZUakIxYWpaeFZrODFkMnBJU0RNelZVRSZ1c3A9c2hhcmluZykgYW5kIFtoZXJlXShodHRwOi8vZ2Vvc2VydmljZXMudGFtdS5lZHUvU2VydmljZXMvR2VvY29kZS9PdGhlckdlb2NvZGVycy8pLiAKCiMjIEdlbmVyaWMgc3RydWN0dXJlIG9mIGEgZ2VvY29kaW5nIHNjcmlwdAoKSWYgeW91IHdlcmUgdG8gd3JpdGUgeW91ciBvd24gc2NyaXB0LCBoZXJlIGFyZSB0aGUgYmFzaWMgc3RlcHMgdG8gdGFrZS4KCiMuIExvYWQgYWRkcmVzcyBkYXRhLiBDbGVhbiB0aGVtIHVwIGlmIG5lY2Vzc2FyeS4KCiMuIFNlbmQgZWFjaCBhZGRyZXNzIHRvIHRoZSBnZW9jb2Rpbmcgc2VydmljZSAodHlwaWNhbGx5OiBjcmVhdGUgYSBVUkwgcmVxdWVzdCBmb3IgZWFjaCBhZGRyZXNzKQoKIy4gUHJvY2VzcyByZXN1bHRzLiBFeHRyYWN0IGdlb2dycGFoaWMgY29vcmRpbmF0ZXMgYW5kIGFueSBvdGhlciB2YWx1ZXMgeW91IGFyZSBpbnRlcmVzdGVkIGluIGFuZCB0dXJuIGludG8gaW50byBhIGNvbnZlbmllbnQgZm9ybWF0LCB1c3VhbGx5IGEgdGFibGUuCgojLiBTYXZlIHRoZSBvdXRwdXQuCgoKIyAzLiBHZW9jb2Rpbmcgd2l0aCB0aGUgR29vZ2xlIG1hcHMgQVBJCgojIyBJbnRlcmFjdGl2ZWx5IAoKV2Ugd2lsbCBzdGFydCBieSB1c2luZyB0aGUgYGdlb2NvZGUoKWAgY29tbWFuZCBmcm9tIHRoZSBgZ2dtYXBgIGxpYnJhcnkuIAoKKioqCiMjIyBFeGVyY2lzZSAyCgoxLiBJbnN0YWxsIChpZiB5b3UgaGF2ZW4ndCkgYW5kIGxvYWQgdGhlIGBnZ21hcGAgbGlicmFyeS4KCjIuIFVzaW5nIHRoZSBgZ2VvY29kZWAgY29tbWFuZCwgaG93IHdvdWxkIHlvdSBzZWFyY2ggZm9yIGxvY2F0aW9uIG9mIHRoZSBjaXR5IG9mIFRvbGVkbwoKYGBge3IgZXZhbD1GQUxTRX0KbGlicmFyeShnZ21hcCkKZ2VvY29kZSgiVG9sZWRvIikKYGBgCgozLiBIb3cgZG9lcyB5b3VyIHJlc3VsdCBjb21wYXJlIHRvIHdoYXQgeW91IGdvdCBmcm9tIHdoZW4geW91IGRpZCB0aGUgcXVlcnkgdGhyb3VnaCB0aGUgd2ViIGJyb3dzZXI/IENoZWNrIG91dCB0aGUgYG91dHB1dD1gIG9wdGlvbiBvZiB0aGUgY29tbWFuZCwgd2hhdCBkb2VzIHRoYXQgZG8/CgpgYGB7ciBldmFsPUZBTFNFfQpnZW9jb2RlKCJUb2xlZG8iLCBvdXB1dCA9ICJhbGwiKQpgYGAKCjQuIE5vdyBnZW9jb2RlIHRoaXMgYWRkcmVzczogYCIzODAgTmV3IFlvcmsgU3QsIFJlZGxhbmRzLCBDQSJgLiBDb21wYXJlIHRoZSByZXN1bHRzIHlvdSBnZXQgd2l0aCB0aGUgR29vZ2xlIGdlb2NvZGVyIHZlcnN1cyB0aGUgRGF0YSBTY2llbmNlIFRvb2xraXQgZ2VvY29kZXIuCgpgYGB7ciBldmFsPUZBTFNFfQpnZW9jb2RlKCIzODAgTmV3IFlvcmsgU3QsIFJlZGxhbmRzLCBDQSIpCmdlb2NvZGUoIjM4MCBOZXcgWW9yayBTdCwgUmVkbGFuZHMsIENBIiwgc291cmNlID0gImRzayIpCmBgYAoKIyMgQmF0Y2ggZ2VvY29kaW5nCgpOb3cgbGV0J3Mgd3JpdGUgYW4gUiBzY3JpcHQgdG8gcHJvY2VzcyBhbiBlbnRpcmUgbGlzdCBvZiBhZHJlc3NlcyBmb3IgZ2VvY29kaW5nIHRoaXMgd2F5LiBXZSB3aWxsIHVzZSB0aGUgZ2VuZXJpYyBvdXRsaW5lIGFib3ZlIGFuZCBpbXBsZW1lbnQgaXQgaW4gUi4KCioqKgojIyMgRXhlcmNpc2UgMwoKMS4gTG9hZCBhZGRyZXNzIGRhdGEuICAKCiAgICBMZXQncyB1c2UgdGhlIGFkZHJlc3NlcyBvZiBkZWNsYXJlZCBkYW5nZXJvdXMgZG9ncyBpbiBBdXN0aW4sIFRYLCBjb3VydGVzeSBvZiB0aGUgW2NpdHkgb2YgQXVzdGluJ3Mgb3BlbiBkYXRhIHBvcnRhbF0oaHR0cHM6Ly9kYXRhLmF1c3RpbnRleGFzLmdvdikuIAoKICAgIFRoZSBkaXJlY3QgbGluayB0byB0aGUgQ1NWIGZpbGUgb2YgdGhlIGRhdGEgaXMgdGhpcyBvbmU6CgogICAgaHR0cHM6Ly9kYXRhLmF1c3RpbnRleGFzLmdvdi9hcGkvdmlld3MveWt3NC1qM2FqL3Jvd3MuY3N2P2FjY2Vzc1R5cGU9RE9XTkxPQUQKCiAgICBSZWFkIHRoZSB0YWJsZSBpbnRvIFIgYW5kIGNhbGwgaXQgYGRvZ3NgLgoKYGBge3IgZXZhbD1GQUxTRX0KZG9ncyA8LSByZWFkLmNzdigiaHR0cHM6Ly9kYXRhLmF1c3RpbnRleGFzLmdvdi9hcGkvdmlld3MveWt3NC1qM2FqL3Jvd3MuY3N2P2FjY2Vzc1R5cGU9RE9XTkxPQUQiKQpgYGAKCjIuIFNlbmQgZWFjaCBhZGRyZXNzIHRvIHRoZSBnZW9jb2Rpbmcgc2VydmljZS4gCiAgICAKICAgIFRoZSBuaWNlIHRoaW5nIGlzIHRoYXQgYGdlb2NvZGUoKWAgY2FuIHRha2UgYSB2ZWN0b3Igb2YgYWRyZXNzZXMuIFNvIGFsbCB3ZSBoYXZlIHRvIGRvIGlzIGZpbmQgb3V0IHdoZXJlIHRoZSBhZGRyZXNzZXMgYXJlIGluIG91ciBgZG9nc2AgZGF0YSBmcmFtZSBhbmQgdGhlbiBzdWJtaXQgdGhlbSB0byB0aGUgYGdlb2NvZGUoKWAgZnVuY3Rpb24uIEFuZCBvZiBjb3Vyc2UsIGNhdGNoIHRoZSByZXN1bHRzIGluIGEgdmFyaWFibGUuCgogICAgKEZvciB0aGUgcHVycG9zZSBvZiB0aGlzIGV4ZXJjaXNlIHdlIHdpbGwgb3Zlcmxvb2sgdGhlIGZhY3QgdGhhdCB0aGUgY29vcmRpbmF0ZXMgYWxyZWFkeSBjb21lIHdpdGggdGhlIHRhYmxlLikKCmBgYHtyIGV2YWw9RkFMU0V9CiMgY29uY2F0ZW5hdGUgdGhlIGFkZHJlc3MKZG9nc19hZHIgPC0gcGFzdGUoZG9ncyRBZGRyZXNzLCAiQXVzdGluLFRYIiwgZG9ncyRaaXAuQ29kZSkKCiMgZ2VvY29kZSAtIGNoZWNrIGZvciB3YXJuaW5ncwpkb2dzX2Nvb3JkcyA8LSBnZW9jb2RlKGRvZ3NfYWRyKQpgYGAKCjMuIFByb2Nlc3MgcmVzdWx0cy4gCgogICAgTW9zdCBvZiB0aGlzIGlzIGFsbCB0YWtlbiBjYXJlIG9mIGFscmVhZHkgaW4gdGhlIGBnZW9jb2RlKClgIGZ1bmN0aW9uLCB3aGljaCByZXR1cm5zIGEgdmVjdG9yIG9mIGdlb2dyYXBoaWMgY29vcmRpbmF0ZXMuIEFsbCB3ZSBuZWVkIHRvIGRvIGlzIGJpbmQgaXQgdGhlIGJhY2sgdG8gb3VyIG9yaWdpbmFsIGRhdGFmcmFtZS4gCgpgYGB7ciBldmFsPUZBTFNFfQpkb2dzIDwtIGRhdGEuZnJhbWUoY2JpbmQoZG9ncywgZG9nc19jb29yZHMpKQpgYGAKCjQuIFNhdmUgdGhlIG91dHB1dCBhcyBzaGFwZWZpbGUuIAogICAgCiAgICBTYXZpbmcgdGhlIGNvb3JkaW5hdGVzIG91dCBhcyBjc3YgaXMgcHJldHR5IGVhc3kgd2l0aCBgd3JpdGUudGFibGUoKWAuIFRvIHNhdmUgdGhlIGRhdGEgZnJhbWUgYXMgYSBzaGFwZWZpbGUsIHdlIGNvbnZlcnQgdGhlIGRhdGFmcmFtZSB0byBhIGBTcGF0aWFsKkRhdGFmcmFtZWAgZmlyc3QsIGFzc2lnbiBhIHByb2plY3Rpb24sIGFuZCB0aGVuIHNhdmUgd2l0aCBgd3JpdGVPR1IoKWAuIFJlbWVtYmVyIHRoYXQgeW91IHdpbGwgbmVlZCBib3RoIHRoZSBgc3BgIGFuZCB0aGUgYHJnZGFsYCBsaWJyYXJpZXMgZm9yIHRoaXMuIAogICAgCiAgICBPcHRpb25hbDogc2F2ZSB0aGUgc2hhcGVmaWxlIHVzaW5nIGBzZmAuCiAgICAKYGBge3IgZXZhbD1GQUxTRX0KbGlicmFyeShzcCkKbGlicmFyeShyZ2RhbCkKCmRvZ3MgPC0gZG9nc1tjb21wbGV0ZS5jYXNlcyhkb2dzKSxdICMgcmVtb3ZlIE5BIGdlb2NvZGluZyByZXN1bHRzIApjb29yZGluYXRlcyhkb2dzKSA8LSBjKCJsb24iLCAibGF0IikgCnByb2o0c3RyaW5nKGRvZ3MpIDwtIENSUygiK3Byb2o9bG9uZ2xhdCArZWxscHM9V0dTODQgK2RhdHVtPVdHUzg0ICtub19kZWZzIikKd3JpdGVPR1IoZG9ncywgIi4iLCAiZGFuZ2Vyb3VzX2RvZ3MiLCBkcml2ZXI9IkVTUkkgU2hhcGVmaWxlIikKCgojIElmIHdlIHdlcmUgdG8gdXNlIHRoZSBgc2ZgIHBhY2thZ2Ugd2Ugd291bGQgZG86CiAgICAKbGlicmFyeShzZikKZG9nc19zZiA8LSBzdF9hc19zZihkb2dzLCBjKCJsb24iLCAibGF0IiksIGNycyA9ICIrcHJvaj1sb25nbGF0ICtlbGxwcz1XR1M4NCArZGF0dW09V0dTODQgK25vX2RlZnMiKQpzdF93cml0ZShkb2dzX3NmLCAiZGFuZ2Vyb3VzX2RvZ3Muc2hwIikKCmBgYAogICAgCgo1LiBOb3cgbGV0J3MgdXNlIGBsZWFmbGV0KClgIHRvIHBsb3QgdGhlIGRvdHMgb3ZlciBhIGJhc2VtYXAuCgpgYGB7ciBldmFsPUZBTFNFfQpsZWFmbGV0KGRvZ3MpICU+JSAgCiAgYWRkVGlsZXMoKSAlPiUgCiAgYWRkQ2lyY2xlTWFya2Vycyhkb2dzJGxvbiwgZG9ncyRsYXQsIHBvcHVwPWRvZ3MkRGVzY3JpcHRpb24ub2YuRG9nKSAKYGBgCgpTb21ldGhpbmcgbGlrZSB0aGlzOgoKYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY29tbWVudD1OQX0KZG9ncyA8LSByZWFkLmNzdigiaHR0cHM6Ly9kYXRhLmF1c3RpbnRleGFzLmdvdi9hcGkvdmlld3MveWt3NC1qM2FqL3Jvd3MuY3N2P2FjY2Vzc1R5cGU9RE9XTkxPQUQiKQojZG9nc19hZHIgPC0gcGFzdGUoZG9ncyRBZGRyZXNzLCAiQXVzdGluLFRYIiwgZG9ncyRaaXAuQ29kZSkKZG9nc19hZHIgPC0gcGFzdGUoZG9ncyRBZGRyZXNzLCAiQXVzdGluLFRYIiwgZG9ncyRaaXAuQ29kZSkKZG9nc19jb29yZHMgPC0gZ2VvY29kZShkb2dzX2FkcikKZG9ncyA8LSBkYXRhLmZyYW1lKGNiaW5kKGRvZ3MsIGRvZ3NfY29vcmRzKSkKZG9ncyA8LSBkb2dzW2NvbXBsZXRlLmNhc2VzKGRvZ3MpLF0gIyByZW1vdmUgTkEgZ2VvY29kaW5nIHJlc3VsdHMgCgpsZWFmbGV0KGRvZ3MpICU+JSAgCiAgYWRkVGlsZXMoKSAlPiUgCiAgYWRkQ2lyY2xlTWFya2Vycyhkb2dzJGxvbiwgZG9ncyRsYXQsIHBvcHVwPWRvZ3MkRGVzY3JpcHRpb24ub2YuRG9nKSAKYGBgCgoKIyA0LiBHZW9jb2Rpbmcgd2l0aCB0aGUgQXJjR0lTIEFQSSAoU3RhbmZvcmQgYWZmaWxpYXRlcyBvbmx5KQoKVGhhbmtzIHRvIG91ciBmYWJ1bG91cyBnZW9zcGF0aWFsIG1hbmFnZXIgW1N0YWNlIE1hcGxlc10oaHR0cHM6Ly9saWJyYXJ5LnN0YW5mb3JkLmVkdS9wZW9wbGUvbWFwbGVzKSB3aG8gaXMgdGlyZWxlc3NseSB3b3JraW5nIHRvIG1ha2Ugb3VyIEdJUyBsaXZlcyBlYXNpZXIgd2UgaGF2ZSBvdXIgb3duIGdlb2xvY2F0b3IgYXQgU3RhbmZvcmQgYXQKCj4+IGh0dHA6Ly9sb2NhdG9yLnN0YW5mb3JkLmVkdS9hcmNnaXMvcmVzdC9zZXJ2aWNlcy9nZW9jb2RlCgpUaGUgc2VydmljZXMgYXZhaWxhYmxlIGhlcmUgY292ZXIgdGhlIFVTIG9ubHkuIFRoZSBnb29kIG5ld3MgaGVyZSBhcmUgdGhhdCB0aGVyZSBhcmUgbm8gbGltaXRzIGFzIG9mIGhvdyBtYW55IGFkZHJlc3NlcyB5b3UgY2FuIHRocm93IGF0IHRoaXMgc2VydmVyLiBIb3dldmVyLCAqKnlvdSBzaG91bGQgbGV0IFN0YWNlIGtub3cgaWYgeW91IGFyZSBpbnRlbmRpbmcgdG8gcnVuIGEgbWFqb3Igam9iISoqCgpZb3UgY2FuIGZpbmQgbW9yZSBkZXRhaWxzIGFzIG9mIGhvdyB0byB1c2UgdGhpcyBzZXJ2aWNlIGZyb20gUiBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2NlbmdlbC9BcmNHSVNfZ2VvY29kaW5nKS4KCgoqKioKIyMjIEV4ZXJjaXNlIDQKCiMuIFVzaW5nIHRoZSBwcm92aWRlZCBmdW5jdGlvbiBgZ2VvY29kZVNMYCB3ZSBjYW4gZ2VvY29kZSB0aGUgYWRyZXNzIHRhYmxlIGZyb20gYWJvdmUgdXNpbmcgdGhlIEFyY0dJUyBnZW9jb2Rpbmcgc2VydmljZToKCmBgYHtyIGV2YWw9RkFMU0V9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocmdkYWwpCgojIHNldCB5b3VyIHRva2VuCm15VG9rZW4gPC0gIlhYWFhYWFhYIgoKIyBzb3VyY2UgdGhlIGZ1bmN0aW9uCnNvdXJjZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2NlbmdlbC9BcmNHSVNfZ2VvY29kaW5nL21hc3Rlci9TVUxfZ2NGdW5jdGlvbnMuUiIpCgojIGdlb2NvZGUKZG9nc19jb29yZHMyIDwtIGRvLmNhbGwoInJiaW5kIiwgbGFwcGx5KGRvZ3NfYWRyLCBmdW5jdGlvbih4KSBnZW9jb2RlU0woeCwgbXlUb2tlbikpKQoKIyBhZGQgYXR0aWJ1dGVzIGJhY2sKZG9nczIgPC0gY2JpbmQoZG9ncywgZG9nc19jb29yZHMyKQoKIyB3cml0ZSBvdXQgYSBDU1YKd3JpdGUuY3N2KGRvZ3MyLCAiZGFuZ2Vyb3VzX2RvZ3MyLmNzdiIsIHJvdy5uYW1lcz1GKQoKYGBgCgojIDUuIE1vcmUgZ2VvY29kaW5nIHNlcnZpY2VzIGFuZCBsaWJyYXJpZXMKCiMjIE90aGVyIFIgbGlicmFyaWVzCgpgZ2dtYXBgIGlzIGNlcnRhaW5seSBub3QgdGhlIG9ubHkgUiBsaWJyYXJ5IHRoYXQgaW5jbHVkZXMgYSBnZW9jb2RpbmcgZnVuY3Rpb24uIFRoZSBsaWJyYXJpZXMgYmVsb3cgYWxzbyBkbyBnZW9jb2Rpbmcgd2l0aCBkaWZmZXJlbnQgc2VydmljZXM6CgotIFtgZ29vZ2xld2F5YF0oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1nb29nbGV3YXkpIGFsc28gY29ubmVjdHMgdG8gR29vZ2xlIAoKLSBbYHRtYXBgXShodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy9wYWNrYWdlPXRtYXApIGNvbm5lY3RzIHRvIFtPU00gTm9taW5hdGltXShodHRwOi8vbm9taW5hdGltLm9wZW5zdHJlZXRtYXAub3JnKQoKLSBbYG5vbWluYXRpbWBdKGh0dHBzOi8vZ2l0aHViLmNvbS9ocmJybXN0ci9ub21pbmF0aW0pIChub3Qgb24gQ1JBTikgY29ubmVjdHMgdG8gW09TTSBOb21pbmF0aW1dKGh0dHA6Ly9ub21pbmF0aW0ub3BlbnN0cmVldG1hcC5vcmcpCgotIFtgb3BlbmNhZ2VgXShodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy9wYWNrYWdlPW9wZW5jYWdlKSBjb25uZWN0cyB0byBbT3BlbkNhZ2UgZ2VvY29kZXJdKGh0dHBzOi8vZ2VvY29kZXIub3BlbmNhZ2VkYXRhLmNvbS8pIAoKLSBbdGhyZWV3b3Jkc10oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT10aHJlZXdvcmRzKSBjb25uZWN0cyB0byB0aGUgW1doYXQzV29yZHNdKGh0dHA6Ly93aGF0M3dvcmRzLmNvbS8pIAoKRm9yIG1vcmUgc2VlIHRoZSBHZW9sb2NhdGlvbi9HZW9jb2Rpbmcgc2VjaW9uIG9uIHRoZSBDUkFOIFRhc2sgVmlldyBmb3IgW1dlYiBUZWNobm9sb2dpZXMgYW5kIFNlcnZpY2VzXShodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy92aWV3PVdlYlRlY2hub2xvZ2llcykuCgoKIyMgQSB3b3JkIGFib3V0IE9wZW4gRGF0YSBTY2llbmNlIFRvb2xraXQgKERTSykKClRoZSBvcGVuIERhdGEgU2NpZW5jZSBUb29sa2l0IChEU0spIGlzIGF2YWlsYWJsZSBhcyBhIHNlbGYtY29udGFpbmVkIFZhZ3JhbnQgVk0gb3IgRUMyIEFNSSB0aGF0IHlvdSBjYW4gZGVwbG95IHlvdXJzZWxmLiBJdCBpbmNsdWRlcyBhIEdvb2dsZS1zdHlsZSBnZW9jb2RlciB3aGljaCBlbXVsYXRlcyBHb29nbGUncyBnZW9jb2RpbmcgQVBJLiBUaGlzIEFQSSB1c2VzIGRhdGEgZnJvbSB0aGUgVVMgQ2Vuc3VzIGFuZCBPcGVuU3RyZWV0TWFwLCBhbG9uZyB3aXRoIGNvZGUgZnJvbSBHZW9JUSBhbmQgU2NodXlsZXIgRXJsZSdzIE1vZHVsYXIgU3RyZWV0IEFkZHJlc3MgR2VvY29kZXIuCgpJbnNydWN0aW9ucyBmb3IgaG93IHRvIHJ1biBEU0sgb24gQW1hem9uIG9yIFZhZ3JhbnQgYXJlIGhlcmU6IGh0dHA6Ly93d3cuZGF0YXNjaWVuY2V0b29sa2l0Lm9yZy9kZXZlbG9wZXJkb2NzI2FtYXpvbgoKTm90ZSB0aGF0IGBnZW9jb2RlYCBmcm9tIGBnZ21hcGAgYWxzbyBoYXMgdGhlIG9wdGlvbiB0byBhY2Nlc3MgRFNLLCBidXQgaXQgd2lsbCB1c2UgdGhlaXIgcHVibGljIHNlcnZlciwgd2hpY2ggaXMgb2Z0ZW4gc2xvdyBvciB1bmF2YWlsYWJsZS4KCiMjIEdlb2NvZGluZyBJUCBhZHJlc3NlcwoKSWYgeW91IGFyZSBpbnRlcmVzdGVkIHRvIGRvIHRoaXMgaW4gUiBzZWUgaGVyZTogaHR0cHM6Ly9naXRodWIuY29tL2NlbmdlbC9yX0lQZ2VvY29kZSAKCgojIDYuIFB1dHRpbmcgaXQgYWxsIHRvZ2V0aGVyOiBXZWJzY3JhcGluZyBmb3IgbWFwcwoKTm93IGZvciBmdW4uIFdlIHdpbGwgcmV0cmlldmUgYSB0YWJsZSBmcm9tIHRoZSBbV2lraXBlZGlhIHBhZ2Ugb24gQ3JpbWUgc3RhdGlzdGljcyBVLlMuIGNpdGllcyB3aXRoIGEgcG9wdWxhdGlvbiBvZiAyNTAsMDAwIG9yIGdyZWF0ZXJdKGh0dHA6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTGlzdF9vZl9Vbml0ZWRfU3RhdGVzX2NpdGllc19ieV9jcmltZV9yYXRlI0NyaW1lX3JhdGVzX3Blcl8xMDAuMkMwMDBfcGVvcGxlXy4yODIwMTIuMjkpLiAKV2Ugd2lsbCB1c2UgdGhlIGNpdHkgbmFtZXMsIGdlb2NvZGUgdGhlaXIgbG9jYXRpb25zIGFuZCBwbG90IHRoZSBjaXRpZXMgYnkgcG9wdWxhdGlvbiBhbmQgY3JpbWUgcmF0ZS4KCllvdSBzaG91bGQgYmUgYWJsZSB0byBydW4gdGhlIGNvZGUgYmVsb3cgYXMgaXMuIEl0IG1pZ2h0IHRha2UgYSBsaXR0bGUgd2hpbGUuCgoqKioKIyMjIEV4ZXJjaXNlIDUKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY29tbWVudD1OQX0KbGlicmFyeShYTUwpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoUkN1cmwpCgojIHJlYWQgaW4gdGhlIGRhdGEKdXJpIDwtICJodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX1VuaXRlZF9TdGF0ZXNfY2l0aWVzX2J5X2NyaW1lX3JhdGUiCiMgd2Ugd2FudCB0aGUgc2Vjb25kIHRhYmxlOiB3aGljaD0yCmNpdGllc0NSIDwtIHJlYWRIVE1MVGFibGUoZ2V0VVJMKHVyaSksIHdoaWNoID0gMiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQoKIyBjbGVhbiB1cCAod2l0aCBtdXRhdGVfZWFjaCBmdW5jdGlvbiBmcm9tIGRwbHlyKTogCiMgcmVtb3ZlIHRoZSBjb21tYSBpbiAxLDAwMCBhbmQgYWJvdmUgYW5kIGNvbnZlcnQgbnVtYmVycyBmcm9tIHN0cmluZ3MgdG8gbnVtZXJpYwpjaXRpZXNDUmNsZWFuIDwtIG11dGF0ZV9lYWNoKGNpdGllc0NSLCBmdW5zKGFzLm51bWVyaWMoZ3N1YigiLCIsICIiLCAuKSkpLCAgLShTdGF0ZTpDaXR5KSkKIAojIGdlb2NvZGUgbG9hdGlvbnMKbGF0bG9uIDwtIGdlb2NvZGUocGFzdGUoY2l0aWVzQ1JjbGVhbiRDaXR5LCBjaXRpZXNDUmNsZWFuJFN0YXRlLCBzZXA9IiwgIikpCgojIGNvbWJpbmUgaW50byBhIG5ldyBkYXRhZnJhbWUKY2l0aWVzQ1JsbCA8LSBkYXRhLmZyYW1lKGNpdGllc0NSY2xlYW4sIGxhdGxvbikKCiNnZXQgYmFzbWFwCm1hcF91cyA8LSBnZXRfbWFwIChsb2NhdGlvbj0nVW5pdGVkIFN0YXRlcycsIHpvb209NCwgY29sb3I9ImJ3IikKCiMgcGxvdApnZ21hcChtYXBfdXMsIGxlZ2VuZD0nYm90dG9tcmlnaHQnLCBleHRlbnQ9J2RldmljZScpICsKICBnZW9tX3BvaW50KGRhdGE9Y2l0aWVzQ1JsbCwKICAgICAgICAgICAgYWVzKHg9bG9uLCB5PWxhdCwgCiAgICAgICAgICAgIGNvbG9yPVZpb2xlbnQuQ3JpbWUsIAogICAgICAgICAgICBzaXplPVBvcHVsYXRpb24pLAogICAgICAgICAgICBuYS5ybT1UKSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdz0id2hpdGUiLCBoaWdoPSJyZWQiKSArCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYyg0LDEyKSkKYGBgCg==