Project

General

Profile

Download (6.88 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
#
20
# Jim Regetz
21
# NCEAS
22
# Created on 16-May-2012
23

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

    
29
#------------------
30
# helper functions
31
#------------------
32

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

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

    
45
# create raster of pixelwise N for a list of input rasters
46
def calc_nobs(maplist, name, overwrite=False):
47
    denominator = '(%s)' % '+'.join(['if(!isnull(%s))' % m
48
        for m in maplist])
49
    gs.mapcalc('nobs_%s = %s' % (name, denominator), overwrite=overwrite)
50

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

    
57
    Arguments:
58
    hdfdir -- path to directory containing the desired HDFs
59
    tile -- tile identifier (e.g., 'h08v04')
60
    start_doy -- integer start of day range (0-366)
61
    end_doy -- integer end of day range (0-366)
62
    year -- integer year (>=2000)
63

    
64
    Returns list of absolute paths to the located HDF files.
65
    """
66
    hdfs = list()
67
    for doy in range(start_doy, end_doy+1):
68
        fileglob = 'MOD11A1.A%d%03d.%s*hdf' % (year, doy, tile)
69
        pathglob = os.path.join(hdfdir, fileglob)
70
        files = glob.glob(pathglob)
71
        if len(files)==0:
72
            # no file found -- print message and move on
73
            print 'cannot access %s: no such file' % pathglob
74
            continue
75
        elif 1<len(files):
76
            # multiple files found! -- just use the first...
77
            print 'multiple hdfs found for tile', tile, 'on', day
78
        hdfs.append(files[0])
79
    return hdfs
80

    
81
# create raster of pixelwise means for a list of input rasters
82
def calc_mean(maplist, name, overwrite=False):
83
    numerator = '(%s)' % '+'.join(['if(isnull(%s), 0, %s)' % (m, m)
84
        for m in maplist])
85
    denominator = '(%s)' % '+'.join(['if(!isnull(%s))' % m
86
        for m in maplist])
87
    gs.mapcalc('mean_%s = %s/%s' % (name, numerator, denominator),
88
        overwrite=overwrite)
89

    
90
# ...combined version of the above two functions...
91
def calc_clim(maplist, name, overwrite=False):
92
    denominator = '(%s)' % '+'.join(['if(!isnull(%s))' % m
93
        for m in maplist])
94
    gs.mapcalc('nobs_%s = %s' % (name, denominator), overwrite=overwrite)
95
    numerator = '(%s)' % '+'.join(['if(isnull(%s), 0, %s)' % (m, m)
96
        for m in maplist])
97
    #gs.mapcalc('mean_%s = round(float(%s)/nobs_%s)' % (name, numerator, name),
98
    gs.mapcalc('mean_%s = %s/nobs_%s' % (name, numerator, name),
99
        overwrite=overwrite)
100

    
101
def load_qc_adjusted_lst(hdf):
102
    """Load LST_Day_1km into GRASS from the specified hdf file, and
103
    nullify any cells for which QA flags indicate anything other than
104
    high quality.
105

    
106
    Argument:
107
    hdf -- local path to the 11A1 HDF file
108

    
109
    Returns the name of the QC-adjusted LST map created in GRASS.
110
    """
111
    lstname =  'LST_' + '_'.join(os.path.basename(hdf).split('.')[1:3])
112
    # read in lst grid
113
    gs.run_command('r.in.gdal',
114
        input='HDF4_EOS:EOS_GRID:%s:MODIS_Grid_Daily_1km_LST:LST_Day_1km' % hdf,
115
        output=lstname)
116
    # read in qc grid
117
    gs.run_command('r.in.gdal',
118
        input = 'HDF4_EOS:EOS_GRID:%s:MODIS_Grid_Daily_1km_LST:QC_Day' % hdf,
119
        output = 'qc_this_day')
120
    gs.run_command('g.region', rast=lstname)
121
    # null out any LST cells for which QC is not high [00]
122
    gs.mapcalc('${lst} = if((qc_this_day & 0x03)==0, ${lst}, null())',
123
        lst=lstname, overwrite=True)
124
    # clean up
125
    gs.run_command('g.remove', rast='qc_this_day')
126
    # return name of qc-adjusted LST raster in GRASS
127
    return lstname
128

    
129
#----------------------------------------
130
# set up a (temporary?) GRASS data store
131
#----------------------------------------
132

    
133
## TODO ##
134

    
135
# hack to fix datum???
136
gs.run_command('g.proj', flags='c',
137
    proj4='+proj=sinu +a=6371007.181 +b=6371007.181 +ellps=sphere')
138
#   proj4='+proj=sinu +R=6371007.181 +nadgrids=@null +wktext')
139

    
140
#-------------------------
141
# test download & process
142
#-------------------------
143

    
144
tile = 'h09v04'
145
year = 2000
146
start_doy = 122
147
end_doy = 152
148

    
149
# connect to data pool and change to the right directory
150
ftp = ftplib.FTP('e4ftl01.cr.usgs.gov')
151
ftp.login('anonymous', '')
152
ftp.cwd('MOLT/MOD11A1.005')
153

    
154
# make list of daily directories to traverse
155
day_dirs = listdirs()
156
days_to_get = [yj_to_ymd(year, x) for x in range(start_doy, end_doy+1)]
157
if not all [day in day_dirs for day in days_to_get]:
158
    error
159

    
160
# using v004 data, this was taking 60 seconds for the 8 hdf/xml pairs on
161
# atlas [16-May-2012] ... each individual hdf is ~23MB
162
hdfs = list()
163
for day in days_to_get:
164
    ftp.cwd(day)
165
    files_to_get = [file for file in list_contents()
166
        if tile in file and file[-3:]=='hdf']
167
    if len(files_to_get)==0:
168
        # no file found -- print message and move on
169
        print 'no hdf found on server for tile', tile, 'on', day
170
        continue
171
    elif 1<len(files_to_get):
172
        # multiple files found! -- just use the first...
173
        print 'multiple hdfs found on server for tile', tile, 'on', day
174
    file = files_to_get[0]
175
    ftp.retrbinary('RETR %s' % file, open(file, 'wb').write)
176
    hdfs.append(file)
177
    ftp.cwd('..')
178

    
179
# politely disconnect
180
ftp.quit()
181

    
182

    
183
#
184
# test 31-day mean with local files
185
#
186

    
187
start_doy = 122
188
end_doy = 152
189
year = 2005
190
hdfdir = '/home/layers/data/climate/MOD11A1.004-OR-orig'
191

    
192
hdfs = get_hdf_paths(hdfdir, tile, start_doy, end_doy, year)
193

    
194
gs.os.environ['GRASS_OVERWRITE'] = '1'
195
# load and qc-adjust all daily LSTs, return list of the GRASS map names
196
LST = [load_qc_adjusted_lst(hdf) for hdf in hdfs]
197
# calculate mean and nobs rasters
198
calc_clim(LST, 'LST_%s_%d_%03d_%03d' % (tile, year, start_doy, end_doy))
199
# clean up
200
gs.run_command('g.remove', rast=','.join(LST))
201
gs.os.environ['GRASS_OVERWRITE'] = '0'
(1-1/3)