knitr::opts_chunk$set(cache = FALSE)
Introduction
This notebook reviews projections from last week and introduces skills for joins in R
.
Dependencies
This notebook requires a number of different packages:
# tidyverse packages
library(dplyr)
library(ggplot2)
library(readr)
# spatial packages
library(mapview)
library(sf)
library(tigris)
# other packages
library(here)
library(viridis)
Load Data
This notebook requires a number of data sets:
# spatial data - census tracts with total area and population
pop <- st_read(here("data", "example-data", "STL_DEMOS_Pop", "STL_DEMOS_Pop.shp"),
stringsAsFactors = FALSE)
# spatial data - north city
north_city <- st_read(here("data", "example-data", "STL_REGIONS_NorthCity", "STL_REGIONS_NorthCity.shp"),
stringsAsFactors = FALSE)
# tabular data - 2012 murders in St. Louis
murders <- read_csv(here("data", "example-data", "STL_CRIME_Murders12.csv"))
Project the Homicide Data
The homicide data are tabular, and have two columns named x
and y
. Our first goal is to determine what projection they’re encoded with. We can get a preview of their values with str()
:
str(murders)
What possibilities exist for coordinate systems?
# solution - state plane east, feet
murders_sf <- st_as_sf(murders, coords = c("x", "y"), crs = "+proj=tmerc +lat_0=35.83333333333334 +lon_0=-90.5 +k=0.9999333333333333 +x_0=250000 +y_0=0 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs")
# verify solution
mapview(murders_sf)
The correct answer is that these data are in Missouri State Plane East (Feet), which has a CRS value of 102696. Unfortunately, it is no longer an accessible projection in R
, so we need to use the full Proj4
string.
Convert All to Same Projection
Currently, our data are in three different projection systems:
# murders
st_crs(murders_sf)
# tracts
st_crs(pop)
# north city
st_crs(north_city)
In order to geoprocess and map our data, we want to convert them all to the same coordinate system:
# murders
murders_sf <- st_transform(murders_sf, crs = 26915)
# tracts
pop <- st_transform(pop, crs = 26915)
# north city
north_city <- st_transform(north_city, crs = 26915)
We’re now ready to move on to our geoprocessing operations.
Identify Points
Our first goal is to identify points - we want to label each homicide with the tract identification number for the tract the homicide occurred in. We’ll use st_intersection()
for this:
murders_tract <- st_intersection(murders_sf, pop) %>%
select(date, address, GEOID)
Notice how we use select()
to subset our data’s columns so that we keep our output data as tidy as possible. Also notice how the number of observations does not change. This is a critical thing to check, because it lets us know that all of the homicides were correctly geocoded. If murders_tract
had a smaller number of homicides, that would let us know that some homicides occured outside of the Census tract boundaries.
We can now preview these data and see the change:
mapview(murders_tract)
Aggregate Points
With identifiers applied to our data, we can aggregate them if we also want counts of homicides by tract.
# aggregate
murders_tract %>%
group_by(GEOID) %>%
summarise(homicides = n()) -> murdersByTract
# remove geometry
st_geometry(murdersByTract) <- NULL
# join data and replace na's
murderPop <- left_join(pop, murdersByTract, by = "GEOID") %>%
mutate(homicides = ifelse(is.na(homicides) == TRUE, 0, homicides))
It’s really important to consider whether NA
values should be replaced with zeros. In this case, we consider the City’s data on crimes authoritative, and so infer that if a tract has no murders, it means that there were zero homicides there. We cannot always make this assumption, however. Consider graffiti calls for service in the CSB data for the final project. Does NA
mean no graffiti, or no calls? Considering the meaning of NAs
is so important as you clean data.
Plot Data
We can now plot homicides by population density:
ggplot() +
geom_sf(data = murderPop, mapping = aes(fill = (homicides/POP_E)*1000)) +
scale_fill_viridis()
We can also plot by area density:
ggplot() +
geom_sf(data = murderPop, mapping = aes(fill = homicides/SQKM)) +
scale_fill_viridis()
Selecting by Area
If we want a data set of only homicides for a certain area, like North City, and we have the geometric data for that region, we can subset our data by that geometric area.
murders_nc <- st_intersection(murders_sf, north_city)
Remember that we should expect murders_nc
to shrink in terms of its overall number of observations, since not all homicides occur in North City.
Replicating Desktop GIS Intersects
If we were using a desktop GIS tool, the intersect functionality would return all of the points of homicides after an intersect with our North City data. If we want to replicate this functionality, we can add st_difference()
into our workflow. This will give us the homicides that did not fall in North City:
murders_not_nc <- st_difference(murders_sf, north_city) %>%
mutate(region = "South City")
It is important to modify the region
output because st_difference()
combines the attribute table, inadvertently labeling our homicides with region
being set equal to North City
. This is not the actual desired outcome.
Once we have our difference data, we can bind them together:
murders_intersect <- rbind(murders_nc, murders_not_nc) %>%
arrange(date)
The arrange()
call puts our observations back in temporal order.
Intersects with Other Types of Geometric Data
I want to quickly illustrate how intersects behave when we have other types of geometric data.
Line Data
First, let’s illustrate intersects with line and polygon data. We’ll use tigris
to get some street data for St. Louis, and then intersect it with our North City polygon. First, we’ll download and wrangle our street data:
## download
roads <- roads(state = 29, county = 510) %>%
st_transform(crs = 26915) %>%
select(LINEARID, FULLNAME)
## preview
mapview(roads)
Now that we have some line data, lets identify street centerlines in North City:
## intersect
roads_nc <- st_intersection(roads, north_city)
## preview
mapview(roads_nc)
Our map is blank! Sometimes, geometric operations change our data from POINT
, LINESTRING
, or POLYGON
to what we call “geometry collections.” These data do not map as expected. We can extract these back to their desired type of geometry and then they should preview:
## repair
roads_nc <- st_collection_extract(roads_nc, type = "LINESTRING")
## preview
mapview(roads_nc)
Hm, this isn’t ideal either. This behavior, once we’ve visually verified that our data have the correct geometry
type, appears to be a bug in mapview
. Try running the code in your console instead! It should map correctly.
Polygon Data
Next, let’s illustrate an intersect with two sets of polygon data. We’ll get the portions of census tracts that lie in North City:
## intersect
tracts_nc <- st_intersection(pop, north_city) %>%
st_collection_extract(type = "POLYGON")
## preview
mapview(tracts_nc)
Excellent!
LS0tCnRpdGxlOiAiTWVldGluZyBFeGFtcGxlcyAtIENvbXBsZXRlZCIKYXV0aG9yOiAiQ2hyaXN0b3BoZXIgUHJlbmVyLCBQaC5ELiIKZGF0ZTogJyhgciBmb3JtYXQoU3lzLnRpbWUoKSwgIiVCICVkLCAlWSIpYCknCm91dHB1dDogCiAgZ2l0aHViX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdCAKLS0tCgpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlID0gRkFMU0UpCmBgYAoKIyMgSW50cm9kdWN0aW9uClRoaXMgbm90ZWJvb2sgcmV2aWV3cyBwcm9qZWN0aW9ucyBmcm9tIGxhc3Qgd2VlayBhbmQgaW50cm9kdWNlcyBza2lsbHMgZm9yIGpvaW5zIGluIGBSYC4KCiMjIERlcGVuZGVuY2llcwpUaGlzIG5vdGVib29rIHJlcXVpcmVzIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBwYWNrYWdlczoKCmBgYHtyIGxvYWQtcGFja2FnZXN9CiMgdGlkeXZlcnNlIHBhY2thZ2VzCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShyZWFkcikKCiMgc3BhdGlhbCBwYWNrYWdlcwpsaWJyYXJ5KG1hcHZpZXcpCmxpYnJhcnkoc2YpCmxpYnJhcnkodGlncmlzKQoKIyBvdGhlciBwYWNrYWdlcwpsaWJyYXJ5KGhlcmUpCmxpYnJhcnkodmlyaWRpcykKYGBgCgojIyBMb2FkIERhdGEKVGhpcyBub3RlYm9vayByZXF1aXJlcyBhIG51bWJlciBvZiBkYXRhIHNldHM6CgpgYGB7ciBsb2FkLWRhdGF9CiMgc3BhdGlhbCBkYXRhIC0gY2Vuc3VzIHRyYWN0cyB3aXRoIHRvdGFsIGFyZWEgYW5kIHBvcHVsYXRpb24KcG9wIDwtIHN0X3JlYWQoaGVyZSgiZGF0YSIsICJleGFtcGxlLWRhdGEiLCAiU1RMX0RFTU9TX1BvcCIsICJTVExfREVNT1NfUG9wLnNocCIpLAogICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojIHNwYXRpYWwgZGF0YSAtIG5vcnRoIGNpdHkgCm5vcnRoX2NpdHkgPC0gc3RfcmVhZChoZXJlKCJkYXRhIiwgImV4YW1wbGUtZGF0YSIsICJTVExfUkVHSU9OU19Ob3J0aENpdHkiLCAiU1RMX1JFR0lPTlNfTm9ydGhDaXR5LnNocCIpLAogICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojIHRhYnVsYXIgZGF0YSAtIDIwMTIgbXVyZGVycyBpbiBTdC4gTG91aXMKbXVyZGVycyA8LSByZWFkX2NzdihoZXJlKCJkYXRhIiwgImV4YW1wbGUtZGF0YSIsICJTVExfQ1JJTUVfTXVyZGVyczEyLmNzdiIpKQpgYGAKCiMjIFByb2plY3QgdGhlIEhvbWljaWRlIERhdGEKVGhlIGhvbWljaWRlIGRhdGEgYXJlIHRhYnVsYXIsIGFuZCBoYXZlIHR3byBjb2x1bW5zIG5hbWVkIGB4YCBhbmQgYHlgLiBPdXIgZmlyc3QgZ29hbCBpcyB0byBkZXRlcm1pbmUgd2hhdCBwcm9qZWN0aW9uIHRoZXkncmUgZW5jb2RlZCB3aXRoLiBXZSBjYW4gZ2V0IGEgcHJldmlldyBvZiB0aGVpciB2YWx1ZXMgd2l0aCBgc3RyKClgOgoKYGBge3IgcHJldmlldy1ob21pY2lkZXN9CnN0cihtdXJkZXJzKQpgYGAKCldoYXQgcG9zc2liaWxpdGllcyBleGlzdCBmb3IgY29vcmRpbmF0ZSBzeXN0ZW1zPwoKYGBge3IgcHJvamVjdC1ob21pY2lkZXN9CiMgc29sdXRpb24gLSBzdGF0ZSBwbGFuZSBlYXN0LCBmZWV0Cm11cmRlcnNfc2YgPC0gc3RfYXNfc2YobXVyZGVycywgY29vcmRzID0gYygieCIsICJ5IiksIGNycyA9ICIrcHJvaj10bWVyYyArbGF0XzA9MzUuODMzMzMzMzMzMzMzMzQgK2xvbl8wPS05MC41ICtrPTAuOTk5OTMzMzMzMzMzMzMzMyAreF8wPTI1MDAwMCAreV8wPTAgK2VsbHBzPUdSUzgwICtkYXR1bT1OQUQ4MyArdG9fbWV0ZXI9MC4zMDQ4MDA2MDk2MDEyMTkyICtub19kZWZzIikKCiMgdmVyaWZ5IHNvbHV0aW9uCm1hcHZpZXcobXVyZGVyc19zZikKYGBgCgpUaGUgY29ycmVjdCBhbnN3ZXIgaXMgdGhhdCB0aGVzZSBkYXRhIGFyZSBpbiBNaXNzb3VyaSBTdGF0ZSBQbGFuZSBFYXN0IChGZWV0KSwgd2hpY2ggaGFzIGEgQ1JTIHZhbHVlIG9mIDEwMjY5Ni4gVW5mb3J0dW5hdGVseSwgaXQgaXMgbm8gbG9uZ2VyIGFuIGFjY2Vzc2libGUgcHJvamVjdGlvbiBpbiBgUmAsIHNvIHdlIG5lZWQgdG8gdXNlIHRoZSBmdWxsIGBQcm9qNGAgc3RyaW5nLgoKIyMgQ29udmVydCBBbGwgdG8gU2FtZSBQcm9qZWN0aW9uCkN1cnJlbnRseSwgb3VyIGRhdGEgYXJlIGluIHRocmVlIGRpZmZlcmVudCBwcm9qZWN0aW9uIHN5c3RlbXM6CgpgYGByCiMgbXVyZGVycwpzdF9jcnMobXVyZGVyc19zZikKCiMgdHJhY3RzIApzdF9jcnMocG9wKQoKIyBub3J0aCBjaXR5CnN0X2Nycyhub3J0aF9jaXR5KQpgYGAKCkluIG9yZGVyIHRvIGdlb3Byb2Nlc3MgYW5kIG1hcCBvdXIgZGF0YSwgd2Ugd2FudCB0byBjb252ZXJ0IHRoZW0gYWxsIHRvIHRoZSBzYW1lIGNvb3JkaW5hdGUgc3lzdGVtOgoKYGBge3IgY29udmVydC1wcm9qZWN0aW9uc30KIyBtdXJkZXJzCm11cmRlcnNfc2YgPC0gc3RfdHJhbnNmb3JtKG11cmRlcnNfc2YsIGNycyA9IDI2OTE1KQoKIyB0cmFjdHMKcG9wIDwtIHN0X3RyYW5zZm9ybShwb3AsIGNycyA9IDI2OTE1KQoKIyBub3J0aCBjaXR5Cm5vcnRoX2NpdHkgPC0gc3RfdHJhbnNmb3JtKG5vcnRoX2NpdHksIGNycyA9IDI2OTE1KQpgYGAKCldlJ3JlIG5vdyByZWFkeSB0byBtb3ZlIG9uIHRvIG91ciBnZW9wcm9jZXNzaW5nIG9wZXJhdGlvbnMuCgojIyBJZGVudGlmeSBQb2ludHMKT3VyIGZpcnN0IGdvYWwgaXMgdG8gaWRlbnRpZnkgcG9pbnRzIC0gd2Ugd2FudCB0byBsYWJlbCBlYWNoIGhvbWljaWRlIHdpdGggdGhlIHRyYWN0IGlkZW50aWZpY2F0aW9uIG51bWJlciBmb3IgdGhlIHRyYWN0IHRoZSBob21pY2lkZSBvY2N1cnJlZCBpbi4gV2UnbGwgdXNlIGBzdF9pbnRlcnNlY3Rpb24oKWAgZm9yIHRoaXM6CgpgYGB7ciBpZGVudGlmeS1ob21pY2lkZXN9Cm11cmRlcnNfdHJhY3QgPC0gc3RfaW50ZXJzZWN0aW9uKG11cmRlcnNfc2YsIHBvcCkgJT4lCiAgc2VsZWN0KGRhdGUsIGFkZHJlc3MsIEdFT0lEKQpgYGAKCk5vdGljZSBob3cgd2UgdXNlIGBzZWxlY3QoKWAgdG8gc3Vic2V0IG91ciBkYXRhJ3MgY29sdW1ucyBzbyB0aGF0IHdlIGtlZXAgb3VyIG91dHB1dCBkYXRhIGFzIHRpZHkgYXMgcG9zc2libGUuIEFsc28gbm90aWNlIGhvdyB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBkb2VzIG5vdCBjaGFuZ2UuIFRoaXMgaXMgYSBjcml0aWNhbCB0aGluZyB0byBjaGVjaywgYmVjYXVzZSBpdCBsZXRzIHVzIGtub3cgdGhhdCBhbGwgb2YgdGhlIGhvbWljaWRlcyB3ZXJlIGNvcnJlY3RseSBnZW9jb2RlZC4gSWYgYG11cmRlcnNfdHJhY3RgIGhhZCBhIHNtYWxsZXIgbnVtYmVyIG9mIGhvbWljaWRlcywgdGhhdCB3b3VsZCBsZXQgdXMga25vdyB0aGF0IHNvbWUgaG9taWNpZGVzIG9jY3VyZWQgb3V0c2lkZSBvZiB0aGUgQ2Vuc3VzIHRyYWN0IGJvdW5kYXJpZXMuCgpXZSBjYW4gbm93IHByZXZpZXcgdGhlc2UgZGF0YSBhbmQgc2VlIHRoZSBjaGFuZ2U6CgpgYGB7ciBwcmV2aWV3LWlkZW50aWZ5fQptYXB2aWV3KG11cmRlcnNfdHJhY3QpCmBgYAoKIyMgQWdncmVnYXRlIFBvaW50cwpXaXRoIGlkZW50aWZpZXJzIGFwcGxpZWQgdG8gb3VyIGRhdGEsIHdlIGNhbiBhZ2dyZWdhdGUgdGhlbSBpZiB3ZSBhbHNvIHdhbnQgY291bnRzIG9mIGhvbWljaWRlcyBieSB0cmFjdC4gCgpgYGB7ciBhZ2dyZWdhdGV9CiMgYWdncmVnYXRlCm11cmRlcnNfdHJhY3QgJT4lCiAgZ3JvdXBfYnkoR0VPSUQpICU+JQogIHN1bW1hcmlzZShob21pY2lkZXMgPSBuKCkpIC0+IG11cmRlcnNCeVRyYWN0CgojIHJlbW92ZSBnZW9tZXRyeQpzdF9nZW9tZXRyeShtdXJkZXJzQnlUcmFjdCkgPC0gTlVMTAoKIyBqb2luIGRhdGEgYW5kIHJlcGxhY2UgbmEncwptdXJkZXJQb3AgPC0gbGVmdF9qb2luKHBvcCwgbXVyZGVyc0J5VHJhY3QsIGJ5ID0gIkdFT0lEIikgJT4lCiAgbXV0YXRlKGhvbWljaWRlcyA9IGlmZWxzZShpcy5uYShob21pY2lkZXMpID09IFRSVUUsIDAsIGhvbWljaWRlcykpCmBgYAoKSXQncyByZWFsbHkgaW1wb3J0YW50IHRvIGNvbnNpZGVyIHdoZXRoZXIgYE5BYCB2YWx1ZXMgc2hvdWxkIGJlIHJlcGxhY2VkIHdpdGggemVyb3MuIEluIHRoaXMgY2FzZSwgd2UgY29uc2lkZXIgdGhlIENpdHkncyBkYXRhIG9uIGNyaW1lcyBhdXRob3JpdGF0aXZlLCBhbmQgc28gaW5mZXIgdGhhdCBpZiBhIHRyYWN0IGhhcyBubyBtdXJkZXJzLCBpdCBtZWFucyB0aGF0IHRoZXJlIHdlcmUgemVybyBob21pY2lkZXMgdGhlcmUuIFdlIGNhbm5vdCBhbHdheXMgbWFrZSB0aGlzIGFzc3VtcHRpb24sIGhvd2V2ZXIuIENvbnNpZGVyIGdyYWZmaXRpIGNhbGxzIGZvciBzZXJ2aWNlIGluIHRoZSBDU0IgZGF0YSBmb3IgdGhlIGZpbmFsIHByb2plY3QuIERvZXMgYE5BYCBtZWFuIG5vIGdyYWZmaXRpLCBvciBubyBjYWxscz8gQ29uc2lkZXJpbmcgdGhlIG1lYW5pbmcgb2YgYE5Bc2AgaXMgc28gaW1wb3J0YW50IGFzIHlvdSBjbGVhbiBkYXRhLgoKIyMjIFBsb3QgRGF0YQpXZSBjYW4gbm93IHBsb3QgaG9taWNpZGVzIGJ5IHBvcHVsYXRpb24gZGVuc2l0eToKCmBgYHtyIG11cmRlci1wb3B1bGF0aW9uLWRlbnNpdHl9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBtdXJkZXJQb3AsIG1hcHBpbmcgPSBhZXMoZmlsbCA9IChob21pY2lkZXMvUE9QX0UpKjEwMDApKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKCkKYGBgCgpXZSBjYW4gYWxzbyBwbG90IGJ5IGFyZWEgZGVuc2l0eToKCmBgYHtyIG11cmRlci1hcmVhLWRlbnNpdHl9CmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBtdXJkZXJQb3AsIG1hcHBpbmcgPSBhZXMoZmlsbCA9IGhvbWljaWRlcy9TUUtNKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcygpCmBgYAoKIyMgU2VsZWN0aW5nIGJ5IEFyZWEKSWYgd2Ugd2FudCBhIGRhdGEgc2V0IG9mIG9ubHkgaG9taWNpZGVzIGZvciBhIGNlcnRhaW4gYXJlYSwgbGlrZSBOb3J0aCBDaXR5LCAqYW5kKiB3ZSBoYXZlIHRoZSBnZW9tZXRyaWMgZGF0YSBmb3IgdGhhdCByZWdpb24sIHdlIGNhbiBzdWJzZXQgb3VyIGRhdGEgYnkgdGhhdCBnZW9tZXRyaWMgYXJlYS4KCmBgYHtyIHNlbGVjdC1ieS1hcmVhfQptdXJkZXJzX25jIDwtIHN0X2ludGVyc2VjdGlvbihtdXJkZXJzX3NmLCBub3J0aF9jaXR5KQpgYGAKClJlbWVtYmVyIHRoYXQgd2Ugc2hvdWxkIGV4cGVjdCBgbXVyZGVyc19uY2AgdG8gc2hyaW5rIGluIHRlcm1zIG9mIGl0cyBvdmVyYWxsIG51bWJlciBvZiBvYnNlcnZhdGlvbnMsIHNpbmNlIG5vdCBhbGwgaG9taWNpZGVzIG9jY3VyIGluIE5vcnRoIENpdHkuCgojIyBSZXBsaWNhdGluZyBEZXNrdG9wIEdJUyBJbnRlcnNlY3RzCklmIHdlIHdlcmUgdXNpbmcgYSBkZXNrdG9wIEdJUyB0b29sLCB0aGUgaW50ZXJzZWN0IGZ1bmN0aW9uYWxpdHkgd291bGQgcmV0dXJuIGFsbCBvZiB0aGUgcG9pbnRzIG9mIGhvbWljaWRlcyBhZnRlciBhbiBpbnRlcnNlY3Qgd2l0aCBvdXIgTm9ydGggQ2l0eSBkYXRhLiBJZiB3ZSB3YW50IHRvIHJlcGxpY2F0ZSB0aGlzIGZ1bmN0aW9uYWxpdHksIHdlIGNhbiBhZGQgYHN0X2RpZmZlcmVuY2UoKWAgaW50byBvdXIgd29ya2Zsb3cuIFRoaXMgd2lsbCBnaXZlIHVzIHRoZSBob21pY2lkZXMgdGhhdCBkaWQgbm90IGZhbGwgaW4gTm9ydGggQ2l0eToKCmBgYHtyIHNlbGVjdC1ieS1kaWZmZXJlbmNlfQptdXJkZXJzX25vdF9uYyA8LSBzdF9kaWZmZXJlbmNlKG11cmRlcnNfc2YsIG5vcnRoX2NpdHkpICU+JQogIG11dGF0ZShyZWdpb24gPSAiU291dGggQ2l0eSIpCmBgYAoKSXQgaXMgaW1wb3J0YW50IHRvIG1vZGlmeSB0aGUgYHJlZ2lvbmAgb3V0cHV0IGJlY2F1c2UgYHN0X2RpZmZlcmVuY2UoKWAgY29tYmluZXMgdGhlIGF0dHJpYnV0ZSB0YWJsZSwgaW5hZHZlcnRlbnRseSBsYWJlbGluZyBvdXIgaG9taWNpZGVzIHdpdGggYHJlZ2lvbmAgYmVpbmcgc2V0IGVxdWFsIHRvIGBOb3J0aCBDaXR5YC4gVGhpcyBpcyBub3QgdGhlIGFjdHVhbCBkZXNpcmVkIG91dGNvbWUuCgpPbmNlIHdlIGhhdmUgb3VyIGRpZmZlcmVuY2UgZGF0YSwgd2UgY2FuIGJpbmQgdGhlbSB0b2dldGhlcjoKCmBgYHtyIGJpbmR9Cm11cmRlcnNfaW50ZXJzZWN0IDwtIHJiaW5kKG11cmRlcnNfbmMsIG11cmRlcnNfbm90X25jKSAlPiUKICBhcnJhbmdlKGRhdGUpCmBgYAoKVGhlIGBhcnJhbmdlKClgIGNhbGwgcHV0cyBvdXIgb2JzZXJ2YXRpb25zIGJhY2sgaW4gdGVtcG9yYWwgb3JkZXIuCgojIyBJbnRlcnNlY3RzIHdpdGggT3RoZXIgVHlwZXMgb2YgR2VvbWV0cmljIERhdGEKSSB3YW50IHRvIHF1aWNrbHkgaWxsdXN0cmF0ZSBob3cgaW50ZXJzZWN0cyBiZWhhdmUgd2hlbiB3ZSBoYXZlIG90aGVyIHR5cGVzIG9mIGdlb21ldHJpYyBkYXRhLiAKCiMjIyBMaW5lIERhdGEKRmlyc3QsIGxldCdzIGlsbHVzdHJhdGUgaW50ZXJzZWN0cyB3aXRoIGxpbmUgYW5kIHBvbHlnb24gZGF0YS4gV2UnbGwgdXNlIGB0aWdyaXNgIHRvIGdldCBzb21lIHN0cmVldCBkYXRhIGZvciBTdC4gTG91aXMsIGFuZCB0aGVuIGludGVyc2VjdCBpdCB3aXRoIG91ciBOb3J0aCBDaXR5IHBvbHlnb24uIEZpcnN0LCB3ZSdsbCBkb3dubG9hZCBhbmQgd3JhbmdsZSBvdXIgc3RyZWV0IGRhdGE6CgpgYGB7ciBnZXQtcm9hZHN9CiMjIGRvd25sb2FkCnJvYWRzIDwtIHJvYWRzKHN0YXRlID0gMjksIGNvdW50eSA9IDUxMCkgJT4lCiAgc3RfdHJhbnNmb3JtKGNycyA9IDI2OTE1KSAlPiUKICBzZWxlY3QoTElORUFSSUQsIEZVTExOQU1FKQoKIyMgcHJldmlldwptYXB2aWV3KHJvYWRzKQpgYGAKCk5vdyB0aGF0IHdlIGhhdmUgc29tZSBsaW5lIGRhdGEsIGxldHMgaWRlbnRpZnkgc3RyZWV0IGNlbnRlcmxpbmVzIGluIE5vcnRoIENpdHk6CgpgYGB7ciBpbnRlcnNlY3Qtcm9hZHN9CiMjIGludGVyc2VjdApyb2Fkc19uYyA8LSBzdF9pbnRlcnNlY3Rpb24ocm9hZHMsIG5vcnRoX2NpdHkpCgojIyBwcmV2aWV3Cm1hcHZpZXcocm9hZHNfbmMpCmBgYAoKT3VyIG1hcCBpcyBibGFuayEgU29tZXRpbWVzLCBnZW9tZXRyaWMgb3BlcmF0aW9ucyBjaGFuZ2Ugb3VyIGRhdGEgZnJvbSBgUE9JTlRgLCBgTElORVNUUklOR2AsIG9yIGBQT0xZR09OYCB0byB3aGF0IHdlIGNhbGwgImdlb21ldHJ5IGNvbGxlY3Rpb25zLiIgVGhlc2UgZGF0YSBkbyBub3QgbWFwIGFzIGV4cGVjdGVkLiBXZSBjYW4gZXh0cmFjdCB0aGVzZSBiYWNrIHRvIHRoZWlyIGRlc2lyZWQgdHlwZSBvZiBnZW9tZXRyeSBhbmQgdGhlbiB0aGV5IHNob3VsZCBwcmV2aWV3OgoKYGBge3IgaW50ZXJzZWN0LXJvYWRzLWNvcnJlY3R9CiMjIHJlcGFpcgpyb2Fkc19uYyA8LSBzdF9jb2xsZWN0aW9uX2V4dHJhY3Qocm9hZHNfbmMsIHR5cGUgPSAiTElORVNUUklORyIpCgojIyBwcmV2aWV3Cm1hcHZpZXcocm9hZHNfbmMpCmBgYAoKSG0sIHRoaXMgaXNuJ3QgaWRlYWwgZWl0aGVyLiBUaGlzIGJlaGF2aW9yLCBvbmNlIHdlJ3ZlIHZpc3VhbGx5IHZlcmlmaWVkIHRoYXQgb3VyIGRhdGEgaGF2ZSB0aGUgY29ycmVjdCBgZ2VvbWV0cnlgIHR5cGUsIGFwcGVhcnMgdG8gYmUgYSBidWcgaW4gYG1hcHZpZXdgLiBUcnkgcnVubmluZyB0aGUgY29kZSBpbiB5b3VyIGNvbnNvbGUgaW5zdGVhZCEgSXQgc2hvdWxkIG1hcCBjb3JyZWN0bHkuCgojIyMgUG9seWdvbiBEYXRhCk5leHQsIGxldCdzIGlsbHVzdHJhdGUgYW4gaW50ZXJzZWN0IHdpdGggdHdvIHNldHMgb2YgcG9seWdvbiBkYXRhLiBXZSdsbCBnZXQgdGhlIHBvcnRpb25zIG9mIGNlbnN1cyB0cmFjdHMgdGhhdCBsaWUgaW4gTm9ydGggQ2l0eToKCmBgYHtyIGludGVyc2VjdC10cmFjdHN9CiMjIGludGVyc2VjdAp0cmFjdHNfbmMgPC0gc3RfaW50ZXJzZWN0aW9uKHBvcCwgbm9ydGhfY2l0eSkgJT4lCiAgc3RfY29sbGVjdGlvbl9leHRyYWN0KHR5cGUgPSAiUE9MWUdPTiIpCgojIyBwcmV2aWV3Cm1hcHZpZXcodHJhY3RzX25jKQpgYGAKCkV4Y2VsbGVudCEKCmBgYHtyIG1vdmUtdG8tZG9jcywgaW5jbHVkZT1GQUxTRX0KIyB5b3UgZG8gbmVlZCB0byBpbmNsdWRlIHRoaXMgaW4gYW55IG5vdGVib29rIHlvdSBjcmVhdGUgZm9yIHRoaXMgY2xhc3MKZnM6OmZpbGVfY29weShoZXJlOjpoZXJlKCJleGFtcGxlcyIsICJtZWV0aW5nLWV4YW1wbGVzLWNvbXBsZXRlLm5iLmh0bWwiKSwgCiAgICAgICAgICAgICAgaGVyZTo6aGVyZSgiZG9jcyIsICJpbmRleC5uYi5odG1sIiksIAogICAgICAgICAgICAgIG92ZXJ3cml0ZSA9IFRSVUUpCmBgYAo=