Suggested citation:

Mendez C. (2020). Geographically weighted regression models: A tutorial using the spgwr package in R. R Studio/RPubs. Available at https://rpubs.com/quarcs-lab/tutorial-gwr1

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

2 Motivation

  • GWR is a nonparametric multivariate model which can indicate where non-stationarity may take place across space (the study area).

  • It can be used to identify how (locally weighted) regression coefficients may vary across space (the study area).

3 Tutorial objectives

  • Explore the residuals of a linear model to understand its limitations
  • Run a GWR and observe its parameters across space
  • Print multiple maps in one graphic using gridExtra() with tmap

5 Import datasets

All used datasets can be obtained from Guy Lansley & James Cheshire (2016).

5.1 Non-spatial data

## Observations: 749
## Variables: 5
## $ OA            <fct> E00004120, E00004121, E00004122, E00004123, E00004124, …
## $ 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,…

5.2 Spatial data

## OGR data source with driver: ESRI Shapefile 
## Source: "/Users/Carlos/GitHub/00-QuaRCS/tutorial-gwr1", layer: "Camden_oa11"
## with 749 features
## It has 1 fields

Using the sf package

## Observations: 749
## Variables: 2
## $ OA11CD   <chr> "E00004527", "E00004525", "E00004522", "E00004287", "E000042…
## $ geometry <POLYGON [m]> POLYGON ((530648 181230, 53..., POLYGON ((530511 181…

7 Run linear model

Let us run a linear model to understand the global relationship between our variables in our study area.

  • Dependent variable: The percentage of people with qualifications
  • Independent (predictor) variables: the percentages of unemployed economically active adults and White British ethnicity
## 
## Call:
## lm(formula = Qualification ~ Unemployed + White_British, data = Census.Data)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -50.31  -8.01   1.01   8.96  38.05 
## 
## Coefficients:
##               Estimate Std. Error t value            Pr(>|t|)    
## (Intercept)    47.8670     2.3357    20.5 <0.0000000000000002 ***
## Unemployed     -3.2946     0.1903   -17.3 <0.0000000000000002 ***
## White_British   0.4109     0.0403    10.2 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 12.7 on 746 degrees of freedom
## Multiple R-squared:  0.464,  Adjusted R-squared:  0.463 
## F-statistic:  323 on 2 and 746 DF,  p-value: <0.0000000000000002

7.1 Model diagnostics

This model has an adjusted R-squared value of 0.463. So we can assume 46% of the variance can be explained by the model. We can also observe the influences of each of the variables. However, the overall fit of the model and each of the coefficients may vary across space if we consider different parts of our study area. It is, therefore, worth considering the standardized residuals from the model to help us understand and improve our future models.

A residual is the difference between the predicted and observed values for an observation in the model. Models with lower r-squared values would have greater residuals on average as the data would not fit the modeled regression line as well. Standardized residuals are represented as Z-scores where 0 represent the predicted values.

If you plot a linear model (i.e. our model object), R will print out four different plots of which are useful for evaluating the model fit. These are very briefly summarized as:

  • Residuals vs Fitted: considers the relationship between the actual and the predicted data. The more dispersed the residuals are, the weaker the R2 should be. This can be useful to identify outlines too.The fit also tells us if the residuals are non-linearly distributed.

  • Normal Q-Q: Demonstrates the extent to which the residuals are normally distributed. Normal residuals should fit the straight line well.

  • Scale-Location: Shows if the residuals are spread equally across the full range of the predictors. If the values in this chart display a linear positive relationship, it suggests that the residuals spread wider and wider for greater values (this is known as heteroscedasticity).

  • Residuals vs Leverage: Identifies outliers, high-leverage points and influential observations. This plot is pretty difficult to interpret and there are other means of identifying these values.

More information on these plots is here

If you want to print just one of the plots you can enter which = n within the plot() function. i.e. plot(model, which = 3)

8 Map residuals

Map the spatial distribution of the residuals

  • Create a column vector of the residuals
  • Add the residuals column to the extended-spatial dataset
## class       : SpatialPolygonsDataFrame 
## features    : 6 
## extent      : 524326, 530660, 181181, 185111  (xmin, xmax, ymin, ymax)
## crs         : +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs 
## variables   : 6
## names       :    OA11CD,    White_British,    Low_Occupancy,       Unemployed,    Qualification, c..1....14.5939740246415...2....11.496973462005...3....6.9935255223138.. 
## min values  : E00004200, 31.8681318681319, 8.54700854700855, 2.11640211640212, 31.7460317460317,                                                         1.73891618985042 
## max values  : E00004527, 56.4516129032258, 19.6850393700787, 7.98319327731092, 67.8571428571429,                                                         21.9914592049372
  • we need to rename the column header from the resids file - in this case its the 6th column of map.resids
## class       : SpatialPolygonsDataFrame 
## features    : 6 
## extent      : 524326, 530660, 181181, 185111  (xmin, xmax, ymin, ymax)
## crs         : +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs 
## variables   : 6
## names       :    OA11CD,    White_British,    Low_Occupancy,       Unemployed,    Qualification,           resids 
## min values  : E00004200, 31.8681318681319, 8.54700854700855, 2.11640211640212, 31.7460317460317, 1.73891618985042 
## max values  : E00004527, 56.4516129032258, 19.6850393700787, 7.98319327731092, 67.8571428571429, 21.9914592049372
## Observations: 749
## Variables: 7
## $ OA11CD        <fct> E00004527, E00004525, E00004522, E00004287, E00004206, …
## $ White_British <dbl> 48.29, 40.94, 44.16, 31.87, 56.45, 39.92, 61.75, 56.36,…
## $ Low_Occupancy <dbl> 12.745, 16.807, 8.547, 12.613, 19.685, 11.765, 7.826, 8…
## $ Unemployed    <dbl> 7.5117, 5.9908, 2.1164, 3.2864, 7.9832, 3.5242, 4.7619,…
## $ Qualification <dbl> 35.81, 42.41, 56.48, 67.86, 31.75, 57.20, 64.55, 16.36,…
## $ resids        <dbl> 14.594, 11.497, 6.994, 1.739, 9.681, 21.991, 9.610, -4.…
## $ geometry      <POLYGON [m]> POLYGON ((530648 181230, 53..., POLYGON ((53051…

No renaming is needed if we use map.resids2 as it is a sf object

  • map the residuals using the quickmap function from tmap
## Variable "resids" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full spectrum of the color palette.

## Variable "resids" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full spectrum of the color palette.

If there is a geographic pattern in the residuals, it is possible that an unobserved variable may be influencing the dependent variable

9 Geographically weighted regression

GWR is the term introduced by Fotheringham, Charlton and Brunsdon (1997, 2002) to describe a family of regression models in which the coefficients are allowed to vary spatially. GWR uses the coordinates of each sample point or zone centroid, ti, as a target point for a form of spatially weighted least squares regression (for some models the target points can be separately defined, e.g. as grid intersection points, rather than observed data points). (de Smith et al, 2015)

9.1 Calculate kernel bandwidth

  • Use an adaptive kernel
## Adaptive q: 0.382 CV score: 101421 
## Adaptive q: 0.618 CV score: 109723 
## Adaptive q: 0.2361 CV score: 96876 
## Adaptive q: 0.1459 CV score: 94192 
## Adaptive q: 0.09017 CV score: 91100 
## Adaptive q: 0.05573 CV score: 88243 
## Adaptive q: 0.03444 CV score: 85633 
## Adaptive q: 0.02129 CV score: 83790 
## Adaptive q: 0.01316 CV score: 83096 
## Adaptive q: 0.008131 CV score: 84177 
## Adaptive q: 0.01535 CV score: 83014 
## Adaptive q: 0.01515 CV score: 82957 
## Adaptive q: 0.01437 CV score: 82858 
## Adaptive q: 0.01441 CV score: 82852 
## Adaptive q: 0.01458 CV score: 82833 
## Adaptive q: 0.0148 CV score: 82855 
## Adaptive q: 0.01462 CV score: 82829 
## Adaptive q: 0.01469 CV score: 82824 
## Adaptive q: 0.01473 CV score: 82836 
## Adaptive q: 0.01469 CV score: 82824

Just to check, let use the data OA.Census2, which is an sp object.

When using and sf object, observation coordinates have to be given

9.2 Run the GWR model

Print results

## Call:
## gwr(formula = Qualification ~ Unemployed + White_British, data = OA.Census, 
##     adapt = GWRbandwidth, hatmatrix = TRUE, se.fit = TRUE)
## Kernel function: gwr.Gauss 
## Adaptive quantile: 0.01469 (about 11 of 749 data points)
## Summary of GWR coefficient estimates at data points:
##                 Min. 1st Qu. Median 3rd Qu.   Max. Global
## X.Intercept.  11.082  34.434 45.769  59.754 85.019  47.87
## Unemployed    -5.453  -3.283 -2.554  -1.794  0.770  -3.29
## White_British -0.280   0.200  0.378   0.532  0.947   0.41
## Number of data points: 749 
## Effective number of parameters (residual: 2traceS - traceS'S): 132.6 
## Effective degrees of freedom (residual: 2traceS - traceS'S): 616.4 
## Sigma (residual: 2traceS - traceS'S): 9.904 
## Effective number of parameters (model: traceS): 94.45 
## Effective degrees of freedom (model: traceS): 654.6 
## Sigma (model: traceS): 9.61 
## Sigma (ML): 8.984 
## AICc (GWR p. 61, eq 2.33; p. 96, eq. 4.21): 5633 
## AIC (GWR p. 96, eq. 4.22): 5509 
## Residual sum of squares: 60452 
## Quasi-global R2: 0.7303

9.3 Create results dataframe

##  [1] "sum.w"                "X.Intercept."         "Unemployed"          
##  [4] "White_British"        "X.Intercept._se"      "Unemployed_se"       
##  [7] "White_British_se"     "gwr.e"                "pred"                
## [10] "pred.se"              "localR2"              "X.Intercept._se_EDF" 
## [13] "Unemployed_se_EDF"    "White_British_se_EDF" "pred.se.1"

9.4 Map results

  • Bind the results to OA.Census polygon.

The variable names followed by the name of our original data frame (i.e. OA.Census.Unemployed) are the coefficients of the model.

  • Spatial distribution of White_British

  • Coefficients of White_British
## Variable "White_British.1" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full spectrum of the color palette.

  • Spatial distribution of Unemployed

  • Coefficients of Unemployed
## Variable "Unemployed.1" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full spectrum of the color palette.

10 Use Grid Extra

## Variable "White_British.1" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full spectrum of the color palette.
## Variable "Unemployed.1" contains positive and negative values, so midpoint is set to 0. Set midpoint = NA to show the full spectrum of the color palette.

11 Run it online

If you are a member of the QuaRCS lab, you can run this tutorial in R Studio Cloud

LS0tCnRpdGxlOiAiR2VvZ3JhcGhpY2FsbHkgd2VpZ2h0ZWQgcmVncmVzc2lvbiBtb2RlbHM6IgpzdWJ0aXRsZTogIkEgdHV0b3JpYWwgdXNpbmcgdGhlIHNwZ3dyIHBhY2thZ2UgaW4gUiIKYXV0aG9yOiAiQ2FybG9zIE1lbmRleiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlCiAgICB0b2NfZGVwdGg6IDQKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiAic2hvdyIKICAgIHRoZW1lOiAiY29zbW8iCiAgICBoaWdobGlnaHQ6ICJtb25vY2hyb21lIgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGhpZ2hsaWdodDogbW9ub2Nocm9tZQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBjb3NtbwogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IG5vCiAgICAgIHNtb290aF9zY3JvbGw6IG5vCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgd29yZF9kb2N1bWVudDogZGVmYXVsdApiaWJsaW9ncmFwaHk6IGJpYmxpby5iaWIKLS0tCgoKPHN0eWxlPgpoMS50aXRsZSB7Zm9udC1zaXplOiAxOHB0OyBjb2xvcjogRGFya0JsdWU7fSAKYm9keSwgaDEsIGgyLCBoMywgaDQge2ZvbnQtZmFtaWx5OiAiUGFsYXRpbm8iLCBzZXJpZjt9CmJvZHkge2ZvbnQtc2l6ZTogMTJwdDt9Ci8qIEhlYWRlcnMgKi8KaDEsaDIsaDMsaDQsaDUsaDZ7Zm9udC1zaXplOiAxNHB0OyBjb2xvcjogIzAwMDA4Qjt9CmJvZHkge2NvbG9yOiAjMzMzMzMzO30KYSwgYTpob3ZlciB7Y29sb3I6ICM4QjNBNjI7fQpwcmUge2ZvbnQtc2l6ZTogMTJweDt9Cjwvc3R5bGU+CgpTdWdnZXN0ZWQgY2l0YXRpb246IAoKPiBNZW5kZXogQy4gKDIwMjApLiAgR2VvZ3JhcGhpY2FsbHkgd2VpZ2h0ZWQgcmVncmVzc2lvbiBtb2RlbHM6IEEgdHV0b3JpYWwgdXNpbmcgdGhlIHNwZ3dyIHBhY2thZ2UgaW4gUi4gUiBTdHVkaW8vUlB1YnMuIEF2YWlsYWJsZSBhdCA8aHR0cHM6Ly9ycHVicy5jb20vcXVhcmNzLWxhYi90dXRvcmlhbC1nd3IxPgoKVGhpcyB3b3JrIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBDcmVhdGl2ZSBDb21tb25zIEF0dHJpYnV0aW9uLVNoYXJlIEFsaWtlIDQuMCBJbnRlcm5hdGlvbmFsIExpY2Vuc2UuIAohW10oTGljZW5zZS5wbmcpCgpBY2tub3dsZWRnbWVudDoKCk1hdGVyaWFsIGFkYXB0ZWQgZnJvbSBtdWx0aXBsZSBzb3VyY2VzLCBpbiBwYXJ0aWN1bGFyIHRoZSBjb3Vyc2UgbWF0ZXJpYWxzIG9mIFtHdXkgTGFuc2xleSAmIEphbWVzIENoZXNoaXJlICgyMDE2KV0oaHR0cHM6Ly9kYXRhLmNkcmMuYWMudWsvdHV0b3JpYWwvYW4taW50cm9kdWN0aW9uLXRvLXNwYXRpYWwtZGF0YS1hbmFseXNpcy1hbmQtdmlzdWFsaXNhdGlvbi1pbi1yKS4KCiMgTGlicmFyaWVzCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCgpsaWJyYXJ5KHRpZHl2ZXJzZSkgICMgTW9kZXJuIGRhdGEgc2NpZW5jZSB3b3JrZmxvdwpsaWJyYXJ5KHNmKQpsaWJyYXJ5KHNwKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KHJnZW9zKQpsaWJyYXJ5KHRtYXApCmxpYnJhcnkodG1hcHRvb2xzKQpsaWJyYXJ5KHNwZ3dyKQpsaWJyYXJ5KGdyaWQpCmxpYnJhcnkoZ3JpZEV4dHJhKQoKCiMgQ2hhbmdlIHRoZSBwcmVzZW50YXRpb24gb2YgZGVjaW1hbCBudW1iZXJzIHRvIDQgYW5kIGF2b2lkIHNjaWVudGlmaWMgbm90YXRpb24Kb3B0aW9ucyhwcm9tcHQ9IlI+ICIsIGRpZ2l0cz00LCBzY2lwZW49OTk5KQpgYGAKCiMgTW90aXZhdGlvbgoKLSBHV1IgaXMgYSBub25wYXJhbWV0cmljIG11bHRpdmFyaWF0ZSBtb2RlbCB3aGljaCBjYW4gaW5kaWNhdGUgKip3aGVyZSBub24tc3RhdGlvbmFyaXR5IG1heSB0YWtlIHBsYWNlKiogYWNyb3NzIHNwYWNlICh0aGUgc3R1ZHkgYXJlYSkuCgotIEl0IGNhbiBiZSB1c2VkIHRvIGlkZW50aWZ5ICoqaG93IChsb2NhbGx5IHdlaWdodGVkKSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyBtYXkgdmFyeSoqIGFjcm9zcyBzcGFjZSAodGhlIHN0dWR5IGFyZWEpLgoKIyBUdXRvcmlhbCBvYmplY3RpdmVzCgotIEV4cGxvcmUgdGhlIHJlc2lkdWFscyBvZiBhIGxpbmVhciBtb2RlbCB0byB1bmRlcnN0YW5kIGl0cyBsaW1pdGF0aW9ucwotIFJ1biBhIEdXUiBhbmQgb2JzZXJ2ZSBpdHMgcGFyYW1ldGVycyBhY3Jvc3Mgc3BhY2UKLSBQcmludCBtdWx0aXBsZSBtYXBzIGluIG9uZSBncmFwaGljIHVzaW5nIGdyaWRFeHRyYSgpIHdpdGggdG1hcAoKCiMgUHJlbGltaW5hcnkgbWF0ZXJpYWxzCgotIFtJbnRyb2R1Y3Rpb24gdG8gbG9jYWxseSB3ZWlnaHRlZCByZWdyZXNzaW9uc10oaHR0cHM6Ly95b3V0dS5iZS9WZjdvSjZ6MkxDYykKLSBbSW50cm9kdWN0aW9uIHRvIEdlb2dyYXBoaWNhbGx5IFdlaWdodGVkIFJlZ3Jlc3Npb25zXShodHRwczovL3lvdXR1LmJlL0NwSDhCMlNpcWRNKSAKCiMgSW1wb3J0IGRhdGFzZXRzCgogQWxsIHVzZWQgZGF0YXNldHMgY2FuIGJlIG9idGFpbmVkIGZyb20gW0d1eSBMYW5zbGV5ICYgSmFtZXMgQ2hlc2hpcmUgKDIwMTYpXShodHRwczovL2RhdGEuY2RyYy5hYy51ay90dXRvcmlhbC9hbi1pbnRyb2R1Y3Rpb24tdG8tc3BhdGlhbC1kYXRhLWFuYWx5c2lzLWFuZC12aXN1YWxpc2F0aW9uLWluLXIpLgoKIyMgTm9uLXNwYXRpYWwgZGF0YQoKYGBge3J9CkNlbnN1cy5EYXRhIDwtcmVhZC5jc3YoInByYWN0aWNhbGRhdGEuY3N2IikKYGBgCgpgYGB7cn0KZ2xpbXBzZShDZW5zdXMuRGF0YSkKYGBgCgoKIyMgU3BhdGlhbCBkYXRhCgpgYGB7cn0KT3V0cHV0LkFyZWFzIDwtIHJlYWRPR1IoIi4iLCAiQ2FtZGVuX29hMTEiKQpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9CiMgUnVuIGl0IGluIGNvbnNvbGUgKGxvbmcgb3V0cHV0KQpnbGltcHNlKE91dHB1dC5BcmVhcykKYGBgCgpVc2luZyB0aGUgYHNmYCBwYWNrYWdlCgpgYGB7cn0KT3V0cHV0LkFyZWFzMiA8LSByZWFkX3NmKCJDYW1kZW5fb2ExMS5zaHAiKQpgYGAKCmBgYHtyfQpnbGltcHNlKE91dHB1dC5BcmVhczIpCmBgYAoKIyBNZXJnZSBkYXRhc2V0cwoKYGBge3J9Ck9BLkNlbnN1cyA8LSBtZXJnZShPdXRwdXQuQXJlYXMsIENlbnN1cy5EYXRhLCBieS54PSJPQTExQ0QiLCBieS55PSJPQSIpCmBgYAoKVHJhbnNmb3JtIGl0IGludG8gYSBgc2ZgIG9iamVjdAoKYGBge3J9Ck9BLkNlbnN1czIgPC0gc3RfYXNfc2YoT0EuQ2Vuc3VzKQpgYGAKCgoKIyBSdW4gbGluZWFyIG1vZGVsCkxldCB1cyAgcnVuIGEgbGluZWFyIG1vZGVsIHRvIHVuZGVyc3RhbmQgdGhlIGdsb2JhbCByZWxhdGlvbnNoaXAgYmV0d2VlbiBvdXIgdmFyaWFibGVzIGluIG91ciBzdHVkeSBhcmVhLgoKLSBEZXBlbmRlbnQgdmFyaWFibGU6ICBUaGUgcGVyY2VudGFnZSBvZiBwZW9wbGUgd2l0aCBxdWFsaWZpY2F0aW9ucwotIEluZGVwZW5kZW50IChwcmVkaWN0b3IpIHZhcmlhYmxlczogdGhlIHBlcmNlbnRhZ2VzIG9mIHVuZW1wbG95ZWQgZWNvbm9taWNhbGx5IGFjdGl2ZSBhZHVsdHMgYW5kIFdoaXRlIEJyaXRpc2ggZXRobmljaXR5IAoKYGBge3J9Cm1vZGVsIDwtIGxtKFF1YWxpZmljYXRpb24gfiBVbmVtcGxveWVkICsgV2hpdGVfQnJpdGlzaCwgZGF0YSA9IENlbnN1cy5EYXRhKQpzdW1tYXJ5KG1vZGVsKQoKYGBgCgojIyBNb2RlbCBkaWFnbm9zdGljcwoKVGhpcyBtb2RlbCBoYXMgYW4gYWRqdXN0ZWQgUi1zcXVhcmVkIHZhbHVlIG9mIDAuNDYzLiBTbyB3ZSBjYW4gYXNzdW1lIDQ2JSBvZiB0aGUgdmFyaWFuY2UgY2FuIGJlIGV4cGxhaW5lZCBieSB0aGUgbW9kZWwuIFdlIGNhbiBhbHNvIG9ic2VydmUgdGhlIGluZmx1ZW5jZXMgb2YgZWFjaCBvZiB0aGUgdmFyaWFibGVzLiBIb3dldmVyLCAqKnRoZSBvdmVyYWxsIGZpdCBvZiB0aGUgbW9kZWwgYW5kIGVhY2ggb2YgdGhlIGNvZWZmaWNpZW50cyBtYXkgdmFyeSBhY3Jvc3Mgc3BhY2UqKiBpZiB3ZSBjb25zaWRlciBkaWZmZXJlbnQgcGFydHMgb2Ygb3VyIHN0dWR5IGFyZWEuIEl0IGlzLCB0aGVyZWZvcmUsIHdvcnRoIGNvbnNpZGVyaW5nIHRoZSBzdGFuZGFyZGl6ZWQgcmVzaWR1YWxzIGZyb20gdGhlIG1vZGVsIHRvIGhlbHAgdXMgdW5kZXJzdGFuZCBhbmQgaW1wcm92ZSBvdXIgZnV0dXJlIG1vZGVscy4KCkEgcmVzaWR1YWwgaXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgcHJlZGljdGVkIGFuZCBvYnNlcnZlZCB2YWx1ZXMgZm9yIGFuIG9ic2VydmF0aW9uIGluIHRoZSBtb2RlbC4gKipNb2RlbHMgd2l0aCBsb3dlciByLXNxdWFyZWQgdmFsdWVzIHdvdWxkIGhhdmUgZ3JlYXRlciByZXNpZHVhbHMgb24gYXZlcmFnZSoqIGFzIHRoZSBkYXRhIHdvdWxkIG5vdCBmaXQgdGhlIG1vZGVsZWQgcmVncmVzc2lvbiBsaW5lIGFzIHdlbGwuIFN0YW5kYXJkaXplZCByZXNpZHVhbHMgYXJlIHJlcHJlc2VudGVkIGFzIFotc2NvcmVzIHdoZXJlIDAgcmVwcmVzZW50IHRoZSBwcmVkaWN0ZWQgdmFsdWVzLgoKSWYgeW91IHBsb3QgYSBsaW5lYXIgbW9kZWwgKGkuZS4gb3VyIG1vZGVsIG9iamVjdCksIFIgd2lsbCBwcmludCBvdXQgZm91ciBkaWZmZXJlbnQgcGxvdHMgb2Ygd2hpY2ggYXJlIHVzZWZ1bCBmb3IgZXZhbHVhdGluZyB0aGUgbW9kZWwgZml0LiBUaGVzZSBhcmUgdmVyeSBicmllZmx5IHN1bW1hcml6ZWQgYXM6CgotIFJlc2lkdWFscyB2cyBGaXR0ZWQ6IGNvbnNpZGVycyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGFjdHVhbCBhbmQgdGhlIHByZWRpY3RlZCBkYXRhLiBUaGUgbW9yZSBkaXNwZXJzZWQgdGhlIHJlc2lkdWFscyBhcmUsIHRoZSB3ZWFrZXIgdGhlIFIyIHNob3VsZCBiZS4gVGhpcyBjYW4gYmUgdXNlZnVsIHRvIGlkZW50aWZ5IG91dGxpbmVzIHRvby5UaGUgZml0IGFsc28gdGVsbHMgdXMgaWYgdGhlIHJlc2lkdWFscyBhcmUgbm9uLWxpbmVhcmx5IGRpc3RyaWJ1dGVkLgogCi0gTm9ybWFsIFEtUTogRGVtb25zdHJhdGVzIHRoZSBleHRlbnQgdG8gd2hpY2ggdGhlIHJlc2lkdWFscyBhcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuIE5vcm1hbCByZXNpZHVhbHMgc2hvdWxkIGZpdCB0aGUgc3RyYWlnaHQgbGluZSB3ZWxsLgoKLSBTY2FsZS1Mb2NhdGlvbjogU2hvd3MgaWYgdGhlIHJlc2lkdWFscyBhcmUgc3ByZWFkIGVxdWFsbHkgYWNyb3NzIHRoZSBmdWxsIHJhbmdlIG9mIHRoZSBwcmVkaWN0b3JzLiBJZiB0aGUgdmFsdWVzIGluIHRoaXMgY2hhcnQgZGlzcGxheSBhIGxpbmVhciBwb3NpdGl2ZSByZWxhdGlvbnNoaXAsIGl0IHN1Z2dlc3RzIHRoYXQgdGhlIHJlc2lkdWFscyBzcHJlYWQgd2lkZXIgYW5kIHdpZGVyIGZvciBncmVhdGVyIHZhbHVlcyAodGhpcyBpcyBrbm93biBhcyAqKmhldGVyb3NjZWRhc3RpY2l0eSoqKS4KCi0gUmVzaWR1YWxzIHZzIExldmVyYWdlOiBJZGVudGlmaWVzIG91dGxpZXJzLCBoaWdoLWxldmVyYWdlIHBvaW50cyBhbmQgaW5mbHVlbnRpYWwgb2JzZXJ2YXRpb25zLiBUaGlzIHBsb3QgaXMgcHJldHR5IGRpZmZpY3VsdCB0byBpbnRlcnByZXQgYW5kIHRoZXJlIGFyZSBvdGhlciBtZWFucyBvZiBpZGVudGlmeWluZyB0aGVzZSB2YWx1ZXMuCgpNb3JlIGluZm9ybWF0aW9uIG9uIHRoZXNlIHBsb3RzIGlzIFtoZXJlXShodHRwczovL2RhdGEubGlicmFyeS52aXJnaW5pYS5lZHUvZGlhZ25vc3RpYy1wbG90cy8pCgpgYGB7cn0KcGFyKG1mcm93PWMoMiwyKSkKcGxvdChtb2RlbCkKYGBgCgpJZiB5b3Ugd2FudCB0byBwcmludCBqdXN0IG9uZSBvZiB0aGUgcGxvdHMgeW91IGNhbiBlbnRlciB3aGljaCA9IG4gd2l0aGluIHRoZSBgcGxvdCgpYCBmdW5jdGlvbi4gaS5lLiBwbG90KG1vZGVsLCB3aGljaCA9IDMpCgojIE1hcCByZXNpZHVhbHMKCk1hcCB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgdGhlIHJlc2lkdWFscwoKLSBDcmVhdGUgYSBjb2x1bW4gdmVjdG9yIG9mIHRoZSByZXNpZHVhbHMKYGBge3J9CnJlc2lkcyA8LSByZXNpZHVhbHMobW9kZWwpCmBgYAoKLSBBZGQgdGhlIHJlc2lkdWFscyBjb2x1bW4gdG8gdGhlIGV4dGVuZGVkLXNwYXRpYWwgZGF0YXNldAoKYGBge3J9Cm1hcC5yZXNpZHMgPC0gY2JpbmQoT0EuQ2Vuc3VzLCByZXNpZHMpIApgYGAKCmBgYHtyfQpoZWFkKG1hcC5yZXNpZHMpCmBgYAoKLSB3ZSBuZWVkIHRvIHJlbmFtZSB0aGUgY29sdW1uIGhlYWRlciBmcm9tIHRoZSByZXNpZHMgZmlsZSAtIGluIHRoaXMgY2FzZSBpdHMgdGhlIDZ0aCBjb2x1bW4gb2YgYG1hcC5yZXNpZHNgCgpgYGB7cn0KbmFtZXMobWFwLnJlc2lkcylbNl0gPC0gInJlc2lkcyIKYGBgCgpgYGB7cn0KaGVhZChtYXAucmVzaWRzKQpgYGAKCgpgYGB7cn0KbWFwLnJlc2lkczIgPC0gY2JpbmQoT0EuQ2Vuc3VzMiwgcmVzaWRzKSAKYGBgCgpgYGB7cn0KZ2xpbXBzZShtYXAucmVzaWRzMikKYGBgCgpObyByZW5hbWluZyBpcyBuZWVkZWQgaWYgd2UgdXNlIGBtYXAucmVzaWRzMmAgYXMgaXQgaXMgYSBgc2ZgIG9iamVjdAoKCi0gIG1hcCB0aGUgcmVzaWR1YWxzIHVzaW5nIHRoZSBxdWlja21hcCBmdW5jdGlvbiBmcm9tIHRtYXAKCmBgYHtyfQpxdG0obWFwLnJlc2lkcywgZmlsbCA9ICJyZXNpZHMiKQpgYGAKCgpgYGB7cn0KcXRtKG1hcC5yZXNpZHMyLCBmaWxsID0gInJlc2lkcyIpCmBgYAoKCklmIHRoZXJlIGlzIGEgZ2VvZ3JhcGhpYyBwYXR0ZXJuIGluIHRoZSByZXNpZHVhbHMsIGl0IGlzIHBvc3NpYmxlIHRoYXQgYW4gdW5vYnNlcnZlZCB2YXJpYWJsZSBtYXkgYmUgaW5mbHVlbmNpbmcgdGhlIGRlcGVuZGVudCB2YXJpYWJsZQoKIyBHZW9ncmFwaGljYWxseSB3ZWlnaHRlZCByZWdyZXNzaW9uCgo+IEdXUiBpcyB0aGUgdGVybSBpbnRyb2R1Y2VkIGJ5IEZvdGhlcmluZ2hhbSwgQ2hhcmx0b24gYW5kIEJydW5zZG9uICgxOTk3LCAyMDAyKSB0byBkZXNjcmliZSBhIGZhbWlseSBvZiByZWdyZXNzaW9uIG1vZGVscyBpbiB3aGljaCB0aGUgKipjb2VmZmljaWVudHMgYXJlIGFsbG93ZWQgdG8gdmFyeSBzcGF0aWFsbHkqKi4gKkdXUiB1c2VzIHRoZSBjb29yZGluYXRlcyBvZiBlYWNoIHNhbXBsZSBwb2ludCogb3Igem9uZSBjZW50cm9pZCwgdGksIGFzIGEgdGFyZ2V0IHBvaW50IGZvciBhIGZvcm0gb2Ygc3BhdGlhbGx5IHdlaWdodGVkIGxlYXN0IHNxdWFyZXMgcmVncmVzc2lvbiAoZm9yIHNvbWUgbW9kZWxzIHRoZSB0YXJnZXQgcG9pbnRzIGNhbiBiZSBzZXBhcmF0ZWx5IGRlZmluZWQsIGUuZy4gYXMgZ3JpZCBpbnRlcnNlY3Rpb24gcG9pbnRzLCByYXRoZXIgdGhhbiBvYnNlcnZlZCBkYXRhIHBvaW50cykuIFsoZGUgU21pdGggZXQgYWwsIDIwMTUpXShodHRwOi8vd3d3LnNwYXRpYWxhbmFseXNpc29ubGluZS5jb20vSFRNTC8/Z2VvZ3JhcGhpY2FsbHlfd2VpZ2h0ZWRfcmVncmVzLmh0bSkKCgojIyBDYWxjdWxhdGUga2VybmVsIGJhbmR3aWR0aAoKLSBVc2UgYW4gYWRhcHRpdmUga2VybmVsCgpgYGB7cn0KR1dSYmFuZHdpZHRoIDwtIGd3ci5zZWwoUXVhbGlmaWNhdGlvbiB+IFVuZW1wbG95ZWQgKyBXaGl0ZV9Ccml0aXNoLCBkYXRhID0gT0EuQ2Vuc3VzLCBhZGFwdCA9IFQpCmBgYAoKSnVzdCB0byBjaGVjaywgbGV0IHVzZSB0aGUgZGF0YSBgT0EuQ2Vuc3VzMmAsIHdoaWNoIGlzIGFuIGBzcGAgb2JqZWN0LgoKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1UfQpHV1JiYW5kd2lkdGgyIDwtIGd3ci5zZWwoUXVhbGlmaWNhdGlvbiB+IFVuZW1wbG95ZWQgKyBXaGl0ZV9Ccml0aXNoLCBkYXRhID0gT0EuQ2Vuc3VzMiwgYWRhcHQgPSBUKQpgYGAKCldoZW4gdXNpbmcgYW5kIGBzZmAgb2JqZWN0LCBvYnNlcnZhdGlvbiBjb29yZGluYXRlcyBoYXZlIHRvIGJlIGdpdmVuCgojIyBSdW4gdGhlIEdXUiBtb2RlbAoKYGBge3J9Cmd3ci5tb2RlbCA9IGd3cihRdWFsaWZpY2F0aW9uIH4gVW5lbXBsb3llZCArIFdoaXRlX0JyaXRpc2gsCiAgICAgICAgICAgICAgICBkYXRhID0gT0EuQ2Vuc3VzLAogICAgICAgICAgICAgICAgYWRhcHQ9R1dSYmFuZHdpZHRoLAogICAgICAgICAgICAgICAgaGF0bWF0cml4PVRSVUUsCiAgICAgICAgICAgICAgICBzZS5maXQ9VFJVRSkgCmBgYAoKUHJpbnQgcmVzdWx0cwoKYGBge3J9Cmd3ci5tb2RlbApgYGAKCgojIyBDcmVhdGUgcmVzdWx0cyBkYXRhZnJhbWUgCgpgYGB7cn0KcmVzdWx0cyA8LWFzLmRhdGEuZnJhbWUoZ3dyLm1vZGVsJFNERikKbmFtZXMocmVzdWx0cykKYGBgCgojIyBNYXAgcmVzdWx0cwoKLSBCaW5kIHRoZSByZXN1bHRzIHRvIGBPQS5DZW5zdXNgIHBvbHlnb24uCgpgYGB7cn0KZ3dyLm1hcCA8LSBjYmluZChPQS5DZW5zdXMsIGFzLm1hdHJpeChyZXN1bHRzKSkKYGBgCgpgYGB7cn0KZ3dyLm1hcDIgPC0gc3RfYXNfc2YoZ3dyLm1hcCkKYGBgCgoKVGhlIHZhcmlhYmxlIG5hbWVzIGZvbGxvd2VkIGJ5IHRoZSBuYW1lIG9mIG91ciBvcmlnaW5hbCBkYXRhIGZyYW1lIChpLmUuIE9BLkNlbnN1cy5VbmVtcGxveWVkKSBhcmUgdGhlIGNvZWZmaWNpZW50cyBvZiB0aGUgbW9kZWwuCgpgYGB7cn0KcXRtKGd3ci5tYXAsIGZpbGwgPSAibG9jYWxSMiIpCmBgYAoKLSBTcGF0aWFsIGRpc3RyaWJ1dGlvbiBvZiBXaGl0ZV9Ccml0aXNoCgpgYGB7cn0KbWFwMSA8LSB0bV9zaGFwZShnd3IubWFwMikgKyAKICB0bV9maWxsKCJXaGl0ZV9Ccml0aXNoIiwKICAgICAgICAgIG4gPSA1LAogICAgICAgICAgc3R5bGUgPSAicXVhbnRpbGUiKSAgKwogIHRtX2xheW91dChmcmFtZSA9IEZBTFNFLAogICAgICAgICAgICBsZWdlbmQudGV4dC5zaXplID0gMC41LAogICAgICAgICAgICBsZWdlbmQudGl0bGUuc2l6ZSA9IDAuNikKbWFwMQpgYGAKCi0gQ29lZmZpY2llbnRzIG9mICBXaGl0ZV9Ccml0aXNoCgoKYGBge3J9Cm1hcDIgPC0gdG1fc2hhcGUoZ3dyLm1hcDIpICsKICB0bV9maWxsKCJXaGl0ZV9Ccml0aXNoLjEiLAogICAgICAgICAgbiA9IDUsCiAgICAgICAgICBzdHlsZSA9ICJxdWFudGlsZSIsCiAgICAgICAgICB0aXRsZSA9ICJXQiBDb2VmZmljaWVudCIpICsKICB0bV9sYXlvdXQoZnJhbWUgPSBGQUxTRSwKICAgICAgICAgICAgbGVnZW5kLnRleHQuc2l6ZSA9IDAuNSwKICAgICAgICAgICAgbGVnZW5kLnRpdGxlLnNpemUgPSAwLjYpCm1hcDIKYGBgCgoKLSBTcGF0aWFsIGRpc3RyaWJ1dGlvbiBvZiBVbmVtcGxveWVkIAoKYGBge3J9Cm1hcDMgPC0gdG1fc2hhcGUoZ3dyLm1hcCkgKwogIHRtX2ZpbGwoIlVuZW1wbG95ZWQiLAogICAgICAgICAgbiA9IDUsCiAgICAgICAgICBzdHlsZSA9ICJxdWFudGlsZSIpICsKICB0bV9sYXlvdXQoZnJhbWUgPSBGQUxTRSwKICAgICAgICAgICAgbGVnZW5kLnRleHQuc2l6ZSA9IDAuNSwKICAgICAgICAgICAgbGVnZW5kLnRpdGxlLnNpemUgPSAwLjYpCm1hcDMKYGBgCgotIENvZWZmaWNpZW50cyBvZiBVbmVtcGxveWVkCgpgYGB7cn0KbWFwNCA8LSB0bV9zaGFwZShnd3IubWFwKSArCiAgdG1fZmlsbCgiVW5lbXBsb3llZC4xIiwKICAgICAgICAgIG4gPSA1LAogICAgICAgICAgc3R5bGUgPSAicXVhbnRpbGUiLAogICAgICAgICAgdGl0bGUgPSAiVWUgQ29lZmZpY2llbnQiKSArCiAgdG1fbGF5b3V0KGZyYW1lID0gRkFMU0UsCiAgICAgICAgICAgIGxlZ2VuZC50ZXh0LnNpemUgPSAwLjUsCiAgICAgICAgICAgIGxlZ2VuZC50aXRsZS5zaXplID0gMC42KQptYXA0IApgYGAKCgoKIyBVc2UgR3JpZCBFeHRyYQoKYGBge3J9CiMgY3JlYXRlcyBhIGNsZWFyIGdyaWQKZ3JpZC5uZXdwYWdlKCkKCiMgYXNzaWducyB0aGUgY2VsbCBzaXplIG9mIHRoZSBncmlkLCBpbiB0aGlzIGNhc2UgMiBieSAyCnB1c2hWaWV3cG9ydCh2aWV3cG9ydChsYXlvdXQ9Z3JpZC5sYXlvdXQoMiwyKSkpCgojIHByaW50cyBhIG1hcCBvYmplY3QgaW50byBhIGRlZmluZWQgY2VsbCAgIApwcmludChtYXAxLCB2cD12aWV3cG9ydChsYXlvdXQucG9zLmNvbCA9IDEsIGxheW91dC5wb3Mucm93ID0xKSkKcHJpbnQobWFwMiwgdnA9dmlld3BvcnQobGF5b3V0LnBvcy5jb2wgPSAyLCBsYXlvdXQucG9zLnJvdyA9MSkpCnByaW50KG1hcDMsIHZwPXZpZXdwb3J0KGxheW91dC5wb3MuY29sID0gMSwgbGF5b3V0LnBvcy5yb3cgPTIpKQpwcmludChtYXA0LCB2cD12aWV3cG9ydChsYXlvdXQucG9zLmNvbCA9IDIsIGxheW91dC5wb3Mucm93ID0yKSkKYGBgCgoKCiMgUnVuIGl0IG9ubGluZQoKSWYgeW91IGFyZSBhIG1lbWJlciBvZiB0aGUgW1F1YVJDUyBsYWJdKGh0dHBzOi8vcXVhcmNzLWxhYi5yYmluZC5pby8pLCB5b3UgY2FuIHJ1biB0aGlzIHR1dG9yaWFsIGluIFtSIFN0dWRpbyBDbG91ZF0oaHR0cHM6Ly9yc3R1ZGlvLmNsb3VkL3NwYWNlcy8xNTU5Ny9wcm9qZWN0Lzk2MTM4MSkKCiMgUmVmZXJlbmNlcwoKICAtIFtBbiBJbnRyb2R1Y3Rpb24gdG8gU3BhdGlhbCBEYXRhIEFuYWx5c2lzIGFuZCBWaXN1YWxpemF0aW9uIGluIFIgLSBHdXkgTGFuc2xleSAmIEphbWVzIENoZXNoaXJlICgyMDE2KV0oaHR0cHM6Ly9kYXRhLmNkcmMuYWMudWsvdHV0b3JpYWwvYW4taW50cm9kdWN0aW9uLXRvLXNwYXRpYWwtZGF0YS1hbmFseXNpcy1hbmQtdmlzdWFsaXNhdGlvbi1pbi1yKQoKLSBbUHJhY3RpY2FsIDEwOiBHZW9ncmFwaGljYWxseSBXZWlnaHRlZCBSZWdyZXNzaW9uIGluIFIKXShodHRwczovL2RhdGEuY2RyYy5hYy51ay9kYXRhc2V0L2FhNTQ5MWM5LWNiYWMtNDAyNi05N2M5LWY5MTY4NDYyZjRhYy9yZXNvdXJjZS9mOGE1MTMxMi05YzA1LTQ3MDEtOGRmMC01ZmQ1OTdlYjljZTQvZG93bmxvYWQvcHJhY3RpY2FsMTAuaHRtbCkKCgoKCkVORAo=