• Load required libraries
  • Load/fetch requried data
    • Load arrondissement borders
    • Fetch open data VDAB office locations
    • Describe and change VDAB-data
  • Construct interactive map
    • Minimal map example
    • Styled map example w/t popups
  • Save stand-alone map

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
ABCDEFGHIJ0123456789
 
 
nid
<chr>
title
<chr>
115929Opleidingscentrum Aalst Tragel
215934Werkwinkel Aalst
315941Werkwinkel Aarschot
415946Opleidingscentrum Anderlecht
515950Werkwinkel Anderlecht
615956Opleidingscentrum talentenwerf Antwerpen
715960Werkwinkel Antwerpen Copernicuslaan
815961Werkwinkel Antwerpen Kipdorp
915963Opleidingscentrum Antwerpen Copernicuslaan
1015964Servicepunt hout en bouw, transport en logistiek

Describe and change VDAB-data

# count te different types of VDAB offices
vdab.kantoren %>%
  group_by(typelocatie) %>%
  tally()
ABCDEFGHIJ0123456789
typelocatie
<chr>
n
<int>
kantoor met onthaal85
opleidingscentrum52
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()
Leaflet | © OpenStreetMap contributors, CC-BY-SA

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==