Lab 17 - Health Applications Part 1

This Lab is adapted from work by Dawn Nekorchuk a Ph.D. alum of UF Geography - https://geog.ufl.edu/alumni-2/nekorchuk/ 

Objective

This lab aims to demonstrate how Google Earth Engine (GEE) can be used to support the modeling and forecasting of vector-borne infectious diseases, such as malaria. In this lab, you will learn how GEE can be used to gather data for subsequent analyses outside of GEE and how the results of these analyses can then be brought back into GEE.

We will calculate and export data on remotely-sensed environmental variables, such as precipitation, temperature, and vegetation water index. These factors can impact mosquito life cycles, malaria parasites, and transmission dynamics. These data can then be used in R for modeling and forecasting malaria in the Amhara region of Ethiopia, using the Epidemic Prognosis Incorporating Disease and Environmental Monitoring for Integrated Assessment (EPIDEMIA) system developed by the EcoGRAPH research group at the University of Oklahoma.

Learning Outcomes 

Background

Vector-borne diseases

Vector-borne diseases cause more than 700,000 deaths annually, of which approximately 400,000 are due to malaria, a parasitic infection by Anopheles mosquitoes (World Health Organization 2018, 2020). The WHO estimates around 229 million clinical malaria cases worldwide in 2019 (WHO 2020). Environmental factors, including temperature, humidity, and rainfall, are known to be important determinants of malaria risk as these affect mosquito and parasite development and life cycles, including larval habitats, mosquito fecundity, growth rates, mortality, and Plasmodium parasite development rates within the mosquito vector (Franklinos et al. 2019, Jones 2008, Wimberly et al. 2021). 

Remote Sensing

Data from Earth-observing satellites can be used to monitor spatial and temporal changes in environmental factors like temperature, humidity, and rainfall (Ford et al., 2009). These data can be incorporated into disease modeling, usually as lagged functions, to help develop early warning systems for forecasting outbreaks (Wimberly et al. 2021, 2022). Accurate forecasts would allow limited resources for prevention and control to be more efficiently and effectively targeted at appropriate locations and times (WHO 2018). 

Forecasting and Impact of Cloud-Based Remote Sensing

To implement near-real-time forecasting, meteorological and climatic data must be acquired, processed, and integrated regularly and frequently. Over the past ten years, the Epidemic Prognosis Incorporating Disease and Environmental Monitoring for Integrated Assessment (EPIDEMIA) project has developed and tested a malaria forecasting system that integrates public health surveillance with monitoring environmental and climate conditions. Since 2018 the environmental data has been acquired using Earth Engine scripts and apps (Wimberly et al. 2022). In 2019 a local team at Bahir Dar University in Ethiopia had been using EPIDEMIA with near-real-time epidemiological data to generate weekly malaria early warning reports in the Amhara region of Ethiopia.

Climate Change

In this example, we look at near-real-time environmental conditions affecting disease vectors and human transmission dynamics. On longer time scales, climate change can alter vector-borne disease transmission cycles and the geographic distributions of various vector and host species (Franklinos 2019). Health applications involving Earth Engine data likely align with a One Health approach to complex health issues. Under One Health, a core assumption is that environmental, animal, and human health are inextricably linked (Mackenzie and Jeggo 2019). 

Part 1 - Getting the base Data into GEE

Start up your Earth Engine Code Editor by visiting https://code.earthengine.google.com/ and signing in. 

To start, we must import the data we will be working with. The first item is an external asset of our study area—these are woredas in the Amhara region of Ethiopia. A woreda is an administrative division of Ethiopia, roughly equivalent to a county in the United States. The woredas are subdivided into kebeles, roughly equivalent to municipalities or townships. Isn't Geography Fun?

To do this, copy and paste the following code.

//import data
var woredas = ee.FeatureCollection("users/sounny/amhara_woredas_20170207");

// Create region outer boundary to filter products on.
var amhara = woredas.geometry().bounds();

The first line of code imports a feature collection of woredas in the Amhara region of Ethiopia from the user "sounny". The second line of code creates a boundary for the region of Amhara by calling the .geometry() method on the woredas feature collection and then calling the .bounds() method on the resulting geometry.

The .geometry() method returns the geometry of the feature collection. The .bounds() method returns the bounds of the geometry. The bounds of geometry are the minimum and maximum coordinates of the geometry.

In this case, the bounds of the amhara variable will be the minimum and maximum coordinates of the woredas in the Amhara region of Ethiopia. We will use the boundary to filter remote-sensing products later on the region of Amhara.

Now we can take a look at the woreda boundaries by adding the following code to draw it onto the map

Copy and paste the following code next. 

// Visualize woredas with black borders and no fill.
// Create an empty image into which to paint the features, cast to byte.
var empty = ee.Image().byte();
// Paint all the polygon edges with the same number and width.
var outline = empty.paint({
    featureCollection: woredas,
    color: 1,
    width: 1
});
// Add woreda boundaries to the map.
Map.setCenter(38, 11.5, 7);
Map.addLayer(outline, {
    palette: '000000'
}, 'Woredas');

Run your code, and you should see something similar to this. 

screenshot of the asset displayed

Now we will add in four remotely sensed data that we will be processing: 

Copy and Paste the following code to your script. 

var gpm = ee.ImageCollection('NASA/GPM_L3/IMERG_V06');
var LSTTerra8 = ee.ImageCollection('MODIS/061/MOD11A2')
    // Due to MCST outage, only use dates after this for this script.
    .filterDate('2001-06-26', Date.now());
var brdfReflect = ee.ImageCollection('MODIS/006/MCD43A4');
var brdfQa = ee.ImageCollection('MODIS/006/MCD43A2');

The first line of code loads an image collection called 'NASA/GPM_L3/IMERG_V06' and assigns it to a variable called 'gpm'. This image collection contains data from the Integrated Multi-satellitE Retrievals for GPM (IMERG) product, produced by NASA's Global Precipitation Measurement (GPM) mission. The IMERG product provides global precipitation estimates at a high spatial and temporal resolution.

The second line of code loads an image collection called 'MODIS/061/MOD11A2' and assigns it to a variable called 'LSTTerra8'. This image collection contains Land Surface Temperature (LST) data from the Moderate Resolution Imaging Spectroradiometer (MODIS) instrument aboard the Terra satellite. The script filters the image collection only to include dates on or after '2001-06-26' up to the current date, which is returned by the 'Date.now()' function.

The third line of code loads an image collection called 'MODIS/006/MCD43A4' and assigns it to a variable called 'brdfReflect'. This image collection contains Bidirectional Reflectance Distribution Function (BRDF) parameters for each pixel on Earth's surface derived from the MODIS instrument.

The fourth line of code loads an image collection called 'MODIS/006/MCD43A2' and assigns it to a variable called 'brdfQa'. This image collection contains the quality assurance (QA) data for the BRDF parameters in the 'brdfReflect' image collection.

These four image collections contain remote sensing data for precipitation, land surface temperature, and BRDF parameters, which we will use for modeling and forecasting Vector-borne diseases.

Part 2 - Data Prep

Now we are ready to start prepping our data, filtering it by space and time to get the images we need to model.  To make our life easier, we will make some variables for start and end times up front that we can modify in the future if we would like to create datasets for different periods. 

Place this code at the end of tour code in the code editor.

//////////////////////////////////////////////////////////////////
// Part 2: Handling of dates

// This is where we define Requested start and end dates, by making variables here we make it easier to modify in the future.
var reqStartDate = ee.Date('2021-10-01');
var reqEndDate = ee.Date('2021-11-30');

Now Some notes about our data:

Copy and paste the following code into your code editor:

// Note on Land Surface Temperature (LST) Dates
// LST MODIS is every 8 days, and a user-requested date will likely not match.
// We want to get the latest previous image date,
//  i.e. the date the closest, but prior to, the requested date. 
// We will filter later. 
// Get date of first image.
var LSTEarliestDate = LSTTerra8.first().date();
// Filter collection to dates from beginning to requested start date. 
var priorLstImgCol = LSTTerra8.filterDate(LSTEarliestDate,
    reqStartDate);
// Get the latest (max) date of this collection of earlier images.
var LSTPrevMax = priorLstImgCol.reduceColumns({
    reducer: ee.Reducer.max(),
    selectors: ['system:time_start']
});
var LSTStartDate = ee.Date(LSTPrevMax.get('max'));
print('LSTStartDate', LSTStartDate);

The code calculates the latest previous LST image date. LST MODIS is every 8 days, so a user-requested date will likely not match an LST date. The code first gets the date of the first LST image. Then, it filters the LST collection to dates from the beginning to the requested start date. Next, it gets this collection's latest (max) date of earlier images. Finally, it prints the LST start date.

Now that we have the LST data let's get the Precipitation.

Place the following code at the end of your script

// Last available data dates
// Different variables have different data lags. 
// Data may not be available in user range.
// To prevent errors from stopping script, 
//  grab last available (if relevant) & filter at end.

//Precipitation 
// Calculate date of most recent measurement for gpm (of all time).
var gpmAllMax = gpm.reduceColumns(ee.Reducer.max(), [
    'system:time_start'
]);
var gpmAllEndDateTime = ee.Date(gpmAllMax.get('max'));
// GPM every 30 minutes, so get just date part.
var gpmAllEndDate = ee.Date.fromYMD({
    year: gpmAllEndDateTime.get('year'),
    month: gpmAllEndDateTime.get('month'),
    day: gpmAllEndDateTime.get('day')
});

// If data ends before requested start, take last data date,
// otherwise use requested date.
var precipStartDate = ee.Date(gpmAllEndDate.millis()
    .min(reqStartDate.millis()));
print('precipStartDate', precipStartDate);

The code calculates the latest available precipitation date. GPM data is every 30 minutes, so the code first gets the most recent measurement date for GPM. Then, it gets just the date part of the date. Next, it checks if the data ends before the requested start date. If it does, the code takes the last data date. Otherwise, the code uses the requested date. Finally, the code prints the precipitation start date.

Now we move on to get the final BRDF data. 

Place this code into your editor. 

// BRDF 
// Calculate date of most recent measurement for brdf (of all time).
var brdfAllMax = brdfReflect.reduceColumns({
    reducer: ee.Reducer.max(),
    selectors: ['system:time_start']
});
var brdfAllEndDate = ee.Date(brdfAllMax.get('max'));
// If data ends before requested start, take last data date,
// otherwise use the requested date. 
var brdfStartDate = ee.Date(brdfAllEndDate.millis()
    .min(reqStartDate.millis()));
print('brdfStartDate', brdfStartDate);
print('brdfEndDate', brdfAllEndDate);

This code works like before with Parciptiatoin. The code calculates the latest available BRDF date. BRDF data is every 30 minutes, so the code first gets the most recent measurement date for BRDF. Then, it gets just the date part of the date. Next, it checks if the data ends before the requested start date. If it does, the code takes the last data date. Otherwise, the code uses the requested date. Finally, the code prints the BRDF start date and end date.

Part 3 - Zonal Stats on Precipitation

Precipitation Filtering and Dates

We will calculate our precipitation variable for the appropriate date range and then perform a zonal summary of our woredas. Using the dates when data exists in the user-requested date range, we create a list of dates to calculate our variable.

Place the following code at the end of your script. 

// Part 3: Precipitation

// Part 3.1: Precipitation filtering and dates

// Filter gpm by date, using modified start if necessary.
var gpmFiltered = gpm
    .filterDate(precipStartDate, reqEndDate.advance(1, 'day'))
    .filterBounds(amhara)
    .select('precipitationCal');

// Calculate date of most recent measurement for gpm 
// (in the modified requested window).
var gpmMax = gpmFiltered.reduceColumns({
    reducer: ee.Reducer.max(),
    selectors: ['system:time_start']
});
var gpmEndDate = ee.Date(gpmMax.get('max'));
var precipEndDate = gpmEndDate;
print('precipEndDate ', precipEndDate);

// Create a list of dates for the precipitation time series.
var precipDays = precipEndDate.difference(precipStartDate, 'day');
var precipDatesPrep = ee.List.sequence(0, precipDays, 1);

function makePrecipDates(n) {
    return precipStartDate.advance(n, 'day');
}
var precipDates = precipDatesPrep.map(makePrecipDates);
Calculate Daily Precipitation

Now we will map a function over our filtered FeatureCollection (gpmFiltered) to calculate the total daily rainfall per day. In this product, precipitation in millimeters per hour is recorded every half hour, so we will sum the day and divide by two. 

// part 3.2: Calculate daily precipitation

// Function to calculate daily precipitation:
function calcDailyPrecip(curdate) {
    curdate = ee.Date(curdate);
    var curyear = curdate.get('year');
    var curdoy = curdate.getRelative('day', 'year').add(1);
    var totprec = gpmFiltered
        .filterDate(curdate, curdate.advance(1, 'day'))
        .select('precipitationCal')
        .sum()
        //every half-hour
        .multiply(0.5)
        .rename('totprec');

    return totprec
        .set('doy', curdoy)
        .set('year', curyear)
        .set('system:time_start', curdate);
}
// Map function over list of dates.
var dailyPrecipExtended =
    ee.ImageCollection.fromImages(precipDates.map(calcDailyPrecip));

// Filter back to the original user requested start date.
var dailyPrecip = dailyPrecipExtended
    .filterDate(reqStartDate, precipEndDate.advance(1, 'day'));
Summarize Daily Precipitation by Woreda

In the last section for precipitation, we will calculate a zonal summary, a mean, of the rainfall per woreda and flatten it for export as a CSV.  The final exports will be done later in the lab. 

// Part 3.3: Summarize daily precipitation by woreda

// Filter precip data for zonal summaries.
var precipSummary = dailyPrecip
    .filterDate(reqStartDate, reqEndDate.advance(1, 'day'));

// Function to calculate zonal statistics for precipitation by woreda.
function sumZonalPrecip(image) {
    // To get the doy and year, 
    // convert the metadata to grids and then summarize.
    var image2 = image.addBands([
        image.metadata('doy').int(),
        image.metadata('year').int()
    ]);
    // Reduce by regions to get zonal means for each county.
    var output = image2.select(['year', 'doy', 'totprec'])
        .reduceRegions({
            collection: woredas,
            reducer: ee.Reducer.mean(),
            scale: 1000
        });
    return output;
}
// Map the zonal statistics function over the filtered precip data.
var precipWoreda = precipSummary.map(sumZonalPrecip);
// Flatten the results for export.
var precipFlat = precipWoreda.flatten();

Part 4 Land Surface Temperature

We will follow a similar pattern of steps for land surface temperatures, though first, we will calculate the variable (mean LST). Then we will calculate the daily values and summarize them by woreda

Calculate LST Variables

We will use the daytime and nighttime observed values to calculate a mean value for the day. We will use the quality layers to mask out poor-quality pixels. Working with the bitmask below, we are taking advantage of bits 6 and 7 being at the end, so the rightShift(6) just returns these two. Then we check if they are less than or equal to 2, meaning average LST error <= 3k (see MODIS documentation for the meaning of each element in the bit sequence). For more information on how to use bitmasks in other situations. To convert the pixel values, we will use the scaling factor in the data product (0.2) and convert from Kelvin to Celsius values (−273.15). 

// Part 4: Land surface temperature

// Part 4.1: Calculate LST variables

// Filter Terra LST by altered LST start date.
// Rarely, but at the end of the year if the last image is late in the year
//  with only a few days in its period, it will sometimes not grab 
//  the next image. Add extra padding to reqEndDate and
//  it will be trimmed at the end.
var LSTFiltered = LSTTerra8
    .filterDate(LSTStartDate, reqEndDate.advance(8, 'day'))
    .filterBounds(amhara)
    .select('LST_Day_1km', 'QC_Day', 'LST_Night_1km', 'QC_Night');

// Filter Terra LST by QA information.
function filterLstQa(image) {
    var qaday = image.select(['QC_Day']);
    var qanight = image.select(['QC_Night']);
    var dayshift = qaday.rightShift(6);
    var nightshift = qanight.rightShift(6);
    var daymask = dayshift.lte(2);
    var nightmask = nightshift.lte(2);
    var outimage = ee.Image(image.select(['LST_Day_1km',
        'LST_Night_1km'
    ]));
    var outmask = ee.Image([daymask, nightmask]);
    return outimage.updateMask(outmask);
}
var LSTFilteredQa = LSTFiltered.map(filterLstQa);

// Rescale temperature data and convert to degrees Celsius (C).
function rescaleLst(image) {
    var LST_day = image.select('LST_Day_1km')
        .multiply(0.02)
        .subtract(273.15)
        .rename('LST_day');
    var LST_night = image.select('LST_Night_1km')
        .multiply(0.02)
        .subtract(273.15)
        .rename('LST_night');
    var LST_mean = image.expression(
        '(day + night) / 2', {
            'day': LST_day.select('LST_day'),
            'night': LST_night.select('LST_night')
        }
    ).rename('LST_mean');
    return image.addBands(LST_day)
        .addBands(LST_night)
        .addBands(LST_mean);
}
var LSTVars = LSTFilteredQa.map(rescaleLst);
Calculate Daily LST

Now, using a mapped function over our filtered collection, we will calculate a daily value from the 8-day composite value by assigning each of the eight days the value of the composite. We will also filter to our user-requested dates, as data exists in that range.

// part 4.2: Calculate daily LST

// Create list of dates for time series.
var LSTRange = LSTVars.reduceColumns({
    reducer: ee.Reducer.max(),
    selectors: ['system:time_start']
});
var LSTEndDate = ee.Date(LSTRange.get('max')).advance(7, 'day');
var LSTDays = LSTEndDate.difference(LSTStartDate, 'day');
var LSTDatesPrep = ee.List.sequence(0, LSTDays, 1);

function makeLstDates(n) {
    return LSTStartDate.advance(n, 'day');
}
var LSTDates = LSTDatesPrep.map(makeLstDates);

// Function to calculate daily LST by assigning the 8-day composite summary 
// to each day in the composite period:
function calcDailyLst(curdate) {
    var curyear = ee.Date(curdate).get('year');
    var curdoy = ee.Date(curdate).getRelative('day', 'year').add(1);
    var moddoy = curdoy.divide(8).ceil().subtract(1).multiply(8).add(
        1);
    var basedate = ee.Date.fromYMD(curyear, 1, 1);
    var moddate = basedate.advance(moddoy.subtract(1), 'day');
    var LST_day = LSTVars
        .select('LST_day')
        .filterDate(moddate, moddate.advance(1, 'day'))
        .first()
        .rename('LST_day');
    var LST_night = LSTVars
        .select('LST_night')
        .filterDate(moddate, moddate.advance(1, 'day'))
        .first()
        .rename('LST_night');
    var LST_mean = LSTVars
        .select('LST_mean')
        .filterDate(moddate, moddate.advance(1, 'day'))
        .first()
        .rename('LST_mean');
    return LST_day
        .addBands(LST_night)
        .addBands(LST_mean)
        .set('doy', curdoy)
        .set('year', curyear)
        .set('system:time_start', curdate);
}
// Map the function over the image collection
var dailyLstExtended =
    ee.ImageCollection.fromImages(LSTDates.map(calcDailyLst));

// Filter back to original user requested start date
var dailyLst = dailyLstExtended
    .filterDate(reqStartDate, LSTEndDate.advance(1, 'day'));
Summarize Daily LST by Woreda

In the final section for LST, we will perform a zonal mean of the temperature to our woredas and flatten in preparation for export as CSV.

// Part 4.3: Summarize daily LST by woreda

// Filter LST data for zonal summaries. 
var LSTSummary = dailyLst
    .filterDate(reqStartDate, reqEndDate.advance(1, 'day'));
// Function to calculate zonal statistics for LST by woreda:
function sumZonalLst(image) {
    // To get the doy and year, we convert the metadata to grids 
    //  and then summarize. 
    var image2 = image.addBands([
        image.metadata('doy').int(),
        image.metadata('year').int()
    ]);
    // Reduce by regions to get zonal means for each county.
    var output = image2
        .select(['doy', 'year', 'LST_day', 'LST_night', 'LST_mean'])
        .reduceRegions({
            collection: woredas,
            reducer: ee.Reducer.mean(),
            scale: 1000
        });
    return output;
}
// Map the zonal statistics function over the filtered LST data.
var LSTWoreda = LSTSummary.map(sumZonalLst);
// Flatten the results for export.
var LSTFlat = LSTWoreda.flatten();

Part 5. Spectral Index: NDWI

We will follow a similar pattern of steps for our spectral index, NDWI, as we did for precipitation and land surface temperatures: first, calculate the variable(s), then calculate the daily values, and finally summarize by woreda

Section 5.1. Calculate NDWI

Here we will focus on NDWI, which we actively used to forecast malaria. The MODIS MCD43A4 product contains simplified band quality information, and it is recommended to use the additional quality information in the MCD43A2 product for your particular application. We will join these two products to apply our selected quality information. (Note that we do not have to worry about snow in our study area.)

// Part 5: Spectral index NDWI

// Part 5.1: Calculate NDWI

// Filter BRDF-Adjusted Reflectance by date.
var brdfReflectVars = brdfReflect
    .filterDate(brdfStartDate, reqEndDate.advance(1, 'day'))
    .filterBounds(amhara)
    .select([
            'Nadir_Reflectance_Band1', 'Nadir_Reflectance_Band2',
            'Nadir_Reflectance_Band3', 'Nadir_Reflectance_Band4',
            'Nadir_Reflectance_Band5', 'Nadir_Reflectance_Band6',
            'Nadir_Reflectance_Band7'
        ],
        ['red', 'nir', 'blue', 'green', 'swir1', 'swir2', 'swir3']);

// Filter BRDF QA by date.
var brdfReflectQa = brdfQa
    .filterDate(brdfStartDate, reqEndDate.advance(1, 'day'))
    .filterBounds(amhara)
    .select([
            'BRDF_Albedo_Band_Quality_Band1',
            'BRDF_Albedo_Band_Quality_Band2',
            'BRDF_Albedo_Band_Quality_Band3',
            'BRDF_Albedo_Band_Quality_Band4',
            'BRDF_Albedo_Band_Quality_Band5',
            'BRDF_Albedo_Band_Quality_Band6',
            'BRDF_Albedo_Band_Quality_Band7',
            'BRDF_Albedo_LandWaterType'
        ],
        ['qa1', 'qa2', 'qa3', 'qa4', 'qa5', 'qa6', 'qa7', 'water']);

// Join the 2 collections. 
var idJoin = ee.Filter.equals({
    leftField: 'system:time_end',
    rightField: 'system:time_end'
});
// Define the join. 
var innerJoin = ee.Join.inner('NBAR', 'QA');
// Apply the join. 
var brdfJoined = innerJoin.apply(brdfReflectVars, brdfReflectQa,
    idJoin);

// Add QA bands to the NBAR collection. 
function addQaBands(image) {
    var nbar = ee.Image(image.get('NBAR'));
    var qa = ee.Image(image.get('QA')).select(['qa2']);
    var water = ee.Image(image.get('QA')).select(['water']);
    return nbar.addBands([qa, water]);
}
var brdfMerged = ee.ImageCollection(brdfJoined.map(addQaBands));

// Function to mask out pixels based on QA and water/land flags. 
function filterBrdf(image) {
    // Using QA info for the NIR band. 
    var qaband = image.select(['qa2']);
    var wband = image.select(['water']);
    var qamask = qaband.lte(2).and(wband.eq(1));
    var nir_r = image.select('nir').multiply(0.0001).rename('nir_r');
    var swir2_r = image.select('swir2').multiply(0.0001).rename(
        'swir2_r');
    return image.addBands(nir_r)
        .addBands(swir2_r)
        .updateMask(qamask);
}
var brdfFilteredVars = brdfMerged.map(filterBrdf);

// Function to calculate spectral indices:
function calcBrdfIndices(image) {
    var curyear = ee.Date(image.get('system:time_start')).get('year');
    var curdoy = ee.Date(image.get('system:time_start'))
        .getRelative('day', 'year').add(1);
    var ndwi6 = image.normalizedDifference(['nir_r', 'swir2_r'])
        .rename('ndwi6');
    return image.addBands(ndwi6)
        .set('doy', curdoy)
        .set('year', curyear);
}
// Map function over image collection. 
brdfFilteredVars = brdfFilteredVars.map(calcBrdfIndices);
Calculate Daily NDWI

Like the other variables, we will calculate a daily value and filter to our user-requested dates, as data exists in that range.

// Part 5.2: Calculate daily NDWI

// Create list of dates for full time series.
var brdfRange = brdfFilteredVars.reduceColumns({
    reducer: ee.Reducer.max(),
    selectors: ['system:time_start']
});
var brdfEndDate = ee.Date(brdfRange.get('max'));
var brdfDays = brdfEndDate.difference(brdfStartDate, 'day');
var brdfDatesPrep = ee.List.sequence(0, brdfDays, 1);

function makeBrdfDates(n) {
    return brdfStartDate.advance(n, 'day');
}
var brdfDates = brdfDatesPrep.map(makeBrdfDates);

// List of dates that exist in BRDF data.
var brdfDatesExist = brdfFilteredVars
    .aggregate_array('system:time_start');

// Get daily brdf values.
function calcDailyBrdfExists(curdate) {
    curdate = ee.Date(curdate);
    var curyear = curdate.get('year');
    var curdoy = curdate.getRelative('day', 'year').add(1);
    var brdfTemp = brdfFilteredVars
        .filterDate(curdate, curdate.advance(1, 'day'));
    var outImg = brdfTemp.first();
    return outImg;
}
var dailyBrdfExtExists =
    ee.ImageCollection.fromImages(brdfDatesExist.map(
        calcDailyBrdfExists));

// Create empty results, to fill in dates when BRDF data does not exist.
function calcDailyBrdfFiller(curdate) {
    curdate = ee.Date(curdate);
    var curyear = curdate.get('year');
    var curdoy = curdate.getRelative('day', 'year').add(1);
    var brdfTemp = brdfFilteredVars
        .filterDate(curdate, curdate.advance(1, 'day'));
    var brdfSize = brdfTemp.size();
    var outImg = ee.Image.constant(0).selfMask()
        .addBands(ee.Image.constant(0).selfMask())
        .addBands(ee.Image.constant(0).selfMask())
        .addBands(ee.Image.constant(0).selfMask())
        .addBands(ee.Image.constant(0).selfMask())
        .rename(['ndvi', 'evi', 'savi', 'ndwi5', 'ndwi6'])
        .set('doy', curdoy)
        .set('year', curyear)
        .set('system:time_start', curdate)
        .set('brdfSize', brdfSize);
    return outImg;
}
// Create filler for all dates.
var dailyBrdfExtendedFiller =
    ee.ImageCollection.fromImages(brdfDates.map(calcDailyBrdfFiller));
// But only used if and when size was 0.
var dailyBrdfExtFillFilt = dailyBrdfExtendedFiller
    .filter(ee.Filter.eq('brdfSize', 0));
// Merge the two collections.
var dailyBrdfExtended = dailyBrdfExtExists
    .merge(dailyBrdfExtFillFilt);

// Filter back to original user requested start date.
var dailyBrdf = dailyBrdfExtended
    .filterDate(reqStartDate, brdfEndDate.advance(1, 'day'));
Summarize Daily Spectral Indices by Woreda

Lastly, in our NDWI section, we will use the mean to summarize the values for each woredas and prepare for export by flattening the dataset.

//Part5.3: Summarize daily spectral indices by woreda

// Filter spectral indices for zonal summaries.
var brdfSummary = dailyBrdf
    .filterDate(reqStartDate, reqEndDate.advance(1, 'day'));

// Function to calculate zonal statistics for spectral indices by woreda:
function sumZonalBrdf(image) {
    // To get the doy and year, we convert the metadata to grids 
    //  and then summarize.
    var image2 = image.addBands([
        image.metadata('doy').int(),
        image.metadata('year').int()
    ]);
    // Reduce by regions to get zonal means for each woreda.
    var output = image2.select(['doy', 'year', 'ndwi6'])
        .reduceRegions({
            collection: woredas,
            reducer: ee.Reducer.mean(),
            scale: 1000
        });
    return output;
}

// Map the zonal statistics function over the filtered spectral index data.
var brdfWoreda = brdfSummary.map(sumZonalBrdf);
// Flatten the results for export.
var brdfFlat = brdfWoreda.flatten();

 

 Part 6 - Map Display 

Here we will look at our calculated variables but before the zonal summary. The full user interface restricts the date to display within the requested range, so be mindful in the code below which date you choose to view.

// Part 6: Map display of calculated environmental variables
var displayDate = ee.Date('2021-10-01');

var precipDisp = dailyPrecip
    .filterDate(displayDate, displayDate.advance(1, 'day'));
var brdfDisp = dailyBrdf
    .filterDate(displayDate, displayDate.advance(1, 'day'));
var LSTDisp = dailyLst
    .filterDate(displayDate, displayDate.advance(1, 'day'));

// Select the image (should be only one) from each collection.
var precipImage = precipDisp.first().select('totprec');
var LSTmImage = LSTDisp.first().select('LST_mean');
var ndwi6Image = brdfDisp.first().select('ndwi6');

// Palettes for environmental variable maps:
var palettePrecip = ['f7fbff', '08306b'];
var paletteLst = ['fff5f0', '67000d'];
var paletteSpectral = ['ffffe5', '004529'];

// Add layers to the map.
// Show precipitation by default,
//  others hidden until users picks them from layers drop down.
Map.addLayer({
    eeObject: precipImage,
    visParams: {
        min: 0,
        max: 20,
        palette: palettePrecip
    },
    name: 'Precipitation',
    shown: true,
    opacity: 0.75
});
Map.addLayer({
    eeObject: LSTmImage,
    visParams: {
        min: 0,
        max: 40,
        palette: paletteLst
    },
    name: 'LST Mean',
    shown: false,
    opacity: 0.75
});
Map.addLayer({
    eeObject: ndwi6Image,
    visParams: {
        min: 0,
        max: 1,
        palette: paletteSpectral
    },
    name: 'NDWI6',
    shown: false,
    opacity: 0.75
});

results after part 6

Part 7 - Exporting 

Two important strengths of Google Earth Engine are the ability to gather and process the remotely sensed data in the cloud and to have the only download be a small text file ready to use in the forecasting software. Most of our project partners were public health experts and did not have a remote sensing or programming background. We also had partners in areas of limited or unreliable internet connectivity. We needed something that could be easily usable by our users in these types of situations. 

This section will create small text CSV downloads for each of our three environmental factors prepared earlier. Each factor may have different data availability within the user’s requested range. These dates will be added to the file name to indicate the actual date range of the downloaded data. 

// Part 7: Exporting

// 7.1 Export naming
var reqStartDateText = reqStartDate.format('yyyy-MM-dd').getInfo();

// Precipitation
var precipPrefix = 'Export_Precip_Data';
var precipLastDate = ee.Date(reqEndDate.millis()
    .min(precipEndDate.millis()));
var precipSummaryEndDate = precipLastDate
    .format('yyyy-MM-dd').getInfo();
var precipFilename = precipPrefix
    .concat('_', reqStartDateText,
        '_', precipSummaryEndDate);
// LST
var LSTPrefix = 'Export_LST_Data';
var LSTLastDate = ee.Date(reqEndDate.millis()
    .min(LSTEndDate.millis()));
var LSTSummaryEndDate = LSTLastDate
    .format('yyyy-MM-dd').getInfo();
var LSTFilename = LSTPrefix
    .concat('_', reqStartDateText,
        '_', LSTSummaryEndDate);
// BRDF
var brdfPrefix = 'Export_Spectral_Data';
var brdfLastDate = ee.Date(reqEndDate.millis()
    .min(brdfEndDate.millis()));
var brdfSummaryEndDate = brdfLastDate
    .format('yyyy-MM-dd').getInfo();
var brdfFilename = brdfPrefix
    .concat('_', reqStartDateText,
        '_', brdfSummaryEndDate);

// 7.2 Export flattened tables to Google Drive
// Need to click 'RUN in the Tasks tab to configure and start each export.
Export.table.toDrive({
    collection: precipFlat,
    description: precipFilename,
    selectors: ['wid', 'woreda', 'doy', 'year', 'totprec']
});
Export.table.toDrive({
    collection: LSTFlat,
    description: LSTFilename,
    selectors: ['wid', 'woreda', 'doy', 'year',
        'LST_day', 'LST_night', 'LST_mean'
    ]
});
Export.table.toDrive({
    collection: brdfFlat,
    description: brdfFilename,
    selectors: ['wid', 'woreda', 'doy', 'year', 'ndwi6']
});

Hit Run and then pay attention to your Task Tab.   In the Earth Engine Tasks tab, click Run to configure and start each export to Google Drive. 

Screenshot 2023-04-14 at 1.57.03 AM.png

example of export settings

After exporting the files, you will find CSV files in your Google Drive.  These can be easily opened in Excel or other software packages like R to do modeling. 

excel sheet of csv file

Submit the CSV files you exported on Canvas

References

Ford TE, Colwell RR, Rose JB, et al (2009) Using satellite images of environmental changes to predict infectious disease outbreaks. Emerg Infect Dis 15:1341–1346. https://doi.org/10.3201/eid/1509.081334

Franklinos LHV, Jones KE, Redding DW, Abubakar I (2019) The effect of global change on mosquito-borne disease. Lancet Infect Dis 19:e302–e312. https://doi.org/10.1016/S1473-3099(19)30161-6

Jones KE, Patel NG, Levy MA, et al (2008) Global trends in emerging infectious diseases. Nature 451:990–993. https://doi.org/10.1038/nature06536

Mackenzie JS, Jeggo M (2019) The one health approachwhy is it so important? Trop. Med. Infect. Dis. 4:88. https://doi.org/10.3390/tropicalmed4020088

Wimberly MC, de Beurs KM, Loboda T V., Pan WK (2021) Satellite observations and malaria: New opportunities for research and applications. Trends Parasitol 37:525–537. https://doi.org/10.1016/j.pt.2021.03.003

Wimberly MC, Nekorchuk DM, Kankanala RR (2022) Cloud-based applications for accessing satellite Earth observations to support malaria early warning. Sci Data 9:1–11. https://doi.org/10.1038/s41597-022-01337-y

World Health Organization (2018) Malaria surveillance, monitoring and evaluation: a reference manual. World Health Organization

World Health Organization (2020) World Malaria Report 2020: 20 years of global progress and challenges. World Health Organization

Lab Submission

Submit lab via email.

Subject: Lab 17 - Health Applications Part 1 - [Your Name]