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=