library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ───────────────────────────────────────────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.3.6     ✔ purrr   0.3.4
✔ tibble  3.1.7     ✔ dplyr   1.0.9
✔ tidyr   1.2.0     ✔ stringr 1.4.0
✔ readr   2.1.2     ✔ forcats 0.5.1── Conflicts ──────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(sf)
Linking to GEOS 3.9.1, GDAL 3.4.3, PROJ 7.2.1; sf_use_s2() is TRUE
library(leaflet)

Leaflet is a javascript library. If yoy know your way around js it goes something like this:

##This is javascript code, DO NOT RUN!

var map = L.map('map').setView([51.505, -0.09], 13);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

L.marker([51.5, -0.09]).addTo(map)
    .bindPopup('A pretty CSS3 popup.<br> Easily customizable.')
    .openPopup();

You should also know to configure a CCS file and include the Leaflet library in the header section of a html file:

##This is html code, DO NOT RUN!

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css"
   integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
   crossorigin=""/>

     
 ##<!-- Make sure you put this AFTER Leaflet's CSS -->
 
     <script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"
   integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og=="
   crossorigin=""> </script>

Fortunately, in the html widgets for R there is a Leaflet for R that let you use most of the capabilities of Leaflet with your regular R code. To create maps with leaflet you can use data.frame objects, sf objects, raster objects and json files.

Leaflet for R is methodologically easy to use and you can get farther by exploring the arguments of the functions. A nice feature of Leaflet is that is integrated with shiny.



1 Rudiments

To create a leaflet object you only have to call the leaflet() function. If you have an object (think about an sf) which you want to plot you should pass it through the function as leaflet( -YourObject- ). To add layers the %>% operator is used before a new function defining a new characteristic of the map. As a background layer leaflet enables to use different maps providers with addProviderTiles or addTiles.

leaflet() %>% 
  addProviderTiles(providers$Esri.NatGeoWorldMap)

You can see the Tiles options here. In the previous map you can zoom in and zoom out all around the world. You can set the specific view you want with the setView() function as in the following examples where the maps are centred in Plaza de la indepencia (Montevideo, Uruguay)

leaflet() %>% 
  addProviderTiles(providers$Esri.NatGeoWorldMap) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 8)
leaflet() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16)

Of course, the main benefit of using leaflet is getting your own data into the map. You can add a marker with addMarkers and specifying the long and lat arguments

leaflet() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16) %>% 
  addMarkers(-56.199735, -34.906543)

You can even assign a pop-up by setting the popup argument.

leaflet() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16) %>% 
  addMarkers(-56.199735, -34.906543, popup = "Plaza Independencia")

Add many more markers can be a matter of calling the addMarker function multiple times.

leaflet() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16) %>% 
  addMarkers(-56.199735, -34.906543, popup = "Plaza Independencia") %>% 
  addMarkers(-56.198309, -34.906563, popup = "Palacio Salvo")

Off course, it does not make sense to add geographic information one by one; rather, a better option is give to the function an object and automatically plot all the markers.

Sites <- data.frame(longitud = c(-56.199735, -56.198309, -56.201037), 
                    lattitud = c(-34.906543, -34.906563, -34.907896), 
                    Name = c("Plaza Independencia", "Palacio Salvo", "Teatro Solis"))

leaflet(Sites) %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16) %>% 
  addMarkers(lng = ~longitud, lat = ~lattitud, popup = ~Name)
NA

In the last example we configured our data in the leaflet() function, but is also possible to do it in the addMarkers() function. What do you think is the difference?

leaflet() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16) %>% 
  addMarkers(data = Sites, lng = ~longitud, lat = ~lattitud, popup = ~Name)

A marker can also be a circle:

leaflet() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16) %>% 
  addCircleMarkers(data = Sites, lng = ~longitud, lat = ~lattitud)


2 Points and Circles

But is time to plot more natural “geometric-vectorial” elements as a circle (addCircles())

leaflet() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16) %>% 
  addCircles(data = Sites, lng = ~longitud, lat = ~lattitud,
                   radius = 100)

What is the difference among addCircleMarker() and addCircle().?

leaflet() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16) %>% 
  addCircles(data = Sites, lng = ~longitud, lat = ~lattitud,
                   radius = 100, opacity = 1, fillOpacity = 1)

Within the arguments of the function there are option to set elements like the radius, the opacity or the colour.

leaflet() %>% 
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -56.199735, lat = -34.906543, zoom = 16) %>% 
  addCircles(data = Sites, lng = ~longitud, lat = ~lattitud,
                   radius = 100, opacity = 1, fillOpacity = 0.7, color = "black", fillColor = "green")


3 Using our shapes

Uruguay <- st_read("Montevideo_Data/Vectoriales_2011/ine_depto.shp")
Reading layer `ine_depto' from data source 
  `C:\Users\Orlan\Dropbox\Teaching\SpatialAnalysis\Tutorials\03_MapMaking\Montevideo_Data\Vectoriales_2011\ine_depto.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 20 features and 5 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 366582.2 ymin: 6127919 xmax: 858252.1 ymax: 6671738
CRS:           NA
#ine_depto.shp
Accidents <- st_read("Montevideo_Data/Accidentes2006-2010/accidentes2006-2010.shp")
Reading layer `accidentes2006-2010' from data source 
  `C:\Users\Orlan\Dropbox\Teaching\SpatialAnalysis\Tutorials\03_MapMaking\Montevideo_Data\Accidentes2006-2010\accidentes2006-2010.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 41121 features and 7 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 558003.5 ymin: 6134508 xmax: 588206.1 ymax: 6157857
CRS:           NA
Uruguay = st_set_crs(Uruguay, 32721)
Accidents = st_set_crs(Accidents, 32721)

Uruguay <- st_transform(Uruguay,4326)
Accidents = st_transform(Accidents, 4326)

Intuitively, perhaps you already know that to get our Uruguay map we only need to use the leaflet() function, pass the sf object and then use the specific function to create areas. This function is addPolygons().

leaflet(Uruguay) %>% 
  addPolygons()

And then again, a tile can provide some context to your map.

leaflet(Uruguay) %>% 
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addPolygons()
leaflet(Uruguay) %>% 
  addProviderTiles(providers$Stamen.Watercolor) %>% 
  addPolygons()
leaflet(Uruguay) %>% 
  addProviderTiles(providers$Stamen.Watercolor) %>% 
  addPolygons(color = "black", weight = 2, fillColor = "white")

A taste of the power of Leaflet is in highlighting the polygons of the shape and perform some operations.

leaflet(Uruguay) %>% 
  addProviderTiles(providers$Stamen.Watercolor) %>% 
  addPolygons(color = "black", weight = 2, fillColor = "white", fillOpacity = 1,
              highlightOptions = highlightOptions(color = "blue", fillColor = "blue", 
                                                  weight = 0, bringToFront = TRUE))
leaflet(Uruguay) %>% 
  addProviderTiles(providers$Stamen.Watercolor) %>% 
  addPolygons(color = "black", weight = 2, fillColor = "white", fillOpacity = 1,
              label = ~NOMBRE, labelOptions = labelOptions(
                style = list("font-weight" = "normal"), 
                textsize = "10px"),
              highlightOptions = highlightOptions(color = "blue", fillColor = "blue", 
                                                  weight = 0, bringToFront = TRUE))

After you get used to leaflet you realize that is easy to create awesome maps. You may struggle a little with the inside function-arguments and probably will iterate a few times, but you will get it done.

#source: https://rstudio.github.io/leaflet/choropleths.html &
# From http://leafletjs.com/examples/choropleth/us-states.js

#install.packages("geojsonio")

#states <- geojsonio::geojson_read("json/us-states.geojson", what = "sp")
#see: https://github.com/rstudio/leaflet/issues/498

states <- geojsonio::geojson_read( 
        x = "https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json"
        , what = "sp"
    )


bins <- c(0, 10, 20, 50, 100, 200, 500, 1000, Inf)
pal <- colorBin("YlOrRd", domain = states$density, bins = bins)

labels <- sprintf(
  "<strong>%s</strong><br/>%g people / mi<sup>2</sup>",
  states$name, states$density
) %>% lapply(htmltools::HTML)

leaflet(states) %>%
  setView(-96, 37.8, 4) %>%
  addProviderTiles("MapBox", options = providerTileOptions(
    id = "mapbox.light",
    accessToken = Sys.getenv('MAPBOX_ACCESS_TOKEN'))) %>%
  addPolygons(
    fillColor = ~pal(density),
    weight = 2,
    opacity = 1,
    color = "white",
    dashArray = "3",
    fillOpacity = 0.7,
    highlight = highlightOptions(
      weight = 5,
      color = "#666",
      dashArray = "",
      fillOpacity = 0.7,
      bringToFront = TRUE),
    label = labels,
    labelOptions = labelOptions(
      style = list("font-weight" = "normal", padding = "3px 8px"),
      textsize = "15px",
      direction = "auto")) %>%
  addLegend(pal = pal, values = ~density, opacity = 0.7, title = NULL,
    position = "bottomright")
NA
NA
Accidents2009 <- Accidents %>% filter(ANIO == "2009")
leaflet(Accidents2009) %>% 
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addCircles()
LS0tDQp0aXRsZTogImxlYWZsZXQgLSB0dXRvcmlhbCINCmF1dGhvcjogIk9ybGFuZG8gU2Fib2dhbC1DYXJkb25hIg0KZGF0ZTogIlN1bW1lciAyMDIzIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IHRydWUNCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQotLS0NCiANCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShsZWFmbGV0KQ0KYGBgDQogDQpMZWFmbGV0IGlzIGEgamF2YXNjcmlwdCBbbGlicmFyeV0oaHR0cHM6Ly9sZWFmbGV0anMuY29tLykuIElmIHlveSBrbm93IHlvdXIgd2F5IGFyb3VuZCAqKmpzKiogaXQgZ29lcyBzb21ldGhpbmcgbGlrZSB0aGlzOiANCg0KYGBge3IsZXZhbD1GQUxTRX0NCiMjVGhpcyBpcyBqYXZhc2NyaXB0IGNvZGUsIERPIE5PVCBSVU4hDQoNCnZhciBtYXAgPSBMLm1hcCgnbWFwJykuc2V0VmlldyhbNTEuNTA1LCAtMC4wOV0sIDEzKTsNCg0KTC50aWxlTGF5ZXIoJ2h0dHBzOi8ve3N9LnRpbGUub3BlbnN0cmVldG1hcC5vcmcve3p9L3t4fS97eX0ucG5nJywgew0KICAgIGF0dHJpYnV0aW9uOiAnJmNvcHk7IDxhIGhyZWY9Imh0dHBzOi8vd3d3Lm9wZW5zdHJlZXRtYXAub3JnL2NvcHlyaWdodCI+T3BlblN0cmVldE1hcDwvYT4gY29udHJpYnV0b3JzJw0KfSkuYWRkVG8obWFwKTsNCg0KTC5tYXJrZXIoWzUxLjUsIC0wLjA5XSkuYWRkVG8obWFwKQ0KICAgIC5iaW5kUG9wdXAoJ0EgcHJldHR5IENTUzMgcG9wdXAuPGJyPiBFYXNpbHkgY3VzdG9taXphYmxlLicpDQogICAgLm9wZW5Qb3B1cCgpOw0KYGBgDQoNCllvdSBzaG91bGQgYWxzbyBrbm93IHRvIGNvbmZpZ3VyZSBhIENDUyBmaWxlIGFuZCBpbmNsdWRlIHRoZSBMZWFmbGV0IGxpYnJhcnkgaW4gdGhlIGhlYWRlciBzZWN0aW9uIG9mIGEgaHRtbCBmaWxlOg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMjVGhpcyBpcyBodG1sIGNvZGUsIERPIE5PVCBSVU4hDQoNCjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly91bnBrZy5jb20vbGVhZmxldEAxLjUuMS9kaXN0L2xlYWZsZXQuY3NzIg0KICAgaW50ZWdyaXR5PSJzaGE1MTIteHdFL0F6OXpyakJJcGhBY0JiM0Y2SlZxeGY0NitDREx3ZkxNSGxvTnU2S0VRQ0FXaTZIY0RVYmVPZkJJcHRGN3RjQ3p1c0tGakZ3Mnl1dkVwREw5d1E9PSINCiAgIGNyb3Nzb3JpZ2luPSIiLz4NCg0KICAgICANCiAjIzwhLS0gTWFrZSBzdXJlIHlvdSBwdXQgdGhpcyBBRlRFUiBMZWFmbGV0J3MgQ1NTIC0tPg0KIA0KICAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly91bnBrZy5jb20vbGVhZmxldEAxLjUuMS9kaXN0L2xlYWZsZXQuanMiDQogICBpbnRlZ3JpdHk9InNoYTUxMi1HZmZQTUYzUnZNZVl5YzFMV01IdEs4RWJQdjBpTlo4L29UdEhQeDkvY2MySUx4USt1OTA1cUl3ZHBVTGFxRGt5QktnT2FCNTdRVE1nN3p0ZzhKbTJPZz09Ig0KICAgY3Jvc3NvcmlnaW49IiI+IDwvc2NyaXB0Pg0KDQpgYGANCg0KRm9ydHVuYXRlbHksIGluIHRoZSBbaHRtbCB3aWRnZXRzIGZvciBSXShodHRwOi8vd3d3Lmh0bWx3aWRnZXRzLm9yZy8pIHRoZXJlIGlzIGEgW0xlYWZsZXQgZm9yIFJdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8pIHRoYXQgbGV0IHlvdSB1c2UgbW9zdCBvZiB0aGUgY2FwYWJpbGl0aWVzIG9mIExlYWZsZXQgd2l0aCB5b3VyIHJlZ3VsYXIgUiBjb2RlLiBUbyBjcmVhdGUgbWFwcyB3aXRoIGxlYWZsZXQgeW91IGNhbiB1c2UgKmRhdGEuZnJhbWUqIG9iamVjdHMsICpzZiogb2JqZWN0cywgKnJhc3Rlciogb2JqZWN0cyBhbmQgKmpzb24qIGZpbGVzLiANCg0KTGVhZmxldCBmb3IgUiBpcyBtZXRob2RvbG9naWNhbGx5IGVhc3kgdG8gdXNlIGFuZCB5b3UgY2FuIGdldCBmYXJ0aGVyIGJ5IGV4cGxvcmluZyB0aGUgYXJndW1lbnRzIG9mIHRoZSBmdW5jdGlvbnMuIEEgbmljZSBmZWF0dXJlIG9mIExlYWZsZXQgaXMgdGhhdCBpcyBpbnRlZ3JhdGVkIHdpdGggc2hpbnkuIA0KDQo8YnIvPg0KPGhyLz4NCg0KIyBSdWRpbWVudHMNCg0KVG8gY3JlYXRlIGEgbGVhZmxldCBvYmplY3QgeW91IG9ubHkgaGF2ZSB0byBjYWxsIHRoZSAqKmxlYWZsZXQoKSoqIGZ1bmN0aW9uLiBJZiB5b3UgaGF2ZSBhbiBvYmplY3QgKHRoaW5rIGFib3V0IGFuICpzZiopIHdoaWNoIHlvdSB3YW50IHRvIHBsb3QgeW91IHNob3VsZCBwYXNzIGl0IHRocm91Z2ggdGhlIGZ1bmN0aW9uIGFzICoqbGVhZmxldCggLVlvdXJPYmplY3QtICkqKi4gVG8gYWRkIGxheWVycyB0aGUgKiolPiUqKiBvcGVyYXRvciBpcyB1c2VkIGJlZm9yZSBhIG5ldyBmdW5jdGlvbiBkZWZpbmluZyBhIG5ldyBjaGFyYWN0ZXJpc3RpYyBvZiB0aGUgbWFwLiANCkFzIGEgYmFja2dyb3VuZCBsYXllciBsZWFmbGV0IGVuYWJsZXMgdG8gdXNlIGRpZmZlcmVudCBtYXBzIHByb3ZpZGVycyB3aXRoICoqYWRkUHJvdmlkZXJUaWxlcyoqIG9yICoqYWRkVGlsZXMuKioNCg0KYGBge3J9DQpsZWFmbGV0KCkgJT4lIA0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRFc3JpLk5hdEdlb1dvcmxkTWFwKQ0KYGBgDQoNCllvdSBjYW4gc2VlIHRoZSBUaWxlcyBvcHRpb25zIFtoZXJlXShodHRwOi8vbGVhZmxldC1leHRyYXMuZ2l0aHViLmlvL2xlYWZsZXQtcHJvdmlkZXJzL3ByZXZpZXcvKS4gDQpJbiB0aGUgcHJldmlvdXMgbWFwIHlvdSBjYW4gem9vbSBpbiBhbmQgem9vbSBvdXQgYWxsIGFyb3VuZCB0aGUgd29ybGQuIFlvdSBjYW4gc2V0IHRoZSBzcGVjaWZpYyB2aWV3IHlvdSB3YW50IHdpdGggdGhlICoqc2V0VmlldygpKiogZnVuY3Rpb24gYXMgaW4gdGhlIGZvbGxvd2luZyBleGFtcGxlcyB3aGVyZSB0aGUgbWFwcyBhcmUgY2VudHJlZCBpbiAqUGxhemEgZGUgbGEgaW5kZXBlbmNpYSogKE1vbnRldmlkZW8sIFVydWd1YXkpDQoNCmBgYHtyfQ0KbGVhZmxldCgpICU+JSANCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkRXNyaS5OYXRHZW9Xb3JsZE1hcCkgJT4lDQogIHNldFZpZXcobG5nID0gLTU2LjE5OTczNSwgbGF0ID0gLTM0LjkwNjU0Mywgem9vbSA9IDgpDQpgYGANCg0KYGBge3J9DQpsZWFmbGV0KCkgJT4lIA0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgc2V0VmlldyhsbmcgPSAtNTYuMTk5NzM1LCBsYXQgPSAtMzQuOTA2NTQzLCB6b29tID0gMTYpDQpgYGANCg0KDQoNCk9mIGNvdXJzZSwgdGhlIG1haW4gYmVuZWZpdCBvZiB1c2luZyBsZWFmbGV0IGlzIGdldHRpbmcgeW91ciBvd24gZGF0YSBpbnRvIHRoZSBtYXAuIFlvdSBjYW4gYWRkIGEgbWFya2VyIHdpdGggKiphZGRNYXJrZXJzKiogYW5kIHNwZWNpZnlpbmcgdGhlICpsb25nKiBhbmQgKmxhdCogYXJndW1lbnRzIA0KDQpgYGB7cn0NCmxlYWZsZXQoKSAlPiUgDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBzZXRWaWV3KGxuZyA9IC01Ni4xOTk3MzUsIGxhdCA9IC0zNC45MDY1NDMsIHpvb20gPSAxNikgJT4lIA0KICBhZGRNYXJrZXJzKC01Ni4xOTk3MzUsIC0zNC45MDY1NDMpDQpgYGANCg0KWW91IGNhbiBldmVuIGFzc2lnbiBhIHBvcC11cCBieSBzZXR0aW5nIHRoZSAqcG9wdXAqIGFyZ3VtZW50Lg0KDQpgYGB7cn0NCmxlYWZsZXQoKSAlPiUgDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBzZXRWaWV3KGxuZyA9IC01Ni4xOTk3MzUsIGxhdCA9IC0zNC45MDY1NDMsIHpvb20gPSAxNikgJT4lIA0KICBhZGRNYXJrZXJzKC01Ni4xOTk3MzUsIC0zNC45MDY1NDMsIHBvcHVwID0gIlBsYXphIEluZGVwZW5kZW5jaWEiKQ0KYGBgDQoNCkFkZCBtYW55IG1vcmUgbWFya2VycyBjYW4gYmUgYSBtYXR0ZXIgb2YgY2FsbGluZyB0aGUgKiphZGRNYXJrZXIqKiBmdW5jdGlvbiBtdWx0aXBsZSB0aW1lcy4gDQoNCmBgYHtyfQ0KbGVhZmxldCgpICU+JSANCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIHNldFZpZXcobG5nID0gLTU2LjE5OTczNSwgbGF0ID0gLTM0LjkwNjU0Mywgem9vbSA9IDE2KSAlPiUgDQogIGFkZE1hcmtlcnMoLTU2LjE5OTczNSwgLTM0LjkwNjU0MywgcG9wdXAgPSAiUGxhemEgSW5kZXBlbmRlbmNpYSIpICU+JSANCiAgYWRkTWFya2VycygtNTYuMTk4MzA5LCAtMzQuOTA2NTYzLCBwb3B1cCA9ICJQYWxhY2lvIFNhbHZvIikNCmBgYA0KDQoNCk9mZiBjb3Vyc2UsIGl0IGRvZXMgbm90IG1ha2Ugc2Vuc2UgdG8gYWRkIGdlb2dyYXBoaWMgaW5mb3JtYXRpb24gb25lIGJ5IG9uZTsgcmF0aGVyLCBhIGJldHRlciBvcHRpb24gaXMgZ2l2ZSB0byB0aGUgZnVuY3Rpb24gYW4gb2JqZWN0IGFuZCBhdXRvbWF0aWNhbGx5IHBsb3QgYWxsIHRoZSBtYXJrZXJzLg0KDQpgYGB7cn0NClNpdGVzIDwtIGRhdGEuZnJhbWUobG9uZ2l0dWQgPSBjKC01Ni4xOTk3MzUsIC01Ni4xOTgzMDksIC01Ni4yMDEwMzcpLCANCiAgICAgICAgICAgICAgICAgICAgbGF0dGl0dWQgPSBjKC0zNC45MDY1NDMsIC0zNC45MDY1NjMsIC0zNC45MDc4OTYpLCANCiAgICAgICAgICAgICAgICAgICAgTmFtZSA9IGMoIlBsYXphIEluZGVwZW5kZW5jaWEiLCAiUGFsYWNpbyBTYWx2byIsICJUZWF0cm8gU29saXMiKSkNCg0KbGVhZmxldChTaXRlcykgJT4lIA0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgc2V0VmlldyhsbmcgPSAtNTYuMTk5NzM1LCBsYXQgPSAtMzQuOTA2NTQzLCB6b29tID0gMTYpICU+JSANCiAgYWRkTWFya2VycyhsbmcgPSB+bG9uZ2l0dWQsIGxhdCA9IH5sYXR0aXR1ZCwgcG9wdXAgPSB+TmFtZSkNCg0KYGBgDQoNCkluIHRoZSBsYXN0IGV4YW1wbGUgd2UgY29uZmlndXJlZCBvdXIgZGF0YSBpbiB0aGUgKipsZWFmbGV0KCkqKiBmdW5jdGlvbiwgYnV0IGlzIGFsc28gcG9zc2libGUgdG8gZG8gaXQgaW4gdGhlICoqYWRkTWFya2VycygpKiogZnVuY3Rpb24uIFdoYXQgZG8geW91IHRoaW5rIGlzIHRoZSBkaWZmZXJlbmNlPw0KDQpgYGB7cn0NCmxlYWZsZXQoKSAlPiUgDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBzZXRWaWV3KGxuZyA9IC01Ni4xOTk3MzUsIGxhdCA9IC0zNC45MDY1NDMsIHpvb20gPSAxNikgJT4lIA0KICBhZGRNYXJrZXJzKGRhdGEgPSBTaXRlcywgbG5nID0gfmxvbmdpdHVkLCBsYXQgPSB+bGF0dGl0dWQsIHBvcHVwID0gfk5hbWUpDQpgYGANCg0KDQpBIG1hcmtlciBjYW4gYWxzbyBiZSBhIGNpcmNsZToNCg0KYGBge3J9DQpsZWFmbGV0KCkgJT4lIA0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgc2V0VmlldyhsbmcgPSAtNTYuMTk5NzM1LCBsYXQgPSAtMzQuOTA2NTQzLCB6b29tID0gMTYpICU+JSANCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gU2l0ZXMsIGxuZyA9IH5sb25naXR1ZCwgbGF0ID0gfmxhdHRpdHVkKQ0KYGBgDQoNCjxici8+DQo8aHIvPg0KDQojIFBvaW50cyBhbmQgQ2lyY2xlcw0KDQpCdXQgaXMgdGltZSB0byBwbG90IG1vcmUgbmF0dXJhbCAiZ2VvbWV0cmljLXZlY3RvcmlhbCIgZWxlbWVudHMgYXMgYSBjaXJjbGUgKCoqYWRkQ2lyY2xlcygpKiopDQoNCmBgYHtyfQ0KbGVhZmxldCgpICU+JSANCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIHNldFZpZXcobG5nID0gLTU2LjE5OTczNSwgbGF0ID0gLTM0LjkwNjU0Mywgem9vbSA9IDE2KSAlPiUgDQogIGFkZENpcmNsZXMoZGF0YSA9IFNpdGVzLCBsbmcgPSB+bG9uZ2l0dWQsIGxhdCA9IH5sYXR0aXR1ZCwNCiAgICAgICAgICAgICAgICAgICByYWRpdXMgPSAxMDApDQpgYGANCg0KDQpXaGF0IGlzIHRoZSBkaWZmZXJlbmNlIGFtb25nICoqYWRkQ2lyY2xlTWFya2VyKCkqKiBhbmQgKiphZGRDaXJjbGUoKS4qKj8NCg0KYGBge3J9DQpsZWFmbGV0KCkgJT4lIA0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgc2V0VmlldyhsbmcgPSAtNTYuMTk5NzM1LCBsYXQgPSAtMzQuOTA2NTQzLCB6b29tID0gMTYpICU+JSANCiAgYWRkQ2lyY2xlcyhkYXRhID0gU2l0ZXMsIGxuZyA9IH5sb25naXR1ZCwgbGF0ID0gfmxhdHRpdHVkLA0KICAgICAgICAgICAgICAgICAgIHJhZGl1cyA9IDEwMCwgb3BhY2l0eSA9IDEsIGZpbGxPcGFjaXR5ID0gMSkNCmBgYA0KDQpXaXRoaW4gdGhlIGFyZ3VtZW50cyBvZiB0aGUgZnVuY3Rpb24gdGhlcmUgYXJlIG9wdGlvbiB0byBzZXQgZWxlbWVudHMgbGlrZSB0aGUgcmFkaXVzLCB0aGUgb3BhY2l0eSBvciB0aGUgY29sb3VyLiANCg0KYGBge3J9DQpsZWFmbGV0KCkgJT4lIA0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgc2V0VmlldyhsbmcgPSAtNTYuMTk5NzM1LCBsYXQgPSAtMzQuOTA2NTQzLCB6b29tID0gMTYpICU+JSANCiAgYWRkQ2lyY2xlcyhkYXRhID0gU2l0ZXMsIGxuZyA9IH5sb25naXR1ZCwgbGF0ID0gfmxhdHRpdHVkLA0KICAgICAgICAgICAgICAgICAgIHJhZGl1cyA9IDEwMCwgb3BhY2l0eSA9IDEsIGZpbGxPcGFjaXR5ID0gMC43LCBjb2xvciA9ICJibGFjayIsIGZpbGxDb2xvciA9ICJncmVlbiIpDQpgYGANCg0KPGJyLz4NCjxoci8+DQoNCiMgVXNpbmcgb3VyIHNoYXBlcw0KDQpgYGB7cn0NClVydWd1YXkgPC0gc3RfcmVhZCgiTW9udGV2aWRlb19EYXRhL1ZlY3RvcmlhbGVzXzIwMTEvaW5lX2RlcHRvLnNocCIpDQojaW5lX2RlcHRvLnNocA0KYGBgDQoNCmBgYHtyfQ0KQWNjaWRlbnRzIDwtIHN0X3JlYWQoIk1vbnRldmlkZW9fRGF0YS9BY2NpZGVudGVzMjAwNi0yMDEwL2FjY2lkZW50ZXMyMDA2LTIwMTAuc2hwIikNCmBgYA0KDQpgYGB7cn0NClVydWd1YXkgPSBzdF9zZXRfY3JzKFVydWd1YXksIDMyNzIxKQ0KQWNjaWRlbnRzID0gc3Rfc2V0X2NycyhBY2NpZGVudHMsIDMyNzIxKQ0KDQpVcnVndWF5IDwtIHN0X3RyYW5zZm9ybShVcnVndWF5LDQzMjYpDQpBY2NpZGVudHMgPSBzdF90cmFuc2Zvcm0oQWNjaWRlbnRzLCA0MzI2KQ0KYGBgDQoNCkludHVpdGl2ZWx5LCBwZXJoYXBzIHlvdSBhbHJlYWR5IGtub3cgdGhhdCB0byBnZXQgb3VyIFVydWd1YXkgbWFwIHdlIG9ubHkgbmVlZCB0byB1c2UgdGhlICoqbGVhZmxldCgpKiogZnVuY3Rpb24sIHBhc3MgdGhlICoqc2YqKiBvYmplY3QgYW5kIHRoZW4gdXNlIHRoZSBzcGVjaWZpYyBmdW5jdGlvbiB0byBjcmVhdGUgYXJlYXMuIFRoaXMgZnVuY3Rpb24gaXMgKiphZGRQb2x5Z29ucygpLioqDQoNCmBgYHtyfQ0KbGVhZmxldChVcnVndWF5KSAlPiUgDQogIGFkZFBvbHlnb25zKCkNCmBgYA0KDQpBbmQgdGhlbiBhZ2FpbiwgYSB0aWxlIGNhbiBwcm92aWRlIHNvbWUgY29udGV4dCB0byB5b3VyIG1hcC4gDQoNCmBgYHtyfQ0KbGVhZmxldChVcnVndWF5KSAlPiUgDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JSANCiAgYWRkUG9seWdvbnMoKQ0KYGBgDQoNCmBgYHtyfQ0KbGVhZmxldChVcnVndWF5KSAlPiUgDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5XYXRlcmNvbG9yKSAlPiUgDQogIGFkZFBvbHlnb25zKCkNCmBgYA0KDQoNCmBgYHtyfQ0KbGVhZmxldChVcnVndWF5KSAlPiUgDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5XYXRlcmNvbG9yKSAlPiUgDQogIGFkZFBvbHlnb25zKGNvbG9yID0gImJsYWNrIiwgd2VpZ2h0ID0gMiwgZmlsbENvbG9yID0gIndoaXRlIikNCmBgYA0KDQpBIHRhc3RlIG9mIHRoZSBwb3dlciBvZiBMZWFmbGV0IGlzIGluIGhpZ2hsaWdodGluZyB0aGUgcG9seWdvbnMgb2YgdGhlIHNoYXBlIGFuZCBwZXJmb3JtIHNvbWUgb3BlcmF0aW9ucy4gDQoNCmBgYHtyfQ0KbGVhZmxldChVcnVndWF5KSAlPiUgDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5XYXRlcmNvbG9yKSAlPiUgDQogIGFkZFBvbHlnb25zKGNvbG9yID0gImJsYWNrIiwgd2VpZ2h0ID0gMiwgZmlsbENvbG9yID0gIndoaXRlIiwgZmlsbE9wYWNpdHkgPSAxLA0KICAgICAgICAgICAgICBoaWdobGlnaHRPcHRpb25zID0gaGlnaGxpZ2h0T3B0aW9ucyhjb2xvciA9ICJibHVlIiwgZmlsbENvbG9yID0gImJsdWUiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gMCwgYnJpbmdUb0Zyb250ID0gVFJVRSkpDQpgYGANCg0KYGBge3J9DQpsZWFmbGV0KFVydWd1YXkpICU+JSANCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLldhdGVyY29sb3IpICU+JSANCiAgYWRkUG9seWdvbnMoY29sb3IgPSAiYmxhY2siLCB3ZWlnaHQgPSAyLCBmaWxsQ29sb3IgPSAid2hpdGUiLCBmaWxsT3BhY2l0eSA9IDEsDQogICAgICAgICAgICAgIGxhYmVsID0gfk5PTUJSRSwgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKA0KICAgICAgICAgICAgICAgIHN0eWxlID0gbGlzdCgiZm9udC13ZWlnaHQiID0gIm5vcm1hbCIpLCANCiAgICAgICAgICAgICAgICB0ZXh0c2l6ZSA9ICIxMHB4IiksDQogICAgICAgICAgICAgIGhpZ2hsaWdodE9wdGlvbnMgPSBoaWdobGlnaHRPcHRpb25zKGNvbG9yID0gImJsdWUiLCBmaWxsQ29sb3IgPSAiYmx1ZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSAwLCBicmluZ1RvRnJvbnQgPSBUUlVFKSkNCmBgYA0KDQoNCkFmdGVyIHlvdSBnZXQgdXNlZCB0byBsZWFmbGV0IHlvdSByZWFsaXplIHRoYXQgaXMgZWFzeSB0byBjcmVhdGUgYXdlc29tZSBtYXBzLiBZb3UgbWF5IHN0cnVnZ2xlIGEgbGl0dGxlICB3aXRoIHRoZSBpbnNpZGUgZnVuY3Rpb24tYXJndW1lbnRzIGFuZCBwcm9iYWJseSB3aWxsIGl0ZXJhdGUgYSBmZXcgdGltZXMsIGJ1dCB5b3Ugd2lsbCBnZXQgaXQgZG9uZS4gDQoNCmBgYHtyfQ0KI3NvdXJjZTogaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0L2Nob3JvcGxldGhzLmh0bWwgJg0KIyBGcm9tIGh0dHA6Ly9sZWFmbGV0anMuY29tL2V4YW1wbGVzL2Nob3JvcGxldGgvdXMtc3RhdGVzLmpzDQoNCiNpbnN0YWxsLnBhY2thZ2VzKCJnZW9qc29uaW8iKQ0KDQojc3RhdGVzIDwtIGdlb2pzb25pbzo6Z2VvanNvbl9yZWFkKCJqc29uL3VzLXN0YXRlcy5nZW9qc29uIiwgd2hhdCA9ICJzcCIpDQojc2VlOiBodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9sZWFmbGV0L2lzc3Vlcy80OTgNCg0Kc3RhdGVzIDwtIGdlb2pzb25pbzo6Z2VvanNvbl9yZWFkKCANCiAgICAgICAgeCA9ICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vUHVibGljYU11bmRpL01hcHBpbmdBUEkvbWFzdGVyL2RhdGEvZ2VvanNvbi91cy1zdGF0ZXMuanNvbiINCiAgICAgICAgLCB3aGF0ID0gInNwIg0KICAgICkNCg0KDQpiaW5zIDwtIGMoMCwgMTAsIDIwLCA1MCwgMTAwLCAyMDAsIDUwMCwgMTAwMCwgSW5mKQ0KcGFsIDwtIGNvbG9yQmluKCJZbE9yUmQiLCBkb21haW4gPSBzdGF0ZXMkZGVuc2l0eSwgYmlucyA9IGJpbnMpDQoNCmxhYmVscyA8LSBzcHJpbnRmKA0KICAiPHN0cm9uZz4lczwvc3Ryb25nPjxici8+JWcgcGVvcGxlIC8gbWk8c3VwPjI8L3N1cD4iLA0KICBzdGF0ZXMkbmFtZSwgc3RhdGVzJGRlbnNpdHkNCikgJT4lIGxhcHBseShodG1sdG9vbHM6OkhUTUwpDQoNCmxlYWZsZXQoc3RhdGVzKSAlPiUNCiAgc2V0VmlldygtOTYsIDM3LjgsIDQpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKCJNYXBCb3giLCBvcHRpb25zID0gcHJvdmlkZXJUaWxlT3B0aW9ucygNCiAgICBpZCA9ICJtYXBib3gubGlnaHQiLA0KICAgIGFjY2Vzc1Rva2VuID0gU3lzLmdldGVudignTUFQQk9YX0FDQ0VTU19UT0tFTicpKSkgJT4lDQogIGFkZFBvbHlnb25zKA0KICAgIGZpbGxDb2xvciA9IH5wYWwoZGVuc2l0eSksDQogICAgd2VpZ2h0ID0gMiwNCiAgICBvcGFjaXR5ID0gMSwNCiAgICBjb2xvciA9ICJ3aGl0ZSIsDQogICAgZGFzaEFycmF5ID0gIjMiLA0KICAgIGZpbGxPcGFjaXR5ID0gMC43LA0KICAgIGhpZ2hsaWdodCA9IGhpZ2hsaWdodE9wdGlvbnMoDQogICAgICB3ZWlnaHQgPSA1LA0KICAgICAgY29sb3IgPSAiIzY2NiIsDQogICAgICBkYXNoQXJyYXkgPSAiIiwNCiAgICAgIGZpbGxPcGFjaXR5ID0gMC43LA0KICAgICAgYnJpbmdUb0Zyb250ID0gVFJVRSksDQogICAgbGFiZWwgPSBsYWJlbHMsDQogICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKA0KICAgICAgc3R5bGUgPSBsaXN0KCJmb250LXdlaWdodCIgPSAibm9ybWFsIiwgcGFkZGluZyA9ICIzcHggOHB4IiksDQogICAgICB0ZXh0c2l6ZSA9ICIxNXB4IiwNCiAgICAgIGRpcmVjdGlvbiA9ICJhdXRvIikpICU+JQ0KICBhZGRMZWdlbmQocGFsID0gcGFsLCB2YWx1ZXMgPSB+ZGVuc2l0eSwgb3BhY2l0eSA9IDAuNywgdGl0bGUgPSBOVUxMLA0KICAgIHBvc2l0aW9uID0gImJvdHRvbXJpZ2h0IikNCg0KDQpgYGANCg0KDQpgYGB7cn0NCkFjY2lkZW50czIwMDkgPC0gQWNjaWRlbnRzICU+JSBmaWx0ZXIoQU5JTyA9PSAiMjAwOSIpDQpsZWFmbGV0KEFjY2lkZW50czIwMDkpICU+JSANCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lIA0KICBhZGRDaXJjbGVzKCkNCmBgYA0KDQoNCg0KIA==