Skip to content

Feature services in ArcGIS Online and ArcGIS Enterprise can store attachments linked to individual features within their layers. This capability is commonly used in Survey123 forms where users upload photos, documents, or other files as part of their data collection. The R-ArcGIS Bridge provides three core functions for managing these attachments: querying metadata, downloading files, and updating attachments.

Understanding Attachment Workflows

Before working with attachments, you need to establish a connection to your feature layer. This requires authentication if you’re accessing private services or plan to modify attachments. The typical workflow involves opening a layer reference, querying to understand what attachments exist, and then downloading or updating those files as needed.

Querying Attachment Metadata

The query_layer_attachments() function retrieves metadata about attachments without downloading the actual files. This lets you inspect what attachments exist, their file names, sizes, and which features they belong to before committing to a download operation.

library(arcgisutils)
library(arcgislayers)

# Authenticate if working with private data
set_arc_token(auth_user())

# Connect to your feature layer
furl <- "https://services1.arcgis.com/hLJbHVT9ZrDIzK0I/arcgis/rest/services/v8_Wide_Area_Search_Form_Feature_Layer___a2fe9c/FeatureServer/0"
layer <- arc_open(furl)

# Query all attachments in the layer
att <- query_layer_attachments(layer)

# preview the results
dplyr::glimpse(att)
#> Rows: 101
#> Columns: 10
#> $ parentGlobalId <chr> "51f34e57-003c-4cb5-b9c6-694c5bc63480", "ca558ac8-3377-…
#> $ parentObjectId <int> 13, 14, 19, 21, 22, 27, 34, 35, 36, 40, 43, 49, 52, 53,…
#> $ id             <int> 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,…
#> $ globalId       <chr> "1646cbf2-b884-4b85-a8fa-6ce38dfe7474", "71bbbee7-0fd6-…
#> $ name           <chr> "02d5cd1337d24bc699d6e1e55292c6d9.jpg", "049441602a4048…
#> $ contentType    <chr> "image/jpeg", "image/jpeg", "image/jpeg", "image/jpeg",…
#> $ size           <int> 401051, 87400, 179744, 250221, 211540, 152460, 155329, …
#> $ keywords       <chr> "", "", "", "", "", "", "", "", "", "", "", "", "", "",…
#> $ url            <chr> "https://services1.arcgis.com/hLJbHVT9ZrDIzK0I/arcgis/r…
#> $ exifInfo       <list> [<data.frame[4 x 2]>], [<data.frame[3 x 2]>], [<data.f…

The returned data frame contains columns like PARENTOBJECTID (which feature the attachment belongs to), att_name (the file name), and id (the attachment’s unique identifier). This metadata allows you to make informed decisions about which attachments to download.

Filtering Attachments

You can filter attachments in two ways:

  1. by feature attributes or,
  2. by attachment properties.

Filtering by feature attributes is useful when you only want attachments from features meeting certain criteria, such as records flagged for follow-up.

The definition_expression argument accepts a standard SQL WHERE clause that is applied to the features in the feature service. This is akin to the where argument in arc_select().

# Filter based on **feature** attributes
# Only get attachments where the followup_status field 
# is set to `"needs_followup"
query_layer_attachments(
  layer, 
  "followup_status = 'needs_followup'"
)
#> # A data frame: 24 × 10
#>    parentGlobalId         parentObjectId    id globalId name  contentType   size
#>  * <chr>                           <int> <int> <chr>    <chr> <chr>        <int>
#>  1 498bb7fa-1b64-48b5-8c…             49    13 dd2afd9… 38f8… image/jpeg  272721
#>  2 0b45291f-ec76-4ee1-91…             53    15 146c89e… 3f12… image/jpeg  289968
#>  3 0b45291f-ec76-4ee1-91…             53    16 e929c75… 3f6c… image/jpeg  163008
#>  4 0b45291f-ec76-4ee1-91…             53    17 54f1beb… 401c… image/jpeg  240901
#>  5 edbe0c9f-f2a7-48b1-89…             69    23 482d4f6… 4f6f… image/jpeg  138262
#>  6 e2d7ed7e-092a-4860-8d…             80    30 36fbdb2… 5c8a… image/jpeg  396238
#>  7 5e059ee4-cbc7-4089-a8…             84    32 9e5cb7b… 669a… image/jpeg  411913
#>  8 8823ae01-6799-4b74-ab…             86    33 2e807a4… 69a2… image/jpeg  344543
#>  9 612edf09-92cd-4e39-8b…             91    36 c8981b5… 70ce… image/jpeg  368083
#> 10 612edf09-92cd-4e39-8b…             91    37 1ff5060… 727b… image/jpeg  288994
#> # ℹ 14 more rows
#> # ℹ 3 more variables: keywords <chr>, url <chr>, exifInfo <list>

Alternatively, you can filter based on the properties of the attachment itself. To see what properties are available to query based on you can check the attachmentProperties element of the layer object.

layer$attachmentProperties
#>          name          fieldName isEnabled
#> 1          id       ATTACHMENTID      TRUE
#> 2    globalId AttachmentGlobalID      TRUE
#> 3        name           ATT_NAME      TRUE
#> 4        size          DATA_SIZE      TRUE
#> 5 contentType       CONTENT_TYPE      TRUE
#> 6    keywords           Keywords      TRUE
#> 7    exifInfo           ExifInfo      TRUE

This shows that we can query based on the fieldNames. To actually query based on these, we use the attachments_definition_expression argument which is applied to the attachment properties.

# Filter based on attachment file names
# Only get attachments from a specific date
attachments <- query_layer_attachments(
  layer,
  attachments_definition_expression = "att_name like '%20221005%'"
)

Downloading Attachments

Once you’ve identified the attachments you need, download_attachments() retrieves the actual files to your local system. The function takes the metadata from query_layer_attachments() and a destination folder, then downloads each file while preserving its original name.

# Create a temporary directory for downloads
tmp <- file.path(tempdir(), ulid::ulid()) 

# Create the directory if it doesn't exist
dir.create(tmp, recursive = TRUE, showWarnings = FALSE)

# Download the queried attachments
res <- download_attachments(attachments, tmp, .progress = FALSE)

# Verify the downloads
downloaded <- list.files(tmp)

The function returns a data frame showing the download results, including file paths and any errors encountered. This makes it easy to verify successful downloads and handle any issues that arose during the transfer.

Updating Attachments

Updating attachments requires write access to the feature service, which typically means you need to own the service or have editing permissions granted by the owner. The update process involves uploading new attachment files and associating them with specific features using the update_attachments() function. This operation requires proper authentication through set_arc_token() before attempting any modifications.

In this example we will prepend the current date to the files. To do this, we will rename the files locally.

# get today's date
today <- Sys.Date()

# prepend attachments with the date
new_filenames <- paste0(today, "-", basename(attachments$name))

# create new file paths
new_fps <- file.path(tmp, new_filenames)

# the full name of downloaded attachments
fps <- file.path(tmp, downloaded)

# rename the files
file.rename(fps, new_fps)
#>  [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [16] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

Now that we have renamed the files, we can use update_attachments() to update the attachments in the feature service.

# update the attachments
update_res <- update_attachments(
  layer,
  # OID of the feature <> attachment relationship
  attachments$parentObjectId,
  # the attachment ID
  attachments$id,
  # the path to the attachment on disk
  new_fps,
  # turn off for rmarkdown
  .progress = FALSE
)

head(update_res)
#>   objectId success
#> 1       75    TRUE
#> 2       76    TRUE
#> 3       77    TRUE
#> 4       78    TRUE
#> 5       79    TRUE
#> 6       80    TRUE

Similarly, we can undo our change just as easily!

# rename the files again
file.rename(new_fps, fps)
#>  [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
#> [16] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
update_res <- update_attachments(
  layer,
  attachments$parentObjectId,
  attachments$id,
  # using the previous filenames
  fps,
  .progress = FALSE
)

head(update_res)
#>   objectId success
#> 1       75    TRUE
#> 2       76    TRUE
#> 3       77    TRUE
#> 4       78    TRUE
#> 5       79    TRUE
#> 6       80    TRUE

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.