CORS for serving of COGs of UAV imagery on AWS with R

aws
s3
r
paws
s3sf
leaflet
leafem
COG
CORS
Author

al

Published

September 21, 2024

Modified

September 21, 2024

Whoa Billy. Time to host our UAV imagery on AWS and serve it out through leaflet and do dank moves like put before after images on slippy maps next to each other for world peace. Ha. Well that is a bit dramatic but hey. Still pretty cool.

First thing is to convert the image to a cog and sync it up to a bucket. Not doing that here. Will do soon though. What we do here is - after we are sure there are public permissions allowed to the bucket but we also need to deal with big bad CORS. We can set a viewing Cross-origin resource sharing (CORS). “CORS defines a way for client web applications that are loaded in one domain to interact with resources in a different domain”. This is done with the following JSON.

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [
            "x-amz-server-side-encryption",
            "x-amz-request-id",
            "x-amz-id-2"
        ],
        "MaxAgeSeconds": 3000
    }
]

We went the daft way here and just copied and pasted this into the CORS section of our bucket in the console but we should be able to use the paws package and the s3_put_bucket_cors function (I would think). We are going to leave that for another day.

Code
library(paws)
Warning: package 'paws' was built under R version 4.4.1
Code
library(s3fs)
library(leaflet)
library(leafem)

List your buckets kid.

Code
s3 <- paws::s3()
buckets <- s3$list_buckets()
purrr::map_chr(buckets$Buckets, "Name")
[1] "23cog"        "new-graphiti"
Code
s3fs::s3_dir_ls(refresh = TRUE) 
[1] "s3://23cog"        "s3://new-graphiti"
Code
bucket_path <- s3fs::s3_path("23cog")
Code
# too much info
s3fs::s3_dir_tree(bucket_path)

Now list em with full paths

Code
# full paths
s3_dir_ls(bucket_path)
 [1] "s3://23cog/.DS_Store"                                  
 [2] "s3://23cog/20210906lampreymoricetribv220230317-DEM.tif"
 [3] "s3://23cog/20210906lampreymoricetribv220230317.las"    
 [4] "s3://23cog/20210906lampreymoricetribv220230317.tif"    
 [5] "s3://23cog/FHAP_template.csv"                          
 [6] "s3://23cog/bc_093l026_xli1m_utm09_2018.tif"            
 [7] "s3://23cog/bc_093l048_xli1m_utm09_2019.tif"            
 [8] "s3://23cog/bc_093l056_xli1m_utm09_2019_cog.tif"        
 [9] "s3://23cog/bc_093l058_xli1m_utm09_2019.tif"            
[10] "s3://23cog/bc_093m032_xli1m_utm09_2019_cog2.tif"       
[11] "s3://23cog/ept.json"                                   
[12] "s3://23cog/glen_valle_lidar_2019_cog.tif"              
[13] "s3://23cog/xli1m_utm09_2019_093L04811.tif"             
[14] "s3://23cog/xli1m_utm09_2019_093L04812.tif"             
[15] "s3://23cog/xli1m_utm09_2019_093L04813.tif"             
[16] "s3://23cog/xli1m_utm09_2019_093L04814.tif"             
[17] "s3://23cog/xli1m_utm09_2019_093L04821.tif"             
[18] "s3://23cog/xli1m_utm09_2019_093L04822.tif"             
[19] "s3://23cog/xli1m_utm09_2019_093L04823.tif"             
[20] "s3://23cog/xli1m_utm09_2019_093L04824.tif"             
[21] "s3://23cog/xli1m_utm09_2019_093L04831.tif"             
[22] "s3://23cog/xli1m_utm09_2019_093L04832.tif"             
[23] "s3://23cog/xli1m_utm09_2019_093L04833.tif"             
[24] "s3://23cog/xli1m_utm09_2019_093L04834.tif"             
[25] "s3://23cog/xli1m_utm09_2019_093L04841.tif"             
[26] "s3://23cog/xli1m_utm09_2019_093L04842.tif"             
[27] "s3://23cog/xli1m_utm09_2019_093L04843.tif"             
[28] "s3://23cog/xli1m_utm09_2019_093L04844.tif"             
[29] "s3://23cog/private"                                    

Biuld a functshi to give us the actual https url. Sure there is a function somewhere already to do this but couldn’t find it.

Code
# Define your S3 path
s3_path <-  "s3://23cog/20210906lampreymoricetribv220230317.tif"

s3_path_to_https <- function(s3_path) {
  # Remove the 's3://' prefix
  path_without_prefix <- sub("^s3://", "", s3_path)
  
  # Split the path into bucket and key
  parts <- strsplit(path_without_prefix, "/", fixed = TRUE)[[1]]
  bucket_name <- parts[1]
  object_key <- paste(parts[-1], collapse = "/")
  
  # Construct the HTTPS URL
  https_url <- sprintf("https://%s.s3.amazonaws.com/%s", bucket_name, object_key)
  return(https_url)
}

url <- s3_path_to_https(s3_path)
print(url)
[1] "https://23cog.s3.amazonaws.com/20210906lampreymoricetribv220230317.tif"

Since we already have some valid COGs up on AWS we will link to one to be sure it works.

Code
  leaflet::leaflet() |>
    leaflet::addTiles() |>
    leafem:::addCOG(
      url = url
      , group = "COG"
      , resolution = 512
      , autozoom = TRUE
    )

Dope.

When we work with the paws package - when we want to get help we use ?s3 and navigate in from there. This next section doesn’t work yet so we turn eval = F and get on with our lives. Would like to activate this policy from R but can’t seem to pull it of yet. To be continued - maybe.

Code
my_bucket_name = "23cog" 

# Define the CORS policy as a list
my_policy <- list(
    list(
        AllowedHeaders = list("*"),  # Must be a list
        AllowedMethods = list("GET"),  # Must be a list
        AllowedOrigins = list("*"),  # Must be a list
        ExposeHeaders = list(
            "x-amz-server-side-encryption",
            "x-amz-request-id",
            "x-amz-id-2"
        ),  # Must be a list
        MaxAgeSeconds = 3000  # Must be a number
    )
)


# Convert the policy list to a pretty JSON format
my_policy_json <- jsonlite::toJSON(my_policy, pretty = TRUE, auto_unbox = TRUE)


# Set the CORS configuration directly using the list
paws::s3()$put_bucket_cors(
    Bucket = my_bucket_name,
    CORSConfiguration = my_policy_json  # Pass the list of rules directly
)