|
| 1 | +import numpy as np |
| 2 | +import xarray as xr |
| 3 | + |
| 4 | +product_name = "wit_ls9" |
| 5 | +version = "0.0.1" |
| 6 | +resampling = { |
| 7 | + "water": "nearest", |
| 8 | + "bs": "nearest", |
| 9 | + "pv": "nearest", |
| 10 | + "npv": "nearest", |
| 11 | + "fmask": "nearest", |
| 12 | + "*": "bilinear", |
| 13 | +} |
| 14 | +output_crs = "EPSG:3577" |
| 15 | +resolution = (-30, 30) |
| 16 | + |
| 17 | +# load Water Observations, Landsat 9 and Fractional Cover data |
| 18 | +input_products = { |
| 19 | + "ga_ls_wo_3": ["water"], |
| 20 | + "ga_ls9c_ard_3": [ |
| 21 | + "nbart_blue", |
| 22 | + "nbart_green", |
| 23 | + "nbart_red", |
| 24 | + "nbart_nir", |
| 25 | + "nbart_swir_1", |
| 26 | + "nbart_swir_2", |
| 27 | + ], |
| 28 | + "ga_ls_fc_3": ["bs", "pv", "npv"], |
| 29 | +} |
| 30 | + |
| 31 | + |
| 32 | +def _tcw(ds: xr.Dataset) -> xr.DataArray: |
| 33 | + # Tasseled Cap Wetness, Crist 1985 |
| 34 | + ds = ds # don't normalise! |
| 35 | + return ( |
| 36 | + 0.0315 * ds.nbart_blue |
| 37 | + + 0.2021 * ds.nbart_green |
| 38 | + + 0.3102 * ds.nbart_red |
| 39 | + + 0.1594 * ds.nbart_nir |
| 40 | + + -0.6806 * ds.nbart_swir_1 |
| 41 | + + -0.6109 * ds.nbart_swir_2 |
| 42 | + ) |
| 43 | + |
| 44 | + |
| 45 | +def transform(inputs: xr.Dataset) -> xr.Dataset: |
| 46 | + # organize the datas structure |
| 47 | + # to apply WIT Notebook processing |
| 48 | + # approach |
| 49 | + |
| 50 | + ard_ds = xr.merge([inputs[e] for e in input_products["ga_ls9c_ard_3"]]) |
| 51 | + wo_ds = xr.merge([inputs[e] for e in input_products["ga_ls_wo_3"]]) |
| 52 | + fc_ds = xr.merge([inputs[e] for e in input_products["ga_ls_fc_3"]]) |
| 53 | + |
| 54 | + tcw = _tcw(ard_ds) |
| 55 | + |
| 56 | + # divide FC values by 100 to keep them in [0, 1] |
| 57 | + bs = fc_ds.bs / 100 |
| 58 | + pv = fc_ds.pv / 100 |
| 59 | + npv = fc_ds.npv / 100 |
| 60 | + |
| 61 | + # generate the WIT raster bands |
| 62 | + # create an empty dataset called 'output_rast' and populate with values from input datasets |
| 63 | + rast_names = ["pv", "npv", "bs", "wet", "water"] |
| 64 | + output_rast = {n: xr.zeros_like(ard_ds) for n in rast_names} |
| 65 | + |
| 66 | + output_rast["bs"] = bs |
| 67 | + output_rast["pv"] = pv |
| 68 | + output_rast["npv"] = npv |
| 69 | + |
| 70 | + # Mask noncontiguous data, low solar incidence angle, cloud, and water out of the wet category |
| 71 | + # by disabling those flags |
| 72 | + mask = (wo_ds.water & 0b01100011) == 0 |
| 73 | + # not apply poly_raster cause we will do it before summarise |
| 74 | + |
| 75 | + open_water = wo_ds.water & (1 << 7) > 0 |
| 76 | + |
| 77 | + # Thresholding |
| 78 | + # set wet pixels where not masked and above threshold of -350 |
| 79 | + wet = tcw.where(mask) > -350 |
| 80 | + |
| 81 | + # TCW |
| 82 | + output_rast["wet"] = wet.astype(float) |
| 83 | + for name in rast_names[:3]: |
| 84 | + output_rast[name].values[wet.values] = 0 |
| 85 | + |
| 86 | + # WO |
| 87 | + output_rast["water"] = open_water.astype(float) |
| 88 | + |
| 89 | + for name in rast_names[0:4]: |
| 90 | + output_rast[name].values[open_water.values] = 0 |
| 91 | + |
| 92 | + # save this mask then can do 90% check in summarise |
| 93 | + output_rast["mask"] = (mask).astype(int) |
| 94 | + |
| 95 | + # masking |
| 96 | + ds_wit = xr.Dataset(output_rast).where(mask) |
| 97 | + |
| 98 | + return ds_wit |
| 99 | + |
| 100 | + |
| 101 | +def summarise(inputs: xr.Dataset) -> xr.Dataset: |
| 102 | + |
| 103 | + # calculate percentage missing |
| 104 | + pc_missing = 1 - (np.nansum(inputs.mask.values) / len(inputs.mask.values)) |
| 105 | + # inputs = inputs.where(pc_missing < 0.1) |
| 106 | + |
| 107 | + output = {} # band -> value |
| 108 | + output["water"] = inputs.water.mean() |
| 109 | + output["wet"] = inputs.wet.mean() |
| 110 | + output["bs"] = inputs.bs.mean() |
| 111 | + output["pv"] = inputs.pv.mean() |
| 112 | + output["npv"] = inputs.npv.mean() |
| 113 | + output["pc_missing"] = pc_missing |
| 114 | + |
| 115 | + return xr.Dataset(output) |
0 commit comments