time-seriesindicesgoogle-earth-enginelandsat

Google Earth Engine: how to map a function over a collection of all Landsat sensors to create NDVI timeseries


I am trying to combine all the Landsat sensors (L4-l8) from 1980s to present in Google Earth Engine, and calculate the time-series of the NDVI index (after having removed the clouds)

I tried to find a work around to solve the issue that L8 uses different bands for NIR and RED than L4-L7, by adding to code from another Q & A (Google Earth Engine: mask clouds and map a function over an image collection of different sensors)

I get an image collection containing all landsat datasets with a single 'NDVI' band. However, when I then add the code for creating the time series chart, I get the following error:

'Error generating chart: No features contain non-null values of "system:time_start"'

I am wondering if it is because the collection is made from different sensor data sets, but not sure how to resolve that.

Can anyone help me please?

Thank you.

Below is the code [EDITED to make code reproducible]:

//Define a region of interest - Baringo county, kenya
var Baringo2 = /* color: #98ff00 */ee.Geometry.Polygon(
        [[[35.69382363692023, 1.4034169899773616],
          [35.69382363692023, 1.2606333558875118],
          [35.61691934004523, 1.0079975313237526],
          [35.58945351973273, 0.6509798625215468],
          [35.71030312910773, 0.35436075019447294],
          [35.72128945723273, 0.18956774160826206],
          [35.61691934004523, 0.18407460674896256],
          [35.58945351973273, 0.13463632293582842],
          [35.71030312910773, 0.04125265421470341],
          [35.68283730879523, -0.0466379620709295],
          [35.74875527754523, -0.18945988757796725],
          [35.96848184004523, 0.05223897866641199],
          [36.09482461348273, 0.002800509340276178],
          [36.27060586348273, 0.2719645271288622],
          [36.23215371504523, 0.45872822561768967],
          [36.32004434004523, 0.6509798625215468],
          [36.47934609785773, 0.8651943843139164],
          [36.32004434004523, 0.9915205478901427],
          [36.18271523848273, 1.1672705367627716],
          [36.08933144942023, 1.1892385469740003],
          [35.79270059004523, 1.6944479915417494]]]);

//print (Baringo2);


//define land cover categories and sample points:

//DEG, 'Open Access Grazing'
var DEG2 = /* color: #d63000 */ee.Geometry.MultiPoint(
        [[35.981082916259766, 0.44974818893112445],
         [35.98460465669632, 0.4510302452289453],
         [35.987091064453125, 0.451808061701256],
         [35.98616033792496, 0.4483427545719104],
         [35.987573862075806, 0.4297931417762091],
         [35.9996223449707, 0.45043481325254864],
         [36.06963872909546, 0.3470539601321629],
         [36.06938123703003, 0.3475260202469071],
         [36.069630682468414, 0.34738386578302555],
         [36.070003509521484, 0.3475903920789125],
         [36.06644153594971, 0.35022933035465353],
         [36.05863630771637, 0.3589468796649346],
         [36.05862021446228, 0.3589039651627689],
         [35.98088979721069, 0.4486537280584078],
         [36.07082426548004, 0.37428880110513496],
         [36.07011079788208, 0.3772229960862265],
         [36.032023429870605, 0.627012774441952],
         [36.03208780288696, 0.6266319235542508],
         [36.03143334388733, 0.6271683332472826],
         [35.98740756511688, 0.4303348413873794],
         [35.997237861156464, 0.44501955275654603]]);

//GM, 'Managed Grazing'
var GM2 = /* color: #8b1062 */ee.Geometry.MultiPoint(
        [[36.030564308166504, 0.6268519274914895],
         [36.03069305419922, 0.6269592094299815],
         [36.03039264678955, 0.6268948402671484],
         [36.03148698806763, 0.6315292660507418],
         [36.03124022483826, 0.6309284876929263],
         [36.03038191795349, 0.630005863651221],
         [36.03060722351074, 0.6335886212975799],
         [35.99500894513767, 0.5747552592254358],
         [36.02037191456475, 0.5758710045457109],
         [36.01653099125542, 0.5782526853495346],
         [36.017239094435354, 0.5785959906083284],
         [36.017346383450786, 0.5729096907628695],
         [36.015372277615825, 0.5705065511240786],
         [36.04236602652236, 0.5423771737070896],
         [36.040499209047994, 0.539373233463203],
         [36.04056358337402, 0.38151398068151043],
         [36.03893280029297, 0.3785528870665109]]);

//REF, Nature Conservancy
var REF2 = /* color: #98ff00 */ee.Geometry.MultiPoint(
        [[36.094207763671875, 0.22436084629270164],
         [36.0926628112793, 0.2264207668774221],
         [36.090946197509766, 0.23088392713673156],
         [36.089229583740234, 0.23672036535773863],
         [36.09781265258789, 0.6451750427539986],
         [36.096739768981934, 0.6450033922571318],
         [36.097726821899414, 0.6443597028423275],
         [36.09734058380127, 0.6434156248868509],
         [36.0975980758667, 0.642686109983424],
         [36.123390197753906, 0.6421282455751846],
         [36.124634742736816, 0.6414845557977334]]);

//CRO, 'Crops'
var CRO2 = /* color: #5980ff */ee.Geometry.MultiPoint(
        [[36.044254302978516, 0.5421843671379806],
         [36.04090690481826, 0.47892844903645376],
         [36.041936873079976, 0.4832198321871975],
         [36.03189468252822, 0.48802617809656],
         [36.02786064016982, 0.4869962471182337],
         [36.02193832266494, 0.4859663159825207],
         [36.014471052767476, 0.47772686127176456],
         [36.02193832266494, 0.47437957996379887],
         [36.018505095125875, 0.4748087186837464],
         [36.00992202627822, 0.47746937815196017],
         [36.00906371939345, 0.4767827564520509],
         [36.00382804739638, 0.47626779013219045],
         [36.003398893954, 0.4747228909418917],
         [36.010007856966695, 0.4700881913046016],
         [36.014471052767476, 0.47128978039605285],
         [36.01962089407607, 0.4713756081803616],
         [36.02485656607314, 0.47970089820257816],
         [36.02511405813857, 0.4857088331740545],
         [36.04159355032607, 0.4773835504432214],
         [36.032752989412984, 0.47377878571117404],
         [36.00447177886963, 0.42708894038807443],
         [36.002798080444336, 0.4288055062751536],
         [36.00245475769043, 0.42777556678899614],
         [36.00425720214844, 0.4239562066570169],
         [36.00627422332764, 0.42588734403892814],
         [36.00099563598633, 0.4293204759663003],
         [36.00425720214844, 0.4286767638469664],
         [36.00425720214844, 0.4327965204722641],
         [36.008548736572266, 0.4323244651387205],
         [36.01219654083252, 0.42425660583700076]]);


//mask clouds for L8, and then L5-7

// Function to cloud mask Landsat 8.
var maskL8SR = function(image) {
  // Bits 3 and 5 are cloud shadow and cloud, respectively.
  var cloudShadowBitMask = ee.Number(2).pow(3).int();
  var cloudsBitMask = ee.Number(2).pow(5).int();
  // Get the 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).and(
            qa.bitwiseAnd(cloudsBitMask).eq(0));
  return image
      // Scale the data to reflectance and temperature.
      .select(['B4', 'B5'], ['Red', 'NIR']).multiply(0.0001)
      .updateMask(mask);
};

// Function to cloud mask Landsats 4-7
var maskL57SR = function(image) {
  var qa = image.select('pixel_qa');
  // Second bit must be zero, meaning none to low cloud confidence.
  var mask1 = qa.bitwiseAnd(ee.Number(2).pow(7).int()).eq(0).and(
      qa.bitwiseAnd(ee.Number(2).pow(3).int()).lte(0)); // cloud shadow
  // This gets rid of noise at the edge of the images.
  var mask2 = image.select('B.*').gt(0).reduce('min');
  return image
      .select(['B3', 'B4'], ['Red', 'NIR']).multiply(0.0001)
      .updateMask(mask1.and(mask2));
};

//NDVI functions

//NDVI functions for l8
var addNDVI_l8 = function(image) {
  var ndvi = image.normalizedDifference(['NIR', 'Red']).rename('NDVI');
  return image.addBands(ndvi);
};

//NDVI functions for l4-7
var addNDVI_l457 = function(image) {
  var ndvi = image.normalizedDifference(['NIR', 'Red']).rename('NDVI');
  return image.addBands(ndvi);
};


// find all data and filter them by date and add NDVI
var lst5 = ee.ImageCollection('LANDSAT/LT05/C01/T1_SR')
    .filterDate('1984-10-01', '2011-10-01')
    .filterBounds(Baringo2)  // filter to area-of-interest; 
    .map(maskL57SR)
    .map(addNDVI_l457).select('NDVI');


var lst7 = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR')
    .filterDate('2011-10-01', '2013-04-07')
    .filterBounds(Baringo2)  // filter to area-of-interest;
    .map(maskL57SR)
    .map(addNDVI_l457).select('NDVI');


var lst8 = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
    .filterDate('2013-04-07', '2018-05-01')
    .filterBounds(Baringo2)  // filter to area-of-interest;
    .map(maskL8SR)
    .map(addNDVI_l8).select('NDVI');


var lst7_08 = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR')
    .filterDate('2007-12-01', '2008-02-01')
    .filterBounds(Baringo2)  // filter to area-of-interest;
    .map(maskL57SR)
    .map(addNDVI_l457).select('NDVI');



var lst7_92 = ee.ImageCollection('LANDSAT/LT04/C01/T1_SR')
    .filterDate('1992-01-02', '1992-04-01')
    .filterBounds(Baringo2)  // filter to area-of-interest;
    .map(maskL57SR)
    .map(addNDVI_l457).select('NDVI');



// Combine all landsat data, 1985 through 2015
var everything = ee.ImageCollection(lst5.merge(lst7));
everything = everything.merge(lst8);
everything = everything.merge(lst7_08);
everything = everything.merge(lst7_92);

print(everything);


//NDVI time-series 
// Create a time series chart for different land cover catergories.
// Define and display a FeatureCollection of three known locations.
var points = ee.FeatureCollection([
   ee.Feature(DEG2, {'label': 'Open Access Grazing'}),
  ee.Feature(GM2, {'label': 'Managed Grazing'}),
  ee.Feature(REF2, {'label': 'Nature Conservancy'}),
  ee.Feature(CRO2, {'label': 'Crops'})
]);


// Create a time series chart.
var NDVITimeSeriesAll = ui.Chart.image.seriesByRegion(
    everything, points, ee.Reducer.mean(), 'NDVI', 30, 'system:time_start','label')
        .setChartType('ScatterChart')
        .setOptions({
          title: 'Mean NDVI, plotted ~monthly, from 1984 to 2017 in 4 LC categories - Baringo Kenya',
          hAxis: {title: ' Date'}, 
          vAxis: {title: 'NDVI value (-1 to +1)', minValue: -0.0, maxValue: 0.8}, 
          lineWidth: 1,
          pointSize: 4,
          series: {
            0: {color: 'FF0000'}, // Unmanaged Grazing
            1: {color: '00FF00'}, // Managed Grazing
            2: {color: '0000FF'}, // Nature Conservancy
            3: {color: '00FFFF'}  // Cropland
}});

// Display.
print(NDVITimeSeriesAll);

Solution

  • The system:time_start property was lost at the masking stage. This property is necessary for plotting time series, as it is used to construct the time axis. Change those two functions to reassign that property to the image the functions return, as in:

    var maskL8SR = function(image) {
      // Bits 3 and 5 are cloud shadow and cloud, respectively.
      var cloudShadowBitMask = ee.Number(2).pow(3).int();
      var cloudsBitMask = ee.Number(2).pow(5).int();
      // Get the 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)
                  .and(qa.bitwiseAnd(cloudsBitMask).eq(0));
     return image
          // Scale the data to reflectance and temperature.
          .select(['B4', 'B5'], ['Red', 'NIR']).multiply(0.0001)
          .updateMask(mask)
          .set('system:time_start', image.get('system:time_start'));
     };
    

    That last set() function takes the system:time_start property from the original input image and assigns it to the output image, ensuring that all the images in your output ImageCollection include the original time stamps. With the set() function included in both masking functions, I was able to produce the time series plots written in your code.