Making Maps in R With GPT4 Prompt Engineering
As a small experiment in map prompt engineering, and to fulfill my scheduled #30DayMapChallenge entry for GDSL, I have been playing with GPT4. The following article conrains code that was all generated by GTP4 without editing which is pretty astonishing. All of the prompts are highlighted in italics.
Write R code that will generate a map of North America from open data or integrated in R packages. It should map something interesting, and utilise points, lines and polygons.
To find out what GPT4 thinks is interesting, run the following code:
# Load the libraries
library(ggplot2)
library(maps)
library(rnaturalearth)
# Get map data for North America
na_map <- map_data("world", region = c("USA", "Canada", "Mexico"))
# Get rivers data using rnaturalearth
rivers <- ne_download(scale = 10, type = 'rivers_lake_centerlines', category = 'physical', returnclass = 'sf')
# Subset rivers for North America - We need to filter these based on the bounding box of North America
bbox <- st_bbox(c(xmin = -170, xmax = -50, ymin = 5, ymax = 85), crs = st_crs("+proj=longlat +datum=WGS84"))
na_rivers <- rivers[st_intersects(rivers, st_as_sfc(bbox), sparse = FALSE), ]
# Some major cities in North America
cities <- data.frame(
name = c("Washington", "Ottawa", "Mexico City"),
lat = c(38.9072, 45.4215, 19.4326),
long = c(-77.0369, -75.6972, -99.1332)
)
# Plot
ggplot() +
geom_polygon(data = na_map, aes(x = long, y = lat, group = group), fill = "lightgray", color = "black") +
geom_sf(data = na_rivers, color = "blue") +
geom_point(data = cities, aes(x = long, y = lat), color = "red", size = 2) +
geom_text(data = cities, aes(x = long, y = lat, label = name), vjust = "bottom", hjust = "right") +
theme_minimal() +
labs(title = "Map of North America", subtitle = "Featuring countries, major rivers, and capital cities")
This obviously doesn’t look that nice, yet. So… additional content was provided:
This was a good start. Now make sure that you only plot continental north america. Also make sure that the rivers are clipped to this area only.
# Load the required libraries
library(ggplot2)
library(maps)
library(rnaturalearth)
library(sf)
# Fetch country data for North America
na_countries <- ne_countries(continent = "North America", returnclass = "sf")
na_countries <- na_countries[!na_countries$name %in% c("Hawaii", "Puerto Rico"), ]
# Define a bounding box for continental North America
bbox <- st_as_sfc(st_bbox(c(xmin = -130, xmax = -60, ymin = 15, ymax = 80), crs = st_crs(4326)))
# Clip the countries by the bounding box
na_countries <- st_intersection(na_countries, bbox)
# Get rivers data using rnaturalearth
rivers <- ne_download(scale = 10, type = 'rivers_lake_centerlines', category = 'physical', returnclass = 'sf')
# Clip rivers to the bounding box
na_rivers <- st_intersection(rivers, bbox)
# Some major cities in continental North America
cities <- data.frame(
name = c("Washington", "Ottawa", "Mexico City"),
lat = c(38.9072, 45.4215, 19.4326),
long = c(-77.0369, -75.6972, -99.1332)
)
# Plot
ggplot(data = na_countries) +
geom_sf(fill = "lightgray", color = "black") + # Countries
geom_sf(data = na_rivers, color = "blue") + # Rivers
geom_point(data = cities, aes(x = long, y = lat), color = "red", size = 2) + # Cities
geom_text(data = cities, aes(x = long, y = lat, label = name), vjust = "bottom", hjust = "right") +
theme_minimal() +
labs(title = "Map of Continental North America", subtitle = "Featuring countries, major rivers, and capital cities")
This was closer, but needed some more tweaks. So:
This was closer to what I want, however the bounding box was a bit too narrow for the continental boundaries of North America. Also make sure you are clipping the rivers to the extent of the polygon.
# Load the required libraries
library(ggplot2)
library(maps)
library(rnaturalearth)
library(sf)
# Fetch country data for North America, excluding non-continental areas
na_countries <- ne_countries(continent = "North America", returnclass = "sf")
# Define a list of regions we want to exclude (islands, etc.)
exclude_regions <- c("Hawaii", "Puerto Rico", "Greenland", "Bermuda", "Bahamas")
# Filter out the excluded regions
na_countries <- na_countries[!na_countries$name %in% exclude_regions, ]
# Get rivers data using rnaturalearth
rivers <- ne_download(scale = 10, type = 'rivers_lake_centerlines', category = 'physical', returnclass = 'sf')
# Calculate the intersection of the rivers with the countries polygon
# This clips the rivers to the actual country boundaries
na_rivers <- st_intersection(rivers, na_countries)
# Some major cities in continental North America
cities <- data.frame(
name = c("Washington", "Ottawa", "Mexico City"),
lat = c(38.9072, 45.4215, 19.4326),
long = c(-77.0369, -75.6972, -99.1332)
)
# Plot
ggplot(data = na_countries) +
geom_sf(fill = "lightgray", color = "black") + # Countries
geom_sf(data = na_rivers, color = "blue") + # Rivers
geom_point(data = cities, aes(x = long, y = lat), color = "red", size = 2) + # Cities
geom_text(data = cities, aes(x = long, y = lat, label = name), vjust = "bottom", hjust = "right") +
theme_minimal() +
labs(title = "Map of Continental North America", subtitle = "Featuring countries, major rivers, and capital cities")
Almost produced a reasonable map, however, some extra changes were needed.
I only want to display continental north america, so please keep only the three largest polygons. I also want the places that you label to have white halo text so that they are more visible.
# Load the required libraries
library(ggplot2)
library(maps)
library(rnaturalearth)
library(sf)
library(ggtext) # for advanced text formatting
# Fetch country data for North America, excluding non-continental areas
na_countries <- ne_countries(continent = "North America", returnclass = "sf")
# Filter out non-continental regions
exclude_regions <- c("Hawaii", "Puerto Rico", "Greenland", "Bermuda", "Bahamas")
na_countries <- na_countries[!na_countries$name %in% exclude_regions, ]
# Order by the area, and keep only the three largest polygons (Canada, USA, Mexico)
na_countries <- na_countries[order(st_area(na_countries), decreasing = TRUE), ][1:3, ]
# Get rivers data using rnaturalearth
rivers <- ne_download(scale = 10, type = 'rivers_lake_centerlines', category = 'physical', returnclass = 'sf')
# Calculate the intersection of the rivers with the countries polygon
na_rivers <- st_intersection(rivers, na_countries)
# Some major cities in continental North America
cities <- data.frame(
name = c("Washington", "Ottawa", "Mexico City"),
lat = c(38.9072, 45.4215, 19.4326),
long = c(-77.0369, -75.6972, -99.1332)
)
# Plot
ggplot(data = na_countries) +
geom_sf(fill = "lightgray", color = "black") + # Countries
geom_sf(data = na_rivers, color = "blue") + # Rivers
geom_point(data = cities, aes(x = long, y = lat), color = "red", size = 2) + # Cities
geom_label(data = cities, aes(x = long, y = lat, label = name),
color = "black", fill = "#FFFFFFC0", size = 3, fontface = "bold",
label.padding = unit(0.2, "lines"),
label.size = NA, # Remove border
family = "sans") +
theme_minimal() +
theme(legend.position = "none") +
labs(title = "Map of Continental North America", subtitle = "Featuring countries, major rivers, and capital cities")
# Print the plot
ggsave("Continental_North_America_Map.png", width = 10, height = 8)
This was very close, but there is a final tweak.
This is very close, but can you make the labels not obscure the points. Maybe have a collecting line to the point from the label.
# Load the required libraries
library(ggplot2)
library(maps)
library(rnaturalearth)
library(sf)
library(ggrepel) # for geom_label_repel
# Fetch country data for North America, excluding non-continental areas
na_countries <- ne_countries(continent = "North America", returnclass = "sf")
# Filter out non-continental regions
exclude_regions <- c("Hawaii", "Puerto Rico", "Greenland", "Bermuda", "Bahamas")
na_countries <- na_countries[!na_countries$name %in% exclude_regions, ]
# Order by the area, and keep only the three largest polygons (Canada, USA, Mexico)
na_countries <- na_countries[order(st_area(na_countries), decreasing = TRUE), ][1:3, ]
# Get rivers data using rnaturalearth
rivers <- ne_download(scale = 10, type = 'rivers_lake_centerlines', category = 'physical', returnclass = 'sf')
# Calculate the intersection of the rivers with the countries polygon
na_rivers <- st_intersection(rivers, na_countries)
# Some major cities in continental North America
cities <- data.frame(
name = c("Washington", "Ottawa", "Mexico City"),
lat = c(38.9072, 45.4215, 19.4326),
long = c(-77.0369, -75.6972, -99.1332)
)
# Plot
ggplot(data = na_countries) +
geom_sf(fill = "lightgray", color = "black") + # Countries
geom_sf(data = na_rivers, color = "blue") + # Rivers
geom_point(data = cities, aes(x = long, y = lat), color = "red", size = 3) + # Cities
geom_label_repel(data = cities, aes(x = long, y = lat, label = name),
box.padding = unit(0.35, "lines"),
point.padding = unit(0.5, "lines"),
segment.color = 'grey50', # Color of the connecting lines
size = 3,
fontface = "bold",
nudge_x = 0.5, nudge_y = 0.5, # You can adjust nudge_x and nudge_y to offset labels
color = "black") +
theme_minimal() +
theme(legend.position = "none") +
labs(title = "Map of Continental North America", subtitle = "Featuring countries, major rivers, and capital cities")
# Print the plot
ggsave("Continental_North_America_Map.png", width = 10, height = 8)
Conclusion
Would I describe this as a perfect map or one that is “interesting” as per the original prompt. Probably not, but definitely something that has produced some reasonable code which is well documented.
Professor of GIScience @ Ghent University (Belgium) - Focusing on GeoAI and GenAI - Director of GeoAI Research Center
1 年Yes indeed, GenAI can certainly play an interesting role in GIScience.