SFF++ library: reading and writing SFF from C++
SFF++ library: reading and writing SFF from C++ Documentation
Author
Thomas Forbriger
Since
December 2003
Date
July 2013

The library is designed to provide a tool for SFF (Stuttgart File Format) writing and reading. It uses libgsexx for the GSE2 layer of SFF. While libgsexx does not explicitely use an array class for time series, libsffxx provides whole array reading and writing functionality. For this purpose the library uses code from libaff.

A major advantage of the library is a feature of libgsexx. With this code you can deal with time series of arbitrary length. In particular no temporary character buffer for CM6 decoding and encoding is needed (like it is in the Fortran library). This means, on the other hand, that the library has no information to fill the nchar field of the DAST line.

Warning
The library will write -1 to the nchar field of the DAST line. You will need an updated version (2.00 or higher) of libstuff or libsff (Fortran version) to read data files written with libsffxx to Fortran code.

Contents


Dependencies

The library depends on other code libaries. They are libtime, libgsexx, libaff, and STL. The packages for libtime, libgsexx, and libaff should be available at the place you obtained libsffxx from.

libtime

You must link against libtime++.a

Date values in WID2 and SRCE structs are stored in libtime::TAbsoluteTime
objects.
The library uses the reading functions of libtime to decode time strings.
The header sffxx.h includes libtime++.h

libgsexx

You must link against libgsexx.a

The GSE2 layer of the library relies on the code provided by libgsexx.
The header sffxx.h includes gsexx.h

libaff

You must link against libaff.a

The reading and writing classes sff::InputWaveform and sff::OutputWaveform
as well as the sff::TraceHeader::scanseries member function deal with
series type containers as defined in libaff.
The header sffxx.h includes aff/series.h and aff/iterator.h

(Standard Template Libarary)

The sff::FREE block struct uses an STL list of strings.

SFF format definition

SFF (Stuttgart File Format) is based on the definition of the GSE2.0 format. It supplements GSE2.0 by header elements the provide source specification (SRCE lines), receiver information (INFO lines), and comments (FREE blocks). It further provides an amplitude scaling factor (DAST line) to support synthetic (non-integer) data.

The Definition of the Stuttgart File Format from file sff.doc in package libsff is provided on a separate page: Definition of the Stuttgart File Format

The functions sff::WIDXline provide an extended WID format which breaks the SFF format standard.


Structure of the library modules

SFF element structs

For each of the structural elements in an SFF file there exists a C++ struct to represent the file contents:

For each relevant entry in the data file there exists a corresponding member data field in the struct. The member data are read and modified by directly accessing the data fields. However some member functions are provided together with the structs. These member functions set default values (constructors), provide an ASCII representation of the values as has to be written to the file (line and write functions) and support reading from a file (read functions). The WID2 struct will be mapped to a WID2 struct from the libgsexx upon reading and writing. There ist no struct for the DAT2 line and the CHK2 line, since both are written and read through the DAT2read and DAT2write class from libgsexx.

Header Classes

The classes sff::FileHeader and sff::TraceHeader glue SFF structs to a compount. Header data is filled either by creating a class object through one of the constructors or through on of the file reading member functions. The member data fields (SFF structs) are only provided for reading access. The Header classes keep track on optional fields. They are selected by using and appropriate constructor or by reading a file. Member functions like sff::FileHeader::hasfree() tell you whether an optional element is used and present.

While an sff::FileHeader can immediately be used for reading from and writing to a data file, this is not the case for sff::TraceHeader. The structure defintion for SFF files wants the optional trace header fields to be placed after the the trace data (after the CHK2 line). This means that the trace header must be written together with the data through an sff::OutputWaveform object. Use an sff::InputWaveform object for the same reason upon reading.

Waveform I/O tools

The class templates sff::OutputWaveform and sff::InputWaveform are used for writing and reading SFF data together with the trace header. They expect a time series to be contained in an aff::Series object and are defined as class templates. You may select your own numerical type for the aff::Series container. Use sff::SkipWaveform to skip the trace data upon reading. The sff::SkipWaveform will still provide the header information for the skipped data block.

Exceptions

The library uses the exception class GSE2::Terror from libgsexx to indicate error conditions.


HOWTO use the library

Examples for the usage of the library modules can be found in sfftest.cc

I/O streams for SFF data

There are input and output streams available for SFF data. The output stream class template sff::SFFostream is available within this library through sffostream.h Using this output stream, you must always write the time series itself first! Header lines are written to the stream after the time series has been written.

A stream for reading SFF data is available within libdatreadxx.a. A short tutorial for reading and writing SFF data with streams is given there too.

Reading from an SFF data file

First open a file input stream.

std::ifstream is("filename");

Then read the file header.

sff::FileHeader fileheader(is);

Optional blocks are read as well and you may check for them and access them through the member functions of sff::FileHeader. Next cycle through the traces as desired. Each trace is read by creating an object of type sff::InputWaveform or sff::SkipWaveform:

sff::InputWaveform<Tseries> inputwaveform(is);
sff::SkipWaveform<Tseries> skipwaveform(is);

Here Tseries should be an appropriate libaff container like

typedef aff::Series<double> Tseries;

You may read to existing objects as well:

is >> inputwaveform;
is >> skipwaveform;

After reading each trace, you may access the trace header object through the sff::InputWaveform::header() or sff::SkipWaveform::header() member functions and the time series through the sff::InputWaveform::series() member function. You should check for more traces through the sff::InputWaveform::last() and sff::SkipWaveform::last() member functions. The sff::InputWaveform::valid() and sff::SkipWaveform::valid() member functions tell you whether the object already contains valid data, which is not the case if the object was created through its default constructor and has no data yet read into it.

Writing to an SFF data file

For writing an SFF data file first open an output file stream.

std::ofstream os("filename");

Create the elements to combine to an sff::FileHeader object.

sff::SRCE srceline;
srceline.cx=1.;
srceline.type="Hammerblow";
srceline.date=libtime::TAbsoluteTime("2003/12/31 10:12:00");
sff::FREE filefree;
filefree.lines.push_back("Example for a free block.");
filefree.lines.puch_back("another line...");

The create an sff::FileHeader from them.

sff::FileHeader fileheader(srceline,filefree);

or

sff::FileHeader fileheader;

in the minimalistic case of no optional blocks. The file header may be written directly through

os << fileheader;

No cycle through all traces. First create all header elements needed for each trace.

sff::INFO infoline;
infoline.cx=20.
infoline.nstacks=5;
sff::FREE tracefree;
tracefree.lines.push_back("A FREE block for a waveform");
sff::WID2 wid2line;
wid2line.station="BFO";
wid2line.channel="UGZ";
wid2line.instype="ET-19";
wid2line.nsamples=2048;
wid2line.dt=10.;

The create an sff::TraceHeader from them.

bool last=true;
sff::TraceHeader traceheader(wid2line,infoline,tracefree,last);

or

sff::TraceHeader traceheader(wid2line);

in the minimalistic case of no optional blocks. At this point you must specify whether this is the last trace in the file or not. If you omit the last flag, it is implicitely set to false.

The trace header together with the time series is written to os through an sff::OutputWaveform object, which may be instantiated just for this purpose:

os << sff::OutputWaveform<Tseries>(series, traceheader, sff::NM_maxdyn);

The sff::OutputWaveform object takes care about normalization of the time series samples provided through the aff::Series object passed as argument series. The way normalization is done, can be selected by passing on of the sff::Enormmode flags to the constructor of sff::OutputWaveform.