Project

General

Profile

Download (8.34 KB) Statistics
| Branch: | Revision:
1
#!/usr/bin/python
2
#
3
# Early version of assorted Python code to download MODIS 11A1 (daily)
4
# data associated with specified dates and tiles, then interface with
5
# GRASS to calculate temporally averaged LST in a way that accounts for
6
# QC flags.
7
#
8
# TODO:
9
#  - functionalize to encapsulate high level procedural steps
10
#  - create a 'main' function to allow script to be run as a command
11
#    that can accept arguments?
12
#  - put downloaded data somewhere other than working directory?
13
#  - write code to set up and take down a temporary GRASS location/mapset
14
#  - write code to export climatology rasters to file (geotiff? compressed?)
15
#  - calculate other aggregate stats? (stddev, ...)
16
#  - deal with nightly LST too?
17
#  - deal with more than just first two bits of QC flags?
18
#     e.g.: r.mapcalc 'qc_level2 = (qc>>2) & 0x03'
19
#  - record all downloads to a log file?
20
#
21
# Jim Regetz
22
# NCEAS
23
# Created on 16-May-2012
24

    
25
import os, glob
26
import datetime
27
import ftplib
28
import grass.script as gs
29

    
30
#------------------
31
# helper functions
32
#------------------
33

    
34
# quick function to reformat e.g. 200065 to 2000.03.05
35
def yj_to_ymd(year, doy):
36
    date = datetime.datetime.strptime('%d%d' % (year, doy), '%Y%j')
37
    return date.strftime('%Y.%m.%d')
38

    
39
# quick function to return list of dirs in wd
40
def list_contents(ftp):
41
    listing = []
42
    ftp.dir(listing.append)
43
    contents = [item.split()[-1] for item in listing[1:]]
44
    return contents
45

    
46
def download_mod11a1(destdir, tile, start_doy, end_doy, year, ver=5):
47
    """Download into destination directory the MODIS 11A1 HDF files
48
    matching a given tile, year, and day range. If for some unexpected
49
    reason there are multiple matches for a given day, only the first is
50
    used. If no matches, the day is skipped with a warning message.
51

    
52
    Arguments:
53
    destdir -- path to local destination directory
54
    tile -- tile identifier (e.g., 'h08v04')
55
    start_doy -- integer start of day range (0-366)
56
    end_doy -- integer end of day range (0-366)
57
    year -- integer year (>=2000)
58
    ver -- MODIS version (4 or 5)
59

    
60
    Returns list of absolute paths to the downloaded HDF files.
61
    """
62
    # connect to data pool and change to the right directory
63
    ftp = ftplib.FTP('e4ftl01.cr.usgs.gov')
64
    ftp.login('anonymous', '')
65
    ftp.cwd('MOLT/MOD11A1.%03d' % ver)
66
    # make list of daily directories to traverse
67
    available_days = list_contents(ftp)
68
    desired_days = [yj_to_ymd(year, x) for x in range(start_doy, end_doy+1)]
69
    days_to_get = filter(lambda day: day in desired_days, available_days)
70
    if len(days_to_get) < len(desired_days):
71
        missing_days = [day for day in desired_days if day not in days_to_get]
72
        print 'skipping %d day(s) not found on server:' % len(missing_days)
73
        print '\n'.join(missing_days)
74
    # get each tile in turn
75
    hdfs = list()
76
    for day in days_to_get:
77
        ftp.cwd(day)
78
        files_to_get = [file for file in list_contents(ftp)
79
            if tile in file and file[-3:]=='hdf']
80
        if len(files_to_get)==0:
81
            # no file found -- print message and move on
82
            print 'no hdf found on server for tile', tile, 'on', day
83
            continue
84
        elif 1<len(files_to_get):
85
            # multiple files found! -- just use the first...
86
            print 'multiple hdfs found on server for tile', tile, 'on', day
87
        file = files_to_get[0]
88
        local_file = os.path.join(destdir, file)
89
        ftp.retrbinary('RETR %s' % file, open(local_file, 'wb').write)
90
        hdfs.append(os.path.abspath(local_file))
91
        ftp.cwd('..')
92
    # politely disconnect
93
    ftp.quit()
94
    # return list of downloaded paths
95
    return hdfs
96

    
97
def get_hdf_paths(hdfdir, tile, start_doy, end_doy, year):
98
    """Look in supplied directory to find the MODIS 11A1 HDF files
99
    matching a given tile, year, and day range. If for some unexpected
100
    reason there are multiple matches for a given day, only the first is
101
    used. If no matches, the day is skipped with a warning message.
102

    
103
    Arguments:
104
    hdfdir -- path to directory containing the desired HDFs
105
    tile -- tile identifier (e.g., 'h08v04')
106
    start_doy -- integer start of day range (0-366)
107
    end_doy -- integer end of day range (0-366)
108
    year -- integer year (>=2000)
109

    
110
    Returns list of absolute paths to the located HDF files.
111
    """
112
    hdfs = list()
113
    for doy in range(start_doy, end_doy+1):
114
        fileglob = 'MOD11A1.A%d%03d.%s*hdf' % (year, doy, tile)
115
        pathglob = os.path.join(hdfdir, fileglob)
116
        files = glob.glob(pathglob)
117
        if len(files)==0:
118
            # no file found -- print message and move on
119
            print 'cannot access %s: no such file' % pathglob
120
            continue
121
        elif 1<len(files):
122
            # multiple files found! -- just use the first...
123
            print 'multiple hdfs found for tile', tile, 'on', day
124
        hdfs.append(os.path.abspath(files[0]))
125
    return hdfs
126

    
127
# create raster of pixelwise means for a list of input rasters
128
def calc_mean(maplist, name, overwrite=False):
129
    numerator = '(%s)' % '+'.join(['if(isnull(%s), 0, %s)' % (m, m)
130
        for m in maplist])
131
    denominator = '(%s)' % '+'.join(['if(!isnull(%s))' % m
132
        for m in maplist])
133
    gs.mapcalc('mean_%s = %s/%s' % (name, numerator, denominator),
134
        overwrite=overwrite)
135

    
136
# create raster of pixelwise N for a list of input rasters
137
def calc_nobs(maplist, name, overwrite=False):
138
    denominator = '(%s)' % '+'.join(['if(!isnull(%s))' % m
139
        for m in maplist])
140
    gs.mapcalc('nobs_%s = %s' % (name, denominator), overwrite=overwrite)
141

    
142
# ...combined version of the above two functions...
143
def calc_clim(maplist, name, overwrite=False):
144
    denominator = '(%s)' % '+'.join(['if(!isnull(%s))' % m
145
        for m in maplist])
146
    gs.mapcalc('nobs_%s = %s' % (name, denominator), overwrite=overwrite)
147
    numerator = '(%s)' % '+'.join(['if(isnull(%s), 0, %s)' % (m, m)
148
        for m in maplist])
149
    gs.mapcalc('mean_%s = round(float(%s)/nobs_%s)' % (name, numerator, name),
150
        overwrite=overwrite)
151
    return ['%s_%s' % (s, name) for s in ['nobs', 'mean']]
152

    
153
def load_qc_adjusted_lst(hdf):
154
    """Load LST_Day_1km into GRASS from the specified hdf file, and
155
    nullify any cells for which QA flags indicate anything other than
156
    high quality.
157

    
158
    Argument:
159
    hdf -- local path to the 11A1 HDF file
160

    
161
    Returns the name of the QC-adjusted LST map created in GRASS.
162
    """
163
    lstname =  'LST_' + '_'.join(os.path.basename(hdf).split('.')[1:3])
164
    # read in lst grid
165
    gs.run_command('r.in.gdal',
166
        input='HDF4_EOS:EOS_GRID:%s:MODIS_Grid_Daily_1km_LST:LST_Day_1km' % hdf,
167
        output=lstname)
168
    # read in qc grid
169
    gs.run_command('r.in.gdal',
170
        input = 'HDF4_EOS:EOS_GRID:%s:MODIS_Grid_Daily_1km_LST:QC_Day' % hdf,
171
        output = 'qc_this_day')
172
    gs.run_command('g.region', rast=lstname)
173
    # null out any LST cells for which QC is not high [00]
174
    gs.mapcalc('${lst} = if((qc_this_day & 0x03)==0, ${lst}, null())',
175
        lst=lstname, overwrite=True)
176
    # clean up
177
    gs.run_command('g.remove', rast='qc_this_day')
178
    # return name of qc-adjusted LST raster in GRASS
179
    return lstname
180

    
181
#---------------------------------------------
182
# test: download then aggregate for one month
183
#---------------------------------------------
184

    
185
# TODO: set up a (temporary?) GRASS database to use for processing? code
186
# currently assumes it's being run within an existing GRASS session
187
# using an appropriately configured LOCATION...
188
#
189
# note the following trick to fix datum for modis sinu;
190
# TODO: check that this works properly...compare to MRT output?
191
# gs.run_command('g.proj', flags='c',
192
#     proj4='+proj=sinu +a=6371007.181 +b=6371007.181 +ellps=sphere')
193
##    proj4='+proj=sinu +R=6371007.181 +nadgrids=@null +wktext')
194

    
195
tile = 'h09v04'
196
year = 2005
197
start_doy = 1
198
end_doy = 31
199
hdfdir = '.'
200

    
201
# download data
202
### [atlas 17-May-2012] Wall time: 111.62 s
203
hdfs = download_mod11a1(hdfdir, tile, start_doy, end_doy, year)
204

    
205
# note: now that we've downloaded the files into local directory
206
# 'hdfdir', this should yield the same list...
207
#hdfs = get_hdf_paths(hdfdir, tile, start_doy, end_doy, year)
208

    
209
# generate monthly pixelwise mean & count of high-quality daytime LST
210
# values
211
### [atlas 17-May-2012] Wall time: 53.79 s
212
gs.os.environ['GRASS_OVERWRITE'] = '1'
213
LST = [load_qc_adjusted_lst(hdf) for hdf in hdfs]
214
calc_clim(LST, 'LST_%s_%d_%03d_%03d' % (tile, year, start_doy, end_doy))
215
# clean up
216
gs.run_command('g.remove', rast=','.join(LST))
217
gs.os.environ['GRASS_OVERWRITE'] = '0'
(1-1/3)