I have some code that is working properly to calculate the monthly NDVI averages for the previous two years from a date but I sometimes have missing data in some of the months and I would like to get an average based on the mean of the previous two months. I suppose I could also interpolate from the closest geographic data point but it seems like it would be better to interpolate across time than distance given that sometimes the clouds cover reasonably large areas.

But I'm certainly open to other opinions, I just want to fill these holes as accurately as possible. Is there an efficient way to do this. Here is a link to my working code and here is the code itself:

// pick a landsat tile footprint to use as my geometry
var wrs2_descending = ee.FeatureCollection('ft:1_RZgjlcqixp-L9hyS6NYGqLaKOlnhSC35AB5M5Ll');
// use a manually defined point to pick the WRS2 tile
var wrs2_filtered = wrs2_descending.filterBounds(roi);
var layer1 = ui.Map.Layer(wrs2_filtered, {}, 'WRS2 filtered');
Map.layers().set(1, layer1);

var imageCollection = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR').filterBounds(wrs2_filtered);
var monthCount = ee.List.sequence(0, 11);

// Function to cloud mask from the pixel_qa band of Landsat 8 SR data.
function maskL8sr(image) {
  // Bits 3 and 5 are cloud shadow and cloud, respectively.
  var cloudShadowBitMask = 1 << 3;
  var cloudsBitMask = 1 << 5;

  // Get the pixel QA band.
  var qa = image.select('pixel_qa');

  // Both flags should be set to zero, indicating clear conditions.
  var mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0)

  // Return the masked image, scaled to reflectance, without the QA bands.
  return image.updateMask(mask).divide(10000)
      .copyProperties(image, ["system:time_start"]);

// run through the image collection and generate monthly NDVI median images
var composites = ee.ImageCollection.fromImages(monthCount.map(function(m) {
  var startMonth = 1; // thinking that I should always start from Jan so the series are similar
  var startYear = ee.Number(2017-1); // number is one year before the current one

  var month = ee.Date.fromYMD(startYear, startMonth, 1).advance(m,'month').get('month');
  var year = ee.Date.fromYMD(startYear, startMonth, 1).advance(m,'month').get('year')

  // filter by year and then filter by month to get monthly mosaics
  var filtered = imageCollection.filter(ee.Filter.calendarRange({
    start: year.subtract(1), // we want an average of the last two years
    end: year,
    field: 'year'
    start: month,
    field: 'month'
  // mask for clouds and then take the median
  var composite = filtered.map(maskL8sr).median();
  return composite.normalizedDifference(['B5', 'B4']).rename('NDVI')
      .set('month', ee.Date.fromYMD(startYear, startMonth, 1).advance(m,'month'));

Map.addLayer(composites, {min: 0, max: 1}, 'check');

// stack the ImageCollection into a multi-band raster for downloading
var stackCollection = function(collection) {
  // Create an initial image.
  var first = ee.Image(collection.first()).select([]);

  // Write a function that appends a band to an image.
  var appendBands = function(image, previous) {
    return ee.Image(previous).addBands(image);
  return ee.Image(collection.iterate(appendBands, first));
var stacked = stackCollection(composites);
print('stacked image', stacked);

// Display the first band of the stacked image.
Map.addLayer(stacked.select(0).clip(wrs2_filtered), {min:0, max:1}, 'stacked');

2 Answers 2


You can replace the values using where().

// Replace masked pixels by the mean of the previous and next months 
// (otherwise, how to deal with the first images??)
var replacedVals = composites.map(function(image){
  var currentDate = ee.Date(image.get('system:time_start'));
  var meanImage = composites.filterDate(
                currentDate.advance(-2, 'month'), currentDate.advance(2, 'month')).mean();
  // replace all masked values:
  return meanImage.where(image, image);

As an addition to your code, I added the property 'system:time_start', so you can use filterDate(). Furthermore, I made a time window of the two previous and next months to construct a meanImage and replace the values where the image is masked. See the link.

Link code

  • Hi @Kuik, thanks for the answer. This seems to work perfectly. I had to look up the docs (developers.google.com/earth-engine/api_docs#eeimagewhere) to understand exactly what .where() was doing but makes total sense now. Also I see your comment about how to deal with the first or last images. Since I'm only interested in general harmonic patterns, I assume I could just loop around and use the first image and second to last if the last image is missing a value. Any idea how to add that logic in there?
    – clifgray
    Commented May 24, 2019 at 15:39
  • In the filterDate(), you could adjust the images included in the composite for replacing the values. E.g. change the start argument to advance(-1, 'month') to include only one image before the current image (asuming you will keep your monthly composites).
    – Kuik
    Commented May 25, 2019 at 21:01
  • 1
    @Kuik could you update your Link code it is blank.
    – Binx
    Commented Mar 24, 2022 at 16:36

I have found this question recently and I took it up as a challenge to create time-series linear interpolation in GEE as an alternative to moving average method as demonstrated. I am sure this is NOT efficient but (so far) I also cannot find an alternative solution.

Code Explanation The code calculates NDVI value from Sentinel-2 Surface Reflectance from Google Earth engine using a clean image (filter out clouds and shadows, hopefully). Then it assigns a time-series index (daily) to each image in the collection and resampled the NDVI based on the whole day interval (e.g. if the input sequence at date 0, 1.3, 3.6 then it will resample to 0, 1, 2, 3, 4 using linear interpolation)

Code Result

I believe that with little more modification, ones could replace linear interpolation with quadratic or cubic interpolation for a smoother curve at the following section

var interpolated = ee.List.sequence(1, dateLimit.add(-1)).map(function (idx) {
  idx = ee.Number(idx).int();
  var minDate = ee.Image(dateMosaicMinDate.get(idx)).float();
  var maxDate = ee.Image(dateMosaicMaxDate.get(idx)).float();
  var minVal = ee.Image(minValMosaic.get(idx)).float();
  var maxVal = ee.Image(maxValMosaic.get(idx)).float();
  var constantIdx = ee.Image.constant(idx).float().unmask(idx).clip(roi);
  return minVal.add(

DISCLAIMER The code is not optimized, so I am not sure how GEE will scale using a larger area. Also, the code is very messy (please don't judge me)

UPDATE I cleaned up the code a bit more so it can be used in the more general collection by calling LinearResampling function (e.g. LinearResampling(s2_collection, "GENERATION_TIME", 1.5, roi)). The parameters needed are the followings

  • collection - an ee.ImageCollection (support multiple bands)
  • date_attribute - a property in collection specify the date of image creation
  • date_interval - a date interval (Can be fraction such as 1.5) unit in day
  • region - an ee.Geometry represent the region of interest (bounds). Use in the clipping process

For the edge case, I decided to use replicate padding (e.g. if the input is [masked, 0.5, masked, 0.7, masked] the result will be [0.5, 0.5, 0.6, 0.7, 0.7])

Prototype code

Cleaned Up code

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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