source: trunk/TOOLS/GENERATE_VEGET_MAP/ESA-LUH2/gen2orc.py

Last change on this file was 7881, checked in by vladislav.bastrikov, 2 years ago

New scripts used to produce ORCHIDEE PFT maps v2023

File size: 9.1 KB
Line 
1import argparse, datetime, numpy as np, netCDF4, sys
2from grid import Grid
3from cwt import cwt_kg31to7, cwt_gen2orc, cwt_gen2orc16
4from basics import printStat, writePFTMAP
5CWT1 = cwt_kg31to7
6CWT2 = cwt_gen2orc # cwt_gen2orc for standard 15 PFTs, cwt_gen2orc16 for additional urbain PFT
7debug = False
8
9parser = argparse.ArgumentParser()
10parser.add_argument("year", action="store", type=int, help="year to process)")
11parser.add_argument("nlat", action="store", type=int, help="number of latitude steps (143 for LMDZ resolution)")
12parser.add_argument("-part", action="store", type=int, default=0, help="part (1 or 2 for nlat=14400 to split in two parts", required = False)
13args = parser.parse_args()
14year = args.year
15nlat = args.nlat
16part = args.part
17nlon = 144 if nlat == 143 else nlat * 2
18if part == 0: lat1=0; lat2=nlat
19elif part == 1: lat1=0; lat2=int(nlat/2)
20elif part == 2: lat1=int(nlat/2); lat2=nlat
21step = 180. / nlat
22luh_year0 = 1992
23esa = "v2.0.7b" if year <= 2015 else "v2.1.1"
24print("GEN2ORC for %s, %sx%s, %s:%s" % (year, nlat, nlon, lat1, lat2))
25
26atts = 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
47path_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)
48path_luh = "/home/surface5/vbastri/BDD/LUH2/TRENDY2022/LUH2v11_1992-2022_%(nlat)sx%(nlon)s.nc" % dict(nlat = nlat, nlon = nlon)
49path_kg  = "/home/surface5/vbastri/BDD/KG2017/KG_%(nlat)sx%(nlon)s.nc" % dict(nlat = nlat, nlon = nlon)
50path_c4  = "/home/surface5/vbastri/BDD/C4FRAC/C4frac_%(nlat)sx%(nlon)s.nc" % dict(nlat = nlat, nlon = nlon)
51path_wb  = "/home/surface5/vbastri/BDD/ESACCI-LC/WATER/WB_%(nlat)sx%(nlon)s.nc" % dict(nlat = nlat, nlon = nlon)
52path_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 "")
53GEN_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"]
54GEN_KEYS = ["TrBrEv", "TrBrDe", "TrNeEv", "TrNeDe", "ShBrEv", "ShBrDe", "ShNeEv", "ShNeDe", "NatGr", "Crops", "BS",  "Water", "SnowIce", "Urban", "NoData"]
55KG_KEYS = CWT1[0][1:]
56ORC_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
60def 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
63def 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
71print("\nREAD GENERIC PFTMAP")
72nc = netCDF4.Dataset(path_gen)
73lat = nc.variables["lat"][lat1:lat2]
74lon = nc.variables["lon"][:]
75gen = np.zeros((len(GEN_LIST), len(lat), len(lon)))
76for idx, item in enumerate(GEN_LIST): gen[idx] = nc.variables[item][lat1:lat2].astype("f8").filled(0)
77nc.close()
78printStat(gen=gen)
79gen = divide(gen, gen.sum(axis=0))
80printStat(genn=gen)
81if debug: grid = Grid(lat=lat, lon=lon)
82if debug: plot(gen=gen, gensum=gen.sum(axis=0))
83
84# Read and normalize Koeppen-Geiger map with 31 classes
85print("\nREAD KG")
86nc = netCDF4.Dataset(path_kg)
87if not np.allclose(lat, nc.variables["lat"][lat1:lat2]) or not np.allclose(lon, nc.variables["lon"][:]): print("Different grid"); sys.exit()
88kg31 = nc.variables["kg"][:,lat1:lat2].astype("f8").filled(0)
89nc.close()
90printStat(kg31=kg31)
91kg31 = divide(kg31, kg31.sum(axis=0))
92printStat(kg31n=kg31)
93
94# Aggregate KG into 7 groups
95print("Check CWT1 kg31to7 = %s" % np.all([np.array(item[1:]).sum() == 1 for item in CWT1[1:]]))
96kg = np.zeros((len(KG_KEYS), len(lat), len(lon)))
97for idx, rule in enumerate(CWT1[1:]): kg = kg + kg31[idx] * np.array(rule[1:])[:,None,None]
98printStat(kg=kg)
99if 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)
101domclass = np.where(kg.sum(axis=(0,2)) == 0, len(kg)-1, kg.sum(axis=2).argmax(axis=0))
102for idx in range(len(lat)): kg[domclass[idx], idx, kg[:,idx,:].sum(axis=0) == 0] = 1
103printStat(kgf=kg)
104if debug: plot(kgf=kg)
105
106print("\nREAD C4")
107nc = netCDF4.Dataset(path_c4)
108if not np.allclose(lat, nc.variables["lat"][lat1:lat2]) or not np.allclose(lon, nc.variables["lon"][:]): print("Different grid"); sys.exit()
109c4 = nc.variables["C4frac"][lat1:lat2].astype("f8").filled(0)
110nc.close()
111printStat(c4=c4)
112if debug: plot(c4=c4)
113
114print("\nREAD WB")
115nc = netCDF4.Dataset(path_wb)
116if not np.allclose(lat, nc.variables["lat"][lat1:lat2]) or not np.allclose(lon, nc.variables["lon"][:]): print("Different grid"); sys.exit()
117wb = nc.variables["wb"][:,lat1:lat2].astype("f8").filled(0)
118nc.close()
119printStat(wb=wb)
120wb = divide(wb, wb.sum(axis=0))
121printStat(wbn=wb)
122ocean = np.where(wb[0] > 0.999, 1, 0) # ocean 0/1-mask
123coast = np.where((wb[0] > 0) & (wb[0] <= 0.999), 1, 0) # coast 0/1-mask
124inland = np.where(wb[0] == 0, wb[2], 0) # inland water fraction excluding coasts
125print("ocean_npts=%s, coast_npts=%s, inland_npts=%s" % (ocean.sum(), coast.sum(), (inland>0).sum()))
126if debug: plot(wb=wb, ocean=ocean, coast=coast, inland=inland)
127
128# read C4 map from LUH2 for crops
129print("\nREAD LUH")
130nc = netCDF4.Dataset(path_luh)
131if not np.allclose(lat, nc.variables["lat"][lat1:lat2]) or not np.allclose(lon, nc.variables["lon"][:]): print("Different grid"); sys.exit()
132c3ann = nc.variables["c3ann"][year-luh_year0, lat1:lat2].astype("f8").filled(0)
133c4ann = nc.variables["c4ann"][year-luh_year0, lat1:lat2].astype("f8").filled(0)
134c3per = nc.variables["c3per"][year-luh_year0, lat1:lat2].astype("f8").filled(0)
135c4per = nc.variables["c4per"][year-luh_year0, lat1:lat2].astype("f8").filled(0)
136c3nfx = nc.variables["c3nfx"][year-luh_year0, lat1:lat2].astype("f8").filled(0)
137total = c3ann + c4ann + c3per + c4per + c3nfx
138luhc4 = divide(c4ann + c4per, total)
139nc.close()
140printStat(luhc4=luhc4)
141if debug: plot(luhc4=luhc4)
142
143# aggregate generic PFTs to ORCHIDEE PFTs
144print("\nAGGREGATE GENERIC PFTs to ORCHIDEE PFTs")
145print("Check CWT2 gen2orc = %s" % np.all([np.array(item[2:]).sum() == 1 for item in CWT2[1:]]))
146orc = np.zeros((len(ORC_KEYS), len(lat), len(lon)))
147for 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]
159printStat(orc=orc)
160
161# set small fractions to zero (< 0.001)
162orc = np.where(orc < 0.001, 0, orc)
163# set 1 to bare soil on the coast if no other PFTs
164orc[0] = np.where((coast == 1) & (orc[1:].sum(axis=0) < 0.001), 1, orc[0])
165# normalize
166orc = divide(orc, orc.sum(axis=0))
167# apply land-sea mask
168orc = np.ma.array(orc)
169orc.mask = (ocean == 1)
170# filter out Antarctica (lat < S60)
171orc[:, lat < -60, :] = np.ma.masked
172
173printStat(orcn=orc)
174if debug: plot(orc=orc, orcsum=orc.sum(axis=0))
175
176writePFTMAP(orc, lat=lat, lon=lon, year=year, filename=path_out, atts=atts)
177
Note: See TracBrowser for help on using the repository browser.