1 | # $HeadURL$ |
---|
2 | # $Date$ |
---|
3 | # $Revision$ |
---|
4 | |
---|
5 | import datetime, netCDF4, numpy as np, os |
---|
6 | |
---|
7 | path_esa = "ESACCI-LC-L4-LCCS-Map-300m-P1Y-aggregated-0.250000Deg-2010-v2.0.7b.nc" |
---|
8 | path_still = "Still_et_al_2009_C4_025deg.nc" |
---|
9 | path_etopo = "ETOPO5.nc" |
---|
10 | path_out = "PFTmap_2010.nc" |
---|
11 | |
---|
12 | # Define global attributes to be written in the output netcdf-file |
---|
13 | atts = dict(description = "ORCHIDEE PFT map based on the ESA CCI Land Cover map v2.0.7b aggregated by the user-tool v3.13 with the cross-walking table v2.4", |
---|
14 | contact = "V.Bastrikov (vladislav.bastrikov@lsce.ipsl.fr), P.Peylin (peylin@lsce.ipsl.fr)", |
---|
15 | web = "https://orchidas.lsce.ipsl.fr/dev/LCCCI.php", |
---|
16 | date = datetime.datetime.now().strftime("%d/%m/%Y %H:%M"), |
---|
17 | resolution = "Regular 0.25-degree", |
---|
18 | pfts = "1-13: standard ORCHIDEE PFTs, 10: Temperate C3 grass, 14: Tropical C3 grass, 15: Boreal C3 grass, 16: Urban, 17: SnowIce, 18: Inland water, 19: Coastal water") |
---|
19 | |
---|
20 | # Set indices for generic PFTs |
---|
21 | GEN_TrBrEv = 0 |
---|
22 | GEN_TrBrDe = 1 |
---|
23 | GEN_TrNeEv = 2 |
---|
24 | GEN_TrNeDe = 3 |
---|
25 | GEN_ShBrEv = 4 |
---|
26 | GEN_ShBrDe = 5 |
---|
27 | GEN_ShNeEv = 6 |
---|
28 | GEN_ShNeDe = 7 |
---|
29 | GEN_Grass = 8 |
---|
30 | GEN_Crops = 9 |
---|
31 | GEN_BS = 10 |
---|
32 | GEN_Water = 11 |
---|
33 | GEN_SnowIce = 12 |
---|
34 | GEN_Urban = 13 |
---|
35 | GEN_NoData = 14 |
---|
36 | 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"] |
---|
37 | |
---|
38 | # Set indices for ORCHIDEE PFTs |
---|
39 | ORC_BS = 0 |
---|
40 | ORC_TropEBF = 1 |
---|
41 | ORC_TropDBF = 2 |
---|
42 | ORC_TempENF = 3 |
---|
43 | ORC_TempEBF = 4 |
---|
44 | ORC_TempDBF = 5 |
---|
45 | ORC_BorENF = 6 |
---|
46 | ORC_BorDBF = 7 |
---|
47 | ORC_BorDNF = 8 |
---|
48 | ORC_TempGrass = 9 |
---|
49 | ORC_C4grass = 10 |
---|
50 | ORC_C3crops = 11 |
---|
51 | ORC_C4crops = 12 |
---|
52 | ORC_TropGrass = 13 |
---|
53 | ORC_BorGrass = 14 |
---|
54 | ORC_Urban = 15 |
---|
55 | ORC_SnowIce = 16 |
---|
56 | ORC_Inland = 17 |
---|
57 | ORC_Coastal = 18 |
---|
58 | |
---|
59 | # Set indices for Koeppen-Geiger zones |
---|
60 | KG_Trop = 1 |
---|
61 | KG_TempWarm = 21 |
---|
62 | KG_TempCool = 22 |
---|
63 | KG_BorWarm = 31 |
---|
64 | KG_BorCool = 32 |
---|
65 | |
---|
66 | # Load ESA CCI land cover data |
---|
67 | nc = netCDF4.Dataset(path_esa) |
---|
68 | nlat = len(nc.dimensions["lat"]) |
---|
69 | nlon = len(nc.dimensions["lon"]) |
---|
70 | lat = nc.variables["lat"][:] |
---|
71 | lon = nc.variables["lon"][:] |
---|
72 | KG = nc.variables["user_map"][:] # Koeppen-Geiger climate zone map |
---|
73 | GEN = np.ma.zeros((len(GEN_LIST), nlat, nlon)) # Generic PFTs |
---|
74 | for idx, var in enumerate(GEN_LIST): |
---|
75 | GEN[idx] = nc.variables[var][:] |
---|
76 | nc.close() |
---|
77 | print "ESA CCI data range : [%s, %s]" % (GEN.min(), GEN.max()) |
---|
78 | |
---|
79 | # Substitute masked values and/or zeros in Koeppen-Geiger map with the dominant value of each latitude or set KG_BorCool if no data |
---|
80 | KG = KG.filled(0) |
---|
81 | for ilat in range(nlat): |
---|
82 | values, count = np.unique(KG[ilat], return_counts=True) |
---|
83 | freq = values[np.argsort(count)][::-1] |
---|
84 | if freq[0] == 0 and len(values) == 1: |
---|
85 | KG[ilat] = KG_BorCool |
---|
86 | else: |
---|
87 | major = freq[0] if freq[0] != 0 else freq[1] |
---|
88 | KG[ilat][KG[ilat]==0] = major |
---|
89 | print "KG data range : [%s, %s]" % (KG.min(), KG.max()) |
---|
90 | |
---|
91 | # Load C4 vegetation data (Still et al., 2009) |
---|
92 | nc = netCDF4.Dataset(path_still) |
---|
93 | C4veg = nc.variables["C4veg"][:] |
---|
94 | nc.close() |
---|
95 | print "Still data range : [%s, %s]" % (C4veg.min(), C4veg.max()) |
---|
96 | |
---|
97 | # Load ETOPO5 data and create land-sea mask |
---|
98 | etopo5 = np.zeros((2161, 4321)) |
---|
99 | nc = netCDF4.Dataset(path_etopo) |
---|
100 | # Load etopo, reverse latitudes (from '-90..90' to '90..-90') and move longitudes by half (from '0..360' to '-180..180) |
---|
101 | etopo5[:,1:] = np.roll(nc.variables["topo"][::-1,:], 2159, axis=1) |
---|
102 | # Copy -180 latitude data from 180 latitude data |
---|
103 | etopo5[:,0] = etopo5[:,-1] |
---|
104 | nc.close() |
---|
105 | # Mask the points with height below 0 |
---|
106 | etopo5 = np.ma.masked_where(etopo5 < 0, etopo5) |
---|
107 | # Create etopo mask at 0.25 resolution (if 4x4 block has at least 1 point above 0, it is considered to be land and equals to 1, otherwise equals to 0) |
---|
108 | etopo025 = np.zeros((nlat, nlon)) |
---|
109 | for ilat in range(nlat): |
---|
110 | for ilon in range(nlon): |
---|
111 | nland = etopo5[ilat*3 : ilat*3+4, ilon*3 : ilon*3+4].count() |
---|
112 | if nland > 0: etopo025[ilat, ilon] = 1 |
---|
113 | # Add Kaspean sea |
---|
114 | ilat = np.where((lat >= 35) & (lat <= 50)) |
---|
115 | ilon = np.where((lon >= 44) & (lon <= 58)) |
---|
116 | ilon, ilat = np.meshgrid(ilon[0], ilat[0]) |
---|
117 | etopo025[ilat, ilon] = 1 |
---|
118 | # Add Chott lake |
---|
119 | ilat = np.where((lat >= 33) & (lat <= 36)) |
---|
120 | ilon = np.where((lon >= 5) & (lon <= 7)) |
---|
121 | ilon, ilat = np.meshgrid(ilon[0], ilat[0]) |
---|
122 | etopo025[ilat, ilon] = 1 |
---|
123 | print "ETOPO data at 0.25 resolution created" |
---|
124 | |
---|
125 | # Create land-sea mask with the land points where generic PFTs has anything other than water |
---|
126 | totveg = np.ma.sum((GEN[:11].sum(axis=0), GEN[12:].sum(axis=0)), axis=0) |
---|
127 | LSM = totveg.mask |
---|
128 | # Create inland/coastal masks |
---|
129 | inland_msk = np.zeros((nlat, nlon)) |
---|
130 | coast_msk = np.zeros((nlat, nlon)) |
---|
131 | for ilat in range(nlat): |
---|
132 | for ilon in range(nlon): |
---|
133 | nland = etopo025[max(0, ilat-2) : ilat+3, max(0, ilon-2) : ilon+3].sum() # calculate number of land points in 5x5 block |
---|
134 | if nland == 25 and ilat < 600: # if all points in 5x5 block are land points, consider the pixel to be inland, set land-sea mask to land (not applied for Antarctica) |
---|
135 | LSM[ilat, ilon] = False |
---|
136 | inland_msk[ilat,ilon] = 1 |
---|
137 | else: # all other pixels consider to be coastal |
---|
138 | coast_msk[ilat,ilon] = 1 |
---|
139 | |
---|
140 | # Fill masked values in generic PFTs with zeros for easier handling |
---|
141 | GEN = GEN.filled(0) |
---|
142 | |
---|
143 | # Allocate Generic PFTs to ORCHIDEE PFTs |
---|
144 | ORC = np.zeros((19, nlat, nlon)) |
---|
145 | |
---|
146 | ORC[ORC_BS] = GEN[GEN_BS] |
---|
147 | ORC[ORC_TropEBF] = np.where(KG==KG_Trop, GEN[GEN_TrBrEv] + GEN[GEN_ShBrEv], 0) |
---|
148 | ORC[ORC_TropDBF] = np.where(KG==KG_Trop, GEN[GEN_TrBrDe] + GEN[GEN_ShBrDe] + GEN[GEN_TrNeDe] + GEN[GEN_ShNeDe], 0) |
---|
149 | ORC[ORC_TempENF] = np.where((KG==KG_Trop)|(KG==KG_TempWarm)|(KG==KG_TempCool)|(KG==KG_BorWarm), GEN[GEN_TrNeEv] + GEN[GEN_ShNeEv], 0) |
---|
150 | ORC[ORC_TempEBF] = np.where((KG==KG_TempWarm)|(KG==KG_TempCool)|(KG==KG_BorWarm)|(KG==KG_BorCool), GEN[GEN_TrBrEv] + GEN[GEN_ShBrEv], 0) |
---|
151 | ORC[ORC_TempDBF] = np.where((KG==KG_TempWarm)|(KG==KG_TempCool)|(KG==KG_BorWarm), GEN[GEN_TrBrDe] + GEN[GEN_ShBrDe], 0) + np.where(KG==KG_TempWarm, GEN[GEN_TrNeDe] + GEN[GEN_ShNeDe], 0) |
---|
152 | ORC[ORC_BorENF] = np.where(KG==KG_BorCool, GEN[GEN_TrNeEv] + GEN[GEN_ShNeEv], 0) |
---|
153 | ORC[ORC_BorDBF] = np.where(KG==KG_BorCool, GEN[GEN_TrBrDe] + GEN[GEN_ShBrDe], 0) |
---|
154 | ORC[ORC_BorDNF] = np.where((KG==KG_TempCool)|(KG==KG_BorWarm)|(KG==KG_BorCool), GEN[GEN_TrNeDe] + GEN[GEN_ShNeDe], 0) |
---|
155 | |
---|
156 | # Split low vegetation to C3/C4 using Still data |
---|
157 | grass = GEN[GEN_Grass] |
---|
158 | crops = GEN[GEN_Crops] |
---|
159 | mix = grass + crops |
---|
160 | veg = GEN[GEN_TrBrEv] + GEN[GEN_TrBrDe] + GEN[GEN_TrNeEv] + GEN[GEN_TrNeDe] + GEN[GEN_ShBrEv] + GEN[GEN_ShBrDe] + GEN[GEN_ShNeEv] + GEN[GEN_ShNeDe] + GEN[GEN_Grass] + GEN[GEN_Crops] |
---|
161 | C4total = np.minimum(C4veg*veg, mix) |
---|
162 | C4grass = np.where(mix==0, 0, C4total * grass / mix) |
---|
163 | C3grass = grass - C4grass |
---|
164 | C4crops = np.where(mix==0, 0, C4total * crops / mix) |
---|
165 | C3crops = crops - C4crops |
---|
166 | |
---|
167 | ORC[ORC_TropGrass] = np.where(KG==KG_Trop, C3grass, 0) |
---|
168 | ORC[ORC_TempGrass] = np.where((KG==KG_TempWarm)|(KG==KG_TempCool), C3grass, 0) |
---|
169 | ORC[ORC_BorGrass] = np.where((KG==KG_BorWarm) |(KG==KG_BorCool), C3grass, 0) |
---|
170 | ORC[ORC_C4grass] = C4grass |
---|
171 | ORC[ORC_C3crops] = C3crops |
---|
172 | ORC[ORC_C4crops] = C4crops |
---|
173 | |
---|
174 | ORC[ORC_Urban] = GEN[GEN_Urban] |
---|
175 | ORC[ORC_SnowIce] = GEN[GEN_SnowIce] |
---|
176 | ORC[ORC_Inland] = GEN[GEN_Water] * inland_msk |
---|
177 | ORC[ORC_Coastal] = GEN[GEN_Water] * coast_msk |
---|
178 | |
---|
179 | # Limit pixels content to [0, 1] range |
---|
180 | print "Limit pixels out of [0,1], current min : %s, max : %s" % (ORC.min(), ORC.max()) |
---|
181 | ORC = np.where(ORC < 0, 0, ORC) |
---|
182 | ORC = np.where(ORC > 1, 1, ORC) |
---|
183 | |
---|
184 | # Print statistics for ORC data |
---|
185 | sum_orc = ORC.sum(axis=0) |
---|
186 | print "ORC SUM min : %s, max : %s" % (sum_orc.min(), sum_orc.max()) |
---|
187 | print "Number of points with sum not equal to 1 :", np.where(sum_orc != 1, 1, 0).sum() |
---|
188 | nout = np.where( (sum_orc > 1.0001) | (sum_orc < 0.9999), 1, 0).sum() |
---|
189 | print "Number of points with sum outside the interval [0.9999, 1.0001] : %s (%.4g%%)" % (nout, nout*100./(nlat*nlon)) |
---|
190 | |
---|
191 | # Apply land-sea mask |
---|
192 | ORC = np.ma.array(ORC) |
---|
193 | ORC.mask = LSM |
---|
194 | sum_orc = ORC.sum(axis=0) |
---|
195 | msk = np.where(sum_orc.mask == False) |
---|
196 | print "ORC data after applying LSM" |
---|
197 | print "Number of ORC points non-masked :", sum_orc.count() |
---|
198 | print "Number of ORC points not equal to 1 :", np.where(sum_orc[msk] != 1, 1, 0).sum() |
---|
199 | nout = np.where( (sum_orc[msk] > 1.0001) | (sum_orc[msk] < 0.9999), 1, 0).sum() |
---|
200 | print "Number of ORC points outside the interval [0.9999, 1.0001] : %s (%.4g%%)" % (nout, nout*100/sum_orc.count()) |
---|
201 | |
---|
202 | # Write file |
---|
203 | print "Write file", path_out |
---|
204 | if os.path.exists(path_out): raw_input("The file already exists! Press CTRL-C to exit or any key to continue") |
---|
205 | ncout = netCDF4.Dataset(path_out, "w", format = "NETCDF3_CLASSIC") |
---|
206 | ncout.setncatts(atts) |
---|
207 | ncout.createDimension("lat", nlat) |
---|
208 | ncout.createDimension("lon", nlon) |
---|
209 | ncout.createDimension("time_counter", None) |
---|
210 | ncout.createDimension("veget", len(ORC)) |
---|
211 | ncout.createVariable("lat", "f8", ("lat",)) |
---|
212 | ncout.createVariable("lon", "f8", ("lon",)) |
---|
213 | ncout.createVariable("maxvegetfrac", "f4", ("time_counter", "veget", "lat", "lon"), fill_value = 1.e+20) |
---|
214 | ncout.createVariable("time_counter", "f4", ("time_counter",)) |
---|
215 | ncout.createVariable("veget", "i4", ("veget",)) |
---|
216 | ncout.variables["lat"].setncatts(dict(long_name="Latitude", valid_max=90., valid_min=-90., units="degrees_north", axis="Y")) |
---|
217 | ncout.variables["lon"].setncatts(dict(long_name="Longitude", valid_max=180., valid_min=-180., units="degrees_east", axis="X", modulo=360., topology="circular")) |
---|
218 | ncout.variables["maxvegetfrac"].setncatts(dict(name = "maxvegetfrac", long_name = "Vegetation types", units = "-")) |
---|
219 | ncout.variables["time_counter"].setncatts(dict(units = "years since 0-1-1", calendar = "gregorian", axis = "T")) |
---|
220 | ncout.variables["veget"].setncatts(dict(valid_min = 1, valid_max = len(ORC), long_name = "Vegetation classes", units = "-", axis = "Z")) |
---|
221 | ncout.variables["lat"][:] = lat |
---|
222 | ncout.variables["lon"][:] = lon |
---|
223 | ncout.variables["maxvegetfrac"][0] = ORC |
---|
224 | ncout.variables["time_counter"][0] = 2010 |
---|
225 | ncout.variables["veget"][:] = range(1, len(ORC)+1) |
---|
226 | ncout.close() |
---|
227 | |
---|