## ----setup, include=FALSE-----------------------------------------------------
knitr::opts_chunk$set(eval = FALSE)

## -----------------------------------------------------------------------------
#  # Won't work.
#  for (i in seq_len(table$size())) {
#    print('No!')
#  }

## -----------------------------------------------------------------------------
#  l8_ts <- sprintf(
#    "LANDSAT/LC08/C01/T1/LC08_044034_%s",
#    c("20140318", "20140403","20140419","20140505")
#  )
#  
#  display_l8ts <- list()
#  for (l8 in l8_ts) {
#    ee_l8 <- ee$Image(l8)
#    display_l8ts[[l8]] <- Map$addLayer(ee_l8)
#  }
#  
#  Map$centerObject(ee_l8)
#  Reduce('+', display_l8ts)

## -----------------------------------------------------------------------------
#  table <- ee$FeatureCollection('USDOS/LSIB_SIMPLE/2017')
#  
#  # Error:
#  foobar <- table$map(function(f) {
#    print(f); # Can't use a client function here.
#    # Can't Export, either.
#  })

## -----------------------------------------------------------------------------
#  table <- ee$FeatureCollection('USDOS/LSIB_SIMPLE/2017')
#  
#  # Do something to every element of a collection.
#  withMoreProperties = table$map(function(f) {
#    # Set a property.
#    f$set("area_sq_meters", f$area())
#  })
#  print(withMoreProperties$first()$get("area_sq_meters")$getInfo())

## -----------------------------------------------------------------------------
#  table <- ee$FeatureCollection('USDOS/LSIB_SIMPLE/2017');
#  
#  # Do NOT do this!!
#  list <- table$toList(table$size())
#  print(list$get(13)$getInfo()) # User memory limit exceeded.

## -----------------------------------------------------------------------------
#  print(table$filter(ee$Filter$eq('country_na', 'Niger'))$first()$getInfo())

## -----------------------------------------------------------------------------
#  table <- ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')
#  
#  # Do NOT do this!
#  veryBad = table$map(function(f) {
#    ee$Algorithms$If(
#      condition = ee$String(f$get('country_na'))$compareTo('Chad')$gt(0),
#      trueCase = f,      # Do something.
#      falseCase = NULL   # Do something else.
#    )
#  }, TRUE)
#  print(veryBad$getInfo()) # User memory limit exceeded.
#  
#  # If() may evaluate both the true and false cases.

## -----------------------------------------------------------------------------
#  print(table$filter(ee$Filter$eq('country_na', 'Chad')))

## -----------------------------------------------------------------------------
#  l8sr <- ee$ImageCollection("LANDSAT/LC08/C01/T1_SR")
#  sf <- ee$Geometry$Point(c(-122.405, 37.786))
#  Map$centerObject(sf, 13)
#  
#  # A reason to reproject - counting pixels and exploring interactively.
#  image <- l8sr$filterBounds(sf)$
#    filterDate("2019-06-01", "2019-12-31")$
#    first()
#  Map$addLayer(image, list(bands = "B10", min = 2800, max = 3100), "image")
#  
#  hotspots <- image$select("B10")$
#    gt(3100)$
#    selfMask()$
#    rename("hotspots")
#  
#  objectSize <- hotspots$connectedPixelCount(256)
#  # Beware of reproject!  Don't zoom out on reprojected data.
#  reprojected <- objectSize$reproject(hotspots$projection())
#  Map$addLayer(objectSize, list(min = 1, max = 256), "Size No Reproject", FALSE) +
#  Map$addLayer(reprojected, list(min = 1, max = 256), "Size Reproject", FALSE)

## -----------------------------------------------------------------------------
#  images <- ee$ImageCollection("COPERNICUS/S2_SR")
#  sf <- ee$Geometry$Point(c(-122.463, 37.768))
#  
#  # Expensive function to reduce the neighborhood of an image.
#  reduceFunction <- function(image) {
#    image$reduceNeighborhood(
#      reducer = ee$Reducer$mean(),
#      kernel = ee$Kernel$square(4)
#    )
#  }
#  
#  bands <- list("B4", "B3", "B2")
#  # Select and filter first!
#  reasonableComputation <- images$select(bands)$
#    filterBounds(sf)$
#    filterDate("2018-01-01", "2019-02-01")$
#    filter(ee$Filter$lt("CLOUDY_PIXEL_PERCENTAGE", 1))$
#    aside(ee_print)$ # Useful for debugging.
#    map(reduceFunction)$
#    reduce('mean')$
#    rename(bands)
#  
#  viz <- list(bands = bands, min = 0, max = 10000)
#  Map$addLayer(reasonableComputation, viz, "resonableComputation")

## -----------------------------------------------------------------------------
#  l8sr <- ee$ImageCollection("LANDSAT/LC08/C01/T1_SR")
#  sf <- ee$Geometry$Point(c(-122.40554461769182, 37.786807309873716))
#  aw3d30 <- ee$Image("JAXA/ALOS/AW3D30_V1_1")
#  
#  Map$centerObject(sf, 7)
#  
#  image <- l8sr$filterBounds(sf)$filterDate("2019-06-01", "2019-12-31")$first()
#  vis <- list(bands = c("B4", "B3", "B2"), min = 0, max = 3000)
#  
#  Map$addLayer(image, vis, "image", FALSE)
#  
#  mask <- aw3d30$select("AVE")$gt(300)
#  Map$addLayer(mask, {}, 'mask', FALSE)
#  
#  # NO!  Don't do this!
#  badMask <- image$mask(mask)
#  Map$addLayer(badMask, vis, "badMask")
#  
#  goodMask <- image.updateMask(mask)
#  Map$addLayer(goodMask, vis, "goodMask", FALSE)

## -----------------------------------------------------------------------------
#  image <- ee$Image('COPERNICUS/S2/20150821T111616_20160314T094808_T30UWU')
#  
#  # Get mean and SD in every band by combining reducers.
#  stats <- image$reduceRegion(
#    reducer = ee$Reducer$mean()$combine(
#      reducer2 = ee$Reducer$stdDev(),
#      sharedInputs = TRUE
#    ),
#    geometry = ee$Geometry$Rectangle(c(-2.15, 48.55, -1.83, 48.72)),
#    scale = 10,
#    bestEffort = TRUE # Use maxPixels if you care about scale.
#  )
#  
#  print(stats$getInfo())
#  
#  # Extract means and SDs to images.
#  meansImage <- stats$toImage()$select('.*_mean')
#  sdsImage <- stats$toImage()$select('.*_stdDev')

## -----------------------------------------------------------------------------
#  link <- '86836482971a35a5e735a17e93c23272'
#  task <- ee$batch$Export$table$toDrive(
#    collection = ee$FeatureCollection(ee$Feature(NULL, stats)),
#    description = paste0("exported_stats_demo_", link),
#    fileFormat = "CSV"
#  )
#  
#  # Using rgee I/O
#  task <- ee_table_to_drive(
#    collection = ee$FeatureCollection(ee$Feature(NULL, stats)),
#    description = paste0("exported_stats_demo_", link),
#    fileFormat = "CSV"
#  )
#  task$start()
#  ee_monitoring(task)
#  
#  exported_stats <- ee_drive_to_local(task = task,dsn = "exported_stats.csv")
#  read.csv(exported_stats)

## -----------------------------------------------------------------------------
#  table <- ee$FeatureCollection('USDOS/LSIB_SIMPLE/2017')
#  l8sr <- ee$ImageCollection('LANDSAT/LC08/C01/T1_SR')
#  
#  chad <- table$filter(ee$Filter$eq('country_na', 'Chad'))$first()
#  
#  # Do NOT clip unless you need to.
#  unnecessaryClip <- l8sr$
#    select('B4')$                           # Good.
#    filterBounds(chad$geometry())$          # Good.
#    filterDate('2019-01-01', '2019-12-31')$ # Good.
#    map(function(image) {
#      image$clip(chad$geometry())   # NO! Bad! Not necessary.
#    })$
#    median()$
#    reduceRegion(
#      reducer = ee$Reducer$mean(),
#      geometry = chad$geometry(),
#      scale = 30,
#      maxPixels = 1e10
#    )
#  print(unnecessaryClip$getInfo())

## -----------------------------------------------------------------------------
#  noClipNeeded <- l8sr$
#    select('B4')$                          # Good.
#    filterBounds(chad$geometry())$          # Good.
#    filterDate('2019-01-01', '2019-12-31')$ # Good.
#    median()$
#    reduceRegion(
#      reducer = ee$Reducer$mean(),
#      geometry = chad$geometry(), # Geometry is specified here.
#      scale = 30,
#      maxPixels = 1e10
#    )
#  print(noClipNeeded$getInfo())

## -----------------------------------------------------------------------------
#  ecoregions <- ee$FeatureCollection('RESOLVE/ECOREGIONS/2017')
#  image <- ee$Image('JAXA/ALOS/AW3D30_V1_1')
#  
#  complexCollection <- ecoregions$
#    filter(
#      ee$Filter$eq(
#        'BIOME_NAME',
#        'Tropical & Subtropical Moist Broadleaf Forests'
#      )
#    )
#  
#  Map$addLayer(complexCollection, {}, 'complexCollection')
#  
#  clippedTheRightWay <- image$select('AVE')$
#    clipToCollection(complexCollection)
#  
#  Map$addLayer(clippedTheRightWay, {}, 'clippedTheRightWay', FALSE)

## -----------------------------------------------------------------------------
#  ecoregions <- ee$FeatureCollection('RESOLVE/ECOREGIONS/2017')
#  image <- ee$Image('JAXA/ALOS/AW3D30_V1_1')
#  complexCollection <- ecoregions$filter(
#    ee$Filter$eq('BIOME_NAME', 'Tropical & Subtropical Moist Broadleaf Forests')
#  )
#  
#  clippedTheRightWay <- image$select('AVE')$clipToCollection(complexCollection)
#  Map$addLayer(clippedTheRightWay, {}, 'clippedTheRightWay')
#  
#  reduction <- clippedTheRightWay$reduceRegion(
#    reducer = ee$Reducer$mean(),
#    geometry = ee$Geometry$Rectangle(
#      coords = c(-179.9, -50, 179.9, 50),  # Almost global.
#      geodesic = FALSE
#    ),
#    scale = 30,
#    maxPixels = 1e12
#  )
#  
#  print(reduction$getInfo()) # If this times out, export it.

## -----------------------------------------------------------------------------
#  ecoregions <- ee$FeatureCollection("RESOLVE/ECOREGIONS/2017")
#  complexCollection <- ecoregions$limit(10)
#  
#  Map$centerObject(complexCollection)
#  Map$addLayer(complexCollection)
#  
#  expensiveOps <- complexCollection$map(function(f) {
#    f$buffer(10000, 200)$bounds(200)
#  })
#  
#  Map$addLayer(expensiveOps, {}, 'expensiveOps')

## -----------------------------------------------------------------------------
#  etopo <- ee$Image('NOAA/NGDC/ETOPO1')
#  
#  # Approximate land boundary.
#  bounds <- etopo$select(0)$gt(-100)
#  
#  # Non-geodesic polygon.
#  almostGlobal <- ee$Geometry$Polygon(
#    coords = list(
#      c(-180, -80),
#      c(180, -80),
#      c(180, 80),
#      c(-180, 80),
#      c(-180, -80)
#    ),
#    proj = "EPSG:4326",
#    geodesic = FALSE
#  )
#  
#  Map$addLayer(almostGlobal, {}, "almostGlobal")
#  
#  vectors <- bounds$selfMask()$reduceToVectors(
#    reducer = ee$Reducer$countEvery(),
#    geometry = almostGlobal,
#    # Set the scale to the maximum possible given
#    # the required precision of the computation.
#    scale = 50000
#  )
#  
#  Map$addLayer(vectors, {}, "vectors")

## -----------------------------------------------------------------------------
#  etopo <- ee$Image('NOAA/NGDC/ETOPO1')
#  mod11a1 <- ee$ImageCollection('MODIS/006/MOD11A1')
#  
#  # Approximate land boundary.
#  bounds <- etopo$select(0)$gt(-100)
#  
#  # Non-geodesic polygon.
#  almostGlobal <- ee$Geometry$Polygon(
#    coords = list(c(-180, -80), c(180, -80), c(180, 80), c(-180, 80), c(-180, -80)),
#    proj = "EPSG:4326",
#    geodesic = FALSE
#  )
#  
#  lst <- mod11a1$first()$select(0)
#  means <- bounds$selfMask()$addBands(lst)$reduceToVectors(
#    reducer = ee$Reducer$mean(),
#    geometry = almostGlobal,
#    scale = 1000,
#    maxPixels = 1e10
#  )
#  print(means$limit(10)$getInfo())

## -----------------------------------------------------------------------------
#  aw3d30 <- ee$Image("JAXA/ALOS/AW3D30_V1_1")
#  
#  # Make a simple binary layer from a threshold on elevation.
#  mask <- aw3d30$select("AVE")$gt(300)
#  Map$setCenter(-122.0703, 37.3872, 11)
#  Map$addLayer(mask, {}, "mask")
#  
#  # Distance in pixel units.
#  distance <- mask$fastDistanceTransform()$sqrt()
#  # Threshold on distance (three pixels) for a dilation.
#  dilation <- distance$lt(3)
#  Map$addLayer(dilation, {}, "dilation")
#  
#  # Do the reverse for an erosion.
#  notDistance <- mask$Not()$fastDistanceTransform()$sqrt()
#  erosion <- notDistance$gt(3)
#  Map$addLayer(erosion, {}, 'erosion')

## -----------------------------------------------------------------------------
#  l8raw <- ee$ImageCollection('LANDSAT/LC08/C01/T1_RT')
#  composite <- ee$Algorithms$Landsat$simpleComposite(l8raw)
#  
#  bands <- c('B4', 'B3', 'B2')
#  
#  optimizedConvolution <- composite$select(bands)$reduceNeighborhood(
#    reducer = ee$Reducer$mean(),
#    kernel = ee$Kernel$square(3),
#    optimization = "boxcar" # Suitable optimization for mean.
#  )$rename(bands)
#  
#  viz <- list(bands = bands, min = 0, max = 72)
#  Map$setCenter(-122.0703, 37.3872, 11)
#  Map$addLayer(composite, viz, "composite") +
#  Map$addLayer(optimizedConvolution, viz, "optimizedConvolution")

## -----------------------------------------------------------------------------
#  l8raw <- ee$ImageCollection('LANDSAT/LC08/C01/T1_RT')
#  composite <- ee$Algorithms$Landsat$simpleComposite(l8raw)
#  labels <- ee$FeatureCollection('projects/google/demo_landcover_labels')
#  
#  # No!  Not necessary.  Don't do this:
#  labels <- labels$map(function(f){f$buffer(100000, 1000)})
#  
#  bands <- c('B2', 'B3', 'B4', 'B5', 'B6', 'B7')
#  
#  training <- composite$select(bands)$sampleRegions(
#    collection = labels,
#    properties = list("landcover"),
#    scale = 30
#  )
#  
#  classifier <- ee$Classifier$smileCart()$train(
#    features = training,
#    classProperty = "landcover",
#    inputProperties = bands
#  )
#  
#  print(classifier$explain()) # Computed value is too large

## -----------------------------------------------------------------------------
#  l8raw <- ee$ImageCollection("LANDSAT/LC08/C01/T1_RT")
#  composite <- ee$Algorithms$Landsat$simpleComposite(l8raw)
#  labels <- ee$FeatureCollection("projects/google/demo_landcover_labels")
#  
#  # Increase the data a little bit, possibly introducing noise.
#  labels <- labels$map(function(f) {f$buffer(100, 10)})
#  
#  bands <- c('B2', 'B3', 'B4', 'B5', 'B6', 'B7')
#  
#  data <- composite$select(bands)$sampleRegions(
#    collection = labels,
#    properties = list("landcover"),
#    scale = 30
#  )
#  
#  # Add a column of uniform random numbers called 'random'.
#  data <- data$randomColumn()
#  
#  # Partition into training and testing.
#  training <- data$filter(ee$Filter$lt("random", 0.5))
#  testing <- data$filter(ee$Filter$gte("random", 0.5))
#  
#  # Tune the minLeafPopulation parameter.
#  minLeafPops <- ee$List$sequence(1, 10)
#  
#  accuracies <- minLeafPops$map(
#    ee_utils_pyfunc(
#      function(p) {
#        classifier <- ee$Classifier$smileCart(minLeafPopulation = p)$
#          train(
#            features = training,
#            classProperty = "landcover",
#            inputProperties = bands
#          )
#  
#        testing$
#          classify(classifier)$
#          errorMatrix("landcover", "classification")$
#          accuracy()
#      }
#    )
#  )
#  
#  minLeafPopulation_array <- accuracies$getInfo()
#  plot(
#    x = minLeafPopulation_array,
#    type = "b",
#    col = "blue",
#    lwd = 2,
#    ylab = "accuracy",
#    xlim = c(0,10),
#    xlab = "value",
#    main = "Hyperparameter tunning (minLeafPopulation)"
#  )
#  

## -----------------------------------------------------------------------------
#  image <- ee$Image('UMD/hansen/global_forest_change_2018_v1_6')
#  geometry <- ee$Geometry$Polygon(
#    coords = list(
#      c(-76.64069800085349, 5.511777325802095),
#      c(-76.64069800085349, -20.483938229362376),
#      c(-35.15632300085349, -20.483938229362376),
#      c(-35.15632300085349, 5.511777325802095)
#    ),
#    proj =  "EPSG:4326",
#    geodesic =  FALSE
#  )
#  
#  testRegion <- ee$Geometry$Polygon(
#    coords = list(
#      c(-48.86726050085349, -3.0475996402515717),
#      c(-48.86726050085349, -3.9248707849303295),
#      c(-47.46101050085349, -3.9248707849303295),
#      c(-47.46101050085349, -3.0475996402515717)
#    ),
#    proj = "EPSG:4326",
#    geodesic = FALSE
#  )
#  
#  # Forest loss in 2016, to stratify a sample.
#  loss <- image$select("lossyear")
#  loss16 <- loss$eq(16)$rename("loss16")
#  
#  # Cloud masking function.
#  maskL8sr <- function(image) {
#    cloudShadowBitMask <- bitwShiftL(1, 3)
#    cloudsBitMask <- bitwShiftL(1, 5)
#    qa <- image$select('pixel_qa')
#    mask <- qa$bitwiseAnd(cloudShadowBitMask)$eq(0)$
#      And(qa$bitwiseAnd(cloudsBitMask)$eq(0))
#  
#    image$updateMask(mask)$
#      divide(10000)$
#      select("B[0-9]*")$
#      copyProperties(image, list("system:time_start"))
#  }
#  
#  collection <- ee$ImageCollection("LANDSAT/LC08/C01/T1_SR")$map(maskL8sr)
#  
#  # Create two annual cloud-free composites.
#  composite1 <- collection$filterDate('2015-01-01', '2015-12-31')$median()
#  composite2 <- collection$filterDate('2017-01-01', '2017-12-31')$median()
#  
#  # We want a strtatified sample of this stack.
#  stack <- composite1$addBands(composite2)$float() # Export the smallest size possible.
#  
#  # Export the image.  This block is commented because the export is complete.
#  # link <- "0b8023b0af6c1b0ac7b5be649b54db06"
#  # desc <- paste0(ee_get_assethome(), "/Logistic_regression_stack_", link)
#  #
#  # #ee_image_info(stack)
#  # task <- ee_image_to_asset(
#  #   image = stack,
#  #   description = link,
#  #   assetId = desc,
#  #   region = geometry,
#  #   scale = 100,
#  #   maxPixels = 1e10
#  # )
#  
#  
#  # Load the exported image.
#  exportedStack <- ee$Image(
#    "projects/google/Logistic_regression_stack_0b8023b0af6c1b0ac7b5be649b54db06"
#  )
#  
#  # Take a very small sample first, to debug.
#  testSample <- exportedStack$addBands(loss16)$stratifiedSample(
#    numPoints = 1,
#    classBand = "loss16",
#    region = testRegion,
#    scale = 30,
#    geometries = TRUE
#  )
#  
#  print(testSample$getInfo()) # Check this in the console.
#  
#  # Take a large sample.
#  sample <- exportedStack$addBands(loss16)$stratifiedSample(
#    numPoints = 10000,
#    classBand = "loss16",
#    region = geometry,
#    scale = 30
#  )
#  
#  # Export the large sample...

## -----------------------------------------------------------------------------
#  s2 <- ee$ImageCollection("COPERNICUS/S2")$
#    filterBounds(ee$Geometry$Point(c(-2.0205, 48.647)))
#  
#  l8 <- ee$ImageCollection("LANDSAT/LC08/C01/T1_SR")
#  
#  joined <- ee$Join$saveAll("landsat")$apply(
#    primary = s2,
#    secondary = l8,
#    condition = ee$Filter$And(
#      ee$Filter$maxDifference(
#        difference = 1000 * 60 * 60 * 24, # One day in milliseconds
#        leftField = "system:time_start",
#        rightField = "system:time_start"
#      ),
#      ee$Filter$intersects(
#        leftField = ".geo",
#        rightField = ".geo"
#      )
#    )
#  )
#  
#  print(joined$first()$getInfo())

## -----------------------------------------------------------------------------
#  s2 <- ee$ImageCollection("COPERNICUS/S2")$
#    filterBounds(ee$Geometry$Point(c(-2.0205, 48.647)))
#  
#  l8 <- ee$ImageCollection("LANDSAT/LC08/C01/T1_SR")
#  
#  mappedFilter <- s2$map(function(image) {
#    date <- image$date()
#    landsat <- l8$
#      filterBounds(image$geometry())$
#      filterDate(date$advance(-1, "day"), date$advance(1, "day"))
#      # Return the input image with matching scenes in a property.
#    image$set(
#      list(
#        landsat = landsat,
#        size = landsat$size()
#      )
#    )
#  })$filter(ee$Filter$gt("size", 0))
#  
#  print(mappedFilter$first()$getInfo())

## -----------------------------------------------------------------------------
#  # Table of countries.
#  countriesTable <- ee$FeatureCollection("USDOS/LSIB_SIMPLE/2017")
#  
#  # Time series of images.
#  mod13a1 <- ee$ImageCollection("MODIS/006/MOD13A1")
#  
#  # MODIS vegetation indices (always use the most recent version).
#  band <- "NDVI"
#  imagery <- mod13a1$select(band)
#  
#  # Option 1: reduceRegions()
#  testTable <- countriesTable$limit(1) # Do this outside map()s and loops.
#  
#  data <- imagery$map(function(image) {
#    image$reduceRegions(
#      collection = testTable,
#      reducer = ee$Reducer$mean(),
#      scale = 500
#    )$map(function(f) {
#      f$set(
#        list(
#          time = image$date()$millis(),
#          date = image$date()$format()
#        )
#      )
#    })
#  })$flatten()
#  
#  print(data$first()$getInfo())
#  
#  # Option 2: mapped reduceRegion()
#  data <- countriesTable$map(function(feature) {
#    imagery$map(
#      function(image) {
#        ee$Feature(
#          feature$geometry()$centroid(100),
#          image$reduceRegion(
#            reducer = ee$Reducer$mean(),
#            geometry = feature$geometry(),
#            scale = 500
#          )
#        )$set(
#          list(
#            time = image$date()$millis(),
#            date = image$date()$format()
#          )
#        )$copyProperties(feature)
#      }
#    )
#  })$flatten()
#  
#  print(data$first()$getInfo())
#  
#  # Option 3: for-loop (WATCH OUT!)
#  size <- countriesTable$size()
#  print(size$getInfo()) # 312
#  
#  countriesList <- countriesTable$toList(1) # Adjust size.
#  data <- ee$FeatureCollection(list()) # Empty table.
#  
#  for (j in (seq_len(countriesList$length()$getInfo()) - 1)) {
#    feature <- ee$Feature(countriesList$get(j))
#    # Convert ImageCollection > FeatureCollection
#    fc <- ee$FeatureCollection(
#      imagery$map(
#        function(image) {
#          ee$Feature(
#            feature$geometry()$centroid(100),
#            image$reduceRegion(
#              reducer = ee$Reducer$mean(),
#              geometry = feature$geometry(),
#              scale = 500
#            )
#          )$set(
#            list(
#              time = image$date()$millis(),
#              date = image$date()$format()
#            )
#          )$copyProperties(feature)
#        }
#      )
#    )
#    data <- data$merge(fc)
#  }
#  print(data$first()$getInfo())

## -----------------------------------------------------------------------------
#  sentinel2 <- ee$ImageCollection("COPERNICUS/S2")
#  sf <- ee$Geometry$Point(c(-122.47555371521855, 37.76884708376152))
#  s2 <- sentinel2$
#    filterBounds(sf)$
#    filterDate("2018-01-01", "2019-12-31")
#  
#  withDoys <- s2$map(function(image) {
#    ndvi <- image$normalizedDifference(c("B4", "B8"))$rename("ndvi")
#    date <- image$date()
#    doy <- date$getRelative("day", "year")
#    time <- image$metadata("system:time_start")
#    doyImage <- ee$Image(doy)$
#      rename("doy")$
#      int()
#  
#    ndvi$
#      addBands(doyImage)$
#      addBands(time)$
#      clip(image$geometry()) # Appropriate use of clip.
#  })
#  
#  array <- withDoys$toArray()
#  timeAxis <- 0
#  bandAxis <- 1
#  
#  dedup <- function(array) {
#    time <- array$arraySlice(bandAxis, -1)
#    sorted <- array$arraySort(time)
#    doy <- sorted$arraySlice(bandAxis, -2, -1)
#    left <- doy$arraySlice(timeAxis, 1)
#    right <- doy$arraySlice(timeAxis, 0, -1)
#    mask <- ee$Image(ee$Array(list(list(1))))$
#      arrayCat(left$neq(right), timeAxis)
#    array$arrayMask(mask)
#  }
#  
#  deduped <- dedup(array)
#  
#  # Inspect these outputs to confirm that duplicates have been removed.
#  print(array$reduceRegion("first", sf, 10)$getInfo())
#  print(deduped$reduceRegion("first", sf, 10)$getInfo())

