Load required libraries

library(dplyr)
library(sf)
library(jsonlite)
library(leaflet)
library(stringr)
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 = str_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 blackground 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, 'vdab_api_popup_map.html')
LS0tDQp0aXRsZTogIlIgdGlkeXZlcnNlIC0gVkRBQiBvcGVuIGRhdGEgZXhhbXBsZSBtYXAiDQphdXRob3I6ICJNYWFydGVuIEhlcm1hbnMgLSBAaGVybWFuc20iDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOiANCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQotLS0NCg0KIyBMb2FkIHJlcXVpcmVkIGxpYnJhcmllcw0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShqc29ubGl0ZSkNCmxpYnJhcnkobGVhZmxldCkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoaHRtbHdpZGdldHMpDQoNCiMgaWYgbmVlZGVkLCBydW4gZmlyc3Q6DQojIGluc3RhbGwucGFja2FnZXMoIkJlbGdpdW1NYXBzLlN0YXRCZWwiLCByZXBvcyA9ICJodHRwOi8vd3d3LmRhdGF0YWlsb3IuYmUvcmN1YmUiLCB0eXBlID0gInNvdXJjZSIpDQpsaWJyYXJ5KEJlbGdpdW1NYXBzLlN0YXRCZWwpIA0KYGBgDQoNCiMgTG9hZC9mZXRjaCByZXF1cmllZCBkYXRhDQoNClVzZWQgaW4gZXhhbXBsZToNCg0KKiBbVkRBQiBvZmZpY2UtbG9jYXRpb25zIGFzIG9wZW4gZGF0YV0oaHR0cHM6Ly93d3cudmRhYi5iZS90cmVuZHMvb3Blbl9kYXRhL2xvY2F0aWVzKSAoSlNPTi1maWxlKS4NCiogQWRtaW5pc3RyYXRpdmUgYm91bmRhcmllcyBmb3IgQmVsZ2lhbiAqYXJyb25kaXNzZW1lbnRlbiosIGNvdXJ0ZXN5IG9mIHRoZSBbQmVsZ2l1bU1hcHMuU3RhdEJlbF0oaHR0cHM6Ly9naXRodWIuY29tL2Jub3NhYy9CZWxnaXVtTWFwcy5TdGF0QmVsKSBSIHBhY2thZ2UuIA0KDQojIyBMb2FkIGFycm9uZGlzc2VtZW50IGJvcmRlcnMNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQojIGxvYWQgZGlzdHJpY3QvYXJyb25kaXNzZW1lbnQgc3BhdGlhbCBzdHJ1Y3R1cmUgZnJvbSBwYWNrYWdlIEJlbGdpdW1NYXBzLlN0YXRCZWwNCmRhdGEoJ0JFX0FETUlOX0RJU1RSSUNUJykgDQoNCiMgY29udmVydCB0byBzaW1wbGUgZmVhdHVyZXMgZGF0YXNldC1zdHJ1Y3R1cmUNCmFycm9uZHMgPSBzdF9hc19zZihCRV9BRE1JTl9ESVNUUklDVCkgDQoNCiMgZmlsdGVyIG9uIG9uIGFycm9uZGlzc2VtZW50ZW4gbG9jYXRlZCBpbiBGbGFuZGVycyBhbmQgQnJ1c3NlbHMNCmFycm9uZHMgPSBhcnJvbmRzICU+JQ0KICBmaWx0ZXIoVFhfUkdOX0RFU0NSX05MICVpbiUgYygnVmxhYW1zIEdld2VzdCcsICdCcnVzc2VscyBIb29mZHN0ZWRlbGlqayBHZXdlc3QnKSkNCmBgYA0KDQpgYGB7cn0NCiMgcm91Z2ggcGxvdCwgdG8gdmVyaWZ5IHRoYXQgc3BhdGlhbCBkYXRhIGlzIGFzIGV4cGVjdGVkDQpwbG90KGFycm9uZHMsIG1heC5wbG90ID0gMSkNCmBgYA0KDQojIyBGZXRjaCBvcGVuIGRhdGEgVkRBQiBvZmZpY2UgbG9jYXRpb25zIA0KDQpgYGB7cn0NCiMgZmV0Y2ggb3BlbiBkYXRhIGluIEpTT04tZm9ybWF0DQp2ZGFiLmthbnRvcmVuID0gYXNfdGliYmxlKGZyb21KU09OKCdodHRwOi8vb3BlbmRhdGEudmRhYi5iZS92ZGFiL2xvY2F0aWVzLmpzb24nKSkNCnZkYWIua2FudG9yZW4gIyBkYXRhc2V0IHdpdGggb2ZmaWNlIGxvY2F0aW9ucywgaW5jbHVkaW5nIGNvb3JkaW5hdGVzDQpgYGANCg0KDQojIyBEZXNjcmliZSBhbmQgY2hhbmdlIFZEQUItZGF0YQ0KDQoNCmBgYHtyfQ0KIyBjb3VudCB0ZSBkaWZmZXJlbnQgdHlwZXMgb2YgVkRBQiBvZmZpY2VzDQp2ZGFiLmthbnRvcmVuICU+JQ0KICBncm91cF9ieSh0eXBlbG9jYXRpZSkgJT4lDQogIHRhbGx5KCkNCmBgYA0KDQoNCmBgYHtyfQ0KdmRhYi5rYW50b3JlbiA9IHZkYWIua2FudG9yZW4gJT4lDQogIG11dGF0ZSgNCiAgICAjIGFkZCBhIHZhcmlhYmxlICdwb3B1cCcgd2l0aCB0aGUgdGhlIEhUTUwtc25pcHBldCBwZXIgb2ZmaWNlLA0KICAgICMgd2hpY2ggIHdpbGwgYmUgc2hvd24gaW4gdGhlIG1hcC1wb3B1cA0KICAgIHBvcHVwID0gc3RyX2dsdWUoIjxoMz57dGl0bGV9PC9oMz48YnIgLz48Yj5UeXBlOiA8L2I+e3R5cGVsb2NhdGllfTxiciAvPjxiPkFkcmVzPC9iPjogIHtzdHJhYXROcn0ge3BsYWF0c308YnIgLz48Yj5UZWxlbG9vbjwvYj46IHt0ZWxlZm9vbm51bW1lcn0iKSwNCiAgICANCiAgICAjIG1ha2Ugc3VyZSB0aGF0IGNvb3JkaW5hdGVzIGFyZSBub3QgYSBjaGFyYWN0ZXIsIGJ1dCBudW1lcmljIHZhcmlhYmxlcw0KICAgIGxhdCA9IGFzLm51bWVyaWMobGF0KSwNCiAgICBsb24gPSBhcy5udW1lcmljKGxvbikpDQpgYGANCg0KDQoNCiMgQ29uc3RydWN0IGludGVyYWN0aXZlIG1hcA0KDQojIyBNaW5pbWFsIG1hcCBleGFtcGxlDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KIyBiYXNpYyBpbnRlcmFjdGl2ZSBtYXAgd2l0aCBhIG9uZWxpbmVyDQojICh3aWR0aC1hcmd1bWVudCBub3QgbmVlZGVkLCBvbmx5IGZvciBvbmxpbmUtb3V0cHV0KQ0KbGVhZmxldCh2ZGFiLmthbnRvcmVuLCB3aWR0aCA9ICcxMDAlJykgJT4lIGFkZFRpbGVzKCkgJT4lIGFkZE1hcmtlcnMoKQ0KYGBgDQoNCiMjIFN0eWxlZCBtYXAgZXhhbXBsZSB3L3QgcG9wdXBzDQoNClN0eWxpbmc6DQoNCiogUG9wdXBzIHdpdGggVkRBQi1vZmZpY2UgaW5mb3JtYXRpb24uDQoqIEN1c3RvbSB0aWxlIGJhY2tncm91bmQNCiogQ3VzdG9tIGNvbG9yICYgc3ltYm9sIG1hcmtlcnMsIGJhc2VkIG9uIGRpc2NyZXRlIHZhbHVlcyAob2ZmaWNlLXR5cGUpDQoqIE92ZXJsYXkgb2YgKmFycm9uZGlzc2VtZW50Ki1wb2x5Z29uczogbm93IGdyZXkvd2hpdGUtc3R5bGVkLCBjb3VsZCBiZSBjb2xvcmVkIGJhc2VkIG9uIGUuZy4gdW5lbXBsb3ltZW50LXJhdGUuIA0KDQpgYGB7cn0NCiMgY3JlYXRlIHR3byBpY29ucyB3aXRoIGRpZmZlcmVudCBjb2xvciAmIHN5bWJvbCBmb3IgdGhlIHR3byB0eXBlcyBvZiBWREFCLW9mZmljZXMNCiMgZm9yIGRpZmZlcmVudCBzeW1ib2xzLCBjZi4gaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0L21hcmtlcnMuaHRtbCANCmthbnRvb3JfaWNvbnMgPC0gYXdlc29tZUljb25MaXN0KA0KICAna2FudG9vciBtZXQgb250aGFhbCcgPSBtYWtlQXdlc29tZUljb24oDQogICAgaWNvbiA9ICJ1c2VyIiwgDQogICAgbGlicmFyeSA9ICJmYSIsIA0KICAgIG1hcmtlckNvbG9yID0gImJsdWUiKSwNCiAgJ29wbGVpZGluZ3NjZW50cnVtJyA9IG1ha2VBd2Vzb21lSWNvbigNCiAgICBpY29uID0gImdyYWR1YXRpb24tY2FwIiwgDQogICAgbGlicmFyeSA9ICJmYSIsIA0KICAgIG1hcmtlckNvbG9yID0gInJlZCIpKQ0KYGBgDQoNCmBgYHtyfQ0KbS5rYW50b3JlbiA9IGxlYWZsZXQoYXJyb25kcywgd2lkdGggPSAnMTAwJScpICU+JSANCiAgIyBhZGQgbWluaW1hbGlzdGljLXN0eWxlIG1hcCBibGFja2dyb3VuZCB0aWxlcw0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUgDQogIA0KICAjIGFkZCBncmV5IGFycm9uZGlzc2VtZW50IHBvbHlnb25zIHcvdCB3aGl0ZSBib3JkZXINCiAgYWRkUG9seWdvbnMoZmlsbENvbG9yID0gICdncmV5MjAnLCBjb2xvciA9ICd3aGl0ZScpICU+JSANCiAgDQogICMgYWRkIGN1c3RvbSBzdHlsZWQgbWFya2VycyB3L3QgcG9wdXBzDQogIGFkZEF3ZXNvbWVNYXJrZXJzKA0KICAgIGRhdGEgPSB2ZGFiLmthbnRvcmVuLA0KICAgIGxuZyA9IH5sb24sIGxhdCA9IH5sYXQsDQogICAgaWNvbiA9IH5rYW50b29yX2ljb25zW3R5cGVsb2NhdGllXSwNCiAgICBwb3B1cCA9IH5wb3B1cCkNCmBgYA0KDQpgYGB7cn0NCm0ua2FudG9yZW4NCmBgYA0KDQojIFNhdmUgc3RhbmQtYWxvbmUgbWFwDQoNCmBgYHtyfQ0Kc2F2ZVdpZGdldChtLmthbnRvcmVuLCAndmRhYl9hcGlfcG9wdXBfbWFwLmh0bWwnKQ0KYGBgDQoNCg==