1. INTRODUCCIƓN

En el presente cuaderno de R se explicarÔ cómo ilustrar distintas funcionalidades que permiten obtener, procesar y visualizar modelos digitales de elevación (DEM) en R. Los datos de elevación son comúnmente empleados para visualizaciones, hidrología y modelado ecológico, entre otras aplicaciones. Estos datos en R estÔn disponibles a través de funciones en muchos paquetes o requiere acceso local a los datos debido a que no ha tenido una sola interfaz. Lo anterior ya no es necesario, ya que existe una variedad de API que brinda acceso programÔtico a los datos de elevación, es el caso del paquete elevatr que estandariza el acceso a estos datos desde las API web y el cual se explorarÔ en este cuaderno.

Para realizar la demostración trabajaremos con los datos del municipio de Rionegro del departamento de Antioquía. Antes de empezar es necesario instalar algunas librerías.

#Decomentar el siguiente código para instalar las librerías
#install.packages("rgdal")
#install.packages("raster")
#install.packages("elevatr")
#install.packages("rasterVis")
#install.packages("rgl")
library(rasterVis)
library(raster)
library(rgdal)
library(elevatr)

2. Obtención de datos de elevación rÔster

Existen diferentes fuentes de modelos digitales de elevación, como Shuttle Radar Topography Mission (SRTM), USGS National Elevation Dataset (NED), Global DEM (GDEM), entre otras. Mapzen combinó varias de estas fuentes para crear un producto de elevación que utiliza los mejores datos disponibles para una región determinada a un nivel de zoom determinado. Estos datos estÔn actualmente accesibles a través de Terrain Tiles en Amazon Web Services (AWS).

Usando get_elev_raster () para acceder a Terrain Tiles en AWS.

La entrada para get_elev_raster () es un data.frame con ubicaciones x (longitud) e y (latitud), un objeto sp o rĆ”ster y src debe establecerse en ā€œmapzenā€ (este es el valor predeterminado), devolviendo un RasterLayer de los mosaicos que se superponen al cuadro delimitador de la entrada.

No existe diferencia en el uso de datos de entrada sp y rƔster. El marco de datos requiere un prj. Se mostrarƔn ejemplos usando un SpatialPolygonsDataFrame. El nivel de zoom (z) serƔ de 8 para evitar problemas de reinicio del programa al usar valores mƔs altos.

Para empezar, debemos cargar un shapefile de los municipios del departamento de AntioquĆ­a obtenidos del geoportal del DANE.

Revisemos el contenido de la carpeta:

list.files("ADMINISTRATIVO")
 [1] "MGN_DPTO_POLITICO.cpg"     "MGN_DPTO_POLITICO.dbf"    
 [3] "MGN_DPTO_POLITICO.prj"     "MGN_DPTO_POLITICO.sbn"    
 [5] "MGN_DPTO_POLITICO.sbx"     "MGN_DPTO_POLITICO.shp"    
 [7] "MGN_DPTO_POLITICO.shp.xml" "MGN_DPTO_POLITICO.shx"    
 [9] "MGN_MPIO_POLITICO.cpg"     "MGN_MPIO_POLITICO.dbf"    
[11] "MGN_MPIO_POLITICO.prj"     "MGN_MPIO_POLITICO.sbn"    
[13] "MGN_MPIO_POLITICO.sbx"     "MGN_MPIO_POLITICO.shp"    
[15] "MGN_MPIO_POLITICO.shp.xml" "MGN_MPIO_POLITICO.shx"    

A través de una función del paquete rÔster leamos el shapefile

(mpio <-  shapefile("ADMINISTRATIVO/MGN_MPIO_POLITICO.shp"))
class       : SpatialPolygonsDataFrame 
features    : 125 
extent      : -77.12783, -73.88128, 5.418558, 8.873974  (xmin, xmax, ymin, ymax)
crs         : +proj=longlat +datum=WGS84 +no_defs  
variables   : 9
names       : DPTO_CCDGO, MPIO_CCDGO, MPIO_CNMBR,                          MPIO_CRSLC,    MPIO_NAREA, MPIO_NANO, DPTO_CNMBR,     Shape_Leng,      Shape_Area 
min values  :         05,      05001,  ABEJORRAL,                                1541,   15.83597275,      2017,  ANTIOQUIA, 0.172962481259, 0.0012928296672 
max values  :         05,      05895,   ZARAGOZA, Ordenanza 9 de Diciembre 15 de 1983, 2959.36315089,      2017,  ANTIOQUIA,  6.61177822771,   0.23894871119 
mpio
class       : SpatialPolygonsDataFrame 
features    : 125 
extent      : -77.12783, -73.88128, 5.418558, 8.873974  (xmin, xmax, ymin, ymax)
crs         : +proj=longlat +datum=WGS84 +no_defs  
variables   : 9
names       : DPTO_CCDGO, MPIO_CCDGO, MPIO_CNMBR,                          MPIO_CRSLC,    MPIO_NAREA, MPIO_NANO, DPTO_CNMBR,     Shape_Leng,      Shape_Area 
min values  :         05,      05001,  ABEJORRAL,                                1541,   15.83597275,      2017,  ANTIOQUIA, 0.172962481259, 0.0012928296672 
max values  :         05,      05895,   ZARAGOZA, Ordenanza 9 de Diciembre 15 de 1983, 2959.36315089,      2017,  ANTIOQUIA,  6.61177822771,   0.23894871119 

Observemos las primeras columnas

head(mpio)

Ahora, seleccionamos un municipio, en este caso Rionegro

rionegro <- mpio[mpio$MPIO_CNMBR=="RIONEGRO",]
plot(rionegro, main="Rionegro", axes=TRUE)
plot(mpio, add=TRUE)
invisible(text(coordinates(mpio), labels=as.character(mpio$MPIO_CNMBR), cex=0.9))

Para descargar los datos de elevación descomente y ejecute el siguiente código.

#elevation <- get_elev_raster(rionegro, z = 8)

Revisemos que hay en el objeto: elevation

elevation
class      : RasterLayer 
dimensions : 1035, 1033, 1069155  (nrow, ncol, ncell)
resolution : 0.00275, 0.00273  (x, y)
extent     : -75.95125, -73.1105, 4.201768, 7.027318  (xmin, xmax, ymin, ymax)
crs        : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
source     : memory
names      : layer 
values     : -430.9265, 5255.14  (min, max)

A tener en cuenta: * dimensions: ā€œtamaƱoā€ del archivo en pĆ­xeles, * nrow, ncol: nĆŗmero de filas y columnas en los datos, * ncells: nĆŗmero total de pĆ­xeles (celdas) que componen el rĆ”ster, * Resolution: tamaƱo de cada pĆ­xel (grados, para este caso) (1 grado decimal representa ~111,11 km), * extent: extensión espacial del rĆ”ster (mismas unidades de coordenadas que el sistema de referencia de coordenadas del rĆ”ster), * crs: cadena del sistema de referencia de coordenadas para el rĆ”ster (para este caso es WGS 84).

plot(elevation, main="DEM de Rionegro (metros)")
plot(rionegro, add=TRUE)

3. Ajustar datos de elevación según la extensión del Ôrea de estudio

Mediante el siguiente código se podra cubrir solamente la extensión del municipio seleccionado, en este caso Rionegro

De todos modos, es una buena idea guardar el DEM para el futuro.

elev_crop = crop(elevation, rionegro)
plot(elev_crop, main="DEM de Rionegro ajustado")
plot(rionegro, add=TRUE)

Revisemos el nuevo objeto

elev_crop
class      : RasterLayer 
dimensions : 64, 60, 3840  (nrow, ncol, ncell)
resolution : 0.00275, 0.00273  (x, y)
extent     : -75.4865, -75.3215, 6.058168, 6.232888  (xmin, xmax, ymin, ymax)
crs        : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
source     : memory
names      : layer 
values     : 2070.517, 2692.006  (min, max)

4. Reproyectar los datos de elevación

Cuando trabajamos con DEM es aconsejable utilizar coordenadas de mapa en lugar de coordenadas geogrÔficas, debido a que en las coordenadas geogrÔficas las unidades de dimensiones horizontales son grados decimales, pero la unidad de dimensión vertical son metros.

Podemos ir a epsg.io y buscar la proyección de la zona MAGNA Colombia BogotÔ. Necesitamos obtener la definición de esta referencia espacial en formato PROJ.4 (el que se usa para las bibliotecas sp y rÔster. Copiemos el texto PROJ.4 y guÔrdelo)

spatialref <- "+proj=tmerc +lat_0=4.596200416666666 +lon_0=-74.07750791666666 
+k=1 +x_0=1000000 +y_0=1000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m 
+no_defs"

Ahora, podemos reproyectar los datos de elevación de las coordenadas geogrÔficas WGS84 en la zona MAGNA Colombia BogotÔ.

pr3 <- projectExtent(elev_crop, spatialref)
res(pr3) <- 100
rep_elev <- projectRaster(elev_crop, pr3)

Observemos que hay en el objeto rep_elev

rep_elev
class      : RasterLayer 
dimensions : 194, 183, 35502  (nrow, ncol, ncell)
resolution : 100, 100  (x, y)
extent     : 844006.4, 862306.4, 1161800, 1181200  (xmin, xmax, ymin, ymax)
crs        : +proj=tmerc +lat_0=4.596200416666666 +lon_0=-74.07750791666666 +k=1 +x_0=1000000 +y_0=1000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs 
source     : memory
names      : layer 
values     : 2070.828, 2695.198  (min, max)

Ahora, volvamos a proyectar el SpatialPolygonsDataFrame que representa el municipio de Rionegro:

(rep_rionegro = spTransform(rionegro,spatialref))
class       : SpatialPolygonsDataFrame 
features    : 1 
extent      : 844111.8, 862244.7, 1161773, 1181278  (xmin, xmax, ymin, ymax)
crs         : +proj=tmerc +lat_0=4.596200416666666 +lon_0=-74.07750791666666 +k=1 +x_0=1000000 +y_0=1000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs 
variables   : 9
names       : DPTO_CCDGO, MPIO_CCDGO, MPIO_CNMBR, MPIO_CRSLC,  MPIO_NAREA, MPIO_NANO, DPTO_CNMBR,     Shape_Leng,      Shape_Area 
value       :         05,      05615,   RIONEGRO,       1783, 195.9401959,      2017,  ANTIOQUIA, 0.890721733569, 0.0159994361869 

Visualización del DEM reproyectado

plot(rep_elev, main="Modelo de elevación digital reproyectado")
plot(rep_rionegro, add=TRUE)

5. Estadísticas bÔsicas de datos de elevación

Realizar una exploración basica de los datos de elevación de Rionegro como su distribución, media, desviaciones.

hist(rep_elev, main="Histograma de datos de elevación", col= "green")

promedio <- cellStats(rep_elev, 'mean')
minimo <- cellStats(rep_elev, 'min')
maximo <- cellStats(rep_elev, 'max')
desviacion  <- cellStats(rep_elev, 'sd')
metricas <- c('mean', 'min', 'max', 'std')
valores <- c(promedio, minimo, maximo, desviacion)
(df_estadisticas <- data.frame(metricas, valores))

6. Obtención de variables geomorfométricas

Primero, calcule la pendiente, el aspecto y el sombreado.

slope = terrain(rep_elev,opt='slope', unit='degrees')
aspect = terrain(rep_elev,opt='aspect',unit='degrees')
hill = hillShade(slope,aspect,40,315)

Visualización del DEM

plot(rep_elev,main="DEM para Rionegro [metros]",
     col=terrain.colors(25,alpha=0.7))

Visualización de la pendiente

plot(slope,main="Pendiente para Rionegro [grados]", 
     col=topo.colors(25,alpha=0.7))

Visualización de la orientación

plot(aspect,main="Orientación para Rionegro [grados]", 
     col=rainbow(25,alpha=0.7))

Visualización combinada del sombreado y el DEM

plot(hill,
     col=gray(1:100/100),  
     legend=FALSE,      
     main="DEM for Medellin",
     axes=FALSE)           
plot(rep_elev, 
     axes=FALSE,
     col=terrain.colors(12, alpha=0.35), add=TRUE) 
plot(rep_rionegro, add=TRUE)

7. Mapeo de datos de elevación con rayshader

Para producir visualizaciones de datos 2D y 3D en R se emplea la librería rayshader. rayshader usa datos de elevación en una matriz R base y una combinación de trazado de rayos, mapeo de textura esférica, superposiciones y oclusión ambiental para generar mapas topogrÔficos 2D y 3D . AdemÔs de los mapas, rayshader también permite al usuario traducir objetos ggplot2 en visualizaciones de datos en 3D.

#install.packages("rayshader")
library(rayshader)
elmat = raster_to_matrix(rep_elev)
[1] "Dimensions of matrix are: 183x194."
elmat %>%
  sphere_shade(texture = "imhof2") %>%
  plot_map()

Calculating Surface Normal [------------------------------------] ETA:  0s
Calculating Surface Normal [====================================] ETA:  0s
                                                                          

elmat %>%
  sphere_shade(texture = "desert") %>%
  add_water(detect_water(elmat), color = "desert") %>%
  plot_map()

Calculating Surface Normal [------------------------------------] ETA:  0s
Calculating Surface Normal [====================================] ETA:  0s
                                                                          

elmat %>%
  sphere_shade(texture = "desert") %>%
  add_water(detect_water(elmat), color = "desert") %>%
  add_shadow(ray_shade(elmat), 0.5) %>%
  plot_map()

Calculating Surface Normal [------------------------------------] ETA:  0s
Calculating Surface Normal [====================================] ETA:  0s
                                                                          

Raytracing [----------------------------------------------------] ETA:  0s
Raytracing [====================================================] ETA:  0s
                                                                          

8. Otra forma de visualización

Antes de inciar instale la librerĆ­a jpeg

#install.packages("jpeg")
library(jpeg)
getv=function(i,a,s){
  ct = dim(i)[1:2]/2
  sx = values(s)/90 * ct[1]
  sy = values(s)/90 * ct[2]
  a = values(a) * 0.01745
  px = floor(ct[1] + sx * -sin(a))
  py = floor(ct[2] + sy * cos(a))
  
  
  template = brick(s,s,s)
  values(template)=NA
  
  cellr = px + py * ct[1]*2
  cellg = px + py * ct[1]*2 + (ct[1]*2*ct[2]*2)
  cellb = px + py * ct[1]*2 + 2*(ct[1]*2*ct[2]*2)
  
  template[[1]] = i[cellr]
  template[[2]] = i[cellg]
  template[[3]] = i[cellb]
  
  template = template * 256
  
  template
}
map=readJPEG("9pvbHjN.jpg")
out = getv(map, aspect, slope)
no non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infno non-missing arguments to min; returning Infno non-missing arguments to max; returning -Inf
plotRGB(out)

LS0tCnRpdGxlOiAiRGF0b3MgZGUgZWxldmFjacOzbiBtdW5pY2lwYWwgZW4gUiIKYXV0aG9yOiAiTGF1cmEgR2luZXRoIEZvcmVybyIKZGF0ZTogIjIxIGRlIFNlcHRpZW1icmUgZGUgMjAyMCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgKioxLiBJTlRST0RVQ0NJw5NOKioKCkVuIGVsIHByZXNlbnRlIGN1YWRlcm5vIGRlIFIgc2UgZXhwbGljYXLDoSBjw7NtbyBpbHVzdHJhciBkaXN0aW50YXMgZnVuY2lvbmFsaWRhZGVzIHF1ZSBwZXJtaXRlbiBvYnRlbmVyLCBwcm9jZXNhciB5IHZpc3VhbGl6YXIgbW9kZWxvcyBkaWdpdGFsZXMgZGUgZWxldmFjacOzbiAoREVNKSBlbiBSLiBMb3MgZGF0b3MgZGUgZWxldmFjacOzbiBzb24gY29tw7pubWVudGUgZW1wbGVhZG9zIHBhcmEgdmlzdWFsaXphY2lvbmVzLCBoaWRyb2xvZ8OtYSB5IG1vZGVsYWRvIGVjb2zDs2dpY28sIGVudHJlIG90cmFzIGFwbGljYWNpb25lcy4gRXN0b3MgZGF0b3MgZW4gUiBlc3TDoW4gZGlzcG9uaWJsZXMgYSB0cmF2w6lzIGRlIGZ1bmNpb25lcyBlbiBtdWNob3MgcGFxdWV0ZXMgbyByZXF1aWVyZSBhY2Nlc28gbG9jYWwgYSBsb3MgZGF0b3MgZGViaWRvIGEgcXVlIG5vIGhhIHRlbmlkbyB1bmEgc29sYSBpbnRlcmZhei4gTG8gYW50ZXJpb3IgeWEgbm8gZXMgbmVjZXNhcmlvLCB5YSBxdWUgZXhpc3RlIHVuYSB2YXJpZWRhZCBkZSBBUEkgcXVlIGJyaW5kYSBhY2Nlc28gcHJvZ3JhbcOhdGljbyBhIGxvcyBkYXRvcyBkZSBlbGV2YWNpw7NuLCBlcyBlbCBjYXNvIGRlbCBwYXF1ZXRlIGVsZXZhdHIgcXVlIGVzdGFuZGFyaXphIGVsIGFjY2VzbyBhIGVzdG9zIGRhdG9zIGRlc2RlIGxhcyBBUEkgd2ViIHkgZWwgY3VhbCBzZSBleHBsb3JhcsOhIGVuIGVzdGUgY3VhZGVybm8uCgpQYXJhIHJlYWxpemFyIGxhIGRlbW9zdHJhY2nDs24gdHJhYmFqYXJlbW9zIGNvbiBsb3MgZGF0b3MgZGVsIG11bmljaXBpbyBkZSBSaW9uZWdybyBkZWwgZGVwYXJ0YW1lbnRvIGRlIEFudGlvcXXDrWEuIEFudGVzIGRlIGVtcGV6YXIgZXMgbmVjZXNhcmlvIGluc3RhbGFyIGFsZ3VuYXMgbGlicmVyw61hcy4KYGBge3J9CiNEZWNvbWVudGFyIGVsIHNpZ3VpZW50ZSBjw7NkaWdvIHBhcmEgaW5zdGFsYXIgbGFzIGxpYnJlcsOtYXMKI2luc3RhbGwucGFja2FnZXMoInJnZGFsIikKI2luc3RhbGwucGFja2FnZXMoInJhc3RlciIpCiNpbnN0YWxsLnBhY2thZ2VzKCJlbGV2YXRyIikKI2luc3RhbGwucGFja2FnZXMoInJhc3RlclZpcyIpCiNpbnN0YWxsLnBhY2thZ2VzKCJyZ2wiKQpgYGAKYGBge3J9CmxpYnJhcnkocmFzdGVyVmlzKQpgYGAKYGBge3J9CmxpYnJhcnkocmFzdGVyKQpgYGAKYGBge3J9CmxpYnJhcnkocmdsKQpgYGAKYGBge3J9CmxpYnJhcnkocmdkYWwpCmBgYApgYGB7cn0KbGlicmFyeShlbGV2YXRyKQpgYGAKCiMjICoqMi4gT2J0ZW5jacOzbiBkZSBkYXRvcyBkZSBlbGV2YWNpw7NuIHLDoXN0ZXIqKiAKRXhpc3RlbiBkaWZlcmVudGVzIGZ1ZW50ZXMgZGUgbW9kZWxvcyBkaWdpdGFsZXMgZGUgZWxldmFjacOzbiwgY29tbyBTaHV0dGxlIFJhZGFyIFRvcG9ncmFwaHkgTWlzc2lvbiAoU1JUTSksIFVTR1MgTmF0aW9uYWwgRWxldmF0aW9uIERhdGFzZXQgKE5FRCksIEdsb2JhbCBERU0gKEdERU0pLCBlbnRyZSBvdHJhcy4gTWFwemVuIGNvbWJpbsOzIHZhcmlhcyBkZSBlc3RhcyBmdWVudGVzIHBhcmEgY3JlYXIgdW4gcHJvZHVjdG8gZGUgZWxldmFjacOzbiBxdWUgdXRpbGl6YSBsb3MgbWVqb3JlcyBkYXRvcyBkaXNwb25pYmxlcyBwYXJhIHVuYSByZWdpw7NuIGRldGVybWluYWRhIGEgdW4gbml2ZWwgZGUgem9vbSBkZXRlcm1pbmFkby4gRXN0b3MgZGF0b3MgZXN0w6FuIGFjdHVhbG1lbnRlIGFjY2VzaWJsZXMgYSB0cmF2w6lzIGRlIFRlcnJhaW4gVGlsZXMgZW4gQW1hem9uIFdlYiBTZXJ2aWNlcyAoQVdTKS4KCiMjIyBVc2FuZG8gZ2V0X2VsZXZfcmFzdGVyICgpIHBhcmEgYWNjZWRlciBhIFRlcnJhaW4gVGlsZXMgZW4gQVdTLgoKTGEgZW50cmFkYSBwYXJhIGdldF9lbGV2X3Jhc3RlciAoKSBlcyB1biBkYXRhLmZyYW1lIGNvbiB1YmljYWNpb25lcyBfeF8gKGxvbmdpdHVkKSBlIF95XyAobGF0aXR1ZCksIHVuIG9iamV0byBzcCBvIHLDoXN0ZXIgeSBzcmMgZGViZSBlc3RhYmxlY2Vyc2UgZW4g4oCcbWFwemVu4oCdIChlc3RlIGVzIGVsIHZhbG9yIHByZWRldGVybWluYWRvKSwgZGV2b2x2aWVuZG8gdW4gUmFzdGVyTGF5ZXIgZGUgbG9zIG1vc2FpY29zIHF1ZSBzZSBzdXBlcnBvbmVuIGFsIGN1YWRybyBkZWxpbWl0YWRvciBkZSBsYSBlbnRyYWRhLgoKTm8gZXhpc3RlIGRpZmVyZW5jaWEgZW4gZWwgdXNvIGRlIGRhdG9zIGRlIGVudHJhZGEgc3AgeSByw6FzdGVyLiBFbCBtYXJjbyBkZSBkYXRvcyByZXF1aWVyZSB1biBwcmouIFNlIG1vc3RyYXLDoW4gZWplbXBsb3MgdXNhbmRvIHVuIFNwYXRpYWxQb2x5Z29uc0RhdGFGcmFtZS4gRWwgbml2ZWwgZGUgem9vbSAoeikgc2Vyw6EgZGUgOCBwYXJhIGV2aXRhciBwcm9ibGVtYXMgZGUgcmVpbmljaW8gZGVsIHByb2dyYW1hIGFsIHVzYXIgdmFsb3JlcyBtw6FzIGFsdG9zLgoKUGFyYSBlbXBlemFyLCBkZWJlbW9zIGNhcmdhciB1biBzaGFwZWZpbGUgZGUgbG9zIG11bmljaXBpb3MgZGVsIGRlcGFydGFtZW50byBkZSBBbnRpb3F1w61hIG9idGVuaWRvcyBkZWwgZ2VvcG9ydGFsIGRlbCBEQU5FLgoKUmV2aXNlbW9zIGVsIGNvbnRlbmlkbyBkZSBsYSBjYXJwZXRhOgpgYGB7cn0KbGlzdC5maWxlcygiQURNSU5JU1RSQVRJVk8iKQpgYGAKQSB0cmF2w6lzIGRlIHVuYSBmdW5jacOzbiBkZWwgcGFxdWV0ZSByw6FzdGVyIGxlYW1vcyBlbCBzaGFwZWZpbGUgCmBgYHtyfQoobXBpbyA8LSAgc2hhcGVmaWxlKCJBRE1JTklTVFJBVElWTy9NR05fTVBJT19QT0xJVElDTy5zaHAiKSkKbXBpbwpgYGAKT2JzZXJ2ZW1vcyBsYXMgcHJpbWVyYXMgY29sdW1uYXMgCmBgYHtyfQpoZWFkKG1waW8pCmBgYApBaG9yYSwgc2VsZWNjaW9uYW1vcyB1biBtdW5pY2lwaW8sIGVuIGVzdGUgY2FzbyBSaW9uZWdybwpgYGB7cn0KcmlvbmVncm8gPC0gbXBpb1ttcGlvJE1QSU9fQ05NQlI9PSJSSU9ORUdSTyIsXQpwbG90KHJpb25lZ3JvLCBtYWluPSJSaW9uZWdybyIsIGF4ZXM9VFJVRSkKcGxvdChtcGlvLCBhZGQ9VFJVRSkKaW52aXNpYmxlKHRleHQoY29vcmRpbmF0ZXMobXBpbyksIGxhYmVscz1hcy5jaGFyYWN0ZXIobXBpbyRNUElPX0NOTUJSKSwgY2V4PTAuOSkpCmBgYApQYXJhIGRlc2NhcmdhciBsb3MgZGF0b3MgZGUgZWxldmFjacOzbiBkZXNjb21lbnRlIHkgZWplY3V0ZSBlbCBzaWd1aWVudGUgY8OzZGlnby4KYGBge3J9CiNlbGV2YXRpb24gPC0gZ2V0X2VsZXZfcmFzdGVyKHJpb25lZ3JvLCB6ID0gOCkKYGBgClJldmlzZW1vcyBxdWUgaGF5IGVuIGVsIG9iamV0bzogIF9lbGV2YXRpb25fCmBgYHtyfQplbGV2YXRpb24KYGBgCj5BIHRlbmVyIGVuIGN1ZW50YTogCiogZGltZW5zaW9uczogInRhbWHDsW8iIGRlbCBhcmNoaXZvIGVuIHDDrXhlbGVzLAoqIG5yb3csIG5jb2w6IG7Dum1lcm8gZGUgZmlsYXMgeSBjb2x1bW5hcyBlbiBsb3MgZGF0b3MsCiogbmNlbGxzOiBuw7ptZXJvIHRvdGFsIGRlIHDDrXhlbGVzIChjZWxkYXMpIHF1ZSBjb21wb25lbiBlbCByw6FzdGVyLAoqIFJlc29sdXRpb246IHRhbWHDsW8gZGUgY2FkYSBww614ZWwgKGdyYWRvcywgcGFyYSBlc3RlIGNhc28pICgxIGdyYWRvIGRlY2ltYWwgcmVwcmVzZW50YSB+MTExLDExIGttKSwKKiBleHRlbnQ6IGV4dGVuc2nDs24gZXNwYWNpYWwgZGVsIHLDoXN0ZXIgKG1pc21hcyB1bmlkYWRlcyBkZSBjb29yZGVuYWRhcyBxdWUgZWwgc2lzdGVtYSBkZSByZWZlcmVuY2lhIGRlIGNvb3JkZW5hZGFzIGRlbCByw6FzdGVyKSwKKiBjcnM6IGNhZGVuYSBkZWwgc2lzdGVtYSBkZSByZWZlcmVuY2lhIGRlIGNvb3JkZW5hZGFzIHBhcmEgZWwgcsOhc3RlciAocGFyYSBlc3RlIGNhc28gZXMgV0dTIDg0KS4KCmBgYHtyfQpwbG90KGVsZXZhdGlvbiwgbWFpbj0iREVNIGRlIFJpb25lZ3JvIChtZXRyb3MpIikKcGxvdChyaW9uZWdybywgYWRkPVRSVUUpCmBgYAoKIyMgKiozLiBBanVzdGFyIGRhdG9zIGRlIGVsZXZhY2nDs24gc2Vnw7puIGxhIGV4dGVuc2nDs24gZGVsIMOhcmVhIGRlIGVzdHVkaW8qKgpNZWRpYW50ZSBlbCBzaWd1aWVudGUgY8OzZGlnbyBzZSBwb2RyYSBjdWJyaXIgc29sYW1lbnRlIGxhIGV4dGVuc2nDs24gZGVsIG11bmljaXBpbyBzZWxlY2Npb25hZG8sIGVuIGVzdGUgY2FzbyBSaW9uZWdybwoKRGUgdG9kb3MgbW9kb3MsIGVzIHVuYSBidWVuYSBpZGVhIGd1YXJkYXIgZWwgREVNIHBhcmEgZWwgZnV0dXJvLgpgYGB7cn0KZWxldl9jcm9wID0gY3JvcChlbGV2YXRpb24sIHJpb25lZ3JvKQpwbG90KGVsZXZfY3JvcCwgbWFpbj0iREVNIGRlIFJpb25lZ3JvIGFqdXN0YWRvIikKcGxvdChyaW9uZWdybywgYWRkPVRSVUUpCmBgYApSZXZpc2Vtb3MgZWwgbnVldm8gb2JqZXRvCmBgYHtyfQplbGV2X2Nyb3AKYGBgCiMjICoqNC4gUmVwcm95ZWN0YXIgbG9zIGRhdG9zIGRlIGVsZXZhY2nDs24qKgpDdWFuZG8gdHJhYmFqYW1vcyBjb24gREVNIGVzIGFjb25zZWphYmxlIHV0aWxpemFyIGNvb3JkZW5hZGFzIGRlIG1hcGEgZW4gbHVnYXIgZGUgY29vcmRlbmFkYXMgZ2VvZ3LDoWZpY2FzLCBkZWJpZG8gYSBxdWUgZW4gbGFzIGNvb3JkZW5hZGFzIGdlb2dyw6FmaWNhcyBsYXMgdW5pZGFkZXMgZGUgZGltZW5zaW9uZXMgaG9yaXpvbnRhbGVzIHNvbiBncmFkb3MgZGVjaW1hbGVzLCBwZXJvIGxhIHVuaWRhZCBkZSBkaW1lbnNpw7NuIHZlcnRpY2FsIHNvbiBtZXRyb3MuCgpQb2RlbW9zIGlyIGEgZXBzZy5pbyB5IGJ1c2NhciBsYSBwcm95ZWNjacOzbiBkZSBsYSB6b25hIE1BR05BIENvbG9tYmlhIEJvZ290w6EuIE5lY2VzaXRhbW9zIG9idGVuZXIgbGEgZGVmaW5pY2nDs24gZGUgZXN0YSByZWZlcmVuY2lhIGVzcGFjaWFsIGVuIGZvcm1hdG8gUFJPSi40IChlbCBxdWUgc2UgdXNhIHBhcmEgbGFzIGJpYmxpb3RlY2FzIHNwIHkgcsOhc3Rlci4gQ29waWVtb3MgZWwgdGV4dG8gUFJPSi40IHkgZ3XDoXJkZWxvKQpgYGB7cn0Kc3BhdGlhbHJlZiA8LSAiK3Byb2o9dG1lcmMgK2xhdF8wPTQuNTk2MjAwNDE2NjY2NjY2ICtsb25fMD0tNzQuMDc3NTA3OTE2NjY2NjYgCitrPTEgK3hfMD0xMDAwMDAwICt5XzA9MTAwMDAwMCArZWxscHM9R1JTODAgK3Rvd2dzODQ9MCwwLDAsMCwwLDAsMCArdW5pdHM9bSAKK25vX2RlZnMiCmBgYAoKQWhvcmEsIHBvZGVtb3MgcmVwcm95ZWN0YXIgbG9zIGRhdG9zIGRlIGVsZXZhY2nDs24gZGUgbGFzIGNvb3JkZW5hZGFzIGdlb2dyw6FmaWNhcyBXR1M4NCBlbiBsYSB6b25hIE1BR05BIENvbG9tYmlhIEJvZ290w6EuCmBgYHtyfQpwcjMgPC0gcHJvamVjdEV4dGVudChlbGV2X2Nyb3AsIHNwYXRpYWxyZWYpCnJlcyhwcjMpIDwtIDEwMApyZXBfZWxldiA8LSBwcm9qZWN0UmFzdGVyKGVsZXZfY3JvcCwgcHIzKQpgYGAKCk9ic2VydmVtb3MgcXVlIGhheSBlbiBlbCBvYmpldG8gX3JlcF9lbGV2XwpgYGB7cn0KcmVwX2VsZXYKYGBgCkFob3JhLCB2b2x2YW1vcyBhIHByb3llY3RhciBlbCBTcGF0aWFsUG9seWdvbnNEYXRhRnJhbWUgcXVlIHJlcHJlc2VudGEgZWwgbXVuaWNpcGlvIGRlIFJpb25lZ3JvOgpgYGB7cn0KKHJlcF9yaW9uZWdybyA9IHNwVHJhbnNmb3JtKHJpb25lZ3JvLHNwYXRpYWxyZWYpKQpgYGAKVmlzdWFsaXphY2nDs24gZGVsIERFTSByZXByb3llY3RhZG8KYGBge3J9CnBsb3QocmVwX2VsZXYsIG1haW49Ik1vZGVsbyBkZSBlbGV2YWNpw7NuIGRpZ2l0YWwgcmVwcm95ZWN0YWRvIikKcGxvdChyZXBfcmlvbmVncm8sIGFkZD1UUlVFKQpgYGAKCiMjICoqNS4gRXN0YWTDrXN0aWNhcyBiw6FzaWNhcyBkZSBkYXRvcyBkZSBlbGV2YWNpw7NuKioKClJlYWxpemFyIHVuYSBleHBsb3JhY2nDs24gYmFzaWNhIGRlIGxvcyBkYXRvcyBkZSBlbGV2YWNpw7NuIGRlIFJpb25lZ3JvIGNvbW8gc3UgZGlzdHJpYnVjacOzbiwgbWVkaWEsIGRlc3ZpYWNpb25lcy4KYGBge3J9Cmhpc3QocmVwX2VsZXYsIG1haW49Ikhpc3RvZ3JhbWEgZGUgZGF0b3MgZGUgZWxldmFjacOzbiIsIGNvbD0gImdyZWVuIikKYGBgCgpgYGB7cn0KcHJvbWVkaW8gPC0gY2VsbFN0YXRzKHJlcF9lbGV2LCAnbWVhbicpCm1pbmltbyA8LSBjZWxsU3RhdHMocmVwX2VsZXYsICdtaW4nKQptYXhpbW8gPC0gY2VsbFN0YXRzKHJlcF9lbGV2LCAnbWF4JykKZGVzdmlhY2lvbiAgPC0gY2VsbFN0YXRzKHJlcF9lbGV2LCAnc2QnKQpgYGAKCmBgYHtyfQptZXRyaWNhcyA8LSBjKCdtZWFuJywgJ21pbicsICdtYXgnLCAnc3RkJykKdmFsb3JlcyA8LSBjKHByb21lZGlvLCBtaW5pbW8sIG1heGltbywgZGVzdmlhY2lvbikKYGBgCgpgYGB7cn0KKGRmX2VzdGFkaXN0aWNhcyA8LSBkYXRhLmZyYW1lKG1ldHJpY2FzLCB2YWxvcmVzKSkKYGBgCgoKIyMgKio2LiBPYnRlbmNpw7NuIGRlIHZhcmlhYmxlcyBnZW9tb3Jmb23DqXRyaWNhcyoqClByaW1lcm8sIGNhbGN1bGUgbGEgcGVuZGllbnRlLCBlbCBhc3BlY3RvIHkgZWwgc29tYnJlYWRvLgpgYGB7cn0Kc2xvcGUgPSB0ZXJyYWluKHJlcF9lbGV2LG9wdD0nc2xvcGUnLCB1bml0PSdkZWdyZWVzJykKYXNwZWN0ID0gdGVycmFpbihyZXBfZWxldixvcHQ9J2FzcGVjdCcsdW5pdD0nZGVncmVlcycpCmhpbGwgPSBoaWxsU2hhZGUoc2xvcGUsYXNwZWN0LDQwLDMxNSkKYGBgCgpWaXN1YWxpemFjacOzbiBkZWwgREVNCmBgYHtyfQpwbG90KHJlcF9lbGV2LG1haW49IkRFTSBwYXJhIFJpb25lZ3JvIFttZXRyb3NdIiwKICAgICBjb2w9dGVycmFpbi5jb2xvcnMoMjUsYWxwaGE9MC43KSkKYGBgCgpWaXN1YWxpemFjacOzbiBkZSBsYSBwZW5kaWVudGUgIApgYGB7cn0KcGxvdChzbG9wZSxtYWluPSJQZW5kaWVudGUgcGFyYSBSaW9uZWdybyBbZ3JhZG9zXSIsIAogICAgIGNvbD10b3BvLmNvbG9ycygyNSxhbHBoYT0wLjcpKQpgYGAKClZpc3VhbGl6YWNpw7NuIGRlIGxhIG9yaWVudGFjacOzbgpgYGB7cn0KcGxvdChhc3BlY3QsbWFpbj0iT3JpZW50YWNpw7NuIHBhcmEgUmlvbmVncm8gW2dyYWRvc10iLCAKICAgICBjb2w9cmFpbmJvdygyNSxhbHBoYT0wLjcpKQpgYGAKClZpc3VhbGl6YWNpw7NuIGNvbWJpbmFkYSBkZWwgc29tYnJlYWRvIHkgZWwgREVNCmBgYHtyfQpwbG90KGhpbGwsCiAgICAgY29sPWdyYXkoMToxMDAvMTAwKSwgIAogICAgIGxlZ2VuZD1GQUxTRSwgICAgICAKICAgICBtYWluPSJERU0gZm9yIE1lZGVsbGluIiwKICAgICBheGVzPUZBTFNFKSAgICAgICAgICAgCnBsb3QocmVwX2VsZXYsIAogICAgIGF4ZXM9RkFMU0UsCiAgICAgY29sPXRlcnJhaW4uY29sb3JzKDEyLCBhbHBoYT0wLjM1KSwgYWRkPVRSVUUpIApwbG90KHJlcF9yaW9uZWdybywgYWRkPVRSVUUpCmBgYAojIyAqKjcuIE1hcGVvIGRlIGRhdG9zIGRlIGVsZXZhY2nDs24gY29uIHJheXNoYWRlcioqCgpQYXJhIHByb2R1Y2lyIHZpc3VhbGl6YWNpb25lcyBkZSBkYXRvcyAyRCB5IDNEIGVuIFIgc2UgZW1wbGVhIGxhIGxpYnJlcsOtYSByYXlzaGFkZXIuIHJheXNoYWRlciB1c2EgZGF0b3MgZGUgZWxldmFjacOzbiBlbiB1bmEgbWF0cml6IFIgYmFzZSB5IHVuYSBjb21iaW5hY2nDs24gZGUgdHJhemFkbyBkZSByYXlvcywgbWFwZW8gZGUgdGV4dHVyYSBlc2bDqXJpY2EsIHN1cGVycG9zaWNpb25lcyB5IG9jbHVzacOzbiBhbWJpZW50YWwgcGFyYSBnZW5lcmFyIG1hcGFzIHRvcG9ncsOhZmljb3MgMkQgeSAzRCAuIEFkZW3DoXMgZGUgbG9zIG1hcGFzLCByYXlzaGFkZXIgdGFtYmnDqW4gcGVybWl0ZSBhbCB1c3VhcmlvIHRyYWR1Y2lyIG9iamV0b3MgZ2dwbG90MiBlbiB2aXN1YWxpemFjaW9uZXMgZGUgZGF0b3MgZW4gM0QuCgpgYGB7cn0KI2luc3RhbGwucGFja2FnZXMoInJheXNoYWRlciIpCmBgYApgYGB7cn0KbGlicmFyeShyYXlzaGFkZXIpCmBgYAoKYGBge3J9CmVsbWF0ID0gcmFzdGVyX3RvX21hdHJpeChyZXBfZWxldikKYGBgCmBgYHtyfQplbG1hdCAlPiUKICBzcGhlcmVfc2hhZGUodGV4dHVyZSA9ICJpbWhvZjIiKSAlPiUKICBwbG90X21hcCgpCmBgYAoKYGBge3J9CmVsbWF0ICU+JQogIHNwaGVyZV9zaGFkZSh0ZXh0dXJlID0gImRlc2VydCIpICU+JQogIGFkZF93YXRlcihkZXRlY3Rfd2F0ZXIoZWxtYXQpLCBjb2xvciA9ICJkZXNlcnQiKSAlPiUKICBwbG90X21hcCgpCmBgYAoKYGBge3J9CmVsbWF0ICU+JQogIHNwaGVyZV9zaGFkZSh0ZXh0dXJlID0gImRlc2VydCIpICU+JQogIGFkZF93YXRlcihkZXRlY3Rfd2F0ZXIoZWxtYXQpLCBjb2xvciA9ICJkZXNlcnQiKSAlPiUKICBhZGRfc2hhZG93KHJheV9zaGFkZShlbG1hdCksIDAuNSkgJT4lCiAgcGxvdF9tYXAoKQpgYGAKCiMjICoqOC4gT3RyYSBmb3JtYSBkZSB2aXN1YWxpemFjacOzbioqCgpBbnRlcyBkZSBpbmNpYXIgaW5zdGFsZSBsYSBsaWJyZXLDrWEgX2pwZWdfCgpgYGB7cn0KI2luc3RhbGwucGFja2FnZXMoImpwZWciKQpgYGAKYGBge3J9CmxpYnJhcnkoanBlZykKYGBgCgpgYGB7cn0KZ2V0dj1mdW5jdGlvbihpLGEscyl7CiAgY3QgPSBkaW0oaSlbMToyXS8yCiAgc3ggPSB2YWx1ZXMocykvOTAgKiBjdFsxXQogIHN5ID0gdmFsdWVzKHMpLzkwICogY3RbMl0KICBhID0gdmFsdWVzKGEpICogMC4wMTc0NQogIHB4ID0gZmxvb3IoY3RbMV0gKyBzeCAqIC1zaW4oYSkpCiAgcHkgPSBmbG9vcihjdFsyXSArIHN5ICogY29zKGEpKQogIAogIAogIHRlbXBsYXRlID0gYnJpY2socyxzLHMpCiAgdmFsdWVzKHRlbXBsYXRlKT1OQQogIAogIGNlbGxyID0gcHggKyBweSAqIGN0WzFdKjIKICBjZWxsZyA9IHB4ICsgcHkgKiBjdFsxXSoyICsgKGN0WzFdKjIqY3RbMl0qMikKICBjZWxsYiA9IHB4ICsgcHkgKiBjdFsxXSoyICsgMiooY3RbMV0qMipjdFsyXSoyKQogIAogIHRlbXBsYXRlW1sxXV0gPSBpW2NlbGxyXQogIHRlbXBsYXRlW1syXV0gPSBpW2NlbGxnXQogIHRlbXBsYXRlW1szXV0gPSBpW2NlbGxiXQogIAogIHRlbXBsYXRlID0gdGVtcGxhdGUgKiAyNTYKICAKICB0ZW1wbGF0ZQp9CmBgYAoKYGBge3J9Cm1hcD1yZWFkSlBFRygiOXB2YkhqTi5qcGciKQpvdXQgPSBnZXR2KG1hcCwgYXNwZWN0LCBzbG9wZSkKcGxvdFJHQihvdXQpCmBgYAoKCgo=