[GIS] reduceRegion() not working in Earth Engine

google-earth-enginejavascript

I have code here that is supposed to take a feature collection of points (defined as "table") and get the values of those points where they intersect imagery from Landsat 5 surface reflectance filtered for 1985 in the month of March. This code works for other datasets in Earth Engine's catalog, but when exporting a CSV of the point values with Landsat imagery (Landsat 4 and 5 are what I tested), I get a CSV with no columns pertaining to the values for each band at each point. I am not sure what I am doing wrong.

The function I wrote ("getImgCoords") is essentially a nested map() using the amount of images in the image collection and the amount of coordinates per image as the length at which each loop will continue on for. For example, after I do all the clipping ("icClip"), I grab each image and check for how many points exist for each image ("getImgCoords"). Then for each point from that image, I use reduceRegion() to get the band values for that point and merge that dictionary with another dictionary of metadata from that image ("getMetadata"). I create a feature collection out of this. Try to disregard the variables "size_equal" and "end." I had to create a conditional statement so the function knows how to distinguish between a list of 2 sets of coordinates of length 2 (basically a list of a list) versus 1 set of coordinates of length 2.

This is the same code from another question regarding how to convert it over to python. I haven't changed the way I use "sequence" and "sequence2" to map things like that post, so apologies in advance!

Another note: I am getting this message whenever I try to print a variable in the "getImgCoords" function: "internal error: function argument not initialized." Not sure what this could mean since I do use my function arguments.

UPDATE: Just figured out it has something to do with my cloud masking. Removing the call for "cloudMaskL457" makes reduceRegion() work, but I am still unsure as to why.

var LANDSAT5 = LANDSAT5 = ee.ImageCollection("LANDSAT/LT05/C01/T1_SR");
var table = ee.FeatureCollection("users/adrianom/CAN_AK_Coordinates_sub1");     
var SearchArea = table;
var SearchAreaGeom = SearchArea.geometry(); 



var cloudMaskL457 = function(image) {
  var qa = image.select('pixel_qa');
  // If the cloud bit (5) is set and the cloud confidence (7) is high
  // or the cloud shadow bit is set (3), then it's a bad pixel.
  var cloud = qa.bitwiseAnd(1 << 5)
          .and(qa.bitwiseAnd(1 << 7))
          .or(qa.bitwiseAnd(1 << 3))
  // Remove edge pixels that don't occur in all bands
  var mask2 = image.mask().reduce(ee.Reducer.min());
  return image.updateMask(cloud.not()).updateMask(mask2);
};

var landsat5_filtered = LANDSAT5.filter(ee.Filter.calendarRange(1985, 1985, 'year'))
                                .filter(ee.Filter.calendarRange(3, 3, 'month')).filterBounds(SearchAreaGeom).map(cloudMaskL457);

var landsat5_select = landsat5_filtered.select(['B1', 'B2', 'B3', 'B4', 'B5', 'B7'], 
                                               ['Blue', 'Green', 'Red', 'NIR', 'SWIR1', 'SWIR2']);

// Clip collection to search area, more precisely than filterBounds above.
var icClip = landsat5_select.map(function(img){
   var i = img.clip(SearchAreaGeom);
   return(i);
});

print(icClip);

var icClip_len = icClip.size();
var col_list = icClip.toList(icClip_len);


var sequence = ee.List.sequence(0, icClip_len.subtract(1));

var getImgCoords = sequence.map(function(i){
   var img = ee.Image(col_list.get(i));
   var coords = img.geometry().coordinates();

   // Create a list of a list and flatten. This is ONLY made for use as a conditional statement.
   var coords_list = ee.List([coords]);
   var coords_flatten = coords_list.flatten();

    // Boolean to check if the coordinates received are a set of coordinates (of length 2) or a list of a list of coordinates.
   var size_equal = ee.Algorithms.IsEqual(coords_flatten.size(), ee.Number(2));
   var coords_len = coords.size();
   var end = ee.Algorithms.If(size_equal, coords_len.subtract(2), coords_len.subtract(1));

   var sequence2 = ee.List.sequence(0, end);

   var getMetadata = sequence2.map(function(j){
      var coor = ee.Algorithms.If(ee.Algorithms.IsEqual(coords_flatten.size(), ee.Number(2)), coords, coords.get(j));
      var geom = ee.Geometry.Point(coor);
      var reduce = img.reduceRegion(ee.Reducer.mean(), geom, 30);

      var dictionary = ee.Dictionary({
         x: ee.List(coor).get(0),
         y: ee.List(coor).get(1),
         image_id: img.id(),
         start_date: ee.Date(img.get('system:time_start')),
         WRS_PATH: img.get('WRS_PATH'),
         WRS_ROW: img.get('WRS_ROW')
       });

       var combine_dict = reduce.combine(dictionary);
       var feature = ee.Feature(geom, combine_dict);
       return(feature);
    });


    return(getMetadata);

 });

 var flat = getImgCoords.flatten();
 var fc = ee.FeatureCollection(flat);
 print(fc);

 Export.table.toDrive({
 collection: fc,
 description: 'Landsat5',
 folder: 'Landsat5',
 fileFormat: 'CSV'
 });

Best Answer

Here is an alternative approach that avoids nested map() calls, ee.Image.clip(), ee.Algorithms.If(), and accumulating results with a dictionary.

Modify the cloud masking function to add a band cloud_flag that indicates whether or not there is a cloud.

// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0

var cloudMaskL457 = function(image) {
  // If the cloud bit (5) is set and the cloud confidence (7) is high
  // or the cloud shadow bit is set (3), then it's a cloudy pixel.
  var qa = image.select('pixel_qa');
  var cloud = qa.bitwiseAnd(1 << 5)
          .and(qa.bitwiseAnd(1 << 7))
          .or(qa.bitwiseAnd(1 << 3)).rename('cloud_flag');

  // Add a band that indicates if valid data exists in all bands.
  var validData = image.mask().reduce(ee.Reducer.min());

   // Mask out cloudy pixels and invalid pixels.
  new_image = image.updateMask(cloud.not()).updateMask(validData);

  // Add a new band that indicates whether there is a cloud. Note that this
  // new bands mask will be the same as the 'pixel_qa' band.
  var new_image = new_image.addBands(cloud);

  return new_image;
};

Map the function across a filtered collection of Landsat images:

var landsat5_filtered = LANDSAT5.filter(ee.Filter.calendarRange(1985, 1985, 'year'))
                                .filter(ee.Filter.calendarRange(3, 3, 'month'))
                                .filterBounds(table)
                                .map(cloudMaskL457);

Next define a function that processes each image, extracting values that correspond with the sample points. This function is then mapped over all of the images to create a feature collection of sampled values.

var reduceRegionsForImage = function(img) {
  // Add a property representing the time of the image.
  img = img.addBands(ee.Image.constant(img.get('system:time_start')).rename('time'));

  var meanFeatures = img.reduceRegions({
    collection: table,
    reducer: ee.Reducer.mean(),
    scale: 30,
  });
  return meanFeatures;
};
var extractValues = landsat5_filtered.map(reduceRegionsForImage).flatten();

The feature collection extractValues will contain many entries with null properties, because many of the images do not have values defined at the sample point locations. We can filter out those features by filtering for pixels that have non-null cloud_flag values (which includes the areas where clouds occur).

extractValues = extractValues.filter(ee.Filter.notNull(['cloud_flag']));

Next, select the attributes that you want to export, and rename them as needed.

extractValues = extractValues.select(
    ['ID', 'Lat', 'Lon', 'cloud_flag', 'time',
     'B1', 'B2', 'B3', 'B4', 'B5', 'B7'], 
    ['ID', 'Lat', 'Lon', 'cloud_flag', 'time',
     'Blue', 'Green', 'Red', 'NIR', 'SWIR1', 'SWIR2']
);

Finally, export the feature collection to create a CSV file.

Export.table.toDrive({
  collection: extractValues,
  description: 'export_sampled_values',
  folder: 'EE Exports',
  fileFormat: 'CSV'
});

Here is a link to the complete script: https://code.earthengine.google.com/55b196d9d01154dddcda362eeb90e041

Related Question