Beispiel #1
0
def process_quad_patch(name, M=None):

    center_idx = np.nonzero(centerlines['name'] == name)[0][0]
    centerline = centerlines['geom'][center_idx]
    if M is None:
        M = centerlines['rows'][center_idx]
    bound = bounds['geom'][bounds['name'] == name][0]

    center = linestring_utils.resample_linearring(np.array(centerline),
                                                  scale,
                                                  closed_ring=0)

    g = unstructured_grid.UnstructuredGrid(max_sides=6)

    # Smooth the exterior

    ext_points = np.array(bound.exterior)
    ext_points = linestring_utils.resample_linearring(ext_points,
                                                      scale,
                                                      closed_ring=True)
    from stompy import filters
    ext_points[:, 0] = filters.lowpass_fir(ext_points[:, 0], 3)
    ext_points[:, 1] = filters.lowpass_fir(ext_points[:, 1], 3)
    smooth_bound = geometry.Polygon(ext_points)

    L = smooth_bound.exterior.length

    def profile(x, s, perp):
        probe_left = geometry.LineString([x, x + L * perp])
        probe_right = geometry.LineString([x, x - L * perp])

        left_cross = smooth_bound.exterior.intersection(probe_left)
        right_cross = smooth_bound.exterior.intersection(probe_right)

        assert left_cross.type == 'Point', "Fix this for multiple intersections"
        assert right_cross.type == 'Point', "Fix this for multiple intersections"

        pnt_left = np.array(left_cross)
        pnt_right = np.array(right_cross)
        d_left = utils.dist(x, pnt_left)
        d_right = utils.dist(x, pnt_right)

        return np.interp(np.linspace(-1, 1, M), [-1, 0, 1],
                         [-d_right, 0, d_left])

    g.add_rectilinear_on_line(center, profile)
    g.renumber()
    return g
Beispiel #2
0
def nudge_by_gage(ds,
                  usgs_station,
                  station,
                  decorr_days,
                  period_start=None,
                  period_end=None):
    # This slicing may be stopping one sample shy, shouldn't be a problem.
    if period_start is None:
        period_start = ds.time.values[0]
    if period_end is None:
        period_end = ds.time.values[-1]

    usgs_gage = usgs_nwis.nwis_dataset(usgs_station,
                                       products=[60],
                                       start_date=period_start,
                                       end_date=period_end,
                                       days_per_request='M',
                                       cache_dir=common.cache_dir)

    # Downsample to daily
    df = usgs_gage['stream_flow_mean_daily'].to_dataframe()
    df_daily = df.resample('D').mean()

    # Get the subset of BAHM data which overlaps this gage data
    time_slc = slice(np.searchsorted(ds.time, df_daily.index.values[0]),
                     1 + np.searchsorted(ds.time, df_daily.index.values[-1]))

    bahm_subset = ds.sel(station=station).isel(time=time_slc)

    assert len(bahm_subset.time) == len(
        df_daily), "Maybe BAHM data doesn't cover entire period"

    errors = bahm_subset.flow_cfs - df_daily.stream_flow_mean_daily

    # Easiest: interpolate errors over nans, apply to bahm data array.
    # the decorrelation time is tricky, though.

    # Specify a decorrelation time scale then relax from error to zero
    # over that period
    valid = np.isfinite(errors.values)
    errors_interp = np.interp(utils.to_dnum(ds.time),
                              utils.to_dnum(df_daily.index[valid]),
                              errors[valid])
    all_valid = np.zeros(len(ds.time), 'f8')
    all_valid[time_slc] = 1 * valid

    weights = (2 * filters.lowpass_fir(all_valid, decorr_days)).clip(0, 1)
    weighted_errors = weights * errors_interp

    # Does this work?
    subset = dict(station=station)

    cfs_vals = ds.flow_cfs.loc[subset] - weighted_errors
    ds.flow_cfs.loc[subset] = cfs_vals.clip(0, np.inf)
    ds.flow_cms.loc[subset] = 0.028316847 * ds.flow_cfs.loc[subset]

    # user feedback
    cfs_shifts = weighted_errors[time_slc]
    print("Nudge: %s => %s, shift in CFS: %.2f +- %.2f" %
          (usgs_station, station, np.mean(cfs_shifts), np.std(cfs_shifts)))
Beispiel #3
0
 def Hsig_adjustment(da):
     Hsig=cdip_mop.hindcast_dataset(station='SM141', # Pescadero State Beach
                                    start_date=da.time.values[0],
                                    end_date=da.time.values[-1],
                                    clip='inclusive',
                                    cache_dir=cache_dir,
                                    variables=['waveHs'])
     Hsig_colo=np.interp(utils.to_dnum(da.time.values),
                         utils.to_dnum(Hsig.time.values), Hsig['waveHs'].values )
     offset=(0.351*Hsig_colo - 0.230).clip(0)
     # TODO: see if a hanning window lowpass makes the "optimal" window size
     # something shorter, which would seem more physical.
     offset=filters.lowpass_fir(offset,winsize=7*24,window='boxcar')
     return da+offset
Beispiel #4
0
##

# Sample datasets
if 1:
    import bathy
    dem = bathy.dem()

    # fake, sparser tracks.
    adcp_shp = wkb2shp.shp2geom('sparse_fake_bathy_trackline.shp')
    xys = []
    for feat in adcp_shp['geom']:
        feat_xy = np.array(feat)
        feat_xy = linestring_utils.resample_linearring(feat_xy,
                                                       1.0,
                                                       closed_ring=0)
        feat_xy = filters.lowpass_fir(feat_xy, winsize=6, axis=0)
        xys.append(feat_xy)
    adcp_xy = np.concatenate(xys)
    source_ds = xr.Dataset()
    source_ds['x'] = ('sample', 'xy'), adcp_xy
    source_ds['z'] = ('sample', ), dem(adcp_xy)

##


def steady_streamline_oneway(g, Uc, x0, max_t=3600, max_dist=np.inf):
    # trace some streamlines
    x0 = np.asarray(x0)
    t0 = 0.0

    c = g.select_cells_nearest(x0, inside=True)
Beispiel #5
0
 def low_high(d, winsize):
     high = percentile_filter(d, 95, winsize)
     low = percentile_filter(d, 5, winsize)
     high = filters.lowpass_fir(high, winsize)
     low = filters.lowpass_fir(low, winsize)
     return low, high
Beispiel #6
0
def figure_usgs_salinity_time_series(station, station_name):
    mod_lp_win = usgs_lp_win = 40  # 40h lowpass

    # Gather USGS data:
    ds = usgs_salinity_time_series(station)
    usgs_dt_s = np.median(np.diff(ds.time)) / np.timedelta64(1, 's')
    usgs_stride = slice(None, None, max(1, int(3600. / usgs_dt_s)))
    if 'salinity_01' in ds:
        obs_salt_davg = np.c_[ds.salinity.values[usgs_stride],
                              ds.salinity_01.values[usgs_stride]]
        obs_salt_davg = np.nanmean(obs_salt_davg, axis=1)
    else:
        obs_salt_davg = ds.salinity.values[usgs_stride]

    dists = utils.dist(his_xy, [ds.x, ds.y])
    station_idx = np.argmin(dists)
    print("Nearest model station is %.0f m away from observation" %
          (dists[station_idx]))
    print(station_idx)

    def low_high(d, winsize):
        high = percentile_filter(d, 95, winsize)
        low = percentile_filter(d, 5, winsize)
        high = filters.lowpass_fir(high, winsize)
        low = filters.lowpass_fir(low, winsize)
        return low, high

    obs_salt_range = low_high(obs_salt_davg, usgs_lp_win)

    mod_salt_davg = his.salinity.isel(stations=station_idx).mean(dim='laydim')
    mod_salt_range = low_high(mod_salt_davg, mod_lp_win)

    # Try picking out a reasonable depth in the model
    surf_label = "Surface"
    bed_label = "Bed"
    if ds.salinity.attrs['elev_mab'] is not None:
        z_mab = ds.salinity.attrs['elev_mab']
        surf_label = "%.1f mab" % z_mab
        mod_salt_surf = extract_at_zab(his,
                                       "salinity",
                                       z_mab,
                                       stations=station_idx)
    else:
        mod_salt_surf = his.salinity.isel(stations=station_idx, laydim=-1)

    if ('salinity_01' in ds) and (ds.salinity_01.attrs['elev_mab']
                                  is not None):
        z_mab = ds.salinity_01.attrs['elev_mab']
        bed_label = "%.1f mab" % z_mab
        mod_salt_bed = extract_at_zab(his,
                                      "salinity",
                                      z_mab,
                                      stations=station_idx)
    else:
        mod_salt_bed = his.salinity.isel(stations=station_idx, laydim=0)

    mod_deltaS = mod_salt_bed - mod_salt_surf

    if 'salinity_01' in ds:
        if ds.site_no == '375607122264701':
            ds = ds.rename({
                "salinity": "salinity_01",
                "salinity_01": "salinity"
            })

    if 'salinity_01' in ds:
        obs_deltaS = ds.salinity_01.values - ds.salinity.values
    else:
        obs_deltaS = None

    if 1:  # plotting time series
        plt.figure(1).clf()
        fig, ax = plt.subplots(num=1)
        fig.set_size_inches([10, 4.75], forward=True)

        # These roughly mimic the style of water level plots in the validation report.
        obs_color = 'cornflowerblue'
        obs_lw = 1.5
        mod_color = 'k'
        mod_lw = 0.8

        if 'salinity_01' in ds:
            ax.plot(utils.to_dnum(ds.time)[usgs_stride],
                    filters.lowpass_fir(ds.salinity[usgs_stride], usgs_lp_win),
                    label='Obs. Upper',
                    lw=obs_lw,
                    color=obs_color)

            ax.plot(utils.to_dnum(ds.time)[usgs_stride],
                    filters.lowpass_fir(ds.salinity_01[usgs_stride],
                                        usgs_lp_win),
                    label='Obs. Lower',
                    lw=obs_lw,
                    color=obs_color,
                    ls='--')
        else:
            ax.plot(utils.to_dnum(ds.time)[usgs_stride],
                    filters.lowpass_fir(ds.salinity[usgs_stride], usgs_lp_win),
                    label='Obs.',
                    lw=obs_lw,
                    color=obs_color)

        ax.plot(utils.to_dnum(his.time),
                filters.lowpass_fir(mod_salt_surf, 40),
                label='Model %s' % surf_label,
                lw=mod_lw,
                color=mod_color)

        ax.plot(utils.to_dnum(his.time),
                filters.lowpass_fir(mod_salt_bed, 40),
                label='Model %s' % bed_label,
                lw=mod_lw,
                color=mod_color,
                ls='--')

        if 1:  # is it worth showing tidal variability?

            ax.fill_between(utils.to_dnum(ds.time)[usgs_stride],
                            obs_salt_range[0],
                            obs_salt_range[1],
                            color=obs_color,
                            alpha=0.3,
                            zorder=-1,
                            lw=0)

            ax.fill_between(utils.to_dnum(his.time),
                            mod_salt_range[0],
                            mod_salt_range[1],
                            color='0.3',
                            alpha=0.3,
                            zorder=-1,
                            lw=0)

        ax.set_title(station_name)
        ax.xaxis.axis_date()
        fig.autofmt_xdate()
        ax.set_ylabel('Salinity (ppt)')
        ax.legend(fontsize=10, loc='lower left')

        ax.axis(xmin=utils.to_dnum(t_spunup), xmax=utils.to_dnum(t_stop))

        fig.tight_layout()

        safe_station = station_name.replace(' ', '_')
        fig.savefig(os.path.join(savepath, "%s.png" % safe_station), dpi=100)
        fig.savefig(os.path.join(savepath, "%s.pdf" % safe_station))

    if tex_fp is not None:  # metrics
        target_time_dnum = utils.to_dnum(his.time.values)

        obs_time_dnum = utils.to_dnum(ds.time.values)

        obs_salt_davg_intp = utils.interp_near(target_time_dnum,
                                               obs_time_dnum[usgs_stride],
                                               obs_salt_davg, 1.5 / 24)
        valid = np.isfinite(mod_salt_davg * obs_salt_davg_intp).values
        valid = (valid
                 & (target_time_dnum >= utils.to_dnum(t_spunup))
                 & (target_time_dnum <= utils.to_dnum(t_stop)))
        dnum = target_time_dnum[valid]
        mod_values = mod_salt_davg[valid].values
        obs_values = obs_salt_davg_intp[valid]

        bias = np.mean(mod_values - obs_values)
        ms = utils.model_skill(mod_values, obs_values)
        r2 = np.corrcoef(mod_values, obs_values)[0, 1]
        rmse = utils.rms(mod_values - obs_values)
        tex_fp.write((
            "%-16s  "  # station name
            " & %7.3f"  # skill
            " & %11.2f"  # bias
            " & %7.3f"  # r2
            " & %10.2f"  # rmse
            " \\\ \\hline \n") % (station_name, ms, bias, r2, rmse))
 def smooth(x):
     return filters.lowpass_fir(x, winsize=lp_win)
Beispiel #8
0
                                          fields=['tnum','swim_x','swim_y','x','y',
                                                  'model_u_surf','model_v_surf'],
                                          fill='interp')
    
    t=expand['tnum'].values
    swim_x=expand['swim_x'].values
    swim_y=expand['swim_y'].values
    geo_x=expand['x'].values
    geo_y=expand['y'].values
    hyd_u=expand['model_u_surf'].values
    hyd_v=expand['model_v_surf'].values

    winsize=7

    # Hydro values
    hyd_hdg=np.arctan2(filters.lowpass_fir(hyd_v,winsize),
                       filters.lowpass_fir(hyd_u,winsize))[1:-1]

    # Swim-based values:
    x_lp=filters.lowpass_fir(swim_x,winsize)
    y_lp=filters.lowpass_fir(swim_y,winsize)
    u=np.diff(x_lp)/np.diff(swim_t)
    v=np.diff(y_lp)/np.diff(swim_t)
    hdg=np.arctan2(v,u)
    swim_turn=(np.diff(hdg) -np.pi)%(2*np.pi) + np.pi
    swim_spd=np.sqrt(u**2+v**2)
    swim_spd_ctr=0.5*(spd[1:]+spd[:-1])
    swim_hdg_ctr=0.5*(hdg[1:]+hdg[:-1]) # this is already relative to the flow
    
    # Geo-based values
    geo_x_lp=filters.lowpass_fir(geo_x,winsize)
Beispiel #9
0
            
        # get the data into a monthly time series before trying to fit seasonal cycle
        valid = np.isfinite(fld_in.values)
        absmonth_mean=bin_mean(absmonth[valid],fld_in.values[valid])
        month_mean=bin_mean(month[valid],fld_in.values[valid])
        
        if np.sum(np.isfinite(month_mean)) < 12:
            print("Insufficient data for seasonal trends - will fill with sample mean")
            trend_and_season=np.nanmean(month_mean) * np.ones(len(dns))
            t_and_s_flag=FLAG_MEAN
        else:
            # fit long-term trend and a stationary seasonal cycle
            # this removes both the seasonal cycle and the long-term mean,
            # leaving just the trend
            trend_hf=fld_in.values - month_mean[month]
            lp = filters.lowpass_fir(trend_hf,lowpass_days,nan_weight_threshold=0.01)
            trend = utils.fill_invalid(lp)
            # recombine with the long-term mean and monthly trend 
            # to get the fill values.
            trend_and_season = trend + month_mean[month]
            t_and_s_flag=FLAG_SEASONAL_TREND

        # long gaps are mostly filled by trend and season
        gaps=mark_gaps(dns,valid,shortgap_days,include_ends=True) 
        fld_in.values[gaps] = trend_and_season[gaps]
        fld_flag.values[gaps] = t_and_s_flag

        still_missing=np.isnan(fld_in.values)
        fld_in.values[still_missing] = utils.fill_invalid(fld_in.values)[still_missing]
        fld_flag.values[still_missing] = FLAG_INTERP
Beispiel #10
0
lamb = 4000  # wave-length of meanders
width = 500  # mean channel width
noise_w = 50  # amplitude of noise to add to the channel banks
noise_l = 1500  # length-scale of noise

centerline = np.c_[s, amp * np.cos(2 * np.pi * s / lamb)]
pline = geometry.LineString(centerline)
channel = pline.buffer(width / 2)
ring = np.array(channel.exterior)
ring_norm = linestring_utils.left_normals(ring)

noise = (np.random.random(len(ring_norm)) - 0.5)
winsize = int(noise_l / (channel.exterior.length / len(ring_norm)))
noise[:winsize] = 0  # so the ends still match up
noise[-winsize:] = 0
noise_lp = filters.lowpass_fir(noise, winsize)
noise_lp *= noise_w / np.sqrt(np.mean(noise_lp**2))

# domain boundary including the random noise
ring_noise = ring + noise_lp[:, None] * ring_norm

# Create the curvilinear section
thalweg = centerline[50:110]

plt.figure(1).clf()
plt.plot(centerline[:, 0], centerline[:, 1], 'k-', zorder=2)
plt.axis('equal')

plot_wkb.plot_wkb(channel, zorder=-2)
plt.plot(ring_noise[:, 0], ring_noise[:, 1], 'm-')
Beispiel #11
0
    def disp_array(self):
        self.hydro.infer_2d_elements()
        self.hydro.infer_2d_links()

        # first calculate all time steps, just in 2D.

        Q = np.zeros((len(self.hydro.t_secs), self.hydro.n_2d_links),
                     np.float64)
        A = np.zeros((len(self.hydro.t_secs), self.hydro.n_2d_links),
                     np.float64)

        for ti in utils.progress(range(len(self.hydro.t_secs))):
            t_sec = self.hydro.t_secs[ti]
            flows = [
                hydro.flows(t_sec) for hydro in [self.hydro_tidal, self.hydro]
            ]
            flow_hp = flows[0] - flows[1]
            # depth-integrate
            flow_hor = flow_hp[:self.hydro_tidal.n_exch_x]
            link_flows = np.bincount(
                self.hydro.exch_to_2d_link['link'],
                self.hydro.exch_to_2d_link['sgn'] * flow_hor)

            Q[ti, :] = link_flows**2
            A[ti, :] = np.bincount(
                self.hydro.exch_to_2d_link['link'],
                self.hydro.areas(t_sec)[:self.hydro.n_exch_x])

        dt_s = np.median(np.diff(self.hydro.t_secs))

        winsize = int(self.lowpass_days * 86400 / dt_s)
        # These are a little slow.  10s?
        # could streamline this some since we later only use a fraction of the values.

        # clip here is because in some cases the values are very low and
        # and some roundoff is creating negatives.
        Qlp = filters.lowpass_fir(Q, winsize=winsize, axis=0).clip(0)
        Alp = filters.lowpass_fir(A, winsize=winsize, axis=0).clip(0)

        rms_flows = np.sqrt(Qlp)
        mean_A = Alp

        Lexch = self.hydro.exchange_lengths.sum(axis=1)[:self.hydro.n_exch_x]
        L = [
            Lexch[exchs[0]] for l, exchs in utils.enumerate_groups(
                self.hydro.exch_to_2d_link['link'])
        ]

        # This is just a placeholder. A proper scaling needs to account for
        # cell size. rms_flows has units of m3/s. probably that should be normalized
        # by dividing by average flux area, and possibly multiplying by the distance
        # between cell centers. that doesn't seem quite right.
        link_K = self.K_scale * rms_flows * L / mean_A

        # this is computed for every time step, but we can trim that down
        # it's lowpassed at winsize.  Try stride of half winsize.
        # That was used for the first round of tests, but it looks a bit
        # sparse.
        K_stride = winsize // 4
        K2D = link_K[::K_stride, :]
        K_t_secs = self.hydro.t_secs[::K_stride]

        if self.amp_factor != 1.0:
            Kbar = K2D.mean(axis=0)
            K2D = (Kbar[None, :] + self.amp_factor *
                   (K2D - Kbar[None, :])).clip(0)

        K = np.zeros((len(K_t_secs), self.hydro.n_exch), np.float64)

        # and then project to 3D
        K[:, :self.hydro.n_exch_x] = K2D[:, self.hydro.exch_to_2d_link['link']]

        if 0:  # DEBUGGING
            # verify that I can get back to the previous, constant in time
            # run.
            log.warning("Debugging K")
            Kconst = super(KautoUnsteady, self).disp_array()
            K[:, :] = Kconst[None, :]

        log.info("Median dispersion coefficient: %g" % (np.median(K)))

        return K_t_secs, K
Beispiel #12
0
# more explicitly:
#  time with a headwind minus the unimpaired time
#  distance      / reduced speed  - unimpaired time
#  (1500 * 0.174) / (1500-0.25)   - 0.174 = 29us
#  So my signal is much smaller -- but the assumption of 0.75m/s and 0.25 m/s is
#  probably an exaggeration, and some of the distance traveled is not parallel to the
#  flow
#  Still need to properly work through the whole formula

if 0:
    # Any chance that filtering out the spikes gives something reasonable?
    lp_skew = net_skew.copy()
    lp_skew[np.abs(lp_skew) > 6000] = np.nan

    lp_skew = filters.lowpass_fir(lp_skew, winsize=5000)
    axs[0].plot(t_complete, lp_skew, label='LP Net skew')
axs[0].legend(loc='upper left')


# plot skew and travel in original time coordinate for
# each
def demean(x):
    return x - np.nanmean(x)


for i, leg in enumerate(legs):
    axs[1].plot(leg.transits.time, leg.transits.clock_skew_us, label=leg.label)

    axs[2].plot(leg.transits.time,
                demean(leg.transits.mean_travel_us),
Beispiel #13
0
def lp(x):
    return filters.lowpass_fir(x, winsize=50)
Beispiel #14
0
def fill_and_flag(ds,fld,site,
                  lowpass_days=3*365,
                  shortgap_days=45 # okay to interpolate a little over a month?
              ):
    """
    Update a single field for a single site in ds, by
    extracting long-term trends, seasonal cycle, and
    interpolating between these and measured data
    """
    # first, create mapping from time index to absolute month
    dts=utils.to_datetime(dns)
    absmonth = [12*dt.year + (dt.month-1) for dt in dts]
    absmonth = np.array(absmonth) - dts[0].year*12
    month=absmonth%12

    fld_in=ds[fld].sel(site=site)
    orig_values=fld_in.values
    fld_flag=ds[fld+'_flag'].sel(site=site)

    prefilled=fld_flag.values & (FLAG_SEASONAL_TREND | FLAG_INTERP | FLAG_MEAN)        
    fld_in.values[prefilled]=np.nan # resets the work of this loop in case it's run multiple times
    n_valid=np.sum(~fld_in.isnull())        

    if n_valid==0:
        msg=" --SKIPPING--"
    else:
        msg=""
    print("   field: %s  %d/%d valid input points %s"%(fld,n_valid,len(fld_in),msg))

    if n_valid==0:
        return

    # get the data into a monthly time series before trying to fit seasonal cycle
    valid = np.isfinite(fld_in.values)
    absmonth_mean=bin_mean(absmonth[valid],fld_in.values[valid])
    month_mean=bin_mean(month[valid],fld_in.values[valid])

    if np.sum(np.isfinite(month_mean)) < 12:
        print("Insufficient data for seasonal trends - will fill with sample mean")
        trend_and_season=np.nanmean(month_mean) * np.ones(len(dns))
        t_and_s_flag=FLAG_MEAN
    else:
        # fit long-term trend and a stationary seasonal cycle
        # this removes both the seasonal cycle and the long-term mean,
        # leaving just the trend
        trend_hf=fld_in.values - month_mean[month]
        lp = filters.lowpass_fir(trend_hf,lowpass_days,nan_weight_threshold=0.01)
        trend = utils.fill_invalid(lp)
        # recombine with the long-term mean and monthly trend 
        # to get the fill values.
        trend_and_season = trend + month_mean[month]
        t_and_s_flag=FLAG_SEASONAL_TREND

    # long gaps are mostly filled by trend and season
    gaps=mark_gaps(dns,valid,shortgap_days,include_ends=True) 
    fld_in.values[gaps] = trend_and_season[gaps]
    fld_flag.values[gaps] = t_and_s_flag

    still_missing=np.isnan(fld_in.values)
    fld_in.values[still_missing] = utils.fill_invalid(fld_in.values)[still_missing]
    fld_flag.values[still_missing] = FLAG_INTERP

    # Make sure all flows are nonnegative
    negative=fld_in.values<0.0
    fld_in.values[negative]=0.0
    fld_flag.values[negative] |= FLAG_CLIPPED

    if 0: # illustrative(?) plots
        fig,ax=plt.subplots()
        ax.plot(dns,orig_values,'m-o',label='Measured %s'%fld)
        ax.plot(dns,fld_in,'k-',label='Final %s'%fld,zorder=5)
        # ax.plot(dns,month_mean[month],'r-',label='Monthly Clim.')
        # ax.plot(dns,trend_hf,'b-',label='Trend w/HF')
        ax.plot(dns,trend,'g-',lw=3,label='Trend')
        ax.plot(dns,trend_and_season,color='orange',label='Trend and season')
Beispiel #15
0
def fill_tidal_data(da,fill_time=True):
    """
    Extract tidal harmonics from an incomplete xarray DataArray, use
    those to fill in the gaps and return a complete DataArray.

    Uses all 37 of the standard NOAA harmonics, may not be stable
    with short time series.
    
    A 5-day lowpass is removed from the harmonic decomposition, and added
    back in afterwards.

    Assumes that the DataArray has a 'time' coordinate with datetime64 values.

    The time dimension must be dense enough to extract an exact time step
    
    If fill_time is True, holes in the time coordinate will be filled, too.
    """
    diffs=np.diff(da.time)
    dt=np.median(diffs)

    if fill_time:
        gaps=np.nonzero(diffs>1.5*dt)[0]
        pieces=[]
        last=0
        for gap_i in gaps:
            # gap_i=10 means that the 10th diff was too big
            # that means the jump from 10 to 11 was too big
            # the preceding piece should go through 9, so
            # exclusive of gap_i
            pieces.append(da.time.values[last:gap_i])
            pieces.append(np.arange( da.time.values[gap_i],
                                     da.time.values[gap_i+1],
                                     dt))
            last=gap_i+1
        pieces.append(da.time.values[last:])
        dense_times=np.concatenate(pieces)
        dense_values=np.nan*np.zeros(len(dense_times),np.float64)
        dense_values[ np.searchsorted(dense_times,da.time.values) ] = da.values
        da=xr.DataArray(dense_values,
                        dims=['time'],coords=[dense_times])
    else:
        pass 

    dnums=utils.to_dnum(da.time)
    data=da.values

    # lowpass at about 5 days, splitting out low/high components
    winsize=int( np.timedelta64(5,'D') / dt )
    data_lp=filters.lowpass_fir(data,winsize)
    data_hp=data - data_lp

    valid=np.isfinite(data_hp)
    omegas=harm_decomp.noaa_37_omegas() # as rad/sec

    harmonics=harm_decomp.decompose(dnums[valid]*86400,data_hp[valid],omegas)

    dense=harm_decomp.recompose(dnums*86400,harmonics,omegas)

    data_recon=utils.fill_invalid(data_lp) + dense

    data_filled=data.copy()
    missing=np.isnan(data_filled)
    data_filled[missing] = data_recon[missing]

    fda=xr.DataArray(data_filled,coords=[da.time],dims=['time'])
    return fda
Beispiel #16
0
# units: precip: kg/m2, monthly average.  Based on prior scripts, this is maybe a per day number
#   https://www.esrl.noaa.gov/psd/data/gridded/data.narr.monolevel.html#plot
#   mentions that this file is "Monthly average of Daily Accumulation"
# units: evap: kg/m2 monthly accumulated average.  Based on prior scripts, maybe a per 3h number??
# From the file metadata, "should be" mm/month.
ax.plot(utils.to_dnum(precip.time.values),
        precip.apcp.isel(x=lon_i, y=lat_i),
        '--',
        label='NARR precip')
ax.plot(utils.to_dnum(evap.time.values),
        8 * evap.pevap.isel(x=lon_i, y=lat_i),
        label='NARR p-evap')

# data in mm, originally at hourly time scale.
ax.plot(utils.to_dnum(union_city.time),
        filters.lowpass_fir(24 * union_city.HlyPrecip, 10 * 24),
        '--',
        label='CIMIS precip')
ax.plot(utils.to_dnum(union_city.time),
        filters.lowpass_fir(24 * union_city.HlyEto, 10 * 24),
        label='CIMIS ETO')

ax.plot(utils.to_dnum(union_city.time),
        filters.lowpass_fir(24 * union_city.HlyEvap, 10 * 24),
        label='CIMIS Evap')

ax.plot(utils.to_dnum(burl_ds.time), burl_ds.evap, label='Burlingame')

# --

ax_cumul.plot(utils.to_dnum(precip.time.values),