[GIS] “Batch processing” data of multiple ROIs in Google Earth Engine

data collectiongoogle-earth-enginesentinel-2

I have written a rather simple script for data selection and subsequent export in GEE:

//Create region of interest -> Should be replaced by manual selection of ROIs or ROI center points and subsequent processing of all ROIs by the process implemented for a single ROI in the following
var roi = ee.Geometry.Rectangle([10, 45, 11, 46]);
//Load Sentinel-2 image collection
var coll_s2 = ee.ImageCollection("COPERNICUS/S2");
//Filter for 10 most recent almost cloud-free images
var coll_s2_filtered = coll_s2.filterBounds(roi)
                      .filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', 5))
                      .sort('system:time_start',false)
                      .limit(10);
//Create RGB composite
var s2_median = coll_s2_filtered.reduce(ee.Reducer.median()).select(['B4_median','B3_median','B2_median']);
//Export image
Export.image.toDrive({
  image: s2_median,
  description: 'Sentinel2',
  scale: 10,
  region: roi
});

What I actually want to do is to avoid hardcoding of a single ROI. Instead, I'd like to select an arbitrary number of rectangular ROIs around the globe and do the same export process for every ROI. I am thinking about doing this by selecting individual marker points and simply adding a buffer around their coordinates in order to achieve rectangular ROIs. However, I don't seem to be able to wrap my head around two critical things:

  1. How to do all this in a batch-processing manner (i.e. as a mapping over a feature collection or as an iteration through the elements of a list etc.)?
  2. How to extract the coordinates from the manually selected marker points in the first place?

I am kind of stuck here.

EDIT: I am now following an advice that suggested to make use of FeatureCollections, mapping, buffers and bounding boxes. I first tried what I want to do in a simple minimal example that actually worked:

var listOfMarkers = /* color: #98ff00 */ee.FeatureCollection(
    [ee.Feature(
        ee.Geometry.Point([6.8115234375, 60.88836817267309]),
        {
          "system:index": "0"
        }),
    ee.Feature(
        ee.Geometry.Point([15.1611328125, 57.469327688204295]),
        {
          "system:index": "1"
        }),
    ee.Feature(
        ee.Geometry.Point([27.0703125, 62.95584745563692]),
        {
          "system:index": "2"
        })]);

     // A function to create a bounding box out of a point
     var createBBox = function(featurePoint) {
       var rectangle = featurePoint.buffer(100000).bounds();
       return ee.Feature(rectangle);
     };

     var roi = listOfMarkers.map(createBBox);
     Map.addLayer(roi)
     print(roi)

     var coll_s2 = ee.ImageCollection("COPERNICUS/S2");
     //Filter for 10 most recent almost cloud-free images
     var coll_s2_filtered = coll_s2.filterBounds(roi)
                  .filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', 5))
                  .sort('system:time_start',false)
                  .limit(10);

For the 3 arbitrarily chosen points in the feature collection, this script would give me the 10 most recent Sentinel-2 images with a cloud coverage below 5%. However, as said before, I want to get these 10 images per every marker-defined ROI. So I tried to extend the script:

//Enter same listOfMarkers as above here...
//A function to create an Image Collection out of a marker point
var createImageCollection = function(featurePoint) {
  var rectangle = ee.Feature(featurePoint.buffer(100000).bounds());
  var coll_s2 = ee.ImageCollection("COPERNICUS/S2");
     //Filter for 10 most recent almost cloud-free images
     var coll_s2_filtered = coll_s2.filterBounds(rectangle)
                  .filter(ee.Filter.lte('CLOUDY_PIXEL_PERCENTAGE', 5))
                  .sort('system:time_start',false)
                  .limit(10);

     return coll_s2_filtered;
  };

  var s2data = listOfMarkers.map(createImageCollection);
  Map.addLayer(s2data)
  print(s2data)

Now, unfortunately, this second script gives me an error: "FeatureCollection (Error), Error in map(ID=2): Feature, argument 'geometry': Invalid type. Expected: Geometry. Actual: Feature."

This seems kind of strange, as I am also using ee.Feature for roi in the first example, and it worked nonetheless. Can anybody shed light on this, or explain how I can convert the rectangle created by

  var rectangle = ee.Feature(featurePoint.buffer(100000).bounds());

into an ee.Geometry.Rectangle object?

Best Answer

Here's an example that hopefully does something like what you want, but you'll need to modify it to meet your requirements for "manually selected marker points" and Sentinel 2.

In general, do NOT use for-loops or getInfo(). To be clear, DO NOT USE FOR LOOPS unless you have a very good reason to be doing so. If you don't know, use map() for all the reasons described here, here, here and here. (And check the same guides for why to NOT use getInfo() or convert to a list as I've done here.) The reason it's OK to do this here (necessary, in fact) is because Export is a client side function and you can't use a client function in map().

// Get some imagery to play with.
var landsat = ee.ImageCollection("LANDSAT/LC08/C01/T1")
    .filterDate('2016-01-01', '2017-01-01');

var composite = ee.Algorithms.Landsat.simpleComposite({
  collection: landsat,
  asFloat: true
});

var rgbVis = {bands: ["B4", "B3", "B2"], min:0, max: 0.3};
Map.addLayer(composite, rgbVis, "RGB");

// This is a hacky way to get a pixel grid at arbitrary resolution.
var pixels = ee.Image.random().multiply(10000000).toInt32()
    .reduceToVectors({
      reducer: ee.Reducer.countEvery(),
      geometry: Map.getBounds(true),
      geometryType: 'bb' ,
      eightConnected: false,
      scale: 20000,
      crs: 'EPSG:4326'
    });
Map.addLayer(pixels);

// Only do this is you have a few regions.  Not suitable for 
// large feature collections.
var pixelsList = pixels.toList(pixels.size());

// This is one of the few places in the EE API where you need 
// a for-loop and a getInfo() call.  Export is a client function.
for (var i=0; i<pixels.size().getInfo(); i++) {
  Export.image.toDrive({
    image: composite, 
    description: 'foo_' + i, 
    fileNamePrefix: 'foo_' + i, 
    region: ee.Feature(pixelsList.get(i)).geometry(), 
    scale: 30, 
  });
}

If you really need precise control over the export regions, you can make lists of coordinates and turn those into a collection of the ROIs you want to export. All that aside, you still need to click 'Run' on the exports. If you want it completely automated, use the Python API and ee.batch.Export followed by task.start().