Snowmelt timing with Sentinel-1 SAR at Grand Mesa, CO¶
Applying my SAR snowmelt timing toolbox to Grand Mesa, CO for snowex hackweek. Snowex hackweek repo can be found here and presentation can be found here.¶
Our timeframe of interest is December 1st 2019 - August 1st 2020.¶
#### CODE FOR DEVELOPMENT-- THIS RELOADS s1_rtc_bs_utils MODULEsys.modules.pop('s1_rtc_bs_utils')imports1_rtc_bs_utils
In [3]:
# GDAL environment variables for better performanceos.environ['AWS_REGION']='us-west-2'os.environ['GDAL_DISABLE_READDIR_ON_OPEN']='EMPTY_DIR'os.environ['AWS_NO_SIGN_REQUEST']='YES'#xr.set_options(keep_attrs=True)# Paste /proxy/localhost:8787 for cluster diagnosticsclient=Client(local_directory='/tmp',processes=False,silence_logs=logging.ERROR)client
/mnt/working/egagli/sw/miniconda3/envs/aws-rtc-stac2/lib/python3.8/site-packages/distributed/node.py:160: UserWarning: Port 8787 is already in use.
Perhaps you already have a cluster running?
Hosting the HTTP server on port 46411 instead
warnings.warn(
/mnt/working/egagli/sw/miniconda3/envs/aws-rtc-stac2/lib/python3.8/site-packages/xarray/core/indexing.py:1227: PerformanceWarning: Slicing is producing a large chunk. To accept the large
chunk and silence this warning, set the option
>>> with dask.config.set(**{'array.slicing.split_large_chunks': False}):
... array[indexer]
To avoid creating the large chunks, set the option
>>> with dask.config.set(**{'array.slicing.split_large_chunks': True}):
... array[indexer]
return self.array[key]
<xarray.plot.facetgrid.FacetGrid at 0x7f57685d8400>
Let's check the SWE measurements at nearby SNOTEL sites.¶
In [13]:
f,ax=plt.subplots(1,2,figsize=(20,6),gridspec_kw={'width_ratios':[1,2]})s1_rtc_bs_utils.plot_closest_snotel(ts_ds,distance_cutoff=30,ax=ax[0])snotel_data=s1_rtc_bs_utils.get_closest_snotel_data(ts_ds,variable_code='SNOTEL:WTEQ_D',distance_cutoff=30,start_date='1900-01-01',end_date=datetime.today().strftime('%Y-%m-%d'))snotel_data_cm=snotel_data*2.54snotel_data_cm.plot(ax=ax[1])ax[1].set_xlabel('Time')ax[1].set_ylabel('Snow Water Equivalent [cm]')ax[1].set_title('SWE for SNOTEL Stations Close in Proximety to Dataset')ax[1].set_xlim(['2019-12-01','2020-08-01'])plt.tight_layout()
Let's try out a plot that combines S1 backscatter, S2 NDSI, and the nearest SNOTEL station.¶
# keep columns we want in the dataframeurl="https://gist.githubusercontent.com/meganmason/40a87840b52c5516efc716236a67ccf3/raw/9767072779ac6f22d13e5e4dbf2c7e85f3e6e15e/SNEX20_TS_SP_cogm_swe.geojson"swe=gpd.read_file(url)trunc=lambdax:x[:6]swe.PitID=swe.PitID.apply(trunc)swe=swe[['PitID','Site','Date/Local Standard Time','SWE (mm)']]swe=swe.rename({'Date/Local Standard Time':'date','SWE (mm)':'swe (mm)'},axis=1)swe.date=pd.to_datetime(swe.date)
Let's select all of the Grand Mesa sites and reproject to UTM.¶
The open snowpit sites are a lot more spatially uniform in DOY estimates as expected. C-band radar will lose coherence in vegetated areas, so the backscatter minima at tree covered pixels will not necessarily be reflecting snowmelt properties.¶
And let's extract the backscatter timeseries over the buffered pixels for each site.¶
In [27]:
PitIDs=['COGMCO','COGMCT','COGMWO','COGMWT','COGMSO','COGMST']f,ax=plt.subplots(3,2,figsize=(12,10))forpitid,axsinzip(PitIDs,ax.ravel()):snowpit=snowpits_buffer[snowpits_buffer['PitID']==pitid]ts_ds_clipped_site=ts_ds_dem_masked.rio.clip(snowpit.geometry)axs.plot(ts_ds_clipped_site.time,10*np.log10(ts_ds_clipped_site).mean(dim=['x','y']),color='black',label='Mean Backscatter [Watts]')start_date='2019-12-01'end_date='2020-08-01'axs.set_xlim([datetime.strptime(start_date,'%Y-%m-%d'),datetime.strptime(end_date,'%Y-%m-%d')])axs.set_ylabel('Backscatter [dB]')#runoff_map_clipped.plot(ax=axs,cmap='twilight',vmin=0,vmax=365)axs.set_title(f'{snowpit.Site.values[0]}\n Sentinel-1 Backscatter Timeseries')# to csvtimeseries_time=ts_ds_clipped_site.time.valuestimeseries_values=10*np.log10(ts_ds_clipped_site).mean(dim=['x','y']).valuesdf=pd.DataFrame(timeseries_values,timeseries_time)df=df.reset_index()df.columns=['datime','backcsatter_db']df.to_csv(f'../output/grandmesa/snowpit_backscatter_means_csv/{snowpit.Site.values[0]}.csv')plt.tight_layout()plt.savefig('../output/grandmesa/plots/bs_snowpits.png')
We can clearly see backscatter minima in the open sites.¶
PitIDs=['COGMCO','COGMCT','COGMWO','COGMWT','COGMSO','COGMST']f,ax=plt.subplots(3,2,figsize=(12,10))forpitid,axsinzip(PitIDs,ax.ravel()):snowpit=snowpits_buffer[snowpits_buffer['PitID']==pitid]ts_ds_clipped_site=ts_ds_dem_masked.rio.clip(snowpit.geometry)backscatter_line,=axs.plot(ts_ds_clipped_site.time,10*np.log10(ts_ds_clipped_site).mean(dim=['x','y']),color='black',label='S1 Mean Backscatter [dB]')start_date='2019-12-01'end_date='2020-08-01'axs.set_xlim([datetime.strptime(start_date,'%Y-%m-%d'),datetime.strptime(end_date,'%Y-%m-%d')])axs.set_ylabel('Backscatter [dB]')#runoff_map_clipped.plot(ax=axs,cmap='twilight',vmin=0,vmax=365)axs.set_title(f'{snowpit.Site.values[0]}')ndsi_ax=axs.twinx()ndsi_clipped_site=ndsi_dem_masked.rio.clip(snowpit.geometry)ndsi_clipped_site=ndsi_clipped_site.where(ndsi_clipped_site<1)ndsi_line,=ndsi_ax.plot(ndsi_clipped_site.time,ndsi_clipped_site.mean(dim=['x','y']),color='green',label='S2 Mean NDSI')ndwi_clipped_site=ndwi_dem_masked.rio.clip(snowpit.geometry)ndwi_clipped_site=ndwi_clipped_site.where(ndsi_clipped_site<1)ndwi_line,=ndsi_ax.plot(ndwi_clipped_site.time,ndwi_clipped_site.mean(dim=['x','y']),color='blue',label='S2 Mean NWSI')#ndsi_ax.set_ylim([0,1])axs.legend(handles=[backscatter_line,ndsi_line,ndwi_line])plt.tight_layout()plt.savefig('../output/grandmesa/plots/bs_snowpits_ndsi_ndwi.png')
PitIDs=['COGMCO','COGMCT','COGMWO','COGMWT','COGMSO','COGMST']f,ax=plt.subplots(3,2,figsize=(14,10))forpitid,axsinzip(PitIDs,ax.ravel()):snowpit=snowpits_buffer[snowpits_buffer['PitID']==pitid]ts_ds_clipped_site=ts_ds_dem_masked.rio.clip(snowpit.geometry)backscatter_line,=axs.plot(ts_ds_clipped_site.time,10*np.log10(ts_ds_clipped_site).mean(dim=['x','y']),color='black',label='S1 Mean Backscatter')ts_log=10*np.log10(ts_ds_clipped_site)axs.fill_between(ts_ds_clipped_site.time,ts_log.mean(dim=['x','y'])-ts_log.std(dim=['x','y']),ts_log.mean(dim=['x','y'])+ts_log.std(dim=['x','y']),alpha=0.1,color='black')start_date='2019-12-01'end_date='2020-08-01'axs.set_xlim([datetime.strptime(start_date,'%Y-%m-%d'),datetime.strptime(end_date,'%Y-%m-%d')])axs.set_ylabel('Backscatter [dB]')#runoff_map_clipped.plot(ax=axs,cmap='twilight',vmin=0,vmax=365)axs.set_title(f'{snowpit.Site.values[0]}')ndsi_ax=axs.twinx()swe_ax=axs.twinx()ndsi_clipped_site=ndsi_dem_masked.rio.clip(snowpit.geometry)ndsi_clipped_site=ndsi_clipped_site.where(ndsi_clipped_site<1)ndsi_line,=ndsi_ax.plot(ndsi_clipped_site.time,ndsi_clipped_site.mean(dim=['x','y']),color='green',label='S2 Mean NDSI')swe_site=swe[swe['PitID']==pitid]swe_scatter=swe_ax.scatter(x=swe_site['date'],y=swe_site['swe (mm)'],label='Snowpit SWE',c='blue')ndsi_ax.spines['right'].set_position(('outward',40))ndsi_ax.set_ylim([-1,1])swe_ax.set_ylim([0,600])ndsi_ax.spines["right"].set_visible(True)ndsi_ax.yaxis.set_label_position('right')ndsi_ax.yaxis.set_ticks_position('right')ndsi_ax.set_ylabel('NDSI')swe_ax.set_ylabel('SWE [mm]')swe_ax.yaxis.label.set_color('blue')ndsi_ax.yaxis.label.set_color(ndsi_line.get_color())axs.tick_params(colors='black',which='both')swe_ax.tick_params(colors='blue',which='both')ndsi_ax.tick_params(colors='green',which='both')axs.legend(handles=[backscatter_line,ndsi_line,swe_scatter],loc='upper left')plt.tight_layout()plt.subplots_adjust(left=None,bottom=None,right=None,top=None,wspace=0.5,hspace=None)plt.savefig('../output/grandmesa/plots/bs_snowpits_ndsi_swe.png')
Everything seems to sync up nicely. We see minima in Sentinel-1 backscatter right as SWE seems to hit an inflection point. NDSI stays plateaued up until this point and drops significantly soon after. Something strange going on in the Mesa West Open data? Might want to check that out, maybe data processing error?¶