Welcome to our mapping unit! Mapping in R is easy if you know how to use ggplot2, as most maps will just build on our ggplots by adding an additional geom layer that is specific to spatial data.

In R, most spatial analysis uses the SF package, which stands for Simple Features. This is the type of data that we will store spatial information in - if you’ve worked with GIS, this is what R calls a shapefile. The main difference between an SF object and a dataframe is that an SF object has a column at the end called “Geometry” that specifies the spatial information for each row of the object.

We’ll explore mapping in R using historical redlining data in US cities from the Mapping Inequality project at the University of Richmond. See below for a description of the Redlining grades made by the Home Owners’ Loan Corporation:

“HOLC staff members, using data and evaluations organized by local real estate professionals—lenders, developers, and real estate appraisers—in each city, assigned grades to residential neighborhoods that reflected their”mortgage security” that would then be visualized on color-coded maps. Neighborhoods receiving the highest grade of “A”—colored green on the maps—were deemed minimal risks for banks and other mortgage lenders when they were determining who should received loans and which areas in the city were safe investments. Those receiving the lowest grade of “D,” colored red, were considered “hazardous.””

Redlining in urban areas across the US is responsible for systematically excluding the poor, families of color, immigrants, and other minoritized groups from accessing homeownership. In some places, this caused widespread housing insecurity for these households, as they had no choice but to rent low-quality housing that was available in their neighborhoods. Excluding entire groups from homeownership has also limited their access to building intergenerational wealth. A significant racial wealth gap persists today, even as families of color have experienced increased incomes and access to opportunity - the legacies of redlining are widespread.

library(sf)     
library(dplyr)   
library(spData) 
library(urbnmapr)
library(ggplot2)
library(sp)
library(ggmap)
library(ggthemes)
library(stringr)

setwd("~/Binghamton/harp325/shapefile")
Warning: The working directory was changed to C:/Users/mhaller/Documents/Binghamton/harp325/shapefile inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
map <- st_read("holc_ad_data.shp")
Reading layer `holc_ad_data' from data source 
  `C:\Users\mhaller\Documents\Binghamton\Harp325\shapefile\holc_ad_data.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 8878 features and 7 fields (with 3 geometries empty)
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -122.7675 ymin: 25.70537 xmax: -70.9492 ymax: 47.72251
Geodetic CRS:  WGS 84
#load the county base map
counties <- get_urbn_map("counties", sf = TRUE)

#Here, I'm changing the CRS of both layers so that they match
#EPSG codes specify the projection of the map
#You can find some state-level epsg codes here: https://github.com/veltman/d3-stateplane
#The espg code I've chosen is for the entire US, and is fine for this map
#I'd definitely pick a different projection if I were using this in a paper or assignment

map <- map %>% st_transform("EPSG:4269")
counties <- counties %>% st_transform("EPSG:4269")
old-style crs object detected; please recreate object with a recent sf::st_crs()
ggplot()+
  geom_sf(counties, mapping = aes(), fill = "gray", color = "white")+
  geom_sf(map, mapping = aes(), fill = "red", color = "red")+
  theme_minimal()

Ok, clearly we are zoomed out way too far! Let’s pick a city to plot. I’ll start with my hometown, Rochester, NY. We’ll do an activity after this so that you can plot a city of your choice as well.

#I need to include this option to turn off spherical geometry
#don't worry about this too much - R won't let us focus on one city unless we do
sf_use_s2(FALSE)

#I'll grab rochester's county, Monroe, for plotting
rochester <- counties %>% filter(county_name == "Monroe County" & state_name == "New York")

#we need to also filter the redlining layer by Rochester - the simplest way to do this is with brackets, like this:
#this only works because we turned off spherical geometry
roc_redline <- map[rochester, ]
although coordinates are longitude/latitude, st_intersects assumes that they are planar
#Before I map it, I want to check the dimensions of the redlining layer, so I can zoom in my map 

roc_redline
Simple feature collection with 61 features and 7 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -77.6922 ymin: 43.10451 xmax: -77.51284 ymax: 43.27137
Geodetic CRS:  NAD83
First 10 features:
     state      city                                  name holc_id
5961    NY Rochester      Shore Front Creece and Rochester      A1
5962    NY Rochester                              Brighton     A10
5963    NY Rochester                 Meadow Brook Brighton     A11
5964    NY Rochester                                  <NA>     A12
5965    NY Rochester Boulevard Manor- Maywood, Irondequoit      A2
5966    NY Rochester          Winona Boulevard Irondequoit      A3
5967    NY Rochester                     Koda Vista Greece      A4
5968    NY Rochester              Latona Homesteads Greece      A5
5969    NY Rochester                 East Avenue Rochester      A6
5970    NY Rochester                Brightondale Rochester      A7
     holc_grade neighborho
5961          A       2367
5962          A       2422
5963          A       2407
5964          A       2425
5965          A       2415
5966          A       2414
5967          A       2410
5968          A       2409
5969          A       2399
5970          A       2405
                                                                                                                                                                                                                                                             area_descr
5961 { "72" : "$19.54 Greece, $41.73 Roch. No sales reported in \\"A\\" areas in Greece. Effective Tax Rate for all \\"A\\" areas in Rochester based on 3 sales $50.49.", "8" : "A small area advantageously located on the lake front at some distance from the center
5962     { "2j" : " Fair up to $10,000 on 2nd hand", "2l" : " *60-75", "3" : "3 On order 1-family at $20,000+", "9" : "11/1/39 A-10 High 1st Brighton", "2m" : "(+)11   *65-85", "1a" : " Slowly ", "5" : "3", "8" : "There are actually two parts to this area. The fi
5963     { "2o" : "Good up to $60 ", "2i" : " 0 4500-12,000 1939 ", "2n" : "0 1939 *50-75 (*Small homes only)  ", "2m" : "- No rentals  ", "2j" : "Fair up to $9,000 on 2nd hand, Good up to $9,000 on new ", "2k" : "Stable ", "2l" : "None in existence ", "0" : "Roc
5964     { "3" : " 0 ", "71" : "9", "4a" : "0", "10" : "", "2" : "100 ", "2a" : "Medium sized 1-family 2-2 1/2 stories, 7-10 rooms ", "0" : "Rochester, New York", "9" : "11/1/39 A-12 Clover Hills, Brighton & Pittsford 1st", "6" : "Ample", "1d" : "0", "1c" : "0 ",
5965     { "2c" : "Up to 20 years ", "2j" : " Fair up to $8,000", "71" : "9", "8" : "Although this area dates back over a considerable period, more recent years have witnessed a very substantial amount of speculative development work which still continues. The ol
5966   { "1d" : "0", "4b" : "24", "2o" : " Not a rental area", "2a" : " Moderate size, one-family 1 1/2-2 1/2 stories, 5-7 rooms", "2h" : "5500-7500   -", "2k" : " Stable", "72" : "16.93, Effective Tax Rate for all \\"A\\" areas in Irondequoit based on 8 sales $1
5967     { "5" : "3", "2c" : "Up to 10 years ", "71" : "9", "9" : "Low 1st Koda Vista, Greece 11/1/39 A-4", "5b" : "11", "2f" : " 96%", "4b" : "19", "4a" : "0", "1c" : "0 ", "10" : "", "2a" : " Moderate sized, 1-family 2-2 1/2 stories, 5-7 rooms", "1b" : "Foremen
5968     { "1b" : "Better paid employees of Eastman Kodak Co. with incomes from $2500 to $4000", "4b" : "0", "4a" : "0", "5b" : "0", "10" : "", "1d" : "0", "2g" : " None in existence", "2a" : " Small 1-family 1 1/2-2-story, 5-6 rooms", "2d" : " Excellent", "2b" :
5969   { "2i" : " 1939 (-)19  *12,500-40,000, *Nominal", "4a" : "0", "72" : "41.73, Effective Tax Rate for all \\"A\\" areas in ROchester based on 3 sales $50.49.", "5" : "3", "2m" : "  - No rentals", "2e" : "98% ", "2d" : "Excellent ", "1a" : "  Yes", "2p" : " "
5970     { "1d" : "0", "1b" : "Executives & white-collar whose incomes range from $3000 to $10,000", "2k" : " Stable in lower bracket & downward in upper", "2d" : " Excellent", "9" : "11/1/39 1st A-7 Brightondale, Rochester", "71" : "9", "2n" : "  65-125 0 1939",
                           geometry
5961 MULTIPOLYGON (((-77.61829 4...
5962 MULTIPOLYGON (((-77.53257 4...
5963 MULTIPOLYGON (((-77.57404 4...
5964 MULTIPOLYGON (((-77.54934 4...
5965 MULTIPOLYGON (((-77.59907 4...
5966 MULTIPOLYGON (((-77.60402 4...
5967 MULTIPOLYGON (((-77.66021 4...
5968 MULTIPOLYGON (((-77.68379 4...
5969 MULTIPOLYGON (((-77.56185 4...
5970 MULTIPOLYGON (((-77.57222 4...
#let's map it using those coordinates!
ggplot()+
  geom_sf(rochester, mapping = aes(), fill = "gray80")+
  geom_sf(roc_redline, mapping = aes(fill = holc_grade), color = "white")+
  theme_minimal()+
  labs(fill = "Redline Score", title = "Redlining in Rochester, NY")+
  theme(plot.title = element_text(hjust = 0.5))+
  ylim(43.1, 43.3)+
  xlim(-77.7, -77.5)

We can do much better than this in R! One issue is that there ins’t a base map here - if you’re familiar with the area, it would be helpful to see the locations of streets and major highways. We can do this using the ggmap package.

#set lat/long of base map
LAT1 = 43.1
LAT2 = 43.3
LON1 = -77.7
LON2 = -77.5

#create basemap - I like the terrain-lines map in ggmap
#you can play around with it on your own. Other basemaps can be found in ?get_stamenmap
#first, set lat/long coordinates of base map
base_map <- get_stamenmap(bbox = c(left = LON1, right = LON2, 
                                   bottom = LAT1, top = LAT2),
                          #this zoom works well for a smaller city
                          #I recommend starting at 10 and increasing by 1 to find                             the best zoom option
                          zoom = 12, 
                          #pick the map type you want
                          maptype = "terrain-lines")
i Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.
#let's make a nice color palette for the legend
colors <- c("seagreen", "seagreen1", "yellow", "tomato")

#use the ggmap function instead of ggplot2 to map the base map
ggmap(base_map)+
  geom_sf(roc_redline, mapping = aes(fill = holc_grade), color = "white",
  #we need to include inheret.aes=F here or our geom_sf layer won't work
  inherit.aes = FALSE, alpha = 0.6)+
  theme_minimal()+
  labs(fill = "Redline Score", title = "Redlining in Rochester, NY")+
  theme(plot.title = element_text(hjust = 0.5))+
  scale_fill_manual(values = colors)
Coordinate system already present. Adding new coordinate system, which will replace the existing one.

Before we move on to the activity, I want to also show you how to map data at the national level. I’ll illustrate this using the inequality data that we’ve used previously from Opportunity Insights.

setwd("~/binghamton/geog380")
Warning: The working directory was changed to C:/Users/mhaller/Documents/binghamton/geog380 inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
data <- read.csv("county_data.csv")

#We'll need to merge this dataset with the county base map
#Because it doesn't have any geographic info of its own to use to map it
#To do this, we need 1-2 columns that identify each unique county in each 
#We'll use county_name and state_name
#The county base map has some words (e.g. County, Parish etc.) and symbols (the . after St.) that our data doesn't have - we have to clean it up, or we can't merge the dataframes
counties1 <- counties %>% 
  #remove unecessary words and the extra space 
  mutate(county_name = str_remove_all(county_name, " County| Parish| Borough| Census Area| Municipality")) %>% 
  #get rid of the period using gsub
  mutate(county_name = gsub("[.]", "", county_name)) 

#the column names need to match for this method - only statename and state_name don't match in the two dataframes
colnames(data)[3] <- "state_name"

#merge the data! Always put the sf object first, or you will lose the geogrpahic information in the merge
data_merged <- merge(counties1, data, by = c("county_name", "state_name"))

#map it!
ggplot()+
  geom_sf(counties, mapping = aes(), fill = "gray", color = "white")+
  geom_sf(data_merged, mapping = aes(fill = poverty_rate), color = "white")+
  theme_map()+
  scale_fill_gradient(low = "white", high = "tomato3")+
  labs(title = "Poverty Rate in US Counties, 2000",
       fill = "Poverty Rate")+
  theme(plot.title = element_text(hjust = 0.5))

We’re still missing a few counties! We’d need to go in manually to check which counties aren’t merging properly. This is a fairly tedious process. Why don’t we try mapping this at the state level instead?

states <- get_urbn_map("states", sf = TRUE)

#aggregate poverty data to the state level
pov_data <- data %>% select(state_name, poverty_rate) %>% 
  group_by(state_name) %>% 
  summarise(mean_poverty = mean(poverty_rate))

#oops, the District of Columbia doesn't match up in both dataframes - the Of is capitalized in pov_data but not in states, let's fix it
pov_data[9,1] <- "District of Columbia"

#now merge it
pov_merged <- merge(states, pov_data, by = "state_name")
old-style crs object detected; please recreate object with a recent sf::st_crs()
#Make the final map
ggplot()+
  geom_sf(states, mapping = aes(), fill = "gray", color = "white")+
  geom_sf(pov_merged, mapping = aes(fill = mean_poverty), color = "white")+
  theme_map()+
  scale_fill_gradient(low = "white", high = "tomato3")+
  labs(title = "Poverty Rate in US States, 2000",
       fill = "Mean Poverty Rate")+
  theme(plot.title = element_text(hjust = 0.5))

states
Simple feature collection with 51 features and 3 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -2600000 ymin: -2363000 xmax: 2516374 ymax: 732352.2
old-style crs object detected; please recreate object with a recent sf::st_crs()
Projected CRS: NAD27 / US National Atlas Equal Area
First 10 features:
old-style crs object detected; please recreate object with a recent sf::st_crs()
old-style crs object detected; please recreate object with a recent sf::st_crs()
   state_fips state_abbv  state_name                       geometry
1          01         AL     Alabama MULTIPOLYGON (((1150023 -15...
2          04         AZ     Arizona MULTIPOLYGON (((-1386136 -1...
3          08         CO    Colorado MULTIPOLYGON (((-786661.9 -...
4          09         CT Connecticut MULTIPOLYGON (((2156197 -83...
5          12         FL     Florida MULTIPOLYGON (((1953691 -20...
6          13         GA     Georgia MULTIPOLYGON (((1308636 -10...
7          16         ID       Idaho MULTIPOLYGON (((-1357097 78...
8          18         IN     Indiana MULTIPOLYGON (((1042064 -71...
9          20         KS      Kansas MULTIPOLYGON (((-174904.2 -...
10         22         LA   Louisiana MULTIPOLYGON (((1075669 -15...

Resources

Data: Mapping Inequality, Redlining New Deal in America. https://dsl.richmond.edu/panorama/redlining/

Data: Opportunity Insights. https://opportunityinsights.org/

Urban Institute (2022). Introduction to Mapping. Retrieved from: https://urbaninstitute.github.io/r-at-urban/mapping.html.

Kahle, D., Wickham, H., Jackson, S. & Korpela, M. (2023). Ggmap. Retrieved from: https://cran.r-project.org/web/packages/ggmap/ggmap.pdf. Read More: https://github.com/dkahle/ggmap

LS0tDQp0aXRsZTogIlRvcGljIDU6IE1hcHBpbmciDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KV2VsY29tZSB0byBvdXIgbWFwcGluZyB1bml0ISBNYXBwaW5nIGluIFIgaXMgZWFzeSBpZiB5b3Uga25vdyBob3cgdG8gdXNlIGdncGxvdDIsIGFzIG1vc3QgbWFwcyB3aWxsIGp1c3QgYnVpbGQgb24gb3VyIGdncGxvdHMgYnkgYWRkaW5nIGFuIGFkZGl0aW9uYWwgZ2VvbSBsYXllciB0aGF0IGlzIHNwZWNpZmljIHRvIHNwYXRpYWwgZGF0YS4gDQoNCkluIFIsIG1vc3Qgc3BhdGlhbCBhbmFseXNpcyB1c2VzIHRoZSBTRiBwYWNrYWdlLCB3aGljaCBzdGFuZHMgZm9yIFNpbXBsZSBGZWF0dXJlcy4gVGhpcyBpcyB0aGUgdHlwZSBvZiBkYXRhIHRoYXQgd2Ugd2lsbCBzdG9yZSBzcGF0aWFsIGluZm9ybWF0aW9uIGluIC0gaWYgeW91J3ZlIHdvcmtlZCB3aXRoIEdJUywgdGhpcyBpcyB3aGF0IFIgY2FsbHMgYSBzaGFwZWZpbGUuIFRoZSBtYWluIGRpZmZlcmVuY2UgYmV0d2VlbiBhbiBTRiBvYmplY3QgYW5kIGEgZGF0YWZyYW1lIGlzIHRoYXQgYW4gU0Ygb2JqZWN0IGhhcyBhIGNvbHVtbiBhdCB0aGUgZW5kIGNhbGxlZCAiR2VvbWV0cnkiIHRoYXQgc3BlY2lmaWVzIHRoZSBzcGF0aWFsIGluZm9ybWF0aW9uIGZvciBlYWNoIHJvdyBvZiB0aGUgb2JqZWN0Lg0KDQpXZSdsbCBleHBsb3JlIG1hcHBpbmcgaW4gUiB1c2luZyBoaXN0b3JpY2FsIHJlZGxpbmluZyBkYXRhIGluIFVTIGNpdGllcyBmcm9tIHRoZSBNYXBwaW5nIEluZXF1YWxpdHkgcHJvamVjdCBhdCB0aGUgVW5pdmVyc2l0eSBvZiBSaWNobW9uZC4gU2VlIGJlbG93IGZvciBhIGRlc2NyaXB0aW9uIG9mIHRoZSBSZWRsaW5pbmcgZ3JhZGVzIG1hZGUgYnkgdGhlIEhvbWUgT3duZXJzJyBMb2FuIENvcnBvcmF0aW9uOg0KDQoiSE9MQyBzdGFmZiBtZW1iZXJzLCB1c2luZyBkYXRhIGFuZCBldmFsdWF0aW9ucyBvcmdhbml6ZWQgYnkgbG9jYWwgcmVhbCBlc3RhdGUgcHJvZmVzc2lvbmFsc+KAlGxlbmRlcnMsIGRldmVsb3BlcnMsIGFuZCByZWFsIGVzdGF0ZSBhcHByYWlzZXJz4oCUaW4gZWFjaCBjaXR5LCBhc3NpZ25lZCBncmFkZXMgdG8gcmVzaWRlbnRpYWwgbmVpZ2hib3Job29kcyB0aGF0IHJlZmxlY3RlZCB0aGVpciAibW9ydGdhZ2Ugc2VjdXJpdHkiIHRoYXQgd291bGQgdGhlbiBiZSB2aXN1YWxpemVkIG9uIGNvbG9yLWNvZGVkIG1hcHMuIE5laWdoYm9yaG9vZHMgcmVjZWl2aW5nIHRoZSBoaWdoZXN0IGdyYWRlIG9mICJBIuKAlGNvbG9yZWQgZ3JlZW4gb24gdGhlIG1hcHPigJR3ZXJlIGRlZW1lZCBtaW5pbWFsIHJpc2tzIGZvciBiYW5rcyBhbmQgb3RoZXIgbW9ydGdhZ2UgbGVuZGVycyB3aGVuIHRoZXkgd2VyZSBkZXRlcm1pbmluZyB3aG8gc2hvdWxkIHJlY2VpdmVkIGxvYW5zIGFuZCB3aGljaCBhcmVhcyBpbiB0aGUgY2l0eSB3ZXJlIHNhZmUgaW52ZXN0bWVudHMuIFRob3NlIHJlY2VpdmluZyB0aGUgbG93ZXN0IGdyYWRlIG9mICJELCIgY29sb3JlZCByZWQsIHdlcmUgY29uc2lkZXJlZCAiaGF6YXJkb3VzLiIiDQoNClJlZGxpbmluZyBpbiB1cmJhbiBhcmVhcyBhY3Jvc3MgdGhlIFVTIGlzIHJlc3BvbnNpYmxlIGZvciBzeXN0ZW1hdGljYWxseSBleGNsdWRpbmcgdGhlIHBvb3IsIGZhbWlsaWVzIG9mIGNvbG9yLCBpbW1pZ3JhbnRzLCBhbmQgb3RoZXIgbWlub3JpdGl6ZWQgZ3JvdXBzIGZyb20gYWNjZXNzaW5nIGhvbWVvd25lcnNoaXAuIEluIHNvbWUgcGxhY2VzLCB0aGlzIGNhdXNlZCB3aWRlc3ByZWFkIGhvdXNpbmcgaW5zZWN1cml0eSBmb3IgdGhlc2UgaG91c2Vob2xkcywgYXMgdGhleSBoYWQgbm8gY2hvaWNlIGJ1dCB0byByZW50IGxvdy1xdWFsaXR5IGhvdXNpbmcgdGhhdCB3YXMgYXZhaWxhYmxlIGluIHRoZWlyIG5laWdoYm9yaG9vZHMuIEV4Y2x1ZGluZyBlbnRpcmUgZ3JvdXBzIGZyb20gaG9tZW93bmVyc2hpcCBoYXMgYWxzbyBsaW1pdGVkIHRoZWlyIGFjY2VzcyB0byBidWlsZGluZyBpbnRlcmdlbmVyYXRpb25hbCB3ZWFsdGguIEEgc2lnbmlmaWNhbnQgcmFjaWFsIHdlYWx0aCBnYXAgcGVyc2lzdHMgdG9kYXksIGV2ZW4gYXMgZmFtaWxpZXMgb2YgY29sb3IgaGF2ZSBleHBlcmllbmNlZCBpbmNyZWFzZWQgaW5jb21lcyBhbmQgYWNjZXNzIHRvIG9wcG9ydHVuaXR5IC0gdGhlIGxlZ2FjaWVzIG9mIHJlZGxpbmluZyBhcmUgd2lkZXNwcmVhZC4gDQpgYGB7cn0NCmxpYnJhcnkoc2YpICAgICANCmxpYnJhcnkoZHBseXIpICAgDQpsaWJyYXJ5KHNwRGF0YSkgDQpsaWJyYXJ5KHVyYm5tYXByKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShzcCkNCmxpYnJhcnkoZ2dtYXApDQpsaWJyYXJ5KGdndGhlbWVzKQ0KbGlicmFyeShzdHJpbmdyKQ0KDQpzZXR3ZCgifi9CaW5naGFtdG9uL2hhcnAzMjUvc2hhcGVmaWxlIikNCm1hcCA8LSBzdF9yZWFkKCJob2xjX2FkX2RhdGEuc2hwIikNCg0KI2xvYWQgdGhlIGNvdW50eSBiYXNlIG1hcA0KY291bnRpZXMgPC0gZ2V0X3VyYm5fbWFwKCJjb3VudGllcyIsIHNmID0gVFJVRSkNCg0KI0hlcmUsIEknbSBjaGFuZ2luZyB0aGUgQ1JTIG9mIGJvdGggbGF5ZXJzIHNvIHRoYXQgdGhleSBtYXRjaA0KI0VQU0cgY29kZXMgc3BlY2lmeSB0aGUgcHJvamVjdGlvbiBvZiB0aGUgbWFwDQojWW91IGNhbiBmaW5kIHNvbWUgc3RhdGUtbGV2ZWwgZXBzZyBjb2RlcyBoZXJlOiBodHRwczovL2dpdGh1Yi5jb20vdmVsdG1hbi9kMy1zdGF0ZXBsYW5lDQojVGhlIGVzcGcgY29kZSBJJ3ZlIGNob3NlbiBpcyBmb3IgdGhlIGVudGlyZSBVUywgYW5kIGlzIGZpbmUgZm9yIHRoaXMgbWFwDQojSSdkIGRlZmluaXRlbHkgcGljayBhIGRpZmZlcmVudCBwcm9qZWN0aW9uIGlmIEkgd2VyZSB1c2luZyB0aGlzIGluIGEgcGFwZXIgb3IgYXNzaWdubWVudA0KDQptYXAgPC0gbWFwICU+JSBzdF90cmFuc2Zvcm0oIkVQU0c6NDI2OSIpDQpjb3VudGllcyA8LSBjb3VudGllcyAlPiUgc3RfdHJhbnNmb3JtKCJFUFNHOjQyNjkiKQ0KDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGNvdW50aWVzLCBtYXBwaW5nID0gYWVzKCksIGZpbGwgPSAiZ3JheSIsIGNvbG9yID0gIndoaXRlIikrDQogIGdlb21fc2YobWFwLCBtYXBwaW5nID0gYWVzKCksIGZpbGwgPSAicmVkIiwgY29sb3IgPSAicmVkIikrDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCk9rLCBjbGVhcmx5IHdlIGFyZSB6b29tZWQgb3V0IHdheSB0b28gZmFyISBMZXQncyBwaWNrIGEgY2l0eSB0byBwbG90LiBJJ2xsIHN0YXJ0IHdpdGggbXkgaG9tZXRvd24sIFJvY2hlc3RlciwgTlkuIFdlJ2xsIGRvIGFuIGFjdGl2aXR5IGFmdGVyIHRoaXMgc28gdGhhdCB5b3UgY2FuIHBsb3QgYSBjaXR5IG9mIHlvdXIgY2hvaWNlIGFzIHdlbGwuIA0KYGBge3J9DQojSSBuZWVkIHRvIGluY2x1ZGUgdGhpcyBvcHRpb24gdG8gdHVybiBvZmYgc3BoZXJpY2FsIGdlb21ldHJ5DQojZG9uJ3Qgd29ycnkgYWJvdXQgdGhpcyB0b28gbXVjaCAtIFIgd29uJ3QgbGV0IHVzIGZvY3VzIG9uIG9uZSBjaXR5IHVubGVzcyB3ZSBkbw0Kc2ZfdXNlX3MyKEZBTFNFKQ0KDQojSSdsbCBncmFiIHJvY2hlc3RlcidzIGNvdW50eSwgTW9ucm9lLCBmb3IgcGxvdHRpbmcNCnJvY2hlc3RlciA8LSBjb3VudGllcyAlPiUgZmlsdGVyKGNvdW50eV9uYW1lID09ICJNb25yb2UgQ291bnR5IiAmIHN0YXRlX25hbWUgPT0gIk5ldyBZb3JrIikNCg0KI3dlIG5lZWQgdG8gYWxzbyBmaWx0ZXIgdGhlIHJlZGxpbmluZyBsYXllciBieSBSb2NoZXN0ZXIgLSB0aGUgc2ltcGxlc3Qgd2F5IHRvIGRvIHRoaXMgaXMgd2l0aCBicmFja2V0cywgbGlrZSB0aGlzOg0KI3RoaXMgb25seSB3b3JrcyBiZWNhdXNlIHdlIHR1cm5lZCBvZmYgc3BoZXJpY2FsIGdlb21ldHJ5DQpyb2NfcmVkbGluZSA8LSBtYXBbcm9jaGVzdGVyLCBdDQoNCiNCZWZvcmUgSSBtYXAgaXQsIEkgd2FudCB0byBjaGVjayB0aGUgZGltZW5zaW9ucyBvZiB0aGUgcmVkbGluaW5nIGxheWVyLCBzbyBJIGNhbiB6b29tIGluIG15IG1hcCANCg0Kcm9jX3JlZGxpbmUNCg0KYGBgDQoNCmBgYHtyfQ0KI2xldCdzIG1hcCBpdCB1c2luZyB0aG9zZSBjb29yZGluYXRlcyENCmdncGxvdCgpKw0KICBnZW9tX3NmKHJvY2hlc3RlciwgbWFwcGluZyA9IGFlcygpLCBmaWxsID0gImdyYXk4MCIpKw0KICBnZW9tX3NmKHJvY19yZWRsaW5lLCBtYXBwaW5nID0gYWVzKGZpbGwgPSBob2xjX2dyYWRlKSwgY29sb3IgPSAid2hpdGUiKSsNCiAgdGhlbWVfbWluaW1hbCgpKw0KICBsYWJzKGZpbGwgPSAiUmVkbGluZSBTY29yZSIsIHRpdGxlID0gIlJlZGxpbmluZyBpbiBSb2NoZXN0ZXIsIE5ZIikrDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSsNCiAgeWxpbSg0My4xLCA0My4zKSsNCiAgeGxpbSgtNzcuNywgLTc3LjUpDQoNCmBgYA0KDQpXZSBjYW4gZG8gbXVjaCBiZXR0ZXIgdGhhbiB0aGlzIGluIFIhIE9uZSBpc3N1ZSBpcyB0aGF0IHRoZXJlIGlucyd0IGEgYmFzZSBtYXAgaGVyZSAtIGlmIHlvdSdyZSBmYW1pbGlhciB3aXRoIHRoZSBhcmVhLCBpdCB3b3VsZCBiZSBoZWxwZnVsIHRvIHNlZSB0aGUgbG9jYXRpb25zIG9mIHN0cmVldHMgYW5kIG1ham9yIGhpZ2h3YXlzLiBXZSBjYW4gZG8gdGhpcyB1c2luZyB0aGUgZ2dtYXAgcGFja2FnZS4NCg0KYGBge3J9DQojc2V0IGxhdC9sb25nIG9mIGJhc2UgbWFwDQpMQVQxID0gNDMuMQ0KTEFUMiA9IDQzLjMNCkxPTjEgPSAtNzcuNw0KTE9OMiA9IC03Ny41DQoNCiNjcmVhdGUgYmFzZW1hcCAtIEkgbGlrZSB0aGUgdGVycmFpbi1saW5lcyBtYXAgaW4gZ2dtYXANCiN5b3UgY2FuIHBsYXkgYXJvdW5kIHdpdGggaXQgb24geW91ciBvd24uIE90aGVyIGJhc2VtYXBzIGNhbiBiZSBmb3VuZCBpbiA/Z2V0X3N0YW1lbm1hcA0KI2ZpcnN0LCBzZXQgbGF0L2xvbmcgY29vcmRpbmF0ZXMgb2YgYmFzZSBtYXANCmJhc2VfbWFwIDwtIGdldF9zdGFtZW5tYXAoYmJveCA9IGMobGVmdCA9IExPTjEsIHJpZ2h0ID0gTE9OMiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvdHRvbSA9IExBVDEsIHRvcCA9IExBVDIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAjdGhpcyB6b29tIHdvcmtzIHdlbGwgZm9yIGEgc21hbGxlciBjaXR5DQogICAgICAgICAgICAgICAgICAgICAgICAgICNJIHJlY29tbWVuZCBzdGFydGluZyBhdCAxMCBhbmQgaW5jcmVhc2luZyBieSAxIHRvIGZpbmQgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZSBiZXN0IHpvb20gb3B0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgIHpvb20gPSAxMiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICNwaWNrIHRoZSBtYXAgdHlwZSB5b3Ugd2FudA0KICAgICAgICAgICAgICAgICAgICAgICAgICBtYXB0eXBlID0gInRlcnJhaW4tbGluZXMiKQ0KDQojbGV0J3MgbWFrZSBhIG5pY2UgY29sb3IgcGFsZXR0ZSBmb3IgdGhlIGxlZ2VuZA0KY29sb3JzIDwtIGMoInNlYWdyZWVuIiwgInNlYWdyZWVuMSIsICJ5ZWxsb3ciLCAidG9tYXRvIikNCg0KI3VzZSB0aGUgZ2dtYXAgZnVuY3Rpb24gaW5zdGVhZCBvZiBnZ3Bsb3QyIHRvIG1hcCB0aGUgYmFzZSBtYXANCmdnbWFwKGJhc2VfbWFwKSsNCiAgZ2VvbV9zZihyb2NfcmVkbGluZSwgbWFwcGluZyA9IGFlcyhmaWxsID0gaG9sY19ncmFkZSksIGNvbG9yID0gIndoaXRlIiwNCiAgI3dlIG5lZWQgdG8gaW5jbHVkZSBpbmhlcmV0LmFlcz1GIGhlcmUgb3Igb3VyIGdlb21fc2YgbGF5ZXIgd29uJ3Qgd29yaw0KICBpbmhlcml0LmFlcyA9IEZBTFNFLCBhbHBoYSA9IDAuNikrDQogIHRoZW1lX21pbmltYWwoKSsNCiAgbGFicyhmaWxsID0gIlJlZGxpbmUgU2NvcmUiLCB0aXRsZSA9ICJSZWRsaW5pbmcgaW4gUm9jaGVzdGVyLCBOWSIpKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9ycykNCg0KYGBgDQpCZWZvcmUgd2UgbW92ZSBvbiB0byB0aGUgYWN0aXZpdHksIEkgd2FudCB0byBhbHNvIHNob3cgeW91IGhvdyB0byBtYXAgZGF0YSBhdCB0aGUgbmF0aW9uYWwgbGV2ZWwuIEknbGwgaWxsdXN0cmF0ZSB0aGlzIHVzaW5nIHRoZSBpbmVxdWFsaXR5IGRhdGEgdGhhdCB3ZSd2ZSB1c2VkIHByZXZpb3VzbHkgZnJvbSBPcHBvcnR1bml0eSBJbnNpZ2h0cy4NCg0KYGBge3J9DQpzZXR3ZCgifi9iaW5naGFtdG9uL2dlb2czODAiKQ0KZGF0YSA8LSByZWFkLmNzdigiY291bnR5X2RhdGEuY3N2IikNCg0KI1dlJ2xsIG5lZWQgdG8gbWVyZ2UgdGhpcyBkYXRhc2V0IHdpdGggdGhlIGNvdW50eSBiYXNlIG1hcA0KI0JlY2F1c2UgaXQgZG9lc24ndCBoYXZlIGFueSBnZW9ncmFwaGljIGluZm8gb2YgaXRzIG93biB0byB1c2UgdG8gbWFwIGl0DQojVG8gZG8gdGhpcywgd2UgbmVlZCAxLTIgY29sdW1ucyB0aGF0IGlkZW50aWZ5IGVhY2ggdW5pcXVlIGNvdW50eSBpbiBlYWNoIA0KI1dlJ2xsIHVzZSBjb3VudHlfbmFtZSBhbmQgc3RhdGVfbmFtZQ0KI1RoZSBjb3VudHkgYmFzZSBtYXAgaGFzIHNvbWUgd29yZHMgKGUuZy4gQ291bnR5LCBQYXJpc2ggZXRjLikgYW5kIHN5bWJvbHMgKHRoZSAuIGFmdGVyIFN0LikgdGhhdCBvdXIgZGF0YSBkb2Vzbid0IGhhdmUgLSB3ZSBoYXZlIHRvIGNsZWFuIGl0IHVwLCBvciB3ZSBjYW4ndCBtZXJnZSB0aGUgZGF0YWZyYW1lcw0KY291bnRpZXMxIDwtIGNvdW50aWVzICU+JSANCiAgI3JlbW92ZSB1bmVjZXNzYXJ5IHdvcmRzIGFuZCB0aGUgZXh0cmEgc3BhY2UgDQogIG11dGF0ZShjb3VudHlfbmFtZSA9IHN0cl9yZW1vdmVfYWxsKGNvdW50eV9uYW1lLCAiIENvdW50eXwgUGFyaXNofCBCb3JvdWdofCBDZW5zdXMgQXJlYXwgTXVuaWNpcGFsaXR5IikpICU+JSANCiAgI2dldCByaWQgb2YgdGhlIHBlcmlvZCB1c2luZyBnc3ViDQogIG11dGF0ZShjb3VudHlfbmFtZSA9IGdzdWIoIlsuXSIsICIiLCBjb3VudHlfbmFtZSkpIA0KDQojdGhlIGNvbHVtbiBuYW1lcyBuZWVkIHRvIG1hdGNoIGZvciB0aGlzIG1ldGhvZCAtIG9ubHkgc3RhdGVuYW1lIGFuZCBzdGF0ZV9uYW1lIGRvbid0IG1hdGNoIGluIHRoZSB0d28gZGF0YWZyYW1lcw0KY29sbmFtZXMoZGF0YSlbM10gPC0gInN0YXRlX25hbWUiDQoNCiNtZXJnZSB0aGUgZGF0YSEgQWx3YXlzIHB1dCB0aGUgc2Ygb2JqZWN0IGZpcnN0LCBvciB5b3Ugd2lsbCBsb3NlIHRoZSBnZW9ncnBhaGljIGluZm9ybWF0aW9uIGluIHRoZSBtZXJnZQ0KZGF0YV9tZXJnZWQgPC0gbWVyZ2UoY291bnRpZXMxLCBkYXRhLCBieSA9IGMoImNvdW50eV9uYW1lIiwgInN0YXRlX25hbWUiKSkNCg0KI21hcCBpdCENCmdncGxvdCgpKw0KICBnZW9tX3NmKGNvdW50aWVzLCBtYXBwaW5nID0gYWVzKCksIGZpbGwgPSAiZ3JheSIsIGNvbG9yID0gIndoaXRlIikrDQogIGdlb21fc2YoZGF0YV9tZXJnZWQsIG1hcHBpbmcgPSBhZXMoZmlsbCA9IHBvdmVydHlfcmF0ZSksIGNvbG9yID0gIndoaXRlIikrDQogIHRoZW1lX21hcCgpKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ3aGl0ZSIsIGhpZ2ggPSAidG9tYXRvMyIpKw0KICBsYWJzKHRpdGxlID0gIlBvdmVydHkgUmF0ZSBpbiBVUyBDb3VudGllcywgMjAwMCIsDQogICAgICAgZmlsbCA9ICJQb3ZlcnR5IFJhdGUiKSsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQoNCmBgYA0KDQpXZSdyZSBzdGlsbCBtaXNzaW5nIGEgZmV3IGNvdW50aWVzISBXZSdkIG5lZWQgdG8gZ28gaW4gbWFudWFsbHkgdG8gY2hlY2sgd2hpY2ggY291bnRpZXMgYXJlbid0IG1lcmdpbmcgcHJvcGVybHkuIFRoaXMgaXMgYSBmYWlybHkgdGVkaW91cyBwcm9jZXNzLiBXaHkgZG9uJ3Qgd2UgdHJ5IG1hcHBpbmcgdGhpcyBhdCB0aGUgc3RhdGUgbGV2ZWwgaW5zdGVhZD8NCg0KYGBge3J9DQpzdGF0ZXMgPC0gZ2V0X3VyYm5fbWFwKCJzdGF0ZXMiLCBzZiA9IFRSVUUpDQoNCiNhZ2dyZWdhdGUgcG92ZXJ0eSBkYXRhIHRvIHRoZSBzdGF0ZSBsZXZlbA0KcG92X2RhdGEgPC0gZGF0YSAlPiUgc2VsZWN0KHN0YXRlX25hbWUsIHBvdmVydHlfcmF0ZSkgJT4lIA0KICBncm91cF9ieShzdGF0ZV9uYW1lKSAlPiUgDQogIHN1bW1hcmlzZShtZWFuX3BvdmVydHkgPSBtZWFuKHBvdmVydHlfcmF0ZSkpDQoNCiNvb3BzLCB0aGUgRGlzdHJpY3Qgb2YgQ29sdW1iaWEgZG9lc24ndCBtYXRjaCB1cCBpbiBib3RoIGRhdGFmcmFtZXMgLSB0aGUgT2YgaXMgY2FwaXRhbGl6ZWQgaW4gcG92X2RhdGEgYnV0IG5vdCBpbiBzdGF0ZXMsIGxldCdzIGZpeCBpdA0KcG92X2RhdGFbOSwxXSA8LSAiRGlzdHJpY3Qgb2YgQ29sdW1iaWEiDQoNCiNub3cgbWVyZ2UgaXQNCnBvdl9tZXJnZWQgPC0gbWVyZ2Uoc3RhdGVzLCBwb3ZfZGF0YSwgYnkgPSAic3RhdGVfbmFtZSIpDQoNCiNNYWtlIHRoZSBmaW5hbCBtYXANCmdncGxvdCgpKw0KICBnZW9tX3NmKHN0YXRlcywgbWFwcGluZyA9IGFlcygpLCBmaWxsID0gImdyYXkiLCBjb2xvciA9ICJ3aGl0ZSIpKw0KICBnZW9tX3NmKHBvdl9tZXJnZWQsIG1hcHBpbmcgPSBhZXMoZmlsbCA9IG1lYW5fcG92ZXJ0eSksIGNvbG9yID0gIndoaXRlIikrDQogIHRoZW1lX21hcCgpKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ3aGl0ZSIsIGhpZ2ggPSAidG9tYXRvMyIpKw0KICBsYWJzKHRpdGxlID0gIlBvdmVydHkgUmF0ZSBpbiBVUyBTdGF0ZXMsIDIwMDAiLA0KICAgICAgIGZpbGwgPSAiTWVhbiBQb3ZlcnR5IFJhdGUiKSsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQpgYGANCmBgYHtyfQ0KI1RoaXMgcHJvamVjdGlvbiBsb29rcyBuaWNlISBXaGF0IGlzIGl0Pw0KDQpzdGF0ZXMNCmBgYA0KDQpSZXNvdXJjZXMNCg0KRGF0YTogTWFwcGluZyBJbmVxdWFsaXR5LCBSZWRsaW5pbmcgTmV3IERlYWwgaW4gQW1lcmljYS4gaHR0cHM6Ly9kc2wucmljaG1vbmQuZWR1L3Bhbm9yYW1hL3JlZGxpbmluZy8NCg0KRGF0YTogT3Bwb3J0dW5pdHkgSW5zaWdodHMuIGh0dHBzOi8vb3Bwb3J0dW5pdHlpbnNpZ2h0cy5vcmcvIA0KDQpVcmJhbiBJbnN0aXR1dGUgKDIwMjIpLiBJbnRyb2R1Y3Rpb24gdG8gTWFwcGluZy4gUmV0cmlldmVkIGZyb206IGh0dHBzOi8vdXJiYW5pbnN0aXR1dGUuZ2l0aHViLmlvL3ItYXQtdXJiYW4vbWFwcGluZy5odG1sLiANCg0KS2FobGUsIEQuLCBXaWNraGFtLCBILiwgSmFja3NvbiwgUy4gJiBLb3JwZWxhLCBNLiAoMjAyMykuIEdnbWFwLiBSZXRyaWV2ZWQgZnJvbTogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dnbWFwL2dnbWFwLnBkZi4gDQpSZWFkIE1vcmU6IGh0dHBzOi8vZ2l0aHViLmNvbS9ka2FobGUvZ2dtYXAgDQoNCg0K