Project

General

Profile

Download (13.2 KB) Statistics
| Branch: | Revision:
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

    
(12-12/22)