-
Notifications
You must be signed in to change notification settings - Fork 0
/
met_data_backfill_wa.py
358 lines (273 loc) · 12.3 KB
/
met_data_backfill_wa.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
#!/usr/bin/env python
# coding: utf-8
# In[1]:
#https://github.com/giswqs/geemap/blob/master/examples/notebooks/11_export_image.ipynb
import ee
import geemap
import numpy as np
import matplotlib.pyplot as plt
import os
#from paths import *
import requests
import pandas as pd
import xarray as xr
from os import listdir
from datetime import datetime, timedelta, date
import contextlib
# Initialize the Earth Engine module
ee.Initialize()
# In[9]:
#########################################################################
############################ USER INPUTS ################################
#########################################################################
# PATHS
# path to temporary folder to store tif files from gee
TIFpath = '/nfs/depot/cce_u1/hill/dfh/op_snowmodel/get_met_data/GEE_Downloads_wa_backfill/'
# path to where you want your output met .dat fime
OUTpath = '/nfs/depot/cce_u1/hill/dfh/op_snowmodel/wa_ne_snowmodel/met/mm_wa.dat'
# DOMAIN
# choose the modeling domain
domain = 'WA'
# TIME
# choose if want to set 'manual' or 'auto' date
date_flag = 'manual'
# If you choose 'manual' set your dates below
# This will start on the 'begin' date at 0:00 and the last iteration will
# be on the day before the 'end' date below.
st_dt = '2021-10-01'
ed_dt = '2022-04-24'
#########################################################################
# In[12]:
# Date setup function
def set_dates(st_dt,ed_dt,date_flag):
if date_flag == 'auto':
# ###automatically select date based on today's date
hoy = date.today()
antes = timedelta(days = 2)
#end date 3 days before today's date
fecha = hoy - antes
eddt = fecha.strftime("%Y-%m-%d")
#whole water year
if (hoy.month == 10) & (hoy.day == 3):
eddt = fecha.strftime("%Y-%m-%d")
stdt = str(hoy.year - 1)+'-10-01'
#start dates
elif fecha.month <10:
stdt = str(fecha.year - 1)+'-10-01'
else:
stdt = str(fecha.year)+'-10-01'
elif date_flag == 'manual':
stdt = st_dt
# add one day to end date because GEE ends on date before last date
eddt = (datetime.strptime(ed_dt, "%Y-%m-%d")+timedelta(days = 1)).strftime("%Y-%m-%d")
return stdt, eddt
# Download CFSv2 met data function
def get_cfsv2(domain, TIFpath, stdt, eddt):
# in GEE the last iteration is on the day before the 'end' date below
# we adjust this here since it is not intuative
#eddt = (datetime.strptime(eddt, '%Y-%m-%d')+timedelta(days = 1)).strftime('%Y-%m-%d')
#create directory with initiation date for ensemble if it doesn't exist
get_ipython().system('mkdir -p $TIFpath')
#path to CSO domains
domains_resp = requests.get("https://raw.githubusercontent.com/snowmodel-tools/preprocess_python/master/CSO_domains.json")
domains = domains_resp.json()
'''
// These are the min and max corners of your domain in Lat, Long
// Western Wyoming
// Input the minimum lat, lower left corner
'''
minLat = domains[domain]['Bbox']['latmin']
#// Input the minimum long, lower left corner
minLong = domains[domain]['Bbox']['lonmin']
#// Input the max lat, upper right corner
maxLat = domains[domain]['Bbox']['latmax']
#// Input the max Long, upper right corner
maxLong = domains[domain]['Bbox']['lonmax']
#/ These are the min and max corners of your reanalysis in Lat, Long (create a slightly larger box)
#// Input the minimum lat, lower left corner
minLatMET = (minLat - 0.25);
#// print(minLat2);
#// Input the minimum long, lower left corner
minLongMET = (minLong - 0.5);
#// Input the max lat, upper right corner
maxLatMET = (maxLat + 0.25);
#// Input the max Long, upper right corner
maxLongMET = (maxLong + 0.5);
# This resolution for the NLCD and DEM outputs for the SnowModel domain in meters
sm_resolution = 100
'''// Resolution for the PRISM output. This shoud change by Latitude of the domain
// because the PRISM product spatial resolution is 2.5 minutes, which equals 150 arc seconds.
// You can use this arc-second calculator to estimate the correct value for the PRISM resolution by latitude
// https://opendem.info/arc2meters.html
// This is one arc-second in meters for 43 degrees N Latitude'''
one_arcsecond = 22.57
PRISM_resolution = one_arcsecond * 150
'''// Define the final output projection using EPSG codes'''
epsg_code = domains[domain]['mod_proj']
#// Name the DEM output
dem_name = 'DEM'
#// Name the Land Cover output
lc_name = 'NLCD2016'
my_domain = ee.Geometry.Rectangle(**{'coords':[minLong,minLat,maxLong,maxLat],'proj': 'EPSG:4326','geodesic':True,});
my_domain_met = ee.Geometry.Rectangle([minLongMET,minLatMET,maxLongMET,maxLatMET])
# download reanalysis data
cfsv2 = ee.ImageCollection('NOAA/CFSV2/FOR6H') .filterBounds(my_domain_met) .filter(ee.Filter.date(stdt,eddt))
data = cfsv2.select('Temperature_height_above_ground', 'Geopotential_height_surface', 'u-component_of_wind_height_above_ground', 'v-component_of_wind_height_above_ground', 'Pressure_surface', 'Specific_humidity_height_above_ground', 'Precipitation_rate_surface_6_Hour_Average')
with contextlib.redirect_stdout(None):
geemap.ee_export_image_collection(data, out_dir=TIFpath,region=my_domain_met,scale=22200,crs=epsg_code)
# In[21]:
# function to check for missing dates
def missing_slice_check(stdt, eddt, TIFpath):
# create a 6-hourly timeseries with no missing values from the start to end date
timesin = pd.date_range(start=stdt, end=eddt, freq='6H')[:-1]
for time in timesin:
nam = time.strftime('%Y%m%d%H')
# compile list of all tif files downloaded from gee
gee_times =[]
for file in listdir(TIFpath):
if file.endswith("tif"):
datetmp = datetime.strptime(file[:-4], '%Y%m%d%H')
gee_times.append(datetmp)
gee_times = sorted(gee_times)
# check for to see if all time slices downloaded from GEE
if len(timesin) != len(gee_times):
#### on 4/16 Nina edited code to print all missing timeslices
print('gee is missing timeslice(s):\n',timesin[~timesin.isin(gee_times)].values)
# if 4 or more consecutive timeslices are missing - quit the function
duration = []
for i in range(len(gee_times)-1):
time_delta = gee_times[i+1] - gee_times[i]
duration.append(time_delta.total_seconds()/60/60)
if max(duration) >= 48:
print('at least two full days of met data are missing - quitting function')
# if there are less than 4 missing consecutive time slices
# repeat the met data from the last valid time slice
else:
missing_idx = np.where(~timesin.isin(gee_times))
missing_dt = timesin[missing_idx]
for j in range(len(missing_dt)):
### on 4/26/22 - Nina added np.squeeze to fix dimensionality error in lines
### 185, and 188-191
if np.squeeze(missing_idx)[j] == 0:
print('choose earlier start date so missing time slices can be filled in')
else:
pre_dt=TIFpath+timesin[np.squeeze(missing_idx)[j]-1].strftime('%Y%m%d%H')+'.tif'
mis_dt = TIFpath+timesin[np.squeeze(missing_idx)[j]].strftime('%Y%m%d%H')+'.tif'
get_ipython().system('cp $pre_dt $mis_dt')
print('replaced', timesin[np.squeeze(missing_idx)[j]].strftime('%Y%m%d%H'),' with ', timesin[np.squeeze(missing_idx)[j]-1].strftime('%Y%m%d%H'))
# In[25]:
# Format gee files for SnowModel function
def MET2SM(TIFpath, OUTpath, stdt, eddt):
# create a 6-hourly timeseries with no missing values from the start to end date
timesin = pd.date_range(start=stdt, end=eddt, freq='6H')[:-1]
#load first tif to get dimensions
ar = xr.open_rasterio(TIFpath+timesin[0].strftime('%Y%m%d%H')+'.tif')
# empty arrays for each met variable
T = np.empty((len(timesin),ar.shape[1],ar.shape[2]))
Z = np.empty((len(timesin),ar.shape[1],ar.shape[2]))
U = np.empty((len(timesin),ar.shape[1],ar.shape[2]))
V = np.empty((len(timesin),ar.shape[1],ar.shape[2]))
P = np.empty((len(timesin),ar.shape[1],ar.shape[2]))
H = np.empty((len(timesin),ar.shape[1],ar.shape[2]))
PR = np.empty((len(timesin),ar.shape[1],ar.shape[2]))
# extract met data from tifs
for i in range(len(timesin)):
#load tif file
nam = TIFpath+timesin[i].strftime('%Y%m%d%H')+'.tif'
ar = xr.open_rasterio(nam)
T[i,:,:] = ar[0,:,:]
Z[i,:,:] = ar[1,:,:]
U[i,:,:] = ar[2,:,:]
V[i,:,:] = ar[3,:,:]
P[i,:,:] = ar[4,:,:]
H[i,:,:] = ar[5,:,:]
PR[i,:,:] = ar[6,:,:]
#number of timesteps per dat
pointsperday = 4
#compute number of grid points and time steps from size of 3d matrix
t,y,x=PR.shape
gridpts=x*y
tsteps=t
#create y m d h vectors
year = timesin.year
month = timesin.month
day = timesin.day
hour = timesin.hour
#create ID numbers for the grid points
ID=1000000+np.linspace(1,gridpts,gridpts)
#create matrices of x and y values
X, Y = np.meshgrid(ar.x.values, ar.y.values)
X=X.flatten(order='F')
Y=Y.flatten(order='F')
#elevation is static (doesn't change with time)
elev=Z[1,:,:].flatten(order='F')
#find number of grid points with <0 elevation. Note: this is related to the
#subroutine met_data_check in the preprocess_code.f. that subroutine seems
#to suggest that negative elevations are ok (say, death valley). But, the
#code itself checks for negative elevations and stops execution is any
#negatives are found.
I = np.where(elev>=0)
validgridpts=np.shape(I)[1]
#remove data at points with neg elevations
ID=ID[I]
X=X[I]
Y=Y[I]
elev=elev[I]
#we are now ready to begin our main loop over the time steps.
fid= open(OUTpath,"w+")
for j in range(tsteps):
#first we write the number of grid points
fid.write('{0:6d}\n'.format(validgridpts))
#prep data matrix for this time step. First, grab the jth time slice
Prtmp=PR[j,:,:].flatten(order='F')
Htmp=H[j,:,:].flatten(order='F')
Ptmp=P[j,:,:].flatten(order='F')
Ttmp=T[j,:,:].flatten(order='F')
Utmp=U[j,:,:].flatten(order='F')
Vtmp=V[j,:,:].flatten(order='F')
#remove data at points with neg elevations
Prtmp=Prtmp[I]
Htmp=Htmp[I]
Ptmp=Ptmp[I]
Ttmp=Ttmp[I]
Utmp=Utmp[I]
Vtmp=Vtmp[I]
#convert precip rate to precip DEPTH (mm) during time interval
Prtmp=Prtmp*24*3600/pointsperday
#convert specific hum. to RH from Clausius-Clapeyron. T is still in K
RHtmp=0.263*Ptmp*Htmp*(np.exp(17.67*(Ttmp-273.16)/(Ttmp-29.65)))**(-1)
#compute wind speed
SPDtmp=np.sqrt(Utmp**2+Vtmp**2)
#compute wind direction. 0-360, with 0 being true north! 90 east, etc.
DIRtmp=np.degrees(np.arctan2(Utmp,Vtmp))
K=np.where(DIRtmp>=180)
J=np.where(DIRtmp<180)
DIRtmp[K]=DIRtmp[K]-180
DIRtmp[J]=DIRtmp[J]+180
#put T in C
Ttmp=Ttmp-273.16
for z in range(len(Prtmp)):
fid.write('{:5.0f}\t'.format(int(year[j]))+'{:5.0f}\t'.format(int(month[j]))+
'{:3.0f}\t'.format(int(day[j]))+'{:6.3f}\t'.format(hour[j])+
'{:9.0f}\t'.format(int(ID[z]))+'{:12.1f}\t'.format(X[z])+
'{:12.1f}\t'.format(Y[z])+'{:8.1f}\t'.format(elev[z])+
'{:9.2f}\t'.format(Ttmp[z])+'{:9.2f}\t'.format(RHtmp[z])+
'{:9.2f}\t'.format(SPDtmp[z])+'{:9.2f}\t'.format(DIRtmp[z])+
'{:9.2f}\n'.format(Prtmp[z]))
fid.close()
# # RUN THE THANG
# In[13]:
# set time parameters
stdt, eddt = set_dates(st_dt,ed_dt,date_flag)
# In[17]:
# download GEE data
get_cfsv2(domain, TIFpath, stdt, eddt)
# In[22]:
# fill in missing time slices or throw error if missing >4 slices
missing_slice_check(stdt, eddt, TIFpath)
# In[26]:
# build SnowModel met file
MET2SM(TIFpath, OUTpath, stdt, eddt)
# In[30]:
# delete directory with tif files
get_ipython().system('rm -rf $TIFpath')