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 create_dir_and_check_existence(path):
|
133
|
try:
|
134
|
os.makedirs(path)
|
135
|
except OSError as exception:
|
136
|
if exception.errno != errno.EEXIST:
|
137
|
raise
|
138
|
|
139
|
def main():
|
140
|
#--------------------------------------------
|
141
|
# Download and Calculate monthly climatology for daily LST time series for a specific area
|
142
|
#--------------------------------------------
|
143
|
|
144
|
# TODO: set up a (temporary?) GRASS database to use for processing? code
|
145
|
# currently assumes it's being run within an existing GRASS session
|
146
|
# using an appropriately configured LOCATION...
|
147
|
#
|
148
|
# note the following trick to fix datum for modis sinu;
|
149
|
# TODO: check that this works properly...compare to MRT output?
|
150
|
# gs.run_command('g.proj', flags='c',
|
151
|
# proj4='+proj=sinu +a=6371007.181 +b=6371007.181 +ellps=sphere')
|
152
|
## proj4='+proj=sinu +R=6371007.181 +nadgrids=@null +wktext')
|
153
|
|
154
|
########## START OF SCRIPT ##############
|
155
|
#### Modified by Benoit on May 13, 2013
|
156
|
|
157
|
### INPUT Parameters
|
158
|
#Inputs from R?? there are 9 parameters
|
159
|
#tiles = ['h11v08','h11v07','h12v07','h12v08','h10v07','h10v08'] #These tiles correspond to Venezuela.
|
160
|
tiles= ['h08v04','h09v04']
|
161
|
start_year = 2001
|
162
|
end_year = 2010
|
163
|
start_month=1
|
164
|
end_month=12
|
165
|
#hdfdir = '/home/layers/commons/modis/MOD11A1_tiles' #destination file where hdf files are stored locally after download.
|
166
|
hdfdir = '/home/parmentier/Data/IPLANT_project/MOD11A1_tiles' #destination file where hdf files are stored locally after download.
|
167
|
night=1 # if 1 then produce night climatology
|
168
|
out_suffix="_03192013"
|
169
|
download=0 # if 1 then download files
|
170
|
|
171
|
#Passing arguments from the shell...using positional assignment
|
172
|
parser = argparse.ArgumentParser()
|
173
|
parser.add_argument("tiles", type=str, help="list of Modis tiles")
|
174
|
parser.add_argument("start_year", type=int, help="start year")
|
175
|
parser.add_argument("end_year", type=int, help="end year")
|
176
|
parser.add_argument("start_month", type=int, help="start month")
|
177
|
parser.add_argument("end_month", type=int, help="end month")
|
178
|
parser.add_argument("hdfdir", type=str, help="destination/source directory for hdf file")
|
179
|
parser.add_argument("night", type=int, help="night")
|
180
|
parser.add_argument("download", type=int, help="out_suffix")
|
181
|
parser.add_argument("out_suffix", type=str, help="out_suffix")
|
182
|
|
183
|
myargs = parser.parse_args()
|
184
|
|
185
|
tiles = myargs.tiles #These tiles correspond to Venezuela.
|
186
|
start_year = myargs.start_year
|
187
|
end_year = myargs.end_year
|
188
|
end_month = myargs.end_month #not used...to be removed
|
189
|
start_month= myargs.start_month #not used...to be removed
|
190
|
hdfdir = myargs.hdfdir
|
191
|
night= myargs.night # if 1 then produce night climatology
|
192
|
out_suffix= myargs.out_suffix #"_03192013"
|
193
|
download=myargs.download# if 1 then download files
|
194
|
|
195
|
tiles =tiles.split(",") #Create a list from string
|
196
|
#need to add on the fly creation of folder for each tile!!
|
197
|
|
198
|
################## First step: download data ###################
|
199
|
# Added tile loop
|
200
|
year_list=range(start_year,end_year+1) #list of year to loop through
|
201
|
|
202
|
# modify loop to take into account "hdfs", list of hdf files needed in the next steps…
|
203
|
################# Second step: compute climatology #################
|
204
|
## Process tile by tile...
|
205
|
var_name = ['LST_Day_1km','LST_Night_1km']
|
206
|
if night==1:
|
207
|
lst_var = var_name[1]
|
208
|
if night==0:
|
209
|
lst_var = var_name[0]
|
210
|
|
211
|
DirLST="LST_averages"
|
212
|
outDir = os.path.join(hdfdir,DirLST) #create output filesfor LST averages
|
213
|
create_dir_and_check_existence(outDir)
|
214
|
#start = time.clock()
|
215
|
for tile in tiles:
|
216
|
# Set up a temporary GRASS data base
|
217
|
tmp_location = 'tmp' + "_"+ tile + "_"+ str(os.getpid()) # Name of the temporary GRASS data base
|
218
|
orig_location = gs.gisenv()['LOCATION_NAME'] #Current location of GRASS DATABASE at start up (config grassgrc?)
|
219
|
orig_mapset = gs.gisenv()['MAPSET'] #Current mapset of GRASS DATABASE at start up (grassgrc config)
|
220
|
gs.os.environ['GRASS_OVERWRITE'] = '1' # Allow for overwrite?? GRASS data base
|
221
|
|
222
|
#path_file = hdfdir #change
|
223
|
path_file = os.path.join(hdfdir,tile)
|
224
|
|
225
|
os.chdir(path_file) #set working directory
|
226
|
os.getcwd() #get current working directory
|
227
|
listfiles_wd2 = glob.glob('*'+tile+'*'+'.hdf') #list the all the hdf modis LST files of interest
|
228
|
name_of_file = listfiles_wd2[0]
|
229
|
#Create the name of the layer to extract from the hdf file used for definition of the database
|
230
|
lst1day = 'HDF4_EOS:EOS_GRID:%s/%s:%s' % (
|
231
|
path_file,
|
232
|
name_of_file,
|
233
|
'MODIS_Grid_Daily_1km_LST:'+ lst_var)
|
234
|
|
235
|
#now import one image and set the location, mapset and database !!create name per tile
|
236
|
gs.run_command('r.in.gdal', input=lst1day, output='LST_1day_'+tile,
|
237
|
location=tmp_location)
|
238
|
|
239
|
# Now that the new location has been created, switch to new location
|
240
|
gs.run_command('g.gisenv', set='LOCATION_NAME=%s' % tmp_location)
|
241
|
gs.run_command('g.gisenv', set='MAPSET=PERMANENT')
|
242
|
|
243
|
# set GRASS the projection system and GRASS region to match the extent of the file
|
244
|
gs.run_command('g.proj', flags='c',
|
245
|
proj4='+proj=sinu +a=6371007.181 +b=6371007.181 +ellps=sphere') #This should be set in the parameters
|
246
|
gs.run_command('g.region', rast='LST_1day_'+tile)
|
247
|
|
248
|
#generate monthly pixelwise mean & count of high-quality daytime LST
|
249
|
gs.os.environ['GRASS_OVERWRITE'] = '1'
|
250
|
|
251
|
#Provide a list of file "hdfs" to process…this should be in a loop...
|
252
|
#tile= 'h12v08'
|
253
|
fileglob = 'MOD11A1.A*.%s*hdf' % (tile)
|
254
|
pathglob = os.path.join(path_file, fileglob)
|
255
|
files = glob.glob(pathglob)
|
256
|
hdfs = files
|
257
|
os.chdir(hdfdir) #set working directory to the general path to modis files...
|
258
|
|
259
|
### LST values in images must be masked out if they have low quality, determined from QC flags stored in hdf files
|
260
|
LST = [load_qc_adjusted_lst(hdf) for hdf in hdfs]
|
261
|
#load qc takes about 8 minutes for about 350 files
|
262
|
### 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.
|
263
|
list_maps_month=list_raster_per_month(LST)
|
264
|
list_maps_name=['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec']
|
265
|
##Now calculate clim per month, do a test for year1
|
266
|
nb_month = 12
|
267
|
for i in range(1,nb_month+1):
|
268
|
clims = calc_clim(list_maps_month[i-1],lst_var+'_'+tile+'_'+list_maps_name[i-1]+'_'+str(i-1)) #length 2, list contains avg and nobs
|
269
|
for j in range(1, len(clims)+1):
|
270
|
if j-1 ==0: #if image is nobs (number of observation, then output as such)
|
271
|
#gs.run_command('r.out.gdal', input= clims[j-1], output=clims[j-1]+ out_suffix +'.tif', type='Float64')
|
272
|
gs.run_command('r.out.gdal', input= clims[j-1], output=os.path.join(outDir,clims[j-1])+out_suffix+'.tif', type='Float64',createopt='COMPRESS=LZW')
|
273
|
if j-1 ==1: #if image is avg clim then rescaled in celsius degrees
|
274
|
gs.mapcalc(' clim_rescaled = ('+ clims[j-1 ]+ ' * 0.02) -273.15')
|
275
|
#gs.run_command('r.out.gdal', input= 'clim_rescaled', output=clims[j-1]+ out_suffix+'.tif', type='Float64')
|
276
|
gs.run_command('r.out.gdal', input= 'clim_rescaled', output=os.path.join(outDir,clims[j-1])+out_suffix+'.tif', type='Float64',createopt='COMPRESS=LZW')
|
277
|
#clims = calc_clim(LST, 'LST_%s_%d_%02d' % (tile, year, month))
|
278
|
|
279
|
# clean up GRASS DATABASE: ok working
|
280
|
|
281
|
gs.run_command('g.remove', rast=','.join(LST))
|
282
|
## get list of remaining files...
|
283
|
maps_list = gs.mlist_strings(type = 'rast') #list all remaining maps in the location
|
284
|
gs.run_command('g.remove', rast=','.join(maps_list)) #remove remaning maps
|
285
|
gs.os.environ['GRASS_OVERWRITE'] = '0'
|
286
|
|
287
|
#clean up temporary location and set the original GRASS database parameters back
|
288
|
gs.run_command('g.gisenv', set='LOCATION_NAME=%s' % orig_location)
|
289
|
gs.run_command('g.gisenv', set='MAPSET=%s' % orig_mapset)
|
290
|
shutil.rmtree(os.path.join(gs.gisenv()['GISDBASE'], tmp_location))
|
291
|
|
292
|
return None
|
293
|
|
294
|
#Need to add this to run
|
295
|
if __name__ == '__main__':
|
296
|
main()
|
297
|
|
298
|
|