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=