def download(asset, name, temporal_resolution, start_year, end_year,
             EXECUTION_ID, logger):
    """
    Download dataset from GEE assets.
    """
    logger.debug("Entering download function.")

    in_img = ee.Image(asset)

    # if temporal_resolution != "one time":
    #     assert (start_year and end_year), "start year or end year not defined"
    #     out = in_img.select('y{}'.format(start_year))
    #     band_info = [BandInfo(name, add_to_map=True, metadata={'year': start_year})]
    #     for y in range(start_year + 1, end_year + 1):
    #         out.addBands(in_img.select('y{}'.format(start_year)))
    #         band_info.append(BandInfo(name, metadata={'year': start_year}))
    # else:
    #     out = in_img
    #     band_info = [BandInfo(name, add_to_map=True)]
    out = in_img
    band_info = [BandInfo(name, add_to_map=True)]
    n_bands = len(in_img.getInfo()['bands'])
    if n_bands > 1:
        band_info.extend([BandInfo(name, add_to_map=False)] * (n_bands - 1))

    return TEImage(out, band_info)
Exemple #2
0
def productivity_trajectory(year_start, year_end, method, ndvi_gee_dataset,
                            climate_gee_dataset, logger):
    logger.debug("Entering productivity_trajectory function.")

    climate_1yr = ee.Image(climate_gee_dataset)
    climate_1yr = climate_1yr.where(climate_1yr.eq(9999), -32768)
    climate_1yr = climate_1yr.updateMask(climate_1yr.neq(-32768))

    if climate_gee_dataset == None and method != 'ndvi_trend':
        raise GEEIOError("Must specify a climate dataset")

    ndvi_dataset = ee.Image(ndvi_gee_dataset)
    ndvi_dataset = ndvi_dataset.where(ndvi_dataset.eq(9999), -32768)
    ndvi_dataset = ndvi_dataset.updateMask(ndvi_dataset.neq(-32768))

    ndvi_mean = ndvi_dataset.select(ee.List(['y{}'.format(i) for i in range(year_start, year_end + 1)])) \
        .reduce(ee.Reducer.mean()).rename(['ndvi'])

    # Run the selected algorithm
    if method == 'ndvi_trend':
        lf_trend, mk_trend = ndvi_trend(year_start, year_end, ndvi_dataset, logger)
    elif method == 'p_restrend':
        lf_trend, mk_trend = p_restrend(year_start, year_end, ndvi_dataset, climate_1yr, logger)
        if climate_1yr == None:
            climate_1yr = precp_gpcc
    elif method == 's_restrend':
        #TODO: need to code this
        raise GEEIOError("s_restrend method not yet supported")
    elif method == 'ue':
        lf_trend, mk_trend = ue_trend(year_start, year_end, ndvi_dataset, climate_1yr, logger)
    else:
        raise GEEIOError("Unrecognized method '{}'".format(method))

    # Define Kendall parameter values for a significance of 0.05
    period = year_end - year_start + 1
    kendall90 = stats.get_kendall_coef(period, 90)
    kendall95 = stats.get_kendall_coef(period, 95)
    kendall99 = stats.get_kendall_coef(period, 99)

    # Create final productivity trajectory output layer. Positive values are 
    # significant increase, negative values are significant decrease.
    signif = ee.Image(-32768) \
        .where(lf_trend.select('scale').gt(0).And(mk_trend.abs().gte(kendall90)), 1) \
        .where(lf_trend.select('scale').gt(0).And(mk_trend.abs().gte(kendall95)), 2) \
        .where(lf_trend.select('scale').gt(0).And(mk_trend.abs().gte(kendall99)), 3) \
        .where(lf_trend.select('scale').lt(0).And(mk_trend.abs().gte(kendall90)), -1) \
        .where(lf_trend.select('scale').lt(0).And(mk_trend.abs().gte(kendall95)), -2) \
        .where(lf_trend.select('scale').lt(0).And(mk_trend.abs().gte(kendall99)), -3) \
        .where(mk_trend.abs().lte(kendall90), 0) \
        .where(lf_trend.select('scale').abs().lte(10), 0)

    return TEImage(lf_trend.select('scale').addBands(signif).addBands(mk_trend).unmask(-32768).int16(),
                   [BandInfo("Productivity trajectory (trend)", metadata={'year_start': year_start, 'year_end': year_end}),
                    BandInfo("Productivity trajectory (significance)", add_to_map=True, metadata={'year_start': year_start, 'year_end': year_end}),
                    BandInfo("Mean annual NDVI integral", metadata={'year_start': year_start, 'year_end': year_end})])
def forest_gain(geojson, EXECUTION_ID, logger):
    logger.debug("Entering Forest Gain function.")
    gainImage = gfc2019.select(['gain'])

    # version, for the clipping
    poly = ee.Geometry(geojson, opt_geodesic=False)

    gainImageAOI = gainImage.clip(poly)
    gainAreaImage = gainImageAOI.multiply(ee.Image.pixelArea())

    return TEImage(gainAreaImage.updateMask(gainAreaImage),
                   [BandInfo("Forest Gain", add_to_map=True, metadata={})])
def forest_loss(geojson, EXECUTION_ID, logger):

    logger.debug("Entering Forest Loss Function.")
    lossImage = gfc2019.select(['loss'])

    # version, for the clipping
    poly = ee.Geometry(geojson, opt_geodesic=False)

    lossImageAOI = lossImage.clip(poly)
    lossAreaImage = lossImageAOI.multiply(ee.Image.pixelArea())

    return TEImage(lossAreaImage.updateMask(lossAreaImage),
                   [BandInfo("Forest Loss", add_to_map=True, metadata={})])
def forest_cover(year_start, year_end, ndvi_gee_dataset, geojson, EXECUTION_ID,
                 logger):
    logger.debug("Entering Forest Cover function.")
    treeCover = gfc2019.select(['treecover2000'])

    # version, for the clipping
    poly = ee.Geometry(geojson, opt_geodesic=False)

    treeCoverAOI = treeCover.clip(poly)
    treeCoverAreaImage = treeCoverAOI.multiply(ee.Image.pixelArea())

    return TEImage(treeCoverAreaImage.updateMask(treeCoverAreaImage),
                   [BandInfo("Tree Cover", add_to_map=True, metadata={})])
def forest_loss_year(year, geojson, EXECUTION_ID, logger):
    logger.debug("Entering Forest Loss Year function.")

    # Make sure the bounding box of the poly is used, and not the geodesic
    # version, for the clipping
    poly = ee.Geometry(geojson, opt_geodesic=False)

    lossYear = gfc2019.select(['lossyear']).eq(int(str(year)[2:]))

    lossImageAOI = lossYear.clip(poly)

    # calculate area in metres
    lossAreaImage = lossImageAOI.multiply(ee.Image.pixelArea())

    return TEImage(lossAreaImage.updateMask(lossAreaImage), [
        BandInfo("Forest Loss in {0}".format(year),
                 add_to_map=True,
                 metadata={year: year})
    ])
Exemple #7
0
def restoration_carbon(rest_type, length_yr, crs, geojsons, EXECUTION_ID,
                       logger):
    logger.debug("Entering restoration_carbon function.")
    # biomass
    agb_30m = ee.Image(
        "users/geflanddegradation/toolbox_datasets/forest_agb_30m_woodhole")
    agb_1km = ee.Image(
        "users/geflanddegradation/toolbox_datasets/forest_agb_1km_geocarbon")

    # c sequestration coefficients by intervention from winrock paper
    agfor0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b1").divide(100)
    agfor2060 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b2").divide(100)
    mshrr0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b3").divide(100)
    mshrr2060 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b4").divide(100)
    mtrer0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b5").divide(100)
    mtrer2060 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b6").divide(100)
    natre0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b7").divide(100)
    natre2060 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b8").divide(100)
    pwobr0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b9").divide(100)
    pweuc0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b10").divide(100)
    pwoak0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b11").divide(100)
    pwoco0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b12").divide(100)
    pwpin0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b13").divide(100)
    pwtea0020 = ee.Image(
        "users/geflanddegradation/toolbox_datasets/winrock_co2_seq_coeff_adm1"
    ).select("b14").divide(100)

    # combine the global 1km and the pantropical 30m datasets into one layer for analysis
    agb = agb_30m.float().unmask(0).where(
        ee.Image.pixelLonLat().select('latitude').abs().gte(29.999), agb_1km)

    # calculate below ground biomass following Mokany et al. 2006 = (0.489)*(AGB)^(0.89)
    bgb = agb.expression('0.489 * BIO**(0.89)', {'BIO': agb})

    # calculate total biomass (t/ha) then convert to carbon equilavent (*0.5) to get Total Carbon (t ha-1) = (AGB+BGB)*0.5
    tbc = agb.expression('(bgb + abg ) * 0.5 ', {'bgb': bgb, 'abg': agb})

    # convert Total carbon to total CO2 eq (One ton of carbon equals 44/12 = 11/3 = 3.67 tons of carbon dioxide)
    current_co2 = tbc.expression('totalcarbon * 3.67 ', {'totalcarbon': tbc})

    if rest_type == "terrestrial":
        if length_yr <= 20:
            d_natre = natre0020.multiply(length_yr).subtract(current_co2)
            d_natre = d_natre.where(d_natre.lte(0), 0)
            d_agfor = agfor0020.multiply(length_yr).subtract(current_co2)
            d_agfor = d_agfor.where(d_agfor.lte(0), 0)
            d_pwtea = pwtea0020.multiply(length_yr).subtract(current_co2)
            d_pweuc = pweuc0020.multiply(length_yr).subtract(current_co2)
            d_pwoak = pwoak0020.multiply(length_yr).subtract(current_co2)
            d_pwobr = pwobr0020.multiply(length_yr).subtract(current_co2)
            d_pwpin = pwpin0020.multiply(length_yr).subtract(current_co2)
            d_pwoco = pwoco0020.multiply(length_yr).subtract(current_co2)
        if length_yr > 20:
            d_natre = natre0020.multiply(20).add(
                natre2060.multiply(length_yr - 20)).subtract(current_co2)
            d_natre = d_natre.where(d_natre.lte(0), 0)
            d_agfor = agfor0020.multiply(20).add(
                agfor2060.multiply(length_yr - 20)).subtract(current_co2)
            d_agfor = d_agfor.where(d_agfor.lte(0), 0)
            d_pwtea = pwtea0020.multiply(20).subtract(current_co2)
            d_pweuc = pweuc0020.multiply(20).subtract(current_co2)
            d_pwoak = pwoak0020.multiply(20).subtract(current_co2)
            d_pwobr = pwobr0020.multiply(20).subtract(current_co2)
            d_pwpin = pwpin0020.multiply(20).subtract(current_co2)
            d_pwoco = pwoco0020.multiply(20).subtract(current_co2)

        output = current_co2.addBands(d_natre).addBands(d_agfor) \
                    .addBands(d_pwtea).addBands(d_pweuc) \
                    .addBands(d_pwoak).addBands(d_pwobr) \
                    .addBands(d_pwpin).addBands(d_pwoco) \
                    .rename(['current','natreg','agrfor','pwteak','pweuca','pwoaks','pwobro','pwpine','pwocon'])

        logger.debug("Setting up output for terrestrial restoration.")
        out = TEImage(output, [
            BandInfo("Biomass (tonnes CO2e per ha)",
                     add_to_map=True,
                     metadata={'year': 'current'}),
            BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                     metadata={
                         'years': length_yr,
                         'type': 'natural regeneration'
                     }),
            BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                     metadata={
                         'years': length_yr,
                         'type': 'agroforestry'
                     }),
            BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                     metadata={
                         'years': length_yr,
                         'type': 'teak plantation'
                     }),
            BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                     metadata={
                         'years': length_yr,
                         'type': 'eucalyptus plantation'
                     }),
            BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                     metadata={
                         'years': length_yr,
                         'type': 'oak plantation'
                     }),
            BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                     metadata={
                         'years': length_yr,
                         'type': 'other broadleaf plantation'
                     }),
            BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                     metadata={
                         'years': length_yr,
                         'type': 'pine plantation'
                     }),
            BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                     metadata={
                         'years': length_yr,
                         'type': 'conifer plantation'
                     })
        ])
    elif rest_type == "coastal":
        if length_yr <= 20:
            d_mshrr = mshrr0020.multiply(length_yr).subtract(current_co2)
            d_mshrr = d_mshrr.where(d_mshrr.lte(0), 0)
            d_mtrer = mtrer0020.multiply(length_yr).subtract(current_co2)
            d_mtrer = d_mtrer.where(d_mtrer.lte(0), 0)
        if length_yr > 20:
            d_mshrr = mshrr0020.multiply(20).add(
                mshrr2060.multiply(length_yr - 20)).subtract(current_co2)
            d_mshrr = d_mshrr.where(d_mshrr.lte(0), 0)
            d_mtrer = mtrer0020.multiply(20).add(
                mtrer2060.multiply(length_yr - 20)).subtract(current_co2)
            d_mtrer = d_mtrer.where(d_mtrer.lte(0), 0)

        logger.debug("Setting up output for coastal restoration.")
        out = TEImage(
            current_co2.addBands(d_mshrr).addBands(d_mtrer).rename(
                ['current', 'mshrr', 'mtrer']),
            [
                BandInfo("Biomass (tonnes CO2e per ha)",
                         add_to_map=True,
                         metadata={'year': 'current'}),
                BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                         metadata={
                             'years': length_yr,
                             'type': 'mangrove shrub'
                         }),
                BandInfo("Restoration biomass difference (tonnes CO2e per ha)",
                         metadata={
                             'years': length_yr,
                             'type': 'mangrove tree'
                         })
            ])
    else:
        raise

    out.image = out.image.reproject(crs=agb_30m.projection())

    return out
Exemple #8
0
def soc(geometry, year_start, year_end, fl, remap_matrix, dl_annual_lc,
        EXECUTION_ID, logger):
    """
    Calculate SOC indicator.
    """
    logger.debug("Entering soc function.")
    geom = ee.Geometry.Polygon(geometry)
    # Location
    area = ee.FeatureCollection(geom)
    # soc
    soc = ee.Image(
        "users/geflanddegradation/toolbox_datasets/soc_sgrid_30cm").clip(area)
    soc_t0 = soc.updateMask(soc.neq(-32768))

    # land cover - note it needs to be reprojected to match soc so that it can
    # be output to cloud storage in the same stack
    lc = ee.Image("users/geflanddegradation/toolbox_datasets/lcov_esacc_1992_2018") \
            .select(ee.List.sequence(year_start - 1992, year_end - 1992, 1)) \
            .clip(area) \
            .reproject(crs=soc.projection())

    lc = lc.where(lc.eq(9999), -32768)
    lc = lc.updateMask(lc.neq(-32768))

    if fl == 'per pixel':
        # Setup a raster of climate regimes to use for coding Fl automatically
        climate = ee.Image("users/geflanddegradation/toolbox_datasets/ipcc_climate_zones") \
            .clip(area) \
            .remap([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
                   [0, 2, 1, 2, 1, 2, 1, 2, 1, 5, 4, 4, 3])
        clim_fl = climate.remap([0, 1, 2, 3, 4, 5],
                                [0, 0.8, 0.69, 0.58, 0.48, 0.64])
    # create empty stacks to store annual land cover maps
    stack_lc = ee.Image().select()

    # create empty stacks to store annual soc maps
    stack_soc = ee.Image().select()

    # loop through all the years in the period of analysis to compute changes in SOC
    for k in range(year_end - year_start):
        # land cover map reclassified to UNCCD 7 classes (1: forest, 2:
        # grassland, 3: cropland, 4: wetland, 5: artifitial, 6: bare, 7: water)
        lc_t0 = lc.select(k).remap(remap_matrix[0], remap_matrix[1])

        lc_t1 = lc.select(k + 1).remap(remap_matrix[0], remap_matrix[1])

        if (k == 0):
            # compute transition map (first digit for baseline land cover, and
            # second digit for target year land cover)
            lc_tr = lc_t0.multiply(10).add(lc_t1)

            # compute raster to register years since transition
            tr_time = ee.Image(2).where(lc_t0.neq(lc_t1), 1)
        else:
            # Update time since last transition. Add 1 if land cover remains
            # constant, and reset to 1 if land cover changed.
            tr_time = tr_time.where(lc_t0.eq(lc_t1), tr_time.add(ee.Image(1))) \
                .where(lc_t0.neq(lc_t1), ee.Image(1))

            # compute transition map (first digit for baseline land cover, and
            # second digit for target year land cover), but only update where
            # changes actually ocurred.
            lc_tr_temp = lc_t0.multiply(10).add(lc_t1)
            lc_tr = lc_tr.where(lc_t0.neq(lc_t1), lc_tr_temp)

        # stock change factor for land use - note the 99 and -99 will be
        # recoded using the chosen Fl option
        lc_tr_fl_0 = lc_tr.remap([
            11, 12, 13, 14, 15, 16, 17, 21, 22, 23, 24, 25, 26, 27, 31, 32, 33,
            34, 35, 36, 37, 41, 42, 43, 44, 45, 46, 47, 51, 52, 53, 54, 55, 56,
            57, 61, 62, 63, 64, 65, 66, 67, 71, 72, 73, 74, 75, 76, 77
        ], [
            1, 1, 99, 1, 0.1, 0.1, 1, 1, 1, 99, 1, 0.1, 0.1, 1, -99, -99, 1,
            1 / 0.71, 0.1, 0.1, 1, 1, 1, 0.71, 1, 0.1, 0.1, 1, 2, 2, 2, 2, 1,
            1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
        ])

        if fl == 'per pixel':
            lc_tr_fl = lc_tr_fl_0.where(lc_tr_fl_0.eq(99), clim_fl)\
                                 .where(lc_tr_fl_0.eq(-99), ee.Image(1).divide(clim_fl))
        else:
            lc_tr_fl = lc_tr_fl_0.where(lc_tr_fl_0.eq(99), fl)\
                                 .where(lc_tr_fl_0.eq(-99), ee.Image(1).divide(fl))

        # stock change factor for management regime
        lc_tr_fm = lc_tr.remap([
            11, 12, 13, 14, 15, 16, 17, 21, 22, 23, 24, 25, 26, 27, 31, 32, 33,
            34, 35, 36, 37, 41, 42, 43, 44, 45, 46, 47, 51, 52, 53, 54, 55, 56,
            57, 61, 62, 63, 64, 65, 66, 67, 71, 72, 73, 74, 75, 76, 77
        ], [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1
        ])

        # stock change factor for input of organic matter
        lc_tr_fo = lc_tr.remap([
            11, 12, 13, 14, 15, 16, 17, 21, 22, 23, 24, 25, 26, 27, 31, 32, 33,
            34, 35, 36, 37, 41, 42, 43, 44, 45, 46, 47, 51, 52, 53, 54, 55, 56,
            57, 61, 62, 63, 64, 65, 66, 67, 71, 72, 73, 74, 75, 76, 77
        ], [
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
            1, 1, 1, 1, 1
        ])

        if (k == 0):
            soc_chg = (soc_t0.subtract((soc_t0.multiply(lc_tr_fl).multiply(
                lc_tr_fm).multiply(lc_tr_fo)))).divide(20)

            # compute final SOC stock for the period
            soc_t1 = soc_t0.subtract(soc_chg)

            # add to land cover and soc to stacks from both dates for the first
            # period
            stack_lc = stack_lc.addBands(lc_t0).addBands(lc_t1)
            stack_soc = stack_soc.addBands(soc_t0).addBands(soc_t1)

        else:
            # compute annual change in soc (updates from previous period based
            # on transition and time <20 years)
            soc_chg = soc_chg.where(lc_t0.neq(lc_t1),
                                    (stack_soc.select(k).subtract(stack_soc.select(k) \
                                                                  .multiply(lc_tr_fl) \
                                                                  .multiply(lc_tr_fm) \
                                                                  .multiply(lc_tr_fo))).divide(20)) \
                             .where(tr_time.gt(20), 0)

            # compute final SOC for the period
            socn = stack_soc.select(k).subtract(soc_chg)

            # add land cover and soc to stacks only for the last year in the
            # period
            stack_lc = stack_lc.addBands(lc_t1)
            stack_soc = stack_soc.addBands(socn)

    # compute soc percent change for the analysis period
    soc_pch = ((stack_soc.select(year_end - year_start) \
                .subtract(stack_soc.select(0))) \
               .divide(stack_soc.select(0))) \
              .multiply(100)

    logger.debug("Setting up output.")
    out = TEImage(soc_pch, [
        BandInfo("Soil organic carbon (degradation)",
                 add_to_map=True,
                 metadata={
                     'year_start': year_start,
                     'year_end': year_end
                 })
    ])

    logger.debug("Adding annual SOC layers.")
    # Output all annual SOC layers
    d_soc = []
    for year in range(year_start, year_end + 1):
        if (year == year_start) or (year == year_end):
            add_to_map = True
        else:
            add_to_map = False
        d_soc.append(
            BandInfo("Soil organic carbon",
                     add_to_map=add_to_map,
                     metadata={'year': year}))
    out.addBands(stack_soc, d_soc)

    if dl_annual_lc:
        logger.debug("Adding all annual LC layers.")
        d_lc = []
        for year in range(year_start, year_end + 1):
            d_lc.append(
                BandInfo("Land cover (7 class)", metadata={'year': year}))
        out.addBands(stack_lc, d_lc)
    else:
        logger.debug("Adding initial and final LC layers.")
        out.addBands(
            stack_lc.select(0).addBands(
                stack_lc.select(len(stack_lc.getInfo()['bands']) - 1)),
            [
                BandInfo("Land cover (7 class)", metadata={'year': year_start
                                                           }),
                BandInfo("Land cover (7 class)", metadata={'year': year_end})
            ])

    out.image = out.image.unmask(-32768).int16()

    return out
def forest_fire(geometry,prefire_start,prefire_end,postfire_start,postfire_end, platform, EXECUTION_ID,logger):
    """
    ===========================================================================================
                 BURN SEVERITY MAPPING USING THE NORMALIZED BURN RATIO (NBR)
    ===========================================================================================
     Normalized Burn Ratio will be applied to imagery from before and after a wild fire. By
     calculating the difference afterwards (dNBR) Burn Severity is derived, showing the spatial
     impact of the disturbance. Imagery used in this process comes from either Sentinel-2 or 
     Landsat 8.
    """

    logger.debug("Entering forest_fire function.")

    # SELECT one of the following:   'L8'  or 'S2' 

    if platform == 'S2' or platform == 's2':
        ImCol = 'COPERNICUS/S2'
        pl = 'Sentinel-2'
    else:
        ImCol = 'LANDSAT/LC08/C01/T1_SR'
        pl = 'Landsat 8'

    # logger.debug(ee.String('Data selected for analysis: ').cat(pl))
    # logger.debug(ee.String('Fire incident occurred between ').cat(prefire_end).cat(' and ').cat(postfire_start))
    geom = ee.Geometry.Polygon(geometry)
    # Location
    area = ee.FeatureCollection(geom)

    # add image collection 
    imagery = ee.ImageCollection(ImCol)

    prefireImCol = ee.ImageCollection(imagery
        # Filter by dates.
        .filterDate(prefire_start, prefire_end)
        # Filter by location.
        .filterBounds(area))

    postfireImCol = ee.ImageCollection(imagery
        # Filter by dates.
        .filterDate(postfire_start, postfire_end)
        # Filter by location.
        .filterBounds(area))

    # logger.debug("Pre-fire Image Collection: "+prefireImCol)
    # logger.debug("Post-fire Image Collection: "+postfireImCol)

    def maskS2sr(image):
        # Bits 10 and 11 are clouds and cirrus, respectively.
        cloudBitMask = ee.Number(2).pow(10).int()
        cirrusBitMask = ee.Number(2).pow(11).int()
        # Get the pixel QA band.
        qa = image.select('QA60')
        # All flags should be set to zero, indicating clear conditions.
        mask = qa.bitwiseAnd(cloudBitMask).eq(0).And(qa.bitwiseAnd(cirrusBitMask).eq(0))
        # Return the masked image, scaled to TOA reflectance, without the QA bands.
        return image.updateMask(mask).copyProperties(image, ["system:time_start"])    

    def maskL8sr(image):
        # Bits 3 and 5 are cloud shadow and cloud, respectively.
        cloudShadowBitMask = 1 << 3
        cloudsBitMask = 1 << 5
        snowBitMask = 1 << 4
        # Get the pixel QA band.
        qa = image.select('pixel_qa')
        # All flags should be set to zero, indicating clear conditions.
        mask = qa.bitwiseAnd(cloudShadowBitMask).eq(0).And(qa.bitwiseAnd(cloudsBitMask).eq(0)).And(qa.bitwiseAnd(snowBitMask).eq(0))
        # Return the masked image, scaled to TOA reflectance, without the QA bands.
        return image.updateMask(mask).select("B[0-9]*").copyProperties(image, ["system:time_start"])

    # Apply platform-specific cloud mask
    if platform == 'S2' or platform == 's2':
        prefire_CM_ImCol = prefireImCol.map(maskS2sr)
        postfire_CM_ImCol = postfireImCol.map(maskS2sr)
    else:
        prefire_CM_ImCol = prefireImCol.map(maskL8sr)
        postfire_CM_ImCol = postfireImCol.map(maskL8sr)

    pre_mos = prefireImCol.mosaic().clip(area)
    post_mos = postfireImCol.mosaic().clip(area)

    pre_cm_mos = prefire_CM_ImCol.mosaic().clip(area)
    post_cm_mos = postfire_CM_ImCol.mosaic().clip(area)

    if platform == 'S2' or platform == 's2':
        preNBR = pre_cm_mos.normalizedDifference(['B8', 'B12'])
        postNBR = post_cm_mos.normalizedDifference(['B8', 'B12'])
    else:
        preNBR = pre_cm_mos.normalizedDifference(['B5', 'B7'])
        postNBR = post_cm_mos.normalizedDifference(['B5', 'B7'])

    # The result is called delta NBR or dNBR
    dNBR_unscaled = preNBR.subtract(postNBR)

    # Scale product to USGS standards
    dNBR = dNBR_unscaled.multiply(1000)

    # reclassify dnbr
    dNBR = dNBR \
        .where(dNBR.gte(-500).And(dNBR.lte(-251)), 1) \
        .where(dNBR.gte(-250).And(dNBR.lte(-101)), 2) \
        .where(dNBR.gte(-100).And(dNBR.lte(99)), 3) \
        .where(dNBR.gte(100).And(dNBR.lte(269)), 4) \
        .where(dNBR.gte(270).And(dNBR.lte(439)), 5) \
        .where(dNBR.gte(440).And(dNBR.lte(659)), 6) \
        .where(dNBR.gte(660).And(dNBR.lte(1300)), 7) \
        .rename("dNBR")

    return TEImage(dNBR.addBands(preNBR).addBands(postNBR),
        [BandInfo("dNBR image", add_to_map=True, metadata={'prefire_start':prefire_start,'prefire_end':prefire_end, 'postfire_start':postfire_start, 'postfire_end':postfire_end}),
         BandInfo("Prefire Normalized Burn Ratio", add_to_map=True,metadata={'prefire_start':prefire_start,'prefire_end':prefire_end}),
         BandInfo("Postfire Normalized Burn Ratio", add_to_map=True,metadata={'postfire_start':postfire_start, 'postfire_end':postfire_end})])
Exemple #10
0
def urban(isi_thr, ntl_thr, wat_thr, cap_ope, pct_suburban, pct_urban, un_adju,
          crs, geojsons, EXECUTION_ID, logger):
    # Impervious surface index computed by Trends.Earth
    isi_series = ee.ImageCollection("projects/trends_earth/isi_1998_2018_20190403").reduce(ee.Reducer.mean()) \
         .select(['isi1998_mean','isi2000_mean','isi2005_mean','isi2010_mean','isi2015_mean','isi2018_mean'],
         ['isi1998','isi2000','isi2005','isi2010','isi2015','isi2018'])
    proj = isi_series.select('isi2000').projection()

    # JRC Global Surface Water Mapping Layers, v1.0 (>20% occurrence)
    water = ee.Image("JRC/GSW1_0/GlobalSurfaceWater").select("occurrence")

    # Define nighttime lights mask from VIIRS Nighttime Day/Night Band 
    # Composites Version 1 (Apr 1, 2012 - May 1, 2018)
    ntl = ee.ImageCollection("NOAA/VIIRS/DNB/MONTHLY_V1/VCMCFG")
    ntl = ntl.filterDate(ee.Date("2015-01-01"), ee.Date("2015-12-31")).select(["avg_rad"], ["ntl"]).median() \
            .clip(ee.Geometry.Polygon([-180, 57, 0, 57, 180, 57, 180, -88, 0, -88, -180, -88], None, False)).unmask(10)
          
    # Mask urban areas based ntl
    urban98 = isi_series.select("isi1998").gte(isi_thr).unmask(0).where(ntl.lte(-1+ntl_thr*31/100), 0)
    urban00 = isi_series.select("isi2000").gte(isi_thr).unmask(0).where(ntl.lte(-1+ntl_thr*31/100), 0)
    urban05 = isi_series.select("isi2005").gte(isi_thr).unmask(0).where(ntl.lte(-1+ntl_thr*31/100), 0)
    urban10 = isi_series.select("isi2010").gte(isi_thr).unmask(0).where(ntl.lte(-1+ntl_thr*31/100), 0)
    urban15 = isi_series.select("isi2015").gte(isi_thr).unmask(0).where(ntl.lte(-1+ntl_thr*31/100), 0)
    urban18 = isi_series.select("isi2018").gte(isi_thr).unmask(0).where(ntl.lte(-1+ntl_thr*31/100), 0)

    urban_series = (urban98.multiply(100000)).add \
                   (urban00.multiply(10000)).add \
                   (urban05.multiply(1000)).add \
                   (urban10.multiply(100)).add \
                   (urban15.multiply(10)).add \
                   (urban18.multiply(1))
          
    urban_sum = urban98.add(urban00).add(urban05).add(urban10).add(urban15).add(urban18)

    if un_adju:
        # Gridded Population of the World Version 4, UN-Adjusted Population 
        # Density
        gpw4_2000 = ee.Image("CIESIN/GPWv4/unwpp-adjusted-population-density/2000") \
                .select(["population-density"], ["p2000"]).reproject(crs=proj, scale=30)
        gpw4_2005 = ee.Image("CIESIN/GPWv4/unwpp-adjusted-population-density/2005") \
                .select(["population-density"], ["p2005"]).reproject(crs=proj, scale=30)
        gpw4_2010 = ee.Image("CIESIN/GPWv4/unwpp-adjusted-population-density/2010") \
                .select(["population-density"], ["p2010"]).reproject(crs=proj, scale=30)
        gpw4_2015 = ee.Image("CIESIN/GPWv4/unwpp-adjusted-population-density/2015") \
                .select(["population-density"], ["p2015"]).reproject(crs=proj, scale=30)
    else:
        gpw4_2000 = ee.Image("CIESIN/GPWv4/population-density/2000") \
                .select(["population-density"], ["p2000"]).reproject(crs=proj, scale=30)
        gpw4_2005 = ee.Image("CIESIN/GPWv4/population-density/2005") \
                .select(["population-density"], ["p2005"]).reproject(crs=proj, scale=30)
        gpw4_2010 = ee.Image("CIESIN/GPWv4/population-density/2010") \
                .select(["population-density"], ["p2010"]).reproject(crs=proj, scale=30)
        gpw4_2015 = ee.Image("CIESIN/GPWv4/population-density/2015") \
                .select(["population-density"], ["p2015"]).reproject(crs=proj, scale=30)

    urban_series = urban_series.where(urban_series.eq(    0), 0) \
                               .where(urban_series.eq(    1), 0) \
                               .where(urban_series.eq(   10), 1) \
                               .where(urban_series.eq(   11), 4) \
                               .where(urban_series.eq(  100), 0) \
                               .where(urban_series.eq(  101), 3) \
                               .where(urban_series.eq(  110), 3) \
                               .where(urban_series.eq(  111), 3) \
                               .where(urban_series.eq( 1000), 0) \
                               .where(urban_series.eq( 1001), 2) \
                               .where(urban_series.eq( 1010), 2) \
                               .where(urban_series.eq( 1011), 2) \
                               .where(urban_series.eq( 1100), 2) \
                               .where(urban_series.eq( 1101), 2) \
                               .where(urban_series.eq( 1110), 2) \
                               .where(urban_series.eq( 1111), 2) \
                               .where(urban_series.eq(10000), 0) \
                               .where(urban_series.eq(10001), 0) \
                               .where(urban_series.eq(10010), 0) \
                               .where(urban_series.eq(10011), 1) \
                               .where(urban_series.eq(10100), 0) \
                               .where(urban_series.eq(10101), 1) \
                               .where(urban_series.eq(10110), 1) \
                               .where(urban_series.eq(10111), 1) \
                               .where(urban_series.eq(11000), 0) \
                               .where(urban_series.eq(11001), 1) \
                               .where(urban_series.eq(11010), 1) \
                               .where(urban_series.eq(11011), 1) \
                               .where(urban_series.eq(11100), 1) \
                               .where(urban_series.eq(11101), 1) \
                               .where(urban_series.eq(11110), 1) \
                               .where(urban_series.eq(11111), 1) \
                               .where(urban_series.gte(100000).And(urban_sum.gte(3)), 1) \
                               .where(urban_series.gte(100000).And(urban_sum.lte(2)), 0) \
                               .where(water.gte(wat_thr), -1) \
                               .reproject(crs=proj, scale=30)

    ## define function to do zonation of cities
    def f_city_zones(built_up, geojson):
        dens = built_up.reduceNeighborhood(reducer=ee.Reducer.mean(), kernel=ee.Kernel.circle(564, "meters"))
        ##rural built up (-32768 no-data), suburban, urban
        city = ee.Image(10).where(dens.lte(pct_suburban).And(built_up.eq(1)), 3) \
                           .where(dens.gt(pct_suburban).And(built_up.eq(1)), 2) \
                           .where(dens.gt(pct_urban).And(built_up.eq(1)), 1) 
  
        dist = city.lte(2).fastDistanceTransform(100).sqrt()

        ## fringe open space, rural built up
        city = city.where(dist.gt(0).And(dist.lte(3)), 4) \
                                    .where(city.eq(3), 3)
  
        open_space = city.updateMask(city.eq(10)).addBands(ee.Image.pixelArea())
        open_space_poly = open_space.reduceToVectors(
            reducer=ee.Reducer.sum().setOutputs(['area']), 
            geometry=geojson,
            scale=30,               
            maxPixels=1e10)
      
        open_space_img = open_space_poly.reduceToImage(properties=['area'], reducer=ee.Reducer.first())
        ## captured open space, rural open space
        city = city.where(city.eq(10).And(open_space_img.gt(0).And(open_space_img.lte(cap_ope*10000))), 5) \
                .where(city.eq(10).And(open_space_img.gt(cap_ope*10000)), 6)

        return city.where(city.eq(4).And(water.gte(wat_thr)), 7) \
                .where(city.eq(5).And(water.gte(wat_thr)), 8) \
                .where(city.eq(6).And(water.gte(wat_thr)), 9) \
                .where(city.eq(10), -32768)

    logger.debug("Processing geojsons")
    outs = []
    for geojson in geojsons:
        city00 = f_city_zones(urban_series.eq(1), geojson)
        city05 = f_city_zones(urban_series.gte(1).And(urban_series.lte(2)), geojson)
        city10 = f_city_zones(urban_series.gte(1).And(urban_series.lte(3)), geojson)
        city15 = f_city_zones(urban_series.gte(1).And(urban_series.lte(4)), geojson)
        rast_export = city00.addBands(city05).addBands(city10).addBands(city15) \
                .addBands(gpw4_2000).addBands(gpw4_2005).addBands(gpw4_2010).addBands(gpw4_2015).addBands(urban_series)
        rast_export = rast_export.unmask(-32768).int16().reproject(crs=proj, scale=30)
        this_out = TEImage(rast_export,
            [BandInfo("Urban", metadata={'year': 2000}),
             BandInfo("Urban", metadata={'year': 2005}),
             BandInfo("Urban", metadata={'year': 2010}),
             BandInfo("Urban", metadata={'year': 2015}),
             BandInfo("Population", metadata={'year': 2000}),
             BandInfo("Population", metadata={'year': 2005}),
             BandInfo("Population", metadata={'year': 2010}),
             BandInfo("Population", metadata={'year': 2015}),
             BandInfo("Urban series", add_to_map=True)])
        outs.append(this_out.export([geojson], 'urban', crs, logger, EXECUTION_ID))
    
    return outs
Exemple #11
0
def climate_quality(year, geometry, EXECUTION_ID, logger):
    """
    ===========================================================================================
                 CLIMATE QUALITY INDEX (CQI)
    ===========================================================================================

    """
    logger.debug("Entering climate quality function.")

    terra_climate = ee.ImageCollection("IDAHO_EPSCOR/TERRACLIMATE") \
                .filter(ee.Filter.date('{}-01-01'.format(year), '{}-12-31'.format(year))) \
                .mean() \
                .clip(geometry)

    precipitation = terra_climate.select('pr')

    precipitation_class = precipitation \
        .where(precipitation.gt(650), 1) \
        .where(precipitation.gte(570).And(precipitation.lt(650)), 1.05) \
        .where(precipitation.gte(490).And(precipitation.lt(570)), 1.15) \
        .where(precipitation.gte(440).And(precipitation.lt(490)), 1.25) \
        .where(precipitation.gte(390).And(precipitation.lt(440)), 1.35) \
        .where(precipitation.gte(345).And(precipitation.lt(390)), 1.50) \
        .where(precipitation.gte(310).And(precipitation.lt(345)), 1.65) \
        .where(precipitation.gte(280).And(precipitation.lt(310)), 1.80) \
        .where(precipitation.lt(280), 2) \
        .rename("Precipitation")

    evapotrans = terra_climate.select('pet')

    aridityIndex = ee.Image().expression(
        '(precipitation / evapotranspiration)', {
            'precipitation': precipitation,
            'evapotranspiration': evapotrans,
        }).rename('aridityIndex')

    aridityIndex = aridityIndex \
        .where(aridityIndex.gte(1.0), 1) \
        .where(aridityIndex.gte(0.75).And(aridityIndex.lt(1.0)), 1.05) \
        .where(aridityIndex.gte(0.65).And(aridityIndex.lt(0.75)), 1.15) \
        .where(aridityIndex.gte(0.50).And(aridityIndex.lt(0.65)), 1.25) \
        .where(aridityIndex.gte(0.35).And(aridityIndex.lt(0.50)), 1.35) \
        .where(aridityIndex.gte(0.20).And(aridityIndex.lt(0.35)), 1.45) \
        .where(aridityIndex.gte(0.10).And(aridityIndex.lt(0.20)), 1.55) \
        .where(aridityIndex.gte(0.03).And(aridityIndex.lt(0.10)), 1.75) \
        .where(aridityIndex.lt(0.03), 2) \
        .rename("Aridity Index")

    cqi = ee.Image().expression('(precipitation * aridity_index) ** 1/2', {
        'precipitation': precipitation_class,
        'aridity_index': aridityIndex,
    })

    cqi_class = cqi \
        .where(cqi.lt(1.15), 1) \
        .where(cqi.gte(1.15).And(cqi.lte(1.81)), 2) \
        .where(cqi.gt(1.81), 3) \
        .rename('Climate Quality Reclass')

    srtm_proj = ee.Image("USGS/SRTMGL1_003").projection()

    # resample parent material to srtm projection
    cqi_class = cqi_class.reproject(crs=srtm_proj)

    return TEImage(cqi_class.clip(geometry), [
        BandInfo("Climate Quality Index (year)",
                 add_to_map=True,
                 metadata={'year': year})
    ])
Exemple #12
0
def land_cover(year_baseline, year_target, trans_matrix, remap_matrix,
               EXECUTION_ID, logger):
    """
    Calculate land cover indicator.
    """
    logger.debug("Entering land_cover function.")

    ## land cover
    lc = ee.Image(
        "users/geflanddegradation/toolbox_datasets/lcov_esacc_1992_2018")
    lc = lc.where(lc.eq(9999), -32768)
    lc = lc.updateMask(lc.neq(-32768))

    # Remap LC according to input matrix
    lc_remapped = lc.select('y{}'.format(year_baseline)).remap(
        remap_matrix[0], remap_matrix[1])
    for year in range(year_baseline + 1, year_target + 1):
        lc_remapped = lc_remapped.addBands(
            lc.select('y{}'.format(year)).remap(remap_matrix[0],
                                                remap_matrix[1]))

    ## target land cover map reclassified to IPCC 6 classes
    lc_bl = lc_remapped.select(0)

    ## baseline land cover map reclassified to IPCC 6 classes
    lc_tg = lc_remapped.select(len(lc_remapped.getInfo()['bands']) - 1)

    ## compute transition map (first digit for baseline land cover, and second digit for target year land cover)
    lc_tr = lc_bl.multiply(10).add(lc_tg)

    ## definition of land cover transitions as degradation (-1), improvement (1), or no relevant change (0)
    lc_dg = lc_tr.remap([
        11, 12, 13, 14, 15, 16, 17, 21, 22, 23, 24, 25, 26, 27, 31, 32, 33, 34,
        35, 36, 37, 41, 42, 43, 44, 45, 46, 47, 51, 52, 53, 54, 55, 56, 57, 61,
        62, 63, 64, 65, 66, 67, 71, 72, 73, 74, 75, 76, 77
    ], trans_matrix)

    ## Remap persistence classes so they are sequential. This
    ## makes it easier to assign a clear color ramp in QGIS.
    lc_tr = lc_tr.remap([
        11, 12, 13, 14, 15, 16, 17, 21, 22, 23, 24, 25, 26, 27, 31, 32, 33, 34,
        35, 36, 37, 41, 42, 43, 44, 45, 46, 47, 51, 52, 53, 54, 55, 56, 57, 61,
        62, 63, 64, 65, 66, 67, 71, 72, 73, 74, 75, 76, 77
    ], [
        1, 12, 13, 14, 15, 16, 17, 21, 2, 23, 24, 25, 26, 27, 31, 32, 3, 34,
        35, 36, 37, 41, 42, 43, 4, 45, 46, 47, 51, 52, 53, 54, 5, 56, 57, 61,
        62, 63, 64, 65, 6, 67, 71, 72, 73, 74, 75, 76, 7
    ])

    logger.debug("Setting up output.")
    out = TEImage(
        lc_dg.addBands(lc.select('y{}'.format(year_baseline))).addBands(
            lc.select('y{}'.format(year_target))).addBands(lc_tr), [
                BandInfo("Land cover (degradation)",
                         add_to_map=True,
                         metadata={
                             'year_baseline': year_baseline,
                             'year_target': year_target
                         }),
                BandInfo("Land cover (ESA classes)",
                         metadata={'year': year_baseline}),
                BandInfo("Land cover (ESA classes)",
                         metadata={'year': year_target}),
                BandInfo("Land cover transitions",
                         add_to_map=True,
                         metadata={
                             'year_baseline': year_baseline,
                             'year_target': year_target
                         })
            ])

    # Return the full land cover timeseries so it is available for reporting
    logger.debug("Adding annual lc layers.")
    d_lc = []
    for year in range(year_baseline, year_target + 1):
        if (year == year_baseline) or (year == year_target):
            add_to_map = True
        else:
            add_to_map = False
        d_lc.append(
            BandInfo("Land cover (7 class)",
                     add_to_map=add_to_map,
                     metadata={'year': year}))
    out.addBands(lc_remapped, d_lc)

    out.image = out.image.unmask(-32768).int16()

    return out
Exemple #13
0
def productivity_state(year_bl_start, year_bl_end,
                       year_tg_start, year_tg_end,
                       ndvi_gee_dataset, EXECUTION_ID, logger):
    logger.debug("Entering productivity_state function.")

    ndvi_1yr = ee.Image(ndvi_gee_dataset)

    # compute min and max of annual ndvi for the baseline period
    bl_ndvi_range = ndvi_1yr.select(ee.List(['y{}'.format(i) for i in range(year_bl_start, year_bl_end + 1)])) \
        .reduce(ee.Reducer.percentile([0, 100]))

    # add two bands to the time series: one 5% lower than min and one 5% higher than max
    bl_ndvi_ext = ndvi_1yr.select(ee.List(['y{}'.format(i) for i in range(year_bl_start, year_bl_end + 1)])) \
        .addBands(bl_ndvi_range.select('p0').subtract((bl_ndvi_range.select('p100').subtract(bl_ndvi_range.select('p0'))).multiply(0.05)))\
        .addBands(bl_ndvi_range.select('p100').add((bl_ndvi_range.select('p100').subtract(bl_ndvi_range.select('p0'))).multiply(0.05)))

    # compute percentiles of annual ndvi for the extended baseline period
    percentiles = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
    bl_ndvi_perc = bl_ndvi_ext.reduce(ee.Reducer.percentile(percentiles))

    # compute mean ndvi for the baseline and target period period
    bl_ndvi_mean = ndvi_1yr.select(ee.List(['y{}'.format(i) for i in range(year_bl_start, year_bl_end + 1)])) \
        .reduce(ee.Reducer.mean()).rename(['ndvi'])
    tg_ndvi_mean = ndvi_1yr.select(ee.List(['y{}'.format(i) for i in range(year_tg_start, year_tg_end + 1)])) \
        .reduce(ee.Reducer.mean()).rename(['ndvi'])

    # reclassify mean ndvi for baseline period based on the percentiles
    bl_classes = ee.Image(-32768) \
        .where(bl_ndvi_mean.lte(bl_ndvi_perc.select('p10')), 1) \
        .where(bl_ndvi_mean.gt(bl_ndvi_perc.select('p10')), 2) \
        .where(bl_ndvi_mean.gt(bl_ndvi_perc.select('p20')), 3) \
        .where(bl_ndvi_mean.gt(bl_ndvi_perc.select('p30')), 4) \
        .where(bl_ndvi_mean.gt(bl_ndvi_perc.select('p40')), 5) \
        .where(bl_ndvi_mean.gt(bl_ndvi_perc.select('p50')), 6) \
        .where(bl_ndvi_mean.gt(bl_ndvi_perc.select('p60')), 7) \
        .where(bl_ndvi_mean.gt(bl_ndvi_perc.select('p70')), 8) \
        .where(bl_ndvi_mean.gt(bl_ndvi_perc.select('p80')), 9) \
        .where(bl_ndvi_mean.gt(bl_ndvi_perc.select('p90')), 10)

    # reclassify mean ndvi for target period based on the percentiles
    tg_classes = ee.Image(-32768) \
        .where(tg_ndvi_mean.lte(bl_ndvi_perc.select('p10')), 1) \
        .where(tg_ndvi_mean.gt(bl_ndvi_perc.select('p10')), 2) \
        .where(tg_ndvi_mean.gt(bl_ndvi_perc.select('p20')), 3) \
        .where(tg_ndvi_mean.gt(bl_ndvi_perc.select('p30')), 4) \
        .where(tg_ndvi_mean.gt(bl_ndvi_perc.select('p40')), 5) \
        .where(tg_ndvi_mean.gt(bl_ndvi_perc.select('p50')), 6) \
        .where(tg_ndvi_mean.gt(bl_ndvi_perc.select('p60')), 7) \
        .where(tg_ndvi_mean.gt(bl_ndvi_perc.select('p70')), 8) \
        .where(tg_ndvi_mean.gt(bl_ndvi_perc.select('p80')), 9) \
        .where(tg_ndvi_mean.gt(bl_ndvi_perc.select('p90')), 10)

    # difference between start and end clusters >= 2 means improvement (<= -2 
    # is degradation)
    classes_chg = tg_classes.subtract(bl_classes).where(bl_ndvi_mean.subtract(tg_ndvi_mean).abs().lte(100), 0)

    band_infos = [BandInfo("Productivity state (degradation)", add_to_map=True,
                        metadata={'year_bl_start': year_bl_start, 'year_bl_end': year_bl_end, 'year_tg_start': year_tg_start, 'year_tg_end': year_tg_end}),
                  BandInfo("Productivity state classes", metadata={'year_start': year_bl_start, 'year_end': year_bl_end}),
                  BandInfo("Productivity state classes", metadata={'year_start': year_tg_start, 'year_end': year_tg_end}),
                  BandInfo("Productivity state NDVI mean", metadata={'year_start': year_bl_start, 'year_end': year_bl_end}),
                  BandInfo("Productivity state NDVI mean", metadata={'year_start': year_tg_start, 'year_end': year_tg_end})]
    return TEImage(classes_chg.addBands(bl_classes).addBands(tg_classes).addBands(bl_ndvi_mean).addBands(tg_ndvi_mean).int16(), band_infos)
Exemple #14
0
def management_quality(year, lu_matrix, geometry, EXECUTION_ID, logger):
    logger.debug("Entering management quality function.")

    lu_remap_matrix = [[
        10, 11, 12, 20, 30, 40, 50, 60, 61, 62, 70, 71, 72, 80, 81, 82, 90,
        100, 110, 120, 121, 122, 130, 140, 150, 151, 152, 153, 160, 170, 180,
        200, 201, 202
    ], lu_matrix]

    ## land cover
    lc = ee.Image(
        "users/geflanddegradation/toolbox_datasets/lcov_esacc_1992_2018").clip(
            geometry)
    lc = lc.where(lc.eq(9999), -32768)
    lc = lc.updateMask(lc.neq(-32768))

    # DEFINE LAND USE INTENSITY
    # Remap LC according to input matrix
    lc_remapped_lu = lc.select('y' + '{}'.format(year)).remap(
        lu_remap_matrix[0], lu_remap_matrix[1]).divide(10)

    # DEFINE POPULATION INTENSITY
    population_density = ee.ImageCollection(
        "CIESIN/GPWv411/GPW_Population_Density").toList(5)

    if (year <= 2000):
        population_density = ee.Image(population_density.get(0))
    elif (year > 2000 and year <= 2005):
        population_density = ee.Image(population_density.get(1))
    elif (year > 2005 and year <= 2010):
        population_density = ee.Image(population_density.get(2))
    elif (year > 2010 and year <= 2015):
        population_density = ee.Image(population_density.get(3))
    elif (year > 2015):
        population_density = ee.Image(population_density.get(4))
    else:
        logger.debug("Invalid date range")

    population_density_class = population_density \
        .select("population_density") \
        .where(population_density.lt(4), 1.0) \
        .where(population_density.gte(4).And(population_density.lt(30)), 1.1) \
        .where(population_density.gte(30).And(population_density.lt(80)), 1.2) \
        .where(population_density.gte(80).And(population_density.lt(170)), 1.3) \
        .where(population_density.gte(170).And(population_density.lt(300)), 1.4) \
        .where(population_density.gte(300).And(population_density.lt(500)), 1.5) \
        .where(population_density.gte(500).And(population_density.lt(850)), 1.6) \
        .where(population_density.gte(850).And(population_density.lt(1400)), 1.7) \
        .where(population_density.gte(1400).And(population_density.lt(2000)), 1.8) \
        .where(population_density.gte(2000).And(population_density.lt(2700)), 1.9) \
        .where(population_density.gte(2700), 2.0) \
        .clip(geometry)

    mqi = ee.Image()
    mqi = mqi.expression(
        '(landuse_intensity * population_density)**(1/2)', {
            'landuse_intensity': lc_remapped_lu,
            'population_density': population_density_class
        }).rename("Management Quality Index")

    mqi_range = mqi \
        .where(mqi.lte(1.25), 1) \
        .where(mqi.lte(1.50).And(mqi.gt(1.25)), 2) \
        .where(mqi.gt(1.50), 3)

    return TEImage(mqi_range.clip(geometry), [
        BandInfo("Management Quality Index",
                 add_to_map=True,
                 metadata={'year': year})
    ])
Exemple #15
0
def tc(geometry, fc_threshold, year_start, year_end, method, biomass_data,
       EXECUTION_ID, logger):
    """
    Calculate total carbon (in belowground and aboveground biomass).
    """
    logger.debug("Entering tc function.")
    # geom = ee.Geometry.Polygon(geometry)
    # Location
    # area = ee.FeatureCollection(geom)
    ##############################################
    # DATASETS
    # Import Hansen global forest dataset
    hansen = ee.Image('UMD/hansen/global_forest_change_2019_v1_7').clip(
        geometry)

    # Aboveground Live Woody Biomass per Hectare (Mg/Ha)
    if biomass_data == 'woodshole':
        agb = ee.ImageCollection(
            "users/geflanddegradation/toolbox_datasets/forest_agb_30m_gfw"
        ).mosaic().unmask(0)
    elif biomass_data == 'geocarbon':
        agb = ee.Image(
            "users/geflanddegradation/toolbox_datasets/forest_agb_1km_geocarbon"
        ).clip(geometry)
    else:
        agb = None
    # All datasets will be reprojected to Hansen resolution
    agb = agb.reproject(crs=hansen.projection())

    # JRC Global Surface Water Mapping Layers, v1.0 (>50% occurrence)
    water = ee.Image("JRC/GSW1_0/GlobalSurfaceWater").select(
        "occurrence").clip(geometry)
    water = water.reproject(crs=hansen.projection())

    # reclass to 1.broadleaf, 2.conifer, 3.mixed, 4.savanna
    f_type = ee.Image("users/geflanddegradation/toolbox_datasets/esa_forest_expanded_2015") \
        .clip(geometry) \
        .remap([50,60,61,62,70,71,72,80,81,82,90,100,110],
               [ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3,  3,  3])
    f_type = f_type.reproject(crs=hansen.projection())

    # IPCC climate zones reclassified as from http://eusoils.jrc.ec.europa.eu/projects/RenewableEnergy/
    # 0-No data, 1-Warm Temperate Moist, 2-k Temperate Dry, 3-Cool Temperate
    # Moist, 4-Cool Temperate Dry, 5-Polar Moist,
    # 6-Polar Dry, 7-Boreal Moist, 8-Boreal Dry, 9-Tropical Montane, 10-Tropical Wet, 11-Tropical Moist, 12-Tropical Dry) to
    # 0: no data, 1:trop/sub moist, 2: trop/sub dry, 3: temperate)
    climate = ee.Image("users/geflanddegradation/toolbox_datasets/ipcc_climate_zones") \
        .clip(geometry) \
        .remap([0,1,2,3,4,5,6,7,8,9,10,11,12],
               [0,1,2,3,3,3,3,3,3,1, 1, 1, 2])
    climate = climate.reproject(crs=hansen.projection())

    # Root to shoot ratio methods
    if method == 'ipcc':
        rs_ratio = (
            ee.Image(-32768)
            # low biomass wet tropical forest
            .where(climate.eq(1).And(agb.lte(125)), 0.42)
            # high biomass wet tropical forest
            .where(climate.eq(1).And(agb.gte(125)), 0.24)
            # dry tropical forest
            .where(climate.eq(2), 0.27)
            # low biomass temperate conifer forest
            .where(climate.eq(3).And(f_type.eq(2).And(agb.lte(50))), 0.46)
            # mid biomass temperate conifer forest
            .where(
                climate.eq(3).And(
                    f_type.eq(2).And(agb.gte(50)).And(agb.lte(150))), 0.32)
            # high biomass temperate conifer forest
            .where(climate.eq(3).And(f_type.eq(2).And(agb.lte(150))), 0.23)
            # low biomass temperate broadleaf forest
            .where(climate.eq(3).And(f_type.eq(1).And(agb.lte(75))), 0.43)
            # low biomass temperate broadleaf forest
            .where(
                climate.eq(3).And(
                    f_type.eq(1).And(agb.gte(75)).And(agb.lte(150))), 0.26)
            # low biomass temperate broadleaf forest
            .where(climate.eq(3).And(f_type.eq(1).And(agb.lte(150))), 0.24)
            # low biomass temperate mixed forest
            .where(
                climate.eq(3).And(f_type.eq(1).And(agb.lte(75))),
                (0.46 + 0.43) / 2)
            # low biomass temperate mixed forest
            .where(
                climate.eq(3).And(
                    f_type.eq(1).And(agb.gte(75)).And(agb.lte(150))),
                (0.32 + 0.26) / 2)
            # low biomass temperate mixed forest
            .where(
                climate.eq(3).And(f_type.eq(1).And(agb.lte(150))),
                (0.23 + 0.24) / 2)
            # savanas regardless of climate
            .where(f_type.eq(4), 2.8))
        bgb = agb.multiply(rs_ratio)
    elif (method == 'mokany'):
        # calculate average above and below ground biomass
        # BGB (t ha-1) Citation Mokany et al. 2006 = (0.489)*(AGB)^(0.89)
        # Mokany used a linear regression of root biomass to shoot biomass for forest
        # and woodland and found that BGB(y) is ~ 0.489 of AGB(x).  However,
        # applying a power (0.89) to the shoot data resulted in an improved
        # model for relating root biomass (y) to shoot biomass (x):
        # y = 0:489 x0:890
        bgb = agb.expression('0.489 * BIO**(0.89)', {'BIO': agb})
        rs_ratio = bgb.divide(agb)
    else:
        raise

    # Calculate Total biomass (t/ha) then convert to carbon equilavent (*0.5) to get Total Carbon (t ha-1) = (AGB+BGB)*0.5
    tbcarbon = agb.expression('(bgb + abg) * 0.5', {'bgb': bgb, 'abg': agb})

    # convert Total Carbon to Total Carbon dioxide tCO2/ha
    # One ton of carbon equals 44/12 = 11/3 = 3.67 tons of carbon dioxide
    # teco2 = agb.expression('totalcarbon * 3.67 ', {'totalcarbon': tbcarbon})

    ##############################################/
    # define forest cover at the starting date
    fc_str = hansen.select("treecover2000").gte(fc_threshold) \
        .multiply(hansen.select('lossyear').unmask(0).lte(0).add(hansen.select('lossyear').unmask(0).gt(year_start-2000)))

    # Create three band layer clipped to study area
    # Band 1: forest layer for initial year (0) and year loss coded as numbers (e.g. 1 = 2001)
    # Band 2: root to shoot ratio
    # Band 3: total carbon stocks (tons of C per ha)
    output = fc_str.multiply(hansen.select('lossyear').unmask(0) \
        .lte(year_end-2000).multiply(hansen.select('lossyear').unmask(0))) \
        .where(fc_str.eq(0),-1) \
        .where(water.gte(50),-2).unmask(-32768) \
        .addBands((rs_ratio.multiply(100)).multiply(fc_str)).unmask(-32768) \
        .addBands((tbcarbon.multiply(10)).multiply(fc_str)).unmask(-32768)
    output = output.reproject(crs=hansen.projection())

    logger.debug("Setting up output.")
    out = TEImage(output.int16().clip(geometry), [
        BandInfo("Forest loss",
                 add_to_map=True,
                 metadata={
                     'year_start': year_start,
                     'year_end': year_end,
                     'ramp_min': year_start - 2000 + 1,
                     'ramp_max': year_end - 2000,
                     'threshold': fc_threshold
                 }),
        BandInfo(
            "Root/shoot ratio", add_to_map=False, metadata={'method': method}),
        BandInfo("Total carbon",
                 add_to_map=True,
                 metadata={
                     'year_start': year_start,
                     'year_end': year_end,
                     'method': method,
                     'threshold': fc_threshold
                 })
    ])
    return out
def urban_area(geojson, un_adju, EXECUTION_ID, logger):
    """
    Calculate urban area.
    """

    logger.debug("Entering urban_area function.")

    aoi = ee.Geometry(geojson)

    # Read asset with the time series of urban extent
    urban_series = ee.Image("users/geflanddegradation/toolbox_datasets/urban_series").int32()

    # Load population data from GPWv4: Gridded Population of the World Version 4, People/km2 (not UN adjusted)
    if un_adju:
        pop_densi = ee.ImageCollection("CIESIN/GPWv4/population-density")
    else:
        pop_densi = ee.ImageCollection("CIESIN/GPWv4/unwpp-adjusted-population-density")
      
    # the .multiply(100).int32() is to make the rasters integers, with units for density of people/km2 * 100
      
    # Select population datasets for each year
    pop2000 = pop_densi.filter(ee.Filter.eq("system:index", "2000")).mean().multiply(100).int32()
    pop2005 = pop_densi.filter(ee.Filter.eq("system:index", "2005")).mean().multiply(100).int32()
    pop2010 = pop_densi.filter(ee.Filter.eq("system:index", "2010")).mean().multiply(100).int32()
    pop2015 = pop_densi.filter(ee.Filter.eq("system:index", "2015")).mean().multiply(100).int32()

    # Use urban extent to mask population density data
    urb_pop2000 = pop2000.updateMask(urban_series.eq(1))
    urb_pop2005 = pop2005.updateMask(urban_series.gte(1).And(urban_series.lte(2)))
    urb_pop2010 = pop2010.updateMask(urban_series.gte(1).And(urban_series.lte(3)))
    urb_pop2015 = pop2015.updateMask(urban_series.gte(1).And(urban_series.lte(4)))
		
    # Compute mean population density per year
    urb_pop2000m = urb_pop2000.reduceRegion(reducer=ee.Reducer.mean(), 
                                            geometry=aoi, scale=30, 
                                            maxPixels=1e12)
    urb_pop2005m = urb_pop2005.reduceRegion(reducer= ee.Reducer.mean(), 
                                            geometry=aoi, scale=30, 
                                            maxPixels=1e12)
    urb_pop2010m = urb_pop2010.reduceRegion(reducer=ee.Reducer.mean(), 
                                            geometry=aoi, scale=30, 
                                            maxPixels=1e12)
    urb_pop2015m = urb_pop2015.reduceRegion(reducer=ee.Reducer.mean(), 
                                            geometry=aoi, scale=30, 
                                            maxPixels=1e12)
		
    # Compute urban area per year
    pixel_area = urban_series.updateMask(urban_series.eq(1)).multiply(ee.Image.pixelArea())

    urb_are2000 = urban_series.updateMask(urban_series.eq(1)).multiply(ee.Image.pixelArea()) \
        .reduceRegion(reducer=ee.Reducer.sum(), geometry=aoi, scale=30,
                      maxPixels=1e12)
    urb_are2005 = urban_series.updateMask(urban_series.gte(1).And(urban_series.lte(2))).multiply(ee.Image.pixelArea()) \
        .reduceRegion(reducer=ee.Reducer.sum(), geometry=aoi, scale=30,
                      maxPixels=1e12)
    urb_are2010 = urban_series.updateMask(urban_series.gte(1).And(urban_series.lte(3))).multiply(ee.Image.pixelArea()) \
        .reduceRegion(reducer=ee.Reducer.sum(), geometry=aoi, scale=30,
                      maxPixels=1e12)
    urb_are2015 = urban_series.updateMask(urban_series.gte(1).And(urban_series.lte(4))).multiply(ee.Image.pixelArea()) \
        .reduceRegion(reducer=ee.Reducer.sum(), geometry=aoi, scale=30,
                      maxPixels=1e12)                              

    # Make a dictionary to contain results
    result_table = ee.Dictionary({
      "pop_dens2000": urb_pop2000m.get('population-density'),
      "pop_dens2005": urb_pop2005m.get('population-density'),
      "pop_dens2010": urb_pop2010m.get('population-density'),
      "pop_dens2015": urb_pop2015m.get('population-density'),
      "urb_area2000": urb_are2000.get('classification'),
      "urb_area2005": urb_are2005.get('classification'),
      "urb_area2010": urb_are2010.get('classification'),
      "urb_area2015": urb_are2015.get('classification'),
    })

    # # Export the FeatureCollection.
    # Export.table.toDrive({
    #   collection: ee.FeatureCollection([ee.Feature(null,result_table)]),
    #   description: "export_urban_extent_table",
    #   folder: 'sdg1131',
    #   fileFormat: 'CSV'})
    #
    # Export raster
    result_raster = urban_series.addBands(urb_pop2000).addBands(urb_pop2005).addBands(urb_pop2010).addBands(urb_pop2015)

    logger.debug("Setting up output.")
    out = TEImage(result_raster.clip(aoi),
                  [BandInfo("Urban series", add_to_map=True, metadata={'years': [2000, 2005, 2010, 2015]}),
                   BandInfo("Population", metadata={'year': 2000}),
                   BandInfo("Population", metadata={'year': 2005}),
                   BandInfo("Population", metadata={'year': 2010}),
                   BandInfo("Population", add_to_map=True, metadata={'year': 2015})])

    #out.image = out.image.unmask(-32768).int16()

    return out
def soil_quality(depth, texture_matrix, geometry, EXECUTION_ID, logger):
    """
    ===========================================================================================
                 SOIL QUALITY INDEX (SQI)
    ===========================================================================================
    The impact of the soil factor to the process of desertification is determined by the strength of cohesion between soil particles, water retention ability of the soil, soil texture, and structure. The soil quality index (SQI), developed by OSS, that will be used is based on four parameters:

    - parent material
    - soil depth (0 to 200cm)
    - soil texture
    - slope
    - rock fragment
    - drainage

    The formula used to compute the SQI from the above-mentioned parameters is as shown below:

    SQI = (parent material*soil depth*soil texture* slope* rock fragment*drainage)^1/6
    """

    logger.debug("Entering soil quality function.")
    srtm = ee.Image("USGS/SRTMGL1_003")

    # ==========================
    # PARENT MATERIAL
    # ==========================

    # parent_material_map = [
    #   [1,2,3,4,5,6,7,8,9, 10, 11, 12, 13, 14, 15],pmaterial_matrix]

    parent_material = ee.Image("users/miswagrace/parent_material_northafrica").clip(geometry)
    parent_material = parent_material.remap([1.0, 1.2, 1.4, 1.5, 1.6, 1.7, 2.0],[1.0, 1.7, 1.7, 1.7, 1.7, 1.7, 2.0])

    # resample parent material to srtm projection
    parent_material = parent_material.reproject(crs=srtm.projection())
    # remap parent material 
    # parent_material = parent_material.remap(parent_material_map[0], parent_material_map[1])

    # ==========================
    # SLOPE
    # ==========================
    slope = ee.Image("users/miswagrace/slope_north_africa").clip(geometry)
    slope = slope.reproject(crs=srtm.projection())
   
    # ==========================
    # TEXTURE
    # ==========================    
    # soil_texture = ee.Image("OpenLandMap/SOL/SOL_TEXTURE-CLASS_USDA-TT_M/v02").clip(geometry)
    soil_texture = ee.Image("users/miswagrace/texture_north_africa").clip(geometry)
    remap_texture = [
        [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],texture_matrix]

    # remap texture 
    soil_texture_remap = soil_texture.remap(remap_texture[0], remap_texture[1])
    # soil_texture_remap = soil_texture_remap \
    #     .where(soil_texture_remap.eq(1), 1) \
    #     .where(soil_texture_remap.eq(2), 1.2) \
    #     .where(soil_texture_remap.eq(3), 1.6) \
    #     .where(soil_texture_remap.eq(4), 2) \
    #     .rename("Soil Texture")

    # soil_texture_remap = soil_texture_remap.updateMask(soil_texture_remap.neq(-32768))

    # ==========================
    # DEPTH
    # ==========================
    if depth<15 :
        depthIndex = 4
    elif depth>=15 and depth<30:
        depthIndex = 3
    elif depth>=30 and depth<75:
        depthIndex = 2
    elif depth>=75:
        depthIndex = 1
    else:
        logger.debug("Unexpected depth value")

    # ==========================
    # ROCK FRAGMENT
    # ==========================
    # classify rock fragment based on 3 classes
    rock_fragment = ee.Image("users/miswagrace/rock_fragment_north_africa")
    fragmentClass = rock_fragment \
        .where(rock_fragment.lt(20), 2) \
        .where(rock_fragment.gte(20).And(rock_fragment.lte(60)), 1.3) \
        .where(rock_fragment.gt(60), 1) \
        .rename("Rock Fragments")

    # ==========================
    # SOIL DRAINAGE
    # ==========================
 
    drainage = ee.Image("users/miswagrace/drainage_northafrica")
    soil_drainage = drainage.remap([1,2,3,4,5,6,7],[2.0, 2.0, 1.4, 1.2, 1.0, 1.7, 2.0])
    # ==========================
    # SOIL QUALITY INDEX
    # ========================== 
    # compute soil quality index based on datasetes generated above
    img = ee.Image()
    sqi = img.expression('(slope * parent_material * soil_texture * soil_depth * rock_fragment *drainage) ** (1/6)', {
        'slope':slope,
        'parent_material':parent_material,
        'soil_texture':soil_texture_remap,
        'soil_depth':depthIndex,
        'rock_fragment':fragmentClass.clip(geometry),
        'drainage':soil_drainage.clip(geometry)
        }).rename('sqi')  

    # classify output sqi into 3 classes 
    sqi = sqi \
        .where(sqi.lt(1.13), 1) \
        .where(sqi.gte(1.13).And(sqi.lte(1.45)), 2) \
        .where(sqi.gt(1.45), 3)
    

    return TEImage(sqi,
        [BandInfo("Soil Quality Index (cm deep)", add_to_map=True, metadata={'depth':depth})])
Exemple #18
0
def vegetation_quality(year, ndvi_start, ndvi_end, drought_matrix, fire_matrix,
                       erosion_matrix, geometry, EXECUTION_ID, logger):

    drought_remap_matrix = [[
        10, 11, 12, 20, 30, 40, 50, 60, 61, 62, 70, 71, 72, 80, 81, 82, 90,
        100, 110, 120, 121, 122, 130, 140, 150, 151, 152, 153, 160, 170, 180,
        200, 201, 202
    ], drought_matrix]

    fire_remap_matrix = [[
        10, 11, 12, 20, 30, 40, 50, 60, 61, 62, 70, 71, 72, 80, 81, 82, 90,
        100, 110, 120, 121, 122, 130, 140, 150, 151, 152, 153, 160, 170, 180,
        200, 201, 202
    ], fire_matrix]

    erosion_remap_matrix = [[
        10, 11, 12, 20, 30, 40, 50, 60, 61, 62, 70, 71, 72, 80, 81, 82, 90,
        100, 110, 120, 121, 122, 130, 140, 150, 151, 152, 153, 160, 170, 180,
        200, 201, 202
    ], erosion_matrix]

    ## land cover
    lc = ee.Image(
        "users/geflanddegradation/toolbox_datasets/lcov_esacc_1992_2018").clip(
            geometry)
    lc = lc.where(lc.eq(9999), -32768)
    lc = lc.updateMask(lc.neq(-32768))

    # DEFINE DROUGHT RESISTANCE
    lc_remapped_drought = lc.select('y' + year).remap(
        drought_remap_matrix[0], drought_remap_matrix[1]).divide(10)

    # DEFINE FIRE RISK
    lc_remapped_fire = lc.select('y' + year).remap(
        fire_remap_matrix[0], fire_remap_matrix[1]).divide(10)

    # DEFINE EROSION PROTECTION
    lc_remapped_erosion = lc.select('y' + year).remap(
        erosion_remap_matrix[0], erosion_remap_matrix[1]).divide(10)

    # DEFINE PLANT COVER
    max_ndvi = ee.ImageCollection('VITO/PROBAV/C1/S1_TOC_100M') \
                  .filter(ee.Filter.date(ndvi_start, ndvi_end)) \
                  .max() \
                  .select('NDVI')

    plant_cover_range = max_ndvi.divide(255).multiply(100)

    plant_cover_class = plant_cover_range \
        .where(plant_cover_range.gte(80), 1.0) \
        .where(plant_cover_range.lt(80).And(plant_cover_range.gte(72)), 1.1) \
        .where(plant_cover_range.lt(72).And(plant_cover_range.gte(62)), 1.2) \
        .where(plant_cover_range.lt(62).And(plant_cover_range.gte(50)), 1.3) \
        .where(plant_cover_range.lt(50).And(plant_cover_range.gte(38)), 1.4) \
        .where(plant_cover_range.lt(38).And(plant_cover_range.gte(16)), 1.5) \
        .where(plant_cover_range.lt(26).And(plant_cover_range.gte(18)), 1.6) \
        .where(plant_cover_range.lt(18).And(plant_cover_range.gte(13)), 1.7) \
        .where(plant_cover_range.lt(13).And(plant_cover_range.gte(11)), 1.8) \
        .where(plant_cover_range.lt(11).And(plant_cover_range.gte(10)), 1.9) \
        .where(plant_cover_range.lt(10), 2) \
        .clip(geometry) \
        .rename("Plant Cover")

    # CALCULATE VEGETATION QUALITY INDEX
    vqi = ee.Image()

    vqi = vqi.expression(
        '(fire_risk * erosion_protection * drought_resistance * plant_cover)**(1/4)',
        {
            'fire_risk': lc_remapped_fire,
            'erosion_protection': lc_remapped_erosion,
            'drought_resistance': lc_remapped_drought,
            'plant_cover': plant_cover_class,
        }).rename("Vegetation Quality Index")

    vqi_range = vqi \
        .where(vqi.lte(1.13), 1) \
        .where(vqi.lte(1.38).And(vqi.gt(1.13)), 2) \
        .where(vqi.gt(1.38), 3)

    return TEImage(vqi_range.clip(geometry), [
        BandInfo("Vegetation Quality Index",
                 add_to_map=True,
                 metadata={'year': year})
    ])
Exemple #19
0
def productivity_performance(year_start, year_end, ndvi_gee_dataset, geojson,
                             EXECUTION_ID, logger):
    logger.debug("Entering productivity_performance function.")

    ndvi_1yr = ee.Image(ndvi_gee_dataset)
    ndvi_1yr = ndvi_1yr.where(ndvi_1yr.eq(9999), -32768)
    ndvi_1yr = ndvi_1yr.updateMask(ndvi_1yr.neq(-32768))

    # land cover data from esa cci
    lc = ee.Image("users/geflanddegradation/toolbox_datasets/lcov_esacc_1992_2019")
    lc = lc.where(lc.eq(9999), -32768)
    lc = lc.updateMask(lc.neq(-32768))

    # global agroecological zones from IIASA
    soil_tax_usda = ee.Image("users/geflanddegradation/toolbox_datasets/soil_tax_usda_sgrid")

    # Make sure the bounding box of the poly is used, and not the geodesic 
    # version, for the clipping
    poly = ee.Geometry(geojson, opt_geodesic=False)

    # compute mean ndvi for the period
    ndvi_avg = ndvi_1yr.select(ee.List(['y{}'.format(i) for i in range(year_start, year_end + 1)])) \
        .reduce(ee.Reducer.mean()).rename(['ndvi']).clip(poly)

    # Handle case of year_start that isn't included in the CCI data
    if year_start > 2015:
        lc_year_start = 2015
    elif year_start < 1992:
        lc_year_start = 1992
    else:
        lc_year_start = year_start
    # reclassify lc to ipcc classes
    lc_t0 = lc.select('y{}'.format(lc_year_start)) \
        .remap([10, 11, 12, 20, 30, 40, 50, 60, 61, 62, 70, 71, 72, 80, 81, 82, 90, 100, 160, 170, 110, 130, 180, 190, 120, 121, 122, 140, 150, 151, 152, 153, 200, 201, 202, 210], 
               [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36])

    # create a binary mask.
    mask = ndvi_avg.neq(0)

    # define modis projection attributes
    modis_proj = ee.Image("users/geflanddegradation/toolbox_datasets/ndvi_modis_2001_2020").projection()

    # reproject land cover, soil_tax_usda and avhrr to modis resolution
    lc_proj = lc_t0.reproject(crs=modis_proj)
    soil_tax_usda_proj = soil_tax_usda.reproject(crs=modis_proj)
    ndvi_avg_proj = ndvi_avg.reproject(crs=modis_proj)

    # define unit of analysis as the intersect of soil_tax_usda and land cover
    units = soil_tax_usda_proj.multiply(100).add(lc_proj)

    # create a 2 band raster to compute 90th percentile per unit (analysis restricted by mask and study area)
    ndvi_id = ndvi_avg_proj.addBands(units).updateMask(mask)

    # compute 90th percentile by unit
    perc90 = ndvi_id.reduceRegion(reducer=ee.Reducer.percentile([90]).
                                  group(groupField=1, groupName='code'),
                                  geometry=poly,
                                  scale=ee.Number(modis_proj.nominalScale()).getInfo(),
                                  maxPixels=1e15)

    # Extract the cluster IDs and the 90th percentile
    groups = ee.List(perc90.get("groups"))
    ids = groups.map(lambda d: ee.Dictionary(d).get('code'))
    perc = groups.map(lambda d: ee.Dictionary(d).get('p90'))

    # remap the units raster using their 90th percentile value
    raster_perc = units.remap(ids, perc)

    # compute the ration of observed ndvi to 90th for that class
    obs_ratio = ndvi_avg_proj.divide(raster_perc)

    # aggregate obs_ratio to original NDVI data resolution (for modis this step does not change anything)
    obs_ratio_2 = obs_ratio.reduceResolution(reducer=ee.Reducer.mean(), maxPixels=2000) \
        .reproject(crs=ndvi_1yr.projection())

    # create final degradation output layer (9999 is background), 0 is not
    # degreaded, -1 is degraded
    lp_perf_deg = ee.Image(-32768).where(obs_ratio_2.gte(0.5), 0) \
        .where(obs_ratio_2.lte(0.5), -1)

    return TEImage(lp_perf_deg.addBands(obs_ratio_2.multiply(10000)).addBands(units).unmask(-32768).int16(),
                   [BandInfo("Productivity performance (degradation)", add_to_map=True, metadata={'year_start': year_start, 'year_end': year_end}),
                    BandInfo("Productivity performance (ratio)", metadata={'year_start': year_start, 'year_end': year_end}),
                    BandInfo("Productivity performance (units)", metadata={'year_start': year_start})])