The working directory was changed to C:/Users/husson/Dropbox/Rpourlastatetladatascience/chapitre/cartes/DONNEES inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the the working directory for notebook chunks.

3.1 Les fonctions graphiques conventionnelles

ozone <- read.table("https://r-stat-sc-donnees.github.io/ozone.txt",header=TRUE)
ozone <- ozone[,c("T12","maxO3","vent","pluie","Vx12")]
summary(ozone)
      T12            maxO3           vent      pluie         Vx12       
 Min.   :14.00   Min.   : 42.00   Est  :10   Pluie:43   Min.   :-7.878  
 1st Qu.:18.60   1st Qu.: 70.75   Nord :31   Sec  :69   1st Qu.:-3.565  
 Median :20.55   Median : 81.50   Ouest:50              Median :-1.879  
 Mean   :21.53   Mean   : 90.30   Sud  :21              Mean   :-1.611  
 3rd Qu.:23.55   3rd Qu.:106.00                         3rd Qu.: 0.000  
 Max.   :33.50   Max.   :166.00                         Max.   : 6.578  

3.1.1 La fonction plot

plot(maxO3~T12,data=ozone)

plot(maxO3~vent,data=ozone,xlab="Secteur du vent",ylab="Pic d’ozone")

boxplot(maxO3~vent,data=ozone)

plot(pluie~vent,data=ozone)

plot(ozone[,"T12"],ozone[,"vent"],pch=3,yaxt="n",xlab="T12",ylab="vent")
axis(side=2,at=1:4,labels=levels(ozone[,"vent"]))

plot(ozone[,"maxO3"], xlab="num.", ylab="maxO3", cex=.5, pch=16)

plot(ozone[,"maxO3"], xlab="num.", ylab="maxO3", type="l")

3.1.2 Représentation d’une distribution

hist(ozone[,"maxO3"], main="Histogramme", prob=TRUE, xlab="Ozone", col="lightblue")

plot(density(ozone[,"maxO3"]), main="Estimateur Ă  noyau", xlab="Ozone")

plot(ozone[,"vent"])

barplot(table(ozone[,"vent"]))

3.1.3 Ajouts aux graphiques

plot(maxO3~T12,data=ozone, pch=20)
text(ozone[,"T12"],ozone[,"maxO3"], substr(rownames(ozone),5,8), cex=.75, pos=3, offset=.3)
abline(v=27,lty=2)

plot(ozone[1:7,"maxO3"],type="l")
lines(ozone[8:14,"maxO3"],col="red") # ajout de la 2ème semaine

ecarty <- range(ozone[1:7,"maxO3"],ozone[8:14,"maxO3"])
plot(ozone[1:7,"maxO3"],type="l",ylim=ecarty,lty=1)
lines(ozone[8:14,"maxO3"],col="red",lty=1)

3.1.4 Graphiques en plusieurs dimensions

f <- function(x,y){10*sin(sqrt(x^2+y^2))/sqrt(x^2+y^2)}
y <- x <- seq(-10,10,length=30)
z <- outer(x,y,f)
persp(x,y,z,theta=30,phi=30,expand=0.5)

zfacette <- (z[-1,-1]+z[-1,-30]+z[-30,-1]+z[-30,-30])/4
niveaucouleur <- cut(zfacette,100)
couleurs <- heat.colors(100)[niveaucouleur]
persp(x,y,z,theta=30,phi=30,expand=0.5,col=couleurs)

library(rgl)
rgl.surface(x,y,z)
library(lattice)
cloud(maxO3~T12+Vx12,type=c("p","h"),data=ozone)

plot3d(ozone[,"T12"],ozone[,"Vx12"],ozone[,"maxO3"],radius=2,xlab="T12", ylab="Vx12",zlab="maxO3",type="s")

3.1.5 Exportation de graphiques

pdf("graphik.pdf")
ecarty <- range(ozone[1:7,"maxO3"],ozone[8:14,"maxO3"])
plot(ozone[1:7,"maxO3"],type="l",ylim=ecarty,lty=1)
lines(ozone[8:14,"maxO3"],col="red",lty=1)
dev.off()

3.1.6 Plusieurs graphiques

par(mfrow=c(1,2))
plot(1:10,10:1,pch=0)
plot(rep(1,4),type="l")

mat <- matrix(c(1,1,2,3),nrow=2,ncol=2,byrow=TRUE)
layout(mat)
plot(1:10,10:1,pch=0)
plot(rep(1,4),type="l")
plot(c(2,3,-1,0),type="b")

3.2 Les fonctions graphiques avec ggplot2

3.2.1 Premiers graphes avec ggplot2

library(ggplot2)
set.seed(1234)
diamonds2 <- diamonds[sample(nrow(diamonds),5000),]
ggplot(diamonds2)+aes(x=cut)+geom_bar()

ggplot(diamonds2)+aes(x=price)+geom_histogram()

ggplot(diamonds2)+aes(x=carat,y=price)+geom_point()

3.2.2 La grammaire ggplot

ggplot(diamonds2)+aes(x=carat,y=price,color=cut)

ggplot(diamonds2)+aes(x=carat,y=price,color=cut)+geom_point()

ggplot(diamonds2)+aes(x=carat,y=price)+geom_point(color="red")

ggplot(diamonds2)+aes(x=cut)+geom_bar(fill="blue")

D <- data.frame(X=seq(-2*pi,2*pi,by=0.01))
ggplot(D)+aes(x=X,y=sin(X))+geom_line()

ggplot(diamonds2)+aes(x=price)+geom_histogram(bins=40)

ggplot(diamonds2)+aes(x=price,y=..count..)+geom_histogram(bins=40)

ggplot(diamonds2)+aes(x=price,y=..density..)+geom_histogram(bins=40)

ggplot(diamonds2)+aes(x=price)+stat_bin(bins=40)

ggplot(diamonds2)+aes(x=price,y=..density..)+stat_bin(bins=40)

ggplot(diamonds2)+aes(x=carat,y=price)+geom_point(size=0.5)+
  stat_smooth(method="loess",size=2)

ggplot(diamonds2)+aes(x=carat,y=price)+geom_point(size=0.5)+
  stat_smooth(method="loess",geom="line",color="blue",size=2)

ggplot(diamonds2)+aes(x=carat,y=price)+geom_point(size=0.5)+
  stat_smooth(method="loess",geom="point",color="blue",size=2)

ggplot(diamonds2)+aes(x=carat,y=price,color=cut)+geom_point()+
  scale_color_manual(values=c("Fair"="black","Good"="yellow",
                              "Very Good"="blue","Premium"="red","Ideal"="green"))

p1 <- ggplot(diamonds2)+aes(x=cut)+geom_bar(aes(fill=cut))
p1

p1 + scale_fill_brewer(palette="Reds")

p2 <- ggplot(diamonds2)+aes(x=carat,y=price)+
geom_point(aes(color=depth))
p2

p2 + scale_color_gradient(low="red",high="yellow")

p2+scale_x_continuous(breaks=seq(0.5,3,by=0.5))+
  scale_y_continuous(name="prix")+
  scale_color_gradient("Profondeur")

3.2.3 Group et facets

ggplot(diamonds2)+aes(x=carat,y=price)+geom_smooth()

ggplot(diamonds2)+aes(x=carat,y=price,group=cut)+geom_smooth()

ggplot(diamonds2)+aes(x=carat,y=price,group=cut,color=cut)+
  geom_smooth()

ggplot(diamonds2)+aes(x=carat,y=price)+geom_point()+
  geom_smooth(method="lm")+facet_grid(color~cut)

ggplot(diamonds2)+aes(x=carat,y=price)+geom_point()+
  geom_smooth(method="lm")+facet_wrap(color~cut)

ggplot(diamonds2)+aes(x=carat,y=price,color=cut)+geom_smooth()+
facet_wrap(~cut,nrow=2)

gr1 <- ggplot(diamonds2)+aes(x=cut)+geom_bar() # 1er graphe
gr2 <- ggplot(diamonds2)+aes(x=price)+geom_histogram() # 2e graphe
library(gridExtra)
grid.arrange(gr1, gr2, ncol=2, nrow=1) # organisation des graphes

3.2.4 Compléments

ggplot(diamonds2)+aes(x=carat,y=price)+geom_point()

ggplot(diamonds2,aes(x=carat,y=price))+geom_point()

ggplot(diamonds2)+geom_point(aes(x=carat,y=price))

X <- seq(-2*pi,2*pi,by=0.001)
Y1 <- cos(X)
Y2 <- sin(X)
donnees1 <- data.frame(X,Y1)
donnees2 <- data.frame(X,Y2)
ggplot(donnees1)+geom_line(aes(x=X,y=Y1))+
  geom_line(data=donnees2,aes(x=X,y=Y2),color="red")

library(FactoMineR) #Pour obtenir le jeu de donnees
data("decathlon")
ggplot(decathlon)+aes(x=`100m`,y=`1500m`)+geom_point()

p <- ggplot(diamonds2)+aes(x=carat,y=price,color=cut)+geom_point()
p + theme_bw()

p + theme_classic()

p + theme_grey()

p + theme_minimal()

qplot(data=diamonds2,x=carat,y=price,geom=c("point","smooth"), facets=color~cut)

monplot <- qplot(data=diamonds2,x=carat,y=price,geom=c("point","smooth"), facets=color~cut)
ggsave("mon_graphique.pdf", plot = monplot, width = 11, height = 8)

3.3 Les graphiques interactifs

library(rAmCharts)
amBoxplot(maxO3 ~ vent, data = ozone, export = TRUE)
data(data_stock_2)
amTimeSeries(data_stock_2, "date", c("ts1", "ts2"))
ggplot(ozone) + aes(x=T12,y=maxO3,color=vent) + geom_point()

library(plotly)
ggplotly()

3.4 Construire des cartes

3.4.1 Carte statique dans R

lat <- -24.66
lon <- 176.9
zoom <- 4
library(ggmap)
MaCarte <- get_map(location=c(lon,lat),zoom=zoom)
ggmap(MaCarte)

data(quakes)
ggmap(MaCarte) +
  geom_point(data=quakes,aes(x=long,y=lat,colour=depth)) +
  scale_color_gradient(low="yellow2",high="red4") + theme_void()

bbox <- make_bbox(long,lat,data=quakes)
bbox
    left   bottom    right      top 
164.5470 -39.9835 189.2530  -9.3265 
MaCarteopt <- get_map(bbox)  ## relancer plusieurs fois si pb de connexion
bounding box given to google - spatial extent only approximate.converting bounding box to center/zoom specification. (experimental)
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=-24.655,176.9&zoom=5&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
ggmap(MaCarteopt)

centre <- geocode("Pic de Rochebrune, Megeve, France")
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Pic%20de%20Rochebrune,%20Megeve,%20France&sensor=false
centre
       lon      lat
1 6.787533 44.82231
CarteRochebrune <- get_map(centre, zoom=13)
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=44.822309,6.787533&zoom=13&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
ggmap(CarteRochebrune)

3.4.2 Carte dans un navigateur

library(leaflet)
m <- leaflet()
m <- addTiles(m)
print(m)
m <- leaflet() %>%
  setView(lng=6.62,lat=45.85,zoom=13) %>%
  addTiles()
print(m)
m <- leaflet(data=quakes) %>%
  setView(lng=176.9, lat=-24.66, zoom=4) %>%
  addTiles()
m %>% addCircles(~long, ~lat)
pal <- colorNumeric(palette=c(low="yellow2",high="red4"), domain = quakes$depth)
pal(quakes$depth[1:3])
[1] "#A03F01" "#911901" "#EEED00"
m %>% addTiles() %>%
  addCircles(~long,~lat,popup=~mag,color=~pal(depth))
sta.paris <- read.csv("https://r-stat-sc-donnees.github.io/velib_paris_01_09_18.csv",encoding = "UTF-8")
summary(sta.paris)
                            name    numBikesAvailable      lon             lat       
 Alexander Fleming - Belvédère: 1   Min.   : 1.000    Min.   :2.264   Min.   :48.82  
 Arsonval Falguière           : 1   1st Qu.: 5.750    1st Qu.:2.306   1st Qu.:48.85  
 Assas - Luxembourg           : 1   Median : 8.000    Median :2.338   Median :48.86  
 Assas - Rennes               : 1   Mean   : 8.985    Mean   :2.336   Mean   :48.86  
 Athènes - Clichy             : 1   3rd Qu.:11.000    3rd Qu.:2.367   3rd Qu.:48.87  
 Bassano -  IĂ©na              : 1   Max.   :36.000    Max.   :2.411   Max.   :48.90  
 (Other)                      :62                                                    
leaflet(data = sta.paris) %>% addTiles() %>%
  addCircleMarkers(~ lon, ~ lat,radius=8,stroke = FALSE, fillOpacity = 0.5,color="red")
leaflet(data = sta.paris) %>% addTiles() %>%
  addCircleMarkers(~ lon, ~ lat,radius=8,stroke = FALSE,
fillOpacity = 0.5,color="red",popup = ~ paste("<b>
        VĂ©los dispos: </b>",as.character(numBikesAvailable)))
leaflet(data = sta.paris) %>% addTiles() %>%
  addCircleMarkers(~ lon, ~ lat,radius=8,stroke = FALSE,
fillOpacity = 0.5,color="red",popup = ~ paste(as.character(name),
"<br/>","<b> VĂ©los dispos: </b>",as.character(numBikesAvailable)))
SV <- geocode("Stade Velodrome Marseille")
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Stade%20Velodrome%20Marseille&sensor=false
info <- paste(sep = "<br/>", "<b><a href=’https://fr.wikipedia.org/wiki/Stade_Vélodrome’>
Stade Velodrome</a></b>", "Marseille")
leaflet() %>% addTiles() %>% addPopups(SV[1]$lon, SV[2]$lat,info,options = popupOptions(closeButton = FALSE))

3.4.3 Carte avec contours : le format shapefile

library(readr)
chomage <- read_delim("https://r-stat-sc-donnees.github.io/tauxchomage.csv",delim=";")
class(chomage)
[1] "tbl_df"     "tbl"        "data.frame"
library(sf)
 # télécharger le fichier en local
dpt <- read_sf("C:/Users/husson/Dropbox/Rpourlastatetladatascience/chapitre/cartes/DONNEES/dpt")
class(dpt)
[1] "sf"         "tbl_df"     "tbl"        "data.frame"
library(dplyr)
dpt2 <- inner_join(dpt,chomage,by="CODE_DEPT")
class(dpt2)
[1] "sf"         "tbl_df"     "tbl"        "data.frame"
library(tidyr)
dpt3 <- dpt2 %>% select(A2006=TCHOMB1T06,A2011=TCHOMB1T11,geometry) %>%
  gather("Annee","TxChomage",-geometry)
class(dpt3)
[1] "sf"         "tbl_df"     "tbl"        "data.frame"
library(ggplot2)
ggplot() + geom_sf(data = dpt3, aes(fill = TxChomage)) +
  facet_wrap(~Annee, nrow = 1) +
  scale_fill_gradient(low="white",high="brown")+theme_bw()

library(maps)
map("state")

dp <- st_transform(dpt2, crs="+init=epsg:4326")
library(leaflet)
fr <- leaflet(data=dp) %>% setView(2.21,46.23,6) %>% addTiles()
pal <- colorNumeric(palette="inferno",domain = c(dp$TCHOMB1T06,dp$TCHOMB1T11))
frf <- fr %>% addPolygons(color=~pal(TCHOMB1T06),group ="2006") %>%
  addPolygons(color=~pal(TCHOMB1T11),group="2011")
frf %>% addLayersControl(overlayGroups = c("2006", "2011"), options=
                           layersControlOptions(collapsed = FALSE))
LS0tDQp0aXRsZTogJ0NoYXBpdHJlIDMgOiBWaXN1YWxpc2VyIGxlcyBkb25uw6llcycNCmF1dGhvcjogIkh1c3NvbiBldCBhbC4iDQpkYXRlOiAiMDkvMDkvMjAxOCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDogeWVzDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICczJw0KICAgIHRvY19mbG9hdDogeWVzDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGNhY2hlID0gVFJVRSkNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCnNldHdkKCJDOi9Vc2Vycy9odXNzb24vRHJvcGJveC9ScG91cmxhc3RhdGV0bGFkYXRhc2NpZW5jZS9jaGFwaXRyZS9jYXJ0ZXMvRE9OTkVFUyIpDQpgYGANCiMgMy4xIExlcyBmb25jdGlvbnMgZ3JhcGhpcXVlcyBjb252ZW50aW9ubmVsbGVzDQoNCmBgYHtyfQ0Kb3pvbmUgPC0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yLXN0YXQtc2MtZG9ubmVlcy5naXRodWIuaW8vb3pvbmUudHh0IixoZWFkZXI9VFJVRSkNCm96b25lIDwtIG96b25lWyxjKCJUMTIiLCJtYXhPMyIsInZlbnQiLCJwbHVpZSIsIlZ4MTIiKV0NCnN1bW1hcnkob3pvbmUpDQpgYGANCg0KIyMgMy4xLjEgTGEgZm9uY3Rpb24gcGxvdA0KDQpgYGB7cn0NCnBsb3QobWF4TzN+VDEyLGRhdGE9b3pvbmUpDQpgYGANCg0KYGBge3J9DQpwbG90KG1heE8zfnZlbnQsZGF0YT1vem9uZSx4bGFiPSJTZWN0ZXVyIGR1IHZlbnQiLHlsYWI9IlBpYyBk4oCZb3pvbmUiKQ0KYGBgDQoNCmBgYHtyfQ0KYm94cGxvdChtYXhPM352ZW50LGRhdGE9b3pvbmUpDQpgYGANCg0KYGBge3J9DQpwbG90KHBsdWllfnZlbnQsZGF0YT1vem9uZSkNCmBgYA0KDQpgYGB7cn0NCnBsb3Qob3pvbmVbLCJUMTIiXSxvem9uZVssInZlbnQiXSxwY2g9Myx5YXh0PSJuIix4bGFiPSJUMTIiLHlsYWI9InZlbnQiKQ0KYXhpcyhzaWRlPTIsYXQ9MTo0LGxhYmVscz1sZXZlbHMob3pvbmVbLCJ2ZW50Il0pKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChvem9uZVssIm1heE8zIl0sIHhsYWI9Im51bS4iLCB5bGFiPSJtYXhPMyIsIGNleD0uNSwgcGNoPTE2KQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChvem9uZVssIm1heE8zIl0sIHhsYWI9Im51bS4iLCB5bGFiPSJtYXhPMyIsIHR5cGU9ImwiKQ0KYGBgDQoNCiMjIDMuMS4yIFJlcHLDqXNlbnRhdGlvbiBkJ3VuZSBkaXN0cmlidXRpb24NCg0KYGBge3J9DQpoaXN0KG96b25lWywibWF4TzMiXSwgbWFpbj0iSGlzdG9ncmFtbWUiLCBwcm9iPVRSVUUsIHhsYWI9Ik96b25lIiwgY29sPSJsaWdodGJsdWUiKQ0KYGBgDQpgYGB7cn0NCnBsb3QoZGVuc2l0eShvem9uZVssIm1heE8zIl0pLCBtYWluPSJFc3RpbWF0ZXVyIMOgIG5veWF1IiwgeGxhYj0iT3pvbmUiKQ0KYGBgDQpgYGB7cn0NCnBsb3Qob3pvbmVbLCJ2ZW50Il0pDQpgYGANCmBgYHtyfQ0KYmFycGxvdCh0YWJsZShvem9uZVssInZlbnQiXSkpDQpgYGANCg0KIyMgMy4xLjMgQWpvdXRzIGF1eCBncmFwaGlxdWVzDQoNCmBgYHtyfQ0KcGxvdChtYXhPM35UMTIsZGF0YT1vem9uZSwgcGNoPTIwKQ0KdGV4dChvem9uZVssIlQxMiJdLG96b25lWywibWF4TzMiXSwgc3Vic3RyKHJvd25hbWVzKG96b25lKSw1LDgpLCBjZXg9Ljc1LCBwb3M9Mywgb2Zmc2V0PS4zKQ0KYWJsaW5lKHY9MjcsbHR5PTIpDQpgYGANCmBgYHtyfQ0KcGxvdChvem9uZVsxOjcsIm1heE8zIl0sdHlwZT0ibCIpDQpsaW5lcyhvem9uZVs4OjE0LCJtYXhPMyJdLGNvbD0icmVkIikgIyBham91dCBkZSBsYSAyw6htZSBzZW1haW5lDQpgYGANCg0KYGBge3J9DQplY2FydHkgPC0gcmFuZ2Uob3pvbmVbMTo3LCJtYXhPMyJdLG96b25lWzg6MTQsIm1heE8zIl0pDQpwbG90KG96b25lWzE6NywibWF4TzMiXSx0eXBlPSJsIix5bGltPWVjYXJ0eSxsdHk9MSkNCmxpbmVzKG96b25lWzg6MTQsIm1heE8zIl0sY29sPSJyZWQiLGx0eT0xKQ0KYGBgDQojIyAzLjEuNCBHcmFwaGlxdWVzIGVuIHBsdXNpZXVycyBkaW1lbnNpb25zDQoNCmBgYHtyfQ0KZiA8LSBmdW5jdGlvbih4LHkpezEwKnNpbihzcXJ0KHheMit5XjIpKS9zcXJ0KHheMit5XjIpfQ0KeSA8LSB4IDwtIHNlcSgtMTAsMTAsbGVuZ3RoPTMwKQ0KeiA8LSBvdXRlcih4LHksZikNCnBlcnNwKHgseSx6LHRoZXRhPTMwLHBoaT0zMCxleHBhbmQ9MC41KQ0KemZhY2V0dGUgPC0gKHpbLTEsLTFdK3pbLTEsLTMwXSt6Wy0zMCwtMV0relstMzAsLTMwXSkvNA0Kbml2ZWF1Y291bGV1ciA8LSBjdXQoemZhY2V0dGUsMTAwKQ0KY291bGV1cnMgPC0gaGVhdC5jb2xvcnMoMTAwKVtuaXZlYXVjb3VsZXVyXQ0KcGVyc3AoeCx5LHosdGhldGE9MzAscGhpPTMwLGV4cGFuZD0wLjUsY29sPWNvdWxldXJzKQ0KYGBgDQoNCmBgYHtyLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkocmdsKQ0KcmdsLnN1cmZhY2UoeCx5LHopDQpgYGANCg0KYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShsYXR0aWNlKQ0KY2xvdWQobWF4TzN+VDEyK1Z4MTIsdHlwZT1jKCJwIiwiaCIpLGRhdGE9b3pvbmUpDQpgYGANCg0KYGBge3J9DQpwbG90M2Qob3pvbmVbLCJUMTIiXSxvem9uZVssIlZ4MTIiXSxvem9uZVssIm1heE8zIl0scmFkaXVzPTIseGxhYj0iVDEyIiwgeWxhYj0iVngxMiIsemxhYj0ibWF4TzMiLHR5cGU9InMiKQ0KYGBgDQoNCiMjIDMuMS41IEV4cG9ydGF0aW9uIGRlIGdyYXBoaXF1ZXMNCg0KYGBge3IsZXZhbD1GQUxTRX0NCnBkZigiZ3JhcGhpay5wZGYiKQ0KZWNhcnR5IDwtIHJhbmdlKG96b25lWzE6NywibWF4TzMiXSxvem9uZVs4OjE0LCJtYXhPMyJdKQ0KcGxvdChvem9uZVsxOjcsIm1heE8zIl0sdHlwZT0ibCIseWxpbT1lY2FydHksbHR5PTEpDQpsaW5lcyhvem9uZVs4OjE0LCJtYXhPMyJdLGNvbD0icmVkIixsdHk9MSkNCmRldi5vZmYoKQ0KYGBgDQoNCiMjIDMuMS42IFBsdXNpZXVycyBncmFwaGlxdWVzDQoNCmBgYHtyfQ0KcGFyKG1mcm93PWMoMSwyKSkNCnBsb3QoMToxMCwxMDoxLHBjaD0wKQ0KcGxvdChyZXAoMSw0KSx0eXBlPSJsIikNCmBgYA0KDQpgYGB7cn0NCm1hdCA8LSBtYXRyaXgoYygxLDEsMiwzKSxucm93PTIsbmNvbD0yLGJ5cm93PVRSVUUpDQpsYXlvdXQobWF0KQ0KcGxvdCgxOjEwLDEwOjEscGNoPTApDQpwbG90KHJlcCgxLDQpLHR5cGU9ImwiKQ0KcGxvdChjKDIsMywtMSwwKSx0eXBlPSJiIikNCmBgYA0KDQojIDMuMiBMZXMgZm9uY3Rpb25zIGdyYXBoaXF1ZXMgYXZlYyBnZ3Bsb3QyDQoNCiMjIDMuMi4xIFByZW1pZXJzIGdyYXBoZXMgYXZlYyBnZ3Bsb3QyDQoNCmBgYHtyLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoZ2dwbG90MikNCnNldC5zZWVkKDEyMzQpDQpkaWFtb25kczIgPC0gZGlhbW9uZHNbc2FtcGxlKG5yb3coZGlhbW9uZHMpLDUwMDApLF0NCmdncGxvdChkaWFtb25kczIpK2Flcyh4PWN1dCkrZ2VvbV9iYXIoKQ0KZ2dwbG90KGRpYW1vbmRzMikrYWVzKHg9cHJpY2UpK2dlb21faGlzdG9ncmFtKCkNCmdncGxvdChkaWFtb25kczIpK2Flcyh4PWNhcmF0LHk9cHJpY2UpK2dlb21fcG9pbnQoKQ0KYGBgDQoNCiMjIDMuMi4yIExhIGdyYW1tYWlyZSBnZ3Bsb3QNCg0KYGBge3J9DQpnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1jYXJhdCx5PXByaWNlLGNvbG9yPWN1dCkNCmdncGxvdChkaWFtb25kczIpK2Flcyh4PWNhcmF0LHk9cHJpY2UsY29sb3I9Y3V0KStnZW9tX3BvaW50KCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkaWFtb25kczIpK2Flcyh4PWNhcmF0LHk9cHJpY2UpK2dlb21fcG9pbnQoY29sb3I9InJlZCIpDQpnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1jdXQpK2dlb21fYmFyKGZpbGw9ImJsdWUiKQ0KYGBgDQoNCmBgYHtyfQ0KRCA8LSBkYXRhLmZyYW1lKFg9c2VxKC0yKnBpLDIqcGksYnk9MC4wMSkpDQpnZ3Bsb3QoRCkrYWVzKHg9WCx5PXNpbihYKSkrZ2VvbV9saW5lKCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkaWFtb25kczIpK2Flcyh4PXByaWNlKStnZW9tX2hpc3RvZ3JhbShiaW5zPTQwKQ0KZ2dwbG90KGRpYW1vbmRzMikrYWVzKHg9cHJpY2UseT0uLmNvdW50Li4pK2dlb21faGlzdG9ncmFtKGJpbnM9NDApDQpnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1wcmljZSx5PS4uZGVuc2l0eS4uKStnZW9tX2hpc3RvZ3JhbShiaW5zPTQwKQ0KZ2dwbG90KGRpYW1vbmRzMikrYWVzKHg9cHJpY2UpK3N0YXRfYmluKGJpbnM9NDApDQpnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1wcmljZSx5PS4uZGVuc2l0eS4uKStzdGF0X2JpbihiaW5zPTQwKQ0KYGBgDQpgYGB7cn0NCmdncGxvdChkaWFtb25kczIpK2Flcyh4PWNhcmF0LHk9cHJpY2UpK2dlb21fcG9pbnQoc2l6ZT0wLjUpKw0KICBzdGF0X3Ntb290aChtZXRob2Q9ImxvZXNzIixzaXplPTIpDQpnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1jYXJhdCx5PXByaWNlKStnZW9tX3BvaW50KHNpemU9MC41KSsNCiAgc3RhdF9zbW9vdGgobWV0aG9kPSJsb2VzcyIsZ2VvbT0ibGluZSIsY29sb3I9ImJsdWUiLHNpemU9MikNCmdncGxvdChkaWFtb25kczIpK2Flcyh4PWNhcmF0LHk9cHJpY2UpK2dlb21fcG9pbnQoc2l6ZT0wLjUpKw0KICBzdGF0X3Ntb290aChtZXRob2Q9ImxvZXNzIixnZW9tPSJwb2ludCIsY29sb3I9ImJsdWUiLHNpemU9MikNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkaWFtb25kczIpK2Flcyh4PWNhcmF0LHk9cHJpY2UsY29sb3I9Y3V0KStnZW9tX3BvaW50KCkrDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiRmFpciI9ImJsYWNrIiwiR29vZCI9InllbGxvdyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVmVyeSBHb29kIj0iYmx1ZSIsIlByZW1pdW0iPSJyZWQiLCJJZGVhbCI9ImdyZWVuIikpDQpgYGANCg0KYGBge3J9DQpwMSA8LSBnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1jdXQpK2dlb21fYmFyKGFlcyhmaWxsPWN1dCkpDQpwMQ0KcDEgKyBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJSZWRzIikNCmBgYA0KYGBge3J9DQpwMiA8LSBnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1jYXJhdCx5PXByaWNlKSsNCmdlb21fcG9pbnQoYWVzKGNvbG9yPWRlcHRoKSkNCnAyDQpwMiArIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0icmVkIixoaWdoPSJ5ZWxsb3ciKQ0KYGBgDQoNCiANCmBgYHtyfQ0KcDIrc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMC41LDMsYnk9MC41KSkrDQogIHNjYWxlX3lfY29udGludW91cyhuYW1lPSJwcml4IikrDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCJQcm9mb25kZXVyIikNCmBgYA0KDQojIyAzLjIuMyBHcm91cCBldCBmYWNldHMNCg0KYGBge3J9DQpnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1jYXJhdCx5PXByaWNlKStnZW9tX3Ntb290aCgpDQpnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1jYXJhdCx5PXByaWNlLGdyb3VwPWN1dCkrZ2VvbV9zbW9vdGgoKQ0KZ2dwbG90KGRpYW1vbmRzMikrYWVzKHg9Y2FyYXQseT1wcmljZSxncm91cD1jdXQsY29sb3I9Y3V0KSsNCiAgZ2VvbV9zbW9vdGgoKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGRpYW1vbmRzMikrYWVzKHg9Y2FyYXQseT1wcmljZSkrZ2VvbV9wb2ludCgpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikrZmFjZXRfZ3JpZChjb2xvcn5jdXQpDQpnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1jYXJhdCx5PXByaWNlKStnZW9tX3BvaW50KCkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iKStmYWNldF93cmFwKGNvbG9yfmN1dCkNCmdncGxvdChkaWFtb25kczIpK2Flcyh4PWNhcmF0LHk9cHJpY2UsY29sb3I9Y3V0KStnZW9tX3Ntb290aCgpKw0KZmFjZXRfd3JhcCh+Y3V0LG5yb3c9MikNCmBgYA0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZ3IxIDwtIGdncGxvdChkaWFtb25kczIpK2Flcyh4PWN1dCkrZ2VvbV9iYXIoKSAjIDFlciBncmFwaGUNCmdyMiA8LSBnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1wcmljZSkrZ2VvbV9oaXN0b2dyYW0oKSAjIDJlIGdyYXBoZQ0KbGlicmFyeShncmlkRXh0cmEpDQpncmlkLmFycmFuZ2UoZ3IxLCBncjIsIG5jb2w9MiwgbnJvdz0xKSAjIG9yZ2FuaXNhdGlvbiBkZXMgZ3JhcGhlcw0KYGBgDQoNCiMjIDMuMi40IENvbXBsw6ltZW50cw0KDQpgYGB7cn0NCmdncGxvdChkaWFtb25kczIpK2Flcyh4PWNhcmF0LHk9cHJpY2UpK2dlb21fcG9pbnQoKQ0KZ2dwbG90KGRpYW1vbmRzMixhZXMoeD1jYXJhdCx5PXByaWNlKSkrZ2VvbV9wb2ludCgpDQpnZ3Bsb3QoZGlhbW9uZHMyKStnZW9tX3BvaW50KGFlcyh4PWNhcmF0LHk9cHJpY2UpKQ0KYGBgDQpgYGB7cn0NClggPC0gc2VxKC0yKnBpLDIqcGksYnk9MC4wMDEpDQpZMSA8LSBjb3MoWCkNClkyIDwtIHNpbihYKQ0KZG9ubmVlczEgPC0gZGF0YS5mcmFtZShYLFkxKQ0KZG9ubmVlczIgPC0gZGF0YS5mcmFtZShYLFkyKQ0KYGBgDQpgYGB7cn0NCmdncGxvdChkb25uZWVzMSkrZ2VvbV9saW5lKGFlcyh4PVgseT1ZMSkpKw0KICBnZW9tX2xpbmUoZGF0YT1kb25uZWVzMixhZXMoeD1YLHk9WTIpLGNvbG9yPSJyZWQiKQ0KYGBgDQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoRmFjdG9NaW5lUikgI1BvdXIgb2J0ZW5pciBsZSBqZXUgZGUgZG9ubmVlcw0KZGF0YSgiZGVjYXRobG9uIikNCmdncGxvdChkZWNhdGhsb24pK2Flcyh4PWAxMDBtYCx5PWAxNTAwbWApK2dlb21fcG9pbnQoKQ0KYGBgDQoNCmBgYHtyfQ0KcCA8LSBnZ3Bsb3QoZGlhbW9uZHMyKSthZXMoeD1jYXJhdCx5PXByaWNlLGNvbG9yPWN1dCkrZ2VvbV9wb2ludCgpDQpwICsgdGhlbWVfYncoKQ0KcCArIHRoZW1lX2NsYXNzaWMoKQ0KcCArIHRoZW1lX2dyZXkoKQ0KcCArIHRoZW1lX21pbmltYWwoKQ0KYGBgDQpgYGB7cn0NCnFwbG90KGRhdGE9ZGlhbW9uZHMyLHg9Y2FyYXQseT1wcmljZSxnZW9tPWMoInBvaW50Iiwic21vb3RoIiksIGZhY2V0cz1jb2xvcn5jdXQpDQpgYGANCmBgYHtyLGV2YWw9RkFMU0V9DQptb25wbG90IDwtIHFwbG90KGRhdGE9ZGlhbW9uZHMyLHg9Y2FyYXQseT1wcmljZSxnZW9tPWMoInBvaW50Iiwic21vb3RoIiksIGZhY2V0cz1jb2xvcn5jdXQpDQpnZ3NhdmUoIm1vbl9ncmFwaGlxdWUucGRmIiwgcGxvdCA9IG1vbnBsb3QsIHdpZHRoID0gMTEsIGhlaWdodCA9IDgpDQpgYGANCg0KIyAzLjMgTGVzIGdyYXBoaXF1ZXMgaW50ZXJhY3RpZnMNCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFLGV2YWw9RkFMU0V9DQpsaWJyYXJ5KHJBbUNoYXJ0cykNCmFtQm94cGxvdChtYXhPMyB+IHZlbnQsIGRhdGEgPSBvem9uZSwgZXhwb3J0ID0gVFJVRSkNCmBgYA0KDQpgYGB7cixldmFsPUZBTFNFfQ0KZGF0YShkYXRhX3N0b2NrXzIpDQphbVRpbWVTZXJpZXMoZGF0YV9zdG9ja18yLCAiZGF0ZSIsIGMoInRzMSIsICJ0czIiKSkNCmBgYA0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3Qob3pvbmUpICsgYWVzKHg9VDEyLHk9bWF4TzMsY29sb3I9dmVudCkgKyBnZW9tX3BvaW50KCkNCmxpYnJhcnkocGxvdGx5KQ0KZ2dwbG90bHkoKQ0KYGBgDQoNCiMgMy40IENvbnN0cnVpcmUgZGVzIGNhcnRlcw0KDQojIyAzLjQuMSBDYXJ0ZSBzdGF0aXF1ZSBkYW5zIFINCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGF0IDwtIC0yNC42Ng0KbG9uIDwtIDE3Ni45DQp6b29tIDwtIDQNCmxpYnJhcnkoZ2dtYXApDQpNYUNhcnRlIDwtIGdldF9tYXAobG9jYXRpb249Yyhsb24sbGF0KSx6b29tPXpvb20pDQpnZ21hcChNYUNhcnRlKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YShxdWFrZXMpDQpnZ21hcChNYUNhcnRlKSArDQogIGdlb21fcG9pbnQoZGF0YT1xdWFrZXMsYWVzKHg9bG9uZyx5PWxhdCxjb2xvdXI9ZGVwdGgpKSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0ieWVsbG93MiIsaGlnaD0icmVkNCIpICsgdGhlbWVfdm9pZCgpDQpgYGANCmBgYHtyfQ0KYmJveCA8LSBtYWtlX2Jib3gobG9uZyxsYXQsZGF0YT1xdWFrZXMpDQpiYm94DQpNYUNhcnRlb3B0IDwtIGdldF9tYXAoYmJveCkgICMjIHJlbGFuY2VyIHBsdXNpZXVycyBmb2lzIHNpIHBiIGRlIGNvbm5leGlvbg0KZ2dtYXAoTWFDYXJ0ZW9wdCkNCmBgYA0KYGBge3J9DQpjZW50cmUgPC0gZ2VvY29kZSgiUGljIGRlIFJvY2hlYnJ1bmUsIE1lZ2V2ZSwgRnJhbmNlIikNCmNlbnRyZQ0KQ2FydGVSb2NoZWJydW5lIDwtIGdldF9tYXAoY2VudHJlLCB6b29tPTEzKSAgIyMgcmVsYW5jZXIgcGx1c2lldXJzIGZvaXMgc2kgcGIgZGUgY29ubmV4aW9uDQpnZ21hcChDYXJ0ZVJvY2hlYnJ1bmUpDQpgYGANCg0KIyMgMy40LjIgQ2FydGUgZGFucyB1biBuYXZpZ2F0ZXVyDQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkobGVhZmxldCkNCm0gPC0gbGVhZmxldCgpDQptIDwtIGFkZFRpbGVzKG0pDQpwcmludChtKQ0KYGBgDQpgYGB7cixpbmNsdWRlPUZBTFNFfQ0KbSA8LSBsZWFmbGV0KCkgJT4lIGFkZFRpbGVzKCkNCnByaW50KG0pDQpgYGANCmBgYHtyfQ0KbSA8LSBsZWFmbGV0KCkgJT4lDQogIHNldFZpZXcobG5nPTYuNjIsbGF0PTQ1Ljg1LHpvb209MTMpICU+JQ0KICBhZGRUaWxlcygpDQpwcmludChtKQ0KYGBgDQpgYGB7cn0NCm0gPC0gbGVhZmxldChkYXRhPXF1YWtlcykgJT4lDQogIHNldFZpZXcobG5nPTE3Ni45LCBsYXQ9LTI0LjY2LCB6b29tPTQpICU+JQ0KICBhZGRUaWxlcygpDQptICU+JSBhZGRDaXJjbGVzKH5sb25nLCB+bGF0KQ0KcGFsIDwtIGNvbG9yTnVtZXJpYyhwYWxldHRlPWMobG93PSJ5ZWxsb3cyIixoaWdoPSJyZWQ0IiksIGRvbWFpbiA9IHF1YWtlcyRkZXB0aCkNCnBhbChxdWFrZXMkZGVwdGhbMTozXSkNCm0gJT4lIGFkZFRpbGVzKCkgJT4lDQogIGFkZENpcmNsZXMofmxvbmcsfmxhdCxwb3B1cD1+bWFnLGNvbG9yPX5wYWwoZGVwdGgpKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RhLnBhcmlzIDwtIHJlYWQuY3N2KCJodHRwczovL3Itc3RhdC1zYy1kb25uZWVzLmdpdGh1Yi5pby92ZWxpYl9wYXJpc18wMV8wOV8xOC5jc3YiLGVuY29kaW5nID0gIlVURi04IikNCnN1bW1hcnkoc3RhLnBhcmlzKQ0KYGBgDQpgYGB7cn0NCmxlYWZsZXQoZGF0YSA9IHN0YS5wYXJpcykgJT4lIGFkZFRpbGVzKCkgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMofiBsb24sIH4gbGF0LHJhZGl1cz04LHN0cm9rZSA9IEZBTFNFLCBmaWxsT3BhY2l0eSA9IDAuNSxjb2xvcj0icmVkIikNCmBgYA0KYGBge3J9DQpsZWFmbGV0KGRhdGEgPSBzdGEucGFyaXMpICU+JSBhZGRUaWxlcygpICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKH4gbG9uLCB+IGxhdCxyYWRpdXM9OCxzdHJva2UgPSBGQUxTRSwNCmZpbGxPcGFjaXR5ID0gMC41LGNvbG9yPSJyZWQiLHBvcHVwID0gfiBwYXN0ZSgiPGI+DQogICAgICAgIFbDqWxvcyBkaXNwb3M6IDwvYj4iLGFzLmNoYXJhY3RlcihudW1CaWtlc0F2YWlsYWJsZSkpKQ0KYGBgDQpgYGB7cn0NCmxlYWZsZXQoZGF0YSA9IHN0YS5wYXJpcykgJT4lIGFkZFRpbGVzKCkgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMofiBsb24sIH4gbGF0LHJhZGl1cz04LHN0cm9rZSA9IEZBTFNFLA0KZmlsbE9wYWNpdHkgPSAwLjUsY29sb3I9InJlZCIscG9wdXAgPSB+IHBhc3RlKGFzLmNoYXJhY3RlcihuYW1lKSwNCiI8YnIvPiIsIjxiPiBWw6lsb3MgZGlzcG9zOiA8L2I+Iixhcy5jaGFyYWN0ZXIobnVtQmlrZXNBdmFpbGFibGUpKSkNCmBgYA0KYGBge3J9DQpTViA8LSBnZW9jb2RlKCJTdGFkZSBWZWxvZHJvbWUgTWFyc2VpbGxlIikgICMjIHJlbGFuY2VyIHBsdXNpZXVycyBmb2lzIHNpIHBiIGRlIGNvbm5leGlvbg0KaW5mbyA8LSBwYXN0ZShzZXAgPSAiPGJyLz4iLCAiPGI+PGEgaHJlZj3igJlodHRwczovL2ZyLndpa2lwZWRpYS5vcmcvd2lraS9TdGFkZV9Ww6lsb2Ryb21l4oCZPg0KU3RhZGUgVmVsb2Ryb21lPC9hPjwvYj4iLCAiTWFyc2VpbGxlIikNCmxlYWZsZXQoKSAlPiUgYWRkVGlsZXMoKSAlPiUgYWRkUG9wdXBzKFNWWzFdJGxvbiwgU1ZbMl0kbGF0LGluZm8sb3B0aW9ucyA9IHBvcHVwT3B0aW9ucyhjbG9zZUJ1dHRvbiA9IEZBTFNFKSkNCmBgYA0KDQojIyAzLjQuMyBDYXJ0ZSBhdmVjIGNvbnRvdXJzIDogbGUgZm9ybWF0IHNoYXBlZmlsZQ0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHJlYWRyKQ0KY2hvbWFnZSA8LSByZWFkX2RlbGltKCJodHRwczovL3Itc3RhdC1zYy1kb25uZWVzLmdpdGh1Yi5pby90YXV4Y2hvbWFnZS5jc3YiLGRlbGltPSI7IikNCmNsYXNzKGNob21hZ2UpDQpgYGANCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoc2YpDQogIyB0w6lsw6ljaGFyZ2VyIGxlIGZpY2hpZXIgZW4gbG9jYWwNCmRwdCA8LSByZWFkX3NmKCJDOi9Vc2Vycy9odXNzb24vRHJvcGJveC9ScG91cmxhc3RhdGV0bGFkYXRhc2NpZW5jZS9jaGFwaXRyZS9jYXJ0ZXMvRE9OTkVFUy9kcHQiKQ0KY2xhc3MoZHB0KQ0KYGBgDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KZHB0MiA8LSBpbm5lcl9qb2luKGRwdCxjaG9tYWdlLGJ5PSJDT0RFX0RFUFQiKQ0KY2xhc3MoZHB0MikNCmBgYA0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHlyKQ0KZHB0MyA8LSBkcHQyICU+JSBzZWxlY3QoQTIwMDY9VENIT01CMVQwNixBMjAxMT1UQ0hPTUIxVDExLGdlb21ldHJ5KSAlPiUNCiAgZ2F0aGVyKCJBbm5lZSIsIlR4Q2hvbWFnZSIsLWdlb21ldHJ5KQ0KY2xhc3MoZHB0MykNCmBgYA0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGdncGxvdDIpDQpnZ3Bsb3QoKSArIGdlb21fc2YoZGF0YSA9IGRwdDMsIGFlcyhmaWxsID0gVHhDaG9tYWdlKSkgKw0KICBmYWNldF93cmFwKH5Bbm5lZSwgbnJvdyA9IDEpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9IndoaXRlIixoaWdoPSJicm93biIpK3RoZW1lX2J3KCkNCmBgYA0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KG1hcHMpDQptYXAoInN0YXRlIikNCmBgYA0KDQpgYGB7cn0NCmRwIDwtIHN0X3RyYW5zZm9ybShkcHQyLCBjcnM9Iitpbml0PWVwc2c6NDMyNiIpDQpsaWJyYXJ5KGxlYWZsZXQpDQpmciA8LSBsZWFmbGV0KGRhdGE9ZHApICU+JSBzZXRWaWV3KDIuMjEsNDYuMjMsNikgJT4lIGFkZFRpbGVzKCkNCnBhbCA8LSBjb2xvck51bWVyaWMocGFsZXR0ZT0iaW5mZXJubyIsZG9tYWluID0gYyhkcCRUQ0hPTUIxVDA2LGRwJFRDSE9NQjFUMTEpKQ0KZnJmIDwtIGZyICU+JSBhZGRQb2x5Z29ucyhjb2xvcj1+cGFsKFRDSE9NQjFUMDYpLGdyb3VwID0iMjAwNiIpICU+JQ0KICBhZGRQb2x5Z29ucyhjb2xvcj1+cGFsKFRDSE9NQjFUMTEpLGdyb3VwPSIyMDExIikNCmZyZiAlPiUgYWRkTGF5ZXJzQ29udHJvbChvdmVybGF5R3JvdXBzID0gYygiMjAwNiIsICIyMDExIiksIG9wdGlvbnM9DQogICAgICAgICAgICAgICAgICAgICAgICAgICBsYXllcnNDb250cm9sT3B0aW9ucyhjb2xsYXBzZWQgPSBGQUxTRSkpDQpgYGANCg0K