Google Earth Engine – Getting Table Instead of Chart for Time Series of Multiple Polygons

google-earth-enginetabletime series

Based on the example mentioned here, and based on the answer provided below,

// Define a FeatureCollection: regions of the American West.
var regions = ee.FeatureCollection([
  ee.Feature(    // San Francisco.
    ee.Geometry.Rectangle(-122.45, 37.74, -122.4, 37.8), {label: 'City'}),
  ee.Feature(  // Tahoe National Forest.
    ee.Geometry.Rectangle(-121, 39.4, -120.8, 39.8), {label: 'Forest'}),
  ee.Feature(  // Black Rock Desert.
    ee.Geometry.Rectangle(-119.15, 40.8, -119, 41), {label: 'Desert'})
]);

// Load Landsat 8 brightness temperature data for 1 year.
var temps2013 = ee.ImageCollection('LANDSAT/LC8_L1T_32DAY_TOA')
    .filterDate('2012-12-25', '2013-12-25')
    .select('B11');

// Collect region, image, value triplets.
var triplets = temps2013.map(function(image) {
  return image.select('B11').reduceRegions({
    collection: regions.select(['label']), 
    reducer: ee.Reducer.mean(), 
    scale: 30
  }).filter(ee.Filter.neq('mean', null))
    .map(function(f) { 
      return f.set('imageId', image.id());
    });
}).flatten();
print(triplets.first());

// Format a table of triplets into a 2D table of rowId x colId.
var format = function(table, rowId, colId) {
  // Get a FeatureCollection with unique row IDs.
  var rows = table.distinct(rowId);
  // Join the table to the unique IDs to get a collection in which
  // each feature stores a list of all features having a common row ID. 
  var joined = ee.Join.saveAll('matches').apply({
    primary: rows, 
    secondary: table, 
    condition: ee.Filter.equals({
      leftField: rowId, 
      rightField: rowId
    })
  });

  return joined.map(function(row) {
      // Get the list of all features with a unique row ID.
      var values = ee.List(row.get('matches'))
        // Map a function over the list of rows to return a list of
        // column ID and value.
        .map(function(feature) {
          feature = ee.Feature(feature);
          return [feature.get(colId), feature.get('mean')];
        });
      // Return the row with its ID property and properties for
      // all matching columns IDs storing the output of the reducer.
      // The Dictionary constructor is using a list of key, value pairs.
      return row.select([rowId]).set(ee.Dictionary(values.flatten()));
    });
};

var link = '6430802a354ca3e5d5267718173afac7';

var table1 = format(triplets, 'imageId', 'label');

var desc1 = 'table_demo_' + link; 
Export.table.toDrive({
  collection: table1, 
  description: desc1, 
  fileNamePrefix: desc1,
  fileFormat: 'CSV'
});

is there no simpler way to get a table of a time series from multiple polygons?
But I don't know, I find it bizarre that there is no simple way to get a table, as this example below where ui.Chart.image.seriesByRegion could be replace by a specific function ?

// Define a FeatureCollection: regions of the American West.
var regions = ee.FeatureCollection([
  ee.Feature(    // San Francisco.
    ee.Geometry.Rectangle(-122.45, 37.74, -122.4, 37.8), {label: 'City'}),
  ee.Feature(  // Tahoe National Forest.
    ee.Geometry.Rectangle(-121, 39.4, -120.8, 39.8), {label: 'Forest'}),
  ee.Feature(  // Black Rock Desert.
    ee.Geometry.Rectangle(-119.15, 40.8, -119, 41), {label: 'Desert'})
]);



var modis = ee.ImageCollection('MODIS/006/MOD11A1');
var modisLST = modis.filterBounds(regions)
                    .filterDate('2003-12-25', '2004-02-25')
                    .select('LST_Day_1km');

// Convert temperature to Celsius.
modisLST = modisLST.map(function(img){
  return img.multiply(0.02).subtract(273.15).copyProperties(img, ['system:time_start'])
});

// Create a graph of the time-series.
var graph = ui.Chart.image.seriesByRegion({
  imageCollection: modisLST, 
  regions: fc2, 
  reducer: ee.Reducer.mean()
})
print(graph)

I'm working on thousands of points and try to avoid the Chart output 5 and it's even not working since out of limits.

Best Answer

The ui.Chart.image.seriesByRegion function actually wraps some reduction computations and then does the plotting for you, which is very nice for exploring and creating quick charts but doesn't give you the data in a variable you can use. You can simulate that functionality with you own function to extract the time series for your featureCollection and have a list of arrays. Here is an example:

// Define a FeatureCollection: regions of the American West.
var regions = ee.FeatureCollection([
  ee.Feature(    // San Francisco.
    ee.Geometry.Rectangle(-122.45, 37.74, -122.4, 37.8), {label: 'City'}),
  ee.Feature(  // Tahoe National Forest.
    ee.Geometry.Rectangle(-121, 39.4, -120.8, 39.8), {label: 'Forest'}),
  ee.Feature(  // Black Rock Desert.
    ee.Geometry.Rectangle(-119.15, 40.8, -119, 41), {label: 'Desert'})
]);


var modis = ee.ImageCollection('MODIS/006/MOD11A1');
var modisLST = modis.filterBounds(regions)
                    .filterDate('2003-12-25', '2004-02-25')
                    .select('LST_Day_1km');

// Convert temperature to Celsius.
modisLST = modisLST.map(function(img){
  return img.multiply(0.02).subtract(273.15).copyProperties(img, ['system:time_start']);
});              


function imageCollection_to_timeSeries(imageCollection,featureCollection,reducer,scale){
  var noDataValue = -999; // internal no data value to use

  // loop over all of the images in the collection
  var series = modisLST.map(function(img){
    var t = ee.List([img.date().millis()]); // get time information
    var fc = img.reduceRegions(regions,reducer,scale); // get the mean values
    // loop over the results and check if the value is non null
    fc = fc.map(function(feature){
      var v = ee.List(feature.get('mean'));
      // set no data if value is null
      var setter = ee.Algorithms.If(v,v,noDataValue);
      return feature.set('result',setter);
    });
    // get a list of the results
    var results = ee.List(fc.aggregate_array('result'));
    // return a feature with time and reduction values for each feature
    return ee.Feature(ee.Geometry.Point(0,0),{'values':results,'time':t});
  });

  // extract some information to format the time series arrays
  var keys = ee.List.sequence(0,featureCollection.size().subtract(1));
  var x = ee.Array(series.aggregate_array('time'));
  var y = ee.Array(series.aggregate_array('values'));

  // loop over all of the features
  var df = keys.map(function(i){
    i = ee.Number(i);
    // slice out time series for feature
    var v = y.slice(1,i,i.add(1));
    // create mask for array
    var mask = v.neq(noDataValue);
    // return dictionary with time index and values
    return ee.List([ee.Dictionary({'index':x.mask(mask),'values':v.mask(mask)})]);
  }).flatten();

  return df;
}

// call custom function
var df = imageCollection_to_timeSeries(modisLST,regions,ee.Reducer.mean(),1000);

// do whatever is needed with the individual arrays after this
print(df);

// get time series for first feature
var x1 = ee.Dictionary(df.get(0)).get('index');
var y1 = ee.Dictionary(df.get(0)).get('values');

// and create a chart from the array
var arrayChart = ui.Chart.array.values(y1,0,x1).setChartType('LineChart');
print('Chart from array',arrayChart);

// Create a graph of the time-series using the default ee.ui.chart functionality.
var graph = ui.Chart.image.seriesByRegion({
  imageCollection: modisLST, 
  regions: regions, 
  reducer: ee.Reducer.mean()
});
print('Default ee.ui.Chart',graph);

This a little bit of a work around to handle different no data information and different length arrays for different features but you will essentially have all of the array information you need and can extract/manipulate how you want for further processing. Here is the link. I hope this helps!

Related Question