I am trying to create a timeseries chart in GEE and I keep having an error:
Error generating chart: Collection.map: A mapped algorithm must return a Feature or Image
What can be the reason for this error and how can I solve it ?
I am trying to create a kNDVI timeseries from each cluster on the site.
var site =
/* color: #0b4a8b */
/* displayProperties: [
{
"type": "rectangle"
}
] */
ee.Geometry.Polygon(
[[[14.189235213951225, -18.464648340471996],
[14.189235213951225, -19.8242220793087],
[18.770534042076225, -19.8242220793087],
[18.770534042076225, -18.464648340471996]]], null, false);
// 2. WorldCover and grassland mask
var worldcover = ee.Image('ESA/WorldCover/v200/2021');
var grassMask = worldcover.eq(30).clip(site);
// 3. Cloud/shadow mask function
function maskS2clouds(image) {
var scl = image.select('SCL');
var mask = scl.neq(3) // 3 = cloud shadow
.and(scl.neq(7)) // 7 = unclassified
.and(scl.neq(8)) // 8 = cloud medium prob
.and(scl.neq(9)) // 9 = cloud high prob
.and(scl.neq(10)) // 10 = thin cirrus
.and(scl.neq(11)); // 11 = snow
return image.updateMask(mask);
}
// 4. Band scaling and kNDVI calculation
function addKNDVI(image) {
// Scale bands
var bands = ['B2','B3','B4','B5','B6','B7','B8','B8A','B11','B12'];
var scaled = image.select(bands).divide(10000);
image = image.addBands(scaled, null, true);
// Calculate kNDVI
var RED = image.select('B4');
var NIR = image.select('B8');
var sigma = 0.15;
var kndvi = NIR.subtract(RED).pow(2)
.divide(sigma * sigma * 4.0)
.tanh()
.rename('kndvi');
return image.addBands(kndvi);
}
// 5. Sentinel-2 ImageCollection
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
.filterBounds(site)
.filterDate('2019-01-01', '2019-12-31')
.map(maskS2clouds)
.map(addKNDVI);
// 6. Create composite and mask to grassland
var composite = s2.select(['kndvi']).mean().clip(site);
var compositeGrass = composite.mask(grassMask);
// 7. Perform clustering
var training = compositeGrass.sample({
region: site,
scale: 10,
numPixels: 1000,
seed: 1
});
var clusterer = ee.Clusterer.wekaKMeans(15).train(training);
var clustered = compositeGrass.cluster(clusterer);
// 8. Time Series Analysis - NOW PROPERLY DEFINED
var timeSeriesData = s2.map(function(image) {
// Add cluster band to each image
var withCluster = image.select('kndvi').addBands(clustered.select('cluster'));
// Calculate mean kNDVI per cluster
var stats = withCluster.reduceRegion({
reducer: ee.Reducer.mean().group({
groupField: 1,
groupName: 'cluster'
}),
geometry: site,
scale: 10,
maxPixels: 1e9
});
// Format results
var date = ee.Date(image.get('system:time_start')).format('YYYY-MM-dd');
var properties = {
'date': date,
'system:time_start': image.get('system:time_start')
};
// Add cluster means
var groups = ee.List(stats.get('groups'));
for (var i = 0; i < 15; i++) {
var clusterData = ee.Dictionary(groups.get(i));
var clusterNum = clusterData.get('cluster');
var meanVal = clusterData.get('mean');
properties['cluster_' + clusterNum] = meanVal;
}
return ee.Feature(null, properties);
});
// 9. Create and display chart
var chartData = timeSeriesData.map(function(feature) {
var date = feature.get('date');
var props = feature.toDictionary();
return ee.List.sequence(1, 15).map(function(clusterNum) {
return ee.Feature(null, {
'date': date,
'cluster': 'Cluster ' + clusterNum,
'kndvi': props.get('cluster_' + clusterNum)
});
});
}).flatten();
var chart = ui.Chart.feature.groups({
features: ee.FeatureCollection(chartData),
xProperty: 'date',
yProperty: 'kndvi',
seriesProperty: 'cluster'
}).setOptions({
title: 'kNDVI Time Series by Cluster',
hAxis: {title: 'Date'},
vAxis: {title: 'kNDVI'},
lineWidth: 1,
pointSize: 3
});
print(chart);
// 10. Export data
Export.table.toDrive({
collection: timeSeriesData,
description: 'Cluster_kNDVI_TimeSeries',
fileFormat: 'CSV',
selectors: ['date', 'system:time_start'].concat(
ee.List.sequence(1, 15).map(function(i) {return 'cluster_' + i;})
)
});
// 11. Display layers
Map.centerObject(site, 8);
Map.addLayer(clustered.randomVisualizer(), {}, 'Clusters');
Map.addLayer(site, {color: 'black'}, 'Study Area');
Map.addLayer(grassMask.updateMask(grassMask), {palette: ['green']}, 'Grassland Mask');