Mapping India’s Cuisines with R: {sf}, {ggpattern}, {magick} and more
Diverse culinary heritage of India with a vibrant map created in R, blending spatial data from {sf}, creative patterns from {ggpattern}, and precise labeling from {ggrepel}. Images of iconic state cuisines were curated using {httr} and {magick}, seamlessly integrated into the visual using the power of {ggplot2}.
Maps
Geocomputation
{ggpattern}
{magick}
{ggrepel}
India
Images
Author
Aditya Dahiya
Published
January 26, 2025
Step 1: Get India’s Map Data, and make a base tibble
Code
# Data Import and Wrangling Toolslibrary(tidyverse) # All things tidylibrary(sf) # Handling simple features in Rlibrary(here) # Folder management# Final plot toolslibrary(scales) # Nice Scales for ggplot2library(fontawesome) # Icons display in ggplot2library(ggtext) # Markdown text in ggplot2library(showtext) # Display fonts in ggplot2library(colorspace) # Lighten and Darken colourslibrary(ggpattern) # Patterns / Images in geomslibrary(magick) # Handling imageslibrary(httr) # Downloading images from Googleindia_map <-read_sf( here::here("data", "india_map", "India_State_Boundary.shp" )) |> janitor::clean_names() |>st_simplify(dTolerance =1000) |>st_transform("EPSG:4326") |>mutate(id =row_number(),area =st_area(geometry) ) |>relocate(id)india_mapindia_map$state_name
Step 2: Get images of states’ cuisines from Google’s Programme Search Engine through an API
Code
# Get a custom google search engine and API key# Tutorial: https://developers.google.com/custom-search/v1/overview# Tutorial 2: https://programmablesearchengine.google.com/# From:https://developers.google.com/custom-search/v1/overview# google_api_key <- "LOAD YOUR GOOGLE API KEY HERE"# From: https://programmablesearchengine.google.com/controlpanel/all# my_cx <- "GET YOUR CUSTOM SEARCH ENGINE ID HERE"# Define function to download and save movie posterdownload_food_images <-function(i) { api_key <- google_api_key cx <- my_cx# Build the API request URL url <-paste0("https://www.googleapis.com/customsearch/v1?q=", URLencode(paste0( india_map$state_name[i], " state cuisine photo HD" )), "&cx=", cx, "&searchType=image&key=", api_key,"&imgSize=large", # Prioritize larger images"&imgType=photo" )# Make the request response <-GET(url) result <-content(response, "parsed")# Get the URL of the first image result image_url <- result$items[[1]]$link im <- magick::image_read(image_url) |>image_resize("x1000")# set background as whiteimage_write(image = im,path = here::here("geocomputation", "images",paste0("temp_food_india_", i,".png")),format ="png" )}for (i in18:nrow(india_map)) {download_food_images(i)}# Custom run the function for id 1, 2, 10, 20custom_ids <-c(1, 2, 10, 20)
Step 2.1: The same Code improved with ChatGPT
Code
# Add a new column to store food names in the tibbleindia_map <- india_map |>mutate(food_name =NA_character_)# Function to extract food item name from the search query resultextract_food_name <-function(items) {if (length(items) >0) {# Attempt to extract meaningful food names from the title or snippet food_name <- items[[1]]$titlereturn(food_name) } else {return(NA) # Return NA if no items are found }}# Improved function to download and save food imagesdownload_food_images <-function(i) { api_key <- google_api_key cx <- my_cx# Build the API request URL with additional filters url <-paste0("https://www.googleapis.com/customsearch/v1?q=",URLencode(paste0(india_map$state_name[i], " traditional cuisine food photo")),"&cx=", cx,"&searchType=image","&key=", api_key,"&imgSize=large", # Restrict to medium-sized images"&imgType=photo","&num=1"# Fetch only one result )# Make the request response <-GET(url)if (response$status_code !=200) {warning("Failed to fetch data for state: ", india_map$state_name[i])return(NULL) }# Parse the response result <-content(response, "parsed")# Extract the image URLif (!is.null(result$items)) { image_url <- result$items[[1]]$link food_name <-extract_food_name(result$items) } else {warning("No results found for state: ", india_map$state_name[i])return(NULL) }# Validate and process the imagetryCatch({ im <- magick::image_read(image_url) |>image_resize("x1000") # Resize image# Save the imageimage_write(image = im,path =here("geocomputation", "images", paste0("temp_food_india_", i, ".png")),format ="png" )# Add the food name to the tibble india_map$food_name[i] <- food_name }, error =function(e) {warning("Failed to process image for state: ", india_map$state_name[i]) })}# Iterate through each state and download imagesfor (i in1:nrow(india_map)) {download_food_images(i)}
Step 3: Set up Visualization Parameters
Code
# Font for titlesfont_add_google("Rye",family ="title_font") # Font for the captionfont_add_google("Voltaire",family ="caption_font") font_add_google("Saira Extra Condensed",family ="caption_font2") showtext_auto()# A base Colourbg_col <-"grey10"seecolor::print_color(bg_col)# Colour for highlighted texttext_hil <-"grey90"seecolor::print_color(text_hil)# Colour for the texttext_col <-"white"seecolor::print_color(text_col)# Define Base Text Sizebts <-90# Caption stuff for the plotsysfonts::font_add(family ="Font Awesome 6 Brands",regular = here::here("docs", "Font Awesome 6 Brands-Regular-400.otf"))github <-""github_username <-"aditya-dahiya"xtwitter <-""xtwitter_username <-"@adityadahiyaias"social_caption_1 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{github};</span> <span style='color: {text_hil}'>{github_username} </span>")social_caption_2 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{xtwitter};</span> <span style='color: {text_hil}'>{xtwitter_username}</span>")plot_caption <-paste0("**Data:** Google & Census of India", " | **Code:** ", social_caption_1, " | **Graphics:** ", social_caption_2 )rm(github, github_username, xtwitter, xtwitter_username, social_caption_1, social_caption_2)# Add text to plot-------------------------------------------------plot_title <-"Flavors of India:\nA Culinary Map"
Step 4: Use ggpattern to make final map Customizing options here.
Step 5: Save thumbnail and clean up temporary files
Code
# Saving a thumbnaillibrary(magick)# Reducing Image Size - its 15 Mb plusimage_read( here::here("geocomputation", "images","ggpattern_with_sf_india_cuisines.png" ) ) |>image_resize(geometry ="x400")# Clean Up: Do no harm and leave the world an untouched place!# Remove temporary image filesunlink(paste0("geocomputation/images/temp_food_india_", 1:nrow(india_map), ".png"))