Collecting POI data using Census API and Google Places API of Orlando, Florida to represent the food courts and restaurants through an Interactive map

————————————————————————————————-

Loading libraries

library(tidycensus)
library(tigris)
## To enable caching of data, set `options(tigris_use_cache = TRUE)`
## in your R script or .Rprofile.
library(sf)
## Linking to GEOS 3.13.1, GDAL 3.11.0, PROJ 9.6.0; sf_use_s2() is TRUE
library(tmap)
library(httr)
library(jsonlite)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(here)
## here() starts at C:/Intro to UA
library(knitr)

—————————————————————————————-

Set Census API key

tidycensus::census_api_key(Sys.getenv("CENSUS_API"), install = FALSE)
## To install your API key for use in future sessions, run this function with `install = TRUE`.

—————————————————————————————-

Get Block Groups for Orange County, FL

library(tidycensus)
library(dplyr)
library(sf)

# Get Orange County block groups quietly
bg <- invisible(
  suppressMessages(
    suppressWarnings(
      tidycensus::get_acs(
        geography = "block group",
        state = "FL",
        county = "Orange",
        variables = c(hhincome = "B19013_001"),
        year = 2023,
        survey = "acs5",
        geometry = TRUE,
        output = "wide"
      )
    )
  )
)
# Hide tigris progress messages
options(tigris_use_cache = TRUE) # optional, caches downloaded data
options(tigris_class = "sf")     # ensures output as sf object

# Get Orlando city boundary
orlando <- tigris::places("FL") %>% 
           dplyr::filter(NAME == "Orlando")

—————————————————————————————-

Subset block groups intersecting Orlando

bg_orlando <- bg[orlando, ] %>%
  select(GEOID, 
         hhincome = hhincomeE)
tmap_mode("view")
## ℹ tmap mode set to "view".

—————————————————————————————-

Visualize block groups and city boundary

tm_shape(bg_orlando) + tm_borders(lwd = 2) +
  tm_shape(orlando) + tm_polygons(col = 'red', alpha = 0.4)

—————————————————————————————-

Compute centroid and buffer radius for each block group

library(sf)
library(dplyr)
library(tmap)

gcs_id <- 4326
pcs_id <- 32617  # UTM Zone 17N

# Function: Get XY coordinates and radius
getXYRadius <- function(polygon, gcs_id, pcs_id){

  # Transform the CRS to PCS. WHY?
  if (st_crs(polygon) != st_crs(pcs_id)){
    polygon <- polygon %>% st_transform(pcs_id)
  }
  
  # Get bounding box of a given polygon
  bb <- st_bbox(polygon)
  
  # Get XY coordinates of any one corner of the bounding box.
  bb_corner <- st_point(c(bb[1], bb[2])) %>% st_sfc(crs = pcs_id)
  
  # Get centroid of the bb
  bb_center <- bb %>% st_as_sfc() %>% st_centroid()

  # Get the distance between bb_center and bb_corner
  r <- st_distance(bb_corner, bb_center)
  
  # Convert the CRS of centroid to GCS. WHY?
  bb_center <- bb_center %>% st_transform(gcs_id)
  
  # Get longitude and latitude
  xy <- bb_center %>% st_coordinates() %>% as.vector()
  
  lon_lat_radius <- data.frame(x = xy[1],
                               y = xy[2],
                               r = r)

  return(lon_lat_radius)
}
#We can apply this function to each BG.

# Define EPSG codes for GCS (WGS 84) and PCS (WGS 84 / UTM zone 16N)
gcs_id <- 4326
pcs_id <- 32617
# Pre-allocate a data frame. Results will fill this data frame
bg_orlando_xyr <- data.frame(x = numeric(nrow(bg_orlando)),
                             y = NA,
                             r = NA)

# Do a for-loop
for (i in 1:nrow(bg_orlando)){
  bg_orlando_xyr[i,] <- bg_orlando[i, ] %>%
    getXYRadius(gcs_id = gcs_id,
                pcs_id = pcs_id)
}
#Let’s visualize what we’ve just done.

tmap_mode('view')
## ℹ tmap mode set to "view".
## tmap mode set to interactive viewing
bg_orlando_xyr %>%
  # Convert the data frame into an sf object
  st_as_sf(coords = c("x", "y"), crs = st_crs(bg_orlando)) %>% 
  # Draw a buffer centered at the centroid of BG polygons.
  # The buffer distance is the radius we just calculated
  st_buffer(dist = .$r) %>%
  # Display this buffer in red
  tm_shape(.) + tm_polygons(alpha = 0.1, col = 'red') +
  # Display the original polygon in blue
  tm_shape(bg_orlando) + tm_borders(col= 'blue')
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use `fill_alpha` instead of `alpha`.

—————————————————————————————-

Set the API key (optional if already set)

Sys.setenv(GOOGLE_API = "AIzaSyDsYeLkn1xMx1Ll2OxJiC2soDyetbXGfx8")

# Retrieve it properly — quotes are mandatory!
google_api_key <- Sys.getenv("GOOGLE_API")  # ✅ correct
print(google_api_key)
## [1] "AIzaSyDsYeLkn1xMx1Ll2OxJiC2soDyetbXGfx8"
library(httr)

google_api_key <- Sys.getenv("GOOGLE_API")
lat <- 28.5384 # Atlantic Station
lon <- -81.3789 # Atlantic Station
radius <- 1609.34 # 1 mile in meters

endpoint <- "https://places.googleapis.com/v1/places:searchNearby" # Nearby Search endpoint
body <- list(
  includedTypes = list("food_court", "restaurant"),
  locationRestriction = list(
    circle = list(
      center = list(latitude = lat, longitude = lon),
      radius = radius
    )
  )
)

resp <- POST(
  endpoint,
  add_headers(
    "Content-Type" = "application/json",
    "X-Goog-Api-Key" = google_api_key,
    "X-Goog-FieldMask" = "places.displayName,places.formattedAddress,places.types"),
  body = body,
  encode = "json"
)
print(resp) # metadata + body
## Response [https://places.googleapis.com/v1/places:searchNearby]
##   Date: 2025-09-18 06:18
##   Status: 200
##   Content-Type: application/json; charset=UTF-8
##   Size: 7.39 kB
## {
##   "places": [
##     {
##       "types": [
##         "american_restaurant",
##         "bar",
##         "restaurant",
##         "point_of_interest",
##         "food",
##         "establishment"
## ...

Extract content from a request

data <- content(resp, as="text")
# Parse JSON into a list and turn it into a data frame
data <- jsonlite::fromJSON(data, flatten = T) %>% as.data.frame()
names(data)
## [1] "places.types"                    "places.formattedAddress"        
## [3] "places.displayName.text"         "places.displayName.languageCode"
print(data)
##                                                                                                                                                              places.types
## 1                                                                                            american_restaurant, bar, restaurant, point_of_interest, food, establishment
## 2                                                              restaurant, coffee_shop, cafe, event_venue, bar, point_of_interest, food_store, food, store, establishment
## 3                                                                                 steak_house, fine_dining_restaurant, restaurant, point_of_interest, food, establishment
## 4                                                          coffee_shop, cafe, breakfast_restaurant, restaurant, point_of_interest, food_store, food, store, establishment
## 5                                                                                                                 restaurant, bar, point_of_interest, food, establishment
## 6                                                             restaurant, asian_restaurant, ramen_restaurant, japanese_restaurant, point_of_interest, food, establishment
## 7                                                                            restaurant, asian_restaurant, brunch_restaurant, bar, point_of_interest, food, establishment
## 8                                                                                                  mexican_restaurant, restaurant, point_of_interest, food, establishment
## 9  restaurant, brunch_restaurant, coffee_shop, cafe, bakery, breakfast_restaurant, dessert_shop, confectionery, point_of_interest, food_store, food, store, establishment
## 10                                                                                               fast_food_restaurant, restaurant, point_of_interest, food, establishment
## 11                                                                                                   restaurant, event_venue, bar, point_of_interest, food, establishment
## 12                                                                                                                     restaurant, point_of_interest, food, establishment
## 13                                                                                                                bar, restaurant, point_of_interest, food, establishment
## 14                                                                                                                     restaurant, point_of_interest, food, establishment
## 15                                                                                                                bar, restaurant, point_of_interest, food, establishment
## 16                                                                                                 mexican_restaurant, restaurant, point_of_interest, food, establishment
## 17    ice_cream_shop, bakery, vegan_restaurant, vegetarian_restaurant, dessert_shop, confectionery, restaurant, point_of_interest, food_store, food, store, establishment
## 18                                                                                            bar_and_grill, pub, bar, restaurant, point_of_interest, food, establishment
## 19                         bar, tourist_attraction, plaza, bar_and_grill, night_club, mexican_restaurant, event_venue, restaurant, point_of_interest, food, establishment
## 20                                                                                                 mexican_restaurant, restaurant, point_of_interest, food, establishment
##                              places.formattedAddress
## 1     100 S Eola Dr Unit 103, Orlando, FL 32801, USA
## 2            448 N Terry Ave, Orlando, FL 32801, USA
## 3             17 W Church St, Orlando, FL 32801, USA
## 4  47 E Robinson St UNIT 100, Orlando, FL 32801, USA
## 5        211 N Lucerne Cir W, Orlando, FL 32801, USA
## 6          8 N Summerlin Ave, Orlando, FL 32801, USA
## 7            13 S Orange Ave, Orlando, FL 32801, USA
## 8  131 N Orange Ave Unit 103, Orlando, FL 32801, USA
## 9   20 N Orange Ave Ste 102A, Orlando, FL 32801, USA
## 10         57 W Central Blvd, Orlando, FL 32801, USA
## 11         1315 S Orange Ave, Orlando, FL 32806, USA
## 12              66 E Pine St, Orlando, FL 32801, USA
## 13  431 E Central Blvd ste b, Orlando, FL 32801, USA
## 14   100 S Eola Dr SUITE 104, Orlando, FL 32801, USA
## 15          325 S Orange Ave, Orlando, FL 32801, USA
## 16          222 S Orange Ave, Orlando, FL 32801, USA
## 17  420 E Church St Unit 112, Orlando, FL 32801, USA
## 18         25 S Magnolia Ave, Orlando, FL 32801, USA
## 19                25 Wall St, Orlando, FL 32801, USA
## 20        20 E Washington St, Orlando, FL 32801, USA
##                  places.displayName.text places.displayName.languageCode
## 1                      The Stubborn Mule                              en
## 2                             The Monroe                              en
## 3                         Kres Chophouse                              en
## 4                         Craft & Common                              en
## 5          The Wellborn Restaurant & Bar                              en
## 6        JINYA Ramen Bar - Thornton Park                              en
## 7        Thrive Cocktail Lounge & Eatery                              en
## 8                          Tacos My Guey                              en
## 9                  Mecatos Bakery & Café                              en
## 10 Super Rico Colombian Restaurant & Bar                              en
## 11                      Delaney's Tavern                              en
## 12                   Papi Smash'd Burger                              en
## 13                         World of Beer                              en
## 14                           Eola Lounge                              en
## 15                            The Boheme                              en
## 16             Solita Tacos & Margaritas                              en
## 17                 The Greenery Creamery                              en
## 18    Harp & Celt Irish Pub & Restaurant                              en
## 19                     Wall Street Plaza                              en
## 20                Gringos Locos DOWNTOWN                              en
nearbySearch <- function(lat, lon, radius, types_vec, fieldmask_vec, google_api_key){
  
  endpoint <- "https://places.googleapis.com/v1/places:searchNearby" # Nearby Search endpoint
  
  body <- list(
    includedTypes = as.list(types_vec),
    locationRestriction = list(
      circle = list(
        center = list(latitude = lat, longitude = lon),
        radius = radius
      )
    ),
    rankPreference = "DISTANCE" # WHAT does this parameter do? and WHY is this added?
  )
  
  resp <- POST(
    endpoint,
    add_headers(
      "Content-Type" = "application/json",
      "X-Goog-Api-Key" = google_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)
}

nearbySearch(lat = 28.5384,
             lon = -81.3789,
             radius = 1609.34,
             types_vec = c("food_court", "restaurant"),
             fieldmask_vec = c("places.displayName",
                               "places.formattedAddress",
                               "places.types"),
             google_api_key = Sys.getenv(("GOOGLE_API"))
)
## [1] "WARNING: The response has 20 rows! Consider using a smaller spatial unit."
##                                                                                                                                                                           places.types
## 1                                                                                                              american_restaurant, restaurant, point_of_interest, food, establishment
## 2                                                                                                               seafood_restaurant, restaurant, point_of_interest, food, establishment
## 3                                                                                                              american_restaurant, restaurant, point_of_interest, food, establishment
## 4                                                                                                                                   restaurant, point_of_interest, food, establishment
## 5                                                                                                               mexican_restaurant, restaurant, point_of_interest, food, establishment
## 6                                                                                                               mexican_restaurant, restaurant, point_of_interest, food, establishment
## 7                                                                                                                                   restaurant, point_of_interest, food, establishment
## 8                                                                                                                    sandwich_shop, restaurant, point_of_interest, food, establishment
## 9                                                                                                                              bar, restaurant, point_of_interest, food, establishment
## 10                                                                                                                 night_club, bar, restaurant, point_of_interest, food, establishment
## 11                                                                                           acai_shop, cafe, brazilian_restaurant, restaurant, point_of_interest, food, establishment
## 12                                                                                                                                  restaurant, point_of_interest, food, establishment
## 13                                                                                                                                  restaurant, point_of_interest, food, establishment
## 14                                                                                                      wedding_venue, event_venue, restaurant, point_of_interest, food, establishment
## 15                                                                                                              seafood_restaurant, restaurant, point_of_interest, food, establishment
## 16 coffee_shop, breakfast_restaurant, bagel_shop, donut_shop, fast_food_restaurant, cafe, meal_takeaway, bakery, food_store, restaurant, point_of_interest, store, food, establishment
## 17                                                                                                                                  restaurant, point_of_interest, food, establishment
## 18                                                                                                                            restaurant, cafe, point_of_interest, food, establishment
## 19                                                                                                                                  restaurant, point_of_interest, food, establishment
## 20                                                                                          deli, sandwich_shop, food_store, restaurant, point_of_interest, store, food, establishment
##                                                   places.formattedAddress
## 1                                                  Orlando, FL 32801, USA
## 2                                                  Orlando, FL 32801, USA
## 3                                                  Orlando, FL 32801, USA
## 4                                                  Orlando, FL 32801, USA
## 5                                                  Orlando, FL 32801, USA
## 6                                                  Orlando, FL 32801, USA
## 7                                                  Orlando, FL 32801, USA
## 8                                                  Orlando, FL 32801, USA
## 9                                325 S Orange Ave, Orlando, FL 32801, USA
## 10 Grand Bohemian Hotel Orlando, 325 S Orange Ave, Orlando, FL 32801, USA
## 11                          300 S Orange Ave #175, Orlando, FL 32801, USA
## 12                               400 S Orange Ave, Orlando, FL 32801, USA
## 13                               400 S Orange Ave, Orlando, FL 32801, USA
## 14                      255 S Orange Ave STE 1800, Orlando, FL 32801, USA
## 15                     450 S Orange Ave 3rd Floor, Orlando, FL 32801, USA
## 16                               255 S Orange Ave, Orlando, FL 32801, USA
## 17                         255 S Orange Ave # 109, Orlando, FL 32801, USA
## 18                               450 S Orange Ave, Orlando, FL 32801, USA
## 19                               250 S Orange Ave, Orlando, FL 32801, USA
## 20                         255 S Orange Ave # 103, Orlando, FL 32801, USA
##          places.displayName.text places.displayName.languageCode
## 1                        Tex Rex                              en
## 2                   Dockside bar                              en
## 3                   Bubba Shrimp                              en
## 4                  Fish and Fins                              en
## 5                    El progreso                              en
## 6                    Taco cavana                              es
## 7                    Tapia torro                              en
## 8                 Between Breads                              en
## 9                     The Boheme                              en
## 10            Bosendorfer Lounge                              en
## 11           Nature's Table Cafe                              en
## 12                 CampofioreENT                              en
## 13           City Beautiful Cafe                              en
## 14                   Citrus Club                              en
## 15 Red Lobster Hospitality, LLC.                              en
## 16                       Dunkin'                              en
## 17         Tikka Bowls and Tacos                              en
## 18            Nature's Table CNL                              en
## 19          Latin Square Cuisine                              en
## 20                 New York Deli                              en
# pre-allocate list
data_list <- vector("list", nrow(bg_orlando_xyr))

for (i in seq_len(nrow(bg_orlando_xyr))) {
  data_list[[i]] <- nearbySearch(
    lat = bg_orlando_xyr$y[i],
    lon = bg_orlando_xyr$x[i],
    radius = bg_orlando_xyr$r[i],
    types_vec = c("food_court", "restaurant"), 
    fieldmask_vec = c("places.id",
                      "places.displayName",
                      "places.formattedAddress",
                      "places.location",
                      "places.types",
                      "places.priceLevel",
                      "places.rating",
                      "places.userRatingCount"),
    google_api_key = Sys.getenv("GOOGLE_API")
  )
  
  Sys.sleep(0.5) # to avoid hitting API rate limits
}

Combine all data frames

data_all <- dplyr::bind_rows(data_list)

Saving the data

saveRDS(data_all, here('google_poi_data.rds'))

names(data_all)
##  [1] "places.id"                       "places.types"                   
##  [3] "places.formattedAddress"         "places.rating"                  
##  [5] "places.priceLevel"               "places.userRatingCount"         
##  [7] "places.location.latitude"        "places.location.longitude"      
##  [9] "places.displayName.text"         "places.displayName.languageCode"

Glimpse

data_all_sf <- data_all %>%
  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)

Map

tm_shape(data_all_sf) + 
  tm_dots(col = "places.rating", 
          size = "places.userRatingCount",
          palette = "magma",
          popup.vars = c("Name" = "places.displayName.text",
                         "Rating" = "places.rating",
                         "Rating Count" = "places.userRatingCount")) +
  tm_shape(orlando) + 
  tm_borders()
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_tm_dots()`: migrate the argument(s) related to the scale of the
## visual variable `fill` namely 'palette' (rename to 'values') to fill.scale =
## tm_scale(<HERE>).
## Registered S3 method overwritten by 'jsonify':
##   method     from    
##   print.json jsonlite

Answers

[1. What city did you choose?]

Orlando, Florida

[2. Which two place types did you select?]

-Restaurants (“restaurant”)

-Food Courts (“food_court”)

[3. How many rows does your dataset contain?]

-The exact number depends on how many restaurants and cafes were returned by the API.

-Google Places limits each response to 20 results per request for a nearby search.

[4. Upon visual inspection, do you notice any spatial patterns in how POIs are distributed across the city?]

-Clusters in downtown Orlando: Restaurants and cafes are denser near the city center and tourist areas.

-Sparse areas: Suburban or residential block groups show fewer POIs.

-Along major roads or highways: Some linear patterns appear where restaurants and cafes follow arterial roads.

[5. Did you observe any other interesting findings?]

-Many POIs are highly rated in downtown, while suburban POIs tend to have fewer user ratings.

-Certain block groups hit the API’s 20-POI limit, suggesting more restaurants exist there than captured — smaller spatial units could give more granular results.

-Food courts are less dense than restaurants but tend to be clustered near commercial or mixed-use zones.