Google Earth Engine – Error Generating Chart: User Memory Limit Exceeded

geocodinggoogle-earth-enginegoogle-earth-engine-javascript-apindvi

I want to generate a vegetation indices Time series chart and the AOI is not that big which might be the problem for the chart not being generated. Secondly, the CSV exporting keeps refusing to export and giving this error Error: Execution failed; out of memory.
Link to the code https://code.earthengine.google.com/?scriptPath=users%2Fmpalasimbarashe%2FProject1%3AkNDVI_Sample2

////// DEFINING THE GRASS LOCATION ////

var Grass = Grass.map(function(feature) { 
  return ee.Feature(feature.geometry(), {'id' : feature.id()})
   })
   
Map.addLayer(Grass,{color:'green'},'Vegetation')

///// FUNCTION TO MASK THE CLOUDS ////
function maskCloudAndShadows(image) {
  var cloudProb = image.select('MSK_CLDPRB');
  var snowProb = image.select('MSK_SNWPRB');
  var cloud = cloudProb.lt(5);
  var snow = snowProb.lt(5);
  var scl = image.select('SCL');
  var shadow = scl.eq(3); /// 3 = cloud shadow
  var cirrus = scl.eq(10); /// 10 = cirrus
  // Cloud probability less than 5% or cloud shadow classification
  var mask = (cloud.and(snow)).and(cirrus.neq(1)).and(shadow.neq(1));
  return image.updateMask(mask);
}

/// SELECTING THE DATE RANGE ////

var startDate =  new Date ('2020-11-01')
var endDate =  new Date ('2021-05-30')
var startDateMillis = startDate.getTime(); // FOR DATE VISUALIZATION
var endDateMillis = endDate.getTime(); // FOR DATE VISUALIZATION


//// COMPUTING THE KNDVI BAND/////
var addKNDVI = function(image){
  
  var RED = image.select('B4');
  var NIR = image.select('B8');
  
/// COMPUTE D2 A RENAME TO d2 ///
 var D2 = NIR.subtract(RED).pow(2)
 .select([0],['d2']);
 
 /// GAMMA DEFINED AS 1/sigma^2
var gamma = ee.Number(4e6).multiply(-2.0);

/// COMPUTE KERNEL (k) AND KNDVI ///

var k = D2.divide(gamma).exp();
var kndvi = ee.Image.constant(1)
    .subtract(k).divide(ee.Image.constant(1).add(k))
    .select([0],['kndvi']).clip(Grass);

  // MAKE A DATE BAND IN UNIX TIME (MILLISECONDS FROM UNIX EPOCH)
  var dateBand = ee.Image(image.date().millis()).toLong().rename('millis');    

/// CALCULATE THE MEAN KNDVI WITHIN THE GEOMETRY ///
var kndviMean = kndvi.reduceRegion({ 
  reducer: ee.Reducer.first(),
  geometry: Grass,
  scale: 10,
  bestEffort: true,
  maxPixels: 1e10
  });

/// ADD KNDVI &  DATE BANDS, SET KNDVI GEOMETRY MEAN AS A PROPERTY
return image.addBands(ee.Image([kndvi,dateBand])).set(kndviMean);
 
};

//// IMPORTING THE IMAGE COLLECTION ////

var collection = ee.ImageCollection('COPERNICUS/S2_SR')
                   .filterDate(startDate, endDate)
                   .map(maskCloudAndShadows)
                   .map(addKNDVI)
                   .filter(ee.Filter.bounds(Grass))
                   
/// VIEWING THE MEDIAN COMPOSITE ////
 var vizParams = { bands: ['B4','B3','B2'] , min: 0, max: 2000}
 Map.addLayer(collection.median(),vizParams, 'collection')


/// Getting Time Series for a singe Location ///

var testPoint = ee.Feature(Grass.first())
//Map.centerObject(testPoint, 10)
var chart = ui.Chart.image.series({
    imageCollection: collection.select('kndvi'),
    region: testPoint.geometry()
    }).setOptions({
      interpolateNulls: true,
      lineWidth: 1,
      pointSize: 3,
      title: 'KNDVI over Time at a Single Location',
      vAxis: {title: 'KNDVI'},
      hAxis: {title: 'Date', format: 'YYYY-MMM', gridlines: {count: 12}}
    })
print(chart)


////// Exporting time series for a single location ////

var filteredCollection = collection.select('kndvi')
  .filter(ee.Filter.bounds(testPoint.geometry()))
var timeSeries = ee.FeatureCollection(filteredCollection.map(function(image) {
  var stats = image.reduceRegion({
    reducer: ee.Reducer.mean(),
    geometry: testPoint.geometry(),
    scale: 10,
    maxPixels: 1e10
  })
  // reduceRegion doesn't return any output if the image doesn't intersect
  // with the point or if the image is masked out due to cloud
  // If there was no ndvi value found, we set the ndvi to a NoData value -9999
  var kndvi = ee.List([stats.get('kndvi'), -9999])
    .reduce(ee.Reducer.firstNonNull())
 
  // Create a feature with null geometry and NDVI value and date as properties
  var f = ee.Feature(null, {'kndvi': kndvi,
    'date': ee.Date(image.get('system:time_start')).format('YYYY-MM-dd')})
  return f
}))
 
// Checking the results
print(timeSeries.first())
 

// Export to CSV
Export.table.toDrive({
    collection: timeSeries,
    description: 'Single_Location_kNDVI_time_series',
    folder: 'earthengine',
    fileNamePrefix: 'kndvi_time_series_single',
    fileFormat: 'CSV'
})

///// CREATIING THE BATCH PROCESSING //////

var blocks = collection.select('ndvi')
  .filter(ee.Filter.bounds(testPoint.geometry()))
var subset = blocks.filterBounds(Grass);
print('size of Grass subset',subset.size());

///// CREATING TRIPLETS /////
var triplets = collection.map(function(image){
  return image.select('kndvi').reduceRegions({
    collection: subset.select(['blockid10']),
    reducer: ee.Reducer.mean(),
    scale:30
  }).filter(ee.Filter.neq('mean',null))
  .map(function(f) {
    return f.set('imageId',image.id());
  });
  }).flatten();

// //// GETTING TIME SERIES FOR MULTIPLE LOCATONS /////

var chart = ui.Chart.image.seriesByRegion({
    imageCollection: collection.select('kndvi'),
    regions: Grass,
    reducer: ee.Reducer.max()
})
// This doesn't work as the result is to large to print
print(chart)

/// HANDLING MASKED PIXELS ////
var triplets = collection.map(function(image) {
  return image.select('kndvi').reduceRegions({
    collection: Grass, 
    reducer: ee.Reducer.mean().setOutputs(['kndvi']), 
    scale: 10,
  })
    // If there was no ndvi value found, we set the ndvi to a NoData value -9999
    .map(function(feature) {
    var kndvi = ee.List([feature.get('kndvi'), -9999])
      .reduce(ee.Reducer.firstNonNull())
    return feature.set({'ndvi': kndvi, 'imageID': image.id()})
    })
  }).flatten()

var format = function(table, rowId, colId) {
  var rows = table.distinct(rowId);
  var joined = ee.Join.saveAll('matches').apply({
    primary: rows,
    secondary: table,
    condition: ee.Filter.equals({
      leftField: rowId,
      rightField: rowId,
    })
  });
  
  return joined.map(function(row) {
    var values = ee.List(row.get('matches'))
    .map(function(feature){
      feature = ee.Feature(feature);
      return [feature.get(colId), feature.get('kndvi')];
    });
    return row.select([rowId]).set(ee.Dictionary(values.flatten()));
  });
};
var sentinelResults = format(triplets,'id','imageID');


/// GRANULE OVERLAPS ////
var merge = function(table, rowId){
  return table.map(function(feature) {
    var id = feature.get(rowId)
    var allKeys = feature.toDictionary().keys().remove(rowId)
    var substrKeys = ee.List(allKeys.map(function(val) { 
        return ee.String(val).slice(0,8)}
        ))
    var uniqueKeys = substrKeys.distinct()
    var pairs = uniqueKeys.map(function(key) {
      var matches = feature.toDictionary().select(allKeys.filter(ee.Filter.stringContains('item', key))).values()
      var val = matches.reduce(ee.Reducer.max())
      return [key, val]
    })
    return feature.select([rowId]).set(ee.Dictionary(pairs.flatten()))
  })
}
var sentinelMerged = merge(sentinelResults, 'id');

//// EXPORTING MULTIPLE LOCATIONS TO DRIVE////
Export.table.toDrive({
    collection: sentinelMerged,
    description: 'Multiple_Locations_kNDVI_time_series',
    folder: 'earthengine',
    fileNamePrefix: 'kndvi_time_series_multiple',
    fileFormat: 'CSV'
})

Best Answer

I found out the issue in your addKNDVI function; specifically in following line:

return image.addBands(ee.Image([kndvi,dateBand])).set(kndviMean);

After converting it in:

return image.addBands(ee.Image([kndvi,dateBand]));

complete code works as expected. It can be observed in following picture.

enter image description here

Additionally, both CSVs files were successfully exported; as it can also be showed in following image:

enter image description here

Related Question