Funciones de scrapeo

La idea de este breve artículo es mostrar una función para descargar datos desde el sitio del Servicio Meteorológico Nacional SMN y ,al mismo tiempo, entrenar el uso de funciones y bucles para un fin determinado.

Ideas

En la sección Descarga del Catálogo de Datos Abiertos del SMN y luego de dar conformidad al acuerdo, es posible acceder a los datos diarios que el servicio pone a disposición, tal cual se ve en la siguiente imágen:

Puede apreciarse todos los tipos de datos que brinda el sitio. A los fines de este artículo, optaremos por descargar los datos horarios (DF) a los cuales es posible acceder “clickeando” en el día buscado. Se puede ver que el enlace que lleva a la descarga del dato contiene su fecha:

Por lo tanto, si lo que buscamos es efectuar varias descargas (por ejemplo todo un mes de observaciones) es necesario generar un objeto fecha que contenga los días buscados para automatizar las descargas mediante un bucle.

Objetivo

Generar un data frame que contenga las temperaturas mínimas y máximas de cada día junto con el valor de humedad promedio

Arranquemos!

Instalación de librerías


#se borran todos los objetos de la memoria

rm(list =ls())

#Se instalan librerías de trabajo

library(tidyverse) # Para limpieza y manipulación de datos
library(lubridate) # Para trabajar con fechas

setwd("C:/Users/Guille/Documents/Proyectos/WebScrapping/clima/Scrap_smn")

Pensando la función

En este ejemplo vamos a intentar descargar observaciones desde el 01-07-2023 hasta el 31-07-2023 en la Ciudad Autónoma de Buenos Aires. Cada información diaria descargada contiene la información por hora y por observatorio. Vamos a tomar los primeros 24 valores correspondientes al observatorio de Aeroparque. Vamos a definir los objetos fecha_inicial y fecha_final para generar una secuencia de fechas a las que, finalmente, limpiaremos para que queden con el formato del link de descarga


fecha_inicial <- ymd("2023-07-01")
fecha_final <- ymd("2023-07-31")

fechas <- seq.Date(fecha_inicial,fecha_final, by = "day") #aux para crear listado de fechas
list <- paste0(substr(fechas,1,4),substr(fechas,6,7),substr(fechas,9,10)) #lista que servirá de insumo para iterar

Bucle

Ya tenemos nuestra lista, ahora vamos pensar un bucle for que itere los elementos de la misma con el objetivo de descargar la información y devolver un data frame, pero antes generamos un df vacío llamado dataclima en donde incorporaremos nuestras descargas:


dataclima <- data.frame()

for (i in list){
  linkday <- glue::glue("https://ssl.smn.gob.ar/dpd/descarga_opendata.php?file=observaciones/datohorario{i}.txt") #link para descargar con la variable fecha
  day <- read.table(linkday, skip = 2, nrows = 24) #tabla con los datos descargados, se especifican las 23 primeras filas porque son las que corresponden a CABA
  dataclima <- rbind(dataclima,day) #se suman las filas diarias
  print(glue::glue("Fecha: {i}")) #a modo de control, se imprime las fechas de descagas
  print(glue::glue("se descargaron {nrow(dataclima)} datos"))
}
Fecha: 20230701
se descargaron 24 datos
Fecha: 20230702
se descargaron 48 datos
Fecha: 20230703
se descargaron 72 datos
Fecha: 20230704
se descargaron 96 datos
Fecha: 20230705
se descargaron 120 datos
Fecha: 20230706
se descargaron 144 datos
Fecha: 20230707
se descargaron 168 datos
Fecha: 20230708
se descargaron 192 datos
Fecha: 20230709
se descargaron 216 datos
Fecha: 20230710
se descargaron 240 datos
Fecha: 20230711
se descargaron 264 datos
Fecha: 20230712
se descargaron 288 datos
Fecha: 20230713
se descargaron 312 datos
Fecha: 20230714
se descargaron 336 datos
Fecha: 20230715
se descargaron 360 datos
Fecha: 20230716
se descargaron 384 datos
Fecha: 20230717
se descargaron 408 datos
Fecha: 20230718
se descargaron 432 datos
Fecha: 20230719
se descargaron 456 datos
Fecha: 20230720
se descargaron 480 datos
Fecha: 20230721
se descargaron 504 datos
Fecha: 20230722
se descargaron 528 datos
Fecha: 20230723
se descargaron 552 datos
Fecha: 20230724
se descargaron 576 datos
Fecha: 20230725
se descargaron 600 datos
Fecha: 20230726
se descargaron 624 datos
Fecha: 20230727
se descargaron 648 datos
Fecha: 20230728
se descargaron 672 datos
Fecha: 20230729
se descargaron 696 datos
Fecha: 20230730
se descargaron 720 datos
Fecha: 20230731
se descargaron 744 datos

Ya tenemos nuestro df con las observaciones, veamos que contiene


summary(dataclima)
       V1                 V2              V3              V4              V5             V6            V7             V8                 V9           
 Min.   : 1072023   Min.   : 0.00   Min.   : 4.30   Min.   :38.00   Min.   :1002   Min.   :  0   Min.   : 0.00   Length:744         Length:744        
 1st Qu.: 8072023   1st Qu.: 5.75   1st Qu.:10.20   1st Qu.:68.00   1st Qu.:1016   1st Qu.: 90   1st Qu.: 7.00   Class :character   Class :character  
 Median :16072023   Median :11.50   Median :12.50   Median :77.00   Median :1019   Median :145   Median :13.00   Mode  :character   Mode  :character  
 Mean   :16072023   Mean   :11.50   Mean   :12.60   Mean   :75.58   Mean   :1020   Mean   :214   Mean   :13.41                                        
 3rd Qu.:24072023   3rd Qu.:17.25   3rd Qu.:14.72   3rd Qu.:85.00   3rd Qu.:1024   3rd Qu.:300   3rd Qu.:17.50                                        
 Max.   :31072023   Max.   :23.00   Max.   :26.00   Max.   :97.00   Max.   :1034   Max.   :990   Max.   :41.00                                        
glimpse(dataclima)
Rows: 744
Columns: 9
$ V1 <int> 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1072023, 1…
$ V2 <int> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19…
$ V3 <dbl> 13.4, 13.2, 13.0, 12.7, 12.4, 12.2, 12.0, 11.7, 11.6, 11.9, 13.5, 13.9, 13.8, 14.0, 13.4, 13.5, 14.1, 13.9, 12.6, 12.8, 12.9, 12.8, 13.1, 13.5, 13.7, 14.1…
$ V4 <int> 74, 75, 76, 77, 79, 79, 81, 83, 85, 84, 80, 78, 79, 79, 82, 80, 78, 77, 87, 87, 88, 92, 91, 88, 87, 86, 89, 86, 94, 90, 90, 90, 91, 89, 88, 83, 84, 83, 84…
$ V5 <dbl> 1024.2, 1023.6, 1023.8, 1023.5, 1023.0, 1022.4, 1022.4, 1022.4, 1023.0, 1023.5, 1023.8, 1024.0, 1022.7, 1022.1, 1021.3, 1020.9, 1020.3, 1019.9, 1020.0, 10…
$ V6 <int> 50, 50, 40, 20, 30, 30, 20, 360, 10, 990, 70, 40, 60, 40, 70, 90, 90, 100, 100, 100, 90, 100, 90, 80, 50, 10, 350, 50, 90, 90, 80, 80, 60, 60, 60, 60, 70,…
$ V7 <int> 13, 13, 9, 6, 6, 6, 7, 11, 6, 4, 6, 7, 9, 7, 9, 13, 17, 17, 17, 19, 17, 19, 17, 13, 9, 7, 7, 9, 9, 11, 13, 11, 11, 13, 13, 13, 9, 13, 13, 19, 15, 7, 11, 1…
$ V8 <chr> "AEROPARQUE", "AEROPARQUE", "AEROPARQUE", "AEROPARQUE", "AEROPARQUE", "AEROPARQUE", "AEROPARQUE", "AEROPARQUE", "AEROPARQUE", "AEROPARQUE", "AEROPARQUE", …
$ V9 <chr> "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "AERO", "A…

Dado que decidimos esquivar las primeras filas para no obtener errores, no sabemos los nombres de las columnas, Vamos a descargar solamente un día para obtener el nombre d de las variables.


linkprueba <- glue::glue("https://ssl.smn.gob.ar/dpd/descarga_opendata.php?file=observaciones/datohorario20240702.txt") #link para descargar con la variable fecha
  
dayprueba <- read.table(linkprueba,nrows = 1)

dayprueba
NA

Limpieza de resultados

Ya tenemos los nombres y podemos ver toda la información que brinda el smn en la modalidad datos horarios. A los fines de cumplir con el objetivo planteado. Vamos a crear un dataframe tomando el día como unidad y captando la temperatura máxima, la mínima y la media de humedad.


df <- dataclima %>% 
  select(Fecha = V1,
         Hora = V2,
         Temp = V3,
         Humedad = V4) %>% 
  group_by(Fecha) %>% 
  summarise(Temp_Min = min(Temp),
            Temp_Max = max(Temp),
            Humedad_Media = round(mean(Humedad),2)) %>% 
  mutate(Fecha = dmy(Fecha)) %>% 
  arrange(Fecha)

df 
NA

Graficando los resultados

Vamos a graficar nuestros datos, para lo cual procedemos a pivotear la tabla


library(ggtext)

tabla1 <- df %>% 
  pivot_longer(cols = c(Temp_Min,Temp_Max,Humedad_Media),
               names_to = "Variables",
               values_to = "Valores") 

ggplot(tabla1, aes(x = Fecha, y = Valores, color = Variables))+
  geom_line()+
  geom_point()+
  scale_color_manual(values = c("Temp_Min" = "#118ab2",
                                "Temp_Max" = "#ef476f",
                                "Humedad_Media" = "#122c91"))+
  labs(title = "Valores climáticos del mes de julio-2023 de<br><b style='color:#122c91;'>Humedad</b>, <b style='color:#ef476f;'>Temperatura Máxima</b> y <b style='color:#118ab2;'>Temperatura Mínima</b>",
       caption = "<b>Fuente</b>: Servicio Meteorológico Nacional")+
  theme_minimal()+
  theme(legend.position = "none", 
        panel.grid.minor = element_blank(),
        plot.title = element_markdown(margin=margin(0,0,10,-30), 
                                      size=12),
        plot.caption = element_markdown(size = 11))

NA
NA
NA

Creando la función

Para finalizar, podemos armar una función con todos los pasos del proceso


get_datos_horarios <- function(fecha_ini,fecha_fin){

fecha_inicial <- ymd(fecha_ini)
fecha_final <- ymd(fecha_fin)
fechas <- seq.Date(fecha_inicial,fecha_final, by = "day") #aux para crear listado de fechas
list <- paste0(substr(fechas,1,4),substr(fechas,6,7),substr(fechas,9,10)) #lista que servirá de insumo para iterar

dataclima <- data.frame()

for (i in list){
  linkday <- glue::glue("data/datohorario{i}.txt") #link para descargar con la variable fecha
 
  day <- read.table(linkday, skip = 2, nrows = 24) #tabla con los datos descargados, se especifican las 23 primeras filas porque son las que corresponden a CABA
  dataclima <- rbind(dataclima,day) #se suman las filas diarias
  print(glue::glue("Fecha: {i}")) #a modo de control, se imprime las fechas de descagas
  print(glue::glue("se descargaron {nrow(dataclima)} datos horarios"))
}

dataclima <- dataclima %>%
  select(Fecha = V1,
         Hora = V2,
         Temp = V3,
         Humedad = V4) %>%
  group_by(Fecha) %>%
  summarise(Temp_Min = min(Temp),
            Temp_Max = max(Temp),
            Humedad_Media = round(mean(Humedad),2)) %>%
  mutate(Fecha = dmy(Fecha)) %>%
  arrange(Fecha)

return(dataclima)

}
LS0tDQp0aXRsZTogIlNjcmFwZW8gU01OIg0KYXV0aG9yOiAiSnVhbiBHdWlsbGVybW8gRmVyY2hlcm8iDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIGRlcHRoOiA0DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KDQpgYGANCg0KIyMgRnVuY2lvbmVzIGRlIHNjcmFwZW8NCg0KTGEgaWRlYSBkZSBlc3RlIGJyZXZlIGFydMOtY3VsbyBlcyBtb3N0cmFyIHVuYSBmdW5jacOzbiBwYXJhIGRlc2NhcmdhciBkYXRvcyBkZXNkZSBlbCBzaXRpbyBkZWwgU2VydmljaW8gTWV0ZW9yb2zDs2dpY28gTmFjaW9uYWwgW1NNTl0oaHR0cHM6Ly93d3cuc21uLmdvYi5hci8pIHkgLGFsIG1pc21vIHRpZW1wbywgZW50cmVuYXIgZWwgdXNvIGRlIGZ1bmNpb25lcyB5IGJ1Y2xlcyBwYXJhIHVuIGZpbiBkZXRlcm1pbmFkby4NCg0KIyBJZGVhcw0KDQpFbiBsYSBzZWNjacOzbiBbRGVzY2FyZ2EgZGVsIENhdMOhbG9nbyBkZSBEYXRvcyBBYmllcnRvcyBkZWwgU01OXShodHRwczovL3d3dy5zbW4uZ29iLmFyL2Rlc2NhcmdhLWRlLWRhdG9zKSB5IGx1ZWdvIGRlIGRhciBjb25mb3JtaWRhZCBhbCBhY3VlcmRvLCBlcyBwb3NpYmxlIGFjY2VkZXIgYSBsb3MgZGF0b3MgZGlhcmlvcyBxdWUgZWwgc2VydmljaW8gcG9uZSBhIGRpc3Bvc2ljacOzbiwgdGFsIGN1YWwgc2UgdmUgZW4gbGEgc2lndWllbnRlIGltw6FnZW46DQoNCmBgYHtyIGVjaG89RkFMU0UsIG91dC53aWR0aCA9ICIzMCUiLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWcvaW1nMS5wbmciKQ0KYGBgDQoNClB1ZWRlIGFwcmVjaWFyc2UgdG9kb3MgbG9zIHRpcG9zIGRlIGRhdG9zIHF1ZSBicmluZGEgZWwgc2l0aW8uIEEgbG9zIGZpbmVzIGRlIGVzdGUgYXJ0w61jdWxvLCBvcHRhcmVtb3MgcG9yIGRlc2NhcmdhciBsb3MgZGF0b3MgaG9yYXJpb3MgKERGKSBhIGxvcyBjdWFsZXMgZXMgcG9zaWJsZSBhY2NlZGVyICJjbGlja2VhbmRvIiBlbiBlbCBkw61hIGJ1c2NhZG8uIFNlIHB1ZWRlIHZlciBxdWUgZWwgZW5sYWNlIHF1ZSBsbGV2YSBhIGxhIGRlc2NhcmdhIGRlbCBkYXRvIGNvbnRpZW5lIHN1IGZlY2hhOg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAiMzAlIiwgZmlnLmFsaWduID0gImNlbnRlciJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1nL2ltZzMucG5nIikNCmBgYA0KDQpQb3IgbG8gdGFudG8sIHNpIGxvIHF1ZSBidXNjYW1vcyBlcyBlZmVjdHVhciB2YXJpYXMgZGVzY2FyZ2FzIChwb3IgZWplbXBsbyB0b2RvIHVuIG1lcyBkZSBvYnNlcnZhY2lvbmVzKSBlcyBuZWNlc2FyaW8gZ2VuZXJhciB1biBvYmpldG8gZmVjaGEgcXVlIGNvbnRlbmdhIGxvcyBkw61hcyBidXNjYWRvcyBwYXJhIGF1dG9tYXRpemFyIGxhcyBkZXNjYXJnYXMgbWVkaWFudGUgdW4gYnVjbGUuDQoNCiMjIE9iamV0aXZvDQoNCioqR2VuZXJhciB1biBkYXRhIGZyYW1lIHF1ZSBjb250ZW5nYSBsYXMgdGVtcGVyYXR1cmFzIG3DrW5pbWFzIHkgbcOheGltYXMgZGUgY2FkYSBkw61hIGp1bnRvIGNvbiBlbCB2YWxvciBkZSBodW1lZGFkIHByb21lZGlvKioNCg0KQXJyYW5xdWVtb3MhDQoNCiMgSW5zdGFsYWNpw7NuIGRlIGxpYnJlcsOtYXMNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KI3NlIGJvcnJhbiB0b2RvcyBsb3Mgb2JqZXRvcyBkZSBsYSBtZW1vcmlhDQoNCnJtKGxpc3QgPWxzKCkpDQoNCiNTZSBpbnN0YWxhbiBsaWJyZXLDrWFzIGRlIHRyYWJham8NCg0KbGlicmFyeSh0aWR5dmVyc2UpICMgUGFyYSBsaW1waWV6YSB5IG1hbmlwdWxhY2nDs24gZGUgZGF0b3MNCmxpYnJhcnkobHVicmlkYXRlKSAjIFBhcmEgdHJhYmFqYXIgY29uIGZlY2hhcw0KDQpzZXR3ZCgiQzovVXNlcnMvR3VpbGxlL0RvY3VtZW50cy9Qcm95ZWN0b3MvV2ViU2NyYXBwaW5nL2NsaW1hL1NjcmFwX3NtbiIpDQoNCmBgYA0KDQojIFBlbnNhbmRvIGxhIGZ1bmNpw7NuDQoNCkVuIGVzdGUgZWplbXBsbyB2YW1vcyBhIGludGVudGFyIGRlc2NhcmdhciBvYnNlcnZhY2lvbmVzIGRlc2RlIGVsIDAxLTA3LTIwMjMgaGFzdGEgZWwgMzEtMDctMjAyMyBlbiBsYSBDaXVkYWQgQXV0w7Nub21hIGRlIEJ1ZW5vcyBBaXJlcy4gQ2FkYSBpbmZvcm1hY2nDs24gZGlhcmlhIGRlc2NhcmdhZGEgY29udGllbmUgbGEgaW5mb3JtYWNpw7NuIHBvciBob3JhIHkgcG9yIG9ic2VydmF0b3Jpby4gVmFtb3MgYSB0b21hciBsb3MgcHJpbWVyb3MgMjQgdmFsb3JlcyBjb3JyZXNwb25kaWVudGVzIGFsIG9ic2VydmF0b3JpbyBkZSBBZXJvcGFycXVlLiBWYW1vcyBhIGRlZmluaXIgbG9zIG9iamV0b3MgYGZlY2hhX2luaWNpYWxgIHkgYGZlY2hhX2ZpbmFsYCBwYXJhIGdlbmVyYXIgdW5hIHNlY3VlbmNpYSBkZSBmZWNoYXMgYSBsYXMgcXVlLCBmaW5hbG1lbnRlLCBsaW1waWFyZW1vcyBwYXJhIHF1ZSBxdWVkZW4gY29uIGVsIGZvcm1hdG8gZGVsIGxpbmsgZGUgZGVzY2FyZ2ENCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KZmVjaGFfaW5pY2lhbCA8LSB5bWQoIjIwMjMtMDctMDEiKQ0KZmVjaGFfZmluYWwgPC0geW1kKCIyMDIzLTA3LTMxIikNCg0KZmVjaGFzIDwtIHNlcS5EYXRlKGZlY2hhX2luaWNpYWwsZmVjaGFfZmluYWwsIGJ5ID0gImRheSIpICNhdXggcGFyYSBjcmVhciBsaXN0YWRvIGRlIGZlY2hhcw0KbGlzdCA8LSBwYXN0ZTAoc3Vic3RyKGZlY2hhcywxLDQpLHN1YnN0cihmZWNoYXMsNiw3KSxzdWJzdHIoZmVjaGFzLDksMTApKSAjbGlzdGEgcXVlIHNlcnZpcsOhIGRlIGluc3VtbyBwYXJhIGl0ZXJhcg0KDQpgYGANCg0KIyMgQnVjbGUNCg0KWWEgdGVuZW1vcyBudWVzdHJhIGxpc3RhLCBhaG9yYSB2YW1vcyBwZW5zYXIgdW4gYnVjbGUgZm9yIHF1ZSBpdGVyZSBsb3MgZWxlbWVudG9zIGRlIGxhIG1pc21hIGNvbiBlbCBvYmpldGl2byBkZSBkZXNjYXJnYXIgbGEgaW5mb3JtYWNpw7NuIHkgZGV2b2x2ZXIgdW4gZGF0YSBmcmFtZSwgcGVybyBhbnRlcyBnZW5lcmFtb3MgdW4gZGYgdmFjw61vIGxsYW1hZG8gZGF0YWNsaW1hIGVuIGRvbmRlIGluY29ycG9yYXJlbW9zIG51ZXN0cmFzIGRlc2NhcmdhczoNCg0KYGBge3IgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPUZBTFNFfQ0KDQpkYXRhY2xpbWEgPC0gZGF0YS5mcmFtZSgpDQoNCmZvciAoaSBpbiBsaXN0KXsNCiAgbGlua2RheSA8LSBnbHVlOjpnbHVlKCJodHRwczovL3NzbC5zbW4uZ29iLmFyL2RwZC9kZXNjYXJnYV9vcGVuZGF0YS5waHA/ZmlsZT1vYnNlcnZhY2lvbmVzL2RhdG9ob3Jhcmlve2l9LnR4dCIpICNsaW5rIHBhcmEgZGVzY2FyZ2FyIGNvbiBsYSB2YXJpYWJsZSBmZWNoYQ0KICBkYXkgPC0gcmVhZC50YWJsZShsaW5rZGF5LCBza2lwID0gMiwgbnJvd3MgPSAyNCkgI3RhYmxhIGNvbiBsb3MgZGF0b3MgZGVzY2FyZ2Fkb3MsIHNlIGVzcGVjaWZpY2FuIGxhcyAyMyBwcmltZXJhcyBmaWxhcyBwb3JxdWUgc29uIGxhcyBxdWUgY29ycmVzcG9uZGVuIGEgQ0FCQQ0KICBkYXRhY2xpbWEgPC0gcmJpbmQoZGF0YWNsaW1hLGRheSkgI3NlIHN1bWFuIGxhcyBmaWxhcyBkaWFyaWFzDQogIHByaW50KGdsdWU6OmdsdWUoIkZlY2hhOiB7aX0iKSkgI2EgbW9kbyBkZSBjb250cm9sLCBzZSBpbXByaW1lIGxhcyBmZWNoYXMgZGUgZGVzY2FnYXMNCiAgcHJpbnQoZ2x1ZTo6Z2x1ZSgic2UgZGVzY2FyZ2Fyb24ge25yb3coZGF0YWNsaW1hKX0gZGF0b3MiKSkNCn0NCg0KYGBgDQoNCllhIHRlbmVtb3MgbnVlc3RybyBkZiBjb24gbGFzIG9ic2VydmFjaW9uZXMsIHZlYW1vcyBxdWUgY29udGllbmUNCg0KYGBge3IgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPUZBTFNFfQ0KDQpzdW1tYXJ5KGRhdGFjbGltYSkNCmdsaW1wc2UoZGF0YWNsaW1hKQ0KDQpgYGANCg0KRGFkbyBxdWUgZGVjaWRpbW9zIGVzcXVpdmFyIGxhcyBwcmltZXJhcyBmaWxhcyBwYXJhIG5vIG9idGVuZXIgZXJyb3Jlcywgbm8gc2FiZW1vcyBsb3Mgbm9tYnJlcyBkZSBsYXMgY29sdW1uYXMsIFZhbW9zIGEgZGVzY2FyZ2FyIHNvbGFtZW50ZSB1biBkw61hIHBhcmEgb2J0ZW5lciBlbCBub21icmUgZCBkZSBsYXMgdmFyaWFibGVzLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQpsaW5rcHJ1ZWJhIDwtIGdsdWU6OmdsdWUoImh0dHBzOi8vc3NsLnNtbi5nb2IuYXIvZHBkL2Rlc2NhcmdhX29wZW5kYXRhLnBocD9maWxlPW9ic2VydmFjaW9uZXMvZGF0b2hvcmFyaW8yMDI0MDcwMi50eHQiKSAjbGluayBwYXJhIGRlc2NhcmdhciBjb24gbGEgdmFyaWFibGUgZmVjaGENCiAgDQpkYXlwcnVlYmEgPC0gcmVhZC50YWJsZShsaW5rcHJ1ZWJhLG5yb3dzID0gMSkNCg0KZGF5cHJ1ZWJhDQoNCmBgYA0KDQojIExpbXBpZXphIGRlIHJlc3VsdGFkb3MNCg0KWWEgdGVuZW1vcyBsb3Mgbm9tYnJlcyB5IHBvZGVtb3MgdmVyIHRvZGEgbGEgaW5mb3JtYWNpw7NuIHF1ZSBicmluZGEgZWwgc21uIGVuIGxhIG1vZGFsaWRhZCBkYXRvcyBob3Jhcmlvcy4gQSBsb3MgZmluZXMgZGUgY3VtcGxpciBjb24gZWwgb2JqZXRpdm8gcGxhbnRlYWRvLiBWYW1vcyBhIGNyZWFyIHVuIGRhdGFmcmFtZSB0b21hbmRvIGVsIGTDrWEgY29tbyB1bmlkYWQgeSBjYXB0YW5kbyBsYSB0ZW1wZXJhdHVyYSBtw6F4aW1hLCBsYSBtw61uaW1hIHkgbGEgbWVkaWEgZGUgaHVtZWRhZC4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KZGYgPC0gZGF0YWNsaW1hICU+JSANCiAgc2VsZWN0KEZlY2hhID0gVjEsDQogICAgICAgICBIb3JhID0gVjIsDQogICAgICAgICBUZW1wID0gVjMsDQogICAgICAgICBIdW1lZGFkID0gVjQpICU+JSANCiAgZ3JvdXBfYnkoRmVjaGEpICU+JSANCiAgc3VtbWFyaXNlKFRlbXBfTWluID0gbWluKFRlbXApLA0KICAgICAgICAgICAgVGVtcF9NYXggPSBtYXgoVGVtcCksDQogICAgICAgICAgICBIdW1lZGFkX01lZGlhID0gcm91bmQobWVhbihIdW1lZGFkKSwyKSkgJT4lIA0KICBtdXRhdGUoRmVjaGEgPSBkbXkoRmVjaGEpKSAlPiUgDQogIGFycmFuZ2UoRmVjaGEpDQoNCmRmIA0KDQpgYGANCg0KIyBHcmFmaWNhbmRvIGxvcyByZXN1bHRhZG9zDQoNClZhbW9zIGEgZ3JhZmljYXIgbnVlc3Ryb3MgZGF0b3MsIHBhcmEgbG8gY3VhbCBwcm9jZWRlbW9zIGEgcGl2b3RlYXIgbGEgdGFibGENCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KbGlicmFyeShnZ3RleHQpDQoNCnRhYmxhMSA8LSBkZiAlPiUgDQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhUZW1wX01pbixUZW1wX01heCxIdW1lZGFkX01lZGlhKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIlZhcmlhYmxlcyIsDQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiVmFsb3JlcyIpIA0KDQpnZ3Bsb3QodGFibGExLCBhZXMoeCA9IEZlY2hhLCB5ID0gVmFsb3JlcywgY29sb3IgPSBWYXJpYWJsZXMpKSsNCiAgZ2VvbV9saW5lKCkrDQogIGdlb21fcG9pbnQoKSsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIlRlbXBfTWluIiA9ICIjMTE4YWIyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRlbXBfTWF4IiA9ICIjZWY0NzZmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkh1bWVkYWRfTWVkaWEiID0gIiMxMjJjOTEiKSkrDQogIGxhYnModGl0bGUgPSAiVmFsb3JlcyBjbGltw6F0aWNvcyBkZWwgbWVzIGRlIGp1bGlvLTIwMjMgZGU8YnI+PGIgc3R5bGU9J2NvbG9yOiMxMjJjOTE7Jz5IdW1lZGFkPC9iPiwgPGIgc3R5bGU9J2NvbG9yOiNlZjQ3NmY7Jz5UZW1wZXJhdHVyYSBNw6F4aW1hPC9iPiB5IDxiIHN0eWxlPSdjb2xvcjojMTE4YWIyOyc+VGVtcGVyYXR1cmEgTcOtbmltYTwvYj4iLA0KICAgICAgIGNhcHRpb24gPSAiPGI+RnVlbnRlPC9iPjogU2VydmljaW8gTWV0ZW9yb2zDs2dpY28gTmFjaW9uYWwiKSsNCiAgdGhlbWVfbWluaW1hbCgpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIA0KICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF9tYXJrZG93bihtYXJnaW49bWFyZ2luKDAsMCwxMCwtMzApLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZT0xMiksDQogICAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfbWFya2Rvd24oc2l6ZSA9IDExKSkNCg0KDQoNCmBgYA0KDQojIENyZWFuZG8gbGEgZnVuY2nDs24NCg0KUGFyYSBmaW5hbGl6YXIsIHBvZGVtb3MgYXJtYXIgdW5hIGZ1bmNpw7NuIGNvbiB0b2RvcyBsb3MgcGFzb3MgZGVsIHByb2Nlc28NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KZ2V0X2RhdG9zX2hvcmFyaW9zIDwtIGZ1bmN0aW9uKGZlY2hhX2luaSxmZWNoYV9maW4pew0KDQpmZWNoYV9pbmljaWFsIDwtIHltZChmZWNoYV9pbmkpDQpmZWNoYV9maW5hbCA8LSB5bWQoZmVjaGFfZmluKQ0KZmVjaGFzIDwtIHNlcS5EYXRlKGZlY2hhX2luaWNpYWwsZmVjaGFfZmluYWwsIGJ5ID0gImRheSIpICNhdXggcGFyYSBjcmVhciBsaXN0YWRvIGRlIGZlY2hhcw0KbGlzdCA8LSBwYXN0ZTAoc3Vic3RyKGZlY2hhcywxLDQpLHN1YnN0cihmZWNoYXMsNiw3KSxzdWJzdHIoZmVjaGFzLDksMTApKSAjbGlzdGEgcXVlIHNlcnZpcsOhIGRlIGluc3VtbyBwYXJhIGl0ZXJhcg0KDQpkYXRhY2xpbWEgPC0gZGF0YS5mcmFtZSgpDQoNCmZvciAoaSBpbiBsaXN0KXsNCiAgbGlua2RheSA8LSBnbHVlOjpnbHVlKCJkYXRhL2RhdG9ob3Jhcmlve2l9LnR4dCIpICNsaW5rIHBhcmEgZGVzY2FyZ2FyIGNvbiBsYSB2YXJpYWJsZSBmZWNoYQ0KIA0KICBkYXkgPC0gcmVhZC50YWJsZShsaW5rZGF5LCBza2lwID0gMiwgbnJvd3MgPSAyNCkgI3RhYmxhIGNvbiBsb3MgZGF0b3MgZGVzY2FyZ2Fkb3MsIHNlIGVzcGVjaWZpY2FuIGxhcyAyMyBwcmltZXJhcyBmaWxhcyBwb3JxdWUgc29uIGxhcyBxdWUgY29ycmVzcG9uZGVuIGEgQ0FCQQ0KICBkYXRhY2xpbWEgPC0gcmJpbmQoZGF0YWNsaW1hLGRheSkgI3NlIHN1bWFuIGxhcyBmaWxhcyBkaWFyaWFzDQogIHByaW50KGdsdWU6OmdsdWUoIkZlY2hhOiB7aX0iKSkgI2EgbW9kbyBkZSBjb250cm9sLCBzZSBpbXByaW1lIGxhcyBmZWNoYXMgZGUgZGVzY2FnYXMNCiAgcHJpbnQoZ2x1ZTo6Z2x1ZSgic2UgZGVzY2FyZ2Fyb24ge25yb3coZGF0YWNsaW1hKX0gZGF0b3MgaG9yYXJpb3MiKSkNCn0NCg0KZGF0YWNsaW1hIDwtIGRhdGFjbGltYSAlPiUNCiAgc2VsZWN0KEZlY2hhID0gVjEsDQogICAgICAgICBIb3JhID0gVjIsDQogICAgICAgICBUZW1wID0gVjMsDQogICAgICAgICBIdW1lZGFkID0gVjQpICU+JQ0KICBncm91cF9ieShGZWNoYSkgJT4lDQogIHN1bW1hcmlzZShUZW1wX01pbiA9IG1pbihUZW1wKSwNCiAgICAgICAgICAgIFRlbXBfTWF4ID0gbWF4KFRlbXApLA0KICAgICAgICAgICAgSHVtZWRhZF9NZWRpYSA9IHJvdW5kKG1lYW4oSHVtZWRhZCksMikpICU+JQ0KICBtdXRhdGUoRmVjaGEgPSBkbXkoRmVjaGEpKSAlPiUNCiAgYXJyYW5nZShGZWNoYSkNCg0KcmV0dXJuKGRhdGFjbGltYSkNCg0KfQ0KDQpgYGANCg==