Suggested citation:
Mendez C. (2020). Spatial autocorrelation analysis in R. R Studio/RPubs. Available at https://rpubs.com/quarcs-lab/spatial-autocorrelation
This work is licensed under the Creative Commons Attribution-Share Alike 4.0 International License.
Acknowledgment:
Material adapted from multiple sources, in particular the course materials of Guy Lansley & James Cheshire (2016).
Libraries
knitr::opts_chunk$set(echo = TRUE)
library(tidyverse) # Modern data science workflow
library(sf)
library(sp)
library(spdep)
library(rgdal)
library(rgeos)
library(tmap)
library(tmaptools)
library(spgwr)
library(grid)
library(gridExtra)
# Change the presentation of decimal numbers to 4 and avoid scientific notation
options(prompt="R> ", digits=4, scipen=999)
Motivation
Waldo Tober’s first law of geography is that “Everything is related to everything else, but near things are more related than distant things.” So we would expect most geographic phenomena to exert a spatial autocorrelation of some kind. In population data this is often the case as persons with similar characteristics tend to reside in similar neighbourhoods due to a range of reasons including house prices, proximity to workplaces and cultural factors.
Tutorial objectives
- Run various measures of spatial correlation
- Evaluate both global and local measures of spatial autocorrelation
- Identify spatial clusters
Import datasets
Non-spatial data
Census.Data <-read.csv("practicaldata.csv")
## Rows: 749
## Columns: 5
## $ OA <chr> "E00004120", "E00004121", "E00004122", "E00004123", "E00…
## $ White_British <dbl> 42.36, 47.20, 40.68, 49.66, 51.14, 41.42, 48.54, 48.68, …
## $ Low_Occupancy <dbl> 6.2937, 5.9322, 2.9126, 0.9259, 2.0000, 3.9326, 5.5556, …
## $ Unemployed <dbl> 1.8939, 2.6882, 1.2121, 2.8037, 3.8168, 3.8462, 4.5455, …
## $ Qualification <dbl> 73.63, 69.90, 67.58, 60.78, 65.99, 74.21, 62.45, 60.35, …
Spatial data
Output.Areas <- readOGR(".", "Camden_oa11")
## Warning in OGRSpatialRef(dsn, layer, morphFromESRI = morphFromESRI, dumpSRS =
## dumpSRS, : Discarded datum OSGB_1936 in Proj4 definition: +proj=tmerc +lat_0=49
## +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +units=m +no_defs
## OGR data source with driver: ESRI Shapefile
## Source: "/Users/carlos/GitHub/tutorial-spatial-autocorrelation", layer: "Camden_oa11"
## with 749 features
## It has 1 fields
# Run it in console (long output)
glimpse(Output.Areas)
Using the sf
package
Output.Areas2 <- read_sf("Camden_oa11.shp")
## Rows: 749
## Columns: 2
## $ OA11CD <chr> "E00004527", "E00004525", "E00004522", "E00004287", "E0000420…
## $ geometry <POLYGON [m]> POLYGON ((530648 181230, 53..., POLYGON ((530511 1815…
Merge datasets
OA.Census <- merge(Output.Areas, Census.Data, by.x="OA11CD", by.y="OA")
Transform it into a sf
object
OA.Census_sf <- st_as_sf(OA.Census)
Spatial distribution
tm_shape(OA.Census_sf) +
tm_fill("Qualification",
palette = "Reds",
style = "quantile",
title = "% with a Qualification") +
tm_borders(alpha=.4)
Neightbour structure
Find queen neighbours
neighbours <- poly2nb(OA.Census)
neighbours
## Neighbour list object:
## Number of regions: 749
## Number of nonzero links: 4342
## Percentage nonzero weights: 0.774
## Average number of links: 5.797
neighbours_sf <- poly2nb(OA.Census_sf)
neighbours_sf
## Neighbour list object:
## Number of regions: 749
## Number of nonzero links: 4342
## Percentage nonzero weights: 0.774
## Average number of links: 5.797
Plot queen neighbours links
plot(OA.Census, border = 'lightgrey')
plot(neighbours, coordinates(OA.Census), add=TRUE, col='red')
Find rook neighbours
neighbours2 <- poly2nb(OA.Census, queen = FALSE)
neighbours2
## Neighbour list object:
## Number of regions: 749
## Number of nonzero links: 4176
## Percentage nonzero weights: 0.7444
## Average number of links: 5.575
Plot rook neighbours
plot(OA.Census, border = 'lightgrey')
plot(neighbours2, coordinates(OA.Census), add=TRUE, col='blue')
Plot queen vs rook
plot(OA.Census, border = 'lightgrey')
plot(neighbours, coordinates(OA.Census), add=TRUE, col='red')
plot(neighbours2, coordinates(OA.Census), add=TRUE, col='blue')
Global spatial autocorrelation
Define weights matrix
listw <- nb2listw(neighbours2)
listw
## Characteristics of weights list object:
## Neighbour list object:
## Number of regions: 749
## Number of nonzero links: 4176
## Percentage nonzero weights: 0.7444
## Average number of links: 5.575
##
## Weights style: W
## Weights constants summary:
## n nn S0 S1 S2
## W 749 561001 749 285.4 3114
Global Moran test
The correlation score is between -1 and 1. Much like a correlation coefficient, 1 determines perfect positive spatial autocorrelation (so our data is clustered), 0 identifies the data is randomly distributed and -1 represents negative spatial autocorrelation (so dissimilar values are next to each other).
globalMoran <- moran.test(OA.Census$Qualification, listw)
globalMoran
##
## Moran I test under randomisation
##
## data: OA.Census$Qualification
## weights: listw
##
## Moran I statistic standard deviate = 24, p-value <0.0000000000000002
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic Expectation Variance
## 0.5448699 -0.0013369 0.0005056
globalMoran[["estimate"]][["Moran I statistic"]]
## [1] 0.5449
## [1] 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001188
The Moran I statistic is 0.54, we can, therefore, determine that there our qualification variable is positively autocorrelated in Camden. In other words, the data does spatially cluster. We can also consider the p-value as a measure of the statistical significance of the model.
Local spatial autocorrelation
Moran scatterplot
moran <- moran.plot(OA.Census$Qualification, listw = nb2listw(neighbours2, style = "W"))
Compute local Moran
local <- localmoran(x = OA.Census$Qualification, listw = nb2listw(neighbours2, style = "W"))
By considering the help page for the localmoran function (run ?localmoran
in R console) we can observe the arguments and outputs. We get a number of useful statistics from the model which are as defined:
Ii: local moran statistic
E.Ii: expectation of local moran statistic
Var.Ii: variance of local moran statistic
Z.Ii: standard deviate of local moran statistic
Pr(): p-value of local moran statistic
Plot local Moran
- A map the local moran statistic (Ii).
A positive value for Ii indicates that the unit is surrounded by units with similar values.
# binds results to our polygon shapefile
moran.map <- cbind(OA.Census, local)
tm_shape(moran.map) +
tm_fill(col = "Ii",
style = "quantile",
title = "local moran statistic")
## Warning in sp::proj4string(obj): CRS object has comment, which is lost in output
## Variable(s) "Ii" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full spectrum of the color palette.
Plot LISA clusters
quadrant <- vector(mode="numeric",length=nrow(local))
# centers the variable of interest around its mean
m.qualification <- OA.Census$Qualification - mean(OA.Census$Qualification)
# centers the local Moran's around the mean
m.local <- local[,1] - mean(local[,1])
# significance threshold
signif <- 0.1
# builds a data quadrant
quadrant[m.qualification >0 & m.local>0] <- 4
quadrant[m.qualification <0 & m.local<0] <- 1
quadrant[m.qualification <0 & m.local>0] <- 2
quadrant[m.qualification >0 & m.local<0] <- 3
quadrant[local[,5]>signif] <- 0
# plot in r
brks <- c(0,1,2,3,4)
colors <- c("white","blue",rgb(0,0,1,alpha=0.4),rgb(1,0,0,alpha=0.4),"red")
plot(OA.Census,border="lightgray",col=colors[findInterval(quadrant,brks,all.inside=FALSE)])
box()
legend("bottomleft", legend = c("insignificant","low-low","low-high","high-low","high-high"),
fill=colors,bty="n")
Getis-Ord approach
The Getis-Ord Gi Statistic looks at neighbours within a defined proximity to identify where either high or low values cluster spatially. Here statistically significant hot-spots are recognised as areas of high values where other areas within a neighbourhood range also share high values too.
First, we need to define a new set of neighbours. Whilst the spatial autocorrection considered units which shared borders, for Getis-Ord we are defining neighbours based on proximity. The example below shows the results where we have a search radius of 250 metres.
However, here a search radius of just 250 metres fails to define nearest neighbours for some areas so we will need to set the radius as 800 metres or more for our model in Camden.
- creates centroid and joins neighbours within 0 and 800 units
nb <- dnearneigh(coordinates(OA.Census), 0, 800)
nb_lw <- nb2listw(nb, style = 'B')
Plot data and neighbours
plot(OA.Census, border = 'lightgrey')
plot(nb, coordinates(OA.Census), add=TRUE, col = 'red')
Getis-Ord Gi statistic
NOTE: On some machines the cbind
may not work with a spatial data file, in this case, you will need to change OA.Census to OA.Census@data so that R knows which part of the spatial data file to join. If you take this approach the subsequent column ordering may be different to what is shown in the example below.
local_g <- localG(OA.Census$Qualification, nb_lw)
local_g <- cbind(OA.Census, as.matrix(local_g))
names(local_g)[6] <- "gstat"
Cluster map
tm_shape(local_g) +
tm_fill("gstat",
palette = "RdBu",
style = "pretty") +
tm_borders(alpha=.4)
## Warning in sp::proj4string(obj): CRS object has comment, which is lost in output
## Variable(s) "gstat" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full spectrum of the color palette.
The Gi Statistic is represented as a Z-score. Greater values represent a greater intensity of clustering and the direction (positive or negative) indicates high or low clusters. The final map should indicate the location of hot-spots across Camden. Repeat this for another variable.
LS0tCnRpdGxlOiAiU3BhdGlhbCBhdXRvY29ycmVsYXRpb24gYW5hbHlzaXMgaW4gUiIKYXV0aG9yOiAiQ2FybG9zIE1lbmRleiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlCiAgICB0b2NfZGVwdGg6IDQKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiAic2hvdyIKICAgIHRoZW1lOiAiY29zbW8iCiAgICBoaWdobGlnaHQ6ICJtb25vY2hyb21lIgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGhpZ2hsaWdodDogbW9ub2Nocm9tZQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBjb3NtbwogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IG5vCiAgICAgIHNtb290aF9zY3JvbGw6IG5vCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgd29yZF9kb2N1bWVudDogZGVmYXVsdApiaWJsaW9ncmFwaHk6IGJpYmxpby5iaWIKLS0tCgo8c3R5bGU+CmgxLnRpdGxlIHtmb250LXNpemU6IDE4cHQ7IGNvbG9yOiBEYXJrQmx1ZTt9IApib2R5LCBoMSwgaDIsIGgzLCBoNCB7Zm9udC1mYW1pbHk6ICJQYWxhdGlubyIsIHNlcmlmO30KYm9keSB7Zm9udC1zaXplOiAxMnB0O30KLyogSGVhZGVycyAqLwpoMSxoMixoMyxoNCxoNSxoNntmb250LXNpemU6IDE0cHQ7IGNvbG9yOiAjMDAwMDhCO30KYm9keSB7Y29sb3I6ICMzMzMzMzM7fQphLCBhOmhvdmVyIHtjb2xvcjogIzhCM0E2Mjt9CnByZSB7Zm9udC1zaXplOiAxMnB4O30KPC9zdHlsZT4KClN1Z2dlc3RlZCBjaXRhdGlvbjogCgo+IE1lbmRleiBDLiAoMjAyMCkuICBTcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBhbmFseXNpcyBpbiBSLiBSIFN0dWRpby9SUHVicy4gQXZhaWxhYmxlIGF0IDxodHRwczovL3JwdWJzLmNvbS9xdWFyY3MtbGFiL3NwYXRpYWwtYXV0b2NvcnJlbGF0aW9uPgoKVGhpcyB3b3JrIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBDcmVhdGl2ZSBDb21tb25zIEF0dHJpYnV0aW9uLVNoYXJlIEFsaWtlIDQuMCBJbnRlcm5hdGlvbmFsIExpY2Vuc2UuIAohW10oTGljZW5zZS5wbmcpCgpBY2tub3dsZWRnbWVudDoKCk1hdGVyaWFsIGFkYXB0ZWQgZnJvbSBtdWx0aXBsZSBzb3VyY2VzLCBpbiBwYXJ0aWN1bGFyIHRoZSBjb3Vyc2UgbWF0ZXJpYWxzIG9mIFtHdXkgTGFuc2xleSAmIEphbWVzIENoZXNoaXJlICgyMDE2KV0oaHR0cHM6Ly9kYXRhLmNkcmMuYWMudWsvdHV0b3JpYWwvYW4taW50cm9kdWN0aW9uLXRvLXNwYXRpYWwtZGF0YS1hbmFseXNpcy1hbmQtdmlzdWFsaXNhdGlvbi1pbi1yKS4KCiMgTGlicmFyaWVzCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCgpsaWJyYXJ5KHRpZHl2ZXJzZSkgICMgTW9kZXJuIGRhdGEgc2NpZW5jZSB3b3JrZmxvdwpsaWJyYXJ5KHNmKQpsaWJyYXJ5KHNwKQpsaWJyYXJ5KHNwZGVwKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KHJnZW9zKQpsaWJyYXJ5KHRtYXApCmxpYnJhcnkodG1hcHRvb2xzKQpsaWJyYXJ5KHNwZ3dyKQpsaWJyYXJ5KGdyaWQpCmxpYnJhcnkoZ3JpZEV4dHJhKQoKCiMgQ2hhbmdlIHRoZSBwcmVzZW50YXRpb24gb2YgZGVjaW1hbCBudW1iZXJzIHRvIDQgYW5kIGF2b2lkIHNjaWVudGlmaWMgbm90YXRpb24Kb3B0aW9ucyhwcm9tcHQ9IlI+ICIsIGRpZ2l0cz00LCBzY2lwZW49OTk5KQpgYGAKCiMgTW90aXZhdGlvbgoKV2FsZG8gVG9iZXLigJlzIGZpcnN0IGxhdyBvZiBnZW9ncmFwaHkgaXMgdGhhdCDigJxFdmVyeXRoaW5nIGlzIHJlbGF0ZWQgdG8gZXZlcnl0aGluZyBlbHNlLCBidXQgbmVhciB0aGluZ3MgYXJlIG1vcmUgcmVsYXRlZCB0aGFuIGRpc3RhbnQgdGhpbmdzLuKAnSBTbyB3ZSB3b3VsZCBleHBlY3QgbW9zdCBnZW9ncmFwaGljIHBoZW5vbWVuYSB0byBleGVydCBhIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIG9mIHNvbWUga2luZC4gSW4gcG9wdWxhdGlvbiBkYXRhIHRoaXMgaXMgb2Z0ZW4gdGhlIGNhc2UgYXMgcGVyc29ucyB3aXRoIHNpbWlsYXIgY2hhcmFjdGVyaXN0aWNzIHRlbmQgdG8gcmVzaWRlIGluIHNpbWlsYXIgbmVpZ2hib3VyaG9vZHMgZHVlIHRvIGEgcmFuZ2Ugb2YgcmVhc29ucyBpbmNsdWRpbmcgaG91c2UgcHJpY2VzLCBwcm94aW1pdHkgdG8gd29ya3BsYWNlcyBhbmQgY3VsdHVyYWwgZmFjdG9ycy4KCgojIFR1dG9yaWFsIG9iamVjdGl2ZXMKCi0gUnVuIHZhcmlvdXMgbWVhc3VyZXMgb2Ygc3BhdGlhbCBjb3JyZWxhdGlvbgotIEV2YWx1YXRlIGJvdGggZ2xvYmFsIGFuZCBsb2NhbCBtZWFzdXJlcyBvZiBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbgotIElkZW50aWZ5IHNwYXRpYWwgY2x1c3RlcnMKCiMgUmVwbGljYXRpb24gZmlsZXMKCi0gQWxsIG5lY2Vzc2FyeSBmaWxlcyBjYW4gYmUgZG93bmxvYWRlZCBmcm9tICBbR3V5IExhbnNsZXkgJiBKYW1lcyBDaGVzaGlyZSAoMjAxNildKGh0dHBzOi8vZGF0YS5jZHJjLmFjLnVrL3R1dG9yaWFsL2FuLWludHJvZHVjdGlvbi10by1zcGF0aWFsLWRhdGEtYW5hbHlzaXMtYW5kLXZpc3VhbGlzYXRpb24taW4tcikuCgotIElmIHlvdSBhcmUgYSBtZW1iZXIgb2YgdGhlIFtRdWFSQ1MgbGFiXShodHRwczovL3F1YXJjcy1sYWIub3JnKSwgeW91IGNhbiBydW4gdGhpcyB0dXRvcmlhbCBpbiBbUiBTdHVkaW8gQ2xvdWRdKGh0dHBzOi8vcnN0dWRpby5jbG91ZC9wcm9qZWN0Lzk2MTUzMCkgYW5kIGFjY2VzcyB0aGUgZmlsZXMgaW4gdGhlIGZvbGxvd2luZyBbR2l0aHViIFJlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9xdWFyY3MtbGFiL3R1dG9yaWFsLXNwYXRpYWwtYXV0b2NvcnJlbGF0aW9uKQoKCiMgUHJlbGltaW5hcnkgbWF0ZXJpYWwKCi0gW01vcmFuJ3MgSV0oaHR0cHM6Ly95b3V0dS5iZS9xV1ExUGE1Y2J0dykKCi0gW0ludGVycHJldGF0aW9ucyBvZiBNb3JhbidzIEldKGh0dHBzOi8veW91dHUuYmUvX0pfYm1XbU9GM0kpCgotIFtNb3JhbiBTY2F0dGVyIFBsb3RdKGh0dHBzOi8veW91dHUuYmUvWmZPYzJIbi1HRmMpCgotIFtHZWFyeSdzIENdKGh0dHBzOi8veW91dHUuYmUvRUtJS0VlQXcwVzgpCgotIFtMb2NhbCBTcGF0aWFsIEF1dG9jb3JyZWxhdGlvbiBDbHVzdGVyc10oaHR0cHM6Ly95b3V0dS5iZS9nWG92dFNHTnoydykKCi0gW0xJU0EgUHJpbmNpcGxlXShodHRwczovL3lvdXR1LmJlL0d2a2dKV3JOTXlJKQoKLSBbTG9jYWwgTW9yYW5dKGh0dHBzOi8veW91dHUuYmUvX1NtTkh0NHI3OWspCgotIFtMb2NhbCBHIFN0YXRpc3RpY3NdKGh0dHBzOi8veW91dHUuYmUvdXJmc2pHby1YWGMpCgotIFtJc3N1ZXMgYW5kIEludGVycHJldGF0aW9uXShodHRwczovL3lvdXR1LmJlL3pqWUVJaE90RFRzKQoKCiMgSW1wb3J0IGRhdGFzZXRzCgoKIyMgTm9uLXNwYXRpYWwgZGF0YQoKYGBge3J9CkNlbnN1cy5EYXRhIDwtcmVhZC5jc3YoInByYWN0aWNhbGRhdGEuY3N2IikKYGBgCgpgYGB7cn0KZ2xpbXBzZShDZW5zdXMuRGF0YSkKYGBgCgoKIyMgU3BhdGlhbCBkYXRhCgpgYGB7cn0KT3V0cHV0LkFyZWFzIDwtIHJlYWRPR1IoIi4iLCAiQ2FtZGVuX29hMTEiKQpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9CiMgUnVuIGl0IGluIGNvbnNvbGUgKGxvbmcgb3V0cHV0KQpnbGltcHNlKE91dHB1dC5BcmVhcykKYGBgCgpVc2luZyB0aGUgYHNmYCBwYWNrYWdlCgpgYGB7cn0KT3V0cHV0LkFyZWFzMiA8LSByZWFkX3NmKCJDYW1kZW5fb2ExMS5zaHAiKQpgYGAKCmBgYHtyfQpnbGltcHNlKE91dHB1dC5BcmVhczIpCmBgYAoKIyBNZXJnZSBkYXRhc2V0cwoKYGBge3J9Ck9BLkNlbnN1cyA8LSBtZXJnZShPdXRwdXQuQXJlYXMsIENlbnN1cy5EYXRhLCBieS54PSJPQTExQ0QiLCBieS55PSJPQSIpCmBgYAoKVHJhbnNmb3JtIGl0IGludG8gYSBgc2ZgIG9iamVjdAoKYGBge3J9Ck9BLkNlbnN1c19zZiA8LSBzdF9hc19zZihPQS5DZW5zdXMpCmBgYAoKIyBTcGF0aWFsIGRpc3RyaWJ1dGlvbgoKYGBge3J9CnRtX3NoYXBlKE9BLkNlbnN1c19zZikgKyAKICB0bV9maWxsKCJRdWFsaWZpY2F0aW9uIiwKICAgICAgICAgIHBhbGV0dGUgPSAiUmVkcyIsIAogICAgICAgICAgc3R5bGUgPSAicXVhbnRpbGUiLCAKICAgICAgICAgIHRpdGxlID0gIiUgd2l0aCBhIFF1YWxpZmljYXRpb24iKSArCiAgdG1fYm9yZGVycyhhbHBoYT0uNCkgIApgYGAKCiMgTmVpZ2h0Ym91ciBzdHJ1Y3R1cmUKCiMjIEZpbmQgcXVlZW4gbmVpZ2hib3VycwoKCmBgYHtyfQpuZWlnaGJvdXJzIDwtIHBvbHkybmIoT0EuQ2Vuc3VzKQpuZWlnaGJvdXJzCmBgYAoKYGBge3J9Cm5laWdoYm91cnNfc2YgPC0gcG9seTJuYihPQS5DZW5zdXNfc2YpCm5laWdoYm91cnNfc2YKYGBgCgoKIyMgUGxvdCBxdWVlbiBuZWlnaGJvdXJzIGxpbmtzIAoKYGBge3J9CnBsb3QoT0EuQ2Vuc3VzLCBib3JkZXIgPSAnbGlnaHRncmV5JykKcGxvdChuZWlnaGJvdXJzLCBjb29yZGluYXRlcyhPQS5DZW5zdXMpLCBhZGQ9VFJVRSwgY29sPSdyZWQnKQpgYGAKCiMjIEZpbmQgcm9vayBuZWlnaGJvdXJzCgpgYGB7cn0KbmVpZ2hib3VyczIgPC0gcG9seTJuYihPQS5DZW5zdXMsIHF1ZWVuID0gRkFMU0UpCm5laWdoYm91cnMyCmBgYAoKIyMgUGxvdCByb29rIG5laWdoYm91cnMKCmBgYHtyfQpwbG90KE9BLkNlbnN1cywgYm9yZGVyID0gJ2xpZ2h0Z3JleScpCnBsb3QobmVpZ2hib3VyczIsIGNvb3JkaW5hdGVzKE9BLkNlbnN1cyksIGFkZD1UUlVFLCBjb2w9J2JsdWUnKQpgYGAKCgojIyBQbG90IHF1ZWVuIHZzIHJvb2sKCmBgYHtyfQpwbG90KE9BLkNlbnN1cywgYm9yZGVyID0gJ2xpZ2h0Z3JleScpCnBsb3QobmVpZ2hib3VycywgY29vcmRpbmF0ZXMoT0EuQ2Vuc3VzKSwgYWRkPVRSVUUsIGNvbD0ncmVkJykKcGxvdChuZWlnaGJvdXJzMiwgY29vcmRpbmF0ZXMoT0EuQ2Vuc3VzKSwgYWRkPVRSVUUsIGNvbD0nYmx1ZScpCmBgYAoKCiMgR2xvYmFsIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uCgojIyBEZWZpbmUgd2VpZ2h0cyBtYXRyaXgKCmBgYHtyfQpsaXN0dyA8LSBuYjJsaXN0dyhuZWlnaGJvdXJzMikKbGlzdHcKYGBgCgojIyBHbG9iYWwgTW9yYW4gdGVzdAoKVGhlIGNvcnJlbGF0aW9uIHNjb3JlIGlzIGJldHdlZW4gLTEgYW5kIDEuIE11Y2ggbGlrZSBhIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50LCAxIGRldGVybWluZXMgcGVyZmVjdCBwb3NpdGl2ZSBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiAoc28gb3VyIGRhdGEgaXMgY2x1c3RlcmVkKSwgMCBpZGVudGlmaWVzIHRoZSBkYXRhIGlzIHJhbmRvbWx5IGRpc3RyaWJ1dGVkIGFuZCAtMSByZXByZXNlbnRzIG5lZ2F0aXZlIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIChzbyBkaXNzaW1pbGFyIHZhbHVlcyBhcmUgbmV4dCB0byBlYWNoIG90aGVyKS4KCgpgYGB7cn0KZ2xvYmFsTW9yYW4gPC0gbW9yYW4udGVzdChPQS5DZW5zdXMkUXVhbGlmaWNhdGlvbiwgbGlzdHcpCmdsb2JhbE1vcmFuCmBgYAoKYGBge3J9Cmdsb2JhbE1vcmFuW1siZXN0aW1hdGUiXV1bWyJNb3JhbiBJIHN0YXRpc3RpYyJdXQpgYGAKCmBgYHtyfQpnbG9iYWxNb3JhbltbInAudmFsdWUiXV0KYGBgCgoKVGhlIE1vcmFuIEkgc3RhdGlzdGljIGlzIDAuNTQsIHdlIGNhbiwgdGhlcmVmb3JlLCBkZXRlcm1pbmUgdGhhdCB0aGVyZSBvdXIgcXVhbGlmaWNhdGlvbiB2YXJpYWJsZSBpcyBwb3NpdGl2ZWx5IGF1dG9jb3JyZWxhdGVkIGluIENhbWRlbi4gSW4gb3RoZXIgd29yZHMsIHRoZSBkYXRhIGRvZXMgc3BhdGlhbGx5IGNsdXN0ZXIuIFdlIGNhbiBhbHNvIGNvbnNpZGVyIHRoZSBwLXZhbHVlIGFzIGEgbWVhc3VyZSBvZiB0aGUgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIG9mIHRoZSBtb2RlbC4KCgojIExvY2FsIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uCgojIyBNb3JhbiBzY2F0dGVycGxvdCAKCmBgYHtyfQptb3JhbiA8LSBtb3Jhbi5wbG90KE9BLkNlbnN1cyRRdWFsaWZpY2F0aW9uLCBsaXN0dyA9IG5iMmxpc3R3KG5laWdoYm91cnMyLCBzdHlsZSA9ICJXIikpCmBgYAoKIyMgQ29tcHV0ZSBsb2NhbCBNb3JhbgoKYGBge3J9CmxvY2FsIDwtIGxvY2FsbW9yYW4oeCA9IE9BLkNlbnN1cyRRdWFsaWZpY2F0aW9uLCBsaXN0dyA9IG5iMmxpc3R3KG5laWdoYm91cnMyLCBzdHlsZSA9ICJXIikpCmBgYAoKQnkgY29uc2lkZXJpbmcgdGhlIGhlbHAgcGFnZSBmb3IgdGhlIGxvY2FsbW9yYW4gZnVuY3Rpb24gKHJ1biBgP2xvY2FsbW9yYW5gIGluIFIgY29uc29sZSkgd2UgY2FuIG9ic2VydmUgdGhlIGFyZ3VtZW50cyBhbmQgb3V0cHV0cy4gV2UgZ2V0IGEgbnVtYmVyIG9mIHVzZWZ1bCBzdGF0aXN0aWNzIGZyb20gdGhlIG1vZGVsIHdoaWNoIGFyZSBhcyBkZWZpbmVkOgoKLSBJaToJbG9jYWwgbW9yYW4gc3RhdGlzdGljCgotIEUuSWk6IGV4cGVjdGF0aW9uIG9mIGxvY2FsIG1vcmFuIHN0YXRpc3RpYwoKLSBWYXIuSWk6CXZhcmlhbmNlIG9mIGxvY2FsIG1vcmFuIHN0YXRpc3RpYwoKLSBaLklpOglzdGFuZGFyZCBkZXZpYXRlIG9mIGxvY2FsIG1vcmFuIHN0YXRpc3RpYwoKLSBQcigpOglwLXZhbHVlIG9mIGxvY2FsIG1vcmFuIHN0YXRpc3RpYwoKIyMgUGxvdCBsb2NhbCBNb3JhbgoKLSBBIG1hcCB0aGUgbG9jYWwgbW9yYW4gc3RhdGlzdGljIChJaSkuIAoKQSBwb3NpdGl2ZSB2YWx1ZSBmb3IgSWkgaW5kaWNhdGVzIHRoYXQgdGhlIHVuaXQgaXMgc3Vycm91bmRlZCBieSB1bml0cyB3aXRoIHNpbWlsYXIgdmFsdWVzLgoKYGBge3J9CiMgYmluZHMgcmVzdWx0cyB0byBvdXIgcG9seWdvbiBzaGFwZWZpbGUKbW9yYW4ubWFwIDwtIGNiaW5kKE9BLkNlbnN1cywgbG9jYWwpCmBgYAoKYGBge3J9CnRtX3NoYXBlKG1vcmFuLm1hcCkgKwogIHRtX2ZpbGwoY29sID0gIklpIiwKICAgICAgICAgIHN0eWxlID0gInF1YW50aWxlIiwKICAgICAgICAgIHRpdGxlID0gImxvY2FsIG1vcmFuIHN0YXRpc3RpYyIpIApgYGAKCiMjIFBsb3QgTElTQSBjbHVzdGVycwoKYGBge3J9CnF1YWRyYW50IDwtIHZlY3Rvcihtb2RlPSJudW1lcmljIixsZW5ndGg9bnJvdyhsb2NhbCkpCgojIGNlbnRlcnMgdGhlIHZhcmlhYmxlIG9mIGludGVyZXN0IGFyb3VuZCBpdHMgbWVhbgptLnF1YWxpZmljYXRpb24gPC0gT0EuQ2Vuc3VzJFF1YWxpZmljYXRpb24gLSBtZWFuKE9BLkNlbnN1cyRRdWFsaWZpY2F0aW9uKSAgICAgCgojIGNlbnRlcnMgdGhlIGxvY2FsIE1vcmFuJ3MgYXJvdW5kIHRoZSBtZWFuCm0ubG9jYWwgPC0gbG9jYWxbLDFdIC0gbWVhbihsb2NhbFssMV0pICAgIAoKIyBzaWduaWZpY2FuY2UgdGhyZXNob2xkCnNpZ25pZiA8LSAwLjEgCgojIGJ1aWxkcyBhIGRhdGEgcXVhZHJhbnQKcXVhZHJhbnRbbS5xdWFsaWZpY2F0aW9uID4wICYgbS5sb2NhbD4wXSA8LSA0ICAKcXVhZHJhbnRbbS5xdWFsaWZpY2F0aW9uIDwwICYgbS5sb2NhbDwwXSA8LSAxICAgICAgCnF1YWRyYW50W20ucXVhbGlmaWNhdGlvbiA8MCAmIG0ubG9jYWw+MF0gPC0gMgpxdWFkcmFudFttLnF1YWxpZmljYXRpb24gPjAgJiBtLmxvY2FsPDBdIDwtIDMKcXVhZHJhbnRbbG9jYWxbLDVdPnNpZ25pZl0gPC0gMCAgIAoKIyBwbG90IGluIHIKYnJrcyA8LSBjKDAsMSwyLDMsNCkKY29sb3JzIDwtIGMoIndoaXRlIiwiYmx1ZSIscmdiKDAsMCwxLGFscGhhPTAuNCkscmdiKDEsMCwwLGFscGhhPTAuNCksInJlZCIpCnBsb3QoT0EuQ2Vuc3VzLGJvcmRlcj0ibGlnaHRncmF5Iixjb2w9Y29sb3JzW2ZpbmRJbnRlcnZhbChxdWFkcmFudCxicmtzLGFsbC5pbnNpZGU9RkFMU0UpXSkKYm94KCkKbGVnZW5kKCJib3R0b21sZWZ0IiwgbGVnZW5kID0gYygiaW5zaWduaWZpY2FudCIsImxvdy1sb3ciLCJsb3ctaGlnaCIsImhpZ2gtbG93IiwiaGlnaC1oaWdoIiksCiAgICAgICBmaWxsPWNvbG9ycyxidHk9Im4iKQpgYGAKCiMgR2V0aXMtT3JkIGFwcHJvYWNoCgpUaGUgR2V0aXMtT3JkIEdpIFN0YXRpc3RpYyBsb29rcyBhdCBuZWlnaGJvdXJzIHdpdGhpbiBhIGRlZmluZWQgcHJveGltaXR5IHRvIGlkZW50aWZ5IHdoZXJlIGVpdGhlciBoaWdoIG9yIGxvdyB2YWx1ZXMgY2x1c3RlciBzcGF0aWFsbHkuIEhlcmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBob3Qtc3BvdHMgYXJlIHJlY29nbmlzZWQgYXMgYXJlYXMgb2YgaGlnaCB2YWx1ZXMgd2hlcmUgb3RoZXIgYXJlYXMgd2l0aGluIGEgbmVpZ2hib3VyaG9vZCByYW5nZSBhbHNvIHNoYXJlIGhpZ2ggdmFsdWVzIHRvby4KCkZpcnN0LCB3ZSBuZWVkIHRvIGRlZmluZSBhIG5ldyBzZXQgb2YgbmVpZ2hib3Vycy4gV2hpbHN0IHRoZSBzcGF0aWFsIGF1dG9jb3JyZWN0aW9uIGNvbnNpZGVyZWQgdW5pdHMgd2hpY2ggc2hhcmVkIGJvcmRlcnMsIGZvciBHZXRpcy1PcmQgd2UgYXJlIGRlZmluaW5nIG5laWdoYm91cnMgYmFzZWQgb24gcHJveGltaXR5LiBUaGUgZXhhbXBsZSBiZWxvdyBzaG93cyB0aGUgcmVzdWx0cyB3aGVyZSB3ZSBoYXZlIGEgc2VhcmNoIHJhZGl1cyBvZiAyNTAgbWV0cmVzLgoKSG93ZXZlciwgaGVyZSBhIHNlYXJjaCByYWRpdXMgb2YganVzdCAyNTAgbWV0cmVzIGZhaWxzIHRvIGRlZmluZSBuZWFyZXN0IG5laWdoYm91cnMgZm9yIHNvbWUgYXJlYXMgc28gd2Ugd2lsbCBuZWVkIHRvIHNldCB0aGUgcmFkaXVzIGFzIDgwMCBtZXRyZXMgb3IgbW9yZSBmb3Igb3VyIG1vZGVsIGluIENhbWRlbi4KCi0gY3JlYXRlcyBjZW50cm9pZCBhbmQgam9pbnMgbmVpZ2hib3VycyB3aXRoaW4gMCBhbmQgODAwIHVuaXRzCgpgYGB7cn0KbmIgPC0gZG5lYXJuZWlnaChjb29yZGluYXRlcyhPQS5DZW5zdXMpLCAwLCA4MDApCmBgYAoKCi0gY3JlYXRlcyBsaXN0dwoKYGBge3J9Cm5iX2x3IDwtIG5iMmxpc3R3KG5iLCBzdHlsZSA9ICdCJykKYGBgCgojIyBQbG90IGRhdGEgYW5kIG5laWdoYm91cnMKCmBgYHtyfQpwbG90KE9BLkNlbnN1cywgYm9yZGVyID0gJ2xpZ2h0Z3JleScpCnBsb3QobmIsIGNvb3JkaW5hdGVzKE9BLkNlbnN1cyksIGFkZD1UUlVFLCBjb2wgPSAncmVkJykKYGBgCgojIyBHZXRpcy1PcmQgR2kgc3RhdGlzdGljCgpOT1RFOiBPbiBzb21lIG1hY2hpbmVzIHRoZSBgY2JpbmRgIG1heSBub3Qgd29yayB3aXRoIGEgc3BhdGlhbCBkYXRhIGZpbGUsIGluIHRoaXMgY2FzZSwgeW91IHdpbGwgbmVlZCB0byBjaGFuZ2UgT0EuQ2Vuc3VzIHRvIE9BLkNlbnN1c0BkYXRhIHNvIHRoYXQgUiBrbm93cyB3aGljaCBwYXJ0IG9mIHRoZSBzcGF0aWFsIGRhdGEgZmlsZSB0byBqb2luLiBJZiB5b3UgdGFrZSB0aGlzIGFwcHJvYWNoIHRoZSBzdWJzZXF1ZW50IGNvbHVtbiBvcmRlcmluZyBtYXkgYmUgZGlmZmVyZW50IHRvIHdoYXQgaXMgc2hvd24gaW4gdGhlIGV4YW1wbGUgYmVsb3cuCgoKYGBge3J9CmxvY2FsX2cgPC0gbG9jYWxHKE9BLkNlbnN1cyRRdWFsaWZpY2F0aW9uLCBuYl9sdykKbG9jYWxfZyA8LSBjYmluZChPQS5DZW5zdXMsIGFzLm1hdHJpeChsb2NhbF9nKSkKbmFtZXMobG9jYWxfZylbNl0gPC0gImdzdGF0IgpgYGAKCiMjIENsdXN0ZXIgbWFwCgpgYGB7cn0KdG1fc2hhcGUobG9jYWxfZykgKyAKICB0bV9maWxsKCJnc3RhdCIsIAogICAgICAgICAgcGFsZXR0ZSA9ICJSZEJ1IiwKICAgICAgICAgIHN0eWxlID0gInByZXR0eSIpICsKICB0bV9ib3JkZXJzKGFscGhhPS40KQpgYGAKClRoZSBHaSBTdGF0aXN0aWMgaXMgcmVwcmVzZW50ZWQgYXMgYSBaLXNjb3JlLiBHcmVhdGVyIHZhbHVlcyByZXByZXNlbnQgYSBncmVhdGVyIGludGVuc2l0eSBvZiBjbHVzdGVyaW5nIGFuZCB0aGUgZGlyZWN0aW9uIChwb3NpdGl2ZSBvciBuZWdhdGl2ZSkgaW5kaWNhdGVzIGhpZ2ggb3IgbG93IGNsdXN0ZXJzLiBUaGUgZmluYWwgbWFwIHNob3VsZCBpbmRpY2F0ZSB0aGUgbG9jYXRpb24gb2YgaG90LXNwb3RzIGFjcm9zcyBDYW1kZW4uIFJlcGVhdCB0aGlzIGZvciBhbm90aGVyIHZhcmlhYmxlLgoKCiMgUmVmZXJlbmNlcwoKLSBbQml2YW5kLCBSLiBTLiwgJiBXb25nLCBELiBXLiAoMjAxOCkuIENvbXBhcmluZyBpbXBsZW1lbnRhdGlvbnMgb2YgZ2xvYmFsIGFuZCBsb2NhbCBpbmRpY2F0b3JzIG9mIHNwYXRpYWwgYXNzb2NpYXRpb24uIFRlc3QsIDI3KDMpLCA3MTYtNzQ4Ll0oaHR0cHM6Ly9saW5rLnNwcmluZ2VyLmNvbS9hcnRpY2xlLzEwLjEwMDcvczExNzQ5LTAxOC0wNTk5LXgpCgoKCiMgT3RoZXIgdHV0b3JpYWxzCgotIFtHZW9EYSBXb3JrYm9va10oaHR0cHM6Ly9nZW9kYWNlbnRlci5naXRodWIuaW8vZG9jdW1lbnRhdGlvbi5odG1sKQoKLSBbRUNTNTMwOiAoVkkpIFNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uXShodHRwczovL3JzYml2YW5kLmdpdGh1Yi5pby9FQ1M1MzBfaDE5L0VDUzUzMF9WSS5odG1sKQogICAgLSBbQ29kZSBhbmQgZGF0YV0oaHR0cHM6Ly9naXRodWIuY29tL3JzYml2YW5kL0VDUzUzMF9oMTkvYmxvYi9tYXN0ZXIvRUNTNTMwX1ZJLlJtZCkKICAgIC0gW1ZpZGVvXShodHRwczovL3lvdXR1LmJlL3Z5U1ZjdERfX0xjKQoKRU5ECg==