1

I am wondering how to export a time series of NDVI across a 20 year time series (1990 - 2010) for each point in my region. To be explicit: There are 1,000 random points in this arbitrary region. I need to export time vs NDVI for each image in this Landsat Collection for each point. I am getting stuck with the method of extraction, because I do not want to reduce an image stack at each point. I have also seen individual point time series chart, but I would like this equivalent for each point. This is what I have so far:

//Mask clouds in Landsat 5 imagery.
var maskClouds = function(image) {
  var scored = ee.Algorithms.Landsat.simpleCloudScore(image);
  return image.updateMask(scored.select(['cloud']).lt(20));
};
//Mask clouds and adds quality bands to Landsat 8 images.
var addQualityBand = function(image) {
  return maskClouds(image)
    //NDVI
    .addBands(image.normalizedDifference(['B4', 'B3']))
    //time in days
    .addBands(image.metadata('system:time_start'));
};
var images= ee.ImageCollection('LANDSAT/LT05/C01/T1_TOA')
  .filterDate('1990-01-01','2011-01-01');

var collection= images.map(addQualityBand);

var randomPoints = ee.FeatureCollection.randomPoints(geometry,1000);

// Add to map
Map.centerObject(randomPoints);
Map.addLayer(randomPoints, {}, 'random points',true);

Here is a link to the full script with the geometry import: https://code.earthengine.google.com/47c441094aef35980e528e3fd9bb9b0a

1 Answer 1

5

Here is a script that you might find useful:

/**
 * @license
 * Copyright 2020 Google LLC.
 * SPDX-License-Identifier: Apache-2.0
 * 
 * @description
 * This script calculates image statistics from the 9 pixels surrounding points
 * in a feature collection for all images in a Landsat surface reflectance
 * collection (TM, ETM+, OLI). Images are cloud masked using CFmask. All images
 * dates are included. The result is a featureCollection; essentially a table
 * with one row per unique observation defined by combinations of image and
 * point. Columns include the image statistic per band, point ID, and image
 * properties (ID, date, satellite).
 */


// #############################################################################
// ### POINT FEATURE COLLECTION SETUP ###
// #############################################################################

// `pts` is a FeatureCollection import from drawing tools.
pts = pts
  // Add a buffer to each point (result is a 90x90m square around each point).
  .map(function(pt){
    return pt.buffer(45).bounds();
  });

// Display the points to the Map.
print(pts.limit(5));
Map.centerObject(pts);
Map.addLayer(pts);


// #############################################################################
// ### DEFINE FUNCTIONS TO PREPARE THE LANDSAT IMAGE COLLECTION  ###
// #############################################################################

// Define function to mask cloud and shadow pixels out of images using CFmask.
function fmask(img) {
  var cloudShadowBitMask = 1 << 3;
  var cloudsBitMask = 1 << 5;
  var qa = img.select('pixel_qa');
  var mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0)
    .and(qa.bitwiseAnd(cloudsBitMask).eq(0));
  return img.updateMask(mask)}

// Function to get and rename bands of interest from OLI.
function renameOli(img) {
  return img.select(
    ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['Blue', 'Green', 'Red', 'NIR', 'SWIR1', 'SWIR2'])}

// Function to get and rename bands of interest from TM/ETM+.
function renameEtm(img) {
  return img.select(
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
    ['Blue', 'Green', 'Red', 'NIR', 'SWIR1', 'SWIR2'])}

// Define function to prepare (cloud mask and rename) OLI images.
function prepOli(img) {
  img = fmask(img);
  img = renameOli(img);
  return img}

// Define function to prepare (cloud mask and rename) TM/ETM+ images.
function prepEtm(img) {
  img = fmask(img);
  img = renameEtm(img);
  return img}


// #############################################################################
// ### FILTER AND PREPARE LANDSAT IMAGE COLLECTIONS ###
// #############################################################################

// Get Landsat surface reflectance collections for OLI, ETM+ and TM sensors.
// filter them by the bounds of the point feature collection and apply the
// relevant image preparation function to each image.
var LC08col = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
  .filterBounds(pts)
  .map(prepOli);
var LE07col = ee.ImageCollection('LANDSAT/LE07/C01/T1_SR')
  .filterBounds(pts)
  .map(prepEtm);
var LT05col = ee.ImageCollection('LANDSAT/LT05/C01/T1_SR')
  .filterBounds(pts)
  .map(prepEtm);

// Merge the prepared sensor collections.
var col = LC08col.merge(LE07col).merge(LT05col);

// #############################################################################
// ### REDUCE REGION: FOR EACH IMAGE AROUND EACH POINT ###
// #############################################################################

// Map over the image collection; for each image find the intersecting points
// and do a region reduction at each point.
var statCol = col.map(function(img) {
  // Get metadata for the image.
  var imgDate = img.date().format('YYYY-MM-dd'); 
  var imgId = img.getString('LANDSAT_ID');
  var imgSat = img.getString('SATELLITE');
  // Filter the points to those that intersect the image.
  var thesePts = pts.filterBounds(img.geometry());

  // Reduce the image by mean for the points that intersect it. 
  var bandStats = img.reduceRegions({
    collection: thesePts,
    reducer: ee.Reducer.mean(),
    scale: 30,
    crs: 'EPSG:5070'
  })
  // Add metadata to each point.
  .map(function(pt) {
    return pt.set({
      'imgDate': imgDate,
      'imgId': imgId,
      'imgSat': imgSat,
      'plotId': pt.getString('PlotID')
    });
  });

  // Return the featureCollection.
  return bandStats;
});

// Flatten the collection of collections; result is a featureCollection. Think
// of it as a table with one row per unique observation defined by combinations
// of image and point. Columns include the image region statistic per band and
// image properties.
statCol = statCol.flatten();

// Filter out observations where bands stats are null (point fell on masked
// pixels);
statCol = statCol.filter(
  ee.Filter.notNull(['Blue', 'Green', 'Red', 'NIR', 'SWIR1', 'SWIR2']));

// Print a sample of the points.
print(statCol.limit(10));


// #############################################################################
// ### BATCH TASK - EXPORT AS ASSET OR GOOGLE DRIVE FILE ###
// #############################################################################

// It's quite likely that for point feature collections that
// cover a large area or contain many points, you'll need to complete this
// operation as a batch task by either exporting the final feature collection as
// an asset or as a CSV/Shapefile/GeoJSON to Google Drive. If your browser
// times out, definitely try exporting the results.

// Export as asset option.
Export.table.toAsset({
  collection: statCol,
  description: 'point_summary_table_asset',
  assetId: 'point_summary_table'
});

// Export to Google Drive option.
Export.table.toDrive({
  collection: statCol,
  description: 'point_summary_table_gdrive',
  fileFormat: 'CSV'});

Code Editor link

2
  • Thank you for this it is helpful. something that I am still confused about is the reducer function: bandstats, where the ee.Reducer.mean() function is applied. I am worried that this is not what I want because I want NDVI from each raw image that intersects my points over my time series... not a summarized value. Am I just confusing myself?
    – ALO
    Commented Mar 18, 2020 at 14:18
  • 3
    1) Don't buffer the points - remove the .map(function(pt){return pt.buffer(45).bounds();}); code; 2) change the reducer to ee.Reducer.first(); 3) If interested in NDVI, add img = img.addBands(img.normalizedDifference(['NIR', 'Red']).rename('NDVI')) to the first line inside of the function that defines the statCol var. Commented Mar 18, 2020 at 15:06

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.