You are filtering year in the correct way. This is how I'd do it:
//Load and filter the Hansen data
var gfc2014 = ee.Image('UMD/hansen/global_forest_change_2015')
.select(['treecover2000','loss','gain','lossyear']);
// list for filter iteration
var years = ee.List.sequence(1, 14)
// turn your scale into a var in case you want to change it
var scale = gfc2014.projection().nominalScale()
//add country districts as a feature collection
var distr = ee.FeatureCollection('ft:1U7sXFHXtxQ--g7XMeXlvPhNXPBcDtPg8Yzr2pvsg', 'geometry');
//look at tree cover, find the area
var treeCover = gfc2014.select(['treecover2000']);
// most recent version of Hansen's data has the treecover2000 layer
// ranging from 0-100. It needs to be divided by 100 if ones wants
// to calculate the areas in ha and not hundreds of ha. If not, the
// layers areaLoss/areaGain are not comparable to the areaCover. Thus
treeCover = treeCover.divide(100); // Thanks to Bruno
var areaCover = treeCover.multiply(ee.Image.pixelArea())
.divide(10000).select([0],["areacover"])
// total loss area
var loss = gfc2014.select(['loss']);
var areaLoss = loss.gt(0).multiply(ee.Image.pixelArea()).multiply(treeCover)
.divide(10000).select([0],["arealoss"]);
// total gain area
var gain = gfc2014.select(['gain'])
var areaGain = gain.gt(0).multiply(ee.Image.pixelArea()).multiply(treeCover)
.divide(10000).select([0],["areagain"]);
// final image
var total = gfc2014.addBands(areaCover)
.addBands(areaLoss)
.addBands(areaGain)
Map.addLayer(total,{},"total")
// Map cover area per feature
var districtSums = areaCover.reduceRegions({
collection: distr,
reducer: ee.Reducer.sum(),
scale: scale,
});
var addVar = function(feature) {
// function to iterate over the sequence of years
var addVarYear = function(year, feat) {
// cast var
year = ee.Number(year).toInt()
feat = ee.Feature(feat)
// actual year to write as property
var actual_year = ee.Number(2000).add(year)
// filter year:
// 1st: get mask
var filtered = total.select("lossyear").eq(year)
// 2nd: apply mask
filtered = total.updateMask(filtered)
// reduce variables over the feature
var reduc = filtered.reduceRegion({
geometry: feature.geometry(),
reducer: ee.Reducer.sum(),
scale: scale,
maxPixels: 1e13
})
// get results
var loss = ee.Number(reduc.get("arealoss"))
var gain = ee.Number(reduc.get("areagain"))
// set names
var nameloss = ee.String("loss_").cat(actual_year)
var namegain = ee.String("gain_").cat(actual_year)
// alternative 1: set property only if change greater than 0
var cond = loss.gt(0).or(gain.gt(0))
return ee.Algorithms.If(cond,
feat.set(nameloss, loss, namegain, gain),
feat)
// alternative 2: always set property
// set properties to the feature
// return feat.set(nameloss, loss, namegain, gain)
}
// iterate over the sequence
var newfeat = ee.Feature(years.iterate(addVarYear, feature))
// return feature with new properties
return newfeat
}
// Map over the FeatureCollection
var areas = districtSums.map(addVar);
Map.addLayer(areas, {}, "areas")
In that script you get 3 fields: loss_{year}, gain_{year}, sum
But if you want better 4 fields: loss, gain, year, sum; change for:
return ee.Algorithms.If(cond,
feat.set("loss", loss, "gain", gain, "year", actual_year),
feat)
You could also compute percentage and set it to the features.
Edit:
Thank to @Bruno_Conte_Leite, who made me reconsider my answer, I have made some updates, the one suggested by Bruno and others.
Scale:
I suggest to keep the original scale of Hansen data.
treeCover:
most recent version of Hansen's data has the treecover2000 layer ranging from 0-100. It needs to be divided by 100 if ones wants to calculate the areas in ha and not hundreds of ha. (Bruno)
areaLoss and areaGain:
Added .multiply(treeCover)
otherwise the area would be of the whole pixel and not of the indicated percentage
maxPixels: I added maxPixels: 1e13
in the reduction
As far as I know you cannot recurse without using client-side code. A workaround to this is to iterate at least as many times as the depth of your recursion. I'm giving an example how this can be done in JavaScript - quicker for me than in Python - but it should translate directly to Python too.
var geometry = ee.Geometry.Point([-73.83275782083713, 3.532610786227175])
var base_level = ee.FeatureCollection("WWF/HydroSHEDS/v1/Basins/hybas_5")
// Since we cannot do recursion without getInfo() or evaluate(),
// iterate a bunch of times, and hope it's enough
var maxSteps = 100
var merged = ee.List(ee.List.sequence(1, maxSteps)
.iterate( // Follow links of NEXT_DOWN and collect a list of feature collections
function(i, acc) {
// Working with feature collections instead of single feature, so that when
// there is no NEXT_DOWN, we don't have to treat that as a separate condition.
// It's simply an empty collection.
acc = ee.List(acc)
var featureCollection = ee.FeatureCollection(acc.get(acc.size().subtract(1))) // Last in accumulator
var nextIds = featureCollection.aggregate_array('NEXT_DOWN')
var nextFeatureCollection = base_level.filter(ee.Filter.inList('HYBAS_ID', nextIds))
return acc.add(nextFeatureCollection)
},
[base_level.filterBounds(geometry)] // The start feature(s)
))
.iterate( // Merge list of feature collections into a single feature collection
function(featureCollection, acc) {
return ee.FeatureCollection(acc)
.merge(ee.FeatureCollection(featureCollection))
},
ee.FeatureCollection([])
)
print(merged)
https://code.earthengine.google.com/0fee9de99173f3252ca2b188a7570d5a
Best Answer
I found that on the feature collection fc the fc.aggregate_array() function does the trick, see Debugging Guide of Earth Engine and search for "Collection.map: A mapped algorithm must return a Feature or Image."
The code:
The output is as expected: ['Western', 'Ashanti', ...]