google-earth-engine – Detrending MODIS NDVI Timeseries for Harmonic Regression in Google Earth Engine

google-earth-enginegoogle-earth-engine-javascript-api

Trying to detrend MODIS MOD13Q1 NDVI time series using a tutorial from Ozzy Campos (@ghidora77). This tutorial was for Landsat NDVI LANDSAT/LC08/C02/T1_L2 but I am trying to modify it for MODIS.

Successfully imported the data, parse it for quality control, masked, scaled and plotted the initial time series with a trend line. See the code in help3. Link to the asset: Oban.

Independent ('constant', 't') and dependent variables ('NDVI') have also been set. However, when trying to detrend the time series, I get the error below (see attached image). Because detrending doesn't work properly, I don't trust the resultant harmonic model.

Error generating chart: Projection error: Unable to compute
intersection of geometries in projections and
.

// List of the independent variable names.
var independents = ee.List(['constant', 't']);
// Name of the dependent variable.
var dependent = ee.String('NDVI');
// Compute a linear trend. This will have two bands: 'residuals' and
// a 2x1 band called 'coefficients' (columns are for dependent variables).
var trend = newndvi.select(independents.add(dependent))
  .reduce(ee.Reducer.linearRegression(independents.length(), 1));
Map.addLayer(trend, {}, 'Trend Array Image');

/////////////////////////////////////////////////
// **************** HELP!!!!!!! ***************

// Flatten the coefficients into a 2-band image.
var coefficients = trend.select('coefficients')
  .arrayProject([0])
  .arrayFlatten([independents]);

Compute a detrended series.

var detrended = newndvi.map(function(image) {
  return image.select(dependent).subtract(
    image.select(independents).multiply(coefficients).reduce('sum'))
    .rename(dependent)
    .copyProperties(image, ['system:time_start']);
});

var detrendedChart = ui.Chart.image.series(detrended, Oban, null, 250)
  .setOptions({
    title: 'Detrended MODIS Time Series at Oban',
    lineWidth: 1,
    pointSize: 3,
  });
print(detrendedChart);

Use these independent variables in the harmonic regression.

var harmonicIndependents = ee.List(['constant', 't', 'cos', 'sin']);
// Add harmonic terms as new image bands.
var harmonicLandsat = newndvi.map(function(image) {
  var timeRadians = image.select('t').multiply(2 * Math.PI);
    return image
      .addBands(timeRadians.cos().rename('cos'))
      .addBands(timeRadians.sin().rename('sin'));
  });
  

var harmonicTrend = harmonicLandsat
  .select(harmonicIndependents.add(dependent))
  // The output of this reducer is a 4x1 array image.
  .reduce(ee.Reducer.linearRegression({
   numX: harmonicIndependents.length(),
   numY: 1
  }));
  

Turn the array image into a multi-band image of coefficients.

var harmonicTrendCoefficients = harmonicTrend.select('coefficients')
  .arrayProject([0])
  .arrayFlatten([harmonicIndependents]);
// Compute fitted values.
var fittedHarmonic = harmonicLandsat.map(function(image) {
  return image.addBands(
    image.select(harmonicIndependents)
      .multiply(harmonicTrendCoefficients)
      .reduce('sum')
      .rename('fitted'));
});
// Plot the fitted model and the original data at the Oban.
print(ui.Chart.image.series(fittedHarmonic.select(['fitted', 'NDVI']), Oban,
      ee.Reducer.mean(), 250)
  .setSeriesNames(['NDVI', 'fitted'])
  .setOptions({
    title: 'Harmonic Model: Original and Fitted Values',
    lineWidth: 1,
    pointSize: 3
  })
);

Compute phase and amplitude.

var phase = harmonicTrendCoefficients.select('sin')
  .atan2(harmonicTrendCoefficients.select('cos'))
  // Scale to [0, 1] from radians.
  .unitScale(-Math.PI, Math.PI);
var amplitude = harmonicTrendCoefficients.select('sin')
  .hypot(harmonicTrendCoefficients.select('cos'))
  // Add a scale factor for visualization.
  .multiply(5);
// Compute the mean NDVI.
var meanNdvi = newndvi.select('NDVI').mean();
// Use the HSV to RGB transformation to display phase and amplitude.
var rgb = ee.Image.cat([phase, amplitude, meanNdvi]).hsvToRgb();
Map.addLayer(rgb, {}, 'Phase (hue), Amplitude (sat), NDVI (val)');

Plots and errors

Best Answer

You're hitting a bug related to a recent change. To work around it until the bug is fixed, assign a default projection to your reduced composites (e.g.: line 111).

var detrended = newndvi.map(function(image) {
  return image.select(dependent).subtract(
    image.select(independents).multiply(coefficients).reduce('sum'))
    .setDefaultProjection("EPSG:4326")
    .rename(dependent)
    .copyProperties(image, ['system:time_start']);
});
Related Question