I am working with Landtrendr Algorithm in GEE. I want to use the Pixel Time Series plotter for my own ImageCollection based on Landsat 4-9. When I am applying the Landtrendr using my own NDVI function, the result looks quite good. But when I am applying the exact same code but with the NDVI provided by the Landtrendr API, it doesn’t seems like that the Algotithm is identifying the right breaking points. I have no idea how to fix and change this, because I need to use the NDVI provided by the API, otherwise the getChangeMap wont work. I am very thankful for any advise or tips.
```
// LANDTRENDR LANDSAT TIME SERIES ANALYSIS
// Define coordinates for the Time Series Plot
var long = 55.40631284191384
var lat = -4.6561753011485605
var coordinates = ee.Geometry.Point(long, lat);
// Define AOI to download Landsat scenes for //
var my_aoi = geometry;
// add to map
Map.addLayer(my_aoi, {}, 'aoi');
// Zoom to aoi
Map.centerObject(my_aoi, 15);
// Define Time Range for your Time Series Analysis
var startYear = 1990 // what year do you want to start the time series, Note: no
images before 1990
var endYear = 2024; // what year do you want to end the time series
var startDay = '01-01'; // what is the beginning of date filter | month-day
var endDay = '12-31'; // what is the end of date filter | month-day
// Defne LandtrendR parameters
var run_params = {
maxSegments: 6,
spikeThreshold: 0.9,
vertexCountOvershoot: 3,
preventOneYearRecovery: true,
recoveryThreshold: 0.25,
pvalThreshold: 0.05,
bestModelProportion: 0.75,
minObservationsNeeded: 6
};
// define the sign of spectral delta for vegetation loss for the segmentation index -
var distDir = -1;
// define index for the Time Series Analysis //
// NDVI
var NDVI = function(img) {
var index = img.normalizedDifference(['NIR', 'R']) //
calculate normalized difference of band 4 and band 7 (B4-B7)/(B4+B7)
.multiply(1000) // ...scale
results by 1000 so we can convert to int and retain some precision
.select([0], ['NDVI'])
.set('system:time_start',
img.get('system:time_start'));
return index ;
};
//
///// ----------------------- \\
/// Prepare Landsat Collections \
///// ----------------------- \\
// Scaling factor is needed for Landsat
function applyScaleFactors(image) {
var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);
return image.addBands(opticalBands, null, true)
.addBands(thermalBands, null, true);
}
// functions to select and rename bands for Landsat-4/5/7
function renameBandsTM_ETM(image) {
var bands = ['SR_B1', 'SR_B2', 'SR_B3', 'SR_B4', 'SR_B5', 'SR_B7'];
var new_bands = ['B', 'G', 'R', 'NIR', 'SWIR1', 'SWIR2'];
return image.select(bands).rename(new_bands);
}
// functions to select and rename bands for Landsat-8
function renameBandsOLI(image) {
var bands = ['SR_B2', 'SR_B3', 'SR_B4', 'SR_B5', 'SR_B6', 'SR_B7'];
var new_bands = ['B', 'G', 'R', 'NIR', 'SWIR1', 'SWIR2'];
return image.select(bands).rename(new_bands);
}
// Mask clouds, snow and cloud shadows based on the pixel_qa band
var cloudMask = function (image) {
// Get the pixel QA band.
var qa = image.select('QA_PIXEL');
// Bits 3 and 5 are cloud shadow and cloud, respectively.
// Use LeftShift-Operators to define Bit values
var CloudShadowBitValue = (1 << 3);
var CloudBitValue = (1 << 4);
// Create masks
var shadow_mask = qa.bitwiseAnd(CloudShadowBitValue).eq(0);
var cloud_mask = qa.bitwiseAnd(CloudBitValue).eq(0);
// Given bit values should be set to zero for masking.
var final_mask = shadow_mask.and(cloud_mask);
return image.updateMask(final_mask);
};
// Function to get Landsat SR collection
// Function to get an SR collection for a given Landsat sensor
var getSRcollection = function(year, startDay, endDay, sensor, aoi) {
var srCollection = ee.ImageCollection('LANDSAT/' + sensor + '/C02/T1_L2')
.filterBounds(aoi)
.filterDate(year + '-' + startDay, year + '-' + endDay)
.map(cloudMask) // Apply your cloud mask function
.map(applyScaleFactors) // Apply scale factors
.map(function(image) {
// Apply appropriate renaming function
var renamed = ee.Algorithms.If(
sensor === 'LC08'|| sensor === 'LC09',
renameBandsOLI(image), // Landsat 8,9
renameBandsTM_ETM(image) // Landsat 4, 5, 7
);
return ee.Image(renamed).set('system:time_start', image.get('system:time_start'));
})
.map(function(image) { return image.clip(aoi); });
return srCollection;
};
// Function to merge collections from different Landsat sensors
var getCombinedSRcollection = function(year, startDay, endDay, aoi) {
var lt4 = getSRcollection(year, startDay, endDay, 'LT04', aoi);
var lt5 = getSRcollection(year, startDay, endDay, 'LT05', aoi);
var le7 = getSRcollection(year, startDay, endDay, 'LE07', aoi);
var lc8 = getSRcollection(year, startDay, endDay, 'LC08', aoi);
var lc9 = getSRcollection(year, startDay, endDay, 'LC09', aoi)
return ee.ImageCollection(lt4.merge(lt5).merge(le7).merge(lc8).merge(lc9));
};
//CREATE ANNUAL MOSAIC COLLECTION
// Function to create a Medoid Composite
var medoidMosaic = function(inCollection, dummyCollection) {
var imageCount = inCollection.toList(1).length();
var finalCollection = ee.ImageCollection(ee.Algorithms.If(imageCount.gt(0),
inCollection, dummyCollection));
var median = finalCollection.median();
var difFromMedian = finalCollection.map(function(img) {
var diff = ee.Image(img).subtract(median).pow(ee.Image.constant(2));
return diff.reduce('sum').addBands(img);
});
return
ee.ImageCollection(difFromMedian).reduce(ee.Reducer.min(7)).select([1,2,3,4,5,6],
['B','G','R','NIR','SWIR1','SWIR2']);
};
// Function to build a yearly mosaic
var buildMosaic = function(year, startDay, endDay, aoi, dummyCollection) {
// create a temp variable to hold the upcoming annual mosiac
var collection = getCombinedSRcollection(year, startDay, endDay, aoi); // get the SR
collection
var img = medoidMosaic(collection, dummyCollection) // apply the
medoidMosaic function to reduce the collection to single image per year by medoid
.set('system:time_start', (new Date(year,8,1)).valueOf());
return ee.Image(img);
};
// Function to build an annual mosaic collection
var buildMosaicCollection = function(startYear, endYear, startDay, endDay, aoi,
dummyCollection) {
var imgs = [];
for (var i = startYear; i <= endYear; i++) {
var tmp = buildMosaic(i, startDay, endDay, aoi, dummyCollection);
imgs = imgs.concat(tmp.set('system:time_start', (new Date(i,8,1)).valueOf()));
}
return ee.ImageCollection(imgs);
};
print(buildMosaicCollection, 'buildMosaicCollection')
// empty dummy collection
var dummyCollection = ee.ImageCollection([ee.Image([0,0,0,0,0,0]).mask(ee.Image(0))]);
// make an image collection from an image with 6 bands all set to 0 and then make them
masked values
// build annual image collection
var annualMosaicCollection = buildMosaicCollection(startYear, endYear, startDay,
endDay, my_aoi, dummyCollection);
print(annualMosaicCollection, 'annualMosaicCollection')
// ----- FUNCTION TO GET LT DATA FOR A PIXEL -----
var getPoint = function(img, geom, z) {
return img.reduceRegion({
reducer: 'first',
geometry: geom,
scale: z
}).getInfo();
};
// ----- FUNCTION TO CHART THE SOURCE AND FITTED TIME SERIES FOR A POINT -----
var chartPoint = function(lt, pt, distDir) {
Map.centerObject(pt, 14);
Map.addLayer(pt, {color: "FF0000"});
var point = getPoint(lt, pt, 10);
var data = [['x', 'y-original', 'y-fitted']];
for (var i = 0; i <= (endYear-startYear); i++) {
data = data.concat([[point.LandTrendr[0][i], point.LandTrendr[1][i]*distDir,
point.LandTrendr[2][i]*distDir]]);
}
print(ui.Chart(data, 'LineChart',
{
'hAxis':
{
'format':'####'
},
'vAxis':
{
'maxValue': 1000,
'minValue': -1000
}
},
{'columns': [0, 1, 2]}
)
);
};
//----- BUILD LT COLLECTION -----
var annualSRcollection = buildMosaicCollection(startYear, endYear, startDay, endDay,
coordinates, dummyCollection); // put together the cloud-free medoid surface
reflectance annual time series collection
print(annualSRcollection, 'annualSRcollection')
// apply the function to calculate the segmentation index and adjust the values by the
var ltCollection = annualSRcollection.map(NDVI)
// map the function over every image in the collection - returns a 1-band annual image
collection of the spectral index
.map(function(img) {return
img.multiply(distDir) // ...multiply the segmentation index by the distDir to ensure that vegetation loss is associated with a positive spectral delta
.set('system:time_start',
img.get('system:time_start'))}); // ...set the output system:time_start metadata to the
input image time_start otherwise it is null
print(ltCollection, 'ltCollection')
//----- RUN LANDTRENDR -----
run_params.timeSeries = ltCollection;
var lt = ee.Algorithms.TemporalSegmentation.LandTrendr(run_params); // run LandTrendr
spectral temporal segmentation algorithm
//----- PLOT THE SOURCE AND FITTED TIME SERIES FOR THE GIVEN POINT -----
chartPoint(lt, coordinates, distDir); // plot the x-y time series for the given point
print(lt, 'lt');
And here is the version with the NDVI provided by the Landtrendr API, everything stays the same except that i am not usinf distDir and changed this part:
var ltgee = require('users/emaprlab/public:Modules/LandTrendr.js');
// Transform collection and include at least two indices/bands, otherwise shit wont
work
var ltCollection_transformed = ltgee.transformSRcollection(annualSRcollection,
['NDVI']);
print(ltCollection_transformed, 'ltCollection_transformed')
//----- RUN LANDTRENDR -----
run_params.timeSeries = ltCollection_transformed;
var lt = ee.Algorithms.TemporalSegmentation.LandTrendr(run_params); //
//----- PLOT THE SOURCE AND FITTED TIME SERIES FOR THE GIVEN POINT -----
chartPoint(lt, coordinates); // plot the x-y time series for the given point
print(lt, 'lt');
Many thanks in advance!