Notes on using Kernel Density Estimation for modelling population density

Packages

First, I am loading all packages necessary for the following analysis.

library(raster)# deal with raster
library(sf) # spatial class
library(mapview) # interactive map viewing
library(tmap) # cartography
library(tmaptools) # little helpers
library(dplyr) # manipulate
library(esri2sf) # get data from the server
library(spatstat) # spatial statistics
library(sp) 
library(rgdal)
library(maptools)
library(spex)

Data

Data Zones Centroids

url_dz =  "http://sedsh127.sedsh.gov.uk/arcgis/rest/services/ScotGov/StatisticalUnits/MapServer/4"

dz_cntr = esri2sf(url_dz, outFields=c("TotPop2011")) %>% st_transform(27700)
## [1] "Feature Layer"
## [1] "esriGeometryPoint"
plot(dz_cntr)

NHS Health Boards bounadaries - above method does not work for polygons in this situation

hb_pol = st_read("data/SG_NHS_HealthBoards_2019.shp")
## Reading layer `SG_NHS_HealthBoards_2019' from data source `/Users/michal/Documents/kdepop/data/SG_NHS_HealthBoards_2019.shp' using driver `ESRI Shapefile'
## Simple feature collection with 14 features and 4 fields
## geometry type:  MULTIPOLYGON
## dimension:      XY
## bbox:           xmin: 7564.996 ymin: 530356.4 xmax: 468812.4 ymax: 1218625
## projected CRS:  OSGB 1936 / British National Grid
plot(hb_pol)

Area of Interest

Here I will select NHS Lothian Health Board and clip the Data Zones centroids to its boundaries

Filter

hb_lothian = hb_pol %>% 
  filter(HBName == "Lothian") %>% 
  select(-Shape_Leng, -Shape_Area)

plot(hb_lothian)

Subset

dz_lothian = dz_cntr[hb_lothian,]

plot(dz_lothian)

## Binning

GRID

rec_grid = st_make_grid(dz_lothian, cellsize = 500, square = TRUE)

rec_grid_sf = st_sf(rec_grid)

rec_grid_sf <- rec_grid_sf %>% mutate(id = row_number())

rec_grid_join = st_join(rec_grid_sf, dz_lothian)

rec_grid_sum = rec_grid_join %>%
  group_by(id) %>%
  summarize(
    dz_count = n(),
    pop_sum  = sum(TotPop2011)
  ) %>% 
  filter(!is.na(pop_sum))
## `summarise()` ungrouping output (override with `.groups` argument)

Map

tmap_mode(mode = c("view"))
## tmap mode set to interactive viewing
tm_shape(rec_grid_sum) +
  tm_fill(col = "pop_sum",
          style = "jenks",
          convert2density = TRUE) + 
  tm_borders(alpha=.8, col = "white")

ESRI - Point Density (SPatial Analyst) - focal operation with movable window e.g. 1 point / (9 cells * 100m2 area per cell) = 0.0011 points per square meter (within a 3x3 search window)

https://pro.arcgis.com/en/pro-app/tool-reference/spatial-analyst/point-density.htm

Kernel Density Estimation

Definition

Kernel Density Estimation (KDE) - method of analyzing the first-order of intensity of point pattern

  • the kernel
  • the bandwidth
  • the edge effect

https://mathisonian.github.io/kde/

Library(spatstat)

Convert sf class to ppp class - DZ Centroids

dz_lothian.sp <- as_Spatial(dz_lothian)
## Warning in showSRID(uprojargs, format = "PROJ", multiline = "NO", prefer_proj
## = prefer_proj): Discarded datum Unknown based on Airy 1830 ellipsoid in CRS
## definition
## Warning in showSRID(SRS_string, format = "PROJ", multiline = "NO", prefer_proj =
## prefer_proj): Discarded datum OSGB 1936 in CRS definition
class(dz_lothian.sp)
## [1] "SpatialPointsDataFrame"
## attr(,"package")
## [1] "sp"
dz_lothian.ppp <- as(dz_lothian.sp, "ppp")

class(dz_lothian.ppp)
## [1] "ppp"

Convert sf class to ppp class - HB Boundaries

hb_lothian.sp <- as_Spatial(hb_lothian)
## Warning in showSRID(uprojargs, format = "PROJ", multiline = "NO", prefer_proj
## = prefer_proj): Discarded datum Unknown based on Airy 1830 ellipsoid in CRS
## definition
## Warning in showSRID(SRS_string, format = "PROJ", multiline = "NO", prefer_proj =
## prefer_proj): Discarded datum OSGB 1936 in CRS definition
class(hb_lothian.sp)
## [1] "SpatialPolygonsDataFrame"
## attr(,"package")
## [1] "sp"
hb_lothian.ppp <- as(hb_lothian.sp, "owin")

class(hb_lothian.ppp)
## [1] "owin"

Plot the Point Pattern for Analysis

# assign boundaries to dz ppp

Window(dz_lothian.ppp) <- hb_lothian.ppp

# plot
plot(dz_lothian.ppp, main=NULL, cols=rgb(0,0,0,.2), pch=20)

Create KDE

kde_200 = density(dz_lothian.ppp, 
        sigma=200, # choose bandwith / diameter of the Kernel in the units your map is in
        eps = 100, # pixel resolution
        weights= dz_lothian.ppp$marks, # population 
        edge=TRUE, 
        varcov=NULL,
        at="pixels",
        eaveoneout=TRUE,
        adjust=1, 
        diggle=FALSE, 
        se=FALSE,
        kernel="gaussian", # choose kernel
        scalekernel=is.character(kernel), 
        positive=FALSE,
        verbose=TRUE)

kde_500 = density(dz_lothian.ppp, 
        sigma=500, # choose bandwith / diameter of the Kernel in the units your map is in
        eps = 100, # pixel resolution
        weights= dz_lothian.ppp$marks, # population
        edge=TRUE, 
        varcov=NULL,
        at="pixels",
        eaveoneout=TRUE,
        adjust=1, 
        diggle=FALSE, 
        se=FALSE,
        kernel="gaussian", # choose kernel
        scalekernel=is.character(kernel), 
        positive=FALSE,
        verbose=TRUE)

Plot KDE

plot(kde_200, main = "Sigma 200m")

plot(kde_500, main = "Sigma 500m")

Extract Clusters

kde_raster = raster(kde_500)

kde_pol = spex::polygonize(kde_raster)

kde_pol2 = kde_pol %>% 
   filter(layer > 0.001) %>% # DEFINE THRESHOLD
   summarize() 

kde_pol2 = kde_pol2 %>% st_set_crs(27700)

plot(kde_pol2)

Settlements

# settlements
sett_url =  "http://sedsh127.sedsh.gov.uk/arcgis/rest/services/NRS/NRS/MapServer/9"

sett_pol = esri2sf(sett_url, outFields=c("name")) %>% st_transform(27700)
## [1] "Feature Layer"
## [1] "esriGeometryPolygon"
sett_lothian = sett_pol[hb_lothian,]

Map

tmap_mode(mode = c("view"))
## tmap mode set to interactive viewing
tm_shape(kde_pol2) +
  tm_fill(col = "red", alpha = 0.5) +
tm_shape(sett_lothian) +
  tm_fill(col = "blue", alpha = 0.5)
## Warning: The shape sett_lothian is invalid. See sf::st_is_valid

Finally, session info

sessionInfo()
## R version 4.0.2 (2020-06-22)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Mojave 10.14.6
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRblas.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] spex_0.7.1          maptools_1.0-2      rgdal_1.5-18       
##  [4] spatstat_1.64-1     rpart_4.1-15        nlme_3.1-148       
##  [7] spatstat.data_1.5-2 esri2sf_0.1.1       dplyr_1.0.2        
## [10] tmaptools_3.0       tmap_3.0            mapview_2.7.8      
## [13] sf_0.9-6            raster_3.1-5        sp_1.4-4           
## 
## loaded via a namespace (and not attached):
##  [1] magic_1.5-9             httr_1.4.2              jsonlite_1.7.2         
##  [4] viridisLite_0.3.0       splines_4.0.2           stats4_4.0.2           
##  [7] yaml_2.2.1              pillar_1.4.7            lattice_0.20-41        
## [10] glue_1.4.2              digest_0.6.27           RColorBrewer_1.1-2     
## [13] polyclip_1.10-0         leaflet.providers_1.9.0 colorspace_1.4-1       
## [16] htmltools_0.5.0         Matrix_1.2-18           XML_3.99-0.3           
## [19] pkgconfig_2.0.3         stars_0.4-1             purrr_0.3.4            
## [22] scales_1.1.1            webshot_0.5.2           tensor_1.5             
## [25] satellite_1.0.2         spatstat.utils_1.17-0   tibble_3.0.4           
## [28] mgcv_1.8-31             generics_0.1.0          ellipsis_0.3.1         
## [31] reproj_0.4.2            leafsync_0.1.0          magrittr_2.0.1         
## [34] crayon_1.3.4            deldir_0.2-3            evaluate_0.14          
## [37] crsmeta_0.3.0           foreign_0.8-80          lwgeom_0.2-3           
## [40] class_7.3-17            tools_4.0.2             lifecycle_0.2.0        
## [43] stringr_1.4.0.9000      munsell_0.5.0           compiler_4.0.2         
## [46] e1071_1.7-4             quadmesh_0.4.5          rlang_0.4.9            
## [49] classInt_0.4-3          units_0.6-7             grid_4.0.2             
## [52] dichromat_2.0-0         htmlwidgets_1.5.1       goftest_1.2-2          
## [55] crosstalk_1.1.0.1       proj4_1.0-10            leafem_0.1.1           
## [58] base64enc_0.1-3         rmarkdown_2.3           geometry_0.4.5         
## [61] codetools_0.2-16        curl_4.3                abind_1.4-5            
## [64] DBI_1.1.0               R6_2.5.0                knitr_1.29             
## [67] KernSmooth_2.23-17      stringi_1.5.3           parallel_4.0.2         
## [70] Rcpp_1.0.5              vctrs_0.3.5             png_0.1-7              
## [73] leaflet_2.0.3           tidyselect_1.1.0        xfun_0.16
 

A work by Michal Michalski

 

LS0tCnRpdGxlOiAiS0RFIGZvciBQb3B1bGF0aW9uIE1vZGVsbGluZyIKYXV0aG9yOiAiTWljaGFsIE1pY2hhbHNraSIKZGF0ZTogIjE0LzEyLzIwMjAiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBmbGF0bHkKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiAKICAgICAgY29sbGFwc2VkOiB0cnVlCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlCiAgCi0tLQo8c3R5bGU+CmRpdi5ibHVlIHsgYmFja2dyb3VuZC1jb2xvcjojRDNEM0QzOyBib3JkZXItcmFkaXVzOiA1cHg7IHBhZGRpbmc6IDIwcHg7fQo8L3N0eWxlPgo8ZGl2IGNsYXNzID0gImJsdWUiPgpOb3RlcyBvbiB1c2luZyBLZXJuZWwgRGVuc2l0eSBFc3RpbWF0aW9uIGZvciBtb2RlbGxpbmcgcG9wdWxhdGlvbiBkZW5zaXR5CjwvZGl2PgoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKIyMgUGFja2FnZXMKRmlyc3QsIEkgYW0gbG9hZGluZyBhbGwgcGFja2FnZXMgbmVjZXNzYXJ5IGZvciB0aGUgZm9sbG93aW5nIGFuYWx5c2lzLgpgYGB7ciBwYWNrYWdlcywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1GQUxTRX0KCmxpYnJhcnkocmFzdGVyKSMgZGVhbCB3aXRoIHJhc3RlcgpsaWJyYXJ5KHNmKSAjIHNwYXRpYWwgY2xhc3MKbGlicmFyeShtYXB2aWV3KSAjIGludGVyYWN0aXZlIG1hcCB2aWV3aW5nCmxpYnJhcnkodG1hcCkgIyBjYXJ0b2dyYXBoeQpsaWJyYXJ5KHRtYXB0b29scykgIyBsaXR0bGUgaGVscGVycwpsaWJyYXJ5KGRwbHlyKSAjIG1hbmlwdWxhdGUKbGlicmFyeShlc3JpMnNmKSAjIGdldCBkYXRhIGZyb20gdGhlIHNlcnZlcgpsaWJyYXJ5KHNwYXRzdGF0KSAjIHNwYXRpYWwgc3RhdGlzdGljcwpsaWJyYXJ5KHNwKSAKbGlicmFyeShyZ2RhbCkKbGlicmFyeShtYXB0b29scykKbGlicmFyeShzcGV4KQpgYGAKCiMjIERhdGEKCkRhdGEgWm9uZXMgQ2VudHJvaWRzCmBgYHtyfQoKdXJsX2R6ID0gICJodHRwOi8vc2Vkc2gxMjcuc2Vkc2guZ292LnVrL2FyY2dpcy9yZXN0L3NlcnZpY2VzL1Njb3RHb3YvU3RhdGlzdGljYWxVbml0cy9NYXBTZXJ2ZXIvNCIKCmR6X2NudHIgPSBlc3JpMnNmKHVybF9keiwgb3V0RmllbGRzPWMoIlRvdFBvcDIwMTEiKSkgJT4lIHN0X3RyYW5zZm9ybSgyNzcwMCkKCnBsb3QoZHpfY250cikKCmBgYAoKCk5IUyBIZWFsdGggQm9hcmRzIGJvdW5hZGFyaWVzIC0gYWJvdmUgbWV0aG9kIGRvZXMgbm90IHdvcmsgZm9yIHBvbHlnb25zIGluIHRoaXMgc2l0dWF0aW9uCmBgYHtyfQoKaGJfcG9sID0gc3RfcmVhZCgiZGF0YS9TR19OSFNfSGVhbHRoQm9hcmRzXzIwMTkuc2hwIikKCnBsb3QoaGJfcG9sKQoKYGBgCgojIyBBcmVhIG9mIEludGVyZXN0CgpIZXJlIEkgd2lsbCBzZWxlY3QgTkhTIExvdGhpYW4gSGVhbHRoIEJvYXJkIGFuZCBjbGlwIHRoZSBEYXRhIFpvbmVzIGNlbnRyb2lkcyB0byBpdHMgYm91bmRhcmllcwoKIyMjIEZpbHRlcgpgYGB7cn0KCmhiX2xvdGhpYW4gPSBoYl9wb2wgJT4lIAogIGZpbHRlcihIQk5hbWUgPT0gIkxvdGhpYW4iKSAlPiUgCiAgc2VsZWN0KC1TaGFwZV9MZW5nLCAtU2hhcGVfQXJlYSkKCnBsb3QoaGJfbG90aGlhbikKCmBgYAoKIyMjIFN1YnNldAoKYGBge3J9Cgpkel9sb3RoaWFuID0gZHpfY250cltoYl9sb3RoaWFuLF0KCnBsb3QoZHpfbG90aGlhbikKCmBgYAojIyBCaW5uaW5nIAoKIyMjIEdSSUQKCmBgYHtyfQpyZWNfZ3JpZCA9IHN0X21ha2VfZ3JpZChkel9sb3RoaWFuLCBjZWxsc2l6ZSA9IDUwMCwgc3F1YXJlID0gVFJVRSkKCnJlY19ncmlkX3NmID0gc3Rfc2YocmVjX2dyaWQpCgpyZWNfZ3JpZF9zZiA8LSByZWNfZ3JpZF9zZiAlPiUgbXV0YXRlKGlkID0gcm93X251bWJlcigpKQoKcmVjX2dyaWRfam9pbiA9IHN0X2pvaW4ocmVjX2dyaWRfc2YsIGR6X2xvdGhpYW4pCgpyZWNfZ3JpZF9zdW0gPSByZWNfZ3JpZF9qb2luICU+JQogIGdyb3VwX2J5KGlkKSAlPiUKICBzdW1tYXJpemUoCiAgICBkel9jb3VudCA9IG4oKSwKICAgIHBvcF9zdW0gID0gc3VtKFRvdFBvcDIwMTEpCiAgKSAlPiUgCiAgZmlsdGVyKCFpcy5uYShwb3Bfc3VtKSkKYGBgCgpNYXAKCmBgYHtyfQoKdG1hcF9tb2RlKG1vZGUgPSBjKCJ2aWV3IikpCgp0bV9zaGFwZShyZWNfZ3JpZF9zdW0pICsKICB0bV9maWxsKGNvbCA9ICJwb3Bfc3VtIiwKICAgICAgICAgIHN0eWxlID0gImplbmtzIiwKICAgICAgICAgIGNvbnZlcnQyZGVuc2l0eSA9IFRSVUUpICsgCiAgdG1fYm9yZGVycyhhbHBoYT0uOCwgY29sID0gIndoaXRlIikKYGBgCgo8ZGl2IGNsYXNzID0gImJsdWUiPgpFU1JJIC0gUG9pbnQgRGVuc2l0eSAoU1BhdGlhbCBBbmFseXN0KSAtIGZvY2FsIG9wZXJhdGlvbiB3aXRoIG1vdmFibGUgd2luZG93IGUuZy4KMSBwb2ludCAvICg5IGNlbGxzICogMTAwbTIgYXJlYSBwZXIgY2VsbCkgPSAwLjAwMTEgcG9pbnRzIHBlciBzcXVhcmUgbWV0ZXIgKHdpdGhpbiBhIDN4MyBzZWFyY2ggd2luZG93KQoKaHR0cHM6Ly9wcm8uYXJjZ2lzLmNvbS9lbi9wcm8tYXBwL3Rvb2wtcmVmZXJlbmNlL3NwYXRpYWwtYW5hbHlzdC9wb2ludC1kZW5zaXR5Lmh0bQo8L2Rpdj4KCiMjIEtlcm5lbCBEZW5zaXR5IEVzdGltYXRpb24KCiMjIyBEZWZpbml0aW9uCgoqKktlcm5lbCBEZW5zaXR5IEVzdGltYXRpb24gKEtERSkqKiAtIG1ldGhvZCBvZiBhbmFseXppbmcgdGhlIGZpcnN0LW9yZGVyIG9mIGludGVuc2l0eSBvZiBwb2ludCBwYXR0ZXJuCgoqIHRoZSBrZXJuZWwKKiB0aGUgYmFuZHdpZHRoCiogdGhlIGVkZ2UgZWZmZWN0CgpodHRwczovL21hdGhpc29uaWFuLmdpdGh1Yi5pby9rZGUvCgoKIyMjIExpYnJhcnkoc3BhdHN0YXQpCgpDb252ZXJ0IHNmIGNsYXNzIHRvIHBwcCBjbGFzcyAtIERaIENlbnRyb2lkcwoKYGBge3J9Cgpkel9sb3RoaWFuLnNwIDwtIGFzX1NwYXRpYWwoZHpfbG90aGlhbikKCmNsYXNzKGR6X2xvdGhpYW4uc3ApCgpkel9sb3RoaWFuLnBwcCA8LSBhcyhkel9sb3RoaWFuLnNwLCAicHBwIikKCmNsYXNzKGR6X2xvdGhpYW4ucHBwKQoKCmBgYAoKCkNvbnZlcnQgc2YgY2xhc3MgdG8gcHBwIGNsYXNzIC0gSEIgQm91bmRhcmllcwoKYGBge3J9CgpoYl9sb3RoaWFuLnNwIDwtIGFzX1NwYXRpYWwoaGJfbG90aGlhbikKCmNsYXNzKGhiX2xvdGhpYW4uc3ApCgpoYl9sb3RoaWFuLnBwcCA8LSBhcyhoYl9sb3RoaWFuLnNwLCAib3dpbiIpCgpjbGFzcyhoYl9sb3RoaWFuLnBwcCkKCgpgYGAKClBsb3QgdGhlIFBvaW50IFBhdHRlcm4gZm9yIEFuYWx5c2lzCgpgYGB7cn0KCiMgYXNzaWduIGJvdW5kYXJpZXMgdG8gZHogcHBwCgpXaW5kb3coZHpfbG90aGlhbi5wcHApIDwtIGhiX2xvdGhpYW4ucHBwCgojIHBsb3QKcGxvdChkel9sb3RoaWFuLnBwcCwgbWFpbj1OVUxMLCBjb2xzPXJnYigwLDAsMCwuMiksIHBjaD0yMCkKCmBgYAoKCkNyZWF0ZSBLREUKCmBgYHtyfQoKa2RlXzIwMCA9IGRlbnNpdHkoZHpfbG90aGlhbi5wcHAsIAogICAgICAgIHNpZ21hPTIwMCwgIyBjaG9vc2UgYmFuZHdpdGggLyBkaWFtZXRlciBvZiB0aGUgS2VybmVsIGluIHRoZSB1bml0cyB5b3VyIG1hcCBpcyBpbgogICAgICAgIGVwcyA9IDEwMCwgIyBwaXhlbCByZXNvbHV0aW9uCiAgICAgICAgd2VpZ2h0cz0gZHpfbG90aGlhbi5wcHAkbWFya3MsICMgcG9wdWxhdGlvbiAKICAgICAgICBlZGdlPVRSVUUsIAogICAgICAgIHZhcmNvdj1OVUxMLAogICAgICAgIGF0PSJwaXhlbHMiLAogICAgICAgIGVhdmVvbmVvdXQ9VFJVRSwKICAgICAgICBhZGp1c3Q9MSwgCiAgICAgICAgZGlnZ2xlPUZBTFNFLCAKICAgICAgICBzZT1GQUxTRSwKICAgICAgICBrZXJuZWw9ImdhdXNzaWFuIiwgIyBjaG9vc2Uga2VybmVsCiAgICAgICAgc2NhbGVrZXJuZWw9aXMuY2hhcmFjdGVyKGtlcm5lbCksIAogICAgICAgIHBvc2l0aXZlPUZBTFNFLAogICAgICAgIHZlcmJvc2U9VFJVRSkKCmtkZV81MDAgPSBkZW5zaXR5KGR6X2xvdGhpYW4ucHBwLCAKICAgICAgICBzaWdtYT01MDAsICMgY2hvb3NlIGJhbmR3aXRoIC8gZGlhbWV0ZXIgb2YgdGhlIEtlcm5lbCBpbiB0aGUgdW5pdHMgeW91ciBtYXAgaXMgaW4KICAgICAgICBlcHMgPSAxMDAsICMgcGl4ZWwgcmVzb2x1dGlvbgogICAgICAgIHdlaWdodHM9IGR6X2xvdGhpYW4ucHBwJG1hcmtzLCAjIHBvcHVsYXRpb24KICAgICAgICBlZGdlPVRSVUUsIAogICAgICAgIHZhcmNvdj1OVUxMLAogICAgICAgIGF0PSJwaXhlbHMiLAogICAgICAgIGVhdmVvbmVvdXQ9VFJVRSwKICAgICAgICBhZGp1c3Q9MSwgCiAgICAgICAgZGlnZ2xlPUZBTFNFLCAKICAgICAgICBzZT1GQUxTRSwKICAgICAgICBrZXJuZWw9ImdhdXNzaWFuIiwgIyBjaG9vc2Uga2VybmVsCiAgICAgICAgc2NhbGVrZXJuZWw9aXMuY2hhcmFjdGVyKGtlcm5lbCksIAogICAgICAgIHBvc2l0aXZlPUZBTFNFLAogICAgICAgIHZlcmJvc2U9VFJVRSkKCmBgYAoKUGxvdCBLREUKCgpgYGB7cn0KCnBsb3Qoa2RlXzIwMCwgbWFpbiA9ICJTaWdtYSAyMDBtIikKcGxvdChrZGVfNTAwLCBtYWluID0gIlNpZ21hIDUwMG0iKQoKYGBgCgo8ZGl2IGNsYXNzID0gImJsdWUiPgpFU1JJIC0gS2VybmVsIERlbnNpdHkgKFNwYXRpYWwgQW5hbHlzdCkKCmh0dHBzOi8vcHJvLmFyY2dpcy5jb20vZW4vcHJvLWFwcC90b29sLXJlZmVyZW5jZS9zcGF0aWFsLWFuYWx5c3Qva2VybmVsLWRlbnNpdHkuaHRtCgo8L2Rpdj4KCiMjIyBFeHRyYWN0IENsdXN0ZXJzCgpgYGB7cn0KCmtkZV9yYXN0ZXIgPSByYXN0ZXIoa2RlXzUwMCkKCmtkZV9wb2wgPSBzcGV4Ojpwb2x5Z29uaXplKGtkZV9yYXN0ZXIpCgprZGVfcG9sMiA9IGtkZV9wb2wgJT4lIAogICBmaWx0ZXIobGF5ZXIgPiAwLjAwMSkgJT4lICMgREVGSU5FIFRIUkVTSE9MRAogICBzdW1tYXJpemUoKSAKCmtkZV9wb2wyID0ga2RlX3BvbDIgJT4lIHN0X3NldF9jcnMoMjc3MDApCgpwbG90KGtkZV9wb2wyKQoKYGBgCgo8ZGl2IGNsYXNzID0gImJsdWUiPgpFU1JJIC0gUmFzdGVyIHRvIFBvbHlnb24gKENvbnZlcnNpb24pCgpodHRwczovL3Byby5hcmNnaXMuY29tL2VuL3Byby1hcHAvdG9vbC1yZWZlcmVuY2UvY29udmVyc2lvbi9yYXN0ZXItdG8tcG9seWdvbi5odG0KCjwvZGl2PgoKIyMgU2V0dGxlbWVudHMKCmBgYHtyfQoKIyBzZXR0bGVtZW50cwpzZXR0X3VybCA9ICAiaHR0cDovL3NlZHNoMTI3LnNlZHNoLmdvdi51ay9hcmNnaXMvcmVzdC9zZXJ2aWNlcy9OUlMvTlJTL01hcFNlcnZlci85IgoKc2V0dF9wb2wgPSBlc3JpMnNmKHNldHRfdXJsLCBvdXRGaWVsZHM9YygibmFtZSIpKSAlPiUgc3RfdHJhbnNmb3JtKDI3NzAwKQoKc2V0dF9sb3RoaWFuID0gc2V0dF9wb2xbaGJfbG90aGlhbixdCgpgYGAKCk1hcAoKYGBge3J9Cgp0bWFwX21vZGUobW9kZSA9IGMoInZpZXciKSkKCnRtX3NoYXBlKGtkZV9wb2wyKSArCiAgdG1fZmlsbChjb2wgPSAicmVkIiwgYWxwaGEgPSAwLjUpICsKdG1fc2hhcGUoc2V0dF9sb3RoaWFuKSArCiAgdG1fZmlsbChjb2wgPSAiYmx1ZSIsIGFscGhhID0gMC41KQogIApgYGAKCgoKIyMgRmluYWxseSwgc2Vzc2lvbiBpbmZvCgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKCgombmJzcDsKPGhyIC8+CjxwIHN0eWxlPSJ0ZXh0LWFsaWduOiBjZW50ZXI7Ij5BIHdvcmsgYnkgPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL3RvcG9ncmFwaG9zLyI+TWljaGFsIE1pY2hhbHNraTwvYT48L3A+CiZuYnNwOwo=