Map
R
AI
Leaflet
Author

George Girton

Published

May 25, 2024

Make a thematic web map with Leaflet and R

Show the R libraries used
library(leaflet)
library(sf)
library(tidyverse)
library(RColorBrewer)

Read the data from a GeoPackage (.gpkg) file that has the two layers.

TK, before starting here, created a national point layer of EV charger locations, and a level 8 hexagon polygon layer. The hexagon polygons layer was loaded into a Scan/US Desktop subscription, and used as a template to aggregate a data item “Vehicles available” for an area around San Francisco.

The two layers were exported as Shapefiles, which were then combined into the GeoPackage .gpkg file SF_TWO.gpkg GeoPackage website, to supposedly make life simpler.

The code section below reads that file into two map layers used by Leaflet in the next step

Show the R code to read the 2 layers
# Read the layers from the two-layer GeoPackage (.gpkg) file
Chargers <- st_read(paste0(here::here(),"/posts/ChargerMapSF/mapdata/SF_Two.gpkg"), layer = "May2024Chargers",quiet=TRUE)
Hex8 <- st_read(paste0(here::here(),"/posts/ChargerMapSF/mapdata/SF_Two.gpkg"), layer = "Hex8_SFpop",quiet=TRUE)

# Avoid the 'inconsistent datum' warning by transforming coordinates to EPSG:4326
Chargers <- st_transform(st_as_sf(Chargers),4326)
Hex8 <- st_transform(st_as_sf(Hex8),4326)

## Get a matching data file to the Charger locations data file, so we can put the station name or maybe some other data 
ChargersData <- read.csv(paste0(here::here(),"/posts/ChargerMapSF/mapdata/SF_Chargers.csv"))
ChargersData$KEY= str_sub(ChargersData$KEY, start=2L)
ChargersLayer <- merge(Chargers, ChargersData, by.x = "KEY", by.y = "KEY")

# Read tiny demographic data file of just a few values, exported from Scan/US 
HexData <- read.csv(paste0(here::here(),"/posts/ChargerMapSF/mapdata/SFHex8Data.csv"))
# Set the KEY field to match exactly by removing the octothorpe character, and no I am not making that up, from the key name
HexData$KEY= str_sub(HexData$KEY, start=2L)

# Merge the SF geography file 'Hex8'  with the data file 'HexData' that matches it, and make a new geo file 'HexLayer' to use directly in leaflet.
HexLayer <- merge(Hex8, HexData, by.x="KEY", by.y = "KEY")

Location map

First, don’t make a thematic map. Just make a point map showing the charger locations (snail emoji: 🐌)

Show the Leaflet make-a-point map code
#Add the point location layer to the  map using the addCircles() function:
leaflet(HexLayer) |> 
  addProviderTiles(providers$CartoDB.Positron) |> 
 addCircles(data = ChargersLayer,
             weight = 4,
             color = "red",
             fill = FALSE,
            popup = ~paste("Location name: ", ChargersLayer$StationName))

Making a palette for choropleth fill

I started by trying to craft a palette manually, using the ‘cut’ function in base R. It turns out, due to one of the helper functions in the RColorBrewer package, did not need to do this at all:

Show the unneccesary code we didn’t have to use
# This was some "make your own palette" code that was given to me by the Mistral beta GPT, when I asked it why the palette code did not work.

# num_bins = 5
# HexLayer$range <- cut(HexLayer$TotalVehicles, breaks = num_bins, labels = FALSE)
# HexLayer$VehColor <- mypal[HexLayer$range]

# Create value-to-color mapping function
# There is a convenient helper function in RColorbrewer package do it for you!

Data map Time!

First set up the palette by making a function that will translate the range of values in TotalVehicles into a particular palette color. You give it the data ‘domain’ so it can perform its own ‘cut’ and generate a palette for blue and purple, which is quaintly named ‘BuPu’. Then, with the ‘bluepurpal()’ function, call the function ON that column during “AddPolygons”.

Show the palette function [ bluepurpal() ] code, and how it is used
# using colorBrewer's 'colorNumeric()', Make a function that will map the values in the TotalVehicles column to a palette
bluepurpal <- colorNumeric(palette="BuPu", domain=HexLayer$TotalVehicles)

#Create a choropleth map of the area layer using addPolygons inside the leaflet() function:

leaflet(HexLayer) |> 
  addTiles() |> 
  addPolygons(
              fillColor = ~bluepurpal(TotalVehicles),  # call the function to map TotalVehicles numbers to palette colors
              fillOpacity = 0.7,
              weight = 1,
              popup = ~paste("Vehicles: ", HexLayer$TotalVehicles)) |> 
  addCircles(data = ChargersLayer,
             weight = 4,
             color = "red",
             fill = FALSE,
             #fillOpacity = 0.8,
             popup = ~paste("Location name: ", ChargersLayer$StationName)
  ) |> 
  addLegend(pal = bluepurpal, # send the palette function to the legend too!
            values = ~TotalVehicles, labFormat = labelFormat(
    prefix = "[", suffix = "]", between = ", "
  ),position = "bottomright" )