Revision 73b1bea9
Added by Benoit Parmentier over 11 years ago
climate/research/oregon/modis-lst/LST_climatology.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
""" |
|
3 |
Created on Fri Oct 26 23:48:57 2012 |
|
4 |
|
|
5 |
@author: benoitparmentier |
|
6 |
""" |
|
7 |
|
|
8 |
#!/usr/bin/python |
|
9 |
# |
|
10 |
# Early version of assorted Python code to download MODIS 11A1 (daily) |
|
11 |
# data associated with specified dates and tiles, then interface with |
|
12 |
# GRASS to calculate temporally averaged LST in a way that accounts for |
|
13 |
# QC flags. |
|
14 |
# |
|
15 |
# TODO: |
|
16 |
# - functionalize to encapsulate high level procedural steps |
|
17 |
# - create a 'main' function to allow script to be run as a command |
|
18 |
# that can accept arguments? |
|
19 |
# - write code to set up and take down a temporary GRASS location/mapset |
|
20 |
# - calculate other aggregate stats? (stddev, ...) |
|
21 |
# - deal with more than just first two bits of QC flags? |
|
22 |
# e.g.: r.mapcalc 'qc_level2 = (qc>>2) & 0x03' |
|
23 |
# 0x03?? that means 0000_0011 and >>2 means shift twice on the right for |
|
24 |
# |
|
25 |
# Created by Jim Regetz |
|
26 |
# NCEAS on 16-May-2012 |
|
27 |
# Modified by Benoit Parmentier |
|
28 |
#NCEAS on 18-October-2012 |
|
29 |
#NCEAS on 19-January-2013 |
|
30 |
#NCEAS on 16-May-2013 |
|
31 |
# |
|
32 |
|
|
33 |
import os, glob |
|
34 |
import datetime, calendar |
|
35 |
import ftplib |
|
36 |
import grass.script as gs |
|
37 |
import argparse |
|
38 |
import shutil |
|
39 |
|
|
40 |
#------------------ |
|
41 |
# helper functions |
|
42 |
#------------------ |
|
43 |
|
|
44 |
#Added on January 16, 2012 by Benoit NCEAS |
|
45 |
#Add other lists to calculate climatology?? later |
|
46 |
def list_raster_per_month(listmaps): |
|
47 |
"""Determine starting and ending numeric day-of-year (doy) |
|
48 |
asscociated with the specified month and year. |
|
49 |
|
|
50 |
Arguments: |
|
51 |
maplist -- list of raster grass-digit integer year |
|
52 |
month -- integer month (1-12) |
|
53 |
year_range -- list of years |
|
54 |
|
|
55 |
Returns tuple of start and end doy for that month/year. |
|
56 |
""" |
|
57 |
list_maps_month=list() |
|
58 |
nb_month=12 |
|
59 |
for i in range(1,nb_month+1): |
|
60 |
#i=i+1 |
|
61 |
#filename = listfiles_wd2[i] #list the files of interest |
|
62 |
#monthlist[:]=[] #empty the list |
|
63 |
monthlist=list() |
|
64 |
#monthlist2[:]=[] #empty the list |
|
65 |
j=0 #counter |
|
66 |
for mapname in listmaps: |
|
67 |
#tmp_str=LST[0] |
|
68 |
date_map=mapname.split('_')[1][1:8] |
|
69 |
#date = filename[filename.rfind('_MOD')-8:filename.rfind('_MOD')] |
|
70 |
d=datetime.datetime.strptime(date_map,'%Y%j') #This produces a date object from a string extracted from the file name. |
|
71 |
month=d.strftime('%m') #Extract the month of the current map |
|
72 |
if int(month)==i: |
|
73 |
#monthlist.append(listmaps[j]) |
|
74 |
monthlist.append(mapname) |
|
75 |
#monthlist2.append(listmaps[j]) |
|
76 |
j=j+1 |
|
77 |
list_maps_month.append(monthlist) #This is a list of a list containing a list for each month |
|
78 |
#list_files2.append(monthlist2) |
|
79 |
return(list_maps_month) |
|
80 |
|
|
81 |
def calc_clim(maplist, name, overwrite=False): |
|
82 |
"""Generate some climatalogies in GRASS based on the input list of |
|
83 |
maps. As usual, current GRASS region settings apply. Produces the |
|
84 |
following output rasters: |
|
85 |
* nobs: count of number of (non-null) values over the input maps |
|
86 |
* mean: arithmetic mean of (non-null) values over the input maps |
|
87 |
|
|
88 |
Arguments: |
|
89 |
maplist -- list of names of GRASS maps to be aggregated |
|
90 |
name -- template (suffix) for GRASS output map names |
|
91 |
|
|
92 |
Returns list of names of the output maps created in GRASS. |
|
93 |
""" |
|
94 |
denominator = '(%s)' % '+'.join(['if(!isnull(%s))' % m |
|
95 |
for m in maplist]) |
|
96 |
gs.mapcalc('nobs_%s = %s' % (name, denominator), overwrite=overwrite) |
|
97 |
numerator = '(%s)' % '+'.join(['if(isnull(%s), 0, %s)' % (m, m) |
|
98 |
for m in maplist]) |
|
99 |
gs.mapcalc('mean_%s = round(float(%s)/nobs_%s)' % (name, numerator, name), |
|
100 |
overwrite=overwrite) |
|
101 |
return ['%s_%s' % (s, name) for s in ['nobs', 'mean']] |
|
102 |
|
|
103 |
def load_qc_adjusted_lst(hdf): |
|
104 |
"""Load LST_Day_1km into GRASS from the specified hdf file, and |
|
105 |
nullify any cells for which QA flags indicate anything other than |
|
106 |
high quality. |
|
107 |
|
|
108 |
Argument: |
|
109 |
hdf -- local path to the 11A1 HDF file |
|
110 |
|
|
111 |
Returns the name of the QC-adjusted LST map created in GRASS. |
|
112 |
""" |
|
113 |
lstname = 'LST_' + '_'.join(os.path.basename(hdf).split('.')[1:3]) |
|
114 |
# read in lst grid |
|
115 |
gs.run_command('r.in.gdal', |
|
116 |
input='HDF4_EOS:EOS_GRID:%s:MODIS_Grid_Daily_1km_LST:LST_Day_1km' % hdf, |
|
117 |
output=lstname) # No new location created since it has already been done with r.in.gdal before!!! |
|
118 |
|
|
119 |
# read in qc grid |
|
120 |
gs.run_command('r.in.gdal', |
|
121 |
input = 'HDF4_EOS:EOS_GRID:%s:MODIS_Grid_Daily_1km_LST:QC_Day' % hdf, |
|
122 |
output = 'qc_this_day') |
|
123 |
gs.run_command('g.region', rast=lstname) |
|
124 |
# null out any LST cells for which QC is not high [00] #THIS WHERE WE MIGHT NEED TO CHANGE... |
|
125 |
gs.mapcalc('${lst} = if((qc_this_day & 0x03)==0, ${lst}, null())', |
|
126 |
lst=lstname, overwrite=True) |
|
127 |
# clean up |
|
128 |
gs.run_command('g.remove', rast='qc_this_day') |
|
129 |
# return name of qc-adjusted LST raster in GRASS |
|
130 |
return lstname |
|
131 |
|
|
132 |
def main(): |
|
133 |
#-------------------------------------------- |
|
134 |
# Download and Calculate monthly climatology for daily LST time series for a specific area |
|
135 |
#-------------------------------------------- |
|
136 |
|
|
137 |
# TODO: set up a (temporary?) GRASS database to use for processing? code |
|
138 |
# currently assumes it's being run within an existing GRASS session |
|
139 |
# using an appropriately configured LOCATION... |
|
140 |
# |
|
141 |
# note the following trick to fix datum for modis sinu; |
|
142 |
# TODO: check that this works properly...compare to MRT output? |
|
143 |
# gs.run_command('g.proj', flags='c', |
|
144 |
# proj4='+proj=sinu +a=6371007.181 +b=6371007.181 +ellps=sphere') |
|
145 |
## proj4='+proj=sinu +R=6371007.181 +nadgrids=@null +wktext') |
|
146 |
|
|
147 |
########## START OF SCRIPT ############## |
|
148 |
#### Modified by Benoit on May 13, 2013 |
|
149 |
|
|
150 |
### INPUT Parameters |
|
151 |
#Inputs from R?? there are 9 parameters |
|
152 |
#tiles = ['h11v08','h11v07','h12v07','h12v08','h10v07','h10v08'] #These tiles correspond to Venezuela. |
|
153 |
#tiles= ['h08v04','h09v04'] |
|
154 |
#start_year = 2001 |
|
155 |
#end_year = 2010 |
|
156 |
#start_month=1 |
|
157 |
#end_month=12 |
|
158 |
#hdfdir = '/home/layers/commons/modis/MOD11A1_tiles' #destination file where hdf files are stored locally after download. |
|
159 |
#hdfdir = '/home/parmentier/Data/IPLANT_project/MOD11A1_tiles' #destination file where hdf files are stored locally after download. |
|
160 |
#night=1 # if 1 then produce night climatology |
|
161 |
#out_suffix="_03192013" |
|
162 |
#download=1 # if 1 then download files |
|
163 |
|
|
164 |
#Passing arguments from the shell...using positional assignment |
|
165 |
parser = argparse.ArgumentParser() |
|
166 |
parser.add_argument("tiles", type=str, help="list of Modis tiles") |
|
167 |
parser.add_argument("start_year", type=int, help="start year") |
|
168 |
parser.add_argument("end_year", type=int, help="end year") |
|
169 |
parser.add_argument("start_month", type=int, help="start month") |
|
170 |
parser.add_argument("end_month", type=int, help="end month") |
|
171 |
parser.add_argument("hdfdir", type=str, help="destination/source directory for hdf file") |
|
172 |
parser.add_argument("night", type=int, help="night") |
|
173 |
parser.add_argument("download", type=int, help="out_suffix") |
|
174 |
parser.add_argument("out_suffix", type=str, help="out_suffix") |
|
175 |
|
|
176 |
myargs = parser.parse_args() |
|
177 |
|
|
178 |
tiles = myargs.tiles #These tiles correspond to Venezuela. |
|
179 |
start_year = myargs.start_year |
|
180 |
end_year = myargs.end_year |
|
181 |
end_month = myargs.end_month #not used...to be removed |
|
182 |
start_month= myargs.start_month #not used...to be removed |
|
183 |
hdfdir = myargs.hdfdir |
|
184 |
night= myargs.night # if 1 then produce night climatology |
|
185 |
out_suffix= myargs.out_suffix #"_03192013" |
|
186 |
download=myargs.download# if 1 then download files |
|
187 |
|
|
188 |
tiles =tiles.split(",") #Create a list from string |
|
189 |
#need to add on the fly creation of folder for each tile!! |
|
190 |
|
|
191 |
################## First step: download data ################### |
|
192 |
# Added tile loop |
|
193 |
year_list=range(start_year,end_year+1) #list of year to loop through |
|
194 |
|
|
195 |
# modify loop to take into account "hdfs", list of hdf files needed in the next steps… |
|
196 |
################# Second step: compute climatology ################# |
|
197 |
## Process tile by tile... |
|
198 |
var_name = ['LST_Day_1km','LST_Night_1km'] |
|
199 |
if night==1: |
|
200 |
lst_var = var_name[1] |
|
201 |
if night==0: |
|
202 |
lst_var = var_name[0] |
|
203 |
|
|
204 |
#start = time.clock() |
|
205 |
for tile in tiles: |
|
206 |
# Set up a temporary GRASS data base |
|
207 |
tmp_location = 'tmp' + "_"+ tile + "_"+ str(os.getpid()) # Name of the temporary GRASS data base |
|
208 |
orig_location = gs.gisenv()['LOCATION_NAME'] #Current location of GRASS DATABASE at start up (config grassgrc?) |
|
209 |
orig_mapset = gs.gisenv()['MAPSET'] #Current mapset of GRASS DATABASE at start up (grassgrc config) |
|
210 |
gs.os.environ['GRASS_OVERWRITE'] = '1' # Allow for overwrite?? GRASS data base |
|
211 |
|
|
212 |
#path_file = hdfdir #change |
|
213 |
path_file = os.path.join(hdfdir,tile) |
|
214 |
|
|
215 |
os.chdir(path_file) #set working directory |
|
216 |
os.getcwd() #get current working directory |
|
217 |
listfiles_wd2 = glob.glob('*'+tile+'*'+'.hdf') #list the all the hdf modis LST files of interest |
|
218 |
name_of_file = listfiles_wd2[0] |
|
219 |
#Create the name of the layer to extract from the hdf file used for definition of the database |
|
220 |
lst1day = 'HDF4_EOS:EOS_GRID:%s/%s:%s' % ( |
|
221 |
path_file, |
|
222 |
name_of_file, |
|
223 |
'MODIS_Grid_Daily_1km_LST:'+ lst_var) |
|
224 |
|
|
225 |
#now import one image and set the location, mapset and database !!create name per tile |
|
226 |
gs.run_command('r.in.gdal', input=lst1day, output='LST_1day_'+tile, |
|
227 |
location=tmp_location) |
|
228 |
|
|
229 |
# Now that the new location has been created, switch to new location |
|
230 |
gs.run_command('g.gisenv', set='LOCATION_NAME=%s' % tmp_location) |
|
231 |
gs.run_command('g.gisenv', set='MAPSET=PERMANENT') |
|
232 |
|
|
233 |
# set GRASS the projection system and GRASS region to match the extent of the file |
|
234 |
gs.run_command('g.proj', flags='c', |
|
235 |
proj4='+proj=sinu +a=6371007.181 +b=6371007.181 +ellps=sphere') #This should be set in the parameters |
|
236 |
gs.run_command('g.region', rast='LST_1day_'+tile) |
|
237 |
|
|
238 |
#generate monthly pixelwise mean & count of high-quality daytime LST |
|
239 |
gs.os.environ['GRASS_OVERWRITE'] = '1' |
|
240 |
|
|
241 |
#Provide a list of file "hdfs" to process…this should be in a loop... |
|
242 |
#tile= 'h12v08' |
|
243 |
fileglob = 'MOD11A1.A*.%s*hdf' % (tile) |
|
244 |
pathglob = os.path.join(path_file, fileglob) |
|
245 |
files = glob.glob(pathglob) |
|
246 |
hdfs = files |
|
247 |
os.chdir(hdfdir) #set working directory to the general path to modis files... |
|
248 |
|
|
249 |
### LST values in images must be masked out if they have low quality, determined from QC flags stored in hdf files |
|
250 |
LST = [load_qc_adjusted_lst(hdf) for hdf in hdfs] |
|
251 |
#load qc takes about 8 minutes for about 350 files |
|
252 |
### load_qc is a list that contains the name of the new lst files in the GRASS format. The files are stored in the temporary GRASS database. |
|
253 |
list_maps_month=list_raster_per_month(LST) |
|
254 |
list_maps_name=['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'] |
|
255 |
##Now calculate clim per month, do a test for year1 |
|
256 |
nb_month = 12 |
|
257 |
for i in range(1,nb_month+1): |
|
258 |
clims = calc_clim(list_maps_month[i-1],lst_var+'_'+tile+'_'+list_maps_name[i-1]+'_'+str(i-1)) |
|
259 |
for j in range(1, len(clims)+1): |
|
260 |
if j-1 ==0: |
|
261 |
gs.run_command('r.out.gdal', input= clims[j-1], output=clims[j-1]+ out_suffix +'.tif', type='Float64') |
|
262 |
if j-1 ==1: |
|
263 |
gs.mapcalc(' clim_rescaled = ('+ clims[j-1 ]+ ' * 0.02) -273.15') |
|
264 |
gs.run_command('r.out.gdal', input= 'clim_rescaled', output=clims[j-1]+ out_suffix+'.tif', type='Float64') |
|
265 |
#clims = calc_clim(LST, 'LST_%s_%d_%02d' % (tile, year, month)) |
|
266 |
|
|
267 |
# clean up GRASS DATABASE: ok working |
|
268 |
|
|
269 |
gs.run_command('g.remove', rast=','.join(LST)) |
|
270 |
## get list of remaining files... |
|
271 |
maps_list = gs.mlist_strings(type = 'rast') #list all remaining maps in the location |
|
272 |
gs.run_command('g.remove', rast=','.join(maps_list)) #remove remaning maps |
|
273 |
gs.os.environ['GRASS_OVERWRITE'] = '0' |
|
274 |
|
|
275 |
#clean up temporary location and set the original GRASS database parameters back |
|
276 |
gs.run_command('g.gisenv', set='LOCATION_NAME=%s' % orig_location) |
|
277 |
gs.run_command('g.gisenv', set='MAPSET=%s' % orig_mapset) |
|
278 |
shutil.rmtree(os.path.join(gs.gisenv()['GISDBASE'], tmp_location)) |
|
279 |
|
|
280 |
return None |
|
281 |
|
|
282 |
#Need to add this to run |
|
283 |
if __name__ == '__main__': |
|
284 |
main() |
|
285 |
|
|
286 |
|
Also available in: Unified diff
splitting LST script-climatology calculation