Example #1
0
def to_swan(
    self,
    filename,
    append=False,
    id="Created by wavespectra",
    ntime=None
):
    """Write spectra in SWAN ASCII format.

    Args:
        - filename (str): str, name for output SWAN ASCII file.
        - append (bool): if True append to existing filename.
        - id (str): used for header in output file.
        - ntime (int, None): number of times to load into memory before dumping output
          file if full dataset does not fit into memory, choose None to load all times.

    Note:
        - Only datasets with lat/lon coordinates are currently supported.
        - Extra dimensions other than time, site, lon, lat, freq, dim not yet
          supported.
        - Only 2D spectra E(f,d) are currently supported.
        - ntime=None optimises speed as the dataset is loaded into memory however the
          dataset may not fit into memory in which case a smaller number of times may
          be prescribed.

    """
    # If grid reshape into site, otherwise ensure there is site dim to iterate over
    dset = self._check_and_stack_dims()
    ntime = min(ntime or dset.time.size, dset.time.size)

    # Ensure time dimension exists
    is_time = attrs.TIMENAME in dset[attrs.SPECNAME].dims
    if not is_time:
        dset = dset.expand_dims({attrs.TIMENAME: [None]})
        times = dset[attrs.TIMENAME].values
    else:
        times = dset[attrs.TIMENAME].to_index().to_pydatetime()
        times = [f"{t:%Y%m%d.%H%M%S}" for t in times]

    # Keeping only supported dimensions
    dims_to_keep = {attrs.TIMENAME, attrs.SITENAME, attrs.FREQNAME, attrs.DIRNAME}
    dset = dset.drop_dims(set(dset.dims) - dims_to_keep)

    # Ensure correct shape
    dset = dset.transpose(attrs.TIMENAME, attrs.SITENAME, attrs.FREQNAME, attrs.DIRNAME)

    # Instantiate swan object
    try:
        x = dset.lon.values
        y = dset.lat.values
    except AttributeError as err:
        raise NotImplementedError(
            "lon-lat variables are required to write SWAN spectra file"
        ) from err
    sfile = SwanSpecFile(
        filename,
        freqs=dset.freq,
        dirs=dset.dir,
        time=is_time,
        x=x,
        y=y,
        append=append,
        id=id,
    )

    # Dump each timestep
    i0 = 0
    i1 = ntime
    while i1 <= dset.time.size or i0 < dset.time.size:
        ds = dset.isel(time=slice(i0, i1))
        part_times = times[i0:i1]
        i0 = i1
        i1 += ntime
        specarray = ds[attrs.SPECNAME].values
        for itime, time in enumerate(part_times):
            darrout = specarray[itime]
            sfile.write_spectra(darrout, time=time)

    sfile.close()
Example #2
0
def to_swan(
    self,
    filename,
    append=False,
    id="Created by wavespectra",
    unique_times=False,
    dircap_270=False,
):
    """Write spectra in SWAN ASCII format.

    Args:
        - filename (str): str, name for output SWAN ASCII file.
        - append (bool): if True append to existing filename.
        - id (str): used for header in output file.
        - unique_times (bool): if True, only last time is taken from
          duplicate indices.
        - dircap_270 (bool): if True, ensure directions do not exceed 270 degrees
          as requerid for swan to prescribe boundaries.

    Note:
        - Only datasets with lat/lon coordinates are currently supported.
        - Extra dimensions other than time, site, lon, lat, freq, dim not yet
          supported.
        - Only 2D spectra E(f,d) are currently supported.

    """
    # If grid reshape into site, otherwise ensure there is site dim to iterate over
    dset = self._check_and_stack_dims()

    # When prescribing bnds, SWAN doesn't like dir>270
    if dircap_270:
        direc = dset[attrs.DIRNAME].values
        direc[direc > 270] = direc[direc > 270] - 360
        dset = dset.update({attrs.DIRNAME: direc}).sortby("dir", ascending=False)

    darray = dset[attrs.SPECNAME]
    is_time = attrs.TIMENAME in darray.dims

    # Instantiate swan object
    try:
        x = dset.lon.values
        y = dset.lat.values
    except NotImplementedError(
        "lon/lat not found in dset, cannot dump SWAN file without locations"
    ):
        raise
    sfile = SwanSpecFile(
        filename,
        freqs=darray.freq,
        dirs=darray.dir,
        time=is_time,
        x=x,
        y=y,
        append=append,
        id=id,
    )

    # Dump each timestep
    if is_time:
        for t in darray.time:
            darrout = darray.sel(time=t, method="nearest")
            if darrout.time.size == 1:
                sfile.write_spectra(
                    darrout.transpose(
                        attrs.SITENAME, attrs.FREQNAME, attrs.DIRNAME
                    ).values,
                    time=to_datetime(t.values),
                )
            elif unique_times:
                sfile.write_spectra(
                    darrout.isel(time=-1)
                    .transpose(attrs.SITENAME, attrs.FREQNAME, attrs.DIRNAME)
                    .values,
                    time=to_datetime(t.values),
                )
            else:
                for it, tt in enumerate(darrout.time):
                    sfile.write_spectra(
                        darrout.isel(time=it)
                        .transpose(attrs.SITENAME, attrs.FREQNAME, attrs.DIRNAME)
                        .values,
                        time=to_datetime(t.values),
                    )
    else:
        sfile.write_spectra(
            darray.transpose(attrs.SITENAME, attrs.FREQNAME, attrs.DIRNAME).values
        )
    sfile.close()
Example #3
0
def read_swans(fileglob, ndays=None, int_freq=True, int_dir=False, dirorder=True, ntimes=None):
    """Read multiple swan files into single Dataset.

    Args:
        - fileglob (str, list): glob pattern specifying files to read.
        - ndays (float): number of days to keep from each file, choose None to
          keep entire period.
        - int_freq (ndarray, bool): frequency array for interpolating onto:
            - ndarray: 1d array specifying frequencies to interpolate onto.
            - True: logarithm array is constructed such that fmin=0.0418 Hz,
              fmax=0.71856 Hz, df=0.1f.
            - False: No interpolation performed in frequency space.
        - int_dir (ndarray, bool): direction array for interpolating onto:
            - ndarray: 1d array specifying directions to interpolate onto.
            - True: circular array is constructed such that dd=10 degrees.
            - False: No interpolation performed in direction space.
        - dirorder (bool): if True ensures directions are sorted.
        - ntimes (int): use it to read only specific number of times, useful
          for checking headers only.
        
    Returns:
        - dset (SpecDataset): spectra dataset object read from file with
          different sites and cycles concatenated along the 'site' and 'time'
          dimensions.

    Note:
        - If multiple cycles are provided, 'time' coordinate is replaced by
          'cycletime' multi-index coordinate.
        - If more than one cycle is prescribed from fileglob, each cycle must
          have same number of sites.
        - Either all or none of the spectra in fileglob must have tabfile
          associated to provide wind/depth data.
        - Concatenation is done with numpy arrays for efficiency.

    """
    swans = sorted(fileglob) if isinstance(fileglob, list) else sorted(glob.glob(fileglob))
    assert swans, 'No SWAN file identified with fileglob %s' % (fileglob)

    # Default spectral basis for interpolating
    if int_freq == True:
        int_freq = [0.04118 * 1.1**n for n in range(31)]
    elif int_freq == False:
        int_freq = None
    if int_dir == True:
        int_dir = np.arange(0, 360, 10)
    elif int_dir == False:
        int_dir = None

    cycles    = list()
    dsets     = SortedDict()
    tabs      = SortedDict()
    all_times = list()
    all_sites = SortedDict()
    all_lons  = SortedDict()
    all_lats  = SortedDict()
    deps      = SortedDict()
    wspds     = SortedDict()
    wdirs     = SortedDict()

    for filename in swans:
        swanfile = SwanSpecFile(filename, dirorder=dirorder)
        times = swanfile.times
        lons = list(swanfile.x)
        lats = list(swanfile.y)
        sites = [os.path.splitext(os.path.basename(filename))[0]] if len(lons)==1 else np.arange(len(lons))+1
        freqs = swanfile.freqs
        dirs = swanfile.dirs

        if ntimes is None:
            spec_list = [s for s in swanfile.readall()]
        else:
            spec_list = [swanfile.read() for itime in range(ntimes)]

        # Read tab files for winds / depth
        if swanfile.is_tab:
            try:
                tab = read_tab(swanfile.tabfile).rename(columns={'dep': attrs.DEPNAME})
                if len(swanfile.times) == tab.index.size:
                    if 'X-wsp' in tab and 'Y-wsp' in tab:
                        tab[attrs.WSPDNAME], tab[attrs.WDIRNAME] = uv_to_spddir(tab['X-wsp'], tab['Y-wsp'], coming_from=True)
                else:
                    warnings.warn(
                        "Times in {} and {} not consistent, not appending winds and depth"
                        .format(swanfile.filename, swanfile.tabfile)
                    )
                    tab = pd.DataFrame()
                tab = tab[list(set(tab.columns).intersection((attrs.DEPNAME, attrs.WSPDNAME, attrs.WDIRNAME)))]
            except Exception as exc:
                warnings.warn(
                    "Cannot parse depth and winds from {}:\n{}".format(swanfile.tabfile, exc)
                )
        else:
            tab = pd.DataFrame()

        # Shrinking times
        if ndays is not None:
            tend = times[0] + datetime.timedelta(days=ndays)
            if tend > times[-1]:
                raise IOError('Times in %s does not extend for %0.2f days' % (filename, ndays))
            iend = times.index(min(times, key=lambda d: abs(d - tend)))
            times = times[0:iend+1]
            spec_list = spec_list[0:iend+1]
            tab = tab.loc[times[0]:tend] if tab is not None else tab

        spec_list = flatten_list(spec_list, [])

        # Interpolate spectra
        if int_freq is not None or int_dir is not None:
            spec_list = [interp_spec(spec, freqs, dirs, int_freq, int_dir) for spec in spec_list]
            freqs = int_freq if int_freq is not None else freqs
            dirs = int_dir if int_dir is not None else dirs

        # Appending
        try:
            arr = np.array(spec_list).reshape(len(times), len(sites), len(freqs), len(dirs))
            cycle = times[0]
            if cycle not in dsets:
                dsets[cycle] = [arr]
                tabs[cycle] = [tab]
                all_sites[cycle] = sites
                all_lons[cycle] = lons
                all_lats[cycle] = lats
                all_times.append(times)
                nsites = 1
            else:
                dsets[cycle].append(arr)
                tabs[cycle].append(tab)
                all_sites[cycle].extend(sites)
                all_lons[cycle].extend(lons)
                all_lats[cycle].extend(lats)
                nsites += 1
        except:
            if len(spec_list) != arr.shape[0]:
                raise IOError('Time length in %s (%i) does not match previous files (%i), cannot concatenate',
                    (filename, len(spec_list), arr.shape[0]))
            else:
                raise
        swanfile.close()

    cycles = dsets.keys()

    # Ensuring sites are consistent across cycles
    sites = all_sites[cycle]
    lons = all_lons[cycle]
    lats = all_lats[cycle]
    for site, lon, lat in zip(all_sites.values(), all_lons.values(), all_lats.values()):
        if (list(site) != list(sites)) or (list(lon) != list(lons)) or (list(lat) != list(lats)):
            raise IOError('Inconsistent sites across cycles in glob pattern provided')

    # Ensuring consistent tabs
    cols = set([frozenset(tabs[cycle][n].columns) for cycle in cycles for n in range(len(tabs[cycle]))])
    if len(cols) > 1:
        raise IOError('Inconsistent tab files, ensure either all or none of the spectra have associated tabfiles and columns are consistent')

    # Concat sites
    for cycle in cycles:
        dsets[cycle] = np.concatenate(dsets[cycle], axis=1)
        deps[cycle] = np.vstack([tab[attrs.DEPNAME].values for tab in tabs[cycle]]).T if attrs.DEPNAME in tabs[cycle][0] else None
        wspds[cycle] = np.vstack([tab[attrs.WSPDNAME].values for tab in tabs[cycle]]).T if attrs.WSPDNAME in tabs[cycle][0] else None
        wdirs[cycle] = np.vstack([tab[attrs.WDIRNAME].values for tab in tabs[cycle]]).T if attrs.WDIRNAME in tabs[cycle][0] else None

    time_sizes = [dsets[cycle].shape[0] for cycle in cycles]

    # Concat cycles
    if len(dsets) > 1:
        dsets = np.concatenate(dsets.values(), axis=0)
        deps = np.concatenate(deps.values(), axis=0) if attrs.DEPNAME in tabs[cycle][0] else None
        wspds = np.concatenate(wspds.values(), axis=0) if attrs.WSPDNAME in tabs[cycle][0] else None
        wdirs = np.concatenate(wdirs.values(), axis=0) if attrs.WDIRNAME in tabs[cycle][0] else None
    else:
        dsets = dsets[cycle]
        deps = deps[cycle] if attrs.DEPNAME in tabs[cycle][0] else None
        wspds = wspds[cycle] if attrs.WSPDNAME in tabs[cycle][0] else None
        wdirs = wdirs[cycle] if attrs.WDIRNAME in tabs[cycle][0] else None

    # Creating dataset
    times = flatten_list(all_times, [])
    dsets = xr.DataArray(
        data=dsets,
        coords=OrderedDict(((attrs.TIMENAME, times), (attrs.SITENAME, sites), (attrs.FREQNAME, freqs), (attrs.DIRNAME, dirs))),
        dims=(attrs.TIMENAME, attrs.SITENAME, attrs.FREQNAME, attrs.DIRNAME),
        name=attrs.SPECNAME,
    ).to_dataset()

    dsets[attrs.LATNAME] = xr.DataArray(data=lats, coords={attrs.SITENAME: sites}, dims=[attrs.SITENAME])
    dsets[attrs.LONNAME] = xr.DataArray(data=lons, coords={attrs.SITENAME: sites}, dims=[attrs.SITENAME])

    if wspds is not None:
        dsets[attrs.WSPDNAME] = xr.DataArray(data=wspds, dims=[attrs.TIMENAME, attrs.SITENAME],
                                       coords=OrderedDict(((attrs.TIMENAME, times), (attrs.SITENAME, sites))))
        dsets[attrs.WDIRNAME] = xr.DataArray(data=wdirs, dims=[attrs.TIMENAME, attrs.SITENAME],
                                       coords=OrderedDict(((attrs.TIMENAME, times), (attrs.SITENAME, sites))))
    if deps is not None:
        dsets[attrs.DEPNAME] = xr.DataArray(data=deps, dims=[attrs.TIMENAME, attrs.SITENAME],
                                      coords=OrderedDict(((attrs.TIMENAME, times), (attrs.SITENAME, sites))))

    # Setting multi-index
    if len(cycles) > 1:
        dsets = dsets.rename({attrs.TIMENAME: 'cycletime'})
        cycletime = zip(
            [item for sublist in [[c]*t for c,t in zip(cycles, time_sizes)] for item in sublist],
            dsets.cycletime.values
        )
        dsets['cycletime'] = pd.MultiIndex.from_tuples(cycletime, names=[attrs.CYCLENAME, attrs.TIMENAME])
        dsets['cycletime'].attrs = attrs.ATTRS[attrs.TIMENAME]

    set_spec_attributes(dsets)
    if 'dir' in dsets and len(dsets.dir)>1:
        dsets[attrs.SPECNAME].attrs.update({'_units': 'm^{2}.s.degree^{-1}', '_variable_name': 'VaDens'})
    else:
        dsets[attrs.SPECNAME].attrs.update({'units': 'm^{2}.s', '_units': 'm^{2}.s', '_variable_name': 'VaDens'})

    return dsets
Example #4
0
def to_swan(self,
            filename,
            append=False,
            id='Created by wavespectra',
            unique_times=False):
    """Write spectra in SWAN ASCII format.

    Args:
        - filename (str): str, name for output SWAN ASCII file.
        - append (bool): if True append to existing filename.
        - id (str): used for header in output file.
        - unique_times (bool): if True, only last time is taken from
          duplicate indices.

    Note:
        - Only datasets with lat/lon coordinates are currently supported.
        - Extra dimensions other than time, site, lon, lat, freq, dim not yet
          supported.
        - Only 2D spectra E(f,d) are currently supported.

    """
    # If grid reshape into site, otherwise ensure there is site dim to iterate over
    dset = self._check_and_stack_dims()

    darray = dset[attrs.SPECNAME]
    is_time = attrs.TIMENAME in darray.dims

    # Instantiate swan object
    try:
        x = dset.lon.values
        y = dset.lat.values
    except NotImplementedError(
            'lon/lat not found in dset, cannot dump SWAN file without locations'
    ):
        raise
    sfile = SwanSpecFile(filename,
                         freqs=darray.freq,
                         dirs=darray.dir,
                         time=is_time,
                         x=x,
                         y=y,
                         append=append,
                         id=id)

    # Dump each timestep
    if is_time:
        for t in darray.time:
            darrout = darray.sel(time=t, method='nearest')
            if darrout.time.size == 1:
                sfile.write_spectra(darrout.transpose(attrs.SITENAME,
                                                      attrs.FREQNAME,
                                                      attrs.DIRNAME).values,
                                    time=to_datetime(t.values))
            elif unique_times:
                sfile.write_spectra(darrout.isel(time=-1).transpose(
                    attrs.SITENAME, attrs.FREQNAME, attrs.DIRNAME).values,
                                    time=to_datetime(t.values))
            else:
                for it, tt in enumerate(darrout.time):
                    sfile.write_spectra(darrout.isel(time=it).transpose(
                        attrs.SITENAME, attrs.FREQNAME, attrs.DIRNAME).values,
                                        time=to_datetime(t.values))
    else:
        sfile.write_spectra(
            darray.transpose(attrs.SITENAME, attrs.FREQNAME,
                             attrs.DIRNAME).values)
    sfile.close()