This tutorial demonstrates how to use the Bing Maps API to calculate drive time (in minutes) and driving distance (in miles) between two locations. In order to use the Bing Maps API, you must first request create a Bing Maps Developer account and then generate an API Key.
This step uses the pacman package to install and load all of the packages for this tutorial.
# Use pacman package to load packages used for exercise
library(pacman)
p_load(dplyr, kableExtra, RCurl, rjson, FNN, lubridate, leaflet)
The Bing Maps API can be used to calculate drive time and distance between two locations. For this tutorial, 10 randomly selected ZIP codes in Allegheny County, PA will be used as starting locations and the nearest hospital from a large regional healthcare provider will be used as the destination location.The list of ZIP codes is from the 2021 Q4 ZIP code to county crosswalk provided by the US Department of Housing and Urban Development.
# Load List of Allegheny County, PA ZIP Codes from HUD
allegheny_zips <- read.csv("./data/ZIP_COUNTY_122021.csv",
colClasses = (zip = "character")) %>%
filter(county == 42003) %>%
group_by(zip) %>%
top_n(1, res_ratio) %>%
mutate(City = gsub("(?<=\\b)([a-z])", "\\U\\1",
tolower(usps_zip_pref_city), perl=TRUE)) %>%
rename(ZIP = zip,
County = county,
State = usps_zip_pref_state) %>%
select(ZIP, City, County, State) %>%
arrange(ZIP)
# Select 10 at random
random_zips <- as.data.frame(allegheny_zips) %>%
sample_n(10)
# Display data
random_zips %>%
kbl(caption = "10 Randomly Selected ZIP Codes in Allegheny County",
align = "r", row.names = FALSE) %>%
kable_minimal("hover")
ZIP | City | County | State |
---|---|---|---|
15236 | Pittsburgh | 42003 | PA |
15005 | Baden | 42003 | PA |
15065 | Natrona Heights | 42003 | PA |
15143 | Sewickley | 42003 | PA |
15229 | Pittsburgh | 42003 | PA |
15020 | Bunola | 42003 | PA |
15126 | Imperial | 42003 | PA |
15226 | Pittsburgh | 42003 | PA |
15134 | Mckeesport | 42003 | PA |
16059 | Valencia | 42003 | PA |
The code below finds coordinates for a street address in each of the ZIP codes loaded in step 1 above, using just the 5-digit ZIP code and the user’s Bing Maps API key. If no match is found, then “NA” will be assigned for the two coordinate fields for that ZIP code.
# Store Bing Maps API key
BingMapsKey <- "[insert your API key here]"
# Loop through each of the randomly selected Allegehny County ZIP codes to find geospatial coordinates for a street address near the center of that ZIP code
for(i in 1:nrow(random_zips)){
url <- URLencode(paste0("http://dev.virtualearth.net/REST/v1/Locations?q=",
random_zips$ZIP[i],"&maxResults=1&key=",BingMapsKey))
json <- fromJSON(getURL(url), simplify = FALSE)
if (json$resourceSets[[1]]$estimatedTotal > 0) {
lat <- json$resourceSets[[1]]$resources[[1]]$point$coordinates[[1]]
lon <- json$resourceSets[[1]]$resources[[1]]$point$coordinates[[2]]
} else {
lat <- lon <- NA
}
random_zips$Latitude[i] <- lat
random_zips$Longitude[i] <- lon
}
# Display ZIP code data with geocoded location
allegheny_zips[1:10,] %>%
kbl(caption = "Allegheny County ZIP Codes with Geocoded Coordinates",
align = "r", row.names = FALSE) %>%
kable_minimal("hover")
ZIP | City | County | State | Latitude | Longitude |
---|---|---|---|---|---|
15001 | Aliquippa | 42003 | PA | 40.60068 | -80.28081 |
15003 | Ambridge | 42003 | PA | 40.59342 | -80.22189 |
15005 | Baden | 42003 | PA | 40.63871 | -80.19928 |
15006 | Bairdford | 42003 | PA | 40.63161 | -79.88007 |
15007 | Bakerstown | 42003 | PA | 40.65100 | -79.93566 |
15012 | Belle Vernon | 42003 | PA | 40.15799 | -79.81738 |
15014 | Brackenridge | 42003 | PA | 40.60984 | -79.74207 |
15015 | Bradfordwoods | 42003 | PA | 40.63476 | -80.08659 |
15017 | Bridgeville | 42003 | PA | 40.34584 | -80.11489 |
15018 | Buena Vista | 42003 | PA | 40.26614 | -79.79324 |
Destinations for each of the ZIP codes will be the nearest UPMC hospital, determined using ‘as the crow flies’.UPMC is a large healthcare provider in the region, headquartered in Pittsburgh. The addresses for the 12 UPMC hospitals in the Southwest PA region were taken from this public page on UPMC’s website.
# Load Southwest PA region UPMC hospital locations
upmc_hospitals <- read.csv("./data/UPMC Locations.csv")
# Display data
upmc_hospitals %>%
kbl(caption = "UPMC Hospitals in Southwest PA",
align = "r", row.names = FALSE) %>%
kable_minimal("hover")
Location | Address |
---|---|
UPMC Children’s Hospital of Pittsburgh | 4401 Penn Ave, Pittsburgh, PA 15224 |
UPMC East | 2775 Mosside Blvd, Monroeville, PA 15146 |
UPMC Magee-Womens Hospital | 300 Halket St, Pittsburgh, PA 15146 |
UPMC McKeesport | 1500 5th Ave, McKeesport, PA 15132 |
UPMC Mercy | 1400 Locust St, Pittsburgh, PA 15219 |
UPMC Montefiore | 3459 Fifth Ave, Pittsburgh, PA 15213 |
UPMC Passavant - Cranberry | 1 St. Francis Way, Craberry Township, PA 16066 |
UPMC Passavant - McCandless | 9100 Babcock Blvd, Pittsburgh, PA 15237 |
UPMC Presbyterian | 200 Lothrop St, Pittsburgh, PA 15213 |
UPMC Shadyside | 5230 Centre Ave, Pittsburgh, PA 15232 |
UPMC St. Margaret | 815 Freeport Rd, Pittsburgh, PA 15215 |
UPMC Western Psychiatric Hospital | 3811 O’Hara St, Pittsburgh, PA 15213 |
The code below takes each of the UPMC Hospital addresses from step 3 above, stored in the field upmc_hospitals$Address, to request the coordinates from the Bing Maps API. If coordinates are found, then they will be added to the corresponding address in the new fields “Latitude” and “Longitude”; otherwise, if no match is found, then “NA” will be assigned for the two coordinate fields of that address.
# Loop through each hospital address and add geospatial coordinates
for(i in 1:nrow(upmc_hospitals)){
url <- URLencode(paste0("http://dev.virtualearth.net/REST/v1/Locations?q=",
upmc_hospitals$Address[i],"&maxResults=1&key=",BingMapsKey))
json <- fromJSON(getURL(url), simplify = FALSE)
if (json$resourceSets[[1]]$estimatedTotal > 0) {
lat <- json$resourceSets[[1]]$resources[[1]]$point$coordinates[[1]]
lon <- json$resourceSets[[1]]$resources[[1]]$point$coordinates[[2]]
} else {
lat <- lon <- NA
}
upmc_hospitals$Latitude[i] <- lat
upmc_hospitals$Longitude[i] <- lon
}
# Display UPMC Hospital data for Southwest PA Region with geocoded location
upmc_hospitals %>%
kbl(caption = "UPMC Hospitals in Southwest PA with Geocoded Coordinates",
align = "r", row.names = FALSE) %>%
kable_minimal("hover")
Location | Address | Latitude | Longitude |
---|---|---|---|
UPMC Children’s Hospital of Pittsburgh | 4401 Penn Ave, Pittsburgh, PA 15224 | 40.46657 | -79.95315 |
UPMC East | 2775 Mosside Blvd, Monroeville, PA 15146 | 40.43666 | -79.76028 |
UPMC Magee-Womens Hospital | 300 Halket St, Pittsburgh, PA 15146 | 40.43681 | -79.96070 |
UPMC McKeesport | 1500 5th Ave, McKeesport, PA 15132 | 40.35162 | -79.84907 |
UPMC Mercy | 1400 Locust St, Pittsburgh, PA 15219 | 40.43623 | -79.98560 |
UPMC Montefiore | 3459 Fifth Ave, Pittsburgh, PA 15213 | 40.44058 | -79.96178 |
UPMC Passavant - Cranberry | 1 St. Francis Way, Craberry Township, PA 16066 | 40.68328 | -80.09830 |
UPMC Passavant - McCandless | 9100 Babcock Blvd, Pittsburgh, PA 15237 | 40.57290 | -80.01395 |
UPMC Presbyterian | 200 Lothrop St, Pittsburgh, PA 15213 | 40.44241 | -79.96029 |
UPMC Shadyside | 5230 Centre Ave, Pittsburgh, PA 15232 | 40.45428 | -79.93960 |
UPMC St. Margaret | 815 Freeport Rd, Pittsburgh, PA 15215 | 40.48925 | -79.89633 |
UPMC Western Psychiatric Hospital | 3811 O’Hara St, Pittsburgh, PA 15213 | 40.44373 | -79.95988 |
Using the geocoded coordinates of a street location within each ZIP code and the geocoded coordinates of each of the Southwest PA UPMC hospitals, the shortest combination of Euclidean distance for each ZIP code are calculated below.
# Find nearest UPMC Hospital to ZIP code point
nearest_hospital <- cbind(random_zips[,],
get.knnx(upmc_hospitals[,c("Latitude","Longitude")],
random_zips[,c("Latitude","Longitude")], k=1)$nn.index,
get.knnx(upmc_hospitals[,c("Latitude","Longitude")],
random_zips[,c("Latitude","Longitude")], k=1)$nn.dist)
# Rename columns
colnames(nearest_hospital)[c(5:6,7:8)] <- c("ZIP_Latitude",
"ZIP_Longitude",
"Hospital_ID",
"Euclidean_Distance")
# Add name of UPMC Hospital, its location, and its coordianates
nearest_hospital$UPMC_Location <- upmc_hospitals[nearest_hospital[,7],1]
nearest_hospital$UPMC_Address <- upmc_hospitals[nearest_hospital[,7],2]
nearest_hospital$UPMC_Latitude <- upmc_hospitals[nearest_hospital[,7],3]
nearest_hospital$UPMC_Longitude <- upmc_hospitals[nearest_hospital[,7],4]
# Display nearest hospital data
nearest_hospital[,c("ZIP", "City", "UPMC_Location", "UPMC_Address")] %>%
kbl(caption = "Nearest UPMC Hospital to Each Randomly Selected ZIP Code",
align = "r", row.names = FALSE) %>%
kable_minimal("hover")
ZIP | City | UPMC_Location | UPMC_Address |
---|---|---|---|
15236 | Pittsburgh | UPMC Mercy | 1400 Locust St, Pittsburgh, PA 15219 |
15005 | Baden | UPMC Passavant - Cranberry | 1 St. Francis Way, Craberry Township, PA 16066 |
15065 | Natrona Heights | UPMC East | 2775 Mosside Blvd, Monroeville, PA 15146 |
15143 | Sewickley | UPMC Passavant - McCandless | 9100 Babcock Blvd, Pittsburgh, PA 15237 |
15229 | Pittsburgh | UPMC Passavant - McCandless | 9100 Babcock Blvd, Pittsburgh, PA 15237 |
15020 | Bunola | UPMC McKeesport | 1500 5th Ave, McKeesport, PA 15132 |
15126 | Imperial | UPMC Passavant - McCandless | 9100 Babcock Blvd, Pittsburgh, PA 15237 |
15226 | Pittsburgh | UPMC Mercy | 1400 Locust St, Pittsburgh, PA 15219 |
15134 | Mckeesport | UPMC McKeesport | 1500 5th Ave, McKeesport, PA 15132 |
16059 | Valencia | UPMC Passavant - McCandless | 9100 Babcock Blvd, Pittsburgh, PA 15237 |
The coordinates for each combination of nearest hospital and starting point within a ZIP code are plugged into the Bing Maps API to return the time and distance it takes to travel by car between those two locations. The code below calculates that drive starting at 9 AM local time on the day the code is run. To specifically pick a time and date for departure, then change the following line of code:
"&dt=",paste0(format(as.Date(Sys.Date(), "%B %d %Y"), "%m/%d/%Y"), " 09:00:00")
To a date and time in the format of:
## [1] "MM/DD/YYYY HH:MM:SS"
## [1] "Example: 02/22/2022 09:00:00"
# Note: uses todays date at 9 AM local time (reasonable traffic) for calculation
for(i in 1:nrow(nearest_hospital)){
nearest_hospital$Drive_Distance[i] <- NA
nearest_hospital$Drive_Time[i] <- NA
url <- paste("http://dev.virtualearth.net/REST/V1/Routes/",
"Driving",
"?wp.0=",paste0(nearest_hospital$ZIP_Latitude[i],"+",nearest_hospital$ZIP_Longitude[i]),
"&wp.1=",paste0(nearest_hospital$UPMC_Latitude[i],"+",nearest_hospital$UPMC_Longitude[i]),
"&dt=",paste0(format(as.Date(Sys.Date(), "%B %d %Y"), "%m/%d/%Y"), " 09:00:00"),
"&tt=","Departure",
"&key=",BingMapsKey,
sep="")
suppressWarnings(json_bing <- fromJSON(paste(readLines(url), collapse=""), unexpected.escape = "skip"))
# Convert distance calculation to miles and time to minutes
travelDistance <- json_bing$resourceSets[[1]]$resources[[1]]$travelDistance / 1.609344
travelDuration <- json_bing$resourceSets[[1]]$resources[[1]]$travelDuration / 60
nearest_hospital$Drive_Distance[i] <- travelDistance
nearest_hospital$Drive_Time[i] <- travelDuration
}
# Add a drive time range for 10 minute intervals, up to 30 minutes
nearest_hospital <- nearest_hospital %>%
mutate(Range = ifelse(Drive_Time < 10, "0-10",
ifelse(Drive_Time < 20, "10-20",
ifelse(Drive_Time < 30, "20-30", "30+"))))
Note: Travel distance is returned by the API in kilometers and travel time is returned in hours. The above code converts these to miles and minutes, respectively.
# Create a color palette for the HERE 2 hour catchment areas
drive_time.colors <- c("#1a9641", "#a6d96a", "#fdae61", "#d7191c")
drive_time.pal <- colorFactor(drive_time.colors, domain = c(nearest_hospital$Range))
# URL for public use icon for hospitals:
icon <- paste0("https://icons.iconarchive.com/icons/google/",
"noto-emoji-travel-places/256/42491-hospital-icon.png")
# Plot icons for each of the UMPC Hospital locations
# and color-coded markers at each ZIP code location
leaflet() %>%
addProviderTiles("CartoDB.Positron", group="Greyscale") %>%
addMarkers(upmc_hospitals$Longitude, upmc_hospitals$Latitude,
icon = list(
iconUrl = icon,
iconSize = c(35, 35)),
popup = paste0("<b>",upmc_hospitals$Location,
"</b><br>",upmc_hospitals$Address)) %>%
addCircles(nearest_hospital$ZIP_Longitude, nearest_hospital$ZIP_Latitude,
popup = paste0("<b>",nearest_hospital$ZIP,
"</b><br>",nearest_hospital$City,
"<br> Nearest Hospital: ",round(nearest_hospital$Drive_Time,1)," minute drive"),
color = drive_time.pal(as.factor(nearest_hospital$Range)),
radius = 2500, opacity = 1) %>%
# Add a legend
addLegend("bottomleft", pal = drive_time.pal, values = nearest_hospital$Range,
opacity = 0.35, title = "Drive time to nearest Hospital<br>(in minutes)")