Load required libraries

library(dplyr)
library(sf)
library(jsonlite)
library(leaflet)
library(glue)
library(htmlwidgets)
# if needed, run first:
# install.packages("BelgiumMaps.StatBel", repos = "http://www.datatailor.be/rcube", type = "source")
library(BelgiumMaps.StatBel) 

Load/fetch requried data

Used in example:

Load arrondissement borders

# load district/arrondissement spatial structure from package BelgiumMaps.StatBel
data('BE_ADMIN_DISTRICT') 
# convert to simple features dataset-structure
arronds = st_as_sf(BE_ADMIN_DISTRICT) 
# filter on on arrondissementen located in Flanders and Brussels
arronds = arronds %>%
  filter(TX_RGN_DESCR_NL %in% c('Vlaams Gewest', 'Brussels Hoofdstedelijk Gewest'))
# rough plot, to verify that spatial data is as expected
plot(arronds, max.plot = 1)

Fetch open data VDAB office locations

# fetch open data in JSON-format
vdab.kantoren = as_tibble(fromJSON('http://opendata.vdab.be/vdab/locaties.json'))
vdab.kantoren # dataset with office locations, including coordinates

Describe and change VDAB-data

# count te different types of VDAB offices
vdab.kantoren %>%
  group_by(typelocatie) %>%
  tally()
vdab.kantoren = vdab.kantoren %>%
  mutate(
    # add a variable 'popup' with the the HTML-snippet per office,
    # which  will be shown in the map-popup
    popup = glue("<h3>{title}</h3><br /><b>Type: </b>{typelocatie}<br /><b>Adres</b>:  {straatNr} {plaats}<br /><b>Teleloon</b>: {telefoonnummer}"),
    
    # make sure that coordinates are not a character, but numeric variables
    lat = as.numeric(lat),
    lon = as.numeric(lon))

Construct interactive map

Minimal map example

# basic interactive map with a oneliner
# (width-argument not needed, only for online-output)
leaflet(vdab.kantoren, width = '100%') %>% addTiles() %>% addMarkers()

Styled map example w/t popups

Styling:

  • Popups with VDAB-office information.
  • Custom tile background
  • Custom color & symbol markers, based on discrete values (office-type)
  • Overlay of arrondissement-polygons: now grey/white-styled, could be colored based on e.g. unemployment-rate.
# create two icons with different color & symbol for the two types of VDAB-offices
# for different symbols, cf. https://rstudio.github.io/leaflet/markers.html 
kantoor_icons <- awesomeIconList(
  'kantoor met onthaal' = makeAwesomeIcon(icon = "user", library = "fa", markerColor = "blue"),
  'opleidingscentrum' = makeAwesomeIcon(icon = "graduation-cap", library = "fa", markerColor = "red"))
m.kantoren = leaflet(arronds, width = '100%') %>% 
  # add minimalistic-style map tiles
  addProviderTiles(providers$CartoDB.Positron) %>% 
  
  # add grey arrondissement polygons w/t white border
  addPolygons(fillColor =  'grey20', color = 'white') %>% 
  
  # add custom styled markers w/t popups
  addAwesomeMarkers(
    data = vdab.kantoren,
    lng = ~lon, lat = ~lat,
    icon = ~kantoor_icons[typelocatie],
    popup = ~popup)
m.kantoren

Save stand-alone map

saveWidget(m.kantoren, 'map_vdab_kantoren.html')
LS0tDQp0aXRsZTogIlIgdGlkeXZlcnNlIC0gVkRBQiBvcGVuIGRhdGEgZXhhbXBsZSBtYXAiDQphdXRob3I6ICJNYWFydGVuIEhlcm1hbnMgLSBAaGVybWFuc20iDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOiANCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQotLS0NCg0KIyBMb2FkIHJlcXVpcmVkIGxpYnJhcmllcw0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShqc29ubGl0ZSkNCmxpYnJhcnkobGVhZmxldCkNCmxpYnJhcnkoZ2x1ZSkNCmxpYnJhcnkoaHRtbHdpZGdldHMpDQoNCiMgaWYgbmVlZGVkLCBydW4gZmlyc3Q6DQojIGluc3RhbGwucGFja2FnZXMoIkJlbGdpdW1NYXBzLlN0YXRCZWwiLCByZXBvcyA9ICJodHRwOi8vd3d3LmRhdGF0YWlsb3IuYmUvcmN1YmUiLCB0eXBlID0gInNvdXJjZSIpDQpsaWJyYXJ5KEJlbGdpdW1NYXBzLlN0YXRCZWwpIA0KYGBgDQoNCiMgTG9hZC9mZXRjaCByZXF1cmllZCBkYXRhDQoNClVzZWQgaW4gZXhhbXBsZToNCg0KKiBbVkRBQiBvZmZpY2UtbG9jYXRpb25zIGFzIG9wZW4gZGF0YV0oaHR0cHM6Ly93d3cudmRhYi5iZS90cmVuZHMvb3Blbl9kYXRhL2xvY2F0aWVzKSAoSlNPTi1maWxlKS4NCiogQWRtaW5pc3RyYXRpdmUgYm91bmRhcmllcyBmb3IgQmVsZ2lhbiAqYXJyb25kaXNzZW1lbnRlbiosIGNvdXJ0ZXN5IG9mIHRoZSBbQmVsZ2l1bU1hcHMuU3RhdEJlbF0oaHR0cHM6Ly9naXRodWIuY29tL2Jub3NhYy9CZWxnaXVtTWFwcy5TdGF0QmVsKSBSIHBhY2thZ2UuIA0KDQojIyBMb2FkIGFycm9uZGlzc2VtZW50IGJvcmRlcnMNCg0KYGBge3J9DQojIGxvYWQgZGlzdHJpY3QvYXJyb25kaXNzZW1lbnQgc3BhdGlhbCBzdHJ1Y3R1cmUgZnJvbSBwYWNrYWdlIEJlbGdpdW1NYXBzLlN0YXRCZWwNCmRhdGEoJ0JFX0FETUlOX0RJU1RSSUNUJykgDQoNCiMgY29udmVydCB0byBzaW1wbGUgZmVhdHVyZXMgZGF0YXNldC1zdHJ1Y3R1cmUNCmFycm9uZHMgPSBzdF9hc19zZihCRV9BRE1JTl9ESVNUUklDVCkgDQoNCiMgZmlsdGVyIG9uIG9uIGFycm9uZGlzc2VtZW50ZW4gbG9jYXRlZCBpbiBGbGFuZGVycyBhbmQgQnJ1c3NlbHMNCmFycm9uZHMgPSBhcnJvbmRzICU+JQ0KICBmaWx0ZXIoVFhfUkdOX0RFU0NSX05MICVpbiUgYygnVmxhYW1zIEdld2VzdCcsICdCcnVzc2VscyBIb29mZHN0ZWRlbGlqayBHZXdlc3QnKSkNCmBgYA0KDQpgYGB7cn0NCiMgcm91Z2ggcGxvdCwgdG8gdmVyaWZ5IHRoYXQgc3BhdGlhbCBkYXRhIGlzIGFzIGV4cGVjdGVkDQpwbG90KGFycm9uZHMsIG1heC5wbG90ID0gMSkNCmBgYA0KDQojIyBGZXRjaCBvcGVuIGRhdGEgVkRBQiBvZmZpY2UgbG9jYXRpb25zIA0KDQpgYGB7cn0NCiMgZmV0Y2ggb3BlbiBkYXRhIGluIEpTT04tZm9ybWF0DQp2ZGFiLmthbnRvcmVuID0gYXNfdGliYmxlKGZyb21KU09OKCdodHRwOi8vb3BlbmRhdGEudmRhYi5iZS92ZGFiL2xvY2F0aWVzLmpzb24nKSkNCnZkYWIua2FudG9yZW4gIyBkYXRhc2V0IHdpdGggb2ZmaWNlIGxvY2F0aW9ucywgaW5jbHVkaW5nIGNvb3JkaW5hdGVzDQpgYGANCg0KDQojIyBEZXNjcmliZSBhbmQgY2hhbmdlIFZEQUItZGF0YQ0KDQoNCmBgYHtyfQ0KIyBjb3VudCB0ZSBkaWZmZXJlbnQgdHlwZXMgb2YgVkRBQiBvZmZpY2VzDQp2ZGFiLmthbnRvcmVuICU+JQ0KICBncm91cF9ieSh0eXBlbG9jYXRpZSkgJT4lDQogIHRhbGx5KCkNCmBgYA0KDQoNCmBgYHtyfQ0KdmRhYi5rYW50b3JlbiA9IHZkYWIua2FudG9yZW4gJT4lDQogIG11dGF0ZSgNCiAgICAjIGFkZCBhIHZhcmlhYmxlICdwb3B1cCcgd2l0aCB0aGUgdGhlIEhUTUwtc25pcHBldCBwZXIgb2ZmaWNlLA0KICAgICMgd2hpY2ggIHdpbGwgYmUgc2hvd24gaW4gdGhlIG1hcC1wb3B1cA0KICAgIHBvcHVwID0gZ2x1ZSgiPGgzPnt0aXRsZX08L2gzPjxiciAvPjxiPlR5cGU6IDwvYj57dHlwZWxvY2F0aWV9PGJyIC8+PGI+QWRyZXM8L2I+OiAge3N0cmFhdE5yfSB7cGxhYXRzfTxiciAvPjxiPlRlbGVsb29uPC9iPjoge3RlbGVmb29ubnVtbWVyfSIpLA0KICAgIA0KICAgICMgbWFrZSBzdXJlIHRoYXQgY29vcmRpbmF0ZXMgYXJlIG5vdCBhIGNoYXJhY3RlciwgYnV0IG51bWVyaWMgdmFyaWFibGVzDQogICAgbGF0ID0gYXMubnVtZXJpYyhsYXQpLA0KICAgIGxvbiA9IGFzLm51bWVyaWMobG9uKSkNCmBgYA0KDQoNCg0KIyBDb25zdHJ1Y3QgaW50ZXJhY3RpdmUgbWFwDQoNCiMjIE1pbmltYWwgbWFwIGV4YW1wbGUNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQojIGJhc2ljIGludGVyYWN0aXZlIG1hcCB3aXRoIGEgb25lbGluZXINCiMgKHdpZHRoLWFyZ3VtZW50IG5vdCBuZWVkZWQsIG9ubHkgZm9yIG9ubGluZS1vdXRwdXQpDQpsZWFmbGV0KHZkYWIua2FudG9yZW4sIHdpZHRoID0gJzEwMCUnKSAlPiUgYWRkVGlsZXMoKSAlPiUgYWRkTWFya2VycygpDQpgYGANCg0KIyMgU3R5bGVkIG1hcCBleGFtcGxlIHcvdCBwb3B1cHMNCg0KU3R5bGluZzoNCg0KKiBQb3B1cHMgd2l0aCBWREFCLW9mZmljZSBpbmZvcm1hdGlvbi4NCiogQ3VzdG9tIHRpbGUgYmFja2dyb3VuZA0KKiBDdXN0b20gY29sb3IgJiBzeW1ib2wgbWFya2VycywgYmFzZWQgb24gZGlzY3JldGUgdmFsdWVzIChvZmZpY2UtdHlwZSkNCiogT3ZlcmxheSBvZiAqYXJyb25kaXNzZW1lbnQqLXBvbHlnb25zOiBub3cgZ3JleS93aGl0ZS1zdHlsZWQsIGNvdWxkIGJlIGNvbG9yZWQgYmFzZWQgb24gZS5nLiB1bmVtcGxveW1lbnQtcmF0ZS4gDQoNCmBgYHtyfQ0KIyBjcmVhdGUgdHdvIGljb25zIHdpdGggZGlmZmVyZW50IGNvbG9yICYgc3ltYm9sIGZvciB0aGUgdHdvIHR5cGVzIG9mIFZEQUItb2ZmaWNlcw0KIyBmb3IgZGlmZmVyZW50IHN5bWJvbHMsIGNmLiBodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvbWFya2Vycy5odG1sIA0Ka2FudG9vcl9pY29ucyA8LSBhd2Vzb21lSWNvbkxpc3QoDQogICdrYW50b29yIG1ldCBvbnRoYWFsJyA9IG1ha2VBd2Vzb21lSWNvbihpY29uID0gInVzZXIiLCBsaWJyYXJ5ID0gImZhIiwgbWFya2VyQ29sb3IgPSAiYmx1ZSIpLA0KICAnb3BsZWlkaW5nc2NlbnRydW0nID0gbWFrZUF3ZXNvbWVJY29uKGljb24gPSAiZ3JhZHVhdGlvbi1jYXAiLCBsaWJyYXJ5ID0gImZhIiwgbWFya2VyQ29sb3IgPSAicmVkIikpDQpgYGANCg0KYGBge3J9DQptLmthbnRvcmVuID0gbGVhZmxldChhcnJvbmRzLCB3aWR0aCA9ICcxMDAlJykgJT4lIA0KICAjIGFkZCBtaW5pbWFsaXN0aWMtc3R5bGUgbWFwIHRpbGVzDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JSANCiAgDQogICMgYWRkIGdyZXkgYXJyb25kaXNzZW1lbnQgcG9seWdvbnMgdy90IHdoaXRlIGJvcmRlcg0KICBhZGRQb2x5Z29ucyhmaWxsQ29sb3IgPSAgJ2dyZXkyMCcsIGNvbG9yID0gJ3doaXRlJykgJT4lIA0KICANCiAgIyBhZGQgY3VzdG9tIHN0eWxlZCBtYXJrZXJzIHcvdCBwb3B1cHMNCiAgYWRkQXdlc29tZU1hcmtlcnMoDQogICAgZGF0YSA9IHZkYWIua2FudG9yZW4sDQogICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwNCiAgICBpY29uID0gfmthbnRvb3JfaWNvbnNbdHlwZWxvY2F0aWVdLA0KICAgIHBvcHVwID0gfnBvcHVwKQ0KYGBgDQoNCmBgYHtyfQ0KbS5rYW50b3Jlbg0KYGBgDQoNCiMgU2F2ZSBzdGFuZC1hbG9uZSBtYXANCg0KYGBge3J9DQpzYXZlV2lkZ2V0KG0ua2FudG9yZW4sICdtYXBfdmRhYl9rYW50b3Jlbi5odG1sJykNCmBgYA0KDQo=