I have lots of custom lists on Google Maps to journal places I’ve visited. I wanted to create an offline backup of these lists, just in case my Google account ever gets lost. Google Takeout allows you to export these lists, but inconveniently these exports don’t include the lat-lon coordinates of the saved places, just a URL and the name of the place.
There are a few websites like Export Google Maps and Takeout Tools that can process Google Maps URLs, but they both have their drawbacks. Export Google Maps has an extremely slow processing time based on a queue system; I was quoted 18 days to process one file. Takeout Tools is a paid service, and this isn’t something I care about enough to pay for.
In the end I wrote an R script using the googleway
package hooked up to the Google Geocoding API
. The geocode_url() function in the script first tests whether a URL can be reached, extracts the place name from the Google Maps URL, uses googleway::google_geocode() to query the place name, and finally extracts additional information from the API query including the lat-lon coordinates, place name, address, and Plus Code. I’m using pass-cli
to manage my API key, but this part of the script could be replaced to have the API key hard-coded as a variable.
This is the form of the URLs returned by Google Takeout:
# Extract lat-lon coordinates from Google Maps URL
# John L. Godlee (johngodlee@gmail.com)
# Last updated: 2026-04-26
# Packages
library(httr2)
library(googleway)
# Call 'pass' to get API key
api_key <- trimws(
system2("pass", args = c("show", "google_geocoding_api_key"),
stdout = TRUE))
# Check if key retrieved successfully
if (length(api_key) == 0 || api_key == "") {
stop("Failed to retrieve API key from pass.")
}
# Set API key
set_key(api_key)
# Define function
geocode_url <- function(url) {
# Expand URL
resp <- tryCatch({
req <- request(url)
req_perform(req)
}, error = function(e) {
message("Failed to resolve URL (Dead link/Timeout): ", url)
return(NULL)
})
# Standardise failure output
fail_out <- data.frame(
url = url, name = NA, address = NA, plus_code = NA, lat = NA, lng = NA)
if (is.null(resp)) {
return(fail_out)
}
# Extract URL from response
full_url <- resp_url(resp)
# Extract place name from URL
regex_pattern <- "/place/([^/]+)/"
match_info <- regexec(regex_pattern, full_url)
place_match <- regmatches(full_url, match_info)
# If place name matched
if (length(place_match) > 0 && length(place_match[[1]]) >= 2) {
raw_place_name <- place_match[[1]][2]
place_name <- gsub("\\+", " ", raw_place_name)
place_name <- URLdecode(place_name)
# Query Google Geocoding API
api_result <- google_geocode(address = place_name)
# Extract coordinates and other information
if (api_result$status == "OK") {
coords <- access_result(api_result, "coordinates")
address <- access_result(api_result, "address")
plus_code <- NA
if ("plus_code" %in% names(api_result$results)) {
plus_code <- api_result$results$plus_code[1, "global_code"]
}
out <- data.frame(
url = url,
name = place_name,
address = address,
plus_code = plus_code,
coords)[1, ]
return(out)
} else {
message("API returned status '", api_result$status, "' for: ", place_name)
return(fail_out)
}
} else {
message("Could not extract place name from resolved URL: ", full_url)
return(fail_out)
}
}
# Import URLs
google_takeout <- read.csv("./Takeout/Saved/Edinburgh food.csv")
urls <- google_takeout$URL
urls <- urls[urls != "" & !is.na(urls)] # Also filter out NAs just in case
# Run function on URLs
result_list <- lapply(urls, geocode_url)
result_df <- do.call(rbind, result_list)
result_df
| url | name | address | plus_code | lat | lng |
|---|---|---|---|---|---|
| https://www.google.com/maps/place/The+Phillips+Collection/data=!4m2!3m1!1s0x89b7b7c8c18d1011:0x5ebf642c05a560f7 | The Phillips Collection | 1600 21st St NW, Washington, DC 20009, USA | 87C4WX63+P6 | 38.91 | -77.05 |
| https://www.google.com/maps/place/National+Building+Museum/data=!4m2!3m1!1s0x89b7b78ee8345b73:0x48233bd191725f45 | National Building Museum | 401 F St NW, Washington, DC 20001, USA | 87C4VXXJ+4X | 38.90 | -77.02 |
| https://www.google.com/maps/place/The+Yards/data=!4m2!3m1!1s0x89b7b9d7207fab4f:0x4ea43e59870c19cc | The Yards | 254 Alta Mar Dr, Ponte Vedra Beach, FL 32082, USA | 862W6J75+XW | 30.21 | -81.39 |
| https://www.google.com/maps/place/Rock+Creek+Park/data=!4m2!3m1!1s0x89b7c8f06a225943:0x601267b7d15c622d | Rock Creek Park | Rock Creek Park, Washington, DC, USA | 87C4XX83+XW | 38.97 | -77.05 |