knitr::opts_chunk$set(fig.align = "center",
                      warning = FALSE,
                      message = FALSE,
                      fig.width = 9)

Bibliotecas

library(rworldxtra)
library(tidyverse)
library(sf)
library(raster)

Datos Solenopsis

  • Ver anexos para obtener código de la base de datos que se carga a continuación.
  • Estos datos están en este directorio de Github.
datos <- read_csv("https://raw.githubusercontent.com/Edimer/Spatial-Data-Science/main/SIG_R/data/solenopsis.csv")
datos

Transformación a sf

  • En el argumento coords se incorpora un vector con la posición de las columnas longitud y latitud, respectivamente.
  • El sistema de coordenadas elegido en el siguiente código es el más sencillo de todos, sin embargo, se puede cambiar (ver clase 01).
# Datos a sf
datos_sf <- datos %>% 
  st_as_sf(coords = c(5, 6), crs = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs +towgs84=0,0,0")

Área de estudio

Área en Colombia

colombia <- raster::getData(name = "GADM", country = "COL", level = 0)
colombia_sf <- st_as_sf(colombia)
colombia_sf %>% 
  ggplot() + 
  geom_sf() +
  geom_sf(data = datos_sf)

Área específica

  • El siguiente mapa corresponde a una región específica de Colombia.
# Mapa del mundo
data("countriesHigh")
mapa <- countriesHigh %>% 
  st_as_sf() %>%
  st_crop(datos_sf)
ggplot() +
  geom_sf(data = mapa) +
  geom_sf(data = datos_sf)

Otros mapas

  • Gráfico con color por especie y tamaño por abundancia: filtro los NA para que no aparezcan en la leyenda.
ggplot() +
  geom_sf(data = mapa) +
  geom_sf(data = datos_sf %>% filter(!is.na(Species)),
          aes(color = Species, size = Measurement))

  • Gráfico por especie: filtro los NA para que no aparezcan en la leyenda.
ggplot() +
  geom_sf(data = mapa) +
  geom_sf(data = datos_sf %>% filter(!is.na(Species)),
          aes(color = Species, size = Measurement)) +
  facet_wrap(~Species)

Resumen descriptivo

datos %>% 
  group_by(Species) %>% 
  summarise(Total = n(),
            AbundanciaPromedio = mean(Measurement),
ximoAbundancia = max(Measurement),
nimoAbundancia = min(Measurement))

Modelación

Especie Geminata

  • Para los ejemplos siguientes sólo uso la especie Geminata. Se observa mayor abundancia en el norte de Colombia.
data_geminata <- datos_sf %>% filter(Species == "geminata")
ggplot() +
  geom_sf(data = mapa) +
  geom_sf(data = data_geminata, aes(size = Measurement))

Datos ambientales

  • Aquí puede encontrar información acerca de las variables bioclimáticas que se descargan con el siguiente código.
  • Se realiza el corte del área de interés con la función crop() del paquete raster.
    • Cuando utilizamos formato shape usamos la función st_crop y con formatos raster utilizamos la función crop().
clima <- getData("worldclim", var = "bio", res = 2.5)
Error in getData("worldclim", var = "bio", res = 2.5) : 
  unused arguments (var = "bio", res = 2.5)
  • Número de capas o variables bioclimáticas:
nlayers(clima2)
[1] 19
  • Para el ejemplo sólo uso las capas 1, 7, 12, 15 y 19. La elección la hago de forma aleatoria para el ejemplo.
clima3 <- clima2[[c(1, 7, 12, 15, 19)]]

Extracción de datos

  • Variables bioclimáticas: algunos puntos quedan con valores NA para las variables bioclimáticas.
clima_data <- extract(clima3, data_geminata) %>% 
  as.data.frame()
clima_data
  • Uniendo datos de abundancia con variables bioclimáticas:
data_final <- data_geminata %>% 
  bind_cols(clima_data)
data_final
Simple feature collection with 52 features and 12 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: -76.52944 ymin: 3.107725 xmax: -72.65786 ymax: 11.09833
CRS:            +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs +towgs84=0,0,0
First 10 features:
                             Reference Study_common_taxon Site_name Sampling_effort  Species
1  Dominguez-Haydar and Armbrecht 2010         Formicidae      0yrs              20 geminata
2  Dominguez-Haydar and Armbrecht 2010         Formicidae      1yrs              20 geminata
3  Dominguez-Haydar and Armbrecht 2010         Formicidae      2yrs              20 geminata
4  Dominguez-Haydar and Armbrecht 2010         Formicidae      4yrs              20 geminata
5  Dominguez-Haydar and Armbrecht 2010         Formicidae      6yrs              20 geminata
6  Dominguez-Haydar and Armbrecht 2010         Formicidae      7yrs              20 geminata
7  Dominguez-Haydar and Armbrecht 2010         Formicidae      8yrs              20 geminata
8  Dominguez-Haydar and Armbrecht 2010         Formicidae     12yrs              20 geminata
9  Dominguez-Haydar and Armbrecht 2010         Formicidae     13yrs              20 geminata
10 Dominguez-Haydar and Armbrecht 2010         Formicidae     14yrs              20 geminata
   Measurement Effort_corrected_measurement bio1 bio7 bio12 bio15 bio19
1            3                            3   NA   NA    NA    NA    NA
2           90                           90  275  130  1370    71   204
3          128                          128  275  130  1370    71   204
4            5                            5   NA   NA    NA    NA    NA
5            3                            3   NA   NA    NA    NA    NA
6           13                           13   NA   NA    NA    NA    NA
7            0                            0   NA   NA    NA    NA    NA
8           12                           12   NA   NA    NA    NA    NA
9            0                            0   NA   NA    NA    NA    NA
10           0                            0   NA   NA    NA    NA    NA
                     geometry
1    POINT (-72.71417 11.085)
2  POINT (-72.71222 11.07944)
3  POINT (-72.71443 11.08131)
4   POINT (-72.7235 11.09015)
5    POINT (-72.71167 11.095)
6  POINT (-72.70503 11.09543)
7  POINT (-72.71333 11.09389)
8  POINT (-72.69667 11.09833)
9  POINT (-72.69019 11.09255)
10     POINT (-72.6975 11.09)

Relaciones

  • Nota: para esta especie en particular el número de datos es bajo.

Lineales

data_final %>% 
  as.data.frame() %>% 
  dplyr::select(bio1:bio19, Measurement) %>% 
  gather(key = "key", value = "value", -Measurement)  %>% 
  ggplot(aes(x = value, y = Measurement)) +
  facet_wrap(~key, scales = "free", ncol = 3) +
  geom_point() +
  geom_smooth(method = "lm", color = "red", se = FALSE) +
  theme_bw()

Lineales (Logaritmos de abundancia)

data_final %>% 
  as.data.frame() %>% 
  dplyr::select(bio1:bio19, Measurement) %>% 
  gather(key = "key", value = "value", -Measurement)  %>% 
  ggplot(aes(x = value, y = Measurement)) +
  facet_wrap(~key, scales = "free", ncol = 3) +
  geom_point() +
  geom_smooth(method = "lm", color = "red", se = FALSE) +
  scale_y_log10() +
  theme_bw()

Modelos

  • Se prueban tres modelos y basado en el criterio de información de Akaike se elige el mejor.
    • El primero modelo es una regresión poisson, es decir, un modelo lineal generalizado con distribución de errores Poisson.
    • El segundo es un modelo de regresión poisson con un polinomio de segundo grado para bio1 y bio15.
    • El tercero es un modelo aditivo generalizado (GAM).

GLM Poisson

modelo1 <- glm(Measurement ~ bio1 + bio7 + bio12 + bio15 + bio19,
               data = data_final, family = "poisson")
summary(modelo1)

Call:
glm(formula = Measurement ~ bio1 + bio7 + bio12 + bio15 + bio19, 
    family = "poisson", data = data_final)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-12.0620   -1.6910   -1.2246    0.5594    5.8436  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)    
(Intercept) 55.808756  23.859812   2.339 0.019334 *  
bio1        -0.080275   0.075201  -1.067 0.285763    
bio7        -0.326484   0.084470  -3.865 0.000111 ***
bio12        0.008604   0.002602   3.307 0.000942 ***
bio15        0.087892   0.065417   1.344 0.179087    
bio19       -0.024663   0.008683  -2.840 0.004506 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for poisson family taken to be 1)

    Null deviance: 1169.74  on 38  degrees of freedom
Residual deviance:  309.68  on 33  degrees of freedom
  (13 observations deleted due to missingness)
AIC: 378.12

Number of Fisher Scoring iterations: 6
  • Residuales:
par(mfrow = c(2,2))
plot(modelo1)

GLM Poisson + Polinomio 2

modelo2 <- glm(Measurement ~ I(bio1^2) + bio7 + bio12 + I(bio15^2) + bio19,
               data = data_final, family = "poisson")
summary(modelo2)

Call:
glm(formula = Measurement ~ I(bio1^2) + bio7 + bio12 + I(bio15^2) + 
    bio19, family = "poisson", data = data_final)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-12.0616   -1.7216   -1.3000    0.5815    5.8441  

Coefficients:
              Estimate Std. Error z value Pr(>|z|)    
(Intercept) 43.8725633 15.4543461   2.839 0.004528 ** 
I(bio1^2)   -0.0001434  0.0001564  -0.917 0.358979    
bio7        -0.3003849  0.0798584  -3.761 0.000169 ***
bio12        0.0076670  0.0027049   2.834 0.004590 ** 
I(bio15^2)   0.0008692  0.0007621   1.141 0.254019    
bio19       -0.0224261  0.0092793  -2.417 0.015658 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for poisson family taken to be 1)

    Null deviance: 1169.74  on 38  degrees of freedom
Residual deviance:  310.28  on 33  degrees of freedom
  (13 observations deleted due to missingness)
AIC: 378.72

Number of Fisher Scoring iterations: 6
  • Residuales:
par(mfrow = c(2,2))
plot(modelo2)

GAM

library(mgcv)
modelo3 <- gam(Measurement ~ s(bio1, k = 4) + s(bio7, k = 5) + 
                 bio12 + bio15 + bio19 + bio1 + bio7,
               data = data_final, family = "poisson")
summary(modelo3)

Family: poisson 
Link function: log 

Formula:
Measurement ~ s(bio1, k = 4) + s(bio7, k = 5) + bio12 + bio15 + 
    bio19 + bio1 + bio7

Parametric coefficients:
            Estimate Std. Error z value Pr(>|z|)
(Intercept)   0.0000     0.0000      NA       NA
bio12         0.2318     1.2777   0.181    0.856
bio15         4.0208     5.0339   0.799    0.424
bio19        -0.6589     4.8372  -0.136    0.892
bio1         -4.5917    23.6431  -0.194    0.846
bio7          7.1399    46.0812   0.155    0.877

Approximate significance of smooth terms:
          edf Ref.df Chi.sq p-value
s(bio1) 0.889  1.019  0.534   0.505
s(bio7) 3.963  3.966  2.168   0.667

Rank: 11/13
R-sq.(adj) =  0.495   Deviance explained =   76%
UBRE = 6.6983  Scale est. = 1         n = 39
  • Residuales:
par(mfrow = c(2, 2))
gam.check(modelo3)

Method: UBRE   Optimizer: outer newton
full convergence after 11 iterations.
Gradient range [2.235383e-06,9.044007e-06]
(score 6.698331 & scale 1).
Hessian positive definite, eigenvalue range [9.541386e-06,0.0005525887].
Model rank =  11 / 13 

Basis dimension (k) checking results. Low p-value (k-index<1) may
indicate that k is too low, especially if edf is close to k'.

           k'   edf k-index p-value
s(bio1) 3.000 0.889    0.99    0.49
s(bio7) 4.000 3.963    1.22    0.94

Mejor Modelo

  • Es mejor el modelo GAM (AIC más bajo). Con este modelo se realizan las predicciones de abundancia.
AIC(modelo1, modelo2, modelo3)

Predicciones

  • Nota: dado el bajo número de datos (51 registros) para la especie bajo análisis, no se logra construir un modelo que proporcione abundancias predichas correctamente. Cabe mencionar que es posible mejorar el análisis incluyendo más información de la especie o ajustando otros modelos.
prediccion <- predict(clima3, modelo2, type = "response")
plot(prediccion, colNA = "black")

  • Mapa con ggplot2:
prediccion_df <- prediccion %>% 
  as("SpatialPixelsDataFrame") %>% 
  as.data.frame() %>% 
  rename(Abundancia = layer)

ggplot() +
  geom_tile(data = prediccion_df, aes(x = x, y = y, fill = Abundancia)) +
  geom_sf(data = mapa, alpha = 0) +
  scale_fill_viridis_c() +
  theme_bw()

Predicciones futuras

  • Imágenes futuras: las mismas con las cuales se entrenó el modelo.
futuro <- raster::getData("CMIP5", var = "bio", res = 2.5, rcp = 85,
                          model = "HD", year = 70) %>% 
  crop(data_geminata)

futuro <- futuro[[c(1, 7, 12, 15, 19)]]
names(futuro) <- c("bio1", "bio7", "bio12", "bio15", "bio19")
plot(futuro)

  • Mapa de predicción de abundancia:
prediccion_futura <- predict(futuro, modelo2, type = "response")

prediccion_df_futuro <- prediccion_futura %>% 
  as("SpatialPixelsDataFrame") %>% 
  as.data.frame() %>% 
  rename(Abundancia = layer)

ggplot() +
  geom_tile(data = prediccion_df_futuro, aes(x = x, y = y, fill = Abundancia)) +
  geom_sf(data = mapa, alpha = 0) +
  scale_fill_viridis_c() +
  theme_bw()

Anexos

  • En el ejemplo de clase se trabaja con la especie Bombus affinis (abejorros), sin embargo, obtuve directamente la base de datos de PREDICTS database y voy a trabajar con el género de hormigas Solenopsis, también conocida como hormiga colorada o de fuego..
  • En este caso filtro información sólo para Colombia.
  • Se filtran datos sólo de abundancia de especies.
  • Mantengo sólo las siguientes variables:
    • Reference: referencia del estudio
    • Study_common_taxon: taxón
    • Site_name: nombre de sitio
    • Sampling_effort
    • Longitude
    • Latitude
    • Species
    • Measurement: medida de abundancia
    • Effort_corrected_measurement
# Base de datos total con más de 3 millones de registros
datos_total <- fread("database.csv", encoding = "UTF-8")
# Filtro solenopsis para Colombia: 551 registros de abundancia
datos_colombia <- datos_total %>% 
  filter(Country == "Colombia") %>% 
  filter(Genus == "Solenopsis") %>%  
  filter(Diversity_metric_type == "Abundance") %>% 
  select(Reference, Study_common_taxon, Site_name, Sampling_effort,
         Longitude, Latitude, Species, Measurement, 
         Effort_corrected_measurement) 
# Exportando datos de solenopsis
write.csv(datos_colombia, file = "solenopsis.csv", row.names = FALSE,
          fileEncoding = "UTF-8")
LS0tDQp0aXRsZTogIlNpc3RlbWFzIGRlIEluZm9ybWFjacOzbiBHZW9ncsOhZmljYSBjb24gUiINCnN1YnRpdGxlOiAiTW9kZWxvcyBjb24gUmFzdGVyIg0KYXV0aG9yOiAiRWRpbWVyIERhdmlkIEphcmFtaWxsbyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6DQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQotLS0NCg0KYGBge3J9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLmFsaWduID0gImNlbnRlciIsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBmaWcud2lkdGggPSA5KQ0KYGBgDQoNCg0KLSBOb3RhcyB0b21hZGFzIGRlbCBjdXJzbyBbIlNpc3RlbWFzIGRlIEluZm9ybWFjacOzbiBHZW9ncsOhZmljYSBlbiBSIChTSUcgZW4gUikiIGNvbiBEZXJlayBDb3Jjb3Jhbi5dKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9OWUtekRRUEdoQm8mYWJfY2hhbm5lbD1EZXJla0NvcmNvcmFuKQ0KDQo8aWZyYW1lIHdpZHRoPSI1NjAiIGhlaWdodD0iMzE1IiBzcmM9Imh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2VtYmVkLzllLXpEUVBHaEJvIiBmcmFtZWJvcmRlcj0iMCIgYWxsb3dmdWxsc2NyZWVuPjwvaWZyYW1lPg0KDQojIEJpYmxpb3RlY2FzDQoNCmBgYHtyfQ0KbGlicmFyeShyd29ybGR4dHJhKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShyYXN0ZXIpDQpgYGANCg0KIyBEYXRvcyBTb2xlbm9wc2lzDQoNCjxjZW50ZXI+DQo8aW1nIHNyYyA9ICJpbWcvaG9ybWlnYS5QTkciIC8+DQo8L2NlbnRlcj4NCg0KLSBWZXIgYW5leG9zIHBhcmEgb2J0ZW5lciBjw7NkaWdvIGRlIGxhIGJhc2UgZGUgZGF0b3MgcXVlIHNlIGNhcmdhIGEgY29udGludWFjacOzbi4NCi0gRXN0b3MgZGF0b3MgZXN0w6FuIGVuIFtlc3RlIGRpcmVjdG9yaW8gZGUgR2l0aHViLl0oaHR0cHM6Ly9naXRodWIuY29tL0VkaW1lci9TcGF0aWFsLURhdGEtU2NpZW5jZS9ibG9iL21haW4vU0lHX1IvZGF0YS9zb2xlbm9wc2lzLmNzdikNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpkYXRvcyA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0VkaW1lci9TcGF0aWFsLURhdGEtU2NpZW5jZS9tYWluL1NJR19SL2RhdGEvc29sZW5vcHNpcy5jc3YiKQ0KZGF0b3MNCmBgYA0KDQojIyBUcmFuc2Zvcm1hY2nDs24gYSBzZg0KDQotIEVuIGVsIGFyZ3VtZW50byBgY29vcmRzYCBzZSBpbmNvcnBvcmEgdW4gdmVjdG9yIGNvbiBsYSBwb3NpY2nDs24gZGUgbGFzIGNvbHVtbmFzIGxvbmdpdHVkIHkgbGF0aXR1ZCwgcmVzcGVjdGl2YW1lbnRlLg0KLSBFbCBzaXN0ZW1hIGRlIGNvb3JkZW5hZGFzIGVsZWdpZG8gZW4gZWwgc2lndWllbnRlIGPDs2RpZ28gZXMgZWwgbcOhcyBzZW5jaWxsbyBkZSB0b2Rvcywgc2luIGVtYmFyZ28sIHNlIHB1ZWRlIGNhbWJpYXIgKFt2ZXIgY2xhc2UgMDFdKGh0dHBzOi8vcnB1YnMuY29tL0VkaW1lci82NzM0MzMpKS4NCg0KYGBge3J9DQojIERhdG9zIGEgc2YNCmRhdG9zX3NmIDwtIGRhdG9zICU+JSANCiAgc3RfYXNfc2YoY29vcmRzID0gYyg1LCA2KSwgY3JzID0gIitwcm9qPWxvbmdsYXQgK2VsbHBzPVdHUzg0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyArdG93Z3M4ND0wLDAsMCIpDQpgYGANCg0KIyMgw4FyZWEgZGUgZXN0dWRpbyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KIyMjIMOBcmVhIGVuIENvbG9tYmlhDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KY29sb21iaWEgPC0gcmFzdGVyOjpnZXREYXRhKG5hbWUgPSAiR0FETSIsIGNvdW50cnkgPSAiQ09MIiwgbGV2ZWwgPSAwKQ0KY29sb21iaWFfc2YgPC0gc3RfYXNfc2YoY29sb21iaWEpDQpjb2xvbWJpYV9zZiAlPiUgDQogIGdncGxvdCgpICsgDQogIGdlb21fc2YoKSArDQogIGdlb21fc2YoZGF0YSA9IGRhdG9zX3NmKQ0KYGBgDQoNCiMjIyDDgXJlYSBlc3BlY8OtZmljYQ0KDQotIEVsIHNpZ3VpZW50ZSBtYXBhIGNvcnJlc3BvbmRlIGEgdW5hIHJlZ2nDs24gZXNwZWPDrWZpY2EgZGUgQ29sb21iaWEuDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KIyBNYXBhIGRlbCBtdW5kbw0KZGF0YSgiY291bnRyaWVzSGlnaCIpDQptYXBhIDwtIGNvdW50cmllc0hpZ2ggJT4lIA0KICBzdF9hc19zZigpICU+JQ0KICBzdF9jcm9wKGRhdG9zX3NmKQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBtYXBhKSArDQogIGdlb21fc2YoZGF0YSA9IGRhdG9zX3NmKQ0KYGBgDQoNCiMjIE90cm9zIG1hcGFzDQoNCi0gKipHcsOhZmljbyBjb24gY29sb3IgcG9yIGVzcGVjaWUgeSB0YW1hw7FvIHBvciBhYnVuZGFuY2lhOioqIGZpbHRybyBsb3MgTkEgcGFyYSBxdWUgbm8gYXBhcmV6Y2FuIGVuIGxhIGxleWVuZGEuDQoNCmBgYHtyfQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBtYXBhKSArDQogIGdlb21fc2YoZGF0YSA9IGRhdG9zX3NmICU+JSBmaWx0ZXIoIWlzLm5hKFNwZWNpZXMpKSwNCiAgICAgICAgICBhZXMoY29sb3IgPSBTcGVjaWVzLCBzaXplID0gTWVhc3VyZW1lbnQpKQ0KYGBgDQoNCi0gKipHcsOhZmljbyBwb3IgZXNwZWNpZToqKiBmaWx0cm8gbG9zIE5BIHBhcmEgcXVlIG5vIGFwYXJlemNhbiBlbiBsYSBsZXllbmRhLg0KDQpgYGB7cn0NCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gbWFwYSkgKw0KICBnZW9tX3NmKGRhdGEgPSBkYXRvc19zZiAlPiUgZmlsdGVyKCFpcy5uYShTcGVjaWVzKSksDQogICAgICAgICAgYWVzKGNvbG9yID0gU3BlY2llcywgc2l6ZSA9IE1lYXN1cmVtZW50KSkgKw0KICBmYWNldF93cmFwKH5TcGVjaWVzKQ0KYGBgDQoNCiMjIFJlc3VtZW4gZGVzY3JpcHRpdm8NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpkYXRvcyAlPiUgDQogIGdyb3VwX2J5KFNwZWNpZXMpICU+JSANCiAgc3VtbWFyaXNlKFRvdGFsID0gbigpLA0KICAgICAgICAgICAgQWJ1bmRhbmNpYVByb21lZGlvID0gbWVhbihNZWFzdXJlbWVudCksDQogICAgICAgICAgICBNw6F4aW1vQWJ1bmRhbmNpYSA9IG1heChNZWFzdXJlbWVudCksDQogICAgICAgICAgICBNw61uaW1vQWJ1bmRhbmNpYSA9IG1pbihNZWFzdXJlbWVudCkpDQpgYGANCg0KIyBNb2RlbGFjacOzbg0KDQojIyBFc3BlY2llIEdlbWluYXRhDQoNCi0gUGFyYSBsb3MgZWplbXBsb3Mgc2lndWllbnRlcyBzw7NsbyB1c28gbGEgZXNwZWNpZSAqR2VtaW5hdGEqLiBTZSBvYnNlcnZhIG1heW9yIGFidW5kYW5jaWEgZW4gZWwgbm9ydGUgZGUgQ29sb21iaWEuDQoNCmBgYHtyfQ0KZGF0YV9nZW1pbmF0YSA8LSBkYXRvc19zZiAlPiUgZmlsdGVyKFNwZWNpZXMgPT0gImdlbWluYXRhIikNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gbWFwYSkgKw0KICBnZW9tX3NmKGRhdGEgPSBkYXRhX2dlbWluYXRhLCBhZXMoc2l6ZSA9IE1lYXN1cmVtZW50KSkNCmBgYA0KDQojIyBEYXRvcyBhbWJpZW50YWxlcw0KDQotIFtBcXXDrV0oaHR0cHM6Ly93d3cud29ybGRjbGltLm9yZy9kYXRhL2Jpb2NsaW0uaHRtbCkgcHVlZGUgZW5jb250cmFyIGluZm9ybWFjacOzbiBhY2VyY2EgZGUgbGFzIHZhcmlhYmxlcyBiaW9jbGltw6F0aWNhcyBxdWUgc2UgZGVzY2FyZ2FuIGNvbiBlbCBzaWd1aWVudGUgY8OzZGlnby4NCi0gU2UgcmVhbGl6YSBlbCBjb3J0ZSBkZWwgw6FyZWEgZGUgaW50ZXLDqXMgY29uIGxhIGZ1bmNpw7NuIGNyb3AoKSBkZWwgcGFxdWV0ZSByYXN0ZXIuDQogIC0gQ3VhbmRvIHV0aWxpemFtb3MgZm9ybWF0byBzaGFwZSB1c2Ftb3MgbGEgZnVuY2nDs24gc3RfY3JvcCB5IGNvbiBmb3JtYXRvcyByYXN0ZXIgdXRpbGl6YW1vcyBsYSBmdW5jacOzbiBjcm9wKCkuDQoNCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD0xMH0NCmNsaW1hIDwtIGdldERhdGEoIndvcmxkY2xpbSIsIHZhciA9ICJiaW8iLCByZXMgPSAyLjUpDQpjbGltYTIgPC0gY2xpbWEgJT4lIGNyb3AoZGF0YV9nZW1pbmF0YSkNCnBsb3QoY2xpbWEyKQ0KYGBgDQoNCi0gKipOw7ptZXJvIGRlIGNhcGFzIG8gdmFyaWFibGVzIGJpb2NsaW3DoXRpY2FzOioqDQoNCmBgYHtyfQ0KbmxheWVycyhjbGltYTIpDQpgYGANCg0KLSBQYXJhIGVsIGVqZW1wbG8gc8OzbG8gdXNvIGxhcyBjYXBhcyAxLCA3LCAxMiwgMTUgeSAxOS4gTGEgZWxlY2Npw7NuIGxhIGhhZ28gZGUgZm9ybWEgYWxlYXRvcmlhIHBhcmEgZWwgZWplbXBsby4NCg0KYGBge3J9DQpjbGltYTMgPC0gY2xpbWEyW1tjKDEsIDcsIDEyLCAxNSwgMTkpXV0NCmBgYA0KDQoNCiMjIEV4dHJhY2Npw7NuIGRlIGRhdG9zDQoNCi0gKipWYXJpYWJsZXMgYmlvY2xpbcOhdGljYXM6KiogYWxndW5vcyBwdW50b3MgcXVlZGFuIGNvbiB2YWxvcmVzIE5BIHBhcmEgbGFzIHZhcmlhYmxlcyBiaW9jbGltw6F0aWNhcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpjbGltYV9kYXRhIDwtIGV4dHJhY3QoY2xpbWEzLCBkYXRhX2dlbWluYXRhKSAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKQ0KY2xpbWFfZGF0YQ0KYGBgDQoNCi0gKipVbmllbmRvIGRhdG9zIGRlIGFidW5kYW5jaWEgY29uIHZhcmlhYmxlcyBiaW9jbGltw6F0aWNhczoqKg0KDQpgYGB7cn0NCmRhdGFfZmluYWwgPC0gZGF0YV9nZW1pbmF0YSAlPiUgDQogIGJpbmRfY29scyhjbGltYV9kYXRhKQ0KZGF0YV9maW5hbA0KYGBgDQoNCiMjIFJlbGFjaW9uZXMgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9DQoNCi0gKipOb3RhOioqIHBhcmEgZXN0YSBlc3BlY2llIGVuIHBhcnRpY3VsYXIgZWwgbsO6bWVybyBkZSBkYXRvcyBlcyBiYWpvLg0KDQojIyMgTGluZWFsZXMNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpkYXRhX2ZpbmFsICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgZHBseXI6OnNlbGVjdChiaW8xOmJpbzE5LCBNZWFzdXJlbWVudCkgJT4lIA0KICBnYXRoZXIoa2V5ID0gImtleSIsIHZhbHVlID0gInZhbHVlIiwgLU1lYXN1cmVtZW50KSAgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSwgeSA9IE1lYXN1cmVtZW50KSkgKw0KICBmYWNldF93cmFwKH5rZXksIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDMpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sb3IgPSAicmVkIiwgc2UgPSBGQUxTRSkgKw0KICB0aGVtZV9idygpDQpgYGANCg0KIyMjIExpbmVhbGVzIChMb2dhcml0bW9zIGRlIGFidW5kYW5jaWEpDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KZGF0YV9maW5hbCAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogIGRwbHlyOjpzZWxlY3QoYmlvMTpiaW8xOSwgTWVhc3VyZW1lbnQpICU+JSANCiAgZ2F0aGVyKGtleSA9ICJrZXkiLCB2YWx1ZSA9ICJ2YWx1ZSIsIC1NZWFzdXJlbWVudCkgICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUsIHkgPSBNZWFzdXJlbWVudCkpICsNCiAgZmFjZXRfd3JhcCh+a2V5LCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSAzKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gInJlZCIsIHNlID0gRkFMU0UpICsNCiAgc2NhbGVfeV9sb2cxMCgpICsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCiMjIE1vZGVsb3Mgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9DQoNCi0gU2UgcHJ1ZWJhbiB0cmVzIG1vZGVsb3MgeSBiYXNhZG8gZW4gZWwgY3JpdGVyaW8gZGUgaW5mb3JtYWNpw7NuIGRlIEFrYWlrZSBzZSBlbGlnZSBlbCBtZWpvci4NCiAgLSBFbCBwcmltZXJvIG1vZGVsbyBlcyB1bmEgcmVncmVzacOzbiBwb2lzc29uLCBlcyBkZWNpciwgdW4gbW9kZWxvIGxpbmVhbCBnZW5lcmFsaXphZG8gY29uIGRpc3RyaWJ1Y2nDs24gZGUgZXJyb3JlcyBQb2lzc29uLg0KICAtIEVsIHNlZ3VuZG8gZXMgdW4gbW9kZWxvIGRlIHJlZ3Jlc2nDs24gcG9pc3NvbiBjb24gdW4gcG9saW5vbWlvIGRlIHNlZ3VuZG8gZ3JhZG8gcGFyYSBiaW8xIHkgYmlvMTUuDQogIC0gRWwgdGVyY2VybyBlcyB1biBtb2RlbG8gYWRpdGl2byBnZW5lcmFsaXphZG8gKEdBTSkuDQoNCiMjIyBHTE0gUG9pc3Nvbg0KDQpgYGB7cn0NCm1vZGVsbzEgPC0gZ2xtKE1lYXN1cmVtZW50IH4gYmlvMSArIGJpbzcgKyBiaW8xMiArIGJpbzE1ICsgYmlvMTksDQogICAgICAgICAgICAgICBkYXRhID0gZGF0YV9maW5hbCwgZmFtaWx5ID0gInBvaXNzb24iKQ0Kc3VtbWFyeShtb2RlbG8xKQ0KYGBgDQoNCi0gKipSZXNpZHVhbGVzOioqDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygyLDIpKQ0KcGxvdChtb2RlbG8xKQ0KYGBgDQoNCiMjIyBHTE0gUG9pc3NvbiArIFBvbGlub21pbyAyDQoNCmBgYHtyfQ0KbW9kZWxvMiA8LSBnbG0oTWVhc3VyZW1lbnQgfiBJKGJpbzFeMikgKyBiaW83ICsgYmlvMTIgKyBJKGJpbzE1XjIpICsgYmlvMTksDQogICAgICAgICAgICAgICBkYXRhID0gZGF0YV9maW5hbCwgZmFtaWx5ID0gInBvaXNzb24iKQ0Kc3VtbWFyeShtb2RlbG8yKQ0KYGBgDQoNCi0gKipSZXNpZHVhbGVzOioqDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygyLDIpKQ0KcGxvdChtb2RlbG8yKQ0KYGBgDQoNCiMjIyBHQU0NCg0KYGBge3J9DQpsaWJyYXJ5KG1nY3YpDQptb2RlbG8zIDwtIGdhbShNZWFzdXJlbWVudCB+IHMoYmlvMSwgayA9IDQpICsgcyhiaW83LCBrID0gNSkgKyANCiAgICAgICAgICAgICAgICAgYmlvMTIgKyBiaW8xNSArIGJpbzE5ICsgYmlvMSArIGJpbzcsDQogICAgICAgICAgICAgICBkYXRhID0gZGF0YV9maW5hbCwgZmFtaWx5ID0gInBvaXNzb24iKQ0Kc3VtbWFyeShtb2RlbG8zKQ0KYGBgDQoNCi0gKipSZXNpZHVhbGVzOioqDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygyLCAyKSkNCmdhbS5jaGVjayhtb2RlbG8zKQ0KYGBgDQojIyMgTWVqb3IgTW9kZWxvDQoNCi0gRXMgbWVqb3IgZWwgbW9kZWxvIEdBTSAoQUlDIG3DoXMgYmFqbykuIENvbiBlc3RlIG1vZGVsbyBzZSByZWFsaXphbiBsYXMgcHJlZGljY2lvbmVzIGRlIGFidW5kYW5jaWEuDQoNCmBgYHtyfQ0KQUlDKG1vZGVsbzEsIG1vZGVsbzIsIG1vZGVsbzMpDQpgYGANCg0KIyMgUHJlZGljY2lvbmVzDQoNCi0gKipOb3RhOioqIGRhZG8gZWwgYmFqbyBuw7ptZXJvIGRlIGRhdG9zICg1MSByZWdpc3Ryb3MpIHBhcmEgbGEgZXNwZWNpZSBiYWpvIGFuw6FsaXNpcywgbm8gc2UgbG9ncmEgY29uc3RydWlyIHVuIG1vZGVsbyBxdWUgcHJvcG9yY2lvbmUgYWJ1bmRhbmNpYXMgcHJlZGljaGFzIGNvcnJlY3RhbWVudGUuIENhYmUgbWVuY2lvbmFyIHF1ZSBlcyBwb3NpYmxlIG1lam9yYXIgZWwgYW7DoWxpc2lzIGluY2x1eWVuZG8gbcOhcyBpbmZvcm1hY2nDs24gZGUgbGEgZXNwZWNpZSBvIGFqdXN0YW5kbyBvdHJvcyBtb2RlbG9zLg0KDQpgYGB7cn0NCnByZWRpY2Npb24gPC0gcHJlZGljdChjbGltYTMsIG1vZGVsbzIsIHR5cGUgPSAicmVzcG9uc2UiKQ0KcGxvdChwcmVkaWNjaW9uLCBjb2xOQSA9ICJibGFjayIpDQpgYGANCg0KLSAqKk1hcGEgY29uIGdncGxvdDI6KioNCg0KYGBge3J9DQpwcmVkaWNjaW9uX2RmIDwtIHByZWRpY2Npb24gJT4lIA0KICBhcygiU3BhdGlhbFBpeGVsc0RhdGFGcmFtZSIpICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgcmVuYW1lKEFidW5kYW5jaWEgPSBsYXllcikNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3RpbGUoZGF0YSA9IHByZWRpY2Npb25fZGYsIGFlcyh4ID0geCwgeSA9IHksIGZpbGwgPSBBYnVuZGFuY2lhKSkgKw0KICBnZW9tX3NmKGRhdGEgPSBtYXBhLCBhbHBoYSA9IDApICsNCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSArDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyBQcmVkaWNjaW9uZXMgZnV0dXJhcw0KDQotICoqSW3DoWdlbmVzIGZ1dHVyYXM6KiogbGFzIG1pc21hcyBjb24gbGFzIGN1YWxlcyBzZSBlbnRyZW7DsyBlbCBtb2RlbG8uDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KZnV0dXJvIDwtIHJhc3Rlcjo6Z2V0RGF0YSgiQ01JUDUiLCB2YXIgPSAiYmlvIiwgcmVzID0gMi41LCByY3AgPSA4NSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSAiSEQiLCB5ZWFyID0gNzApICU+JSANCiAgY3JvcChkYXRhX2dlbWluYXRhKQ0KDQpmdXR1cm8gPC0gZnV0dXJvW1tjKDEsIDcsIDEyLCAxNSwgMTkpXV0NCm5hbWVzKGZ1dHVybykgPC0gYygiYmlvMSIsICJiaW83IiwgImJpbzEyIiwgImJpbzE1IiwgImJpbzE5IikNCnBsb3QoZnV0dXJvKQ0KYGBgDQoNCi0gKipNYXBhIGRlIHByZWRpY2Npw7NuIGRlIGFidW5kYW5jaWE6KioNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpwcmVkaWNjaW9uX2Z1dHVyYSA8LSBwcmVkaWN0KGZ1dHVybywgbW9kZWxvMiwgdHlwZSA9ICJyZXNwb25zZSIpDQoNCnByZWRpY2Npb25fZGZfZnV0dXJvIDwtIHByZWRpY2Npb25fZnV0dXJhICU+JSANCiAgYXMoIlNwYXRpYWxQaXhlbHNEYXRhRnJhbWUiKSAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogIHJlbmFtZShBYnVuZGFuY2lhID0gbGF5ZXIpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV90aWxlKGRhdGEgPSBwcmVkaWNjaW9uX2RmX2Z1dHVybywgYWVzKHggPSB4LCB5ID0geSwgZmlsbCA9IEFidW5kYW5jaWEpKSArDQogIGdlb21fc2YoZGF0YSA9IG1hcGEsIGFscGhhID0gMCkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCg0KDQojIEFuZXhvcw0KDQotIEVuIGVsIGVqZW1wbG8gZGUgY2xhc2Ugc2UgdHJhYmFqYSBjb24gbGEgZXNwZWNpZSBCb21idXMgYWZmaW5pcyAoYWJlam9ycm9zKSwgc2luIGVtYmFyZ28sIG9idHV2ZSBkaXJlY3RhbWVudGUgbGEgYmFzZSBkZSBkYXRvcyBkZSBbUFJFRElDVFMgZGF0YWJhc2VdKGh0dHBzOi8vZGF0YS5uaG0uYWMudWsvZGF0YXNldC90aGUtMjAxNi1yZWxlYXNlLW9mLXRoZS1wcmVkaWN0cy1kYXRhYmFzZSkgeSB2b3kgYSB0cmFiYWphciBjb24gZWwgZ8OpbmVybyBkZSBob3JtaWdhcyBbKlNvbGVub3BzaXMqLF0oaHR0cHM6Ly9lcy53aWtpcGVkaWEub3JnL3dpa2kvU29sZW5vcHNpcykgdGFtYmnDqW4gY29ub2NpZGEgY29tbyBob3JtaWdhIGNvbG9yYWRhIG8gZGUgZnVlZ28uLg0KLSAgRW4gZXN0ZSBjYXNvIGZpbHRybyBpbmZvcm1hY2nDs24gc8OzbG8gcGFyYSBDb2xvbWJpYS4NCi0gU2UgZmlsdHJhbiBkYXRvcyBzw7NsbyBkZSBhYnVuZGFuY2lhIGRlIGVzcGVjaWVzLg0KLSBNYW50ZW5nbyBzw7NsbyBsYXMgc2lndWllbnRlcyB2YXJpYWJsZXM6DQogIC0gKipSZWZlcmVuY2U6KiogcmVmZXJlbmNpYSBkZWwgZXN0dWRpbw0KICAtICoqU3R1ZHlfY29tbW9uX3RheG9uOioqIHRheMOzbg0KICAtICoqU2l0ZV9uYW1lOioqIG5vbWJyZSBkZSBzaXRpbw0KICAtIFNhbXBsaW5nX2VmZm9ydA0KICAtIExvbmdpdHVkZQ0KICAtIExhdGl0dWRlDQogIC0gU3BlY2llcw0KICAtICoqTWVhc3VyZW1lbnQ6KiogbWVkaWRhIGRlIGFidW5kYW5jaWENCiAgLSBFZmZvcnRfY29ycmVjdGVkX21lYXN1cmVtZW50DQoNCmBgYHtyfQ0KIyBCYXNlIGRlIGRhdG9zIHRvdGFsIGNvbiBtw6FzIGRlIDMgbWlsbG9uZXMgZGUgcmVnaXN0cm9zDQpkYXRvc190b3RhbCA8LSBmcmVhZCgiZGF0YWJhc2UuY3N2IiwgZW5jb2RpbmcgPSAiVVRGLTgiKQ0KIyBGaWx0cm8gc29sZW5vcHNpcyBwYXJhIENvbG9tYmlhOiA1NTEgcmVnaXN0cm9zIGRlIGFidW5kYW5jaWENCmRhdG9zX2NvbG9tYmlhIDwtIGRhdG9zX3RvdGFsICU+JSANCiAgZmlsdGVyKENvdW50cnkgPT0gIkNvbG9tYmlhIikgJT4lIA0KICBmaWx0ZXIoR2VudXMgPT0gIlNvbGVub3BzaXMiKSAlPiUgIA0KICBmaWx0ZXIoRGl2ZXJzaXR5X21ldHJpY190eXBlID09ICJBYnVuZGFuY2UiKSAlPiUgDQogIHNlbGVjdChSZWZlcmVuY2UsIFN0dWR5X2NvbW1vbl90YXhvbiwgU2l0ZV9uYW1lLCBTYW1wbGluZ19lZmZvcnQsDQogICAgICAgICBMb25naXR1ZGUsIExhdGl0dWRlLCBTcGVjaWVzLCBNZWFzdXJlbWVudCwgDQogICAgICAgICBFZmZvcnRfY29ycmVjdGVkX21lYXN1cmVtZW50KSANCiMgRXhwb3J0YW5kbyBkYXRvcyBkZSBzb2xlbm9wc2lzDQp3cml0ZS5jc3YoZGF0b3NfY29sb21iaWEsIGZpbGUgPSAic29sZW5vcHNpcy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSwNCiAgICAgICAgICBmaWxlRW5jb2RpbmcgPSAiVVRGLTgiKQ0KYGBgDQoNCg0K