Introduction

This notebook provides examples of binding data, dissolving features, calculating centroids, and creating buffers.

New Package

We need a new package, nngeo, that can be installed with the following script:

install.packages("nngeo")

Dependencies

This notebook requires the following packages

# tidyverse packages
library(dplyr)    # data wrangling

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
# spatial packages
library(mapview)  # preview spatial data
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
GDAL version >= 3.1.0 | setting mapviewOptions(fgb = TRUE)
library(nngeo)    # eliminiate holes
Loading required package: sf
Linking to GEOS 3.8.1, GDAL 3.1.4, PROJ 6.3.1
library(sf)       # spatial tools
library(tigris)   # TIGER/Line
To enable 
caching of data, set `options(tigris_use_cache = TRUE)` in your R script or .Rprofile.
# other packages
library(here)     # file path management
here() starts at /Users/chris/GitHub/slu-soc5650/content/module-3-dissolve

Load Data

This notebook requires three files:

# precinct data
precinct <- st_read(here("data", "example-data", "POL_WRD_2010_Prec", "POL_WRD_2010_Prec.shp"), 
                    stringsAsFactors = FALSE) %>%
  st_transform(crs = 3602)
Reading layer `POL_WRD_2010_Prec' from data source `/Users/chris/GitHub/slu-soc5650/content/module-3-dissolve/data/example-data/POL_WRD_2010_Prec/POL_WRD_2010_Prec.shp' using driver `ESRI Shapefile'
Simple feature collection with 233 features and 5 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 871512.3 ymin: 982403 xmax: 915268.6 ymax: 1070966
projected CRS:  Custom
# COVID zip code data
city <- st_read(here("data", "example-data", "daily_snapshot_stl_city.geojson"), 
                crs = 4326, stringsAsFactors = FALSE) %>%
  st_transform(crs = 3602) %>%
  mutate(GEOID_ZCTA = as.numeric(GEOID_ZCTA))
Reading layer `daily_snapshot_stl_city' from data source `/Users/chris/GitHub/slu-soc5650/content/module-3-dissolve/data/example-data/daily_snapshot_stl_city.geojson' using driver `GeoJSON'
Simple feature collection with 30 features and 10 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -90.32052 ymin: 38.53185 xmax: -90.16657 ymax: 38.77443
geographic CRS: WGS 84
county <- st_read(here("data", "example-data", "daily_snapshot_stl_county.geojson"), 
                  crs = 4326, stringsAsFactors = FALSE) %>%
  st_transform(crs = 3602)
Reading layer `daily_snapshot_stl_county' from data source `/Users/chris/GitHub/slu-soc5650/content/module-3-dissolve/data/example-data/daily_snapshot_stl_county.geojson' using driver `GeoJSON'
Simple feature collection with 49 features and 10 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -90.73653 ymin: 38.3883 xmax: -90.11771 ymax: 38.89118
geographic CRS: WGS 84

We’ll also get some data on the St. Louis City and County boundaries as well as on Dunklin and Pemiscot counties in the Bootheel:

counties <- counties(state = 29) %>%
  filter(GEOID %in% c("29189", "29510")) %>%
  select(GEOID, NAMELSAD) %>%
  st_transform(crs = 3602)

bootheel <- counties(state = 29) %>%
  filter(GEOID %in% c("29069", "29155")) %>%
  select(GEOID, NAMELSAD) %>%
  st_transform(crs = 3602)

Dissolving Features

In our preinct data, we have a variable named WARD10. This is the City Ward that each precinct falls within. If we wanted to map wards instead of precincts, we can modify our geometric data using group_by() and summarise():

precinct %>%
  select(WARD10) %>%
  rename(ward = WARD10) %>%
  group_by(ward) %>%
  summarise() -> ward

Once these have been dissolved, we can explore them with mapview():

mapview(ward)

Notice how some wards, such as Ward 4 and Ward 21 in North City, Ward 6 and Ward 7 in Downtown, and Wards 12, 15, and 23 in South City have “holes.” These are common artifacts of the dissolve process that result from precincts’ geometries not perfectly abutting each other.

The nngeo package has a great function st_remove_holes() that can be used to get rid of these:

ward <- st_remove_holes(ward)

We can check out the differences with mapview():

mapview(ward)

Be careful with removing holes, particularly if your features have enclaves in them (as Kansas City does) - those enclaves will get removed as well, and st_difference() will have to be used to cut the enclaves back out!

Merging Features

Last week, we quickly introduced rbind() to combine sf objects. I want to talk a bit more about that process today, and also connect it to the dissolve workflow from above. Sometimes, we get data that we want to use in separate files, such as the city and county COVID data (current as of 2021-03-21). Be sure to check them first to make sure they have the same names/types of columns to prevent issues with your bound data:

str(city)
Classes ‘sf’ and 'data.frame':  30 obs. of  11 variables:
 $ GEOID_ZCTA   : num  63101 63102 63103 63104 63105 ...
 $ report_date  : Date, format: "2021-03-21" "2021-03-21" "2021-03-21" "2021-03-21" ...
 $ geoid        : chr  "29510" "29510" "29510" "29510" ...
 $ county       : chr  "St. Louis City" "St. Louis City" "St. Louis City" "St. Louis City" ...
 $ state        : chr  "Missouri" "Missouri" "Missouri" "Missouri" ...
 $ cases        : num  269 197 652 1351 58 ...
 $ new_cases    : num  0 0 0 0 0 0 0 0 0 0 ...
 $ case_avg     : num  0.7143 0.1429 1.0714 1.2857 0.0714 ...
 $ case_rate    : num  81.3 89.5 78.5 67.8 61.5 ...
 $ case_avg_rate: num  2.16 0.649 1.29 0.646 0.757 ...
 $ geometry     :sfc_MULTIPOLYGON of length 30; first list element: List of 2
  ..$ :List of 1
  .. ..$ : num [1:19, 1:2] 276815 276797 276808 276815 276819 ...
  ..$ :List of 1
  .. ..$ : num [1:135, 1:2] 276237 276267 276272 276281 276285 ...
  ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA
  ..- attr(*, "names")= chr [1:10] "GEOID_ZCTA" "report_date" "geoid" "county" ...
str(county)
Classes ‘sf’ and 'data.frame':  49 obs. of  11 variables:
 $ GEOID_ZCTA   : chr  "63005" "63011" "63017" "63021" ...
 $ report_date  : Date, format: "2021-03-21" "2021-03-21" "2021-03-21" "2021-03-21" ...
 $ geoid        : chr  "29189" "29189" "29189" "29189" ...
 $ county       : chr  "St. Louis" "St. Louis" "St. Louis" "St. Louis" ...
 $ state        : chr  "Missouri" "Missouri" "Missouri" "Missouri" ...
 $ cases        : num  1747 3084 3765 4869 1453 ...
 $ new_cases    : num  0 3 8 1 0 1 9 2 1 1 ...
 $ case_avg     : num  2.5 4.07 5.07 6.43 3.36 ...
 $ case_rate    : num  97.7 80.2 90.6 86.6 173.2 ...
 $ case_avg_rate: num  1.4 1.06 1.22 1.14 4 ...
 $ geometry     :sfc_MULTIPOLYGON of length 49; first list element: List of 1
  ..$ :List of 1
  .. ..$ : num [1:1239, 1:2] 229763 229765 229766 229769 229769 ...
  ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA
  ..- attr(*, "names")= chr [1:10] "GEOID_ZCTA" "report_date" "geoid" "county" ...

We’re looking for matching column types and a minimal number of needed columns. For instance, I know that I’m going to get rid of everything except the ZIP itself (GEOID_ZCTA) and the total count of cases. We should also check to see if GEOID_ZCTA and cases are the same type of data in both objects, and correct if they are not:

# city data
city %>%
  select(GEOID_ZCTA, cases) %>%
  mutate(GEOID_ZCTA = as.character(GEOID_ZCTA)) -> city

# county data
county <- select(county, GEOID_ZCTA, cases)

Once we feel confident with any changes that need to be made (using mutate() and select()), we can use rbind() to combine them:

region <- rbind(city, county)

Once these have been merged, we can explore them with mapview():

mapview(region, zcol = "GEOID_ZCTA")

Notice that zip codes that lie along the city-county boundary are split. We can use the dissolve features workflow to combine them!

region %>%
  select(GEOID_ZCTA, cases) %>%
  group_by(GEOID_ZCTA) %>%
  summarise(cases = sum(cases, na.rm = TRUE)) -> region

We also want to check to make sure these data are not in a geometry collection. We see "sfc_GEOMETRY" but expect either "sfc_POLYGON" or "sfc_MULTIPOLYGON" instead. We can convert to polygon using:

region <- st_collection_extract(region, "POLYGON")

Once these have been reformatted correctly, we can explore them with mapview():

mapview(region, zcol = "GEOID_ZCTA")

Centroids

Centroids are the geographic center of a feature. Take, for example, these two counties (Dunklin and Pemiscot) in the very southeast corner of Missouri - what we call the “Bootheel”:

mapview(bootheel)

If we want to find the geographic center of both counties, we can calculate their centroids:

bootheel_centroids <- st_centroid(bootheel)

Once we have those calculated, we can preview them to see how they’ve changed:

mapview(bootheel_centroids)

Take a look at the centroid for Dunklin County, which falls just outside of the country boundary itself! In this case, the geographic center of Dunklin County is not actually in Dunklin County because of its distinctive shape. I love it! This is something to be aware of when you’re geoprocessing your data. For example:

st_intersection(bootheel_centroids, bootheel)

Notice how the centroid for Dunklin County isn’t returned!

Example

Imagine we had not started out with county attributes in our ZIP code data, but we wanted to know which ZIP codes fall in St. Louis City or St. Louis County. This is tricky, because we don’t know (in this hypothetical scenario) how many cases can be attributed to the city or the county. One way to get a rough sense is to located ZIPs by where their centroid falls. To do this, we’ll start by calculating centroids for each ZIP:

# calculate
centroids <- st_centroid(region)

Let’s take a look at how these data changed. First, we’ll check out their structure:

str(centroids)

Notice that they have the exact same attributes, but that their geometry has become points. Let’s also map them:

mapview(centroids, zcol = "GEOID_ZCTA")

One thing I look for are centroid that might actually fall outside of the county itself. ZIP 63304 (along the Missouri River near Chesterfield) stands out as a possibility here.

Next, we’ll label our zips with the centroid they fall in. To do this, we’ll intersect our county polygons with the centroid values:

centroids <- st_intersection(centroids, counties)

Notice that the number of observations doesn’t change, so we know that all of the centroids fell within the tract boundaries. If we did lose a county, how could we handle it?

We’ll preview them to get a sense of how the data have changed:

mapview(centroids, zcol = "GEOID_ZCTA")

Buffers

Finally, I want to illustrate calculating a buffer. For example, we might want to get a count of crimes that fall within or near a ZIP code in the City. We’ll extract 63108, the ZIP that SLU partially falls in, and buffer around it:

# data cleaning
region %>%
  select(GEOID_ZCTA) %>%
  filter(GEOID_ZCTA == "63108") -> zip_pre

# calculate buffer
zip_post <- st_buffer(zip_pre, dis = 500)

The value 500 refers to 500 meters. We know it is meters based on the coordinate system:

st_crs(zip_post)

We’ll preview the initial zip first, and then preview the change:

mapview(zip_pre)
mapview(zip_post)
LS0tCnRpdGxlOiAiTWVldGluZyBOb3RlYm9vayAtIENvbXBsZXRlIgphdXRob3I6ICJDaHJpc3RvcGhlciBQcmVuZXIsIFBoLkQuIgpkYXRlOiAnKGByIGZvcm1hdChTeXMudGltZSgpLCAiJUIgJWQsICVZIilgKScKb3V0cHV0OiAKICBnaXRodWJfZG9jdW1lbnQ6IGRlZmF1bHQKICBodG1sX25vdGVib29rOiBkZWZhdWx0IAotLS0KCiMjIEludHJvZHVjdGlvbgpUaGlzIG5vdGVib29rIHByb3ZpZGVzIGV4YW1wbGVzIG9mIGJpbmRpbmcgZGF0YSwgZGlzc29sdmluZyBmZWF0dXJlcywgY2FsY3VsYXRpbmcgY2VudHJvaWRzLCBhbmQgY3JlYXRpbmcgYnVmZmVycy4KCiMjIE5ldyBQYWNrYWdlCldlIG5lZWQgYSBuZXcgcGFja2FnZSwgYG5uZ2VvYCwgdGhhdCBjYW4gYmUgaW5zdGFsbGVkIHdpdGggdGhlIGZvbGxvd2luZyBzY3JpcHQ6CgpgYGByCmluc3RhbGwucGFja2FnZXMoIm5uZ2VvIikKYGBgCgojIyBEZXBlbmRlbmNpZXMKVGhpcyBub3RlYm9vayByZXF1aXJlcyB0aGUgZm9sbG93aW5nIHBhY2thZ2VzCgpgYGB7ciBsb2FkLXBhY2thZ2VzfQojIHRpZHl2ZXJzZSBwYWNrYWdlcwpsaWJyYXJ5KGRwbHlyKSAgICAjIGRhdGEgd3JhbmdsaW5nCgojIHNwYXRpYWwgcGFja2FnZXMKbGlicmFyeShtYXB2aWV3KSAgIyBwcmV2aWV3IHNwYXRpYWwgZGF0YQpsaWJyYXJ5KG5uZ2VvKSAgICAjIGVsaW1pbmF0ZSBob2xlcwpsaWJyYXJ5KHNmKSAgICAgICAjIHNwYXRpYWwgdG9vbHMKbGlicmFyeSh0aWdyaXMpICAgIyBUSUdFUi9MaW5lCgojIG90aGVyIHBhY2thZ2VzCmxpYnJhcnkoaGVyZSkgICAgICMgZmlsZSBwYXRoIG1hbmFnZW1lbnQKYGBgCgojIyBMb2FkIERhdGEKVGhpcyBub3RlYm9vayByZXF1aXJlcyB0aHJlZSBmaWxlczoKCmBgYHtyIGxvYWQtZGF0YX0KIyBwcmVjaW5jdCBkYXRhCnByZWNpbmN0IDwtIHN0X3JlYWQoaGVyZSgiZGF0YSIsICJleGFtcGxlLWRhdGEiLCAiUE9MX1dSRF8yMDEwX1ByZWMiLCAiUE9MX1dSRF8yMDEwX1ByZWMuc2hwIiksIAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkgJT4lCiAgc3RfdHJhbnNmb3JtKGNycyA9IDM2MDIpCgojIENPVklEIHppcCBjb2RlIGRhdGEKY2l0eSA8LSBzdF9yZWFkKGhlcmUoImRhdGEiLCAiZXhhbXBsZS1kYXRhIiwgImRhaWx5X3NuYXBzaG90X3N0bF9jaXR5Lmdlb2pzb24iKSwgCiAgICAgICAgICAgICAgICBjcnMgPSA0MzI2LCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpICU+JQogIHN0X3RyYW5zZm9ybShjcnMgPSAzNjAyKSAlPiUKICBtdXRhdGUoR0VPSURfWkNUQSA9IGFzLm51bWVyaWMoR0VPSURfWkNUQSkpCgpjb3VudHkgPC0gc3RfcmVhZChoZXJlKCJkYXRhIiwgImV4YW1wbGUtZGF0YSIsICJkYWlseV9zbmFwc2hvdF9zdGxfY291bnR5Lmdlb2pzb24iKSwgCiAgICAgICAgICAgICAgICAgIGNycyA9IDQzMjYsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkgJT4lCiAgc3RfdHJhbnNmb3JtKGNycyA9IDM2MDIpCmBgYAoKV2UnbGwgYWxzbyBnZXQgc29tZSBkYXRhIG9uIHRoZSBTdC4gTG91aXMgQ2l0eSBhbmQgQ291bnR5IGJvdW5kYXJpZXMgYXMgd2VsbCBhcyBvbiBEdW5rbGluIGFuZCBQZW1pc2NvdCBjb3VudGllcyBpbiB0aGUgQm9vdGhlZWw6CgpgYGB7ciB0aWdyaXMtZGF0YX0KY291bnRpZXMgPC0gY291bnRpZXMoc3RhdGUgPSAyOSkgJT4lCiAgZmlsdGVyKEdFT0lEICVpbiUgYygiMjkxODkiLCAiMjk1MTAiKSkgJT4lCiAgc2VsZWN0KEdFT0lELCBOQU1FTFNBRCkgJT4lCiAgc3RfdHJhbnNmb3JtKGNycyA9IDM2MDIpCgpib290aGVlbCA8LSBjb3VudGllcyhzdGF0ZSA9IDI5KSAlPiUKICBmaWx0ZXIoR0VPSUQgJWluJSBjKCIyOTA2OSIsICIyOTE1NSIpKSAlPiUKICBzZWxlY3QoR0VPSUQsIE5BTUVMU0FEKSAlPiUKICBzdF90cmFuc2Zvcm0oY3JzID0gMzYwMikKYGBgCgojIyBEaXNzb2x2aW5nIEZlYXR1cmVzCkluIG91ciBgcHJlaW5jdGAgZGF0YSwgd2UgaGF2ZSBhIHZhcmlhYmxlIG5hbWVkIGBXQVJEMTBgLiBUaGlzIGlzIHRoZSBDaXR5IFdhcmQgdGhhdCBlYWNoIHByZWNpbmN0IGZhbGxzIHdpdGhpbi4gSWYgd2Ugd2FudGVkIHRvIG1hcCB3YXJkcyBpbnN0ZWFkIG9mIHByZWNpbmN0cywgd2UgY2FuIG1vZGlmeSBvdXIgZ2VvbWV0cmljIGRhdGEgdXNpbmcgYGdyb3VwX2J5KClgIGFuZCBgc3VtbWFyaXNlKClgOgoKYGBge3IgZGlzc29sdmUtd2FyZH0KcHJlY2luY3QgJT4lCiAgc2VsZWN0KFdBUkQxMCkgJT4lCiAgcmVuYW1lKHdhcmQgPSBXQVJEMTApICU+JQogIGdyb3VwX2J5KHdhcmQpICU+JQogIHN1bW1hcmlzZSgpIC0+IHdhcmQKYGBgCgpPbmNlIHRoZXNlIGhhdmUgYmVlbiBkaXNzb2x2ZWQsIHdlIGNhbiBleHBsb3JlIHRoZW0gd2l0aCBgbWFwdmlldygpYDoKCmBgYHtyIGV4cGxvcmUtd2FyZH0KbWFwdmlldyh3YXJkKQpgYGAKCk5vdGljZSBob3cgc29tZSB3YXJkcywgc3VjaCBhcyBXYXJkIDQgYW5kIFdhcmQgMjEgaW4gTm9ydGggQ2l0eSwgV2FyZCA2IGFuZCBXYXJkIDcgaW4gRG93bnRvd24sIGFuZCBXYXJkcyAxMiwgMTUsIGFuZCAyMyBpbiBTb3V0aCBDaXR5IGhhdmUgImhvbGVzLiIgVGhlc2UgYXJlIGNvbW1vbiBhcnRpZmFjdHMgb2YgdGhlIGRpc3NvbHZlIHByb2Nlc3MgdGhhdCByZXN1bHQgZnJvbSBwcmVjaW5jdHMnIGdlb21ldHJpZXMgbm90ICpwZXJmZWN0bHkqIGFidXR0aW5nIGVhY2ggb3RoZXIuCgpUaGUgYG5uZ2VvYCBwYWNrYWdlIGhhcyBhIGdyZWF0IGZ1bmN0aW9uIGBzdF9yZW1vdmVfaG9sZXMoKWAgdGhhdCBjYW4gYmUgdXNlZCB0byBnZXQgcmlkIG9mIHRoZXNlOgoKYGBge3IgcmVtb3ZlLXdhcmQtaG9sZXN9CndhcmQgPC0gc3RfcmVtb3ZlX2hvbGVzKHdhcmQpCmBgYAoKV2UgY2FuIGNoZWNrIG91dCB0aGUgZGlmZmVyZW5jZXMgd2l0aCBgbWFwdmlldygpYDoKCmBgYHtyIGNoZWNrLXdhcmR9Cm1hcHZpZXcod2FyZCkKYGBgCgpCZSBjYXJlZnVsIHdpdGggcmVtb3ZpbmcgaG9sZXMsIHBhcnRpY3VsYXJseSBpZiB5b3VyIGZlYXR1cmVzIGhhdmUgZW5jbGF2ZXMgaW4gdGhlbSAoYXMgS2Fuc2FzIENpdHkgZG9lcykgLSB0aG9zZSBlbmNsYXZlcyB3aWxsIGdldCByZW1vdmVkIGFzIHdlbGwsIGFuZCBgc3RfZGlmZmVyZW5jZSgpYCB3aWxsIGhhdmUgdG8gYmUgdXNlZCB0byBjdXQgdGhlIGVuY2xhdmVzIGJhY2sgb3V0IQoKIyMgTWVyZ2luZyBGZWF0dXJlcwpMYXN0IHdlZWssIHdlIHF1aWNrbHkgaW50cm9kdWNlZCBgcmJpbmQoKWAgdG8gY29tYmluZSBgc2ZgIG9iamVjdHMuIEkgd2FudCB0byB0YWxrIGEgYml0IG1vcmUgYWJvdXQgdGhhdCBwcm9jZXNzIHRvZGF5LCBhbmQgYWxzbyBjb25uZWN0IGl0IHRvIHRoZSBkaXNzb2x2ZSB3b3JrZmxvdyBmcm9tIGFib3ZlLiBTb21ldGltZXMsIHdlIGdldCBkYXRhIHRoYXQgd2Ugd2FudCB0byB1c2UgaW4gc2VwYXJhdGUgZmlsZXMsIHN1Y2ggYXMgdGhlIGBjaXR5YCBhbmQgYGNvdW50eWAgQ09WSUQgZGF0YSAoY3VycmVudCBhcyBvZiAyMDIxLTAzLTIxKS4gQmUgc3VyZSB0byBjaGVjayB0aGVtIGZpcnN0IHRvIG1ha2Ugc3VyZSB0aGV5IGhhdmUgdGhlIHNhbWUgbmFtZXMvdHlwZXMgb2YgY29sdW1ucyB0byBwcmV2ZW50IGlzc3VlcyB3aXRoIHlvdXIgYm91bmQgZGF0YToKCmBgYHtyIGNoZWNrfQpzdHIoY2l0eSkKc3RyKGNvdW50eSkKYGBgCgpXZSdyZSBsb29raW5nIGZvciBtYXRjaGluZyBjb2x1bW4gdHlwZXMgYW5kIGEgbWluaW1hbCBudW1iZXIgb2YgbmVlZGVkIGNvbHVtbnMuIEZvciBpbnN0YW5jZSwgSSBrbm93IHRoYXQgSSdtIGdvaW5nIHRvIGdldCByaWQgb2YgZXZlcnl0aGluZyBleGNlcHQgdGhlIFpJUCBpdHNlbGYgKGBHRU9JRF9aQ1RBYCkgYW5kIHRoZSB0b3RhbCBjb3VudCBvZiBjYXNlcy4gV2Ugc2hvdWxkIGFsc28gY2hlY2sgdG8gc2VlIGlmIGBHRU9JRF9aQ1RBYCBhbmQgYGNhc2VzYCBhcmUgdGhlIHNhbWUgdHlwZSBvZiBkYXRhIGluIGJvdGggb2JqZWN0cywgYW5kIGNvcnJlY3QgaWYgdGhleSBhcmUgbm90OgoKYGBge3IgcHJlLWZvcm1hdH0KIyBjaXR5IGRhdGEKY2l0eSAlPiUKICBzZWxlY3QoR0VPSURfWkNUQSwgY2FzZXMpICU+JQogIG11dGF0ZShHRU9JRF9aQ1RBID0gYXMuY2hhcmFjdGVyKEdFT0lEX1pDVEEpKSAtPiBjaXR5CgojIGNvdW50eSBkYXRhCmNvdW50eSA8LSBzZWxlY3QoY291bnR5LCBHRU9JRF9aQ1RBLCBjYXNlcykKYGBgCgpPbmNlIHdlIGZlZWwgY29uZmlkZW50IHdpdGggYW55IGNoYW5nZXMgdGhhdCBuZWVkIHRvIGJlIG1hZGUgKHVzaW5nIGBtdXRhdGUoKWAgYW5kIGBzZWxlY3QoKWApLCB3ZSBjYW4gdXNlIGByYmluZCgpYCB0byBjb21iaW5lIHRoZW06CgpgYGB7ciBtZXJnZX0KcmVnaW9uIDwtIHJiaW5kKGNpdHksIGNvdW50eSkKYGBgCgpPbmNlIHRoZXNlIGhhdmUgYmVlbiBtZXJnZWQsIHdlIGNhbiBleHBsb3JlIHRoZW0gd2l0aCBgbWFwdmlldygpYDoKCmBgYHtyIGV4cGxvcmUtcmVnaW9ufQptYXB2aWV3KHJlZ2lvbiwgemNvbCA9ICJHRU9JRF9aQ1RBIikKYGBgCgpOb3RpY2UgdGhhdCB6aXAgY29kZXMgdGhhdCBsaWUgYWxvbmcgdGhlIGNpdHktY291bnR5IGJvdW5kYXJ5IGFyZSBzcGxpdC4gV2UgY2FuIHVzZSB0aGUgZGlzc29sdmUgZmVhdHVyZXMgd29ya2Zsb3cgdG8gY29tYmluZSB0aGVtIQoKYGBge3IgZGlzc29sdmUtemlwc30KcmVnaW9uICU+JQogIHNlbGVjdChHRU9JRF9aQ1RBLCBjYXNlcykgJT4lCiAgZ3JvdXBfYnkoR0VPSURfWkNUQSkgJT4lCiAgc3VtbWFyaXNlKGNhc2VzID0gc3VtKGNhc2VzLCBuYS5ybSA9IFRSVUUpKSAtPiByZWdpb24KYGBgCgpXZSBhbHNvIHdhbnQgdG8gY2hlY2sgdG8gbWFrZSBzdXJlIHRoZXNlIGRhdGEgYXJlIG5vdCBpbiBhIGdlb21ldHJ5IGNvbGxlY3Rpb24uIFdlIHNlZSBgInNmY19HRU9NRVRSWSJgIGJ1dCBleHBlY3QgZWl0aGVyIGAic2ZjX1BPTFlHT04iYCBvciBgInNmY19NVUxUSVBPTFlHT04iYCBpbnN0ZWFkLiBXZSBjYW4gY29udmVydCB0byBwb2x5Z29uIHVzaW5nOgoKYGBge3IgY29sbGVjdGlvbi1leHRyYWN0fQpyZWdpb24gPC0gc3RfY29sbGVjdGlvbl9leHRyYWN0KHJlZ2lvbiwgIlBPTFlHT04iKQpgYGAKCk9uY2UgdGhlc2UgaGF2ZSBiZWVuIHJlZm9ybWF0dGVkIGNvcnJlY3RseSwgd2UgY2FuIGV4cGxvcmUgdGhlbSB3aXRoIGBtYXB2aWV3KClgOgoKYGBge3IgZXhwbG9yZS1kaXNzb2x2ZWQtcmVnaW9ufQptYXB2aWV3KHJlZ2lvbiwgemNvbCA9ICJHRU9JRF9aQ1RBIikKYGBgCgojIyBDZW50cm9pZHMKQ2VudHJvaWRzIGFyZSB0aGUgZ2VvZ3JhcGhpYyBjZW50ZXIgb2YgYSBmZWF0dXJlLiBUYWtlLCBmb3IgZXhhbXBsZSwgdGhlc2UgdHdvIGNvdW50aWVzIChEdW5rbGluIGFuZCBQZW1pc2NvdCkgaW4gdGhlIHZlcnkgc291dGhlYXN0IGNvcm5lciBvZiBNaXNzb3VyaSAtIHdoYXQgd2UgY2FsbCB0aGUgIkJvb3RoZWVsIjoKCmBgYHtyIHByZXZpZXctYm9vdGhlZWx9Cm1hcHZpZXcoYm9vdGhlZWwpCmBgYAoKSWYgd2Ugd2FudCB0byBmaW5kIHRoZSBnZW9ncmFwaGljIGNlbnRlciBvZiBib3RoIGNvdW50aWVzLCB3ZSBjYW4gY2FsY3VsYXRlIHRoZWlyIGNlbnRyb2lkczoKCmBgYHtyIGJvb3RoZWVsLWNlbnRyb2lkc30KYm9vdGhlZWxfY2VudHJvaWRzIDwtIHN0X2NlbnRyb2lkKGJvb3RoZWVsKQpgYGAKCk9uY2Ugd2UgaGF2ZSB0aG9zZSBjYWxjdWxhdGVkLCB3ZSBjYW4gcHJldmlldyB0aGVtIHRvIHNlZSBob3cgdGhleSd2ZSBjaGFuZ2VkOgoKYGBge3IgcHJldmlldy1ib290aGVlbC1jZW50cm9pZHN9Cm1hcHZpZXcoYm9vdGhlZWxfY2VudHJvaWRzKQpgYGAKClRha2UgYSBsb29rIGF0IHRoZSBjZW50cm9pZCBmb3IgRHVua2xpbiBDb3VudHksIHdoaWNoIGZhbGxzICpqdXN0KiBvdXRzaWRlIG9mIHRoZSBjb3VudHJ5IGJvdW5kYXJ5IGl0c2VsZiEgSW4gdGhpcyBjYXNlLCB0aGUgZ2VvZ3JhcGhpYyBjZW50ZXIgb2YgRHVua2xpbiBDb3VudHkgaXMgbm90IGFjdHVhbGx5IGluIER1bmtsaW4gQ291bnR5IGJlY2F1c2Ugb2YgaXRzIGRpc3RpbmN0aXZlIHNoYXBlLiBJIGxvdmUgaXQhIFRoaXMgaXMgc29tZXRoaW5nIHRvIGJlIGF3YXJlIG9mIHdoZW4geW91J3JlIGdlb3Byb2Nlc3NpbmcgeW91ciBkYXRhLiBGb3IgZXhhbXBsZToKCmBgYHtyIGludGVyc2VjdC1ib290aGVlbC1jZW50cm9pZHN9CnN0X2ludGVyc2VjdGlvbihib290aGVlbF9jZW50cm9pZHMsIGJvb3RoZWVsKQpgYGAKCk5vdGljZSBob3cgdGhlIGNlbnRyb2lkIGZvciBEdW5rbGluIENvdW50eSBpc24ndCByZXR1cm5lZCEKCiMjIyBFeGFtcGxlCkltYWdpbmUgd2UgaGFkIG5vdCBzdGFydGVkIG91dCB3aXRoIGNvdW50eSBhdHRyaWJ1dGVzIGluIG91ciBaSVAgY29kZSBkYXRhLCBidXQgd2Ugd2FudGVkIHRvIGtub3cgd2hpY2ggWklQIGNvZGVzIGZhbGwgaW4gU3QuIExvdWlzIENpdHkgb3IgU3QuIExvdWlzIENvdW50eS4gVGhpcyBpcyB0cmlja3ksIGJlY2F1c2Ugd2UgZG9uJ3Qga25vdyAoaW4gdGhpcyBoeXBvdGhldGljYWwgc2NlbmFyaW8pIGhvdyBtYW55IGNhc2VzIGNhbiBiZSBhdHRyaWJ1dGVkIHRvIHRoZSBjaXR5IG9yIHRoZSBjb3VudHkuIE9uZSB3YXkgdG8gZ2V0IGEgcm91Z2ggc2Vuc2UgaXMgdG8gbG9jYXRlZCBaSVBzIGJ5IHdoZXJlIHRoZWlyIGNlbnRyb2lkIGZhbGxzLiBUbyBkbyB0aGlzLCB3ZSdsbCBzdGFydCBieSBjYWxjdWxhdGluZyBjZW50cm9pZHMgZm9yIGVhY2ggWklQOgoKYGBge3IgY2FsY3VsYXRlLWNlbnRyb2lkc30KIyBjYWxjdWxhdGUKY2VudHJvaWRzIDwtIHN0X2NlbnRyb2lkKHJlZ2lvbikKYGBgCgpMZXQncyB0YWtlIGEgbG9vayBhdCBob3cgdGhlc2UgZGF0YSBjaGFuZ2VkLiBGaXJzdCwgd2UnbGwgY2hlY2sgb3V0IHRoZWlyIHN0cnVjdHVyZToKCmBgYHtyIGNlbnRyb2lkLXN0cnVjdHVyZX0Kc3RyKGNlbnRyb2lkcykKYGBgCgpOb3RpY2UgdGhhdCB0aGV5IGhhdmUgdGhlICpleGFjdCBzYW1lKiBhdHRyaWJ1dGVzLCBidXQgdGhhdCB0aGVpciBnZW9tZXRyeSBoYXMgYmVjb21lIHBvaW50cy4gTGV0J3MgYWxzbyBtYXAgdGhlbToKCmBgYHtyIHByZXZpZXctY2VudHJvaWRzfQptYXB2aWV3KGNlbnRyb2lkcywgemNvbCA9ICJHRU9JRF9aQ1RBIikKYGBgCgpPbmUgdGhpbmcgSSBsb29rIGZvciBhcmUgY2VudHJvaWQgdGhhdCBtaWdodCBhY3R1YWxseSBmYWxsIG91dHNpZGUgb2YgdGhlIGNvdW50eSBpdHNlbGYuIFpJUCA2MzMwNCAoYWxvbmcgdGhlIE1pc3NvdXJpIFJpdmVyIG5lYXIgQ2hlc3RlcmZpZWxkKSBzdGFuZHMgb3V0IGFzIGEgcG9zc2liaWxpdHkgaGVyZS4KCk5leHQsIHdlJ2xsIGxhYmVsIG91ciB6aXBzIHdpdGggdGhlIGNlbnRyb2lkIHRoZXkgZmFsbCBpbi4gVG8gZG8gdGhpcywgd2UnbGwgaW50ZXJzZWN0IG91ciBjb3VudHkgcG9seWdvbnMgd2l0aCB0aGUgY2VudHJvaWQgdmFsdWVzOgoKYGBge3J9CmNlbnRyb2lkcyA8LSBzdF9pbnRlcnNlY3Rpb24oY2VudHJvaWRzLCBjb3VudGllcykKYGBgCgpOb3RpY2UgdGhhdCB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBkb2Vzbid0IGNoYW5nZSwgc28gd2Uga25vdyB0aGF0IGFsbCBvZiB0aGUgY2VudHJvaWRzIGZlbGwgd2l0aGluIHRoZSB0cmFjdCBib3VuZGFyaWVzLiBJZiB3ZSBkaWQgbG9zZSBhIGNvdW50eSwgaG93IGNvdWxkIHdlIGhhbmRsZSBpdD8KCldlJ2xsIHByZXZpZXcgdGhlbSB0byBnZXQgYSBzZW5zZSBvZiBob3cgdGhlIGRhdGEgaGF2ZSBjaGFuZ2VkOgoKYGBge3IgcHJldmlldy16aXAtY2VudHJvaWRzfQptYXB2aWV3KGNlbnRyb2lkcywgemNvbCA9ICJHRU9JRF9aQ1RBIikKYGBgCgojIyBCdWZmZXJzCkZpbmFsbHksIEkgd2FudCB0byBpbGx1c3RyYXRlIGNhbGN1bGF0aW5nIGEgYnVmZmVyLiBGb3IgZXhhbXBsZSwgd2UgbWlnaHQgd2FudCB0byBnZXQgYSBjb3VudCBvZiBjcmltZXMgdGhhdCBmYWxsIHdpdGhpbiBvciBuZWFyIGEgWklQIGNvZGUgaW4gdGhlIENpdHkuIFdlJ2xsIGV4dHJhY3QgNjMxMDgsIHRoZSBaSVAgdGhhdCBTTFUgcGFydGlhbGx5IGZhbGxzIGluLCBhbmQgYnVmZmVyIGFyb3VuZCBpdDoKCmBgYHtyfQojIGRhdGEgY2xlYW5pbmcKcmVnaW9uICU+JQogIHNlbGVjdChHRU9JRF9aQ1RBKSAlPiUKICBmaWx0ZXIoR0VPSURfWkNUQSA9PSAiNjMxMDgiKSAtPiB6aXBfcHJlCgojIGNhbGN1bGF0ZSBidWZmZXIKemlwX3Bvc3QgPC0gc3RfYnVmZmVyKHppcF9wcmUsIGRpcyA9IDUwMCkKYGBgCgpUaGUgdmFsdWUgYDUwMGAgcmVmZXJzIHRvIDUwMCBtZXRlcnMuIFdlIGtub3cgaXQgaXMgbWV0ZXJzIGJhc2VkIG9uIHRoZSBjb29yZGluYXRlIHN5c3RlbToKCmBgYHtyfQpzdF9jcnMoemlwX3Bvc3QpCmBgYAoKV2UnbGwgcHJldmlldyB0aGUgaW5pdGlhbCB6aXAgZmlyc3QsIGFuZCB0aGVuIHByZXZpZXcgdGhlIGNoYW5nZToKCmBgYHtyIHByZXZpZXctcHJlfQptYXB2aWV3KHppcF9wcmUpCmBgYAoKYGBge3IgcHJldmlldy1wb3N0fQptYXB2aWV3KHppcF9wb3N0KQpgYGAKIApgYGB7ciBtb3ZlLXRvLWRvY3MsIGluY2x1ZGU9RkFMU0V9CiMgeW91IGRvIG5lZWQgdG8gaW5jbHVkZSB0aGlzIGluIGFueSBub3RlYm9vayB5b3UgY3JlYXRlIGZvciB0aGlzIGNsYXNzCmZzOjpmaWxlX2NvcHkoaGVyZTo6aGVyZSgiZXhhbXBsZXMiLCAibWVldGluZy1leGFtcGxlcy1jb21wbGV0ZS5uYi5odG1sIiksIAogICAgICAgICAgICAgIGhlcmU6OmhlcmUoImRvY3MiLCAiaW5kZXgubmIuaHRtbCIpLCAKICAgICAgICAgICAgICBvdmVyd3JpdGUgPSBUUlVFKQpgYGAK