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).

1 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)

2 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.

3 Tutorial objectives

  • Run various measures of spatial correlation
  • Evaluate both global and local measures of spatial autocorrelation
  • Identify spatial clusters

4 Replication files

6 Import datasets

6.1 Non-spatial data

Census.Data <-read.csv("practicaldata.csv")
glimpse(Census.Data)
## 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, …

6.2 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")
glimpse(Output.Areas2)
## Rows: 749
## Columns: 2
## $ OA11CD   <chr> "E00004527", "E00004525", "E00004522", "E00004287", "E0000420…
## $ geometry <POLYGON [m]> POLYGON ((530648 181230, 53..., POLYGON ((530511 1815…

7 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)

8 Spatial distribution

tm_shape(OA.Census_sf) + 
  tm_fill("Qualification",
          palette = "Reds", 
          style = "quantile", 
          title = "% with a Qualification") +
  tm_borders(alpha=.4)  

9 Neightbour structure

9.1 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

9.3 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

9.4 Plot rook neighbours

plot(OA.Census, border = 'lightgrey')
plot(neighbours2, coordinates(OA.Census), add=TRUE, col='blue')

9.5 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')

10 Global spatial autocorrelation

10.1 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

10.2 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
globalMoran[["p.value"]]
## [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.

11 Local spatial autocorrelation

11.1 Moran scatterplot

moran <- moran.plot(OA.Census$Qualification, listw = nb2listw(neighbours2, style = "W"))

11.2 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

11.3 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.

11.4 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")

12 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)
  • creates listw
nb_lw <- nb2listw(nb, style = 'B')

12.1 Plot data and neighbours

plot(OA.Census, border = 'lightgrey')
plot(nb, coordinates(OA.Census), add=TRUE, col = 'red')

12.2 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 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"

12.3 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==