Now that we have started working with some spatial statistics, this next unit will help us start building towards spatial regression. Before we can get there, we need to learn one foundational topic in spatial statistics: spatial autocorrelation. One assumption of any regression model is independence. Let’s say we are working with state-level data. If each state represents an independent observation, then no one state influences any other. But is this really ever true?

In Geography, Tobler’s Law states: Everything is related to everything else, but near things are more related than distant things. We might expect, therefore, that neighboring states influence each other. This tends to be the case - if I pick any two states in the South, they are likely to be more related to each other than, say, a Northern state. So my independence assumption is violated - we often call this “Spatial Autocorrelation”. Data with high spatial autocorrelation is data where neighboring geographic units are more related than distant geographic units. In today’s unit, we will practice methods to check for spatial autocorrelation. Later on, we will work on methods to control for this problem in spatial regression.

To illustrate this, let’s load in some data. Suppose I’m a researcher studying poverty in NYC. I have income data at the neighborhood level, and I have created a Poor-index based on the ratio of a household’s income to the poverty level. I then aggregated the number of households with an index value of 1 or below to create my poor count variable. Are impoverished households clustered near each other in NYC? This will help us to learn whether we should concentrate resources in certain areas more than others. Is there spatial inequality alongside economic inequality?

library(sf)     
library(dplyr)   
library(spData) 
library(ggplot2)
library(ggthemes)
#Let's load in some packages and data
setwd("~/Binghamton/DIDA 370/nyc_neighborhoods")
Warning: The working directory was changed to C:/Users/melha/OneDrive/Documents/Binghamton/DIDA 370/nyc_neighborhoods 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.
nyc <- st_read("NYC_Nhood ACS2008_12.shp")
Reading layer `NYC_Nhood ACS2008_12' from data source 
  `C:\Users\melha\OneDrive\Documents\Binghamton\DIDA 370\nyc_neighborhoods\NYC_Nhood ACS2008_12.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 195 features and 98 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -74.25559 ymin: 40.49612 xmax: -73.70001 ymax: 40.91553
Geodetic CRS:  WGS 84
#Let's plot the data first

ggplot()+
  geom_sf(data = nyc, aes(fill=poor))+
  scale_fill_steps(
    name = "Number of \nPoor Households",
    low = "lightsteelblue1",
    high = "tomato1",
    n.breaks = 4,
    show.limits = T)+
  theme_void()

What do you think, is there spatial autocorrelation in this data?

We can test for this using a common statistic called Moran’s I. This essentially tests whether our data was produced by a spatially random process (or CSR). We will need to follow a few steps to do this. First, we need to download a new packages (spdep). Then, we will need to create some information for R about the spatial relationships between the neighborhoods in the data. We will create what are called spatial weights. Similarly to the weights used to compute a weighted average, a higher weight indicates that two neighborhoods are geographically closer to eachother, while a lower weight indicates that the neighborhoods are furthe apart. Let’s see what this looks like:

nyc_list <- nyc %>% 
  #first convert polygons to a neighbor object
  #R needs to know a unique ID for each neighborhood    
  #(cartodb_id)
  #Neighbors determined using Queen weights
  poly2nb(st_geometry(nyc), row.names = nyc$cartodb_id) %>% 
  #zero.policy just tells R not to remove 0 values
  nb2listw(zero.policy = TRUE)
Error in Ops.sfc(snap, 0) : operation < not supported

Here, with a Moran’s I statistic of 0.363 and a p-value of <0.001, we can reject the null hypothesis and conclude that our data was not generated by a spatially random process. In other words, the number of poor households by NYC neighborhood are not spatially independent - neighborhoods that are close together tend to be more similar than neigborhoods that are further apart.

Let’s look at a plot of these results!

The solid line here goes through the mean value, and slope is the Moran’s I statistic. The positive slope indicates positive autocorrelation - in other words, neighborhoods with high Poor counts tend to be near other neighborhoods with high poor counts. For each point, the x-axis represents the actual Poor count for the neighborhood, while the y-axis represents an estimate of this value for that neighborhood’s neighbors. If the points in our plot are mostly concentrated in the bottom left and top right quadrants of the graph, we have positive autocorrelation. If they are concentrated in the top left and bottom right quadrants, we have negative autocorrelation. In data with no autocorrelation, we would expect to see a random cloud of points (with no pattern).

Next, we’ll take a look at Local Indicators of Spatial Autocorrelation (LISA) plots. This will help us to identify clusters of low or high values in the graph, as well as outliers. This uses the Local Moran’s I statistic. Instead of calculating one Moran’s I value for the whole plot (as is the case with the global statistic), the local version calculates a local I for each spatial unit (neighborhood)

lisaRslt <- localmoran(nyc$poor, nyc_list, 
            zero.policy = TRUE, na.action = na.omit)

Let’s talk about the values in this output for a moment. The Pr column is our P-value. A significant result (i.e. a p-value less than 0.05) tells us that a neighborhood is either a) part of a cluster or b) a spatial outlier. The Li column gives us more info about which one it is. If Li is positive, then a neighborhood is part of a spatial cluster. Otherwise, it is a spatial outlier.

The code below reproduces a LISA plot that is commonly used in GIS software. Essentially, this plot a) helps us to identify significant neighborhoods, and b) visualizes whether they are clusters or outliers. High-High (HH) and Low-Low (LL) neighborhoods are clusters - these are units that are surrounded by units with similar values. High-Low (HL) and Low-High (LH) units are spatial outliers - they are different from their surrounding units. The L and H tell us whether the number of poor households in a neighborhood is low or high relative to the mean.

# Now we can derive the cluster/outlier types (COType in ArcGIS term) for each spatial feature in the data
library(magrittr)

Attaching package: ‘magrittr’

The following object is masked from ‘package:purrr’:

    set_names

The following object is masked from ‘package:tidyr’:

    extract
library(tidyr)
significanceLevel <- 0.05; # 95% confidence
meanVal <- mean(nyc$poor);

lisaRslt %<>% as_tibble() %>%
  set_colnames(c("Ii","E.Ii","Var.Ii","Z.Ii","Pr(z > 0)")) %>%
  #Create a new categorical value
  #This variable will call insignificant units "insignificant"
  #Outliers are determined by LI <0 - units higher than the mean are HL and units lower than the mean are LH
  #Clusters are units with LI >= 0, and classified as HH or LL based on whether they are above or below the mean
  mutate(coType = case_when(
  `Pr(z > 0)` > 0.05 ~ "Insignificant",
  `Pr(z > 0)` <= 0.05 & Ii >= 0 & nyc$poor >= meanVal ~ "HH",
  `Pr(z > 0)` <= 0.05 & Ii >= 0 & nyc$poor < meanVal ~ "LL",
  `Pr(z > 0)` <= 0.05 & Ii < 0 & nyc$poor >= meanVal ~ "HL",
  `Pr(z > 0)` <= 0.05 & Ii < 0 & nyc$poor < meanVal ~ "LH"))

# Now add this coType to original sf data
nyc$coType <- lisaRslt$coType %>% replace_na("Insignificant")

#Now we'll plot it!
ggplot(nyc) +
  geom_sf(aes(fill=coType),color = 'lightgrey') +
  scale_fill_manual(values = c('red','brown','NA','blue','cyan'), name='Clusters & \nOutliers') +
  labs(title = "Number of Poor Families by Neighborhood")+
  theme_minimal()

Now, it’s your turn! Pick a new variable from the data and re-run the analysis. What do you find? Do you see different clustering patterns than the one above?
r #Analysis here

Resources ArcGIS (2022). Global Vs. Local Spatial Autocorrelation. Retrieved from: https://storymaps.arcgis.com/stories/5b26f25bb81a437b89003423505e2f71.

Sun, S. (N. D.). 4.1: Spatial Autocorrelation. Retrieved from: http://www.geo.hunter.cuny.edu/~ssun/R-Spatial/spregression.html#spatial-autocorrelation.

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KTm93IHRoYXQgd2UgaGF2ZSBzdGFydGVkIHdvcmtpbmcgd2l0aCBzb21lIHNwYXRpYWwgc3RhdGlzdGljcywgdGhpcyBuZXh0IHVuaXQgd2lsbCBoZWxwIHVzIHN0YXJ0IGJ1aWxkaW5nIHRvd2FyZHMgc3BhdGlhbCByZWdyZXNzaW9uLiBCZWZvcmUgd2UgY2FuIGdldCB0aGVyZSwgd2UgbmVlZCB0byBsZWFybiBvbmUgZm91bmRhdGlvbmFsIHRvcGljIGluIHNwYXRpYWwgc3RhdGlzdGljczogc3BhdGlhbCBhdXRvY29ycmVsYXRpb24uIE9uZSBhc3N1bXB0aW9uIG9mIGFueSByZWdyZXNzaW9uIG1vZGVsIGlzIGluZGVwZW5kZW5jZS4gTGV0J3Mgc2F5IHdlIGFyZSB3b3JraW5nIHdpdGggc3RhdGUtbGV2ZWwgZGF0YS4gSWYgZWFjaCBzdGF0ZSByZXByZXNlbnRzIGFuIGluZGVwZW5kZW50IG9ic2VydmF0aW9uLCB0aGVuIG5vIG9uZSBzdGF0ZSBpbmZsdWVuY2VzIGFueSBvdGhlci4gQnV0IGlzIHRoaXMgcmVhbGx5IGV2ZXIgdHJ1ZT8NCg0KSW4gR2VvZ3JhcGh5LCBUb2JsZXIncyBMYXcgc3RhdGVzOiBFdmVyeXRoaW5nIGlzIHJlbGF0ZWQgdG8gZXZlcnl0aGluZyBlbHNlLCBidXQgbmVhciB0aGluZ3MgYXJlIG1vcmUgcmVsYXRlZCB0aGFuIGRpc3RhbnQgdGhpbmdzLiBXZSBtaWdodCBleHBlY3QsIHRoZXJlZm9yZSwgdGhhdCBuZWlnaGJvcmluZyBzdGF0ZXMgaW5mbHVlbmNlIGVhY2ggb3RoZXIuIFRoaXMgdGVuZHMgdG8gYmUgdGhlIGNhc2UgLSBpZiBJIHBpY2sgYW55IHR3byBzdGF0ZXMgaW4gdGhlIFNvdXRoLCB0aGV5IGFyZSBsaWtlbHkgdG8gYmUgbW9yZSByZWxhdGVkIHRvIGVhY2ggb3RoZXIgdGhhbiwgc2F5LCBhIE5vcnRoZXJuIHN0YXRlLiBTbyBteSBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbiBpcyB2aW9sYXRlZCAtIHdlIG9mdGVuIGNhbGwgdGhpcyAiU3BhdGlhbCBBdXRvY29ycmVsYXRpb24iLiBEYXRhIHdpdGggaGlnaCBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBpcyBkYXRhIHdoZXJlIG5laWdoYm9yaW5nIGdlb2dyYXBoaWMgdW5pdHMgYXJlIG1vcmUgcmVsYXRlZCB0aGFuIGRpc3RhbnQgZ2VvZ3JhcGhpYyB1bml0cy4gSW4gdG9kYXkncyB1bml0LCB3ZSB3aWxsIHByYWN0aWNlIG1ldGhvZHMgdG8gY2hlY2sgZm9yIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uLiBMYXRlciBvbiwgd2Ugd2lsbCB3b3JrIG9uIG1ldGhvZHMgdG8gY29udHJvbCBmb3IgdGhpcyBwcm9ibGVtIGluIHNwYXRpYWwgcmVncmVzc2lvbi4NCg0KVG8gaWxsdXN0cmF0ZSB0aGlzLCBsZXQncyBsb2FkIGluIHNvbWUgZGF0YS4gU3VwcG9zZSBJJ20gYSByZXNlYXJjaGVyIHN0dWR5aW5nIHBvdmVydHkgaW4gTllDLiBJIGhhdmUgaW5jb21lIGRhdGEgYXQgdGhlIG5laWdoYm9yaG9vZCBsZXZlbCwgYW5kIEkgaGF2ZSBjcmVhdGVkIGEgUG9vci1pbmRleCBiYXNlZCBvbiB0aGUgcmF0aW8gb2YgYSBob3VzZWhvbGQncyBpbmNvbWUgdG8gdGhlIHBvdmVydHkgbGV2ZWwuIEkgdGhlbiBhZ2dyZWdhdGVkIHRoZSBudW1iZXIgb2YgaG91c2Vob2xkcyB3aXRoIGFuIGluZGV4IHZhbHVlIG9mIDEgb3IgYmVsb3cgdG8gY3JlYXRlIG15IHBvb3IgY291bnQgdmFyaWFibGUuIEFyZSBpbXBvdmVyaXNoZWQgaG91c2Vob2xkcyBjbHVzdGVyZWQgbmVhciBlYWNoIG90aGVyIGluIE5ZQz8gVGhpcyB3aWxsIGhlbHAgdXMgdG8gbGVhcm4gd2hldGhlciB3ZSBzaG91bGQgY29uY2VudHJhdGUgcmVzb3VyY2VzIGluIGNlcnRhaW4gYXJlYXMgbW9yZSB0aGFuIG90aGVycy4gSXMgdGhlcmUgc3BhdGlhbCBpbmVxdWFsaXR5IGFsb25nc2lkZSBlY29ub21pYyBpbmVxdWFsaXR5Pw0KDQoNCmBgYHtyfQ0KbGlicmFyeShzZikgICAgIA0KbGlicmFyeShkcGx5cikgICANCmxpYnJhcnkoc3BEYXRhKSANCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZ2d0aGVtZXMpDQojTGV0J3MgbG9hZCBpbiBzb21lIHBhY2thZ2VzIGFuZCBkYXRhDQpzZXR3ZCgifi9CaW5naGFtdG9uL0RJREEgMzcwL255Y19uZWlnaGJvcmhvb2RzIikNCm55YyA8LSBzdF9yZWFkKCJOWUNfTmhvb2QgQUNTMjAwOF8xMi5zaHAiKQ0KDQojTGV0J3MgcGxvdCB0aGUgZGF0YSBmaXJzdA0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gbnljLCBhZXMoZmlsbD1wb29yKSkrDQogIHNjYWxlX2ZpbGxfc3RlcHMoDQogICAgbmFtZSA9ICJOdW1iZXIgb2YgXG5Qb29yIEhvdXNlaG9sZHMiLA0KICAgIGxvdyA9ICJsaWdodHN0ZWVsYmx1ZTEiLA0KICAgIGhpZ2ggPSAidG9tYXRvMSIsDQogICAgbi5icmVha3MgPSA0LA0KICAgIHNob3cubGltaXRzID0gVCkrDQogIHRoZW1lX3ZvaWQoKQ0KDQpgYGANCldoYXQgZG8geW91IHRoaW5rLCBpcyB0aGVyZSBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBpbiB0aGlzIGRhdGE/DQoNCldlIGNhbiB0ZXN0IGZvciB0aGlzIHVzaW5nIGEgY29tbW9uIHN0YXRpc3RpYyBjYWxsZWQgTW9yYW4ncyBJLiBUaGlzIGVzc2VudGlhbGx5IHRlc3RzIHdoZXRoZXIgb3VyIGRhdGEgd2FzIHByb2R1Y2VkIGJ5IGEgc3BhdGlhbGx5IHJhbmRvbSBwcm9jZXNzIChvciBDU1IpLiBXZSB3aWxsIG5lZWQgdG8gZm9sbG93IGEgZmV3IHN0ZXBzIHRvIGRvIHRoaXMuIEZpcnN0LCB3ZSBuZWVkIHRvIGRvd25sb2FkIGEgbmV3IHBhY2thZ2VzIChzcGRlcCkuIFRoZW4sIHdlIHdpbGwgbmVlZCB0byBjcmVhdGUgc29tZSBpbmZvcm1hdGlvbiBmb3IgUiBhYm91dCB0aGUgc3BhdGlhbCByZWxhdGlvbnNoaXBzIGJldHdlZW4gdGhlIG5laWdoYm9yaG9vZHMgaW4gdGhlIGRhdGEuIFdlIHdpbGwgY3JlYXRlIHdoYXQgYXJlIGNhbGxlZCBzcGF0aWFsIHdlaWdodHMuIFNpbWlsYXJseSB0byB0aGUgd2VpZ2h0cyB1c2VkIHRvIGNvbXB1dGUgYSB3ZWlnaHRlZCBhdmVyYWdlLCBhIGhpZ2hlciB3ZWlnaHQgaW5kaWNhdGVzIHRoYXQgdHdvIG5laWdoYm9yaG9vZHMgYXJlIGdlb2dyYXBoaWNhbGx5IGNsb3NlciB0byBlYWNob3RoZXIsIHdoaWxlIGEgbG93ZXIgd2VpZ2h0IGluZGljYXRlcyB0aGF0IHRoZSBuZWlnaGJvcmhvb2RzIGFyZSBmdXJ0aGUgYXBhcnQuIExldCdzIHNlZSB3aGF0IHRoaXMgbG9va3MgbGlrZToNCg0KYGBge3J9DQpsaWJyYXJ5KHNwZGVwKQ0KDQojVGhpcyBjb2RlIGNyZWF0ZXMgdGhlIHdlaWdodHMNCm55Y19saXN0IDwtIG55YyAlPiUgDQogICNmaXJzdCBjb252ZXJ0IHBvbHlnb25zIHRvIGEgbmVpZ2hib3Igb2JqZWN0DQogICNSIG5lZWRzIHRvIGtub3cgYSB1bmlxdWUgSUQgZm9yIGVhY2ggbmVpZ2hib3Job29kICAgIA0KICAjKGNhcnRvZGJfaWQpDQogICNOZWlnaGJvcnMgZGV0ZXJtaW5lZCB1c2luZyBRdWVlbiB3ZWlnaHRzDQogIHBvbHkybmIoc3RfZ2VvbWV0cnkobnljKSkgJT4lIA0KICAjemVyby5wb2xpY3kganVzdCB0ZWxscyBSIG5vdCB0byByZW1vdmUgMCB2YWx1ZXMNCiAgbmIybGlzdHcoemVyby5wb2xpY3kgPSBUUlVFKSANCg0KI05vdyB3ZSBjYW4gY2FsY3VsYXRlIHRoZSAiR2xvYmFsIE1vcmFuJ3MgSSIgZm9yIHRoZSBkYXRhDQojc3RhcnQgd2l0aCB0aGUgd2VpZ2h0cywgdGhlbiBnaXZlIFIgdGhlIGNvcnJlc3BvbmRpbmcgdmFyaWFibGUNCm55Y19saXN0ICU+JSANCiAgbW9yYW4udGVzdChueWMkcG9vciwgLiwgemVyby5wb2xpY3kgPSBUUlVFKQ0KDQpgYGANCkhlcmUsIHdpdGggYSBNb3JhbidzIEkgc3RhdGlzdGljIG9mIDAuMzYzIGFuZCBhIHAtdmFsdWUgb2YgPDAuMDAxLCB3ZSBjYW4gcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMgYW5kIGNvbmNsdWRlIHRoYXQgb3VyIGRhdGEgd2FzIG5vdCBnZW5lcmF0ZWQgYnkgYSBzcGF0aWFsbHkgcmFuZG9tIHByb2Nlc3MuIEluIG90aGVyIHdvcmRzLCB0aGUgbnVtYmVyIG9mIHBvb3IgaG91c2Vob2xkcyBieSBOWUMgbmVpZ2hib3Job29kIGFyZSBub3Qgc3BhdGlhbGx5IGluZGVwZW5kZW50IC0gbmVpZ2hib3Job29kcyB0aGF0IGFyZSBjbG9zZSB0b2dldGhlciB0ZW5kIHRvIGJlIG1vcmUgc2ltaWxhciB0aGFuIG5laWdib3Job29kcyB0aGF0IGFyZSBmdXJ0aGVyIGFwYXJ0LiANCg0KTGV0J3MgbG9vayBhdCBhIHBsb3Qgb2YgdGhlc2UgcmVzdWx0cyENCg0KYGBge3J9DQptb3Jhbi5wbG90KG55YyRwb29yLCANCiAgICAgICAgICAgbnljX2xpc3QsIA0KICAgICAgICAgICB6ZXJvLnBvbGljeSA9IFRSVUUsIA0KICAgICAgICAgICBsYWJlbHMgPSBGLA0KICAgICAgICAgICB4bGFiID0gJ051bWJlciBvZiBQb29yIEhvdXNlaG9sZHMnLA0KICAgICAgICAgICB5bGFiID0gJ0xhZ2dlZCBQb29yIEhvdXNlaG9sZHMgKG9mIE5laWdoYm9ycyknLA0KICAgICAgICAgICAgICAgICAgIHBjaD0yMCkNCmBgYA0KVGhlIHNvbGlkIGxpbmUgaGVyZSBnb2VzIHRocm91Z2ggdGhlIG1lYW4gdmFsdWUsIGFuZCBzbG9wZSBpcyB0aGUgTW9yYW4ncyBJIHN0YXRpc3RpYy4gVGhlIHBvc2l0aXZlIHNsb3BlIGluZGljYXRlcyBwb3NpdGl2ZSBhdXRvY29ycmVsYXRpb24gLSBpbiBvdGhlciB3b3JkcywgbmVpZ2hib3Job29kcyB3aXRoIGhpZ2ggUG9vciBjb3VudHMgdGVuZCB0byBiZSBuZWFyIG90aGVyIG5laWdoYm9yaG9vZHMgd2l0aCBoaWdoIHBvb3IgY291bnRzLiBGb3IgZWFjaCBwb2ludCwgdGhlIHgtYXhpcyByZXByZXNlbnRzIHRoZSBhY3R1YWwgUG9vciBjb3VudCBmb3IgdGhlIG5laWdoYm9yaG9vZCwgd2hpbGUgdGhlIHktYXhpcyByZXByZXNlbnRzIGFuIGVzdGltYXRlIG9mIHRoaXMgdmFsdWUgZm9yIHRoYXQgbmVpZ2hib3Job29kJ3MgbmVpZ2hib3JzLiBJZiB0aGUgcG9pbnRzIGluIG91ciBwbG90IGFyZSBtb3N0bHkgY29uY2VudHJhdGVkIGluIHRoZSBib3R0b20gbGVmdCBhbmQgdG9wIHJpZ2h0IHF1YWRyYW50cyBvZiB0aGUgZ3JhcGgsIHdlIGhhdmUgcG9zaXRpdmUgYXV0b2NvcnJlbGF0aW9uLiBJZiB0aGV5IGFyZSBjb25jZW50cmF0ZWQgaW4gdGhlIHRvcCBsZWZ0IGFuZCBib3R0b20gcmlnaHQgcXVhZHJhbnRzLCB3ZSBoYXZlIG5lZ2F0aXZlIGF1dG9jb3JyZWxhdGlvbi4gSW4gZGF0YSB3aXRoIG5vIGF1dG9jb3JyZWxhdGlvbiwgd2Ugd291bGQgZXhwZWN0IHRvIHNlZSBhIHJhbmRvbSBjbG91ZCBvZiBwb2ludHMgKHdpdGggbm8gcGF0dGVybikuIA0KDQpOZXh0LCB3ZSdsbCB0YWtlIGEgbG9vayBhdCBMb2NhbCBJbmRpY2F0b3JzIG9mIFNwYXRpYWwgQXV0b2NvcnJlbGF0aW9uIChMSVNBKSBwbG90cy4gVGhpcyB3aWxsIGhlbHAgdXMgdG8gaWRlbnRpZnkgY2x1c3RlcnMgb2YgbG93IG9yIGhpZ2ggdmFsdWVzIGluIHRoZSBncmFwaCwgYXMgd2VsbCBhcyBvdXRsaWVycy4gVGhpcyB1c2VzIHRoZSBMb2NhbCBNb3JhbidzIEkgc3RhdGlzdGljLiBJbnN0ZWFkIG9mIGNhbGN1bGF0aW5nIG9uZSBNb3JhbidzIEkgdmFsdWUgZm9yIHRoZSB3aG9sZSBwbG90IChhcyBpcyB0aGUgY2FzZSB3aXRoIHRoZSBnbG9iYWwgc3RhdGlzdGljKSwgdGhlIGxvY2FsIHZlcnNpb24gY2FsY3VsYXRlcyBhIGxvY2FsIEkgZm9yIGVhY2ggc3BhdGlhbCB1bml0IChuZWlnaGJvcmhvb2QpDQoNCmBgYHtyfQ0KbGlzYVJzbHQgPC0gbG9jYWxtb3JhbihueWMkcG9vciwgbnljX2xpc3QsIA0KICAgICAgICAgICAgemVyby5wb2xpY3kgPSBUUlVFLCBuYS5hY3Rpb24gPSBuYS5vbWl0KQ0KYGBgDQoNCkxldCdzIHRhbGsgYWJvdXQgdGhlIHZhbHVlcyBpbiB0aGlzIG91dHB1dCBmb3IgYSBtb21lbnQuIFRoZSBQciBjb2x1bW4gaXMgb3VyIFAtdmFsdWUuIEEgc2lnbmlmaWNhbnQgcmVzdWx0IChpLmUuIGEgcC12YWx1ZSBsZXNzIHRoYW4gMC4wNSkgdGVsbHMgdXMgdGhhdCBhIG5laWdoYm9yaG9vZCBpcyBlaXRoZXIgYSkgcGFydCBvZiBhIGNsdXN0ZXIgb3IgYikgYSBzcGF0aWFsIG91dGxpZXIuIFRoZSBMaSBjb2x1bW4gZ2l2ZXMgdXMgbW9yZSBpbmZvIGFib3V0IHdoaWNoIG9uZSBpdCBpcy4gSWYgTGkgaXMgcG9zaXRpdmUsIHRoZW4gYSBuZWlnaGJvcmhvb2QgaXMgcGFydCBvZiBhIHNwYXRpYWwgY2x1c3Rlci4gT3RoZXJ3aXNlLCBpdCBpcyBhIHNwYXRpYWwgb3V0bGllci4gDQoNClRoZSBjb2RlIGJlbG93IHJlcHJvZHVjZXMgYSBMSVNBIHBsb3QgdGhhdCBpcyBjb21tb25seSB1c2VkIGluIEdJUyBzb2Z0d2FyZS4gRXNzZW50aWFsbHksIHRoaXMgcGxvdCBhKSBoZWxwcyB1cyB0byBpZGVudGlmeSBzaWduaWZpY2FudCBuZWlnaGJvcmhvb2RzLCBhbmQgYikgdmlzdWFsaXplcyB3aGV0aGVyIHRoZXkgYXJlIGNsdXN0ZXJzIG9yIG91dGxpZXJzLiBIaWdoLUhpZ2ggKEhIKSBhbmQgTG93LUxvdyAoTEwpIG5laWdoYm9yaG9vZHMgYXJlIGNsdXN0ZXJzIC0gdGhlc2UgYXJlIHVuaXRzIHRoYXQgYXJlIHN1cnJvdW5kZWQgYnkgdW5pdHMgd2l0aCBzaW1pbGFyIHZhbHVlcy4gSGlnaC1Mb3cgKEhMKSBhbmQgTG93LUhpZ2ggKExIKSB1bml0cyBhcmUgc3BhdGlhbCBvdXRsaWVycyAtIHRoZXkgYXJlIGRpZmZlcmVudCBmcm9tIHRoZWlyIHN1cnJvdW5kaW5nIHVuaXRzLiBUaGUgTCBhbmQgSCB0ZWxsIHVzIHdoZXRoZXIgdGhlIG51bWJlciBvZiBwb29yIGhvdXNlaG9sZHMgaW4gYSBuZWlnaGJvcmhvb2QgaXMgbG93IG9yIGhpZ2ggcmVsYXRpdmUgdG8gdGhlIG1lYW4uIA0KDQpgYGB7cn0NCiMgTm93IHdlIGNhbiBkZXJpdmUgdGhlIGNsdXN0ZXIvb3V0bGllciB0eXBlcyAoQ09UeXBlIGluIEFyY0dJUyB0ZXJtKSBmb3IgZWFjaCBzcGF0aWFsIGZlYXR1cmUgaW4gdGhlIGRhdGENCmxpYnJhcnkobWFncml0dHIpDQpsaWJyYXJ5KHRpZHlyKQ0Kc2lnbmlmaWNhbmNlTGV2ZWwgPC0gMC4wNTsgIyA5NSUgY29uZmlkZW5jZQ0KbWVhblZhbCA8LSBtZWFuKG55YyRwb29yKTsNCg0KbGlzYVJzbHQgJTw+JSBhc190aWJibGUoKSAlPiUNCiAgc2V0X2NvbG5hbWVzKGMoIklpIiwiRS5JaSIsIlZhci5JaSIsIlouSWkiLCJQcih6ID4gMCkiKSkgJT4lDQogICNDcmVhdGUgYSBuZXcgY2F0ZWdvcmljYWwgdmFsdWUNCiAgI1RoaXMgdmFyaWFibGUgd2lsbCBjYWxsIGluc2lnbmlmaWNhbnQgdW5pdHMgImluc2lnbmlmaWNhbnQiDQogICNPdXRsaWVycyBhcmUgZGV0ZXJtaW5lZCBieSBMSSA8MCAtIHVuaXRzIGhpZ2hlciB0aGFuIHRoZSBtZWFuIGFyZSBITCBhbmQgdW5pdHMgbG93ZXIgdGhhbiB0aGUgbWVhbiBhcmUgTEgNCiAgI0NsdXN0ZXJzIGFyZSB1bml0cyB3aXRoIExJID49IDAsIGFuZCBjbGFzc2lmaWVkIGFzIEhIIG9yIExMIGJhc2VkIG9uIHdoZXRoZXIgdGhleSBhcmUgYWJvdmUgb3IgYmVsb3cgdGhlIG1lYW4NCiAgbXV0YXRlKGNvVHlwZSA9IGNhc2Vfd2hlbigNCiAgYFByKHogPiAwKWAgPiAwLjA1IH4gIkluc2lnbmlmaWNhbnQiLA0KICBgUHIoeiA+IDApYCA8PSAwLjA1ICYgSWkgPj0gMCAmIG55YyRwb29yID49IG1lYW5WYWwgfiAiSEgiLA0KICBgUHIoeiA+IDApYCA8PSAwLjA1ICYgSWkgPj0gMCAmIG55YyRwb29yIDwgbWVhblZhbCB+ICJMTCIsDQogIGBQcih6ID4gMClgIDw9IDAuMDUgJiBJaSA8IDAgJiBueWMkcG9vciA+PSBtZWFuVmFsIH4gIkhMIiwNCiAgYFByKHogPiAwKWAgPD0gMC4wNSAmIElpIDwgMCAmIG55YyRwb29yIDwgbWVhblZhbCB+ICJMSCIpKQ0KDQojIE5vdyBhZGQgdGhpcyBjb1R5cGUgdG8gb3JpZ2luYWwgc2YgZGF0YQ0KbnljJGNvVHlwZSA8LSBsaXNhUnNsdCRjb1R5cGUgJT4lIHJlcGxhY2VfbmEoIkluc2lnbmlmaWNhbnQiKQ0KDQojTm93IHdlJ2xsIHBsb3QgaXQhDQpnZ3Bsb3QobnljKSArDQogIGdlb21fc2YoYWVzKGZpbGw9Y29UeXBlKSxjb2xvciA9ICdsaWdodGdyZXknKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJ3JlZCcsJ2Jyb3duJywnTkEnLCdibHVlJywnY3lhbicpLCBuYW1lPSdDbHVzdGVycyAmIFxuT3V0bGllcnMnKSArDQogIGxhYnModGl0bGUgPSAiTnVtYmVyIG9mIFBvb3IgRmFtaWxpZXMgYnkgTmVpZ2hib3Job29kIikrDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCk5vdywgaXQncyB5b3VyIHR1cm4hIFBpY2sgYSBuZXcgdmFyaWFibGUgZnJvbSB0aGUgZGF0YSBhbmQgcmUtcnVuIHRoZSBhbmFseXNpcy4gV2hhdCBkbyB5b3UgZmluZD8gRG8geW91IHNlZSBkaWZmZXJlbnQgY2x1c3RlcmluZyBwYXR0ZXJucyB0aGFuIHRoZSBvbmUgYWJvdmU/DQpgYGB7cn0NCiNBbmFseXNpcyBoZXJlDQpgYGANCg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpSZXNvdXJjZXMNCkFyY0dJUyAoMjAyMikuIEdsb2JhbCBWcy4gTG9jYWwgU3BhdGlhbCBBdXRvY29ycmVsYXRpb24uIFJldHJpZXZlZCBmcm9tOiBodHRwczovL3N0b3J5bWFwcy5hcmNnaXMuY29tL3N0b3JpZXMvNWIyNmYyNWJiODFhNDM3Yjg5MDAzNDIzNTA1ZTJmNzEuIA0KDQpTdW4sIFMuIChOLiBELikuIDQuMTogU3BhdGlhbCBBdXRvY29ycmVsYXRpb24uIFJldHJpZXZlZCBmcm9tOiBodHRwOi8vd3d3Lmdlby5odW50ZXIuY3VueS5lZHUvfnNzdW4vUi1TcGF0aWFsL3NwcmVncmVzc2lvbi5odG1sI3NwYXRpYWwtYXV0b2NvcnJlbGF0aW9uLiA=