Choose two place types for the includedTypes parameter in the Google Places Nearby Search API. Refer to the API documentation for options.
Select a U.S. city. Use the tigris package to obtain an sf object of the city boundary.
City chosen: Paradise Valley, Arizona Place types chosen: ‘bus_station’ and ‘transit_station’
place_types <- c("bus_station", "transit_station")
api_key <- Sys.getenv("GOOGLE_API")
pv <- tigris::places("AZ", progress_bar = FALSE) %>%
filter(NAME == "Paradise Valley") %>%
st_transform(4326)
## Retrieving data for the year 2024
Divide the city into small areas using one of the following methods and prepare locationRestriction parameter values (longitude, latitude, and radius) for each area:
Using Method 1: Census Block Groups (BGs) that intersect city polygon
bg <- suppressMessages(
tidycensus::get_acs(geography = "block group",
state = "AZ",
county = "Maricopa",
variables = c(totalpop = "B01001_001"),
year = 2023,
survey = "acs5",
geometry = TRUE,
output = "wide"
)
)
## Warning: • You have not set a Census API key. Users without a key are limited to 500
## queries per day and may experience performance limitations.
## ℹ For best results, get a Census API key at
## http://api.census.gov/data/key_signup.html and then supply the key to the
## `census_api_key()` function to use it throughout your tidycensus session.
## This warning is displayed once per session.
## | | | 0% | |= | 1% | |== | 2% | |== | 3% | |=== | 5% | |==== | 6% | |===== | 7% | |====== | 8% | |====== | 9% | |======= | 10% | |======== | 11% | |======== | 12% | |========= | 13% | |========= | 14% | |========== | 14% | |=========== | 15% | |=========== | 16% | |============ | 17% | |============= | 18% | |============= | 19% | |============== | 20% | |============== | 21% | |=============== | 21% | |================ | 22% | |================ | 23% | |================= | 24% | |================= | 25% | |================== | 26% | |=================== | 27% | |=================== | 28% | |==================== | 28% | |==================== | 29% | |===================== | 30% | |====================== | 31% | |====================== | 32% | |======================= | 33% | |======================== | 34% | |========================= | 35% | |========================= | 36% | |========================== | 37% | |=========================== | 38% | |=========================== | 39% | |============================ | 40% | |============================= | 41% | |============================= | 42% | |============================== | 43% | |============================== | 44% | |=============================== | 44% | |================================ | 45% | |================================ | 46% | |================================= | 47% | |================================== | 48% | |================================== | 49% | |=================================== | 50% | |=================================== | 51% | |==================================== | 51% | |===================================== | 52% | |===================================== | 53% | |====================================== | 54% | |====================================== | 55% | |======================================= | 56% | |======================================== | 57% | |======================================== | 58% | |========================================= | 58% | |========================================= | 59% | |========================================== | 60% | |=========================================== | 61% | |============================================ | 62% | |============================================ | 63% | |============================================= | 64% | |============================================= | 65% | |============================================== | 66% | |=============================================== | 67% | |================================================ | 68% | |================================================ | 69% | |================================================= | 70% | |================================================== | 71% | |=================================================== | 72% | |=================================================== | 73% | |==================================================== | 74% | |==================================================== | 75% | |===================================================== | 76% | |====================================================== | 77% | |======================================================= | 78% | |======================================================= | 79% | |======================================================== | 80% | |========================================================= | 81% | |========================================================= | 82% | |========================================================== | 83% | |=========================================================== | 84% | |=========================================================== | 85% | |============================================================ | 86% | |============================================================= | 86% | |============================================================= | 87% | |============================================================== | 88% | |============================================================== | 89% | |=============================================================== | 90% | |================================================================ | 91% | |================================================================ | 92% | |================================================================= | 93% | |================================================================== | 94% | |=================================================================== | 95% | |=================================================================== | 96% | |==================================================================== | 97% | |==================================================================== | 98% | |===================================================================== | 99% | |======================================================================| 100%
bg <- st_transform(bg, 4326)
pv <- st_transform(pv, 4326)
pv_bg <- bg[pv,]
#tempe_blocks
#plot(st_geometry(tempe_blocks))
#head(coordinates)
nrow(pv_bg)
## [1] 28
Verify coverage to ensure there are no uncovered areas by visualizing your locationRestriction parameters on a map:
Create a POINT sf object using longitude, latitude, and sf::st_as_sf().
gcs_id <- 4326
pcs_id <- 32612
getXYRadius <- function(polygon, gcs_id, pcs_id){
if(st_crs(polygon) != st_crs(pcs_id)){
polygon <- polygon %>% st_transform(pcs_id)
}
bb <- st_bbox(polygon)
bb_corner <- st_point(c(bb[1], bb[2])) %>% st_sfc(crs = pcs_id)
bb_center <- bb %>% st_as_sfc() %>% st_centroid()
r <- st_distance(bb_corner, bb_center)
bb_center <- bb_center %>% st_transform(gcs_id)
xy <- bb_center %>% st_coordinates() %>% as.vector()
lon_lat_radius <- data.frame(x = xy[1],
y = xy[2],
r = r)
return(lon_lat_radius)
}
Create buffers using radius and sf::st_buffer().
bg_pv_xyr <- data.frame(x = numeric(nrow(pv_bg)),
y = NA,
r = NA)
# Do a for-loop
for (i in 1:nrow(pv_bg)){
bg_pv_xyr[i,] <- pv_bg[i, ] %>%
getXYRadius(gcs_id = gcs_id,
pcs_id = pcs_id)
}
Build an interactive map displaying (1) the city boundary and (2) the buffer objects with the tmap package.
tmap_mode("view")
## ℹ tmap mode set to "view".
bg_pv_xyr %>%
st_as_sf(coords = c("x", "y"), crs = st_crs(pv_bg)) %>%
st_buffer(dist = .$r) %>%
tm_shape(.) + tm_polygons(alpha = 0.1, col = 'red') +
tm_shape(pv_bg) + tm_borders(col= 'blue')
##
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use 'fill' for the fill color of polygons/symbols
## (instead of 'col'), and 'col' for the outlines (instead of 'border.col').[v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.
nearbySearch <- function(lat, lon, radius, types_vec, fieldmask_vec, api_key){
endpoint <-"https://places.googleapis.com/v1/places:searchNearby"
body <- list(
includedTypes = as.list(types_vec),
locationRestriction = list(
circle = list(
center = list(latitude = lat, longitude = lon),
radius = radius
)
),
rankPreference = "DISTANCE"
)
resp <- POST(
endpoint,
add_headers(
"Content-Type" = "application/json",
"X-Goog-Api-Key" = api_key,
"X-Goog-FieldMask" = paste(fieldmask_vec, collapse = ",")),
body = body,
encode = "json"
)
data <- content(resp, as="text") %>%
jsonlite::fromJSON(flatten = T) %>%
as.data.frame()
if (nrow(data) == 20){
print("WARNING: The response has 20 rows! Consider using a smaller spatial unit.")
}
return(data)
}
get latitude and long
# get centroids for Paradise Valley (or Tempe, depending on what you're using)
centroids <- st_centroid(pv_bg) # or tempe_bg
## Warning: st_centroid assumes attributes are constant over geometries
coords <- st_coordinates(centroids)
#coords
x <- coords[1,1]
y <- coords[1,2]
#required field mask
field_mask <- paste(
c("places.id",
"places.displayName",
"places.formattedAddress",
"places.location.latitude",
"places.location.longitude",
"places.types",
"places.priceLevel",
"places.rating",
"places.userRatingCount"),
collapse = ","
)
test_poi <- nearbySearch(
lat = 33.4255, # Y = latitude
lon = -111.94, # X = longitude
radius = 1000,
types_vec = place_types,
fieldmask_vec = field_mask,
api_key = Sys.getenv("GOOGLE_API")
)
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
#str(test_poi)
Parse the API response into a data frame.
data_list <- vector("list", nrow(bg_pv_xyr))
for (i in seq_len(nrow(bg_pv_xyr))) {
data_list[[i]] <- nearbySearch(
lat = bg_pv_xyr$y[i],
lon = bg_pv_xyr$x[i],
radius = bg_pv_xyr$r[i],
types_vec = place_types,
fieldmask_vec = field_mask,
api_key = Sys.getenv("GOOGLE_API")
)
Sys.sleep(1)
}
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
Export the POI dataset into an RData or RDS file.
data_all <- dplyr::bind_rows(data_list)
saveRDS(data_all, file = here::here("google_poi_data.rds"))
Convert the POI dataset into an sf object using sf::st_as_sf().
library(purrr)
## Warning: package 'purrr' was built under R version 4.3.3
##
## Attaching package: 'purrr'
## The following object is masked from 'package:jsonlite':
##
## flatten
poi_flat <- data_all %>%
mutate(places.types = places.types %>%
map_chr(~ paste(.x, collapse = ",")))
data_all_sf <- poi_flat %>%
rename(x = places.location.longitude, y = places.location.latitude) %>%
filter(!is.na(x) & !is.na(y)) %>%
st_as_sf(coords = c("x", "y"), crs = 4326)
Create an interactive map showing both the POIs and the city boundary.
tmap_mode("view")
## ℹ tmap mode set to "view".
tm_shape(pv) +
tm_borders(lwd = 1, col = "black") +
tm_shape(data_all_sf) +
tm_dots(shape = 21,
col = "black",
lwd = 1,
fill = "places.rating",
fill.scale = tm_scale_continuous(values = "magma"),
size = "places.userRatingCount",
popup.vars = c("Name" = "places.displayName.text",
"Type" = "places.types",
"Address" = "places.formattedAddress",
"Rating" = "places.rating",
"Rating Count" = "places.userRatingCount"))
What city did you choose? - I chose Paradise Valley, Arizona Which two place types did you select? - I chose bus stations and transit staions How many rows does your dataset contain? - 347 Upon visual inspection, do you notice any spatial patterns in how POIs are distributed across the city? -most of the transit and bus stations are outside of the city boundaries not directly within. They are not really evenly distributed either which is surprising, mostly just on the main roads. (Optional) Did you observe any other interesting findings? ```