R Hack – Getting started with maps in R using GeoJSON and library sf

A simple workflow to load and plot maps in R

GeoJSON
ggplot
sf
maps
R-Hacks
Author

Lucio Colonna

Published

December 5, 2025

This hack is based on my Cyclistic analysis on Kaggle (see Chapter 8.12.4):
🔗 https://www.kaggle.com/code/lcolon/cyclistic-2023-google-da-capstone-project-r

Working with maps in R is easier than it seems.
If you want to visualize points, regions, or spatial patterns, the combination of GeoJSON files and the sf package gives you a simple and modern workflow.

In this hack, we’ll load a real city map (Chicago) directly from a URL, plot it with geom_sf(), and overlay simple point data.


Step 0 – What is a GeoJSON file?

A GeoJSON file is a JSON text file that stores geographic shapes (points, lines, polygons), together with their attributes (names, boundaries, codes, etc.).

It is one of the most common formats for open geographic data because it is lightweight, human-readable, and web-friendly.

You can often find GeoJSON files on:

  • city open data portals (City of Chicago, NYC Open Data, etc.)
  • national and international platforms (e.g., data.gov)
  • GitHub repositories
  • many public mapping resources

Step 1 – Load the required packages

library(sf)       # for spatial data (simple features)
library(ggplot2)  # for plotting

Step 2 – Read a GeoJSON map with sf::st_read()

In this hack, we use a GeoJSON of Chicago community areas, provided by the City of Chicago Open Data Portal.

⚠️ Before using any online map or dataset, always remember to check the terms of use of the source.

You can read a GeoJSON directly from a URL, without downloading anything manually:

map_of_chicago <- sf::st_read(
  "https://data.cityofchicago.org/resource/y6yq-dbs2.geojson",
  quiet = TRUE
  )

head(map_of_chicago)
Simple feature collection with 6 features and 4 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -87.74141 ymin: 41.80189 xmax: -87.60641 ymax: 41.93272
Geodetic CRS:  WGS 84
           pri_neigh          sec_neigh    shape_area    shape_len
1    Grand Boulevard        BRONZEVILLE 48492503.1554 28196.837157
2       Printers Row       PRINTERS ROW 2162137.97139  6864.247156
3      United Center      UNITED CENTER 32520512.7053 23101.363745
4 Sheffield & DePaul SHEFFIELD & DEPAUL 10482592.2987 13227.049745
5      Humboldt Park      HUMBOLDT PARK 125010425.593 46126.751351
6      Garfield Park      GARFIELD PARK 89976069.5947  44460.91922
                        geometry
1 MULTIPOLYGON (((-87.60671 4...
2 MULTIPOLYGON (((-87.62761 4...
3 MULTIPOLYGON (((-87.66707 4...
4 MULTIPOLYGON (((-87.65833 4...
5 MULTIPOLYGON (((-87.7406 41...
6 MULTIPOLYGON (((-87.6954 41...

What you get:

  • map_of_chicago is an sf object -essentially a data frame with a special geometry column where each row is a polygon that represents one community area

Step 3 – Plot a basic map with geom_sf()

Once the GeoJSON file has been loaded into an sf object, you can visualize it with geom_sf().

geom_sf() is the ggplot2 layer designed specifically for spatial data: it knows how to read the geometry column of an sf object and automatically draw points, lines, or polygons depending on what the data contains. In our case, it will draw the polygons of Chicago’s community areas.

ggplot() +
  geom_sf(data = map_of_chicago, 
          fill = "#f1f0f0", color = "grey50") +
  labs(title = "Chicago community areas",
       subtitle = "GeoJSON map loaded with sf::st_read()") +
  theme_void()


Step 4 – Add your own data on top of the map

If you have a dataset with latitude/longitude, you can overlay it on the map.

For example, let’s create a small dummy dataset of bike sharing station locations:

stations <- data.frame(
  start_lng = c(
    -87.6278, -87.6560, -87.6705, -87.6045, -87.5950,
    -87.6850, -87.6515, -87.7080, -87.6405, -87.5600),
  start_lat = c(
    41.8925, 41.9000, 41.9120, 41.8560, 41.7920,
    41.9220, 41.9250, 41.8810, 41.9410, 41.7600)
  )

stations
   start_lng start_lat
1   -87.6278   41.8925
2   -87.6560   41.9000
3   -87.6705   41.9120
4   -87.6045   41.8560
5   -87.5950   41.7920
6   -87.6850   41.9220
7   -87.6515   41.9250
8   -87.7080   41.8810
9   -87.6405   41.9410
10  -87.5600   41.7600

Now we can plot the base map plus the stations:

ggplot() +
  geom_sf(data = map_of_chicago, 
          fill = "#f1f0f0", color = "grey70") +
  geom_point(data = stations,
             aes(x = start_lng, y = start_lat),
             color = "red",
             size = 3) +
  labs(title = "Bike stations on top of a GeoJSON map of Chicago",
       subtitle = "Base map: Chicago community areas (GeoJSON + sf)") +
  theme_void()


To Recap

  • GeoJSON = JSON file with geometry + attributes

  • Load it directly from a URL with sf::st_read()

  • Plot it with geom_sf()

  • Add your own points using regular ggplot layers

  • This workflow is simple, beginner-friendly, and requires no GIS tools


Thanks for reading!

Tip

If you want to stay up to date with the latest events from the Rome R Users Group, click here:

👉 https://www.meetup.com/rome-r-users-group/

And if you are curious, the full Kaggle notebook used for this tip is available here:

🔗 https://www.kaggle.com/code/lcolon/cyclistic-2023-google-da-capstone-project-r

Back to top