1 | import argparse, datetime, numpy as np, netCDF4, sys |
---|
2 | from grid import Grid |
---|
3 | from cwt import cwt_kg31to7, cwt_gen2orc, cwt_gen2orc16 |
---|
4 | from basics import printStat, writePFTMAP |
---|
5 | CWT1 = cwt_kg31to7 |
---|
6 | CWT2 = cwt_gen2orc # cwt_gen2orc for standard 15 PFTs, cwt_gen2orc16 for additional urbain PFT |
---|
7 | debug = False |
---|
8 | |
---|
9 | parser = argparse.ArgumentParser() |
---|
10 | parser.add_argument("year", action="store", type=int, help="year to process)") |
---|
11 | parser.add_argument("nlat", action="store", type=int, help="number of latitude steps (143 for LMDZ resolution)") |
---|
12 | parser.add_argument("-part", action="store", type=int, default=0, help="part (1 or 2 for nlat=14400 to split in two parts", required = False) |
---|
13 | args = parser.parse_args() |
---|
14 | year = args.year |
---|
15 | nlat = args.nlat |
---|
16 | part = args.part |
---|
17 | nlon = 144 if nlat == 143 else nlat * 2 |
---|
18 | if part == 0: lat1=0; lat2=nlat |
---|
19 | elif part == 1: lat1=0; lat2=int(nlat/2) |
---|
20 | elif part == 2: lat1=int(nlat/2); lat2=nlat |
---|
21 | step = 180. / nlat |
---|
22 | luh_year0 = 1992 |
---|
23 | esa = "v2.0.7b" if year <= 2015 else "v2.1.1" |
---|
24 | print("GEN2ORC for %s, %sx%s, %s:%s" % (year, nlat, nlon, lat1, lat2)) |
---|
25 | |
---|
26 | atts = dict(description = "ORCHIDEE PFT map derived from ESACCI Land Cover map %(esa)s for %(year)s, aggregated using user-tool v4.3, cross-walking table v2.4, C3/C4 for grasses from C.Still (May 2018), C3/C4 for crops from LUH2 (2022)" % dict(esa = esa, year = year), |
---|
27 | PFT1 = "bare soil", |
---|
28 | PFT2 = "tropical broadleaf evergreen", |
---|
29 | PFT3 = "tropical broadleaf raingreen", |
---|
30 | PFT4 = "temperate needleleaf evergreen", |
---|
31 | PFT5 = "temperate broadleaf evergreen", |
---|
32 | PFT6 = "temperate broadleaf summergreen", |
---|
33 | PFT7 = "boreal needleleaf evergreen", |
---|
34 | PFT8 = "boreal broadleaf summergreen", |
---|
35 | PFT9 = "boreal needleleaf summergreen", |
---|
36 | PFT10 = "temperate C3 grass", |
---|
37 | PFT11 = "C4 grass", |
---|
38 | PFT12 = "C3 agriculture", |
---|
39 | PFT13 = "C4 agriculture", |
---|
40 | PFT14 = "tropical C3 grass", |
---|
41 | PFT15 = "boreal C3 grass", |
---|
42 | # PFT16 = "urban", # add this attribute for 16 PFTs (with additional urbain PFT) |
---|
43 | contact = "V.Bastrikov (vladislav.bastrikov@lsce.ipsl.fr), P.Peylin (peylin@lsce.ipsl.fr)", |
---|
44 | web = "https://orchidas.lsce.ipsl.fr/dev/lccci/", |
---|
45 | date = datetime.datetime.now().strftime("%d/%m/%Y %H:%M")) |
---|
46 | |
---|
47 | path_gen = "/home/surface5/vbastri/BDD/ESACCI-LC/GENERIC_PFT/%(step)s/ESACCI-LC-L4-LCCS-Map-300m-P1Y-aggregated-%(step).6fDeg-%(year)s-%(esa)s.nc" % dict(step = step, year = year, esa = esa) |
---|
48 | path_luh = "/home/surface5/vbastri/BDD/LUH2/TRENDY2022/LUH2v11_1992-2022_%(nlat)sx%(nlon)s.nc" % dict(nlat = nlat, nlon = nlon) |
---|
49 | path_kg = "/home/surface5/vbastri/BDD/KG2017/KG_%(nlat)sx%(nlon)s.nc" % dict(nlat = nlat, nlon = nlon) |
---|
50 | path_c4 = "/home/surface5/vbastri/BDD/C4FRAC/C4frac_%(nlat)sx%(nlon)s.nc" % dict(nlat = nlat, nlon = nlon) |
---|
51 | path_wb = "/home/surface5/vbastri/BDD/ESACCI-LC/WATER/WB_%(nlat)sx%(nlon)s.nc" % dict(nlat = nlat, nlon = nlon) |
---|
52 | path_out = "PFTmap_%(year)s_%(nlat)sx%(nlon)s%(end)s.nc" % dict(year = year, nlat = nlat, nlon = nlon, end = "_%(lat1)s-%(lat2)s" % dict(lat1 = lat1, lat2 = lat2) if part!=0 else "") |
---|
53 | GEN_LIST = ["Tree_Broadleaf_Evergreen", "Tree_Broadleaf_Deciduous", "Tree_Needleleaf_Evergreen", "Tree_Needleleaf_Deciduous", "Shrub_Broadleaf_Evergreen", "Shrub_Broadleaf_Deciduous", "Shrub_Needleleaf_Evergreen", "Shrub_Needleleaf_Deciduous", "Natural_Grass", "Crops", "Bare_soil", "Water", "Snow_Ice", "Urban", "NoData"] |
---|
54 | GEN_KEYS = ["TrBrEv", "TrBrDe", "TrNeEv", "TrNeDe", "ShBrEv", "ShBrDe", "ShNeEv", "ShNeDe", "NatGr", "Crops", "BS", "Water", "SnowIce", "Urban", "NoData"] |
---|
55 | KG_KEYS = CWT1[0][1:] |
---|
56 | ORC_KEYS = CWT2[0][2:] |
---|
57 | |
---|
58 | # Function to safely divide two streams: if denominator equals zero, division result is set to zero |
---|
59 | # During division zero values in denominator are changed to 1 so that python does not warn on errors |
---|
60 | def divide(num, den): return np.where(den == 0, 0, num / np.where(den == 0, 1, den)) |
---|
61 | |
---|
62 | # Function to make plots for data streams |
---|
63 | def plot(**kwargs): |
---|
64 | for name, data in kwargs.items(): |
---|
65 | for idx, item in enumerate(data if len(data.shape)>2 else [data]): |
---|
66 | grid.plotmap(np.ma.masked_where(item==0, item), dpi=150, vmin=0, vmax=1, |
---|
67 | filename="%s%s.png" % (name.lower(), idx if len(data.shape)>2 else ""), title="%s%s" % (name.upper(), idx if len(data.shape)>2 else ""), |
---|
68 | info="min=%.3g\nmax=%.3g\nmean=%.3g\nnzpts=%s" % (item.min(), item.max(), grid.meanmask(np.ma.masked_where(item==0, item)), (item>0).sum())) |
---|
69 | |
---|
70 | # Read and normalize generic PFTmap |
---|
71 | print("\nREAD GENERIC PFTMAP") |
---|
72 | nc = netCDF4.Dataset(path_gen) |
---|
73 | lat = nc.variables["lat"][lat1:lat2] |
---|
74 | lon = nc.variables["lon"][:] |
---|
75 | gen = np.zeros((len(GEN_LIST), len(lat), len(lon))) |
---|
76 | for idx, item in enumerate(GEN_LIST): gen[idx] = nc.variables[item][lat1:lat2].astype("f8").filled(0) |
---|
77 | nc.close() |
---|
78 | printStat(gen=gen) |
---|
79 | gen = divide(gen, gen.sum(axis=0)) |
---|
80 | printStat(genn=gen) |
---|
81 | if debug: grid = Grid(lat=lat, lon=lon) |
---|
82 | if debug: plot(gen=gen, gensum=gen.sum(axis=0)) |
---|
83 | |
---|
84 | # Read and normalize Koeppen-Geiger map with 31 classes |
---|
85 | print("\nREAD KG") |
---|
86 | nc = netCDF4.Dataset(path_kg) |
---|
87 | if not np.allclose(lat, nc.variables["lat"][lat1:lat2]) or not np.allclose(lon, nc.variables["lon"][:]): print("Different grid"); sys.exit() |
---|
88 | kg31 = nc.variables["kg"][:,lat1:lat2].astype("f8").filled(0) |
---|
89 | nc.close() |
---|
90 | printStat(kg31=kg31) |
---|
91 | kg31 = divide(kg31, kg31.sum(axis=0)) |
---|
92 | printStat(kg31n=kg31) |
---|
93 | |
---|
94 | # Aggregate KG into 7 groups |
---|
95 | print("Check CWT1 kg31to7 = %s" % np.all([np.array(item[1:]).sum() == 1 for item in CWT1[1:]])) |
---|
96 | kg = np.zeros((len(KG_KEYS), len(lat), len(lon))) |
---|
97 | for idx, rule in enumerate(CWT1[1:]): kg = kg + kg31[idx] * np.array(rule[1:])[:,None,None] |
---|
98 | printStat(kg=kg) |
---|
99 | if debug: plot(kg=kg) |
---|
100 | # Fill non-defined values in KG with the dominant class across each latitude (or last class if no data in a latitude) |
---|
101 | domclass = np.where(kg.sum(axis=(0,2)) == 0, len(kg)-1, kg.sum(axis=2).argmax(axis=0)) |
---|
102 | for idx in range(len(lat)): kg[domclass[idx], idx, kg[:,idx,:].sum(axis=0) == 0] = 1 |
---|
103 | printStat(kgf=kg) |
---|
104 | if debug: plot(kgf=kg) |
---|
105 | |
---|
106 | print("\nREAD C4") |
---|
107 | nc = netCDF4.Dataset(path_c4) |
---|
108 | if not np.allclose(lat, nc.variables["lat"][lat1:lat2]) or not np.allclose(lon, nc.variables["lon"][:]): print("Different grid"); sys.exit() |
---|
109 | c4 = nc.variables["C4frac"][lat1:lat2].astype("f8").filled(0) |
---|
110 | nc.close() |
---|
111 | printStat(c4=c4) |
---|
112 | if debug: plot(c4=c4) |
---|
113 | |
---|
114 | print("\nREAD WB") |
---|
115 | nc = netCDF4.Dataset(path_wb) |
---|
116 | if not np.allclose(lat, nc.variables["lat"][lat1:lat2]) or not np.allclose(lon, nc.variables["lon"][:]): print("Different grid"); sys.exit() |
---|
117 | wb = nc.variables["wb"][:,lat1:lat2].astype("f8").filled(0) |
---|
118 | nc.close() |
---|
119 | printStat(wb=wb) |
---|
120 | wb = divide(wb, wb.sum(axis=0)) |
---|
121 | printStat(wbn=wb) |
---|
122 | ocean = np.where(wb[0] > 0.999, 1, 0) # ocean 0/1-mask |
---|
123 | coast = np.where((wb[0] > 0) & (wb[0] <= 0.999), 1, 0) # coast 0/1-mask |
---|
124 | inland = np.where(wb[0] == 0, wb[2], 0) # inland water fraction excluding coasts |
---|
125 | print("ocean_npts=%s, coast_npts=%s, inland_npts=%s" % (ocean.sum(), coast.sum(), (inland>0).sum())) |
---|
126 | if debug: plot(wb=wb, ocean=ocean, coast=coast, inland=inland) |
---|
127 | |
---|
128 | # read C4 map from LUH2 for crops |
---|
129 | print("\nREAD LUH") |
---|
130 | nc = netCDF4.Dataset(path_luh) |
---|
131 | if not np.allclose(lat, nc.variables["lat"][lat1:lat2]) or not np.allclose(lon, nc.variables["lon"][:]): print("Different grid"); sys.exit() |
---|
132 | c3ann = nc.variables["c3ann"][year-luh_year0, lat1:lat2].astype("f8").filled(0) |
---|
133 | c4ann = nc.variables["c4ann"][year-luh_year0, lat1:lat2].astype("f8").filled(0) |
---|
134 | c3per = nc.variables["c3per"][year-luh_year0, lat1:lat2].astype("f8").filled(0) |
---|
135 | c4per = nc.variables["c4per"][year-luh_year0, lat1:lat2].astype("f8").filled(0) |
---|
136 | c3nfx = nc.variables["c3nfx"][year-luh_year0, lat1:lat2].astype("f8").filled(0) |
---|
137 | total = c3ann + c4ann + c3per + c4per + c3nfx |
---|
138 | luhc4 = divide(c4ann + c4per, total) |
---|
139 | nc.close() |
---|
140 | printStat(luhc4=luhc4) |
---|
141 | if debug: plot(luhc4=luhc4) |
---|
142 | |
---|
143 | # aggregate generic PFTs to ORCHIDEE PFTs |
---|
144 | print("\nAGGREGATE GENERIC PFTs to ORCHIDEE PFTs") |
---|
145 | print("Check CWT2 gen2orc = %s" % np.all([np.array(item[2:]).sum() == 1 for item in CWT2[1:]])) |
---|
146 | orc = np.zeros((len(ORC_KEYS), len(lat), len(lon))) |
---|
147 | for rule in CWT2[1:]: |
---|
148 | idx = GEN_KEYS.index(rule[0]) |
---|
149 | cond = rule[1] |
---|
150 | fracs = np.array(rule[2:]) |
---|
151 | if cond in KG_KEYS: coef = kg[KG_KEYS.index(cond)] |
---|
152 | elif cond in ["C3", "C4"]: coef = c4 if cond == "C4" else 1-c4 |
---|
153 | elif cond in ["luhC3", "luhC4"]: coef = luhc4 if cond == "luhC4" else 1-luhc4 |
---|
154 | elif "/" in cond: coef = kg[KG_KEYS.index(cond.split("/")[0])] * (c4 if cond.split("/")[1] == "C4" else 1-c4) |
---|
155 | elif cond == "inland": coef = inland |
---|
156 | elif cond == "": coef = 1 |
---|
157 | else: print("Unknown condition: %s" % cond); sys.exit() |
---|
158 | orc = orc + gen[idx] * coef * fracs[:,None,None] |
---|
159 | printStat(orc=orc) |
---|
160 | |
---|
161 | # set small fractions to zero (< 0.001) |
---|
162 | orc = np.where(orc < 0.001, 0, orc) |
---|
163 | # set 1 to bare soil on the coast if no other PFTs |
---|
164 | orc[0] = np.where((coast == 1) & (orc[1:].sum(axis=0) < 0.001), 1, orc[0]) |
---|
165 | # normalize |
---|
166 | orc = divide(orc, orc.sum(axis=0)) |
---|
167 | # apply land-sea mask |
---|
168 | orc = np.ma.array(orc) |
---|
169 | orc.mask = (ocean == 1) |
---|
170 | # filter out Antarctica (lat < S60) |
---|
171 | orc[:, lat < -60, :] = np.ma.masked |
---|
172 | |
---|
173 | printStat(orcn=orc) |
---|
174 | if debug: plot(orc=orc, orcsum=orc.sum(axis=0)) |
---|
175 | |
---|
176 | writePFTMAP(orc, lat=lat, lon=lon, year=year, filename=path_out, atts=atts) |
---|
177 | |
---|