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');