R tiene un ecosistema muy potente para poder realizar tareas de geocomputación, entre las que podemos mencionar:

  • Análisis exploratorio de datos (EDA)
  • Procesamiento de datos
  • Transformación de datos (p. ej., cambio de proyección, cálculos)
  • Visualización de datos (no solo por medio de mapas)
  • Desarrollo de aplicaciones web
  • Desarrollo de software, en forma de funciones o paquetes (por ejemplo, para compartir nuevos métodos)

El libro Geocomputación con R es una muy buena guía para ver en detalle muchas de estas tareas.

Hay muchas maneras de manejar datos geográficos en R, con muchos paquetes en el área. Entre ellos se encuentran:

y muchos, muchos paquetes más.

En la vista Spatial de CRAN https://cran.r-project.org/view=Spatial se cuenta con una visión general de las diferentes tareas espaciales que se pueden resolver con R.

Tipos de datos

Para trabajar con datos especiales, en general, representamos la información de dos maneras:

  • Vectores: la realidad se representa utilizando puntos, líneas o polígonos.

  • Raster: la información se representa por medio de grillas o píxeles.

En R contamos con diferentes paquetes para poder trabajar con estos dos formatos. Vamos a ver unos ejemplos con datos vectoriales.

Datos vectoriales

Los datos vectoriales se pueden acceder como cualquier otro dato en R:

  • podemos leerlos desde un archivo en nuestra computadora.

  • podemos cargarlos con un paquete y utilizarlos.

Vamos a trabajar con ambos métodos.

Leyendo datos de un archivo

Existen muchas funciones distintas para leer datos dependiendo del formato en el que están guardados. Para datos tabulares, la forma más útil es el formato csv, que es un archivo de texto plano con datos separados por coma.

Para importar datos hace falta escribir el código correspondiente pero también podés aprovechar el entorno gráfico de RStudio:

File → Import Dataset → From Text (readr)…

Esto te va abrir una ventana donde podrás elegir el archivo a importar (en este caso el archivo estaciones_smn.csv que está dentro de la capeta datos del proyecto) y otros detalles.

Diálogo de importar datos de RStudio.

En la pantalla principal vas a poder previsualizar los datos. Abajo a la izquierda tenés varias opciones: el nombre que vas a usar para la variable (en este caso llamaremos estaciones_smn), si la primera fila contiene los nombres de las columnas (First Row as Names), qué delimitador tienen los datos (en este caso comma, pero podría ser punto y coma u otro), etc…

Y abajo a la derecha es el código que vas a necesitar para efectivamente importar los datos. Podrías apretar el botón “Import” para leer los datos pero si bien es posible, al mismo tiempo esas líneas de código no se guardan en ningún lado y entonces nuestro trabajo luego no se puede reproducir. Por eso, te proponemos que copies ese código, cierres esa ventana con el botón “Cancel”, y pegues el código en el archivo donde estés trabajando. Cuando lo ejecutes, se va a generar la variable estaciones_smn con los datos.

library(readr)

estaciones_smn <- read_csv("datos/estaciones_smn.csv") 
## Rows: 117 Columns: 5
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (2): nombre, provincia
## dbl (3): lon, lat, altua
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.

Nota: Notá que en este caso el código para leer los datos consta de dos líneas. La primera carga el paquete readr y el segundo usa la función read_csv() (del paquete readr) para leer el archivo .csv. No es necesario cargar el paquete cada vez que vas a leer un archivo, pero asegurate de incluir esta línea en el primer bloque de código de tu archivo.

Nota: La interfaz de usuario de RStudio sirve para autogenerar el código que lee el archivo. Una vez que lo tenés, no necesitás abrirla de nuevo.

Todo ese texto naranja/rojo es intimidante pero no te preocupes, es sólo un mensaje que nos informa que los datos se leyeron y qué tipo de dato tiene cada columna. Podemos explorar la estructura de la variable estaciones_smn usando la función str() (de structure en inglés).

str(estaciones_smn)
## spec_tbl_df [117 x 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ nombre   : chr [1:117] "AZUL AERO" "BAHIA BLANCA AERO" "BENITO JUAREZ AERO" "BOLIVAR AERO" ...
##  $ provincia: chr [1:117] "BUENOS AIRES" "BUENOS AIRES" "BUENOS AIRES" "BUENOS AIRES" ...
##  $ lon      : num [1:117] -59.9 -62.2 -59.8 -61.1 -58.7 ...
##  $ lat      : num [1:117] -36.8 -38.7 -37.7 -36.2 -34.5 ...
##  $ altua    : num [1:117] 147 83 207 94 26 247 233 9 12 20 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   nombre = col_character(),
##   ..   provincia = col_character(),
##   ..   lon = col_double(),
##   ..   lat = col_double(),
##   ..   altua = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>

Esto nos dice un montón. La primera línea dice que es una tibble, que es un caso especial de la estructura de datos tabular básica de R llamada data.frame. Tiene 117 filas (las observaciones) y 5 columnas (o variables que describen las observaciones). Las siguientes líneas nos dicen los nombres de las columnas (nombre, provincia, lon, lat, y altua), su tipo de dato (chr o num), la longitud ([1:117]) y sus primeros elementos.

Podemos ver que esta tabla tiene dos variables que indican la latitud y longitud de cada fila. En este caso estamos ante un tipo de dato vectorial de puntos.

Para poder graficarlos vamos a utilizar el paquete {ggplot2} que permite generar gráficos de gran calidad en pocos pasos. Pero antes de graficar, veamos otra manera de leer datos vectoriales.

Usando un paquete: Natural Earth, datos del mundo.

{rnaturalearth} es un paquete de R para mantener y facilitar la interacción con los datos de los mapas vectoriales de la tierra natural un conjunto de datos cartográficos de dominio público que incluye vectores de países y otras fronteras administrativas.

Es muy útil para confeccionar mapas base, por ejemplo, para graficar el mapa de Argentina y sus países limítrofes cargamos los datos con ne_countries():

library(rnaturalearth)

mapa <- ne_countries(country = c("argentina", "chile", "uruguay", 
                                                "paraguay", "brazil", "bolivia", 
                                                "falkland islands"), 
                                    returnclass = "sf")

El argumento country es un vector con los países que necesitamos. El argumento returnclass hace referencia a la estructura que queremos que devuelva. En este caso, returnclass = "sf" hace que devuelva un objeto de clase “Simple Features”. Este tipo de dato también se pueden graficar con {ggplot2}.

Veamos el contenido de mapa

str(mapa)
## Classes 'sf' and 'data.frame':   7 obs. of  64 variables:
##  $ scalerank : int  1 1 1 1 1 1 1
##  $ featurecla: chr  "Admin-0 country" "Admin-0 country" "Admin-0 country" "Admin-0 country" ...
##  $ labelrank : num  2 3 2 2 5 4 4
##  $ sovereignt: chr  "Argentina" "Bolivia" "Brazil" "Chile" ...
##  $ sov_a3    : chr  "ARG" "BOL" "BRA" "CHL" ...
##  $ adm0_dif  : num  0 0 0 0 1 0 0
##  $ level     : num  2 2 2 2 2 2 2
##  $ type      : chr  "Sovereign country" "Sovereign country" "Sovereign country" "Sovereign country" ...
##  $ admin     : chr  "Argentina" "Bolivia" "Brazil" "Chile" ...
##  $ adm0_a3   : chr  "ARG" "BOL" "BRA" "CHL" ...
##  $ geou_dif  : num  0 0 0 0 0 0 0
##  $ geounit   : chr  "Argentina" "Bolivia" "Brazil" "Chile" ...
##  $ gu_a3     : chr  "ARG" "BOL" "BRA" "CHL" ...
##  $ su_dif    : num  0 0 0 0 0 0 0
##  $ subunit   : chr  "Argentina" "Bolivia" "Brazil" "Chile" ...
##  $ su_a3     : chr  "ARG" "BOL" "BRA" "CHL" ...
##  $ brk_diff  : num  0 0 0 0 1 0 0
##  $ name      : chr  "Argentina" "Bolivia" "Brazil" "Chile" ...
##  $ name_long : chr  "Argentina" "Bolivia" "Brazil" "Chile" ...
##  $ brk_a3    : chr  "ARG" "BOL" "BRA" "CHL" ...
##  $ brk_name  : chr  "Argentina" "Bolivia" "Brazil" "Chile" ...
##  $ brk_group : chr  NA NA NA NA ...
##  $ abbrev    : chr  "Arg." "Bolivia" "Brazil" "Chile" ...
##  $ postal    : chr  "AR" "BO" "BR" "CL" ...
##  $ formal_en : chr  "Argentine Republic" "Plurinational State of Bolivia" "Federative Republic of Brazil" "Republic of Chile" ...
##  $ formal_fr : chr  NA NA NA NA ...
##  $ note_adm0 : chr  NA NA NA NA ...
##  $ note_brk  : chr  NA NA NA NA ...
##  $ name_sort : chr  "Argentina" "Bolivia" "Brazil" "Chile" ...
##  $ name_alt  : chr  NA NA NA NA ...
##  $ mapcolor7 : num  3 1 5 5 6 6 1
##  $ mapcolor8 : num  1 5 6 1 6 3 2
##  $ mapcolor9 : num  3 2 5 5 6 6 2
##  $ mapcolor13: num  13 3 7 9 3 2 10
##  $ pop_est   : num  4.09e+07 9.78e+06 1.99e+08 1.66e+07 3.14e+03 ...
##  $ gdp_md_est: num  573900 43270 1993000 244500 105 ...
##  $ pop_year  : num  NA NA NA NA NA NA NA
##  $ lastcensus: num  2010 2001 2010 2002 NA ...
##  $ gdp_year  : num  NA NA NA NA NA NA NA
##  $ economy   : chr  "5. Emerging region: G20" "5. Emerging region: G20" "3. Emerging region: BRIC" "5. Emerging region: G20" ...
##  $ income_grp: chr  "3. Upper middle income" "4. Lower middle income" "3. Upper middle income" "3. Upper middle income" ...
##  $ wikipedia : num  NA NA NA NA NA NA NA
##  $ fips_10   : chr  NA NA NA NA ...
##  $ iso_a2    : chr  "AR" "BO" "BR" "CL" ...
##  $ iso_a3    : chr  "ARG" "BOL" "BRA" "CHL" ...
##  $ iso_n3    : chr  "032" "068" "076" "152" ...
##  $ un_a3     : chr  "032" "068" "076" "152" ...
##  $ wb_a2     : chr  "AR" "BO" "BR" "CL" ...
##  $ wb_a3     : chr  "ARG" "BOL" "BRA" "CHL" ...
##  $ woe_id    : num  NA NA NA NA NA NA NA
##  $ adm0_a3_is: chr  "ARG" "BOL" "BRA" "CHL" ...
##  $ adm0_a3_us: chr  "ARG" "BOL" "BRA" "CHL" ...
##  $ adm0_a3_un: num  NA NA NA NA NA NA NA
##  $ adm0_a3_wb: num  NA NA NA NA NA NA NA
##  $ continent : chr  "South America" "South America" "South America" "South America" ...
##  $ region_un : chr  "Americas" "Americas" "Americas" "Americas" ...
##  $ subregion : chr  "South America" "South America" "South America" "South America" ...
##  $ region_wb : chr  "Latin America & Caribbean" "Latin America & Caribbean" "Latin America & Caribbean" "Latin America & Caribbean" ...
##  $ name_len  : num  9 7 6 5 12 8 7
##  $ long_len  : num  9 7 6 5 16 8 7
##  $ abbrev_len: num  4 7 6 5 8 5 4
##  $ tiny      : num  NA NA NA NA NA NA NA
##  $ homepart  : num  1 1 1 1 NA 1 1
##  $ geometry  :sfc_MULTIPOLYGON of length 7; first list element: List of 2
##   ..$ :List of 1
##   .. ..$ : num [1:11, 1:2] -65.5 -66.5 -67 -67.6 -68.6 ...
##   ..$ :List of 1
##   .. ..$ : num [1:110, 1:2] -65 -64.4 -64 -62.8 -62.7 ...
##   ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
##  - attr(*, "sf_column")= chr "geometry"
##  - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
##   ..- attr(*, "names")= chr [1:63] "scalerank" "featurecla" "labelrank" "sovereignt" ...

Vemos que tiene muchas más variables, hacia el final de los datos podemos ver que se indica que hay una clase “MULTIPOLYGON”

Graficando

Ahora si veamos como podemos generar un mapa con los puntos y los polígonos que obtuvimos en los pasos anteriors.

Cualquier gráfico de ggplot tendrá como mínimo 3 componentes: los datos, un sistema de coordenadas y una geometría (la representación visual de los datos) y se irá construyendo por capas.

Primera capa: el área del gráfico

La función principal de {ggplot2} es justamente ggplot() que permite iniciar el gráfico y además definir las características globales. El primer argumento de esta función serán los datos que vas a visualizar, siempre en un data frame. En este caso usamos estaciones_smn.

El segundo argumento se llama “mapping” (mapeo en inglés). Este argumento define la relación entre cada columna del data frame y los distintos parámetros gráficos. Por ejemplo, qué columna va a representar el eje x, cuál va a ser el eje y, etc. Este mapeo se hace siempre con la función aes() (que viene de aesthetics, estética en inglés).

Por ejemplo, si queremos hacer un gráfico que muestre la ubicación de las estaciones usarías algo como esto:

library(ggplot2)
ggplot(data = estaciones_smn, mapping = aes(x = lon, y = lat))

Este código le indica a ggplot que genere un gráfico donde el eje x se mapea a la columna lon y el eje y, a la columna lat. Pero, como se ve, esto sólo genera el área del gráfico y los ejes. Lo que falta es indicar con qué geometrías representar los datos.

Segunda capa: geometrías

Para agregar geometrías que representen los datos lo que hay que hacer es sumar el resultado de una función que devuelva una capa de geometrías. Estas suelen ser funciones que empiezan con “geom_” y luego el nombre de la geometría (en inglés). Para representar los datos usando puntos, hay que uasr geom_point()

ggplot(data = estaciones_smn, mapping = aes(x = lon, y = lat)) +
  geom_point()  

¡Nuestro primer gráfico!

Ahora veamos como se grafican los polígonos teniendo en cuenta lo que aprendimos recien. Los datos de los polígonos están en mapa y sabemos que es del tipo sf, asi que buscamos una geometría que nos permita graficar ese tipo de datos, de esta manera:

ggplot(mapa) +
  geom_sf()

Por defecto, el mapa se dibuja con un fondo gris, pero el problema es que ese fondo puede tapar los datos de puntos de las estaciones. Para para dibujar sólo los contornos hay que modificar la geometría un poco:

ggplot(mapa) +
  geom_sf(fill = NA, color = "black", size = 0.2)

Ahora vamos a juntar los dos mapas: el de puntos y el de polígonos

ggplot(mapa) +
  geom_sf(fill = NA, color = "black", size = 0.2) +
  geom_point(data = estaciones_smn, mapping = aes(lon, lat)) 

Finalmente, podemos restringir el área del mapa para que se muestre solo donde hay datos:

ggplot(mapa) +
  geom_sf(fill = NA, color = "black", size = 0.2) +
  geom_point(data = estaciones_smn, mapping = aes(lon, lat)) +
  coord_sf(ylim = c(-55, -20), xlim = c(-80, -50))

Desafío

Intentá replicar este mismo mapa pero con los datos que están en el archivo estaciones_siga.csv dentro de la carpeta datos del proyecto.

LS0tDQp0aXRsZTogIkVjb3Npc3RlbWEocykgZXNwYWNpYWwoZXMpIGRlIFIiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQotLS0NCg0KUiB0aWVuZSB1biBlY29zaXN0ZW1hIG11eSBwb3RlbnRlIHBhcmEgcG9kZXIgcmVhbGl6YXIgdGFyZWFzIGRlIGdlb2NvbXB1dGFjacOzbiwgDQplbnRyZSBsYXMgcXVlIHBvZGVtb3MgbWVuY2lvbmFyOg0KDQoqIEFuw6FsaXNpcyBleHBsb3JhdG9yaW8gZGUgZGF0b3MgKEVEQSkNCiogUHJvY2VzYW1pZW50byBkZSBkYXRvcyANCiogVHJhbnNmb3JtYWNpw7NuIGRlIGRhdG9zIChwLiBlai4sIGNhbWJpbyBkZSBwcm95ZWNjacOzbiwgY8OhbGN1bG9zKQ0KKiBWaXN1YWxpemFjacOzbiBkZSBkYXRvcyAobm8gc29sbyBwb3IgbWVkaW8gZGUgbWFwYXMpDQoqIERlc2Fycm9sbG8gZGUgYXBsaWNhY2lvbmVzIHdlYg0KKiBEZXNhcnJvbGxvIGRlIHNvZnR3YXJlLCBlbiBmb3JtYSBkZSBmdW5jaW9uZXMgbyBwYXF1ZXRlcyAocG9yIGVqZW1wbG8sIHBhcmEgY29tcGFydGlyIG51ZXZvcyBtw6l0b2RvcykNCg0KOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99DQpFbCBsaWJybyBbR2VvY29tcHV0YWNpw7NuIGNvbiBSXShodHRwczovL2dlb2NvbXByLmdpdGh1Yi5pby9lcy8pIGVzIHVuYSBtdXkgYnVlbmEgZ3XDrWEgcGFyYSB2ZXIgZW4gZGV0YWxsZSBtdWNoYXMgZGUgZXN0YXMgdGFyZWFzLg0KOjo6DQoNCg0KSGF5IG11Y2hhcyBtYW5lcmFzIGRlIG1hbmVqYXIgZGF0b3MgZ2VvZ3LDoWZpY29zIGVuIFIsIGNvbiBtdWNob3MgcGFxdWV0ZXMgZW4gZWwgw6FyZWEuIEVudHJlIGVsbG9zIHNlIGVuY3VlbnRyYW46DQoNCiogW3tzZn1dKGh0dHBzOi8vZ2l0aHViLmNvbS9yLXNwYXRpYWwvc2YpLCBbe3NwfV0oaHR0cHM6Ly9naXRodWIuY29tL2VkemVyL3NwKSwgW3t0ZXJyYX1dKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3BhdGlhbC90ZXJyYSksIFt7cmFzdGVyfV0oaHR0cHM6Ly9naXRodWIuY29tL3JzcGF0aWFsL3Jhc3RlciksIFt7c3RhcnN9XShodHRwczovL2dpdGh1Yi5jb20vci1zcGF0aWFsL3N0YXJzKSAtIGNsYXNlcyBlc3BhY2lhbGVzDQoqIFt7ZHBseXJ9XShodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL2RwbHlyKSwgW3tybWFwc2hhcGVyfV0oaHR0cHM6Ly9naXRodWIuY29tL2F0ZXVjaGVyL3JtYXBzaGFwZXIpIC0gcHJvY2VzYW1pZW50byBkZSB0YWJsYXMgZGUgYXRyaWJ1dG9zL2dlb21ldHLDrWFzDQoqIFt7cm5hdHVyYWxlYXJ0aH1dKGh0dHBzOi8vZG9jcy5yb3BlbnNjaS5vcmcvcm5hdHVyYWxlYXJ0aC8pLCBbe29zbWRhdGF9XShodHRwczovL2RvY3Mucm9wZW5zY2kub3JnL29zbWRhdGEvKSwgW3tyc2F0fV0oaHR0cHM6Ly9kb2NzLnJvcGVuc2NpLm9yZy9yc2F0LyksIFt7TU9ESVNUb29sc31dKGh0dHBzOi8vZG9jcy5yb3BlbnNjaS5vcmcvTU9ESVNUb29scy8pLSBkZXNjYXJnYSBkZSBkYXRvcyBlc3BhY2lhbGVzDQoqIFt7cmdyYXNzfV0oaHR0cHM6Ly9naXRodWIuY29tL3JzYml2YW5kL3JncmFzcyksIFt7cWdpc3Byb2Nlc3N9XShodHRwczovL2dpdGh1Yi5jb20vcGFsZW9saW1ib3QvcWdpc3Byb2Nlc3MpLCBbe3JnZWV9XShodHRwczovL2dpdGh1Yi5jb20vci1zcGF0aWFsL3JnZWUpIC0gY29uZXhpw7NuIGNvbiBvdHJvcyBzb2Z0d2FyZSBkZSBTaXN0ZW1hcyBkZSBJbmZvcm1hY2nDs24gR2VvZ3LDoWZpY28NCiogW3tnc3RhdH1dKGh0dHBzOi8vZ2l0aHViLmNvbS9yLXNwYXRpYWwvZ3N0YXQpLCBbe21scjN9XShodHRwczovL2dpdGh1Yi5jb20vbWxyLW9yZy9tbHIzKSwgW3tDQVNUfV0oaHR0cHM6Ly9naXRodWIuY29tL0hhbm5hTWV5ZXIvQ0FTVCkgLSBtb2RlbGFkbyBkZSBkYXRvcyBlc3BhY2lhbGVzDQoqIFt7cmFzdGVyVmlzfV0oaHR0cHM6Ly9naXRodWIuY29tL29zY2FycGVycGluYW4vcmFzdGVydmlzKSwgW3t0bWFwfV0oaHR0cHM6Ly9naXRodWIuY29tL210ZW5uZWtlcy90bWFwKSwgW3tnZ3Bsb3QyfV0oaHR0cHM6Ly9naXRodWIuY29tL3RpZHl2ZXJzZS9nZ3Bsb3QyKSAtIHZpc3VhbGl6YWNpb25lcyBlc3TDoXRpY2FzDQoqIFt7bGVhZmxldH1dKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2xlYWZsZXQpLCBbe21hcHZpZXd9XShodHRwczovL2dpdGh1Yi5jb20vci1zcGF0aWFsL21hcHZpZXcpLCBbe21hcGRlY2t9XShodHRwczovL2dpdGh1Yi5jb20vU3ltYm9saXhBVS9tYXBkZWNrKSAtIHZpc3VhbGl6YWNpb25lcyBpbnRlcmFjdGl2YXMNCiogW3tzcGF0c3RhdH1dKGh0dHA6Ly9zcGF0c3RhdC5vcmcvKSwgW3tzcGRlcH1dKGh0dHBzOi8vZ2l0aHViLmNvbS9yLXNwYXRpYWwvc3BkZXApLCBbe3NwYXRpYWxyZWd9XShodHRwczovL2dpdGh1Yi5jb20vci1zcGF0aWFsL3NwYXRpYWxyZWcpLCBbe2Rpc21vfV0oaHR0cHM6Ly9naXRodWIuY29tL3JzcGF0aWFsL2Rpc21vKSwgW3tsYW5kc2NhcGVtZXRyaWNzfV0oaHR0cHM6Ly9naXRodWIuY29tL3Itc3BhdGlhbGVjb2xvZ3kvbGFuZHNjYXBlbWV0cmljcyksIFt7UlN0b29sYm94fV0oaHR0cDovL2JsZXV0bmVyLmdpdGh1Yi5pby9SU3Rvb2xib3gvcnN0YngtZG9jdS9SU3Rvb2xib3guaHRtbCksIFt7cmF5c2hhZGVyfV0oaHR0cHM6Ly9naXRodWIuY29tL3R5bGVybW9yZ2Fud2FsbC9yYXlzaGFkZXIpLCBbe2dkYWxjdWJlc31dKGh0dHBzOi8vZ2l0aHViLmNvbS9hcHBlbG1hci9nZGFsY3ViZXNfUiksIFt7c2ZuZXR3b3Jrc31dKGh0dHBzOi8vZ2l0aHViLmNvbS9sdXVrdmRtZWVyL3NmbmV0d29ya3MpLCBbe21ldFJ9XShodHRwczovL2dpdGh1Yi5jb20vZWxpb2NhbXAvbWV0UikgLSBkaWZlcmVudGVzIHRpcG9zIGRlIGFuw6FsaXNpcyBkZSBkYXRvcyBlc3BhY2lhbGVzDQoNCnkgbXVjaG9zLCBtdWNob3MgcGFxdWV0ZXMgbcOhcy4gDQoNCjo6OiB7LmFsZXJ0IC5hbGVydC1pbmZvfQ0KRW4gbGEgdmlzdGEgX1NwYXRpYWxfIGRlIENSQU4gaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvdmlldz1TcGF0aWFsIHNlIGN1ZW50YSBjb24gdW5hIHZpc2nDs24gZ2VuZXJhbCBkZSBsYXMgZGlmZXJlbnRlcyB0YXJlYXMgZXNwYWNpYWxlcyBxdWUgc2UgcHVlZGVuIHJlc29sdmVyIGNvbiBSLg0KOjo6DQoNCiMjIFRpcG9zIGRlIGRhdG9zDQoNClBhcmEgdHJhYmFqYXIgY29uIGRhdG9zIGVzcGVjaWFsZXMsIGVuIGdlbmVyYWwsIHJlcHJlc2VudGFtb3MgbGEgaW5mb3JtYWNpw7NuIGRlIGRvcyBtYW5lcmFzOg0KDQoqIFZlY3RvcmVzOiBsYSByZWFsaWRhZCBzZSByZXByZXNlbnRhIHV0aWxpemFuZG8gcHVudG9zLCBsw61uZWFzIG8gcG9sw61nb25vcy4NCg0KKiBSYXN0ZXI6IGxhIGluZm9ybWFjacOzbiBzZSByZXByZXNlbnRhIHBvciBtZWRpbyBkZSBncmlsbGFzIG8gcMOteGVsZXMuDQoNCkVuIFIgY29udGFtb3MgY29uIGRpZmVyZW50ZXMgcGFxdWV0ZXMgcGFyYSBwb2RlciB0cmFiYWphciBjb24gZXN0b3MgZG9zIGZvcm1hdG9zLiAgVmFtb3MgYSB2ZXIgdW5vcyBlamVtcGxvcyBjb24gZGF0b3MgdmVjdG9yaWFsZXMuDQoNCiMjIERhdG9zIHZlY3RvcmlhbGVzDQoNCkxvcyBkYXRvcyB2ZWN0b3JpYWxlcyBzZSBwdWVkZW4gYWNjZWRlciBjb21vIGN1YWxxdWllciBvdHJvIGRhdG8gZW4gUjoNCg0KKiBwb2RlbW9zIGxlZXJsb3MgZGVzZGUgdW4gYXJjaGl2byBlbiBudWVzdHJhIGNvbXB1dGFkb3JhLg0KDQoqIHBvZGVtb3MgY2FyZ2FybG9zIGNvbiB1biBwYXF1ZXRlIHkgdXRpbGl6YXJsb3MuDQoNClZhbW9zIGEgdHJhYmFqYXIgY29uIGFtYm9zIG3DqXRvZG9zLg0KDQoNCiMjIyBMZXllbmRvIGRhdG9zIGRlIHVuIGFyY2hpdm8NCg0KRXhpc3RlbiBtdWNoYXMgZnVuY2lvbmVzIGRpc3RpbnRhcyBwYXJhIGxlZXIgZGF0b3MgZGVwZW5kaWVuZG8gZGVsIGZvcm1hdG8gZW4gZWwgcXVlIGVzdMOhbiBndWFyZGFkb3MuDQpQYXJhIGRhdG9zIHRhYnVsYXJlcywgbGEgZm9ybWEgbcOhcyDDunRpbCBlcyBlbCBmb3JtYXRvIGNzdiwgcXVlIGVzIHVuIGFyY2hpdm8gZGUgdGV4dG8gcGxhbm8gY29uIGRhdG9zIHNlcGFyYWRvcyBwb3IgY29tYS4NCg0KUGFyYSBpbXBvcnRhciBkYXRvcyBoYWNlIGZhbHRhIGVzY3JpYmlyIGVsIGPDs2RpZ28gY29ycmVzcG9uZGllbnRlIHBlcm8gdGFtYmnDqW4gcG9kw6lzIGFwcm92ZWNoYXIgZWwgZW50b3JubyBncsOhZmljbyBkZSBSU3R1ZGlvOg0KDQo6Ojogey5hbGVydCAuYWxlcnQtc2Vjb25kYXJ5fQ0KRmlsZSDihpIgSW1wb3J0IERhdGFzZXQg4oaSIEZyb20gVGV4dCAocmVhZHIpLi4uDQo6OjoNCg0KRXN0byB0ZSB2YSBhYnJpciB1bmEgdmVudGFuYSBkb25kZSBwb2Ryw6FzIGVsZWdpciBlbCBhcmNoaXZvIGEgaW1wb3J0YXIgKGVuIGVzdGUgY2FzbyBlbCBhcmNoaXZvIGBlc3RhY2lvbmVzX3Ntbi5jc3ZgIHF1ZSBlc3TDoSBkZW50cm8gZGUgbGEgY2FwZXRhIGBkYXRvc2AgZGVsIHByb3llY3RvKSB5IG90cm9zIGRldGFsbGVzLg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgZmlnLmFsdCA9ICJEacOhbG9nbyBkZSBpbXBvcnRhciBkYXRvcyBkZSBSU3R1ZGlvLiJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1nL2xlZXJfY3N2LnBuZyIpDQpgYGANCg0KRW4gbGEgcGFudGFsbGEgcHJpbmNpcGFsIHZhcyBhIHBvZGVyIHByZXZpc3VhbGl6YXIgbG9zIGRhdG9zLg0KQWJham8gYSBsYSBpenF1aWVyZGEgdGVuw6lzIHZhcmlhcyBvcGNpb25lczogZWwgbm9tYnJlIHF1ZSB2YXMgYSB1c2FyIHBhcmEgbGEgdmFyaWFibGUgKGVuIGVzdGUgY2FzbyBsbGFtYXJlbW9zIGBlc3RhY2lvbmVzX3NtbmApLCBzaSBsYSBwcmltZXJhIGZpbGEgY29udGllbmUgbG9zIG5vbWJyZXMgZGUgbGFzIGNvbHVtbmFzIChgRmlyc3QgUm93IGFzIE5hbWVzYCksIHF1w6kgZGVsaW1pdGFkb3IgdGllbmVuIGxvcyBkYXRvcyAoZW4gZXN0ZSBjYXNvIGBjb21tYWAsIHBlcm8gcG9kcsOtYSBzZXIgcHVudG8geSBjb21hIHUgb3RybyksIGV0Yy4uLg0KDQpZIGFiYWpvIGEgbGEgZGVyZWNoYSBlcyBlbCBjw7NkaWdvIHF1ZSB2YXMgYSBuZWNlc2l0YXIgcGFyYSBlZmVjdGl2YW1lbnRlIGltcG9ydGFyIGxvcyBkYXRvcy4NClBvZHLDrWFzIGFwcmV0YXIgZWwgYm90w7NuICJJbXBvcnQiIHBhcmEgbGVlciBsb3MgZGF0b3MgcGVybyBzaSBiaWVuIGVzIHBvc2libGUsIGFsIG1pc21vIHRpZW1wbyBlc2FzIGzDrW5lYXMgZGUgY8OzZGlnbyBubyBzZSBndWFyZGFuIGVuIG5pbmfDum4gbGFkbyB5IGVudG9uY2VzIG51ZXN0cm8gdHJhYmFqbyBsdWVnbyBubyBzZSBwdWVkZSByZXByb2R1Y2lyLg0KUG9yIGVzbywgdGUgcHJvcG9uZW1vcyBxdWUgY29waWVzIGVzZSBjw7NkaWdvLCBjaWVycmVzIGVzYSB2ZW50YW5hIGNvbiBlbCBib3TDs24gIkNhbmNlbCIsIHkgcGVndWVzIGVsIGPDs2RpZ28gZW4gZWwgYXJjaGl2byBkb25kZSBlc3TDqXMgdHJhYmFqYW5kby4NCkN1YW5kbyBsbyBlamVjdXRlcywgc2UgdmEgYSBnZW5lcmFyIGxhIHZhcmlhYmxlIGBlc3RhY2lvbmVzX3NtbmAgY29uIGxvcyBkYXRvcy4NCg0KYGBge3J9DQpsaWJyYXJ5KHJlYWRyKQ0KDQplc3RhY2lvbmVzX3NtbiA8LSByZWFkX2NzdigiZGF0b3MvZXN0YWNpb25lc19zbW4uY3N2IikgDQpgYGANCg0KOjo6IHsuYWxlcnQgLmFsZXJ0LXN1Y2Nlc3N9DQoqKk5vdGEqKjogTm90w6EgcXVlIGVuIGVzdGUgY2FzbyBlbCBjw7NkaWdvIHBhcmEgbGVlciBsb3MgZGF0b3MgY29uc3RhIGRlIGRvcyBsw61uZWFzLg0KTGEgcHJpbWVyYSBjYXJnYSBlbCBwYXF1ZXRlICoqcmVhZHIqKiB5IGVsIHNlZ3VuZG8gdXNhIGxhIGZ1bmNpw7NuIGByZWFkX2NzdigpYCAoZGVsIHBhcXVldGUgcmVhZHIpIHBhcmEgbGVlciBlbCBhcmNoaXZvIC5jc3YuDQpObyBlcyBuZWNlc2FyaW8gY2FyZ2FyIGVsIHBhcXVldGUgY2FkYSB2ZXogcXVlIHZhcyBhIGxlZXIgdW4gYXJjaGl2bywgcGVybyBhc2VndXJhdGUgZGUgaW5jbHVpciBlc3RhIGzDrW5lYSBlbiBlbCBwcmltZXIgYmxvcXVlIGRlIGPDs2RpZ28gZGUgdHUgYXJjaGl2by4NCjo6Og0KDQoNCjo6OiB7LmFsZXJ0IC5hbGVydC1zdWNjZXNzfQ0KKipOb3RhKio6IExhIGludGVyZmF6IGRlIHVzdWFyaW8gZGUgUlN0dWRpbyBzaXJ2ZSBwYXJhIGF1dG9nZW5lcmFyIGVsIGPDs2RpZ28gcXVlIGxlZSBlbCBhcmNoaXZvLg0KVW5hIHZleiBxdWUgbG8gdGVuw6lzLCBubyBuZWNlc2l0w6FzIGFicmlybGEgZGUgbnVldm8uDQo6OjoNCg0KVG9kbyBlc2UgdGV4dG8gbmFyYW5qYS9yb2pvIGVzIGludGltaWRhbnRlIHBlcm8gbm8gdGUgcHJlb2N1cGVzLCBlcyBzw7NsbyB1biBtZW5zYWplIHF1ZSBub3MgaW5mb3JtYSBxdWUgbG9zIGRhdG9zIHNlIGxleWVyb24geSBxdcOpIHRpcG8gZGUgZGF0byB0aWVuZSBjYWRhIGNvbHVtbmEuDQpQb2RlbW9zIGV4cGxvcmFyIGxhIGVzdHJ1Y3R1cmEgZGUgbGEgdmFyaWFibGUgYGVzdGFjaW9uZXNfc21uYCB1c2FuZG8gbGEgZnVuY2nDs24gYHN0cigpYCAoZGUgKnN0cnVjdHVyZSogZW4gaW5nbMOpcykuDQoNCmBgYHtyfQ0Kc3RyKGVzdGFjaW9uZXNfc21uKQ0KYGBgDQoNCkVzdG8gbm9zIGRpY2UgdW4gbW9udMOzbi4NCkxhIHByaW1lcmEgbMOtbmVhIGRpY2UgcXVlIGVzIHVuYSBgdGliYmxlYCwgcXVlIGVzIHVuIGNhc28gZXNwZWNpYWwgZGUgbGEgZXN0cnVjdHVyYSBkZSBkYXRvcyB0YWJ1bGFyIGLDoXNpY2EgZGUgUiBsbGFtYWRhIGBkYXRhLmZyYW1lYC4NClRpZW5lIGByIG5yb3coZXN0YWNpb25lc19zbW4pYCBmaWxhcyAobGFzICoqb2JzZXJ2YWNpb25lcyoqKSB5IGByIG5jb2woZXN0YWNpb25lc19zbW4pYCBjb2x1bW5hcyAobyAqKnZhcmlhYmxlcyoqIHF1ZSBkZXNjcmliZW4gbGFzIG9ic2VydmFjaW9uZXMpLg0KTGFzIHNpZ3VpZW50ZXMgbMOtbmVhcyBub3MgZGljZW4gbG9zIG5vbWJyZXMgZGUgbGFzIGNvbHVtbmFzIChgciBrbml0cjo6Y29tYmluZV93b3Jkcyhjb2xuYW1lcyhlc3RhY2lvbmVzX3NtbiksIGFuZCA9ICJ5ICIpYCksIHN1IHRpcG8gZGUgZGF0byAoYGNocmAgbyBgbnVtYCksIGxhIGxvbmdpdHVkIChgciBwYXN0ZTAoIlsxOiIsIG5yb3coZXN0YWNpb25lc19zbW4pLCAiXSIpYCkgeSBzdXMgcHJpbWVyb3MgZWxlbWVudG9zLg0KDQpQb2RlbW9zIHZlciBxdWUgZXN0YSB0YWJsYSB0aWVuZSBkb3MgdmFyaWFibGVzIHF1ZSBpbmRpY2FuIGxhIGxhdGl0dWQgeSBsb25naXR1ZCBkZSBjYWRhIGZpbGEuICBFbiBlc3RlIGNhc28gZXN0YW1vcyBhbnRlIHVuIHRpcG8gZGUgZGF0byB2ZWN0b3JpYWwgZGUgcHVudG9zLg0KDQpQYXJhIHBvZGVyIGdyYWZpY2FybG9zIHZhbW9zIGEgdXRpbGl6YXIgZWwgcGFxdWV0ZSB7Z2dwbG90Mn0gcXVlIHBlcm1pdGUgZ2VuZXJhciBncsOhZmljb3MgZGUgZ3JhbiBjYWxpZGFkIGVuIHBvY29zIHBhc29zLiAgUGVybyBhbnRlcyBkZSBncmFmaWNhciwgdmVhbW9zIG90cmEgbWFuZXJhIGRlIGxlZXIgZGF0b3MgdmVjdG9yaWFsZXMuDQoNCiMjIyBVc2FuZG8gdW4gcGFxdWV0ZTogTmF0dXJhbCBFYXJ0aCwgZGF0b3MgZGVsIG11bmRvLg0KDQp7cm5hdHVyYWxlYXJ0aH0gZXMgdW4gcGFxdWV0ZSBkZSBSIHBhcmEgbWFudGVuZXIgeSBmYWNpbGl0YXIgbGEgaW50ZXJhY2Npw7NuIGNvbiBsb3MgZGF0b3MgZGUgbG9zIG1hcGFzIHZlY3RvcmlhbGVzIGRlIGxhIFt0aWVycmEgbmF0dXJhbF0oaHR0cDovL3d3dy5uYXR1cmFsZWFydGhkYXRhLmNvbS8pIHVuIGNvbmp1bnRvIGRlIGRhdG9zIGNhcnRvZ3LDoWZpY29zIGRlIGRvbWluaW8gcMO6YmxpY28gcXVlIGluY2x1eWUgdmVjdG9yZXMgZGUgcGHDrXNlcyB5IG90cmFzIGZyb250ZXJhcyBhZG1pbmlzdHJhdGl2YXMuDQoNCkVzIG11eSDDunRpbCBwYXJhIGNvbmZlY2Npb25hciBtYXBhcyBiYXNlLCBwb3IgZWplbXBsbywgcGFyYSBncmFmaWNhciBlbCBtYXBhIGRlIEFyZ2VudGluYSB5IHN1cyBwYcOtc2VzIGxpbcOtdHJvZmVzIGNhcmdhbW9zIGxvcyBkYXRvcyBjb24gYG5lX2NvdW50cmllcygpYDoNCg0KYGBge3J9DQpsaWJyYXJ5KHJuYXR1cmFsZWFydGgpDQoNCm1hcGEgPC0gbmVfY291bnRyaWVzKGNvdW50cnkgPSBjKCJhcmdlbnRpbmEiLCAiY2hpbGUiLCAidXJ1Z3VheSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInBhcmFndWF5IiwgImJyYXppbCIsICJib2xpdmlhIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZmFsa2xhbmQgaXNsYW5kcyIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybmNsYXNzID0gInNmIikNCmBgYA0KDQpFbCBhcmd1bWVudG8gYGNvdW50cnlgIGVzIHVuIHZlY3RvciBjb24gbG9zIHBhw61zZXMgcXVlIG5lY2VzaXRhbW9zLiBFbCBhcmd1bWVudG8gYHJldHVybmNsYXNzYCBoYWNlIHJlZmVyZW5jaWEgYSBsYSBlc3RydWN0dXJhIHF1ZSBxdWVyZW1vcyBxdWUgZGV2dWVsdmEuIEVuIGVzdGUgY2FzbywgYHJldHVybmNsYXNzID0gInNmImAgaGFjZSBxdWUgZGV2dWVsdmEgdW4gb2JqZXRvIGRlIGNsYXNlICJTaW1wbGUgRmVhdHVyZXMiLiBFc3RlIHRpcG8gZGUgZGF0byB0YW1iacOpbiBzZSBwdWVkZW4gZ3JhZmljYXIgY29uIHtnZ3Bsb3QyfS4NCg0KVmVhbW9zIGVsIGNvbnRlbmlkbyBkZSBtYXBhDQoNCmBgYHtyfQ0Kc3RyKG1hcGEpDQpgYGANClZlbW9zIHF1ZSB0aWVuZSBtdWNoYXMgbcOhcyB2YXJpYWJsZXMsIGhhY2lhIGVsIGZpbmFsIGRlIGxvcyBkYXRvcyBwb2RlbW9zIHZlciBxdWUgc2UgaW5kaWNhIHF1ZSBoYXkgdW5hIGNsYXNlIF8iTVVMVElQT0xZR09OIl8gDQoNCg0KIyMgR3JhZmljYW5kbw0KDQpBaG9yYSBzaSB2ZWFtb3MgY29tbyBwb2RlbW9zIGdlbmVyYXIgdW4gbWFwYSBjb24gbG9zIHB1bnRvcyB5IGxvcyBwb2zDrWdvbm9zIHF1ZSBvYnR1dmltb3MgZW4gbG9zIHBhc29zIGFudGVyaW9ycy4NCg0KQ3VhbHF1aWVyIGdyw6FmaWNvIGRlIGdncGxvdCB0ZW5kcsOhIGNvbW8gbcOtbmltbyAzIGNvbXBvbmVudGVzOiBsb3MgKipkYXRvcyoqLCB1biAqKnNpc3RlbWEgZGUgY29vcmRlbmFkYXMqKiB5IHVuYSAqKmdlb21ldHLDrWEqKiAobGEgcmVwcmVzZW50YWNpw7NuIHZpc3VhbCBkZSBsb3MgZGF0b3MpIHkgc2UgaXLDoSBjb25zdHJ1eWVuZG8gcG9yIGNhcGFzLg0KDQojIyBQcmltZXJhIGNhcGE6IGVsIMOhcmVhIGRlbCBncsOhZmljbw0KDQpMYSBmdW5jacOzbiBwcmluY2lwYWwgZGUge2dncGxvdDJ9IGVzIGp1c3RhbWVudGUgYGdncGxvdCgpYCBxdWUgcGVybWl0ZSAqaW5pY2lhciogZWwgZ3LDoWZpY28geSBhZGVtw6FzIGRlZmluaXIgbGFzIGNhcmFjdGVyw61zdGljYXMgKmdsb2JhbGVzKi4NCkVsIHByaW1lciBhcmd1bWVudG8gZGUgZXN0YSBmdW5jacOzbiBzZXLDoW4gbG9zIGRhdG9zIHF1ZSB2YXMgYSB2aXN1YWxpemFyLCBzaWVtcHJlIGVuIHVuIGRhdGEgZnJhbWUuDQpFbiBlc3RlIGNhc28gdXNhbW9zIGBlc3RhY2lvbmVzX3NtbmAuDQoNCkVsIHNlZ3VuZG8gYXJndW1lbnRvIHNlIGxsYW1hICJtYXBwaW5nIiAoKm1hcGVvKiBlbiBpbmdsw6lzKS4gDQpFc3RlIGFyZ3VtZW50byBkZWZpbmUgbGEgcmVsYWNpw7NuIGVudHJlIGNhZGEgY29sdW1uYSBkZWwgZGF0YSBmcmFtZSB5IGxvcyBkaXN0aW50b3MgcGFyw6FtZXRyb3MgZ3LDoWZpY29zLiBQb3IgZWplbXBsbywgcXXDqSBjb2x1bW5hIHZhIGEgcmVwcmVzZW50YXIgZWwgZWplIHgsIGN1w6FsIHZhIGEgc2VyIGVsIGVqZSB5LCBldGMuIA0KRXN0ZSBtYXBlbyBzZSBoYWNlICoqc2llbXByZSoqIGNvbiBsYSBmdW5jacOzbiBgYWVzKClgIChxdWUgdmllbmUgZGUgKmFlc3RoZXRpY3MqLCAqZXN0w6l0aWNhKiBlbiBpbmdsw6lzKS4gDQoNClBvciBlamVtcGxvLCBzaSBxdWVyZW1vcyBoYWNlciB1biBncsOhZmljbyBxdWUgbXVlc3RyZSBsYSB1YmljYWNpw7NuIGRlIGxhcyBlc3RhY2lvbmVzIHVzYXLDrWFzIGFsZ28gY29tbyBlc3RvOg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmdncGxvdChkYXRhID0gZXN0YWNpb25lc19zbW4sIG1hcHBpbmcgPSBhZXMoeCA9IGxvbiwgeSA9IGxhdCkpDQpgYGANCg0KRXN0ZSBjw7NkaWdvIGxlIGluZGljYSBhIGdncGxvdCBxdWUgZ2VuZXJlIHVuIGdyw6FmaWNvIGRvbmRlIGVsIGVqZSAqKngqKiBzZSBtYXBlYSBhIGxhIGNvbHVtbmEgYGxvbmAgeSBlbCBlamUgKip5KiosIGEgbGEgY29sdW1uYSBgbGF0YC4gDQpQZXJvLCBjb21vIHNlIHZlLCBlc3RvIHPDs2xvIGdlbmVyYSBlbCDDoXJlYSBkZWwgZ3LDoWZpY28geSBsb3MgZWplcy4gDQpMbyBxdWUgZmFsdGEgZXMgaW5kaWNhciBjb24gcXXDqSBnZW9tZXRyw61hcyByZXByZXNlbnRhciBsb3MgZGF0b3MuDQoNCg0KIyMgU2VndW5kYSBjYXBhOiBnZW9tZXRyw61hcw0KDQoNClBhcmEgYWdyZWdhciBnZW9tZXRyw61hcyBxdWUgcmVwcmVzZW50ZW4gbG9zIGRhdG9zIGxvIHF1ZSBoYXkgcXVlIGhhY2VyIGVzICpzdW1hciogZWwgcmVzdWx0YWRvIGRlIHVuYSBmdW5jacOzbiBxdWUgZGV2dWVsdmEgdW5hIGNhcGEgZGUgZ2VvbWV0csOtYXMuIA0KRXN0YXMgc3VlbGVuIHNlciBmdW5jaW9uZXMgcXVlIGVtcGllemFuIGNvbiAiZ2VvbV8iIHkgbHVlZ28gZWwgbm9tYnJlIGRlIGxhIGdlb21ldHLDrWEgKGVuIGluZ2zDqXMpLiANClBhcmEgcmVwcmVzZW50YXIgbG9zIGRhdG9zIHVzYW5kbyBwdW50b3MsIGhheSBxdWUgdWFzciBgZ2VvbV9wb2ludCgpYA0KDQpgYGB7cn0NCg0KZ2dwbG90KGRhdGEgPSBlc3RhY2lvbmVzX3NtbiwgbWFwcGluZyA9IGFlcyh4ID0gbG9uLCB5ID0gbGF0KSkgKw0KICBnZW9tX3BvaW50KCkgIA0KYGBgDQoNCsKhTnVlc3RybyBwcmltZXIgZ3LDoWZpY28hDQoNCkFob3JhIHZlYW1vcyBjb21vIHNlIGdyYWZpY2FuIGxvcyBwb2zDrWdvbm9zIHRlbmllbmRvIGVuIGN1ZW50YSBsbyBxdWUgYXByZW5kaW1vcyByZWNpZW4uIExvcyBkYXRvcyBkZSBsb3MgcG9sw61nb25vcyBlc3TDoW4gZW4gYG1hcGFgIHkgc2FiZW1vcyBxdWUgZXMgZGVsIHRpcG8gc2YsIGFzaSBxdWUgYnVzY2Ftb3MgdW5hIGdlb21ldHLDrWEgcXVlIG5vcyBwZXJtaXRhIGdyYWZpY2FyIGVzZSB0aXBvIGRlIGRhdG9zLCBkZSBlc3RhIG1hbmVyYTogDQoNCmBgYHtyfQ0KZ2dwbG90KG1hcGEpICsNCiAgZ2VvbV9zZigpDQpgYGANCg0KUG9yIGRlZmVjdG8sIGVsIG1hcGEgc2UgZGlidWphIGNvbiB1biBmb25kbyBncmlzLCBwZXJvIGVsIHByb2JsZW1hIGVzIHF1ZSBlc2UgZm9uZG8gcHVlZGUgdGFwYXIgbG9zIGRhdG9zIGRlIHB1bnRvcyBkZSBsYXMgZXN0YWNpb25lcy4gUGFyYSBwYXJhIGRpYnVqYXIgc8OzbG8gbG9zIGNvbnRvcm5vcyBoYXkgcXVlIG1vZGlmaWNhciBsYSBnZW9tZXRyw61hIHVuIHBvY286DQoNCmBgYHtyfQ0KZ2dwbG90KG1hcGEpICsNCiAgZ2VvbV9zZihmaWxsID0gTkEsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDAuMikNCiAgDQpgYGANCg0KQWhvcmEgdmFtb3MgYSBqdW50YXIgbG9zIGRvcyBtYXBhczogZWwgZGUgcHVudG9zIHkgZWwgZGUgcG9sw61nb25vcw0KDQoNCmBgYHtyfQ0KZ2dwbG90KG1hcGEpICsNCiAgZ2VvbV9zZihmaWxsID0gTkEsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDAuMikgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBlc3RhY2lvbmVzX3NtbiwgbWFwcGluZyA9IGFlcyhsb24sIGxhdCkpIA0KDQpgYGANCg0KDQpGaW5hbG1lbnRlLCBwb2RlbW9zIHJlc3RyaW5naXIgZWwgw6FyZWEgZGVsIG1hcGEgcGFyYSBxdWUgc2UgbXVlc3RyZSBzb2xvIGRvbmRlIGhheSBkYXRvczoNCg0KYGBge3J9DQpnZ3Bsb3QobWFwYSkgKw0KICBnZW9tX3NmKGZpbGwgPSBOQSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMC4yKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGVzdGFjaW9uZXNfc21uLCBtYXBwaW5nID0gYWVzKGxvbiwgbGF0KSkgKw0KICBjb29yZF9zZih5bGltID0gYygtNTUsIC0yMCksIHhsaW0gPSBjKC04MCwgLTUwKSkNCg0KYGBgDQoNCjo6OiB7LmFsZXJ0IC5hbGVydC1pbmZvfQ0KKipEZXNhZsOtbyoqDQoNCkludGVudMOhIHJlcGxpY2FyIGVzdGUgbWlzbW8gbWFwYSBwZXJvIGNvbiBsb3MgZGF0b3MgcXVlIGVzdMOhbiBlbiBlbCBhcmNoaXZvIF9lc3RhY2lvbmVzX3NpZ2EuY3N2XyBkZW50cm8gZGUgbGEgY2FycGV0YSBkYXRvcyBkZWwgcHJveWVjdG8uDQoNCjo6Og0KDQoNCg0KDQo=