This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.

library(tidyverse)
Loading tidyverse: ggplot2
Loading tidyverse: tibble
Loading tidyverse: tidyr
Loading tidyverse: readr
Loading tidyverse: purrr
Loading tidyverse: dplyr
Conflicts with tidy packages -------------------------------------------------------------------------------------
filter(): dplyr, stats
lag():    dplyr, stats
library(ggplot2)
library(eurostat)
library(lubridate)

Attaching package: ‘lubridate’

The following object is masked from ‘package:base’:

    date
library(gridExtra)

Attaching package: ‘gridExtra’

The following object is masked from ‘package:dplyr’:

    combine
library(stringr)
library('rgdal')      # Lire et reprojeter les cartes
Loading required package: sp
rgdal: version: 1.2-15, (SVN revision 691)
 Geospatial Data Abstraction Library extensions to R successfully loaded
 Loaded GDAL runtime: GDAL 2.2.2, released 2017/09/15
 Path to GDAL shared files: /usr/share/gdal/2.2
 GDAL binary built with GEOS: TRUE 
 Loaded PROJ.4 runtime: Rel. 4.9.2, 08 September 2015, [PJ_VERSION: 492]
 Path to PROJ.4 shared files: (autodetected)
 Linking to sp version: 1.2-4 
library('plotrix')    # Créer des échelles de couleurs
library('classInt')   # Affecter ces couleurs aux données
library('Cairo')
library('sf')
Linking to GEOS 3.5.1, GDAL 2.2.2, proj.4 4.9.2
library(rgeos)  # for gIntersection
rgeos version: 0.3-26, (SVN revision 560)
 GEOS runtime version: 3.5.1-CAPI-1.9.1 r4246 
 Linking to sp version: 1.2-4 
 Polygon checking: TRUE 
library(raster)

Attaching package: ‘raster’

The following object is masked from ‘package:dplyr’:

    select

The following object is masked from ‘package:tidyr’:

    extract

Load World Map, Bosnia and Oceans

world.map <- readOGR(dsn="eurostats_data/ne_10m_admin_0_countries/",layer="ne_10m_admin_0_countries")
OGR data source with driver: ESRI Shapefile 
Source: "eurostats_data/ne_10m_admin_0_countries/", layer: "ne_10m_admin_0_countries"
with 255 features
It has 71 fields
world.map <- world.map[world.map$CONTINENT %in% c('Europe','Africa','Asia'),]
#world.map <- gSimplify(world.map, tol = 0.00002)
world.map <- spTransform(world.map, CRS("+init=epsg:2154"))
bosnia.map <- readOGR(dsn="eurostats_data/BIH_adm_shp/",layer="BIH_adm1")
OGR data source with driver: ESRI Shapefile 
Source: "eurostats_data/BIH_adm_shp/", layer: "BIH_adm1"
with 3 features
It has 12 fields
Integer64 fields read as strings:  ID_0 ID_1 CCN_1 
bosnia.map <- aggregate(bosnia.map)
bosnia.map <- spTransform(bosnia.map, CRS("+init=epsg:2154"))
ocean <- readOGR(dsn="eurostats_data/ne_50m_ocean/", layer="ne_50m_ocean")
OGR data source with driver: ESRI Shapefile 
Source: "eurostats_data/ne_50m_ocean/", layer: "ne_50m_ocean"
with 1 features
It has 3 fields
Integer64 fields read as strings:  scalerank 
ocean <- spTransform(ocean, CRS("+init=epsg:2154"))

Load eurostats DATASET

dataset<- 'hlth_rs_prsrg'
df.data <- get_eurostat('hlth_rs_prsrg')
trying URL 'http://ec.europa.eu/eurostat/estat-navtree-portlet-prod/BulkDownloadListing?sort=1&file=data%2Fhlth_rs_prsrg.tsv.gz'
Content type 'application/octet-stream;charset=UTF-8' length 210173 bytes (205 KB)
==================================================
downloaded 205 KB

Table hlth_rs_prsrg cached at /tmp/RtmpXWhSkT/eurostat/hlth_rs_prsrg_date_code_TF.rds
df.data <- df.data %>% filter(unit=='P_HTHAB')#%>% filter(str_length(geo)>2)
dput(df.data,'hlth_rs_prsrg.put')
hlth_rs_prsrg<- dget('hlth_rs_prsrg.put')
df.labels <-  (label_eurostat(df.data,lang='en',fix_duplicated = TRUE)) %>% dplyr::select(isco08,geo)
Labels for geo includes duplicated labels in the Eurostat dictionary. Codes have been added as a prefix for dublicated.
Modified labels are: LU Luxembourg, LU00 Luxembourg, MT Malta, MT00 Malta, LI Liechtenstein, LI00 Liechtenstein
colnames(df.labels)<- c('profession','region')
df.data<- tibble::rowid_to_column(df.data, "ID")
df.labels<- tibble::rowid_to_column(df.labels, "ID")
df<- merge(df.data,df.labels,by='ID',all=TRUE)
dput(df,'hlth_rs_prsrg.dput')
df$year<- year(df$time)
df$time<-NULL
df$ID<-NULL

Define quartiles for each profession

str(df)
'data.frame':   20630 obs. of  7 variables:
 $ unit      : Factor w/ 3 levels "HAB_P","NR","P_HTHAB": 3 3 3 3 3 3 3 3 3 3 ...
 $ isco08    : Factor w/ 5 levels "OC221","OC222_322",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ geo       : Factor w/ 298 levels "IT","ITC1","ITC2",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ values    : num  NA NA NA NA NA NA NA NA NA NA ...
 $ profession: Factor w/ 5 levels "Medical doctors",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ region    : Factor w/ 298 levels "Italy","Piemonte",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ year      : num  2016 2016 2016 2016 2016 ...
ggplot(df,aes(profession,values))+ geom_boxplot()

ggplot(df, aes(x=values, fill=profession)) +
    geom_histogram(binwidth=.5, alpha=.5, position="identity")

quartiles <- df %>% filter(!is.na(values)) %>% group_by(profession) %>% summarise(q0=quantile(values,c(0)),
                                                                     q1=quantile(values,c(0.25)),
                                                                     q2=quantile(values,c(0.5)),
                                                                     q3=quantile(values,c(0.75)))
quartiles
quartiles.list <- list('Medical doctors'= c(80,100,250,320,380,600,1000),
'Nurses and midwives'=c(30,500,650,1000,1500),
'Dentists' = c(4,40,60,75),
'Pharmacists' =  c(4,45,65,100),
'Physiotherapists' = c(0.01,20,60,100))

Set Date ranges

dates_range <- unique(df$year)
professions <- unique(df$profession)
date.range <- c(2000, 2005, 2010, 2014,2015)

Function to prepare data for a given profession

create.data.per.profession <- function(df,prof){
  
  df.europe.year <- df %>% filter(profession==prof) %>% filter(year %in% date.range)  %>% filter(!geo=='EU28')
  df.europe.year$country <- str_sub(df.europe.year$geo,1,2)
  df.europe.year$geo.level <-str_length(df.europe.year$geo)-1
  df.europe.year <- df.europe.year %>% spread(year,values)
  
  ## Define the geo level for each country
  geo.level.df <- df.europe.year %>% group_by(country,geo.level) %>% summarise(count = n())
  number.geo.level <- sort(unique(geo.level.df$geo.level))
  print(str(number.geo.level))
  geo.level.df <- geo.level.df %>% spread(geo.level,count)
  print (head(geo.level.df))
  
  if(identical(number.geo.level,c(1,2,3))){
    print ('coucou 1')
    colnames(geo.level.df)<- c('country','level.1','level.2','level.3')
    geo.level.df <- geo.level.df %>% mutate('lowest.geo.level'=ifelse(!is.na(level.3),3,ifelse(!is.na(level.2),2,1)))
  
  } else if(identical(number.geo.level,c(1,2))){
     print ('coucou 2')
     colnames(geo.level.df)<- c('country','level.1','level.2')
    geo.level.df <- geo.level.df %>% mutate('lowest.geo.level'=ifelse(!is.na(level.2),2,1))
  
} else if(identical(number.geo.level,c(1,3))){
   print ('coucou 3')
     colnames(geo.level.df)<- c('country','level.1','level.3')
    geo.level.df <- geo.level.df %>% mutate('lowest.geo.level'=ifelse(!is.na(level.3),3,1))
  }
  
  
  ## Filter data on the lowest geo level available
  df.europe.year <- merge(df.europe.year,geo.level.df%>% dplyr::select(country,lowest.geo.level),by='country')
  df.europe.year <- df.europe.year %>% filter(geo.level==lowest.geo.level)
  print (head(df.europe.year))
  colnames(df.europe.year) <- make.names(colnames(df.europe.year), unique = TRUE)
  
  return (df.europe.year)
}
df.test <- create.data.per.profession(df,professions[3])
 num [1:3] 1 2 3
NULL
[1] "coucou 1"

Rename Countries to match with World Map

EU_NUTS <- readOGR(dsn = "eurostats_data/NUTS_2010_60M_SH/Data", layer = "NUTS_RG_60M_2010")
OGR data source with driver: ESRI Shapefile 
Source: "eurostats_data/NUTS_2010_60M_SH/Data", layer: "NUTS_RG_60M_2010"
with 1920 features
It has 4 fields
proj4string(EU_NUTS)
[1] "+proj=longlat +ellps=GRS80 +no_defs"
EU_NUTS <- spTransform(EU_NUTS, CRS("+init=epsg:2154"))
df.EU_NUTS <- data.frame(EU_NUTS@data)
EU.ISO <- unique(str_sub(df.EU_NUTS$NUTS_ID,1,2))
#EU.ISO <- replace(EU.ISO, EU.ISO=="EL",'GR')
EU.ISO <- replace(EU.ISO, EU.ISO=="UK",'GB')
EU.ISO <- replace(EU.ISO, EU.ISO=="PT",'PR')
EU.ISO <- replace(EU.ISO, EU.ISO=="AT",'AU')
EU.ISO <- replace(EU.ISO, EU.ISO=="PL",'PO')
EU.ISO <- replace(EU.ISO, EU.ISO=="IE",'IR')
EU.ISO <- replace(EU.ISO, EU.ISO=="DK",'DN')
EU.ISO <- replace(EU.ISO, EU.ISO=="SE",'SW')

Function to ploat a given year/profession

plot_map<- function(df,year,profession,breaks){
  
  breaks <- quartiles.list[[profession]]
  print (length(breaks))
  year.col <-paste('X',as.character(year),sep='')
  print(year.col)
  
  df.europe.year <- create.data.per.profession(df,profession)
  
    EU_NUTS_with_Data <- merge(EU_NUTS,df.europe.year,by.x='NUTS_ID',by.y='geo',all.x=TRUE, duplicateGeoms = TRUE)
    EU_NUTS_with_Data <- EU_NUTS_with_Data[!is.na(EU_NUTS_with_Data$geo.level),]
    
    EU_NUTS_with_Data <- EU_NUTS_with_Data[!is.na(EU_NUTS_with_Data@data[,year.col]),]
    
    df.EU_NUTS_with_Data <- data.frame(EU_NUTS_with_Data@data)
    
    
    col <- findColours(classIntervals(
              EU_NUTS_with_Data@data[,year.col], length(breaks)-1, style="fixed",fixedBreaks=breaks),
              smoothColors("#7ad2f6",98,"#192058"))
    # Légende
    leg <- findColours(classIntervals(
              round(EU_NUTS_with_Data@data[,year.col]),  length(breaks)-1, style="fixed",fixedBreaks=breaks),
              smoothColors("#7ad2f6",7,"#192058"),
              under="<", over=">", between="–",
              cutlabels=FALSE)
    
    # define map limits
    xlim = c(-828843  ,4287547)
    ylim = c(5107843  ,9602614)
    CP <- as(extent(c(xlim,ylim)), "SpatialPolygons")
    
    proj4string(CP) <- proj4string(EU_NUTS_with_Data)
    CP <- spTransform(CP, CRS("+init=epsg:2154"))
    
    title <- paste(profession,as.character(year))
    png(file =paste(title,'.png',sep=''), w = 1800, h = 1800, res = 300)
    plot.new()
    
    
    EU_NUTS_with_Data <- crop(EU_NUTS_with_Data, extent(CP))
    EU_NUTS <- crop(EU_NUTS, extent(CP))
    
    ocean.cropped <- gSimplify(ocean, tol = 0.00001)
    
    # this is a well known R / GEOS hack (usually combined with the above) to 
    # deal with "bad" polygons
    ocean.cropped <- gBuffer(ocean.cropped, byid=TRUE, width=0)
    ocean.cropped <- crop(ocean.cropped, extent(CP))
    
    world.map <- gBuffer(world.map, byid=TRUE, width=0)
    world.map <- crop(world.map, extent(CP))
    
    
    plot(world.map,lwd=.1,col='lightgrey',main=title)
    plot(EU_NUTS,col='white',lwd=.1,add=TRUE)
    plot(ocean.cropped,col='lightblue',lwd=0.1,add=TRUE) 
    plot(bosnia.map,lwd=0.1,col='lightgrey',add=TRUE)
    plot(EU_NUTS_with_Data,col=col,lwd=.1,add=TRUE)
    
    legend(2362890,9348213,fill=c('white',attr(leg, "palette")),
     legend=c('No Data',names(attr(leg,"table"))),
      title = 'per 100 000 Habitants :',cex = .5)
    
    dev.off()
  
}

Tests

plot_map(df,2015,professions[1])
[1] 7
[1] "X2015"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
null device 
          1 
for(prof in professions){
  for(year in date.range){
    plot_map(df,year,prof)
  }
}
[1] 7
[1] "X2000"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 7
[1] "X2005"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 7
[1] "X2010"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 7
[1] "X2014"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 7
[1] "X2015"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 5
[1] "X2000"
 num [1:2] 1 3
NULL
[1] "coucou 3"
[1] 5
[1] "X2005"
 num [1:2] 1 3
NULL
[1] "coucou 3"
[1] 5
[1] "X2010"
 num [1:2] 1 3
NULL
[1] "coucou 3"
[1] 5
[1] "X2014"
 num [1:2] 1 3
NULL
[1] "coucou 3"
[1] 5
[1] "X2015"
 num [1:2] 1 3
NULL
[1] "coucou 3"
[1] 4
[1] "X2000"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2005"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2010"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2014"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2015"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2000"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2005"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2010"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2014"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2015"
 num [1:3] 1 2 3
NULL
[1] "coucou 1"
[1] 4
[1] "X2000"
 num [1:2] 1 3
NULL
[1] "coucou 3"
[1] 4
[1] "X2005"
 num [1:2] 1 3
NULL
[1] "coucou 3"
[1] 4
[1] "X2010"
 num [1:2] 1 3
NULL
[1] "coucou 3"
[1] 4
[1] "X2014"
 num [1:2] 1 3
NULL
[1] "coucou 3"
[1] 4
[1] "X2015"
 num [1:2] 1 3
NULL
[1] "coucou 3"
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ21kK1NoaWZ0K0VudGVyKi4gCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShldXJvc3RhdCkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoJ3JnZGFsJykgICAgICAjIExpcmUgZXQgcmVwcm9qZXRlciBsZXMgY2FydGVzCmxpYnJhcnkoJ3Bsb3RyaXgnKSAgICAjIENyw6llciBkZXMgw6ljaGVsbGVzIGRlIGNvdWxldXJzCmxpYnJhcnkoJ2NsYXNzSW50JykgICAjIEFmZmVjdGVyIGNlcyBjb3VsZXVycyBhdXggZG9ubsOpZXMKbGlicmFyeSgnQ2Fpcm8nKQpsaWJyYXJ5KCdzZicpCmxpYnJhcnkocmdlb3MpICAjIGZvciBnSW50ZXJzZWN0aW9uCmxpYnJhcnkocmFzdGVyKQpgYGAKICAKIyBMb2FkIFdvcmxkIE1hcCwgQm9zbmlhIGFuZCBPY2VhbnMKYGBge3J9CndvcmxkLm1hcCA8LSByZWFkT0dSKGRzbj0iZXVyb3N0YXRzX2RhdGEvbmVfMTBtX2FkbWluXzBfY291bnRyaWVzLyIsbGF5ZXI9Im5lXzEwbV9hZG1pbl8wX2NvdW50cmllcyIpCndvcmxkLm1hcCA8LSB3b3JsZC5tYXBbd29ybGQubWFwJENPTlRJTkVOVCAlaW4lIGMoJ0V1cm9wZScsJ0FmcmljYScsJ0FzaWEnKSxdCgojd29ybGQubWFwIDwtIGdTaW1wbGlmeSh3b3JsZC5tYXAsIHRvbCA9IDAuMDAwMDIpCndvcmxkLm1hcCA8LSBzcFRyYW5zZm9ybSh3b3JsZC5tYXAsIENSUygiK2luaXQ9ZXBzZzoyMTU0IikpCgpib3NuaWEubWFwIDwtIHJlYWRPR1IoZHNuPSJldXJvc3RhdHNfZGF0YS9CSUhfYWRtX3NocC8iLGxheWVyPSJCSUhfYWRtMSIpCmJvc25pYS5tYXAgPC0gYWdncmVnYXRlKGJvc25pYS5tYXApCmJvc25pYS5tYXAgPC0gc3BUcmFuc2Zvcm0oYm9zbmlhLm1hcCwgQ1JTKCIraW5pdD1lcHNnOjIxNTQiKSkKCm9jZWFuIDwtIHJlYWRPR1IoZHNuPSJldXJvc3RhdHNfZGF0YS9uZV81MG1fb2NlYW4vIiwgbGF5ZXI9Im5lXzUwbV9vY2VhbiIpCm9jZWFuIDwtIHNwVHJhbnNmb3JtKG9jZWFuLCBDUlMoIitpbml0PWVwc2c6MjE1NCIpKQoKYGBgCgojIyMgTG9hZCBldXJvc3RhdHMgREFUQVNFVApgYGB7cn0KZGF0YXNldDwtICdobHRoX3JzX3Byc3JnJwoKZGYuZGF0YSA8LSBnZXRfZXVyb3N0YXQoJ2hsdGhfcnNfcHJzcmcnKQpkZi5kYXRhIDwtIGRmLmRhdGEgJT4lIGZpbHRlcih1bml0PT0nUF9IVEhBQicpIyU+JSBmaWx0ZXIoc3RyX2xlbmd0aChnZW8pPjIpCmRwdXQoZGYuZGF0YSwnaGx0aF9yc19wcnNyZy5wdXQnKQpobHRoX3JzX3Byc3JnPC0gZGdldCgnaGx0aF9yc19wcnNyZy5wdXQnKQpkZi5sYWJlbHMgPC0gIChsYWJlbF9ldXJvc3RhdChkZi5kYXRhLGxhbmc9J2VuJyxmaXhfZHVwbGljYXRlZCA9IFRSVUUpKSAlPiUgZHBseXI6OnNlbGVjdChpc2NvMDgsZ2VvKQpjb2xuYW1lcyhkZi5sYWJlbHMpPC0gYygncHJvZmVzc2lvbicsJ3JlZ2lvbicpCgpkZi5kYXRhPC0gdGliYmxlOjpyb3dpZF90b19jb2x1bW4oZGYuZGF0YSwgIklEIikKZGYubGFiZWxzPC0gdGliYmxlOjpyb3dpZF90b19jb2x1bW4oZGYubGFiZWxzLCAiSUQiKQoKZGY8LSBtZXJnZShkZi5kYXRhLGRmLmxhYmVscyxieT0nSUQnLGFsbD1UUlVFKQoKZHB1dChkZiwnaGx0aF9yc19wcnNyZy5kcHV0JykKZGYkeWVhcjwtIHllYXIoZGYkdGltZSkKZGYkdGltZTwtTlVMTApkZiRJRDwtTlVMTAoKYGBgCiMjIERlZmluZSBxdWFydGlsZXMgZm9yIGVhY2ggcHJvZmVzc2lvbgpgYGB7cn0Kc3RyKGRmKQoKZ2dwbG90KGRmLGFlcyhwcm9mZXNzaW9uLHZhbHVlcykpKyBnZW9tX2JveHBsb3QoKQoKZ2dwbG90KGRmLCBhZXMoeD12YWx1ZXMsIGZpbGw9cHJvZmVzc2lvbikpICsKICAgIGdlb21faGlzdG9ncmFtKGJpbndpZHRoPS41LCBhbHBoYT0uNSwgcG9zaXRpb249ImlkZW50aXR5IikKCnF1YXJ0aWxlcyA8LSBkZiAlPiUgZmlsdGVyKCFpcy5uYSh2YWx1ZXMpKSAlPiUgZ3JvdXBfYnkocHJvZmVzc2lvbikgJT4lIHN1bW1hcmlzZShxMD1xdWFudGlsZSh2YWx1ZXMsYygwKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHExPXF1YW50aWxlKHZhbHVlcyxjKDAuMjUpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcTI9cXVhbnRpbGUodmFsdWVzLGMoMC41KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHEzPXF1YW50aWxlKHZhbHVlcyxjKDAuNzUpKSkKCnF1YXJ0aWxlcwoKcXVhcnRpbGVzLmxpc3QgPC0gbGlzdCgnTWVkaWNhbCBkb2N0b3JzJz0gYyg4MCwxMDAsMjUwLDMyMCwzODAsNjAwLDEwMDApLAonTnVyc2VzIGFuZCBtaWR3aXZlcyc9YygzMCw1MDAsNjUwLDEwMDAsMTUwMCksCidEZW50aXN0cycgPSBjKDQsNDAsNjAsNzUpLAonUGhhcm1hY2lzdHMnID0gIGMoNCw0NSw2NSwxMDApLAonUGh5c2lvdGhlcmFwaXN0cycgPSBjKDAuMDEsMjAsNjAsMTAwKSkKYGBgCgojIyBTZXQgRGF0ZSByYW5nZXMKYGBge3J9CmRhdGVzX3JhbmdlIDwtIHVuaXF1ZShkZiR5ZWFyKQpwcm9mZXNzaW9ucyA8LSB1bmlxdWUoZGYkcHJvZmVzc2lvbikKZGF0ZS5yYW5nZSA8LSBjKDIwMDAsIDIwMDUsIDIwMTAsIDIwMTQsMjAxNSkKYGBgCgoKIyMjIEZ1bmN0aW9uIHRvIHByZXBhcmUgZGF0YSBmb3IgYSBnaXZlbiBwcm9mZXNzaW9uCmBgYHtyfQpjcmVhdGUuZGF0YS5wZXIucHJvZmVzc2lvbiA8LSBmdW5jdGlvbihkZixwcm9mKXsKICAKICBkZi5ldXJvcGUueWVhciA8LSBkZiAlPiUgZmlsdGVyKHByb2Zlc3Npb249PXByb2YpICU+JSBmaWx0ZXIoeWVhciAlaW4lIGRhdGUucmFuZ2UpICAlPiUgZmlsdGVyKCFnZW89PSdFVTI4JykKICBkZi5ldXJvcGUueWVhciRjb3VudHJ5IDwtIHN0cl9zdWIoZGYuZXVyb3BlLnllYXIkZ2VvLDEsMikKICBkZi5ldXJvcGUueWVhciRnZW8ubGV2ZWwgPC1zdHJfbGVuZ3RoKGRmLmV1cm9wZS55ZWFyJGdlbyktMQogIGRmLmV1cm9wZS55ZWFyIDwtIGRmLmV1cm9wZS55ZWFyICU+JSBzcHJlYWQoeWVhcix2YWx1ZXMpCiAgCiAgIyMgRGVmaW5lIHRoZSBnZW8gbGV2ZWwgZm9yIGVhY2ggY291bnRyeQogIGdlby5sZXZlbC5kZiA8LSBkZi5ldXJvcGUueWVhciAlPiUgZ3JvdXBfYnkoY291bnRyeSxnZW8ubGV2ZWwpICU+JSBzdW1tYXJpc2UoY291bnQgPSBuKCkpCiAgbnVtYmVyLmdlby5sZXZlbCA8LSBzb3J0KHVuaXF1ZShnZW8ubGV2ZWwuZGYkZ2VvLmxldmVsKSkKICBwcmludChzdHIobnVtYmVyLmdlby5sZXZlbCkpCiAgZ2VvLmxldmVsLmRmIDwtIGdlby5sZXZlbC5kZiAlPiUgc3ByZWFkKGdlby5sZXZlbCxjb3VudCkKICBwcmludCAoaGVhZChnZW8ubGV2ZWwuZGYpKQogIAogIGlmKGlkZW50aWNhbChudW1iZXIuZ2VvLmxldmVsLGMoMSwyLDMpKSl7CiAgICBwcmludCAoJ2NvdWNvdSAxJykKICAgIGNvbG5hbWVzKGdlby5sZXZlbC5kZik8LSBjKCdjb3VudHJ5JywnbGV2ZWwuMScsJ2xldmVsLjInLCdsZXZlbC4zJykKICAgIGdlby5sZXZlbC5kZiA8LSBnZW8ubGV2ZWwuZGYgJT4lIG11dGF0ZSgnbG93ZXN0Lmdlby5sZXZlbCc9aWZlbHNlKCFpcy5uYShsZXZlbC4zKSwzLGlmZWxzZSghaXMubmEobGV2ZWwuMiksMiwxKSkpCiAgCiAgfSBlbHNlIGlmKGlkZW50aWNhbChudW1iZXIuZ2VvLmxldmVsLGMoMSwyKSkpewogICAgIHByaW50ICgnY291Y291IDInKQogICAgIGNvbG5hbWVzKGdlby5sZXZlbC5kZik8LSBjKCdjb3VudHJ5JywnbGV2ZWwuMScsJ2xldmVsLjInKQogICAgZ2VvLmxldmVsLmRmIDwtIGdlby5sZXZlbC5kZiAlPiUgbXV0YXRlKCdsb3dlc3QuZ2VvLmxldmVsJz1pZmVsc2UoIWlzLm5hKGxldmVsLjIpLDIsMSkpCiAgCn0gZWxzZSBpZihpZGVudGljYWwobnVtYmVyLmdlby5sZXZlbCxjKDEsMykpKXsKICAgcHJpbnQgKCdjb3Vjb3UgMycpCiAgICAgY29sbmFtZXMoZ2VvLmxldmVsLmRmKTwtIGMoJ2NvdW50cnknLCdsZXZlbC4xJywnbGV2ZWwuMycpCiAgICBnZW8ubGV2ZWwuZGYgPC0gZ2VvLmxldmVsLmRmICU+JSBtdXRhdGUoJ2xvd2VzdC5nZW8ubGV2ZWwnPWlmZWxzZSghaXMubmEobGV2ZWwuMyksMywxKSkKICB9CiAgCiAgCiAgIyMgRmlsdGVyIGRhdGEgb24gdGhlIGxvd2VzdCBnZW8gbGV2ZWwgYXZhaWxhYmxlCiAgZGYuZXVyb3BlLnllYXIgPC0gbWVyZ2UoZGYuZXVyb3BlLnllYXIsZ2VvLmxldmVsLmRmJT4lIGRwbHlyOjpzZWxlY3QoY291bnRyeSxsb3dlc3QuZ2VvLmxldmVsKSxieT0nY291bnRyeScpCiAgZGYuZXVyb3BlLnllYXIgPC0gZGYuZXVyb3BlLnllYXIgJT4lIGZpbHRlcihnZW8ubGV2ZWw9PWxvd2VzdC5nZW8ubGV2ZWwpCiAgcHJpbnQgKGhlYWQoZGYuZXVyb3BlLnllYXIpKQogIGNvbG5hbWVzKGRmLmV1cm9wZS55ZWFyKSA8LSBtYWtlLm5hbWVzKGNvbG5hbWVzKGRmLmV1cm9wZS55ZWFyKSwgdW5pcXVlID0gVFJVRSkKICAKICByZXR1cm4gKGRmLmV1cm9wZS55ZWFyKQp9CgpkZi50ZXN0IDwtIGNyZWF0ZS5kYXRhLnBlci5wcm9mZXNzaW9uKGRmLHByb2Zlc3Npb25zWzNdKQoKYGBgCgoKIyBSZW5hbWUgQ291bnRyaWVzIHRvIG1hdGNoIHdpdGggV29ybGQgTWFwCmBgYHtyfQpFVV9OVVRTIDwtIHJlYWRPR1IoZHNuID0gImV1cm9zdGF0c19kYXRhL05VVFNfMjAxMF82ME1fU0gvRGF0YSIsIGxheWVyID0gIk5VVFNfUkdfNjBNXzIwMTAiKQpwcm9qNHN0cmluZyhFVV9OVVRTKQoKRVVfTlVUUyA8LSBzcFRyYW5zZm9ybShFVV9OVVRTLCBDUlMoIitpbml0PWVwc2c6MjE1NCIpKQpkZi5FVV9OVVRTIDwtIGRhdGEuZnJhbWUoRVVfTlVUU0BkYXRhKQpFVS5JU08gPC0gdW5pcXVlKHN0cl9zdWIoZGYuRVVfTlVUUyROVVRTX0lELDEsMikpCiNFVS5JU08gPC0gcmVwbGFjZShFVS5JU08sIEVVLklTTz09IkVMIiwnR1InKQpFVS5JU08gPC0gcmVwbGFjZShFVS5JU08sIEVVLklTTz09IlVLIiwnR0InKQpFVS5JU08gPC0gcmVwbGFjZShFVS5JU08sIEVVLklTTz09IlBUIiwnUFInKQpFVS5JU08gPC0gcmVwbGFjZShFVS5JU08sIEVVLklTTz09IkFUIiwnQVUnKQpFVS5JU08gPC0gcmVwbGFjZShFVS5JU08sIEVVLklTTz09IlBMIiwnUE8nKQpFVS5JU08gPC0gcmVwbGFjZShFVS5JU08sIEVVLklTTz09IklFIiwnSVInKQpFVS5JU08gPC0gcmVwbGFjZShFVS5JU08sIEVVLklTTz09IkRLIiwnRE4nKQpFVS5JU08gPC0gcmVwbGFjZShFVS5JU08sIEVVLklTTz09IlNFIiwnU1cnKQpgYGAKCgoKCiMjIEZ1bmN0aW9uIHRvIHBsb2F0IGEgZ2l2ZW4geWVhci9wcm9mZXNzaW9uCmBgYHtyfQpwbG90X21hcDwtIGZ1bmN0aW9uKGRmLHllYXIscHJvZmVzc2lvbixicmVha3MpewogIAogIGJyZWFrcyA8LSBxdWFydGlsZXMubGlzdFtbcHJvZmVzc2lvbl1dCiAgcHJpbnQgKGxlbmd0aChicmVha3MpKQogIHllYXIuY29sIDwtcGFzdGUoJ1gnLGFzLmNoYXJhY3Rlcih5ZWFyKSxzZXA9JycpCiAgcHJpbnQoeWVhci5jb2wpCiAgCiAgZGYuZXVyb3BlLnllYXIgPC0gY3JlYXRlLmRhdGEucGVyLnByb2Zlc3Npb24oZGYscHJvZmVzc2lvbikKICAKICAgIEVVX05VVFNfd2l0aF9EYXRhIDwtIG1lcmdlKEVVX05VVFMsZGYuZXVyb3BlLnllYXIsYnkueD0nTlVUU19JRCcsYnkueT0nZ2VvJyxhbGwueD1UUlVFLCBkdXBsaWNhdGVHZW9tcyA9IFRSVUUpCiAgICBFVV9OVVRTX3dpdGhfRGF0YSA8LSBFVV9OVVRTX3dpdGhfRGF0YVshaXMubmEoRVVfTlVUU193aXRoX0RhdGEkZ2VvLmxldmVsKSxdCiAgICAKICAgIEVVX05VVFNfd2l0aF9EYXRhIDwtIEVVX05VVFNfd2l0aF9EYXRhWyFpcy5uYShFVV9OVVRTX3dpdGhfRGF0YUBkYXRhWyx5ZWFyLmNvbF0pLF0KICAgIAogICAgZGYuRVVfTlVUU193aXRoX0RhdGEgPC0gZGF0YS5mcmFtZShFVV9OVVRTX3dpdGhfRGF0YUBkYXRhKQogICAgCiAgICAKICAgIGNvbCA8LSBmaW5kQ29sb3VycyhjbGFzc0ludGVydmFscygKICAgICAgICAgICAgICBFVV9OVVRTX3dpdGhfRGF0YUBkYXRhWyx5ZWFyLmNvbF0sIGxlbmd0aChicmVha3MpLTEsIHN0eWxlPSJmaXhlZCIsZml4ZWRCcmVha3M9YnJlYWtzKSwKICAgICAgICAgICAgICBzbW9vdGhDb2xvcnMoIiM3YWQyZjYiLDk4LCIjMTkyMDU4IikpCiAgICAjIEzDqWdlbmRlCiAgICBsZWcgPC0gZmluZENvbG91cnMoY2xhc3NJbnRlcnZhbHMoCiAgICAgICAgICAgICAgcm91bmQoRVVfTlVUU193aXRoX0RhdGFAZGF0YVsseWVhci5jb2xdKSwgIGxlbmd0aChicmVha3MpLTEsIHN0eWxlPSJmaXhlZCIsZml4ZWRCcmVha3M9YnJlYWtzKSwKICAgICAgICAgICAgICBzbW9vdGhDb2xvcnMoIiM3YWQyZjYiLDcsIiMxOTIwNTgiKSwKICAgICAgICAgICAgICB1bmRlcj0iPCIsIG92ZXI9Ij4iLCBiZXR3ZWVuPSLigJMiLAogICAgICAgICAgICAgIGN1dGxhYmVscz1GQUxTRSkKICAgIAogICAgIyBkZWZpbmUgbWFwIGxpbWl0cwogICAgeGxpbSA9IGMoLTgyODg0MyAgLDQyODc1NDcpCiAgICB5bGltID0gYyg1MTA3ODQzICAsOTYwMjYxNCkKICAgIENQIDwtIGFzKGV4dGVudChjKHhsaW0seWxpbSkpLCAiU3BhdGlhbFBvbHlnb25zIikKICAgIAogICAgcHJvajRzdHJpbmcoQ1ApIDwtIHByb2o0c3RyaW5nKEVVX05VVFNfd2l0aF9EYXRhKQogICAgQ1AgPC0gc3BUcmFuc2Zvcm0oQ1AsIENSUygiK2luaXQ9ZXBzZzoyMTU0IikpCiAgICAKICAgIHRpdGxlIDwtIHBhc3RlKHByb2Zlc3Npb24sYXMuY2hhcmFjdGVyKHllYXIpKQogICAgcG5nKGZpbGUgPXBhc3RlKHRpdGxlLCcucG5nJyxzZXA9JycpLCB3ID0gMTgwMCwgaCA9IDE4MDAsIHJlcyA9IDMwMCkKICAgIHBsb3QubmV3KCkKICAgIAogICAgCiAgICBFVV9OVVRTX3dpdGhfRGF0YSA8LSBjcm9wKEVVX05VVFNfd2l0aF9EYXRhLCBleHRlbnQoQ1ApKQogICAgRVVfTlVUUyA8LSBjcm9wKEVVX05VVFMsIGV4dGVudChDUCkpCiAgICAKICAgIG9jZWFuLmNyb3BwZWQgPC0gZ1NpbXBsaWZ5KG9jZWFuLCB0b2wgPSAwLjAwMDAxKQogICAgCiAgICAjIHRoaXMgaXMgYSB3ZWxsIGtub3duIFIgLyBHRU9TIGhhY2sgKHVzdWFsbHkgY29tYmluZWQgd2l0aCB0aGUgYWJvdmUpIHRvIAogICAgIyBkZWFsIHdpdGggImJhZCIgcG9seWdvbnMKICAgIG9jZWFuLmNyb3BwZWQgPC0gZ0J1ZmZlcihvY2Vhbi5jcm9wcGVkLCBieWlkPVRSVUUsIHdpZHRoPTApCiAgICBvY2Vhbi5jcm9wcGVkIDwtIGNyb3Aob2NlYW4uY3JvcHBlZCwgZXh0ZW50KENQKSkKICAgIAogICAgd29ybGQubWFwIDwtIGdCdWZmZXIod29ybGQubWFwLCBieWlkPVRSVUUsIHdpZHRoPTApCiAgICB3b3JsZC5tYXAgPC0gY3JvcCh3b3JsZC5tYXAsIGV4dGVudChDUCkpCiAgICAKICAgIAogICAgcGxvdCh3b3JsZC5tYXAsbHdkPS4xLGNvbD0nbGlnaHRncmV5JyxtYWluPXRpdGxlKQogICAgcGxvdChFVV9OVVRTLGNvbD0nd2hpdGUnLGx3ZD0uMSxhZGQ9VFJVRSkKICAgIHBsb3Qob2NlYW4uY3JvcHBlZCxjb2w9J2xpZ2h0Ymx1ZScsbHdkPTAuMSxhZGQ9VFJVRSkgCiAgICBwbG90KGJvc25pYS5tYXAsbHdkPTAuMSxjb2w9J2xpZ2h0Z3JleScsYWRkPVRSVUUpCiAgICBwbG90KEVVX05VVFNfd2l0aF9EYXRhLGNvbD1jb2wsbHdkPS4xLGFkZD1UUlVFKQogICAgCiAgICBsZWdlbmQoMjM2Mjg5MCw5MzQ4MjEzLGZpbGw9Yygnd2hpdGUnLGF0dHIobGVnLCAicGFsZXR0ZSIpKSwKICAgICBsZWdlbmQ9YygnTm8gRGF0YScsbmFtZXMoYXR0cihsZWcsInRhYmxlIikpKSwKICAgICAgdGl0bGUgPSAncGVyIDEwMCAwMDAgSGFiaXRhbnRzIDonLGNleCA9IC41KQogICAgCiAgICBkZXYub2ZmKCkKICAKfQpgYGAKCgojIyBUZXN0cwpgYGB7cn0KCnBsb3RfbWFwKGRmLDIwMTUscHJvZmVzc2lvbnNbMV0pCmZvcihwcm9mIGluIHByb2Zlc3Npb25zKXsKICBmb3IoeWVhciBpbiBkYXRlLnJhbmdlKXsKICAgIHBsb3RfbWFwKGRmLHllYXIscHJvZikKICB9Cn0KCmBgYAoK