source: trunk/TOOLS/GENERATE_VEGET_MAP/ESA-LUH2/CCI_to_PFT19.py @ 6073

Last change on this file since 6073 was 4777, checked in by josefine.ghattas, 7 years ago

Added propset

  • Property svn:executable set to *
  • Property svn:keywords set to Date Revision HeadURL Author
File size: 9.7 KB
Line 
1# $HeadURL$
2# $Date$
3# $Revision$
4
5import datetime, netCDF4, numpy as np, os
6
7path_esa   = "ESACCI-LC-L4-LCCS-Map-300m-P1Y-aggregated-0.250000Deg-2010-v2.0.7b.nc"
8path_still = "Still_et_al_2009_C4_025deg.nc"
9path_etopo = "ETOPO5.nc"
10path_out   = "PFTmap_2010.nc"
11
12# Define global attributes to be written in the output netcdf-file
13atts = 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
21GEN_TrBrEv  = 0
22GEN_TrBrDe  = 1
23GEN_TrNeEv  = 2
24GEN_TrNeDe  = 3
25GEN_ShBrEv  = 4
26GEN_ShBrDe  = 5
27GEN_ShNeEv  = 6
28GEN_ShNeDe  = 7
29GEN_Grass   = 8
30GEN_Crops   = 9
31GEN_BS      = 10
32GEN_Water   = 11
33GEN_SnowIce = 12
34GEN_Urban   = 13
35GEN_NoData  = 14
36GEN_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
39ORC_BS        = 0
40ORC_TropEBF   = 1
41ORC_TropDBF   = 2
42ORC_TempENF   = 3
43ORC_TempEBF   = 4
44ORC_TempDBF   = 5
45ORC_BorENF    = 6
46ORC_BorDBF    = 7
47ORC_BorDNF    = 8
48ORC_TempGrass = 9
49ORC_C4grass   = 10
50ORC_C3crops   = 11
51ORC_C4crops   = 12
52ORC_TropGrass = 13
53ORC_BorGrass  = 14
54ORC_Urban     = 15
55ORC_SnowIce   = 16
56ORC_Inland    = 17
57ORC_Coastal   = 18
58
59# Set indices for Koeppen-Geiger zones
60KG_Trop     = 1
61KG_TempWarm = 21
62KG_TempCool = 22
63KG_BorWarm  = 31
64KG_BorCool  = 32
65
66# Load ESA CCI land cover data
67nc = netCDF4.Dataset(path_esa)
68nlat = len(nc.dimensions["lat"])
69nlon = len(nc.dimensions["lon"])
70lat = nc.variables["lat"][:]
71lon = nc.variables["lon"][:]
72KG = nc.variables["user_map"][:] # Koeppen-Geiger climate zone map
73GEN = np.ma.zeros((len(GEN_LIST), nlat, nlon)) # Generic PFTs
74for idx, var in enumerate(GEN_LIST):
75    GEN[idx] = nc.variables[var][:]
76nc.close()     
77print "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
80KG = KG.filled(0)
81for 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
89print "KG data range : [%s, %s]" % (KG.min(), KG.max())
90
91# Load C4 vegetation data (Still et al., 2009)
92nc = netCDF4.Dataset(path_still)
93C4veg = nc.variables["C4veg"][:]
94nc.close()
95print "Still data range : [%s, %s]" % (C4veg.min(), C4veg.max())
96
97# Load ETOPO5 data and create land-sea mask
98etopo5 = np.zeros((2161, 4321))
99nc = 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)
101etopo5[:,1:] = np.roll(nc.variables["topo"][::-1,:], 2159, axis=1)
102# Copy -180 latitude data from 180 latitude data
103etopo5[:,0] = etopo5[:,-1]
104nc.close()
105# Mask the points with height below 0
106etopo5 = 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)
108etopo025 = np.zeros((nlat, nlon))
109for 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
114ilat = np.where((lat >= 35) & (lat <= 50))
115ilon = np.where((lon >= 44) & (lon <= 58))
116ilon, ilat = np.meshgrid(ilon[0], ilat[0])
117etopo025[ilat, ilon] = 1
118# Add Chott lake
119ilat = np.where((lat >= 33) & (lat <= 36))
120ilon = np.where((lon >= 5) & (lon <= 7))
121ilon, ilat = np.meshgrid(ilon[0], ilat[0])
122etopo025[ilat, ilon] = 1
123print "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
126totveg = np.ma.sum((GEN[:11].sum(axis=0), GEN[12:].sum(axis=0)), axis=0)
127LSM = totveg.mask
128# Create inland/coastal masks
129inland_msk = np.zeros((nlat, nlon))
130coast_msk = np.zeros((nlat, nlon))
131for 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
141GEN = GEN.filled(0)
142
143# Allocate Generic PFTs to ORCHIDEE PFTs
144ORC = np.zeros((19, nlat, nlon))
145
146ORC[ORC_BS] = GEN[GEN_BS]
147ORC[ORC_TropEBF] = np.where(KG==KG_Trop, GEN[GEN_TrBrEv] + GEN[GEN_ShBrEv], 0)
148ORC[ORC_TropDBF] = np.where(KG==KG_Trop, GEN[GEN_TrBrDe] + GEN[GEN_ShBrDe] + GEN[GEN_TrNeDe] + GEN[GEN_ShNeDe], 0)
149ORC[ORC_TempENF] = np.where((KG==KG_Trop)|(KG==KG_TempWarm)|(KG==KG_TempCool)|(KG==KG_BorWarm), GEN[GEN_TrNeEv] + GEN[GEN_ShNeEv], 0)
150ORC[ORC_TempEBF] = np.where((KG==KG_TempWarm)|(KG==KG_TempCool)|(KG==KG_BorWarm)|(KG==KG_BorCool), GEN[GEN_TrBrEv] + GEN[GEN_ShBrEv], 0)
151ORC[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)
152ORC[ORC_BorENF] = np.where(KG==KG_BorCool, GEN[GEN_TrNeEv] + GEN[GEN_ShNeEv], 0)
153ORC[ORC_BorDBF] = np.where(KG==KG_BorCool, GEN[GEN_TrBrDe] + GEN[GEN_ShBrDe], 0)
154ORC[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
157grass = GEN[GEN_Grass]
158crops = GEN[GEN_Crops]
159mix = grass + crops
160veg = 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]
161C4total = np.minimum(C4veg*veg, mix)
162C4grass = np.where(mix==0, 0, C4total * grass / mix)
163C3grass = grass - C4grass
164C4crops = np.where(mix==0, 0, C4total * crops / mix)
165C3crops = crops - C4crops
166
167ORC[ORC_TropGrass] = np.where(KG==KG_Trop, C3grass, 0)
168ORC[ORC_TempGrass] = np.where((KG==KG_TempWarm)|(KG==KG_TempCool), C3grass, 0)
169ORC[ORC_BorGrass]  = np.where((KG==KG_BorWarm) |(KG==KG_BorCool),  C3grass, 0)
170ORC[ORC_C4grass] = C4grass
171ORC[ORC_C3crops] = C3crops
172ORC[ORC_C4crops] = C4crops
173
174ORC[ORC_Urban] = GEN[GEN_Urban]
175ORC[ORC_SnowIce] = GEN[GEN_SnowIce]
176ORC[ORC_Inland] = GEN[GEN_Water] * inland_msk
177ORC[ORC_Coastal] = GEN[GEN_Water] * coast_msk
178
179# Limit pixels content to [0, 1] range
180print "Limit pixels out of [0,1], current min : %s, max : %s" % (ORC.min(), ORC.max())
181ORC = np.where(ORC < 0, 0, ORC)
182ORC = np.where(ORC > 1, 1, ORC)
183
184# Print statistics for ORC data
185sum_orc = ORC.sum(axis=0)
186print "ORC SUM min : %s, max : %s" % (sum_orc.min(), sum_orc.max())
187print "Number of points with sum not equal to 1 :", np.where(sum_orc != 1, 1, 0).sum()
188nout = np.where( (sum_orc > 1.0001) | (sum_orc < 0.9999), 1, 0).sum()
189print "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
192ORC = np.ma.array(ORC)
193ORC.mask = LSM
194sum_orc = ORC.sum(axis=0)
195msk = np.where(sum_orc.mask == False)
196print "ORC data after applying LSM"
197print "Number of ORC points non-masked :", sum_orc.count()
198print "Number of ORC points not equal to 1 :", np.where(sum_orc[msk] != 1, 1, 0).sum()
199nout = np.where( (sum_orc[msk] > 1.0001) | (sum_orc[msk] < 0.9999), 1, 0).sum()
200print "Number of ORC points outside the interval [0.9999, 1.0001] : %s (%.4g%%)" % (nout, nout*100/sum_orc.count())
201
202# Write file
203print "Write file", path_out
204if os.path.exists(path_out): raw_input("The file already exists! Press CTRL-C to exit or any key to continue")
205ncout = netCDF4.Dataset(path_out, "w", format = "NETCDF3_CLASSIC")
206ncout.setncatts(atts)
207ncout.createDimension("lat", nlat)
208ncout.createDimension("lon", nlon)
209ncout.createDimension("time_counter", None)
210ncout.createDimension("veget", len(ORC))
211ncout.createVariable("lat", "f8", ("lat",))
212ncout.createVariable("lon", "f8", ("lon",))
213ncout.createVariable("maxvegetfrac", "f4", ("time_counter", "veget", "lat", "lon"), fill_value = 1.e+20)
214ncout.createVariable("time_counter", "f4", ("time_counter",))
215ncout.createVariable("veget", "i4", ("veget",))
216ncout.variables["lat"].setncatts(dict(long_name="Latitude", valid_max=90., valid_min=-90., units="degrees_north", axis="Y"))
217ncout.variables["lon"].setncatts(dict(long_name="Longitude", valid_max=180., valid_min=-180., units="degrees_east", axis="X", modulo=360., topology="circular"))
218ncout.variables["maxvegetfrac"].setncatts(dict(name = "maxvegetfrac", long_name = "Vegetation types", units = "-"))
219ncout.variables["time_counter"].setncatts(dict(units = "years since 0-1-1", calendar = "gregorian", axis = "T"))
220ncout.variables["veget"].setncatts(dict(valid_min = 1, valid_max = len(ORC), long_name = "Vegetation classes", units = "-", axis = "Z"))
221ncout.variables["lat"][:] = lat
222ncout.variables["lon"][:] = lon
223ncout.variables["maxvegetfrac"][0] = ORC
224ncout.variables["time_counter"][0] = 2010
225ncout.variables["veget"][:] = range(1, len(ORC)+1)
226ncout.close()
227
Note: See TracBrowser for help on using the repository browser.