Reiniciar R

Cargamos las librerías. En esta ocasión usaremos por primera vez el paquete sf.

library(tidyverse)
library(ggthemes)
library(sf)

Representación de la información en mapas

Trabajaremos con datos del portal de datos abiertos de la Ciudad de Buenos Aires. En este caso, usamos los radios censales de la ciudad y los hemos descargado de https://bitsandbricks.github.io/data/CABA_rc.geojson.

La función st_read nos permite levantar un archivo de tipo .geojson. (también permite cargar otros con capas, pero no lo veremos en esta ocasión).

Vemos que el mismo cuenta con 3.554 features y 8 campos.
epsg (SRID): 4326 y proj4string: +proj=longlat +datum=WGS84 +no_defs refieren a que nuestros datos usan el sistema de coordenadas WGS84, también conocido por su código EPSG 4326 . Es el mismo que usan los sistemas GPS, Google Maps, y las aplicaciones de internet en general.

radios <- st_read("../fuentes/CABA_rc.geojson")
Reading layer `CABA_rc' from data source `/home/diego/Documents/0_GIT/2_Ed/intro_ds/fuentes/CABA_rc.geojson' using driver `GeoJSON'
Simple feature collection with 3554 features and 8 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -58.53092 ymin: -34.70574 xmax: -58.33455 ymax: -34.528
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs

Como los 8 campos son equivalentes a columnas/variables, podemos pedir un summary de la información contenida en radios.

summary(radios)
    RADIO_ID          BARRIO         COMUNA       POBLACION        VIVIENDAS     
 1_1_1  :   1   PALERMO  : 295   1      : 329   Min.   :   0.0   Min.   :   0.0  
 1_10_1 :   1   CABALLITO: 215   13     : 305   1st Qu.: 646.2   1st Qu.: 311.2  
 1_10_10:   1   RECOLETA : 198   14     : 295   Median : 786.0   Median : 377.0  
 1_10_11:   1   BALVANERA: 191   3      : 254   Mean   : 813.2   Mean   : 401.4  
 1_10_12:   1   FLORES   : 183   4      : 252   3rd Qu.: 928.0   3rd Qu.: 462.0  
 1_10_13:   1   BELGRANO : 170   7      : 250   Max.   :3945.0   Max.   :1405.0  
 (Other):3548   (Other)  :2302   (Other):1869                                    
    HOGARES        HOGARES_NBI        AREA_KM2                 geometry   
 Min.   :   0.0   Min.   :  0.00   Min.   :0.004468   MULTIPOLYGON :3554  
 1st Qu.: 259.0   1st Qu.:  2.00   1st Qu.:0.018626   epsg:4326    :   0  
 Median : 310.0   Median :  6.00   Median :0.035548   +proj=long...:   0  
 Mean   : 323.6   Mean   : 19.35   Mean   :0.057350                       
 3rd Qu.: 371.0   3rd Qu.: 23.00   3rd Qu.:0.062847                       
 Max.   :1093.0   Max.   :403.00   Max.   :3.804422                       
                                                                          

A la hora de graficar, podemos representar la información utilizando la combinación del famoso ggplot() en conjunto con geom_sf, que identifica la variable con la geometría y la grafica.

ggplot() + 
  geom_sf(data = radios)

Podemos colorear los radios censales de acuerdo a la cantidad de viviendas que hay en cada una de ellas, convocando a la variable en el parámetro de relleno. A su vez, podemos “jugar” con el color y ancho de los bordes.

ggplot(data = radios, aes(fill = VIVIENDAS)) + 
  geom_sf() 


ggplot(data = radios, aes(fill = VIVIENDAS)) + 
  geom_sf(color = "white") 


ggplot(data = radios, aes(fill = VIVIENDAS)) +
  geom_sf(color = "white", size=.1) 

O bien podemos omitir los bordes.

ggplot(data = radios, aes(fill = POBLACION)) + 
  geom_sf(color = NA)

También podemos usar el campo del relleno para realizar una transformación de los datos. Por ejemplo, podemos calcular la densidad de la población utilizando los datos de POBLACION y AREA_KM2. Aprovechamos para mejorar otras cuestiones estéticas 🌈

ggplot(data = radios, aes(fill = POBLACION/AREA_KM2)) + 
    geom_sf(color = NA) +
    scale_fill_viridis_c() +
    labs(title = "Densidad de población",
         subtitle = "Ciudad Autónoma de Buenos Aires",
         fill = "hab/km2") +
  theme_void()

También podemos rellenar de acuerdo a la variable BARRIO, que es una forma de graficar agregados. De todas formas, luego veremos como agruparlos con group_by.

ggplot(data = radios, aes(fill = BARRIO)) + 
  geom_sf(color = NA) +
  theme(legend.position = 'none')

También podemos representar rápidamente la información de alguna de las variables realizando un select() de la misma y la geometría, y pidiendo un plot() (el parámetro lwd define el ancho de las líneas de cada geometría, en este caso, los radios censales). En términos visuales, es como haber agregado por BARRIO la información, no?

radios %>% 
  select(BARRIO, geometry) %>% 
  plot(lwd=0.06)

Agrupando polígonos

Sin embargo, si lo que queremos es reconstruir los polígonos de los barrios, sólo necesitamos hacer un group_by y un summarise, y automáticamente en la columna geometry se crea el polígono combinado. Además, podemos hacer una agregación de las demás variables.

radios %>% 
  sample_n(5)
Simple feature collection with 5 features and 8 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -58.49039 ymin: -34.64358 xmax: -58.37329 ymax: -34.56128
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
  RADIO_ID        BARRIO COMUNA POBLACION VIVIENDAS HOGARES HOGARES_NBI
1  15_11_6     CHACARITA     15       798       288     266          22
2   2_20_7      RECOLETA      2       569       380     247           5
3   5_12_4       ALMAGRO      5       735       407     328           8
4   12_9_6 VILLA URQUIZA     12       960       452     377           3
5  4_12_11      BARRACAS      4       632       296     260           7
    AREA_KM2                       geometry
1 0.07511176 MULTIPOLYGON (((-58.43938 -...
2 0.02277754 MULTIPOLYGON (((-58.40963 -...
3 0.01722292 MULTIPOLYGON (((-58.42623 -...
4 0.07053921 MULTIPOLYGON (((-58.48944 -...
5 0.03611037 MULTIPOLYGON (((-58.3737 -3...
barrios_geo <- radios %>% 
    group_by(BARRIO) %>% 
    summarise(POBLACION = sum(POBLACION),
              VIVIENDAS = sum(VIVIENDAS),
              HOGARES = sum(HOGARES),
              HOGARES_NBI = sum(HOGARES_NBI),
              AREA_KM2 = sum(AREA_KM2))

barrios_geo %>% 
  sample_n(5)
Simple feature collection with 5 features and 6 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: -58.53092 ymin: -34.67492 xmax: -58.43793 ymax: -34.55955
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs

Si pedimos un plot() del objeto, nos devuelve la representación geográfica con los datos de cada una de las variables.

plot(barrios_geo)

Si deseamos agregar otra capa al gráfico que incluya texto o etiquetas de los datos, contamos con geom_sf_text(), en la cual podemos también solicitar que se etiqueten sólo aquellos datos que cumplen con alguna condición.

ggplot(data = barrios_geo, aes(fill = POBLACION/AREA_KM2)) +
    geom_sf(color = NA) +
    geom_sf_text(data = barrios_geo %>% 
                   filter(POBLACION/AREA_KM2>25000), 
                 aes(label = BARRIO), size=3)+
  theme(legend.position = 'bottom')+
  scale_fill_viridis_c(option = 'C') +
  labs(title = "Densidad de población",
        subtitle = "Ciudad Autónoma de Buenos Aires",
        fill = "hab/km2")+
  theme_void()

Volcando en el mapa información de múltiples fuentes: Subtes

Ahora utilizaremos la información de líneas y estaciones de subte. Hemos descargado los .geojson de http://bitsandbricks.github.io/data/subte_lineas.geojson y http://bitsandbricks.github.io/data/subte_estaciones.geojson.

subte_lineas <- st_read('../fuentes/subte_lineas.geojson')
Reading layer `subte_lineas' from data source `/home/diego/Documents/0_GIT/2_Ed/intro_ds/fuentes/subte_lineas.geojson' using driver `GeoJSON'
Simple feature collection with 80 features and 2 fields
geometry type:  MULTILINESTRING
dimension:      XY
bbox:           xmin: -58.48639 ymin: -34.64331 xmax: -58.36993 ymax: -34.55564
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
subte_lineas %>% 
  sample_n(5)
Simple feature collection with 5 features and 2 fields
geometry type:  MULTILINESTRING
dimension:      XY
bbox:           xmin: -58.46165 ymin: -34.64331 xmax: -58.37804 ymax: -34.57842
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
  ID LINEASUB                       geometry
1 16  LINEA C MULTILINESTRING ((-58.37953...
2  6  LINEA D MULTILINESTRING ((-58.4212 ...
3 70  LINEA A MULTILINESTRING ((-58.44865...
4 59  LINEA E MULTILINESTRING ((-58.45789...
5  5  LINEA D MULTILINESTRING ((-58.42571...
subte_estaciones <- st_read('../fuentes/subte_estaciones.geojson')
Reading layer `subte_estaciones' from data source `/home/diego/Documents/0_GIT/2_Ed/intro_ds/fuentes/subte_estaciones.geojson' using driver `GeoJSON'
Simple feature collection with 86 features and 3 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: -58.48639 ymin: -34.64331 xmax: -58.36993 ymax: -34.55564
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
subte_estaciones %>% 
  sample_n(5)
Simple feature collection with 5 features and 3 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: -58.48101 ymin: -34.62192 xmax: -58.37992 ymax: -34.5778
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
  ID                 ESTACION LINEA                    geometry
1 74 DE LOS INCAS -PQUE. CHAS     B POINT (-58.47424 -34.58125)
2 58                 SAN JUAN     C POINT (-58.37992 -34.62192)
3 18            INDEPENDENCIA     C POINT (-58.38017 -34.61813)
4 80               ECHEVERR�A     B  POINT (-58.48101 -34.5778)
5 46            INDEPENDENCIA     E POINT (-58.38153 -34.61794)

Con geom_sf agregamos diferentes capas de información geográfica:

  • los barrios: coloreados según su proporción de hogares con necesidades básicas insatisfechas, sin color de borde.
  • las líneas de subte: líneas en amarillo
  • las estaciones de subte: puntos en naranja

Alguna reflexión sobre los resultados?

ggplot() +
    geom_sf(data = barrios_geo, aes(fill = HOGARES_NBI/HOGARES), color = NA) +
    geom_sf(data = subte_lineas, color = "yellow") +
    geom_sf(data = subte_estaciones, color = "orange") +
  theme(legend.position = 'bottom')+
  scale_fill_viridis_c()+
    labs(title = "Sistema de transporte subterráneo (SUBTE)",
         subtitle = "Ciudad de Buenos Aires")

Mapeo de Palos borrachos rosados en Buenos Aires

Ahora utilizaremos información del portal de datos abiertos de la Ciudad de Buenos Aires sobre arbolado. Nos hemos quedado con una selección de dicha base de datos, conservando sólo a los árboles de tipo “palo borracho rosado”.

Notemos que en este caso la información está en formato .rds, es un dataframe donde los ejemplares están georreferenciados con variables long-lat, no hay una geometría como en los casos anteriores.

palos_borrachos <- read_rds('../fuentes/arbolado_palo_borracho.rds')

palos_borrachos %>% 
  sample_n(5)

Transformación de datos tabulados a datos sf

Para transformar los datos a tipo sf, tenemos la función st_as_sf(), a la cual le indicamos en el parámetro coords las variables que contienen las coordenadas. Con la función st_set_crs() podemos indicar el sistema de coordenadas de referencia.

palos_borrachos <- st_as_sf(palos_borrachos, coords = c('long','lat')) %>% 
    st_set_crs(4326)

palos_borrachos %>% 
  sample_n(5)
Simple feature collection with 5 features and 18 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: -58.51574 ymin: -34.66754 xmax: -58.4335 ymax: -34.54007
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs

Ahora podemos mapear los palos borrachos en los barrios de CABA que ya estuvimos trabajando!

ggplot() +
  geom_sf(data = barrios_geo) +
  geom_sf(data = palos_borrachos, color = "darkgreen", size=.5) +
  labs(title = 'localización de los palos borrachos de Buenos Aires')+
  theme_void()+
  theme(legend.position = 'none')

Join espacial

Ahora probemos la forma de unir la información contenida en palos_borrachos con la de barrios_geo. Para eso usamos st_join(), pero… en qué orden deberíamos unirlos?

palos_borrachos %>% 
  st_join(barrios_geo) %>% 
  sample_n(5)
Simple feature collection with 5 features and 24 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: -58.4673 ymin: -34.63316 xmax: -58.36219 ymax: -34.59118
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
barrios_geo %>% 
  st_join(palos_borrachos) %>% 
  sample_n(5)
Simple feature collection with 5 features and 24 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: -58.50844 ymin: -34.62557 xmax: -58.33925 ymax: -34.53199
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs

Si queremos visualizar la densidad de borrachos por barrio:

palos_borrachos_barrio <- barrios_geo %>% 
  st_join(palos_borrachos)

palos_borrachos_barrio %>% 
  group_by(BARRIO) %>% 
  summarise(densidad=n()/unique(AREA_KM2)) %>% 
  ggplot(aes(fill=densidad, label=BARRIO))+
  geom_sf()+
  geom_sf_text( color='white')+
  theme_void()

LS0tCnRpdGxlOiBJbnRyb2R1Y2Npw7NuIGEgZGF0b3MgZ2VvZ3LDoWZpY29zIGVuIFIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKZGF0ZTogIiIKc3VidGl0bGU6IFByw6FjdGljYSBHdWlhZGEKLS0tCgo+IFJlaW5pY2lhciBSCgpDYXJnYW1vcyBsYXMgbGlicmVyw61hcy4gRW4gZXN0YSBvY2FzacOzbiB1c2FyZW1vcyBwb3IgcHJpbWVyYSB2ZXogZWwgcGFxdWV0ZSBgc2ZgLgoKYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeShzZikKYGBgCgojIyMgUmVwcmVzZW50YWNpw7NuIGRlIGxhIGluZm9ybWFjacOzbiBlbiBtYXBhcwoKVHJhYmFqYXJlbW9zIGNvbiBkYXRvcyBkZWwgcG9ydGFsIGRlIGRhdG9zIGFiaWVydG9zIGRlIGxhIENpdWRhZCBkZSBCdWVub3MgQWlyZXMuIEVuIGVzdGUgY2FzbywgdXNhbW9zIGxvcyByYWRpb3MgY2Vuc2FsZXMgZGUgbGEgY2l1ZGFkIHkgbG9zIGhlbW9zIGRlc2NhcmdhZG8gZGUgaHR0cHM6Ly9iaXRzYW5kYnJpY2tzLmdpdGh1Yi5pby9kYXRhL0NBQkFfcmMuZ2VvanNvbi4gICAKCkxhIGZ1bmNpw7NuIGBzdF9yZWFkYCBub3MgcGVybWl0ZSBsZXZhbnRhciB1biBhcmNoaXZvIGRlIHRpcG8gXy5nZW9qc29uXy4gKHRhbWJpw6luIHBlcm1pdGUgY2FyZ2FyIG90cm9zIGNvbiBjYXBhcywgcGVybyBubyBsbyB2ZXJlbW9zIGVuIGVzdGEgb2Nhc2nDs24pLiAgICAKClZlbW9zIHF1ZSBlbCBtaXNtbyBjdWVudGEgY29uIDMuNTU0IF9mZWF0dXJlc18geSA4IGNhbXBvcy4gICAgIApgZXBzZyAoU1JJRCk6IDQzMjZgIHkgYHByb2o0c3RyaW5nOiArcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCArbm9fZGVmc2AgcmVmaWVyZW4gYSBxdWUgbnVlc3Ryb3MgZGF0b3MgdXNhbiBlbCBzaXN0ZW1hIGRlIGNvb3JkZW5hZGFzIFdHUzg0LCB0YW1iacOpbiBjb25vY2lkbyBwb3Igc3UgY8OzZGlnbyBFUFNHIDQzMjYgLiBFcyBlbCBtaXNtbyBxdWUgdXNhbiBsb3Mgc2lzdGVtYXMgR1BTLCBHb29nbGUgTWFwcywgeSBsYXMgYXBsaWNhY2lvbmVzIGRlIGludGVybmV0IGVuIGdlbmVyYWwuCgpgYGB7ciBlY2hvPVRSVUV9CnJhZGlvcyA8LSBzdF9yZWFkKCIuLi9mdWVudGVzL0NBQkFfcmMuZ2VvanNvbiIpCmBgYAoKQ29tbyBsb3MgOCBjYW1wb3Mgc29uIGVxdWl2YWxlbnRlcyBhIGNvbHVtbmFzL3ZhcmlhYmxlcywgcG9kZW1vcyBwZWRpciB1biBzdW1tYXJ5IGRlIGxhIGluZm9ybWFjacOzbiBjb250ZW5pZGEgZW4gYHJhZGlvc2AuCgpgYGB7ciBlY2hvPVRSVUV9CnN1bW1hcnkocmFkaW9zKQpgYGAKCkEgbGEgaG9yYSBkZSBncmFmaWNhciwgcG9kZW1vcyByZXByZXNlbnRhciBsYSBpbmZvcm1hY2nDs24gdXRpbGl6YW5kbyBsYSBjb21iaW5hY2nDs24gZGVsIGZhbW9zbyBgZ2dwbG90KClgIGVuIGNvbmp1bnRvIGNvbiBgZ2VvbV9zZmAsIHF1ZSBpZGVudGlmaWNhIGxhIHZhcmlhYmxlIGNvbiBsYSBnZW9tZXRyw61hIHkgbGEgZ3JhZmljYS4KCmBgYHtyIGVjaG89VFJVRX0KZ2dwbG90KCkgKyAKICBnZW9tX3NmKGRhdGEgPSByYWRpb3MpCmBgYAoKClBvZGVtb3MgY29sb3JlYXIgbG9zIHJhZGlvcyBjZW5zYWxlcyBkZSBhY3VlcmRvIGEgbGEgY2FudGlkYWQgZGUgdml2aWVuZGFzIHF1ZSBoYXkgZW4gY2FkYSB1bmEgZGUgZWxsYXMsIGNvbnZvY2FuZG8gYSBsYSB2YXJpYWJsZSBlbiBlbCBwYXLDoW1ldHJvIGRlIHJlbGxlbm8uIEEgc3UgdmV6LCBwb2RlbW9zICJqdWdhciIgY29uIGVsIGNvbG9yIHkgYW5jaG8gZGUgbG9zIGJvcmRlcy4KCmBgYHtyIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSByYWRpb3MsIGFlcyhmaWxsID0gVklWSUVOREFTKSkgKyAKICBnZW9tX3NmKCkgCgpnZ3Bsb3QoZGF0YSA9IHJhZGlvcywgYWVzKGZpbGwgPSBWSVZJRU5EQVMpKSArIAogIGdlb21fc2YoY29sb3IgPSAid2hpdGUiKSAKCmdncGxvdChkYXRhID0gcmFkaW9zLCBhZXMoZmlsbCA9IFZJVklFTkRBUykpICsKICBnZW9tX3NmKGNvbG9yID0gIndoaXRlIiwgc2l6ZT0uMSkgCmBgYAoKTyBiaWVuIHBvZGVtb3Mgb21pdGlyIGxvcyBib3JkZXMuCgpgYGB7ciBlY2hvPVRSVUV9CmdncGxvdChkYXRhID0gcmFkaW9zLCBhZXMoZmlsbCA9IFBPQkxBQ0lPTikpICsgCiAgZ2VvbV9zZihjb2xvciA9IE5BKQpgYGAKCgpUYW1iacOpbiBwb2RlbW9zIHVzYXIgZWwgY2FtcG8gZGVsIHJlbGxlbm8gcGFyYSByZWFsaXphciB1bmEgdHJhbnNmb3JtYWNpw7NuIGRlIGxvcyBkYXRvcy4gUG9yIGVqZW1wbG8sIHBvZGVtb3MgY2FsY3VsYXIgbGEgZGVuc2lkYWQgZGUgbGEgcG9ibGFjacOzbiB1dGlsaXphbmRvIGxvcyBkYXRvcyBkZSBgUE9CTEFDSU9OYCB5IGBBUkVBX0tNMmAuIEFwcm92ZWNoYW1vcyBwYXJhIG1lam9yYXIgb3RyYXMgY3Vlc3Rpb25lcyBlc3TDqXRpY2FzIGByIGVtbzo6amkoInJhaW5ib3ciKWAKCmBgYHtyIGVjaG89VFJVRX0KZ2dwbG90KGRhdGEgPSByYWRpb3MsIGFlcyhmaWxsID0gUE9CTEFDSU9OL0FSRUFfS00yKSkgKyAKICAgIGdlb21fc2YoY29sb3IgPSBOQSkgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSArCiAgICBsYWJzKHRpdGxlID0gIkRlbnNpZGFkIGRlIHBvYmxhY2nDs24iLAogICAgICAgICBzdWJ0aXRsZSA9ICJDaXVkYWQgQXV0w7Nub21hIGRlIEJ1ZW5vcyBBaXJlcyIsCiAgICAgICAgIGZpbGwgPSAiaGFiL2ttMiIpICsKICB0aGVtZV92b2lkKCkKYGBgCgoKVGFtYmnDqW4gcG9kZW1vcyByZWxsZW5hciBkZSBhY3VlcmRvIGEgbGEgdmFyaWFibGUgYEJBUlJJT2AsIHF1ZSBlcyB1bmEgZm9ybWEgZGUgZ3JhZmljYXIgYWdyZWdhZG9zLiBEZSB0b2RhcyBmb3JtYXMsIGx1ZWdvIHZlcmVtb3MgY29tbyBhZ3J1cGFybG9zIGNvbiBgZ3JvdXBfYnlgLgoKYGBge3J9CmdncGxvdChkYXRhID0gcmFkaW9zLCBhZXMoZmlsbCA9IEJBUlJJTykpICsgCiAgZ2VvbV9zZihjb2xvciA9IE5BKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKQpgYGAKCgpUYW1iacOpbiBwb2RlbW9zIHJlcHJlc2VudGFyIHLDoXBpZGFtZW50ZSBsYSBpbmZvcm1hY2nDs24gZGUgYWxndW5hIGRlIGxhcyB2YXJpYWJsZXMgcmVhbGl6YW5kbyB1biBgc2VsZWN0KClgIGRlIGxhIG1pc21hIHkgbGEgZ2VvbWV0csOtYSwgeSBwaWRpZW5kbyB1biBgcGxvdCgpYCAoZWwgcGFyw6FtZXRybyBgbHdkYCBkZWZpbmUgZWwgYW5jaG8gZGUgbGFzIGzDrW5lYXMgZGUgY2FkYSBnZW9tZXRyw61hLCBlbiBlc3RlIGNhc28sIGxvcyByYWRpb3MgY2Vuc2FsZXMpLiBFbiB0w6lybWlub3MgdmlzdWFsZXMsIGVzIGNvbW8gaGFiZXIgYWdyZWdhZG8gcG9yIGBCQVJSSU9gIGxhIGluZm9ybWFjacOzbiwgbm8/CgpgYGB7cn0KcmFkaW9zICU+JSAKICBzZWxlY3QoQkFSUklPLCBnZW9tZXRyeSkgJT4lIAogIHBsb3QobHdkPTAuMDYpCmBgYAoKCiMjIyMgQWdydXBhbmRvIHBvbMOtZ29ub3MKClNpbiBlbWJhcmdvLCBzaSBsbyBxdWUgcXVlcmVtb3MgZXMgcmVjb25zdHJ1aXIgbG9zIHBvbMOtZ29ub3MgZGUgbG9zIGJhcnJpb3MsIHPDs2xvIG5lY2VzaXRhbW9zIGhhY2VyIHVuIGBncm91cF9ieWAgeSB1biBgc3VtbWFyaXNlYCwgeSBhdXRvbcOhdGljYW1lbnRlIGVuIGxhIGNvbHVtbmEgYGdlb21ldHJ5YCBzZSBjcmVhIGVsIHBvbMOtZ29ubyBjb21iaW5hZG8uIEFkZW3DoXMsIHBvZGVtb3MgaGFjZXIgdW5hIGFncmVnYWNpw7NuIGRlIGxhcyBkZW3DoXMgdmFyaWFibGVzLgoKYGBge3J9CnJhZGlvcyAlPiUgCiAgc2FtcGxlX24oNSkKYGBgCgpgYGB7cn0KYmFycmlvc19nZW8gPC0gcmFkaW9zICU+JSAKICAgIGdyb3VwX2J5KEJBUlJJTykgJT4lIAogICAgc3VtbWFyaXNlKFBPQkxBQ0lPTiA9IHN1bShQT0JMQUNJT04pLAogICAgICAgICAgICAgIFZJVklFTkRBUyA9IHN1bShWSVZJRU5EQVMpLAogICAgICAgICAgICAgIEhPR0FSRVMgPSBzdW0oSE9HQVJFUyksCiAgICAgICAgICAgICAgSE9HQVJFU19OQkkgPSBzdW0oSE9HQVJFU19OQkkpLAogICAgICAgICAgICAgIEFSRUFfS00yID0gc3VtKEFSRUFfS00yKSkKCmJhcnJpb3NfZ2VvICU+JSAKICBzYW1wbGVfbig1KQpgYGAKClNpIHBlZGltb3MgdW4gYHBsb3QoKWAgZGVsIG9iamV0bywgbm9zIGRldnVlbHZlIGxhIHJlcHJlc2VudGFjacOzbiBnZW9ncsOhZmljYSBjb24gbG9zIGRhdG9zIGRlIGNhZGEgdW5hIGRlIGxhcyB2YXJpYWJsZXMuCgpgYGB7cn0KcGxvdChiYXJyaW9zX2dlbykKYGBgCgpTaSBkZXNlYW1vcyBhZ3JlZ2FyIG90cmEgY2FwYSBhbCBncsOhZmljbyBxdWUgaW5jbHV5YSB0ZXh0byBvIGV0aXF1ZXRhcyBkZSBsb3MgZGF0b3MsIGNvbnRhbW9zIGNvbiBgZ2VvbV9zZl90ZXh0KClgLCBlbiBsYSBjdWFsIHBvZGVtb3MgdGFtYmnDqW4gc29saWNpdGFyIHF1ZSBzZSBldGlxdWV0ZW4gc8OzbG8gYXF1ZWxsb3MgZGF0b3MgcXVlIGN1bXBsZW4gY29uIGFsZ3VuYSBjb25kaWNpw7NuLgoKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEwfQpnZ3Bsb3QoZGF0YSA9IGJhcnJpb3NfZ2VvLCBhZXMoZmlsbCA9IFBPQkxBQ0lPTi9BUkVBX0tNMikpICsKICAgIGdlb21fc2YoY29sb3IgPSBOQSkgKwogICAgZ2VvbV9zZl90ZXh0KGRhdGEgPSBiYXJyaW9zX2dlbyAlPiUgCiAgICAgICAgICAgICAgICAgICBmaWx0ZXIoUE9CTEFDSU9OL0FSRUFfS00yPjI1MDAwKSwgCiAgICAgICAgICAgICAgICAgYWVzKGxhYmVsID0gQkFSUklPKSwgc2l6ZT0zKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnYm90dG9tJykrCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gJ0MnKSArCiAgbGFicyh0aXRsZSA9ICJEZW5zaWRhZCBkZSBwb2JsYWNpw7NuIiwKICAgICAgICBzdWJ0aXRsZSA9ICJDaXVkYWQgQXV0w7Nub21hIGRlIEJ1ZW5vcyBBaXJlcyIsCiAgICAgICAgZmlsbCA9ICJoYWIva20yIikrCiAgdGhlbWVfdm9pZCgpCmBgYAoKIyMjIyBWb2xjYW5kbyBlbiBlbCBtYXBhIGluZm9ybWFjacOzbiBkZSBtw7psdGlwbGVzIGZ1ZW50ZXM6IFN1YnRlcwoKQWhvcmEgdXRpbGl6YXJlbW9zIGxhIGluZm9ybWFjacOzbiBkZSBsw61uZWFzIHkgZXN0YWNpb25lcyBkZSBzdWJ0ZS4gSGVtb3MgZGVzY2FyZ2FkbyBsb3MgLmdlb2pzb24gZGUgaHR0cDovL2JpdHNhbmRicmlja3MuZ2l0aHViLmlvL2RhdGEvc3VidGVfbGluZWFzLmdlb2pzb24geSBodHRwOi8vYml0c2FuZGJyaWNrcy5naXRodWIuaW8vZGF0YS9zdWJ0ZV9lc3RhY2lvbmVzLmdlb2pzb24uCgpgYGB7cn0Kc3VidGVfbGluZWFzIDwtIHN0X3JlYWQoJy4uL2Z1ZW50ZXMvc3VidGVfbGluZWFzLmdlb2pzb24nKQpzdWJ0ZV9saW5lYXMgJT4lIAogIHNhbXBsZV9uKDUpCmBgYAoKYGBge3J9CnN1YnRlX2VzdGFjaW9uZXMgPC0gc3RfcmVhZCgnLi4vZnVlbnRlcy9zdWJ0ZV9lc3RhY2lvbmVzLmdlb2pzb24nKQoKc3VidGVfZXN0YWNpb25lcyAlPiUgCiAgc2FtcGxlX24oNSkKYGBgCgpDb24gYGdlb21fc2ZgIGFncmVnYW1vcyBkaWZlcmVudGVzIGNhcGFzIGRlIGluZm9ybWFjacOzbiBnZW9ncsOhZmljYTogCgotIGxvcyBiYXJyaW9zOiBjb2xvcmVhZG9zIHNlZ8O6biBzdSBwcm9wb3JjacOzbiBkZSBob2dhcmVzIGNvbiBuZWNlc2lkYWRlcyBiw6FzaWNhcyBpbnNhdGlzZmVjaGFzLCBzaW4gY29sb3IgZGUgYm9yZGUuCi0gbGFzIGzDrW5lYXMgZGUgc3VidGU6IGzDrW5lYXMgZW4gYW1hcmlsbG8KLSBsYXMgZXN0YWNpb25lcyBkZSBzdWJ0ZTogcHVudG9zIGVuIG5hcmFuamEgICAgICAgCgpBbGd1bmEgcmVmbGV4acOzbiBzb2JyZSBsb3MgcmVzdWx0YWRvcz8KCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD03fQpnZ3Bsb3QoKSArCiAgICBnZW9tX3NmKGRhdGEgPSBiYXJyaW9zX2dlbywgYWVzKGZpbGwgPSBIT0dBUkVTX05CSS9IT0dBUkVTKSwgY29sb3IgPSBOQSkgKwogICAgZ2VvbV9zZihkYXRhID0gc3VidGVfbGluZWFzLCBjb2xvciA9ICJ5ZWxsb3ciKSArCiAgICBnZW9tX3NmKGRhdGEgPSBzdWJ0ZV9lc3RhY2lvbmVzLCBjb2xvciA9ICJvcmFuZ2UiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScpKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkrCiAgICBsYWJzKHRpdGxlID0gIlNpc3RlbWEgZGUgdHJhbnNwb3J0ZSBzdWJ0ZXJyw6FuZW8gKFNVQlRFKSIsCiAgICAgICAgIHN1YnRpdGxlID0gIkNpdWRhZCBkZSBCdWVub3MgQWlyZXMiKQpgYGAKCgoKCiMjIyBNYXBlbyBkZSBQYWxvcyBib3JyYWNob3Mgcm9zYWRvcyBlbiBCdWVub3MgQWlyZXMKCiFbXShpbWcvUGFsbyBib3JyYWNobyByb3NhZG8uanBnKXt3aWR0aD01MDB9CgpBaG9yYSB1dGlsaXphcmVtb3MgaW5mb3JtYWNpw7NuIGRlbCBwb3J0YWwgZGUgZGF0b3MgYWJpZXJ0b3MgZGUgbGEgQ2l1ZGFkIGRlIEJ1ZW5vcyBBaXJlcyBzb2JyZSBhcmJvbGFkby4gTm9zIGhlbW9zIHF1ZWRhZG8gY29uIHVuYSBzZWxlY2Npw7NuIGRlIGRpY2hhIGJhc2UgZGUgZGF0b3MsIGNvbnNlcnZhbmRvIHPDs2xvIGEgbG9zIMOhcmJvbGVzIGRlIHRpcG8gInBhbG8gYm9ycmFjaG8gcm9zYWRvIi4gICAgCgpOb3RlbW9zIHF1ZSBlbiBlc3RlIGNhc28gbGEgaW5mb3JtYWNpw7NuIGVzdMOhIGVuIGZvcm1hdG8gLnJkcywgZXMgdW4gZGF0YWZyYW1lIGRvbmRlIGxvcyBlamVtcGxhcmVzIGVzdMOhbiBnZW9ycmVmZXJlbmNpYWRvcyBjb24gdmFyaWFibGVzIGxvbmctbGF0LCBubyBoYXkgdW5hIGdlb21ldHLDrWEgY29tbyBlbiBsb3MgY2Fzb3MgYW50ZXJpb3Jlcy4KCmBgYHtyfQpwYWxvc19ib3JyYWNob3MgPC0gcmVhZF9yZHMoJy4uL2Z1ZW50ZXMvYXJib2xhZG9fcGFsb19ib3JyYWNoby5yZHMnKQoKcGFsb3NfYm9ycmFjaG9zICU+JSAKICBzYW1wbGVfbig1KQpgYGAKCiMjIyMgVHJhbnNmb3JtYWNpw7NuIGRlIGRhdG9zIHRhYnVsYWRvcyBhIGRhdG9zIHNmCgpQYXJhIHRyYW5zZm9ybWFyIGxvcyBkYXRvcyBhIHRpcG8gYHNmYCwgdGVuZW1vcyBsYSBmdW5jacOzbiBgc3RfYXNfc2YoKWAsIGEgbGEgY3VhbCBsZSBpbmRpY2Ftb3MgZW4gZWwgcGFyw6FtZXRybyBgY29vcmRzYCBsYXMgdmFyaWFibGVzIHF1ZSBjb250aWVuZW4gbGFzIGNvb3JkZW5hZGFzLiBDb24gbGEgZnVuY2nDs24gYHN0X3NldF9jcnMoKWAgcG9kZW1vcyBpbmRpY2FyIGVsIHNpc3RlbWEgZGUgY29vcmRlbmFkYXMgZGUgcmVmZXJlbmNpYS4KCmBgYHtyfQpwYWxvc19ib3JyYWNob3MgPC0gc3RfYXNfc2YocGFsb3NfYm9ycmFjaG9zLCBjb29yZHMgPSBjKCdsb25nJywnbGF0JykpICU+JSAKICAgIHN0X3NldF9jcnMoNDMyNikKCnBhbG9zX2JvcnJhY2hvcyAlPiUgCiAgc2FtcGxlX24oNSkKYGBgCgpBaG9yYSBwb2RlbW9zIG1hcGVhciBsb3MgcGFsb3MgYm9ycmFjaG9zIGVuIGxvcyBiYXJyaW9zIGRlIENBQkEgcXVlIHlhIGVzdHV2aW1vcyB0cmFiYWphbmRvIQoKYGBge3IgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9N30KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IGJhcnJpb3NfZ2VvKSArCiAgZ2VvbV9zZihkYXRhID0gcGFsb3NfYm9ycmFjaG9zLCBjb2xvciA9ICJkYXJrZ3JlZW4iLCBzaXplPS41KSArCiAgbGFicyh0aXRsZSA9ICdsb2NhbGl6YWNpw7NuIGRlIGxvcyBwYWxvcyBib3JyYWNob3MgZGUgQnVlbm9zIEFpcmVzJykrCiAgdGhlbWVfdm9pZCgpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykKYGBgCgojIyMjIEpvaW4gZXNwYWNpYWwKCkFob3JhIHByb2JlbW9zIGxhIGZvcm1hIGRlIHVuaXIgbGEgaW5mb3JtYWNpw7NuIGNvbnRlbmlkYSBlbiBgcGFsb3NfYm9ycmFjaG9zYCBjb24gbGEgZGUgYGJhcnJpb3NfZ2VvYC4gUGFyYSBlc28gdXNhbW9zIGBzdF9qb2luKClgLCBwZXJvLi4uIGVuIHF1w6kgb3JkZW4gZGViZXLDrWFtb3MgdW5pcmxvcz8KCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGFsb3NfYm9ycmFjaG9zICU+JSAKICBzdF9qb2luKGJhcnJpb3NfZ2VvKSAlPiUgCiAgc2FtcGxlX24oNSkKYGBgCgpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmJhcnJpb3NfZ2VvICU+JSAKICBzdF9qb2luKHBhbG9zX2JvcnJhY2hvcykgJT4lIAogIHNhbXBsZV9uKDUpCmBgYAoKU2kgcXVlcmVtb3MgdmlzdWFsaXphciBsYSBkZW5zaWRhZCBkZSBib3JyYWNob3MgcG9yIGJhcnJpbzoKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGFsb3NfYm9ycmFjaG9zX2JhcnJpbyA8LSBiYXJyaW9zX2dlbyAlPiUgCiAgc3Rfam9pbihwYWxvc19ib3JyYWNob3MpCgpwYWxvc19ib3JyYWNob3NfYmFycmlvICU+JSAKICBncm91cF9ieShCQVJSSU8pICU+JSAKICBzdW1tYXJpc2UoZGVuc2lkYWQ9bigpL3VuaXF1ZShBUkVBX0tNMikpICU+JSAKICBnZ3Bsb3QoYWVzKGZpbGw9ZGVuc2lkYWQsIGxhYmVsPUJBUlJJTykpKwogIGdlb21fc2YoKSsKICBnZW9tX3NmX3RleHQoIGNvbG9yPSd3aGl0ZScpKwogIHRoZW1lX3ZvaWQoKQpgYGAKCg==