Working with REST APIs to access spatial data
SUDS Workshop

REST API?

HTTP methods

Today’s focus

URL components

library(httr2)

url <- file.path(
  "https://tigerweb.geo.census.gov",
  "arcgis/rest/services/TIGERweb/State_County/MapServer/0"
)

url_parse(url)
#> <httr2_url> https://tigerweb.geo.census.gov/arcgis/rest/services/TIGERweb/State_County/MapServer/0
#> * scheme: https
#> * hostname: tigerweb.geo.census.gov
#> * path: /arcgis/rest/services/TIGERweb/State_County/MapServer/0

Building queries

req <- url |>
  request() |>
  req_url_query(where = "NAME='Utah'", fields = "'NAME'")

url_parse(req[["url"]])
#> <httr2_url> https://tigerweb.geo.census.gov/arcgis/rest/services/TIGERweb/State_County/MapServer/0?where=NAME%3D%27Utah%27&fields=%27NAME%27
#> * scheme: https
#> * hostname: tigerweb.geo.census.gov
#> * path: /arcgis/rest/services/TIGERweb/State_County/MapServer/0
#> * query:
#>   * where: NAME='Utah'
#>   * fields: 'NAME'

arcgislayers

the core data access package in the R-ArcGIS Bridge, providing a unified interface for working with ArcGIS data services. As part of the arcgis metapackage, it enables seamless integration between R and the ArcGIS Web GIS ecosystem, including ArcGIS Online, Enterprise, and Location Platform.

https://r.esri.com/arcgislayers/

Josiah Parry, Senior Product Engineer @ Esri

Intuition Pumps

AGOL query in the browser

Return JSON

{
    "displayFieldName": "BASENAME",
    "fieldAliases": {
        "NAME": "NAME"
    },
    "geometryType": "esriGeometryPolygon",
    "spatialReference": {
        "wkid": 102100,
        "latestWkid": 3857
    },
    "fields": [...],
    "features": [
        {
            "attributes": {
                "NAME": "Utah"
            },
            "geometry": {...}
        }
    ]
}

Serialize into simple feature

library(sf)

utah <- read_sf("utah.json")

utah[c("STATE", "NAME")]
#> Simple feature collection with 1 feature and 2 fields
#> Geometry type: POLYGON
#> Dimension:     XY
#> Bounding box:  xmin: -12696310 ymin: 4438780 xmax: -12138450 ymax: 5161234
#> Projected CRS: WGS 84 / Pseudo-Mercator
#>   STATE NAME                       geometry
#> 1    49 Utah POLYGON ((-12412992 5160912...

arcgislayers in R

Get US Census TIGER/line data

library(arcgislayers)

# connect to a map service
service <- arc_open(url)

# write and execute http request
utah <- arc_select(service, where = "NAME='Utah'")

# querying a <FeatureLayer> returns sf
utah[c("STATE", "NAME")]
#> Simple feature collection with 1 feature and 2 fields
#> Geometry type: POLYGON
#> Dimension:     XY
#> Bounding box:  xmin: -12696310 ymin: 4438780 xmax: -12138450 ymax: 5161234
#> Projected CRS: WGS 84 / Pseudo-Mercator
#>   STATE NAME                       geometry
#> 1    49 Utah POLYGON ((-12412992 5160912...

Why care?

Focus on remote access…

  1. makes data used for research more accessible,
  2. shifts effort from data management to data processing,
  3. reduces the need for costly data duplication,
  4. supports collaborative research, and
  5. promotes reproducibility.

Only the source() is real!

utah.json ~ 1070 KB

utah.R ~ 0.3 KB

But sharing the R script is basically sharing the data,
since getting the data is as simple as source("utah.R")*.

* There are always trade-offs…

But also money!

Modest costs:

In fiscal year (FY) 2022, the combined budget request for all the major statistical agencies and other statistical programs in federal agencies (including the decennial census, economic, and agricultural censuses) totaled $7.1 billion. This amounted to about 0.03 percent of GDP.

For significant ROI:

A recent publication from the Department of Commerce estimates the value of government data-intensive sector as $407.9 billion in 2012 rapidly growing to $778.0 billion in 2022.

Source: NASEM. 2025. Principles and Practices for a Federal Statistical Agency: Eighth Edition. https://doi.org/10.17226/27934.

Arguments to arc_select()

When we pass query parameters, arc_select() helps us build a url query string and then sends that request to the service endpoint.

fields for column selection

# available fields at this service endpoint
list_fields(service)[["name"]]
#>  [1] "MTFCC"      "OID"        "GEOID"      "STATE"      "STATENS"   
#>  [6] "BASENAME"   "NAME"       "LSADC"      "FUNCSTAT"   "AREALAND"  
#> [11] "AREAWATER"  "REGION"     "DIVISION"   "STUSAB"     "STGEOMETRY"
#> [16] "CENTLAT"    "CENTLON"    "INTPTLAT"   "INTPTLON"   "OBJECTID"

states <- arc_select(service, fields = "NAME")
names(states)
#> [1] "NAME"     "geometry"

where for SQL queries

intermountain <- arc_select(service, where = "NAME IN ('Utah', 'Nevada')")

intermountain[["NAME"]]
#> [1] "Nevada" "Utah"

crs for return spatial reference

library(sf)

states <- arc_select(service, crs = 26912)

st_crs(states)$epsg
#> [1] 26912

geometry for returning geometry

state_attributes <- arc_select(service, geometry = FALSE)

# are all geometries empty?
all(st_is_empty(state_attributes))
#> [1] TRUE

filter_geom for spatial filters

# watershed boundaries database service
wbd_url <- file.path(
  "https://hydrowfs.nationalmap.gov",
  "arcgis/rest/services/wbd/MapServer/3"
)

wbd <- arc_open(wbd_url)

# basins (huc6) of utah
basins <- arc_select(wbd, filter_geom = st_geometry(utah))

basins[["name"]]
#>  [1] "Lower Green"                  "Colorado Headwaters"         
#>  [3] "Weber"                        "Jordan"                      
#>  [5] "Lower San Juan"               "Upper Colorado-Dirty Devil"  
#>  [7] "Upper Colorado-Dolores"       "Upper Green"                 
#>  [9] "White-Yampa"                  "Lower Colorado-Lake Mead"    
#> [11] "Lower Bear"                   "Upper Bear"                  
#> [13] "Great Salt Lake"              "Escalante Desert-Sevier Lake"
#> [15] "Upper Snake"

predicate for spatial relation

# intersects is the default
basins <- arc_select(
  wbd,
  filter_geom = st_geometry(utah),
  predicate = "within"
)

basins[["name"]]
#> [1] "Jordan"

What about rasters?

The answer is yes.

arc_raster()

url <- file.path(
  "https://elevation.nationalmap.gov",
  "arcgis/rest/services/3DEPElevation/ImageServer"
)

gs3dep <- arc_open(url)

gsl <- basins |>
  subset(name == "Great Salt Lake") |>
  st_transform(st_crs(gs3dep))

bb8 <- st_bbox(gsl)

dx <- diff(bb8[c("xmin", "xmax")])
dy <- diff(bb8[c("ymin", "ymax")])

dem <- arc_raster(
  gs3dep,
  xmin = bb8[["xmin"]],
  xmax = bb8[["xmax"]],
  ymin = bb8[["ymin"]],
  ymax = bb8[["ymax"]],
  width = 1000,
  height = round(1000 * (dy / dx) / 100) * 100
)

Result

library(terra)

plot(dem)
plot(
  st_geometry(gsl),
  add = TRUE,
  color = "transparent",
  border = "white",
  lwd = 3
)

Conclusion

arcgislayers is excellent for querying AGOL

arcgislayers also offers write access

Can publish to AGOL, so you can share scripts and your own data.

Federal spatial datasets are everywhere

USGS, EPA, NOAA, USDA, USFS, BLM, NPS, Census, etc.

With additional endpoints through other APIs

Once you understand the process, it is straightforward to access it with httr2.