2

This question uses code based off @Kel Markert https://code.earthengine.google.com/349615d7802d59f677181bef0badad9f

I am attempting to get a maximum monthly NDVI value from 6 small polygons over a number of years from Sentinel 2 in Google Earth Engine and export to CSV. The main difference between my code and his is the use of Sentinel 2 data instead of landsat. When I run my exact code with landsat image collection instead of Sentinel it works, but I cannot figure out why.

When I run it with the Sentinel 2 data I get the error

"FeatureCollection (Error) Error in map(ID=00000000000000000001): Dictionary.get: Dictionary does not contain key: NDVI."

This leads me to believe that there must be a problem with my band names, but when I inspect them after selecting out only the NDVI band into var NDVI_only, there is only 1 band named "NDVI" just like I would expect...

Link to my code

Link to my feature class

var geometry = ee.FeatureCollection("users/marshallthewolf/valley_bottoms");
print(geometry);

Map.centerObject(geometry);

// Filter by Geo and Growing days
var S2_SR = ee.ImageCollection('COPERNICUS/S2_SR')
            .filterDate('2018-05-01', '2020-10-01') //first BDA year
            .filterBounds(geometry) // filter to ROI
            .map(function(image){return image.clip(geometry)}) //iterate and clips roi over whole collection
            .filterMetadata('CLOUDY_PIXEL_PERCENTAGE',"less_than", 25); 
// Add NDVI band
var addNDVI = function(image) {
  var ndvi = image.normalizedDifference(['B8', 'B4']).rename('NDVI');
  return image.addBands(ndvi);
};
// Apply across whole collection 
var S2_NDVI = S2_SR.map(addNDVI);

// Select out only NDVI band
var NDVI_only = ee.ImageCollection(S2_NDVI.select(["NDVI"], ["NDVI"]));
print(NDVI_only)

// Update table and export -----------------------------------------------//
var startDate = ee.Date('2018-05-01'); // set analysis start time
var endDate = ee.Date('2020-10-01'); // set analysis end time

var bandName = ee.Image(NDVI_only.first()).bandNames().get(0);
print(bandName)

// calculate the number of months to process
var nMonths = ee.Number(endDate.difference(startDate,'month')).round();

// get a list of time strings to pass into a dictionary later on
var monList = ee.List(ee.List.sequence(0,nMonths).map(function (n){
  return startDate.advance(n,'month').format('YYYMMdd');
}))
print(monList)

var result = geometry.map(function(feature){
  // map over each month
  var timeSeries = ee.List.sequence(0,nMonths).map(function (n){
    // calculate the offset from startDate
    var ini = startDate.advance(n,'month');
    // advance just one month
    var end = ini.advance(1,'month');
    // filter and reduce
    var data = NDVI_only.filterDate(ini,end).mean().reduceRegion({
      reducer: ee.Reducer.mean(),
      geometry: feature.geometry(),
      scale: 1000
    });
    // get the value and check that it has data
    var val = ee.Number(data.get(bandName));
    val = ee.Number(ee.Algorithms.If(val,val,-999));
    // return zonal mean
    return val;
  });
  // create new dictionary with date strings and values
  var timeDict = ee.Dictionary.fromLists(monList,ee.List(timeSeries));
  // return feature with a timeseries property and results
  return feature.set(timeDict);
});

// print to see if it is doing what we expect...
print(result);
0

The error occurs as a result of no images being present for the time span and cloud cover filter. You can check how much images are present for each month using for example:

  var numbOfImages = ee.List.sequence(0,nMonths).map(function (n){
    var ini = startDate.advance(n,'month');
    var end = ini.advance(1,'month');
    return NDVI_only.filterDate(ini,end).size();
  });

You will see that there are multiple date ranges without imagery present. You could choose to extent your time span and cloud cover filter. However, noting the ee.Algorithms.If() within your code, you are possibly trying to assign a non-valid value to missing images. An idea to to that would be:

var timeSeries = ee.FeatureCollection(ee.List.sequence(0,nMonths).map(function (n){
  // calculate the offset from startDate
  var ini = startDate.advance(n,'month');
  var end = ini.advance(1,'month');

  // check if there are images in time span
  var image = ee.Image(ee.Algorithms.If({
    condition: NDVI_only.filterDate(ini,end).size().gte(1), 
          // the valid NDVI image
    trueCase: NDVI_only.filterDate(ini,end).mean(), 
          // make a constant non-valid NDVI value image
    falseCase: ee.Image(-999).rename(bandName) 
  }));
  
  // filter and reduce (returns featureCollection)
  var data = image.reduceRegions({
    reducer: ee.Reducer.mean(),
    collection: geometry,
    scale: 1000
  })
    // add the date of the image to each feature
    .map(function(feat){
      return feat.set('system:time_start', ini.millis(),
                      'system:time_end', end.millis(),
                      'numbImages', NDVI_only.filterDate(ini,end).size(),
                      'YYYMMdd', ini.format('YYYMMdd'));
    });
    
  return data;
})).flatten();

I would advice using reduceRegions for reducing multiple geometries in once. See also some other suggestion in your code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.