0

I am trying to temporally reduce an image from 365 bands (daily) to 12 bands (monthly).

Context: I imported an image collection with Soil Moisture (SM) that had 8 values per day (every three hours). I first reduced it to average daily values to calculate SM anomalies with this formula: SManom = (SM-SMmean)/ SMstdev

Then I created a new variable (irrigation events) that is 1 when there is an irrigation event and 0 if not. So now I have an image with one band for each day, and I want to reduce it to accumulated monthly values. This is the code that I have for now, below it I will post what I tried.

(This is actually not what I want to achieve (accumulate irrigation events) but it is a working example, otherwise the entire code would be too long. )

var dataset = ee.ImageCollection("NASA/GLDAS/V021/NOAH/G025/T3H"),
    geometry = ee.Geometry.Polygon(
        [[[21.742985018165424, 52.321076580783036],
          [21.742985018165424, 44.02344004522736],
          [40.28790689316542, 44.02344004522736],
          [40.28790689316542, 52.321076580783036]]], null, false);

//Import precipitation and soil moisture for a year.
var pcp = dataset.select('Rainf_tavg').filterDate('2019-01-01', '2020-01-01');
var sm = dataset.select('SoilMoi0_10cm_inst').filterDate('2019-01-01', '2020-01-01');

//Reduce SM to daily values and compute mean, std dev, and anomalies.   
var sm_daily = ee.ImageCollection.fromImages(days.map(function(d) {
      var filtered = sm.filter(ee.Filter.calendarRange({
        start: d,
        field: 'day_of_year'
      }));
      return filtered.mean().set('day', d);
    }));
    
var sm_mean = sm_daily.mean();
var sm_stdev = sm_daily.reduce(ee.Reducer.stdDev());
var sm_img = sm_daily.toBands();
var sm_anom = (sm_img.subtract(sm_mean)).divide(sm_stdev);

//Reducing precipitation to daily values.
var pcp_daily = ee.ImageCollection.fromImages(days.map(function(d) {
   var filtered = pcp.filter(ee.Filter.calendarRange({
     start: d,
     field: 'day_of_year'
   }));
   return filtered.mean().multiply(86400).set('day', d);
}));
    
var pcp_img = pcp_daily.toBands();

//Computing irrigation events.
var irrigation_event = pcp_img.lte(1).and(sm_anom.gte(1));

I tried to adapt the part of the code that I used to reduce to daily values but the fact that now it is an image rather than an image collection is problematic. The best solution I have found is to use slice on the image in order to return images from specifically selected bands and creating an image collection from those images. I think this could work but I'm having trouble selecting bands in an iterative manner. I also tried creating an array with lists that contain sequences of the days of each month but that didn't work since the lists have to be the same length.

var months = ee.List.sequence(1, 12);
//Here I want to map this function to the variable months but I can't seem to figure out how to select 
//the right bands in every iteration.
var irr_jan_acc = ee.ImageCollection.fromImages(months.map(function(g) {
  var filtered = irrigation_event.slice(g);
  return filtered.reduce(ee.Reducer.sum());
}));

I am quite new to GEE and especially to iterations.

1 Answer 1

1

I think it would be easiest to actually write a system:time_start variable when you reduce to days. And then instead of calling .toBands() you can .map() over the Image Collection to subtract the mean:

//Reduce SM to daily values and compute mean, std dev, and anomalies.   
var sm_daily = ee.ImageCollection.fromImages(days.map(function(d) {
      var filtered = sm.filter(ee.Filter.calendarRange({
        start: d,
        field: 'day_of_year'
      }));
      var date = filtered.first().get('system:time_start')
      return filtered
        .mean()
        .set('day', d)
        .set('system:time_start', date);
    }));
    
var sm_mean = sm_daily.mean();
var sm_stdev = sm_daily.reduce(ee.Reducer.stdDev());
var sm_anom = sm_daily.map(function(image){
  return (image.subtract(sm_mean)).divide(sm_stdev);
})

You have to do the same conversion to mapping over a image collection instead of using .toBands() also for pcp_img and irrigation_event (Try it yourself first and if you need help ask). Then you can use the same method as before with the days to also reduce the months:

var months = ee.List.sequence(1, 12);
//Here I want to map this function to the variable months but I can't seem to figure out how to select 
//the right bands in every iteration.
var irr_jan_acc = ee.ImageCollection.fromImages(months.map(function(g) {
  var filtered = sm.filter(ee.Filter.calendarRange({
        start: g,
        field: 'month'
      }));
  return filtered.sum().set('month', g);
}));

EDIT:

This would work for you. It could be nicer, because I didn't notice that sm and pcp come from the same Image Collection but it works nonetheless. Basically everything is done on a per Image basis. In general a good way of programming in Google Earth Engine is to just do the process for one Image of an image collection using .first() and then apply the same method over all images with .map().

//Import precipitation and soil moisture for a year.
var pcp = dataset.select('Rainf_tavg').filterDate('2019-01-01', '2020-01-01');
var sm = dataset.select('SoilMoi0_10cm_inst').filterDate('2019-01-01', '2020-01-01');

var days = ee.List.sequence(1,365)

//Reduce SM to daily values and compute mean, std dev, and anomalies.   
var daily = ee.ImageCollection.fromImages(days.map(function(d) {
      var image = sm.filter(ee.Filter.calendarRange({
            start: d,
            field: 'day_of_year'
          })).mean().rename('sm')
        .addBands(
          pcp.filter(ee.Filter.calendarRange({
            start: d,
            field: 'day_of_year'
          })).mean().multiply(86400).rename('pcp')
        )
      
      return image
        .set('day', d)
    }));

var sm_mean = daily.select('sm').mean()
var sm_stdev = daily.select('sm').reduce(ee.Reducer.stdDev());

daily = daily.map(function(image){
  var sm_anom = image
    .addBands(image
      .select('sm')
      .subtract(sm_mean)
      .divide(sm_stdev)
      .rename('sm_anom')
    )
  
  return sm_anom.addBands(image
      .select('pcp').lte(1)
      .and(sm_anom.select('sm_anom').gte(1))
      .rename('irrigation_event')
    )
})

print(daily)
9
  • Thanks! However the sm_anom variable is just one variable that enters in the calculation of irrigation events. For other variables, I need to do math between image collections, that's why I decided to call .toBands()... or is it possible to do math between image collections? Jan 15, 2021 at 14:21
  • So do you mean like var irrigation_event = pcp_img.lte(1).and(sm_anom.gte(1));? If all the image collections are daily then the best way to do that would probably be to combine them into one imagecollection, where every image has multiple bands with the data you need. Then you can map over it without any issue
    – JonasV
    Jan 15, 2021 at 16:25
  • So actually I have variables for daily evapotranspiration, runoff, precipitation (which are all image collections, in the same unit) and one variable for the derivative of soil moisture (image). To get irrigation, the calculation is: Irrig = ET + runoff + dSM - pcp. Quesiton is, how can I have Irrig as an image collection? Because these variables are a mix of image and image collection I'm at a loss on how to be able to combine them. That is why I converted everything to images in the beginning with .toBands(). Jan 19, 2021 at 14:04
  • If ET, runoff and precipitation are all daily, then have a single ImageCollection with ET, runoff and precipitation as bands. In the end you really need to keep them as Image Collections instead of using .toBands() otherwise it all gets to complicated
    – JonasV
    Jan 19, 2021 at 14:39
  • So I have 365 images in each collection. When merged I have a collection of 1095 images. How can I then do the calculation between the images themselves (images of ET + images of runoff + images of pcp)? In order to have an image collection with 365 images again. Jan 19, 2021 at 15:01

Your Answer

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

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