Angie Alejandra Juyo Buitrago

2 de abril de 2020

1.Introducción

Este es un cuaderno R donde se Ilustra varias funcionalidades para obtener, procesar y visualizar modelos digitales de elevación en R. Es realizado para el curso de geomatica básica de la Universidad Nacional de Colombia, Sede Bogotá

2.El paquete elevatr

El paquete elevatr fue escrito para estandarizar el acceso a los datos de elevación desde las APIs (interfaz de programaci?n de aplicaciones) de la web.Pues , de forma anterior el acceso a estos datos en R no tenia una sola interfaz, pues se precisaba el uso de funciones en muchos paquetes, o requieria el acceso local a los datos.

Los datos de elevación se utilizan para una amplia gama de aplicaciones, entre ellas, por ejemplo, la visualización, la hidrología y la modelización ecológica.

Para acceder a los datos de elevación rasterizados (por ejemplo, un DEM) el paquete elevatr utiliza Amazon Web Services. Exploraremos esta funcionalidad en este cuaderno.

Primero , instalamos el paquete elevatr junto a los otros paquetes que van a ser utilizados en el desarrollo de este cuaderno

# install.packages("rgdal")
# install.packages("raster")
# install.packages("elevatr")
#install.packages("rasterVis")
#install.packages("rgl")

Luego , procedemos a cargar las librerias necesarias

library(rgdal)
rgdal: version: 1.4-8, (SVN revision 845)
 Geospatial Data Abstraction Library extensions to R successfully loaded
 Loaded GDAL runtime: GDAL 2.2.3, released 2017/11/20
 Path to GDAL shared files: C:/Users/LUISA CARRION/Documents/R/win-library/3.6/rgdal/gdal
 GDAL binary built with GEOS: TRUE 
 Loaded PROJ.4 runtime: Rel. 4.9.3, 15 August 2016, [PJ_VERSION: 493]
 Path to PROJ.4 shared files: C:/Users/LUISA CARRION/Documents/R/win-library/3.6/rgdal/proj
 Linking to sp version: 1.4-1 
library(raster)
library(elevatr)
library(rasterVis)
Loading required package: lattice
Loading required package: latticeExtra
library(rgl)

3. Obtener los datos de elevación de Raster

Hay varias fuentes para los modelos de elevación digital como la Shuttle Radar Topography Mission (SRTM),el USGS National Elevation Dataset (NED), Global DEM (GDEM), y otras.

Cada uno de estos DEM tiene ventajas y desventajas para su uso. Antes de su cierre en enero de 2018, Mapzen combinó varias de estas fuentes para crear un producto de elevación de síntesis que utiliza los mejores datos de elevaci?n disponibles para una región determinada a un nivel de zoom determinado. Aunque cerrado, estos datos compilados por Mapzen son actualmente accesibles a través de Amazon Web Services (AWS).

Para comenzar con la exploración de estas funciones,primero, necesitamos cargar un archivo de forma que represente los municipios de nuestro departamento.

En este cuaderno, cargaremos un shapefile descargado del geoportal del DANE ,que contiene datos correspondientes al departamento de Vichada

Primero, podemos ver que contiene la carpeta donde fueron descargados los datos

list.files("C:/Users/LUISA CARRION/Documents/99_VICHADA/ADMINISTRATIVO")
 [1] "MGN_DPTO_POLITICO.cpg"     "MGN_DPTO_POLITICO.dbf"     "MGN_DPTO_POLITICO.prj"    
 [4] "MGN_DPTO_POLITICO.sbn"     "MGN_DPTO_POLITICO.sbx"     "MGN_DPTO_POLITICO.shp"    
 [7] "MGN_DPTO_POLITICO.shp.xml" "MGN_DPTO_POLITICO.shx"     "MGN_MPIO_POLITICO.cpg"    
[10] "MGN_MPIO_POLITICO.dbf"     "MGN_MPIO_POLITICO.prj"     "MGN_MPIO_POLITICO.sbn"    
[13] "MGN_MPIO_POLITICO.sbx"     "MGN_MPIO_POLITICO.shp"     "MGN_MPIO_POLITICO.shp.xml"
[16] "MGN_MPIO_POLITICO.shx"    

Luego leemos nuestro shapefile con ayuda de una de las funciones del paquete ráster

(munic <-  shapefile("C:/Users/LUISA CARRION/Documents/99_VICHADA/ADMINISTRATIVO/MGN_MPIO_POLITICO.shp"))
class       : SpatialPolygonsDataFrame 
features    : 4 
extent      : -71.07793, -67.4098, 2.737109, 6.324317  (xmin, xmax, ymin, ymax)
crs         : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
variables   : 9
names       : DPTO_CCDGO, MPIO_CCDGO,     MPIO_CNMBR,                           MPIO_CRSLC,    MPIO_NAREA, MPIO_NANO, DPTO_CNMBR,    Shape_Leng,     Shape_Area 
min values  :         99,      99001,       CUMARIBO,       Decreto 1594 de Ago 5  de 1974, 3898.56891769,      2017,    VICHADA, 3.29670807195, 0.316732435778 
max values  :         99,      99773, SANTA ROSALÍA, Ordenanza 66 de Noviembre 22 de 1996, 65599.7022767,      2017,    VICHADA,  18.794382661,   5.3085802966 

Una vez leidos ,podemos ver , cuales son los atributos de nuestro objeto, con ayuda de la función “head”

head(munic)

Luego, seleccionamos solo la capital de nuestro departamento

Pto_Carreno <- munic[munic$MPIO_CNMBR=="PUERTO CARREÑO",]
plot(Pto_Carreno, main="Puerto Carreño", axes=TRUE)
plot(munic, add=TRUE)
invisible(text(coordinates(munic), labels=as.character(munic$MPIO_CNMBR), cex=0.8))

3.1Usar get_elev_raster() para acceder a los Terrain Tiles en AWS.

La entrada para get_elev_raster() es un data.frame con ubicaciones x(longitud) e y(latitud) como las dos primeras columnas, cualquier objeto sp o cualquier objeto r?ster y devuelve un RasterLayer de los mosaicos que se superponen al cuadro delimitador de la entrada. Si se recuperan m?ltiples mosaicos, la salida resultante sar? una capa r?ster fusionada.

Como se mencionó, un data frame con columnas xey, un objeto sp o un raster debe ser la entrada y src debe establecerse en “mapzen” (este es el valor predeterminado).

No hay diferencia en el uso de las sp y raster de entrada de tipos de datos. El marco de datos requiere a prj.

El nivel de zoom determina la resolución del raster de salida y su valor predeterminado es 9 (Pues tiene una buena resolución y no es mucho el tiempo de descarga)

elevation <- get_elev_raster(Pto_Carreno, z = 8)

Luego,podemos ver que está contenido en el objeto “Elevacion”, obtendremos información como dimensiones: el “tamaño” del archivo en píxeles, numero de filas, numero de columnas ,el número total de celdas que componen el raster inclusive podemos obtener información de la referencia de sistema de coordenadas para el ráster

elevation
class      : RasterLayer 
dimensions : 2570, 2567, 6597190  (nrow, ncol, ncell)
resolution : 0.00275, 0.00274  (x, y)
extent     : -71.7325, -64.67325, -0.01443207, 7.027368  (xmin, xmax, ymin, ymax)
crs        : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
source     : memory
names      : layer 
values     : -419.5286, 2829.102  (min, max)

Podemos trazar los datos de elevacion del objeto de la siguiente manera

plot(elevation, main=" DEM [metros]")

plot(Pto_Carreno, add=TRUE)

4.Recortar los datos de elevación para que coincidan con el área de estudio

Como observamos, el DEM cubre una extensión mayor que la que necesitamos. Esto se debe a la disposición espacial de los mosaicos de elevación en AWS. De todos modos,antes de recortar es una buena idea guardar el DEM para el futuro.

writeRaster(elevation, filename = "Aqui iria la ruta de mi computador", dataType='INT4S', overwrite=TRUE)
argument "datatype" misspelled as "dataType"
class      : RasterLayer 
dimensions : 2570, 2567, 6597190  (nrow, ncol, ncell)
resolution : 0.00275, 0.00274  (x, y)
extent     : -71.7325, -64.67325, -0.01443207, 7.027368  (xmin, xmax, ymin, ymax)
crs        : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
source     : C:/Users/LUISA CARRION/Downloads/Aqui iria la ruta de mi computador.grd 
names      : layer 
values     : -420, 2829  (min, max)

Y recortamos nuestros datos correspondientes al departamento de Vichada

elev_crop = crop(elevation, Pto_Carreno)
plot(elev_crop, main="Modelo de elevación recortado")

plot(Pto_Carreno, add=TRUE)

Luego, vemos el nuevo objeto

elev_crop
class      : RasterLayer 
dimensions : 1309, 1334, 1746206  (nrow, ncol, ncell)
resolution : 0.00275, 0.00274  (x, y)
extent     : -71.078, -67.4095, 2.736528, 6.323188  (xmin, xmax, ymin, ymax)
crs        : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
source     : memory
names      : layer 
values     : -320.8194, 1803.522  (min, max)

5.Reproyectar los datos de elevación

Cuando se trabaja con DEM, siempre es una buena idea usar coordenadas de mapa en lugar de coordenadas geográficas. Esto se debe al hecho de que, en las coordenadas geográficas, las unidades de dimensiones horizontales son grados decimales pero la unidad de dimensi?n vertical son los metros.

Podemos ir a (epsg.io) y buscar la proyección “MAGNA- SIRGAS Colombia Bogota zone”. Necesitamos obtener la definición de esta referencia espacial en formato PROJ.4 ( Pues esta es la que se usa para las bibliotecas sp y raster ). Copiemamos el texto PROJ.4 y lo guardamos.

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 MAGNA Colombia Bogota zone

pr3 <- projectExtent(elev_crop, spatialref)
res(pr3) <- 100
rep_elev <- projectRaster(elev_crop, pr3)
rep_elev
class      : RasterLayer 
dimensions : 4010, 4111, 16485110  (nrow, ncol, ncell)
resolution : 100, 100  (x, y)
extent     : 1332035, 1743135, 794733, 1195733  (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     : -310.4869, 1802.343  (min, max)

Ahora, se va a reproyectar el SpatialPolygonsDataFrame Poligono con atributos que representa a Puerto Carreño:

(rep_Pto_Carreno = spTransform(Pto_Carreno,spatialref))

Después, trazamos el objeto obtenido

plot(rep_elev, main = "DEM reproyectado")

plot(rep_Pto_Carreno, add= TRUE)
Error in plot(rep_Pto_Carreno, add = TRUE) : 
  objeto 'rep_Pto_Carreno' no encontrado

Luego , guardamos nuestro DEM

writeRaster(rep_elev, filename = "Ac? va la ruta de archivo", dataType='INT4S', overwrite=TRUE)

6.Estadísticas básicas de los datos de elevación

Se hace una exploaración básica de las estadísticas del DEM

Primero,realizamos un histograma

hist(rep_elev)

Luego realizamos un dataframe con estadásticas de elevación en metros

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

Obtención de Variables geomorfomótricas

Primero, calcule la pendiente, el aspecto y el sombreado

La pendiente puede ser calculada con la función terrain de la libreria Raster, mientras que la función hillshade, calcula la sombra de una montaña o una colina a partir de la pendiente ,el aspecto el ángulo de elevación del sol en grados y “azimuth”" el ángulo dirección del sol en grados

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

Luego, trazamos elevación , pendiente y aspecto , fijandonos en las paletas de color que son usadas para cada uno de los ploteos

Elevación

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

Pendiente

plot(slope,main="Pendiente para Puerto Carreño [Grados]", col=topo.colors(25,alpha=0.7))

Aspecto

plot(aspect,main="Aspecto para Puerto Carreño [Grados]", col=rainbow(25,alpha=0.7))

También ,podemos hacer un plot combinado

plot (hill, col= grey(1:100/100), legend= FALSE, main = "DEM para Puerto Carreño",  axes= FALSE)
plot(rep_elev, axes= FALSE,  col= terrain.colors(12, alpha = 0.35), add= TRUE)

##7. Mapeo de datos de elevación con rayshader.

La librería rayshader es un paquete de código abierto para producir visualizaciones de datos 2D y 3D en R. Rayshader utiliza datos de elevaci?n en una matriz base de R y una combinación de trazado de rayos, mapeo de textura esf?rica, superposiciones y oclusi?n ambiental para generar mapas topográficos en 2D y 3D.

Además de los mapas, rayshader también permite al usuario traducir objetos ggplot2 en visualizaciones de datos 3D.

Instalamos y cargamos la libreria

#install.packages("rayshader")
library(rayshader)

Primero , convertimos nuestro DEM , en una matriz

elmat = raster_to_matrix(rep_elev)
[1] "Dimensions of matrix are: 4111x4010."

Podemos asignar o incorporar una textura RGB a un sombreado mediante un mapeo esférico con la función “Sphere_Shade”

elmat %>%
  sphere_shade(texture = "imhof2") %>%
  plot_map()

Podemos usar “detect_water” para detectar cuerpos de agua ( Rayshader usa un algoritmo de inundación para este fin ) con la función para agregar un color de agua al mapa

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

También podemos agregar una capa de trazado de rayos desde esa dirección del sol

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

8.Otra forma de visualización

Un experto en R, sugiere otra forma de visualización aqui

#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("ACA IRIA UNA RUTA DE ARCHIVO") 
out = getv(map, aspect, slope)
plotRGB(out, main = "Montañas en Vichada")
LS0tDQp0aXRsZTogIkRhdG9zIGRlIGVsZXZhY2lvbiBlbiBSIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KQW5naWUgQWxlamFuZHJhIEp1eW8gQnVpdHJhZ28NCg0KKjIgZGUgYWJyaWwgZGUgMjAyMCoNCg0KDQojIyAxLkludHJvZHVjY2nDs24gDQoNCkVzdGUgZXMgdW4gY3VhZGVybm8gUiBkb25kZSBzZSBJbHVzdHJhIHZhcmlhcyBmdW5jaW9uYWxpZGFkZXMgcGFyYSBvYnRlbmVyLCBwcm9jZXNhciB5IHZpc3VhbGl6YXIgbW9kZWxvcyBkaWdpdGFsZXMgZGUgZWxldmFjacOzbiBlbiBSLiBFcyByZWFsaXphZG8gcGFyYSBlbCBjdXJzbyBkZSBnZW9tYXRpY2EgYsOhc2ljYSBkZSBsYSBVbml2ZXJzaWRhZCBOYWNpb25hbCBkZSBDb2xvbWJpYSwgU2VkZSBCb2dvdMOhDQoNCg0KIyMgMi5FbCBwYXF1ZXRlIGVsZXZhdHINCg0KRWwgcGFxdWV0ZSBlbGV2YXRyIGZ1ZSBlc2NyaXRvIHBhcmEgZXN0YW5kYXJpemFyIGVsIGFjY2VzbyBhIGxvcyBkYXRvcyBkZSBlbGV2YWNpw7NuIGRlc2RlIGxhcyBBUElzIChpbnRlcmZheiBkZSBwcm9ncmFtYWNpP24gZGUgYXBsaWNhY2lvbmVzKSBkZSBsYSB3ZWIuUHVlcyAsIGRlIGZvcm1hIGFudGVyaW9yIGVsIGFjY2VzbyBhIGVzdG9zIGRhdG9zIGVuIFIgbm8gdGVuaWEgdW5hIHNvbGEgaW50ZXJmYXosIHB1ZXMgc2UgcHJlY2lzYWJhIGVsIHVzbyBkZSBmdW5jaW9uZXMgZW4gbXVjaG9zIHBhcXVldGVzLCBvIHJlcXVpZXJpYSBlbCBhY2Nlc28gbG9jYWwgYSBsb3MgZGF0b3MuDQoNCkxvcyBkYXRvcyBkZSBlbGV2YWNpw7NuIHNlIHV0aWxpemFuIHBhcmEgdW5hIGFtcGxpYSBnYW1hIGRlIGFwbGljYWNpb25lcywgZW50cmUgZWxsYXMsIHBvciBlamVtcGxvLCBsYSB2aXN1YWxpemFjacOzbiwgbGEgaGlkcm9sb2fDrWEgeSBsYSBtb2RlbGl6YWNpw7NuIGVjb2zDs2dpY2EuICANCg0KDQpQYXJhIGFjY2VkZXIgYSBsb3MgZGF0b3MgZGUgZWxldmFjacOzbiByYXN0ZXJpemFkb3MgKHBvciBlamVtcGxvLCB1biBERU0pIGVsIHBhcXVldGUgZWxldmF0ciB1dGlsaXphIEFtYXpvbiBXZWIgU2VydmljZXMuIEV4cGxvcmFyZW1vcyBlc3RhIGZ1bmNpb25hbGlkYWQgZW4gZXN0ZSBjdWFkZXJuby4NCg0KDQoNClByaW1lcm8gLCBpbnN0YWxhbW9zIGVsIHBhcXVldGUgZWxldmF0ciBqdW50byBhIGxvcyBvdHJvcyBwYXF1ZXRlcyBxdWUgdmFuIGEgc2VyIHV0aWxpemFkb3MgZW4gZWwgZGVzYXJyb2xsbyBkZSBlc3RlIGN1YWRlcm5vDQoNCg0KYGBge3J9DQojIGluc3RhbGwucGFja2FnZXMoInJnZGFsIikNCiMgaW5zdGFsbC5wYWNrYWdlcygicmFzdGVyIikNCiMgaW5zdGFsbC5wYWNrYWdlcygiZWxldmF0ciIpDQojaW5zdGFsbC5wYWNrYWdlcygicmFzdGVyVmlzIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJyZ2wiKQ0KYGBgDQoNCg0KTHVlZ28gLCBwcm9jZWRlbW9zIGEgY2FyZ2FyIGxhcyBsaWJyZXJpYXMgbmVjZXNhcmlhcw0KDQoNCmBgYHtyfQ0KbGlicmFyeShyZ2RhbCkNCmxpYnJhcnkocmFzdGVyKQ0KbGlicmFyeShlbGV2YXRyKQ0KbGlicmFyeShyYXN0ZXJWaXMpDQpsaWJyYXJ5KHJnbCkNCg0KYGBgDQoNCg0KIyMgMy4gT2J0ZW5lciBsb3MgZGF0b3MgZGUgZWxldmFjacOzbiBkZSBSYXN0ZXINCg0KSGF5IHZhcmlhcyBmdWVudGVzIHBhcmEgbG9zIG1vZGVsb3MgZGUgZWxldmFjacOzbiBkaWdpdGFsIGNvbW8gbGEgU2h1dHRsZSBSYWRhciBUb3BvZ3JhcGh5IE1pc3Npb24gKFNSVE0pLGVsIFVTR1MgTmF0aW9uYWwgRWxldmF0aW9uIERhdGFzZXQgKE5FRCksIEdsb2JhbCBERU0gKEdERU0pLCB5IG90cmFzLiANCg0KDQpDYWRhIHVubyBkZSBlc3RvcyBERU0gdGllbmUgdmVudGFqYXMgeSBkZXN2ZW50YWphcyBwYXJhIHN1IHVzby4gQW50ZXMgZGUgc3UgY2llcnJlIGVuIGVuZXJvIGRlIDIwMTgsIE1hcHplbiBjb21iaW7DsyB2YXJpYXMgZGUgZXN0YXMgZnVlbnRlcyBwYXJhIGNyZWFyIHVuIHByb2R1Y3RvIGRlIGVsZXZhY2nDs24gZGUgc8OtbnRlc2lzIHF1ZSB1dGlsaXphIGxvcyBtZWpvcmVzIGRhdG9zIGRlIGVsZXZhY2k/biBkaXNwb25pYmxlcyBwYXJhIHVuYSByZWdpw7NuIGRldGVybWluYWRhIGEgdW4gbml2ZWwgZGUgem9vbSBkZXRlcm1pbmFkby4gQXVucXVlIGNlcnJhZG8sIGVzdG9zIGRhdG9zIGNvbXBpbGFkb3MgcG9yIE1hcHplbiBzb24gYWN0dWFsbWVudGUgYWNjZXNpYmxlcyBhIHRyYXbDqXMgZGUgQW1hem9uIFdlYiBTZXJ2aWNlcyAoQVdTKS4NCg0KUGFyYSBjb21lbnphciBjb24gbGEgZXhwbG9yYWNpw7NuIGRlIGVzdGFzIGZ1bmNpb25lcyxwcmltZXJvLCBuZWNlc2l0YW1vcyBjYXJnYXIgdW4gYXJjaGl2byBkZSBmb3JtYSBxdWUgcmVwcmVzZW50ZSBsb3MgbXVuaWNpcGlvcyBkZSBudWVzdHJvIGRlcGFydGFtZW50by4gDQoNCkVuIGVzdGUgY3VhZGVybm8sIGNhcmdhcmVtb3MgdW4gc2hhcGVmaWxlIGRlc2NhcmdhZG8gZGVsIGdlb3BvcnRhbCBkZWwgREFORSAscXVlIGNvbnRpZW5lIGRhdG9zIGNvcnJlc3BvbmRpZW50ZXMgYWwgZGVwYXJ0YW1lbnRvIGRlIFZpY2hhZGENCg0KUHJpbWVybywgcG9kZW1vcyB2ZXIgcXVlIGNvbnRpZW5lIGxhIGNhcnBldGEgZG9uZGUgZnVlcm9uIGRlc2NhcmdhZG9zIGxvcyBkYXRvcyANCg0KYGBge3J9DQpsaXN0LmZpbGVzKCJDOi9Vc2Vycy9MVUlTQSBDQVJSSU9OL0RvY3VtZW50cy85OV9WSUNIQURBL0FETUlOSVNUUkFUSVZPIikNCmBgYA0KDQoNCkx1ZWdvIGxlZW1vcyBudWVzdHJvIHNoYXBlZmlsZSBjb24gYXl1ZGEgZGUgdW5hIGRlIGxhcyBmdW5jaW9uZXMgZGVsIHBhcXVldGUgcsOhc3Rlcg0KDQpgYGB7cn0NCihtdW5pYyA8LSAgc2hhcGVmaWxlKCJDOi9Vc2Vycy9MVUlTQSBDQVJSSU9OL0RvY3VtZW50cy85OV9WSUNIQURBL0FETUlOSVNUUkFUSVZPL01HTl9NUElPX1BPTElUSUNPLnNocCIpKQ0KYGBgDQoNClVuYSB2ZXogbGVpZG9zICxwb2RlbW9zIHZlciAsIGN1YWxlcyBzb24gbG9zIGF0cmlidXRvcyBkZSBudWVzdHJvIG9iamV0bywgY29uIGF5dWRhIGRlIGxhIGZ1bmNpw7NuICJoZWFkIg0KDQoNCmBgYHtyfQ0KaGVhZChtdW5pYykNCmBgYA0KDQoNCkx1ZWdvLCBzZWxlY2Npb25hbW9zIHNvbG8gbGEgY2FwaXRhbCBkZSBudWVzdHJvIGRlcGFydGFtZW50bw0KDQoNCmBgYHtyfQ0KUHRvX0NhcnJlbm8gPC0gbXVuaWNbbXVuaWMkTVBJT19DTk1CUj09IlBVRVJUTyBDQVJSRcORTyIsXQ0KcGxvdChQdG9fQ2FycmVubywgbWFpbj0iUHVlcnRvIENhcnJlw7FvIiwgYXhlcz1UUlVFKQ0KcGxvdChtdW5pYywgYWRkPVRSVUUpDQppbnZpc2libGUodGV4dChjb29yZGluYXRlcyhtdW5pYyksIGxhYmVscz1hcy5jaGFyYWN0ZXIobXVuaWMkTVBJT19DTk1CUiksIGNleD0wLjgpKQ0KYGBgDQoNCiMjIyAzLjFVc2FyIGdldF9lbGV2X3Jhc3RlcigpIHBhcmEgYWNjZWRlciBhIGxvcyBUZXJyYWluIFRpbGVzIGVuIEFXUy4NCg0KDQpMYSBlbnRyYWRhIHBhcmEgZ2V0X2VsZXZfcmFzdGVyKCkgZXMgdW4gZGF0YS5mcmFtZSBjb24gdWJpY2FjaW9uZXMgeChsb25naXR1ZCkgZSB5KGxhdGl0dWQpIGNvbW8gbGFzIGRvcyBwcmltZXJhcyBjb2x1bW5hcywgY3VhbHF1aWVyIG9iamV0byBzcCBvIGN1YWxxdWllciBvYmpldG8gcj9zdGVyIHkgZGV2dWVsdmUgdW4gUmFzdGVyTGF5ZXIgZGUgbG9zIG1vc2FpY29zIHF1ZSBzZSBzdXBlcnBvbmVuIGFsIGN1YWRybyBkZWxpbWl0YWRvciBkZSBsYSBlbnRyYWRhLiBTaSBzZSByZWN1cGVyYW4gbT9sdGlwbGVzIG1vc2FpY29zLCBsYSBzYWxpZGEgcmVzdWx0YW50ZSBzYXI/IHVuYSBjYXBhIHI/c3RlciBmdXNpb25hZGEuDQoNCkNvbW8gc2UgbWVuY2lvbsOzLCB1biBkYXRhIGZyYW1lIGNvbiBjb2x1bW5hcyB4ZXksIHVuIG9iamV0byBzcCAgbyB1biByYXN0ZXIgZGViZSBzZXIgbGEgZW50cmFkYSB5IHNyYyBkZWJlIGVzdGFibGVjZXJzZSBlbiAibWFwemVuIiAoZXN0ZSBlcyBlbCB2YWxvciBwcmVkZXRlcm1pbmFkbykuIA0KDQpObyBoYXkgZGlmZXJlbmNpYSBlbiBlbCB1c28gZGUgbGFzIHNwIHkgcmFzdGVyIGRlIGVudHJhZGEgZGUgdGlwb3MgZGUgZGF0b3MuIEVsIG1hcmNvIGRlIGRhdG9zIHJlcXVpZXJlIGEgcHJqLiAgDQoNCkVsIG5pdmVsIGRlIHpvb20gZGV0ZXJtaW5hIGxhIHJlc29sdWNpw7NuIGRlbCByYXN0ZXIgZGUgc2FsaWRhIHkgc3UgdmFsb3IgcHJlZGV0ZXJtaW5hZG8gZXMgOSAoUHVlcyB0aWVuZSB1bmEgYnVlbmEgcmVzb2x1Y2nDs24geSBubyBlcyBtdWNobyBlbCB0aWVtcG8gZGUgZGVzY2FyZ2EpDQoNCg0KYGBge3J9DQplbGV2YXRpb24gPC0gZ2V0X2VsZXZfcmFzdGVyKFB0b19DYXJyZW5vLCB6ID0gOCkNCmBgYA0KDQoNCkx1ZWdvLHBvZGVtb3MgdmVyIHF1ZSBlc3TDoSBjb250ZW5pZG8gZW4gZWwgb2JqZXRvICJFbGV2YWNpb24iLCBvYnRlbmRyZW1vcyBpbmZvcm1hY2nDs24gY29tbyBkaW1lbnNpb25lczogZWwgInRhbWHDsW8iIGRlbCBhcmNoaXZvIGVuIHDDrXhlbGVzLCBudW1lcm8gZGUgZmlsYXMsIG51bWVybyBkZSBjb2x1bW5hcyAsZWwgbsO6bWVybyB0b3RhbCBkZSBjZWxkYXMgcXVlIGNvbXBvbmVuIGVsIHJhc3RlciBpbmNsdXNpdmUgcG9kZW1vcyBvYnRlbmVyIGluZm9ybWFjacOzbiBkZSBsYSByZWZlcmVuY2lhIGRlIHNpc3RlbWEgZGUgY29vcmRlbmFkYXMgcGFyYSBlbCByw6FzdGVyDQoNCmBgYHtyfQ0KZWxldmF0aW9uDQpgYGANCg0KUG9kZW1vcyB0cmF6YXIgbG9zIGRhdG9zIGRlIGVsZXZhY2lvbiBkZWwgb2JqZXRvIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmENCg0KYGBge3J9DQpwbG90KGVsZXZhdGlvbiwgbWFpbj0iIERFTSBbbWV0cm9zXSIpDQpwbG90KFB0b19DYXJyZW5vLCBhZGQ9VFJVRSkNCmBgYA0KDQojIyA0LlJlY29ydGFyIGxvcyBkYXRvcyBkZSBlbGV2YWNpw7NuIHBhcmEgcXVlIGNvaW5jaWRhbiBjb24gZWwgw6FyZWEgZGUgZXN0dWRpbw0KDQoNCkNvbW8gb2JzZXJ2YW1vcywgZWwgREVNIGN1YnJlIHVuYSBleHRlbnNpw7NuIG1heW9yIHF1ZSBsYSBxdWUgbmVjZXNpdGFtb3MuIEVzdG8gc2UgZGViZSBhIGxhIGRpc3Bvc2ljacOzbiBlc3BhY2lhbCBkZSBsb3MgbW9zYWljb3MgZGUgZWxldmFjacOzbiBlbiBBV1MuIERlIHRvZG9zIG1vZG9zLGFudGVzIGRlIHJlY29ydGFyIGVzIHVuYSBidWVuYSBpZGVhIGd1YXJkYXIgZWwgREVNIHBhcmEgZWwgZnV0dXJvLg0KDQoNCg0KYGBge3J9DQp3cml0ZVJhc3RlcihlbGV2YXRpb24sIGZpbGVuYW1lID0gIkFxdWkgaXJpYSBsYSBydXRhIGRlIG1pIGNvbXB1dGFkb3IiLCBkYXRhVHlwZT0nSU5UNFMnLCBvdmVyd3JpdGU9VFJVRSkNCmBgYA0KDQpZIHJlY29ydGFtb3MgbnVlc3Ryb3MgZGF0b3MgY29ycmVzcG9uZGllbnRlcyBhbCBkZXBhcnRhbWVudG8gZGUgVmljaGFkYQ0KDQpgYGB7cn0NCmVsZXZfY3JvcCA9IGNyb3AoZWxldmF0aW9uLCBQdG9fQ2FycmVubykNCnBsb3QoZWxldl9jcm9wLCBtYWluPSJNb2RlbG8gZGUgZWxldmFjacOzbiByZWNvcnRhZG8iKQ0KcGxvdChQdG9fQ2FycmVubywgYWRkPVRSVUUpDQpgYGANCg0KDQoNCkx1ZWdvLCB2ZW1vcyBlbCBudWV2byBvYmpldG8gDQoNCmBgYHtyfQ0KZWxldl9jcm9wDQpgYGANCg0KDQojIyA1LlJlcHJveWVjdGFyIGxvcyBkYXRvcyBkZSBlbGV2YWNpw7NuDQoNCkN1YW5kbyBzZSB0cmFiYWphIGNvbiBERU0sIHNpZW1wcmUgZXMgdW5hIGJ1ZW5hIGlkZWEgdXNhciBjb29yZGVuYWRhcyBkZSBtYXBhIGVuIGx1Z2FyIGRlIGNvb3JkZW5hZGFzIGdlb2dyw6FmaWNhcy4gRXN0byBzZSBkZWJlIGFsIGhlY2hvIGRlIHF1ZSwgZW4gbGFzIGNvb3JkZW5hZGFzIGdlb2dyw6FmaWNhcywgbGFzIHVuaWRhZGVzIGRlIGRpbWVuc2lvbmVzIGhvcml6b250YWxlcyBzb24gZ3JhZG9zIGRlY2ltYWxlcyBwZXJvIGxhIHVuaWRhZCBkZSBkaW1lbnNpP24gdmVydGljYWwgc29uIGxvcyBtZXRyb3MuIA0KDQpQb2RlbW9zIGlyIGEgKGVwc2cuaW8pIHkgYnVzY2FyIGxhIHByb3llY2Npw7NuICoiTUFHTkEtIFNJUkdBUyBDb2xvbWJpYSBCb2dvdGEgem9uZSIqLiBOZWNlc2l0YW1vcyBvYnRlbmVyIGxhIGRlZmluaWNpw7NuIGRlIGVzdGEgcmVmZXJlbmNpYSBlc3BhY2lhbCBlbiBmb3JtYXRvIFBST0ouNCAoIFB1ZXMgZXN0YSBlcyBsYSBxdWUgc2UgdXNhIHBhcmEgbGFzIGJpYmxpb3RlY2FzIHNwIHkgcmFzdGVyICkuIENvcGllbWFtb3MgZWwgdGV4dG8gUFJPSi40IHkgbG8gZ3VhcmRhbW9zLg0KDQoNCmBgYHtyfQ0Kc3BhdGlhbHJlZiA8LSAiK3Byb2o9dG1lcmMgK2xhdF8wPTQuNTk2MjAwNDE2NjY2NjY2ICtsb25fMD0tNzQuMDc3NTA3OTE2NjY2NjYgK2s9MSAreF8wPTEwMDAwMDAgK3lfMD0xMDAwMDAwICtlbGxwcz1HUlM4MCArdG93Z3M4ND0wLDAsMCwwLDAsMCwwICt1bml0cz1tICtub19kZWZzIiANCmBgYA0KDQoNCkFob3JhLCBwb2RlbW9zICoqcmVwcm95ZWN0YXIqKiBsb3MgZGF0b3MgZGUgZWxldmFjacOzbiBkZSBsYXMgY29vcmRlbmFkYXMgZ2VvZ3LDoWZpY2FzIFdHUzg0IGVuICpNQUdOQSBDb2xvbWJpYSBCb2dvdGEgem9uZSoNCg0KYGBge3J9DQpwcjMgPC0gcHJvamVjdEV4dGVudChlbGV2X2Nyb3AsIHNwYXRpYWxyZWYpDQpyZXMocHIzKSA8LSAxMDANCnJlcF9lbGV2IDwtIHByb2plY3RSYXN0ZXIoZWxldl9jcm9wLCBwcjMpDQpgYGANCg0KDQpgYGB7cn0NCnJlcF9lbGV2DQpgYGANCg0KDQpBaG9yYSwgc2UgdmEgYSByZXByb3llY3RhciBlbCBTcGF0aWFsUG9seWdvbnNEYXRhRnJhbWUgKlBvbGlnb25vIGNvbiBhdHJpYnV0b3MqIHF1ZSByZXByZXNlbnRhIGEgUHVlcnRvIENhcnJlw7FvOg0KDQpgYGB7cn0NCihyZXBfUHRvX0NhcnJlbm8gPSBzcFRyYW5zZm9ybShQdG9fQ2FycmVubyxzcGF0aWFscmVmKSkNCmBgYA0KDQoNCkRlc3B1w6lzLCB0cmF6YW1vcyBlbCBvYmpldG8gb2J0ZW5pZG8gDQoNCg0KDQpgYGB7cn0NCnBsb3QocmVwX2VsZXYsIG1haW4gPSAiREVNIHJlcHJveWVjdGFkbyIpDQpwbG90KHJlcF9QdG9fQ2FycmVubywgYWRkPSBUUlVFKQ0KYGBgDQoNCg0KDQpMdWVnbyAsIGd1YXJkYW1vcyBudWVzdHJvIERFTQ0KDQpgYGB7cn0NCndyaXRlUmFzdGVyKHJlcF9lbGV2LCBmaWxlbmFtZSA9ICJBYz8gdmEgbGEgcnV0YSBkZSBhcmNoaXZvIiwgZGF0YVR5cGU9J0lOVDRTJywgb3ZlcndyaXRlPVRSVUUpDQpgYGANCg0KDQoNCiMjIDYuRXN0YWTDrXN0aWNhcyBiw6FzaWNhcyBkZSBsb3MgZGF0b3MgZGUgZWxldmFjacOzbg0KDQoNClNlIGhhY2UgdW5hIGV4cGxvYXJhY2nDs24gYsOhc2ljYSBkZSBsYXMgZXN0YWTDrXN0aWNhcyBkZWwgREVNDQoNCg0KUHJpbWVybyxyZWFsaXphbW9zIHVuIGhpc3RvZ3JhbWENCg0KDQpgYGB7cn0NCmhpc3QocmVwX2VsZXYpDQpgYGANCg0KDQoNCkx1ZWdvIHJlYWxpemFtb3MgdW4gZGF0YWZyYW1lIGNvbiBlc3RhZMOhc3RpY2FzIGRlIGVsZXZhY2nDs24gZW4gbWV0cm9zDQoNCg0KYGBge3J9DQpwcm9tZWRpbyA8LSBjZWxsU3RhdHMocmVwX2VsZXYsICdtZWFuJykNCm1pbmltbyA8LSBjZWxsU3RhdHMocmVwX2VsZXYsICdtaW4nKQ0KbWF4aW1vIDwtIGNlbGxTdGF0cyhyZXBfZWxldiwgJ21heCcpDQpkZXN2aWFjaW9uICA8LSBjZWxsU3RhdHMocmVwX2VsZXYsICdzZCcpDQpgYGANCg0KYGBge3J9DQptZXRyaWNhcyA8LSBjKCdtZWFuJywgJ21pbicsICdtYXgnLCAnc3RkJykNCnZhbG9yZXMgPC0gYyhwcm9tZWRpbywgbWluaW1vLCBtYXhpbW8sIGRlc3ZpYWNpb24pDQpgYGANCg0KYGBge3J9DQooZGZfZXN0YWRpc3RpY2FzIDwtIGRhdGEuZnJhbWUobWV0cmljYXMsIHZhbG9yZXMpKQ0KYGBgDQoNCg0KIyMgT2J0ZW5jacOzbiBkZSBWYXJpYWJsZXMgZ2VvbW9yZm9tw7N0cmljYXMNCg0KUHJpbWVybywgY2FsY3VsZSBsYSBwZW5kaWVudGUsIGVsIGFzcGVjdG8geSBlbCBzb21icmVhZG8NCg0KTGEgcGVuZGllbnRlIHB1ZWRlIHNlciBjYWxjdWxhZGEgY29uIGxhIGZ1bmNpw7NuIHRlcnJhaW4gZGUgbGEgbGlicmVyaWEgUmFzdGVyLCBtaWVudHJhcyBxdWUgbGEgZnVuY2nDs24gaGlsbHNoYWRlLCBjYWxjdWxhIGxhIHNvbWJyYSBkZSB1bmEgbW9udGHDsWEgbyB1bmEgY29saW5hIGEgcGFydGlyIGRlIGxhIHBlbmRpZW50ZSAsZWwgYXNwZWN0byBlbCDDoW5ndWxvIGRlIGVsZXZhY2nDs24gZGVsIHNvbCBlbiBncmFkb3MgeSAiYXppbXV0aCIiIGVsIMOhbmd1bG8gZGlyZWNjacOzbiBkZWwgc29sIGVuIGdyYWRvcyANCg0KYGBge3J9DQpzbG9wZSA9IHRlcnJhaW4ocmVwX2VsZXYsIG9wdD0nc2xvcGUnLCB1bml0PSdkZWdyZWVzJykNCmFzcGVjdCA9IHRlcnJhaW4ocmVwX2VsZXYsIG9wdD0nYXNwZWN0JywgdW5pdD0nZGVncmVlcycpDQpoaWxsID0gaGlsbFNoYWRlKHNsb3BlLCBhc3BlY3QsIDQwLCAzMTUpDQpgYGANCg0KTHVlZ28sIHRyYXphbW9zIGVsZXZhY2nDs24gLCBwZW5kaWVudGUgeSBhc3BlY3RvICwgZmlqYW5kb25vcyBlbiBsYXMgcGFsZXRhcyBkZSBjb2xvciBxdWUgc29uIHVzYWRhcyBwYXJhIGNhZGEgdW5vIGRlIGxvcyBwbG90ZW9zDQoNCioqRWxldmFjacOzbioqDQoNCmBgYHtyfQ0KcGxvdChyZXBfZWxldiwgbWFpbj0gIkRFTSBwYXJhIFB1ZXJ0byBDYXJyZcOxb1ttZXRyb3NdIiwgY29sPSB0ZXJyYWluLmNvbG9ycygyNSwgYWxwaGEgPSAwLjcpKQ0KYGBgDQoNCg0KKipQZW5kaWVudGUqKg0KDQpgYGB7cn0NCnBsb3Qoc2xvcGUsbWFpbj0iUGVuZGllbnRlIHBhcmEgUHVlcnRvIENhcnJlw7FvIFtHcmFkb3NdIiwgY29sPXRvcG8uY29sb3JzKDI1LGFscGhhPTAuNykpDQpgYGANCg0KKipBc3BlY3RvKioNCg0KYGBge3J9DQpwbG90KGFzcGVjdCxtYWluPSJBc3BlY3RvIHBhcmEgUHVlcnRvIENhcnJlw7FvIFtHcmFkb3NdIiwgY29sPXJhaW5ib3coMjUsYWxwaGE9MC43KSkNCmBgYA0KDQpUYW1iacOpbiAscG9kZW1vcyBoYWNlciB1biBwbG90IGNvbWJpbmFkbyANCg0KYGBge3J9DQpwbG90IChoaWxsLCBjb2w9IGdyZXkoMToxMDAvMTAwKSwgbGVnZW5kPSBGQUxTRSwgbWFpbiA9ICJERU0gcGFyYSBQdWVydG8gQ2FycmXDsW8iLCAgYXhlcz0gRkFMU0UpDQpwbG90KHJlcF9lbGV2LCBheGVzPSBGQUxTRSwgIGNvbD0gdGVycmFpbi5jb2xvcnMoMTIsIGFscGhhID0gMC4zNSksIGFkZD0gVFJVRSkNCg0KYGBgDQoNCg0KIyM3LiBNYXBlbyBkZSBkYXRvcyBkZSBlbGV2YWNpw7NuIGNvbiByYXlzaGFkZXIuDQoNCg0KTGEgbGlicmVyw61hIHJheXNoYWRlciBlcyB1biBwYXF1ZXRlIGRlIGPDs2RpZ28gYWJpZXJ0byBwYXJhIHByb2R1Y2lyIHZpc3VhbGl6YWNpb25lcyBkZSBkYXRvcyAyRCB5IDNEIGVuIFIuDQpSYXlzaGFkZXIgdXRpbGl6YSBkYXRvcyBkZSBlbGV2YWNpP24gZW4gdW5hIG1hdHJpeiBiYXNlIGRlIFIgeSB1bmEgY29tYmluYWNpw7NuIGRlIHRyYXphZG8gZGUgcmF5b3MsIG1hcGVvIGRlIHRleHR1cmEgZXNmP3JpY2EsIHN1cGVycG9zaWNpb25lcyB5IG9jbHVzaT9uIGFtYmllbnRhbCBwYXJhIGdlbmVyYXIgbWFwYXMgdG9wb2dyw6FmaWNvcyBlbiAyRCB5IDNELiANCg0KQWRlbcOhcyBkZSBsb3MgbWFwYXMsIHJheXNoYWRlciB0YW1iacOpbiBwZXJtaXRlIGFsIHVzdWFyaW8gdHJhZHVjaXIgb2JqZXRvcyBnZ3Bsb3QyIGVuIHZpc3VhbGl6YWNpb25lcyBkZSBkYXRvcyAzRC4NCg0KDQoNCkluc3RhbGFtb3MgeSBjYXJnYW1vcyBsYSBsaWJyZXJpYQ0KDQpgYGB7cn0NCiNpbnN0YWxsLnBhY2thZ2VzKCJyYXlzaGFkZXIiKQ0KbGlicmFyeShyYXlzaGFkZXIpDQpgYGANCg0KUHJpbWVybyAsIGNvbnZlcnRpbW9zIG51ZXN0cm8gREVNICwgZW4gdW5hIG1hdHJpeg0KDQpgYGB7cn0NCmVsbWF0ID0gcmFzdGVyX3RvX21hdHJpeChyZXBfZWxldikNCmBgYA0KDQoNClBvZGVtb3MgYXNpZ25hciBvIGluY29ycG9yYXIgdW5hIHRleHR1cmEgUkdCIGEgdW4gc29tYnJlYWRvIG1lZGlhbnRlIHVuIG1hcGVvIGVzZsOpcmljbyBjb24gbGEgZnVuY2nDs24gIlNwaGVyZV9TaGFkZSINCg0KYGBge3J9DQplbG1hdCAlPiUNCiAgc3BoZXJlX3NoYWRlKHRleHR1cmUgPSAiaW1ob2YyIikgJT4lDQogIHBsb3RfbWFwKCkNCmBgYA0KDQoNClBvZGVtb3MgdXNhciAiZGV0ZWN0X3dhdGVyIiAgcGFyYSBkZXRlY3RhciBjdWVycG9zIGRlIGFndWEgKCBSYXlzaGFkZXIgdXNhIHVuIGFsZ29yaXRtbyBkZSBpbnVuZGFjacOzbiBwYXJhIGVzdGUgZmluICkgY29uIGxhIGZ1bmNpw7NuIHBhcmEgYWdyZWdhciB1biBjb2xvciBkZSBhZ3VhIGFsIG1hcGENCg0KDQpgYGB7cn0NCmVsbWF0ICU+JQ0KICBzcGhlcmVfc2hhZGUodGV4dHVyZSA9ICJkZXNlcnQiKSAlPiUNCiAgYWRkX3dhdGVyKGRldGVjdF93YXRlcihlbG1hdCksIGNvbG9yID0gImRlc2VydCIpICU+JQ0KICBwbG90X21hcCgpDQpgYGANCg0KDQpUYW1iacOpbiBwb2RlbW9zIGFncmVnYXIgdW5hIGNhcGEgZGUgdHJhemFkbyBkZSByYXlvcyBkZXNkZSBlc2EgZGlyZWNjacOzbiBkZWwgc29sDQoNCmBgYHtyfQ0KZWxtYXQgJT4lDQogIHNwaGVyZV9zaGFkZSh0ZXh0dXJlID0gImRlc2VydCIpICU+JQ0KICBhZGRfd2F0ZXIoZGV0ZWN0X3dhdGVyKGVsbWF0KSwgY29sb3IgPSAiZGVzZXJ0IikgJT4lDQogIGFkZF9zaGFkb3cocmF5X3NoYWRlKGVsbWF0KSwgMC41KSAlPiUNCiAgcGxvdF9tYXAoKQ0KYGBgDQoNCiMjIDguT3RyYSBmb3JtYSBkZSB2aXN1YWxpemFjacOzbg0KDQoNClVuIGV4cGVydG8gZW4gUiwgc3VnaWVyZSBvdHJhIGZvcm1hIGRlIHZpc3VhbGl6YWNpw7NuIGFxdWkNCg0KDQpgYGB7cn0NCiNpbnN0YWxsLnBhY2thZ2VzKCJqcGVnIikNCmxpYnJhcnkoanBlZykNCmBgYA0KDQoNCmBgYHtyfQ0KZ2V0dj1mdW5jdGlvbihpLGEscyl7DQogIGN0ID0gZGltKGkpWzE6Ml0vMg0KICBzeCA9IHZhbHVlcyhzKS85MCAqIGN0WzFdDQogIHN5ID0gdmFsdWVzKHMpLzkwICogY3RbMl0NCiAgYSA9IHZhbHVlcyhhKSAqIDAuMDE3NDUNCiAgcHggPSBmbG9vcihjdFsxXSArIHN4ICogLXNpbihhKSkNCiAgcHkgPSBmbG9vcihjdFsyXSArIHN5ICogY29zKGEpKQ0KICANCiAgDQogIHRlbXBsYXRlID0gYnJpY2socyxzLHMpDQogIHZhbHVlcyh0ZW1wbGF0ZSk9TkENCiAgDQogIGNlbGxyID0gcHggKyBweSAqIGN0WzFdKjINCiAgY2VsbGcgPSBweCArIHB5ICogY3RbMV0qMiArIChjdFsxXSoyKmN0WzJdKjIpDQogIGNlbGxiID0gcHggKyBweSAqIGN0WzFdKjIgKyAyKihjdFsxXSoyKmN0WzJdKjIpDQogIA0KICB0ZW1wbGF0ZVtbMV1dID0gaVtjZWxscl0NCiAgdGVtcGxhdGVbWzJdXSA9IGlbY2VsbGddDQogIHRlbXBsYXRlW1szXV0gPSBpW2NlbGxiXQ0KICANCiAgdGVtcGxhdGUgPSB0ZW1wbGF0ZSAqIDI1Ng0KICANCiAgdGVtcGxhdGUNCn0NCmBgYA0KDQoNCmBgYHtyfQ0KbWFwID0gcmVhZEpQRUcoIkFDQSBJUklBIFVOQSBSVVRBIERFIEFSQ0hJVk8iKSANCmBgYA0KDQoNCg0KYGBge3J9DQpvdXQgPSBnZXR2KG1hcCwgYXNwZWN0LCBzbG9wZSkNCmBgYA0KDQoNCmBgYHtyfQ0KcGxvdFJHQihvdXQsIG1haW4gPSAiTW9udGHDsWFzIGVuIFZpY2hhZGEiKQ0KYGBgDQoNCg==