2

I'm calculating a time series for a 7-year period over a grid tessellation AOI. My goal is to have a CSV of zonal statistics for all features in a feature collection from 3 different bands in an image collection.

To do this, I'm running reduceRegion() on an image collection using a combined reducer, over a feature collection of 55,000 grid cells. The image collection has 3 different image bands (NDBI, NDVI, UI). I want to copy the six resulting reduction values (2 for each of the 3 bands) to each feature as properties, then export those values as columns in a csv. However, I am having trouble actually copying the results of the reduction to the feature collection as properties.

My Script (https://code.earthengine.google.com/b58961f43a176ea520400c36a4940de1)

What I have tried:

  1. A nested .map() function that reducesRegion by each image in a collection, and then maps those results to the feature collection (line 125-136):
// combine the max, mean reducers
var reducers = ee.Reducer.mean().combine({
  reducer2: ee.Reducer.count(),
  sharedInputs: true
});

var scale = ee.Image(l8sr_bands.first()).projection().nominalScale();

// try copying image bands as a feature property?
// this works w/o error, but no bands are copied to feature properties. reducer values must be returned or copied back to featureCol, but not sure where.
var l8_data = table.map(function(feature) {
  return l8sr_bands.map(function(image) {
    return ee.Feature(feature)
      image.reduceRegion({
        reducer: reducers,
        geometry: feature.geometry(),
        bestEffort: true,
        scale: scale
      }).copyProperties(image).copyProperties(feature);
  });
}).flatten();
print("output test 1: ", l8_data.first());
  1. Alternate code that copies the six reducer results (mean and sum for all 3 bands) as properties to the images (line 149-162). This does not do exactly what I want, because it produces results as properties of an image collection. I want to map these properties to the feature collection.
var reducers = ee.Reducer.mean().combine({
  reducer2: ee.Reducer.count(),
  sharedInputs: true
});

var scale = ee.Image(l8sr_bands.first()).projection().nominalScale();

var getStats = (function(img) {
            var image = ee.Image(img);
            var reducers = ee.Reducer.mean().combine({
                reducer2: ee.Reducer.count(),
                sharedInputs: true
                });
            var stats = image.reduceRegion({
                reducer: reducers,
                geometry: table.geometry(),
                bestEffort: true,
                scale: scale
                });
            return image.setMulti(stats);
})

// map all stats onto the feature collection while copying the image properties as feature properties
// result = sum and mean for each band as image properties. how do I map this to the feature collection?
var stats_fc = table.map(function(feature) {
  return l8sr_bands.map(getStats);
}).flatten();
print("output test 2: ", stats_fc.first());

I believe I'm missing a return or feature.set() statement somewhere to copy the reducer values back to the features, but I'm not sure where to do this. I'm new to GEE and JS, so I'm sure it's something simple.

I am aware that there are similar questions already on GIS SE, such as this question and ReduceRegions not writting to feature properties in GEE, as well as the developer's forum, but posted solutions have not worked for me.

4
  • I took a look at your code and it looks like your "Alternate code that copies the six reducer results" succeeds: the output features contain the six properties. I can see room for improvement in that code, but could you edit your question to explain how that is not the result you want? (Also, for Stack Exchange rules, you should make sure to put the code you're asking about — preferably concise but runnable — in the text of the question, not just behind a link. Otherwise your question may be closed and can't receive answers.)
    – Kevin Reid
    Commented Nov 25, 2020 at 19:06
  • Hi @KevinReid, thanks for your response & feedback. I edited my post to include code snippets and an explanation of why option 2 didn't do exactly what I want. the result maps the reduction to the image collection, but I want the results mapped to the feature collection.
    – jblng
    Commented Nov 25, 2020 at 19:56
  • Since there is more than one image, there are more than six properties — are you looking for (6 × number of images) columns on all the features? Or are you looking for one output feature per (input feature, input image) pair, which is what your code currently seems to be aiming for?
    – Kevin Reid
    Commented Nov 25, 2020 at 20:27
  • The latter; I am looking to have one output feature per input feature/image pair, with the six properties as columns
    – jblng
    Commented Nov 25, 2020 at 20:39

1 Answer 1

4

A nested .map() function that reducesRegion by each image in a collection, and then maps those results to the feature collection (line 125-136)

You wrote

  return l8sr_bands.map(function(image) {
    return ee.Feature(feature)
      image.reduceRegion({
        reducer: reducers,
        geometry: feature.geometry(),
        bestEffort: true,
        scale: scale
      }).copyProperties(image).copyProperties(feature);
  });

In this code, return ee.Feature(feature) is its own statement and therefore the original feature is returned and the reduceRegion is never executed.

There's a hint in the Code Editor about this: the sidebar on that line is telling you "Missing semicolon", which means there's a statement that wasn't ended with a semicolon — since you're in the good habit of including the semicolons, it telling you that you missed one is a sign that something is wrong.

Alternate code that copies the six reducer results (mean and sum for all 3 bands) as properties to the images (line 149-162).

var getStats = (function(img) {
            var image = ee.Image(img);
            var reducers = ee.Reducer.mean().combine({
                reducer2: ee.Reducer.count(),
                sharedInputs: true
                });
            var stats = image.reduceRegion({
                reducer: reducers,
                geometry: table.geometry(),
                bestEffort: true,
                scale: scale
                });
            return image.setMulti(stats);
});

var stats_fc = table.map(function(feature) {
  return l8sr_bands.map(getStats);
}).flatten();

This structure can work, but, you've used the entire table's geometry instead of the individual features, so every feature gets every other feature's results, and the feature properties aren't preserved — both of these because getStats doesn't get the feature to work with.

Let's fix the first version so it works the way you want:

var l8_data = table.map(function(feature) {
  return l8sr_bands.map(function(image) {
    var reduction = image.reduceRegion({
      reducer: reducers,
      geometry: feature.geometry(),
      scale: 500,
    });
    return feature             // Use the feature as base
      .setMulti(reduction)     // like your second try
      .copyProperties(image);  // also include image properties
  });
}).flatten();

Notice that I changed the scale to 500 meters — this is because the nominal scale value (111319) is so large that all of the pixels were falling outside of the region. I'm not sure why the scale is that large when the pixels are clearly much smaller — projection math isn't my strong point. You might find you want to set it even lower to sample more pixels, as this is getting only about 9 pixels per feature (as the count reducer indicates).

Beyond making it work, there's an easy way to make this more efficient. The reduceRegions operation is designed for reducing multiple regions in an image, getting separate results but more efficiently (since the image only needs to be loaded once). As a bonus, it adds the results as properties to the input features, so you don't need to use setMulti at all.

Since it wants one image and many features, we end up with the outermost operation being mapping over the image collection instead of the feature collection:

var l8_data = l8sr_bands.map(function(image) {
  return image.reduceRegions({
    reducer: reducers,
    collection: table,
    scale: 500,
  }).map(function (featureWithReduction) {
    return featureWithReduction.copyProperties(image);
  });
}).flatten();
1
  • Kevin, many many thanks! This is an excellent explanation, and answers my question exactly.
    – jblng
    Commented Nov 25, 2020 at 21:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.