package com.isti.traceview.data;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.log4j.Logger;
import com.isti.traceview.TraceView;
import com.isti.traceview.data.TraceViewSacExportBuilder;
import edu.iris.Fissures.seed.app.Jseedr;
import edu.iris.Fissures.seed.builder.ExportBuilder;
import edu.iris.Fissures.seed.container.Blockette;
import edu.iris.Fissures.seed.container.Btime;
import edu.iris.Fissures.seed.container.SeedObject;
import edu.iris.Fissures.seed.container.Waveform;
import edu.iris.Fissures.seed.director.ExportDirector;
import edu.iris.Fissures.seed.director.ExportTemplate;
import edu.iris.Fissures.seed.director.SeedExportDirector;
import edu.iris.Fissures.seed.exception.BuilderException;
import edu.iris.Fissures.seed.exception.SeedException;
import edu.iris.Fissures.seed.util.Utility;
/**
* During parse phase seed is decoded, SAC file create in TMP data area for every found segment. After that we
* add to data module raw data provider for this SAC. Reasons for such solution is impossibility of using random access to
* seed data (library use streams) and desire not to store all seed contents in memory during parse phase.
*
* @author Max Kokoulin, ISTI
* @version 11/5/2008
*/
public class SourceFileSeed extends SourceFile implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(SourceFileSeed.class);
private boolean verboseMode = false; // set to true to turn Jseedr verbose mode ON
// when exporting
public SourceFileSeed(File file) {
super(file);
}
public FormatType getFormatType(){
return FormatType.SEED;
}
public Set<RawDataProvider> parse(DataModule dataModule) {
Set<RawDataProvider> ret = new HashSet<RawDataProvider>();
DataInputStream dataInputStream = null;
try {
Jseedr jseedr = new Jseedr();
jseedr.setVerboseMode(verboseMode); // set verbosity for jseedr
// establish a what kind of file we are importing
jseedr.getNewImportDirector("SEED");
ExportTemplate template = jseedr.getNewTemplate();
dataInputStream = new DataInputStream(new FileInputStream(getFile()));
jseedr.importFrom(dataInputStream);
ExportBuilder exportBuilder = new TraceViewSacExportBuilder();
ExportDirector exportDirector = new SeedExportDirector();
exportDirector.assignContainer(jseedr.getContainer());
((TraceViewSacExportBuilder) exportBuilder).splitToFiles(verboseMode);
exportDirector.assignBuilder(exportBuilder);
exportDirector.assignTemplate(template);
exportDirector.fillTemplate(null);
exportDirector.construct();
} catch (Exception e) {
logger.error("Exception:", e);
} finally {
try {
dataInputStream.close();
} catch (IOException e) {
// do nothing
logger.error("IOException:", e);
}
}
return ret;
}
public void load(Segment segment){
}
}
/**
* Mainly borrowed from JavaSEED SacExportBuilder, but slightly modified - unfortunately, it's possible to
* change behavior only in this way, so as original SacSplitFilter class is private.
* Parse seed volume and generate SAC file for every found channel in configured library temporary storage area, then add
* this files as data sources.
*
* Concrete Builder class for exporting Blockette objects from the SeedObjectContainer to the SAC
* file format. Capable of single output stream or multi-file output.
* <p>
* Derived from code developed by:<br>
* Chris Laughbon<br>
* Doug Neuhauser<br>
* Allen Nance<br>
* Dennis O'Neill
*
* @author Robert Casey, IRIS DMC
* @version 11/5/2004
*/
class TraceViewSacExportBuilder extends ExportBuilder {
private static final Logger logger = Logger.getLogger(TraceViewSacExportBuilder.class);
/**
* Create a new Sac Export Buider.
*/
public TraceViewSacExportBuilder() {
setDefaultMode(); // set up the default SAC output mode (only current option)
// set some default values
logicalRecordLength = 1024;
physicalRecordLength = 2000000000; // set to impossibly high value
logicalRecords = new Vector<LogicalRecord>(8, 8);
exportMold = new Vector<Object>(8, 8);
recordPadding = (byte) 0; // use nulls for padding, although it probably won't be used
scriptNesting = new int[8];
nestingScore = new int[scriptNesting.length];
builderType = "SAC"; // indicate the type of builder we are
stationList = new Vector<SacStation>(8, 8); // set up station list vector
}
// public methods
/**
* This String represents the script pattern for binary SAC volume. Each element is separated by
* a comma.
*
* @see edu.iris.Fissures.seed.builder.ExportBuilder#getNext()
*/
public void setDefaultMode() {
scriptString = new String("^,A,(,50,(,52,(,58,),),),71,v,(,999,),B");
genScriptArray(); // generate array from script string
}
/**
* Get the end time (as a Btime object). Projected from the start time based on the number of
* samples and the calculated sample rate.
*/
public static Btime getEndTime(Btime startTime, int numSamples, int srFactor, int srMult) throws Exception {
// get the sample rate
double true_rate;
if ((srFactor * srMult) == 0.0) {
true_rate = 10000.0;
} else {
true_rate = (Math.pow((double) (Math.abs(srFactor)), (double) (srFactor / Math.abs(srFactor))) * Math.pow((double) (Math.abs(srMult)),
(double) (srMult / Math.abs(srMult))));
}
double ttSeconds = ((double) numSamples) / true_rate * 10000.0; // find the number of
// ten-thousands of seconds
return startTime.projectTime(ttSeconds); // the end time is the projection from the start
// time
}
/**
* Output to multiple SAC files. Pipe the OutputStream assigned to this Builder through a filter
* that separates SAC header/data groups into separate output files. All files are written to
* local directory. Set the verbose flag to true to print file progress to stderr.
*/
public void splitToFiles(boolean verboseFlag) throws Exception {
// close the current output stream in the builder
close();
// make a new output stream connected to a pipe
PipedOutputStream pout = new PipedOutputStream();
DataOutputStream dout = new DataOutputStream(pout);
// set up an input stream connected to the pipe
PipedInputStream pin = new PipedInputStream(pout);
// open this new output stream in the builder
open(dout);
// start up the file split filter
splitter = new SacSplitFilter(pin, verboseFlag); // this class extends Thread
splitter.start(); // start filter thread
}
/**
* Force data encoding of data records. Forces the encoding of the data records to be of this
* type, as typified by the assigned string -- see SeedEncodingResolver.java:encodingArray[] for
* encoding types.
*/
public void setEncoding(String s) {
fixedEncoding = s;
}
// protected methods
/**
* No function performed. Overrides ExportBuilder method. SAC format does not need padding.
*/
protected void padLogical() {
// override parent class method...
// do nothing here -- SAC format does not need padding
}
/**
* No function performed. Overrides ExportBuilder method. SAC format does not need padding.
*/
protected void padPhysical() {
// override parent class method...
// do nothing here -- SAC format does not need padding
}
/**
* Convert SEED object info to SAC orientation.
*/
protected void packToRecord() throws Exception {
// refresh logicalPerPhysical value
logicalPerPhysical = physicalRecordLength / logicalRecordLength;
if (exportMold == null) {
throw new BuilderException("null export mold");
}
if (exportMold.size() == 0) {
return; // empty export mold, then do nothing.
}
// our objects arrive in exportMold...
Blockette blk = (Blockette) exportMold.get(0); // get the first blockette object
// System.err.println("DEBUG: blk lookupId: " + blk.getLookupId());
// System.err.println("DEBUG: export mold size: " + exportMold.size());
int bType = blk.getType(); // get the blockette type
// System.err.println("DEBUG: blk type: " + bType);
if (bType == 50) {
// when we get a station blockette, tabulate the station name and
// network code. Add this new object to the station list
SacStation newStation = new SacStation();
//
newStation.stationName = blk.toString(3);
newStation.startEffTime = new Btime(blk.toString(13));
newStation.endEffTime = new Btime(blk.toString(14));
newStation.networkCode = blk.toString(16);
//
stationList.add(newStation); // add station info to the list vector
currentStation = newStation; // handle to current station
} else if (bType == 52) {
// when we get a channel blockette, associate it with the station
// and record appropriate info
SacChannel newChannel = new SacChannel();
//
newChannel.channelName = blk.toString(4);
newChannel.locationId = blk.toString(3);
newChannel.latitude = Float.parseFloat(blk.toString(10));
newChannel.longitude = Float.parseFloat(blk.toString(11));
newChannel.elevation = Float.parseFloat(blk.toString(12));
newChannel.depth = Float.parseFloat(blk.toString(13));
newChannel.azimuth = Float.parseFloat(blk.toString(14));
newChannel.dip = Float.parseFloat(blk.toString(15));
newChannel.sampleRate = Float.parseFloat(blk.toString(18));
newChannel.startEffTime = new Btime(blk.toString(22));
newChannel.endEffTime = new Btime(blk.toString(23));
//
if (currentStation == null)
throw new BuilderException("got a channel blockette before a station blockette");
currentStation.channels.add(newChannel); // add channel info to the current station's
// list
currentChannel = newChannel; // handle to current channel
} else if (bType == 58) {
if (Integer.parseInt(blk.toString(3)) == 0) { // check to see that this is a Stage 0
// (sensitivity) blockette
if (currentChannel == null)
throw new BuilderException("got a sensitivity blockette before a channel blockette");
currentChannel.scale = Float.parseFloat(blk.toString(4));
}
} else if (bType == 71) {
// if this is the event hypocenter blockette, then take down the event
// info so we can include it in the SAC header
sacEvent = new SacEvent(); // assign object to global variable
//
sacEvent.eventTime = new Btime(blk.toString(3));
sacEvent.eventLat = Float.parseFloat(blk.toString(5));
sacEvent.eventLon = Float.parseFloat(blk.toString(6));
sacEvent.eventDep = Float.parseFloat(blk.toString(7));
} else if (bType == 999) {
// check to see if there is waveform data attached...if not, then we
// are not interested in this record
Waveform thisWaveform = blk.getWaveform();
if (thisWaveform == null) {
// System.err.println("DEBUG: thisWaveform == null...do nothing");
exportMold.clear(); // clear the export mold
return; // do nothing else
}
// get data record parameters
dataQuality = (byte) blk.toString(2).charAt(0); // get just the first character
stationName = blk.toString(4);
locationId = blk.toString(5);
channelName = blk.toString(6);
networkCode = blk.toString(7);
startTime = new Btime(blk.toString(8));
numSamples = Integer.parseInt(blk.toString(9));
srFactor = Integer.parseInt(blk.toString(10));
srMult = Integer.parseInt(blk.toString(11));
// check to see if this data record is continuous with the previous
prevEndTime = endTime; // save the previous end time
endTime = getEndTime(startTime, numSamples, srFactor, srMult);
long timeDiff = startTime.diffSeconds(prevEndTime);
// System.err.println("DEBUG: startTime = " + startTime);
// System.err.println("DEBUG: endTime = " + endTime);
// System.err.println("DEBUG: timeDiff = " + timeDiff);
if (timeDiff < -1 || timeDiff > 1) { // if greater than a difference of 1 second,
// then this trace ends
// end the current data record. also ends the current file in multi-file output
// mode.
// System.err.println("DEBUG: triggered end of data trace...writing to output...");
volumeFinish();
// reset sample count
sampleCount = 0;
// reset the last sample value
lastSampleValue = 0.0F;
// reset number of logical records stored
logicalPerPhysical = physicalRecordLength / logicalRecordLength;
// generate a new (header) logical record.
startNewLogical(null, false);
// keep a separate marker on this object for future updates.
headerRecord = logicalRecord;
// match data record parameters to SacStation and SacChannel objects.
SacStation currentStation = null;
int stationListSize = stationList.size();
for (int i = 0; i < stationListSize; i++) { // find station header object
//(SacStation)
currentStation = stationList.get(i);
if (currentStation.stationName.equals(stationName) && currentStation.networkCode.equals(networkCode)
&& currentStation.endEffTime.diffSeconds(startTime) >= 0 && currentStation.startEffTime.diffSeconds(endTime) <= 0)
break; // found match
currentStation = null;
}
if (currentStation == null)
throw new BuilderException("data record " + stationName + "/" + networkCode + "/" + channelName + "/" + locationId
+ " without station header info.");
SacChannel currentChannel = null;
int channelsSize = currentStation.channels.size();
for (int i = 0; i < channelsSize; i++) {
//(SacChannel)
currentChannel = currentStation.channels.get(i);
if (currentChannel.channelName.equals(channelName) && currentChannel.locationId.equals(locationId)
&& currentChannel.endEffTime.diffSeconds(startTime) >= 0 && currentChannel.startEffTime.diffSeconds(endTime) <= 0)
break; // found match
currentChannel = null;
}
if (currentChannel == null)
throw new BuilderException("data record " + stationName + "/" + networkCode + "/" + channelName + "/" + locationId
+ " without channel header info.");
// write header information in proper fields.
int wordNum = 0; // this will be the 32-bit word we are writing to
float wordVal = 0.0F; // this will be the float value we are writing
// DELTA -- word 0
if (currentChannel.sampleRate == 0.0F)
throw new BuilderException("Sample rate for " + stationName + "/" + networkCode + "/" + channelName + "/" + locationId
+ " has a value of zero.");
wordVal = 1 / currentChannel.sampleRate;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// SCALE -- word 3
wordNum = 3;
wordVal = currentChannel.scale;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// B -- word 5
wordNum = 5;
wordVal = 0.0F;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// O -- word 7
if (sacEvent != null) {
wordNum = 7;
wordVal = sacEvent.eventTime.diffSeconds(startTime);
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
}
// INTERNAL (1) -- word 9
// set to fixed value of 2
wordNum = 9;
wordVal = 2.0F;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// STLA -- word 31
wordNum = 31;
wordVal = currentChannel.latitude;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// STLO -- word 32
wordNum = 32;
wordVal = currentChannel.longitude;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// STEL -- word 33
wordNum = 33;
wordVal = currentChannel.elevation;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// STDP -- word 34
wordNum = 34;
wordVal = currentChannel.depth;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// EVLA -- word 35
if (sacEvent != null) {
wordNum = 35;
wordVal = sacEvent.eventLat;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// EVLO -- word 36
wordNum = 36;
wordVal = sacEvent.eventLon;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// EVDP -- word 38
wordNum = 38;
wordVal = sacEvent.eventDep;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
}
// CMPAZ -- word 57
wordNum = 57;
wordVal = currentChannel.azimuth;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// CMPINC -- word 58
wordNum = 58;
wordVal = currentChannel.dip + 90.0F;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// NZYEAR -- word 70
StringTokenizer timeTok = new StringTokenizer(startTime.toString(), ",:.");
int wordInt = 0; // now inserting int values
wordNum = 70;
if (timeTok.hasMoreTokens())
wordInt = Integer.parseInt(timeTok.nextToken());
else
wordInt = 1900;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// NZJDAY -- word 71
wordNum = 71;
if (timeTok.hasMoreTokens())
wordInt = Integer.parseInt(timeTok.nextToken());
else
wordInt = 1;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// NZHOUR -- word 72
wordNum = 72;
if (timeTok.hasMoreTokens())
wordInt = Integer.parseInt(timeTok.nextToken());
else
wordInt = 0;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// NZMIN -- word 73
wordNum = 73;
if (timeTok.hasMoreTokens())
wordInt = Integer.parseInt(timeTok.nextToken());
else
wordInt = 0;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// NZSEC -- word 74
wordNum = 74;
if (timeTok.hasMoreTokens())
wordInt = Integer.parseInt(timeTok.nextToken());
else
wordInt = 0;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// NZMSEC -- word 75
wordNum = 75;
if (timeTok.hasMoreTokens())
wordInt = (Integer.parseInt(timeTok.nextToken()) / 10); // milliseconds
else
wordInt = 0;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// NVHDR -- [INTERNAL (4)] -- word 76
// fixed value of 6
wordNum = 76;
wordInt = 6;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// INTERNAL (5) -- word 77
// fixed value of 0
wordNum = 77;
wordInt = 0;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// INTERNAL (6) -- word 78
// fixed value of 0
wordNum = 78;
wordInt = 0;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// IFTYPE -- word 85
wordNum = 85;
wordInt = 1; // default to this value
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// LEVEN -- word 105
wordNum = 105;
wordInt = 1; // default to logical TRUE
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// LCALDA -- word 108
// set to false at this time
wordNum = 108;
wordInt = 0;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
// KSTNM -- word 110
wordNum = 110;
byte[] wordChar = new byte[]{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; // now
// writing
// char
// array
System.arraycopy(stationName.getBytes(), 0, wordChar, 0, stationName.length()); // fit
// to 8
// char
// mold
System.arraycopy(wordChar, 0, headerRecord.contents, wordNum * 4, 8); // write to
// SAC
// header
// field
// KHOLE -- word 116
wordNum = 116;
wordChar = new byte[]{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
System.arraycopy(locationId.getBytes(), 0, wordChar, 0, locationId.length());
System.arraycopy(wordChar, 0, headerRecord.contents, wordNum * 4, 8);
// KUSER0 - word 144 - placeholder for data quality flag
wordNum = 144;
wordChar = new byte[]{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
wordChar[0] = dataQuality; // set the first byte only
System.arraycopy(wordChar, 0, headerRecord.contents, wordNum * 4, 8);
// KCMPNM -- word 150
wordNum = 150;
wordChar = new byte[]{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
System.arraycopy(channelName.getBytes(), 0, wordChar, 0, channelName.length());
System.arraycopy(wordChar, 0, headerRecord.contents, wordNum * 4, 8);
// KNETWK -- word 152
wordNum = 152;
wordChar = new byte[]{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
System.arraycopy(networkCode.getBytes(), 0, wordChar, 0, networkCode.length());
System.arraycopy(wordChar, 0, headerRecord.contents, wordNum * 4, 8);
//
// finally, note the position in this record before writing data section
headerRecord.position = (158 * 4); // header offset
} // end if new trace...
// in all cases...
// decompress/translate data values to a float array.
//
if (fixedEncoding != null)
thisWaveform.setEncoding(fixedEncoding); // force the encoding type
if (thisWaveform.getEncoding().equals("UNKNOWN")) {
throw new BuilderException("Waveform data encoding is UNKNOWN");
}
float[] dataValues;
try {
dataValues = thisWaveform.getDecodedFloats(lastSampleValue); // using
// lastSampleValue
// as bias for
// decoding
} catch (SeedException e) {
// SeedException is thrown if we cannot decode using the listed
// data encoding type.
// fall back to some default encoding and proceed forward with a
// printed warning.
String defaultEncoding = "Steim1";
StringBuilder message = new StringBuilder();
message.append("proceeding using default encoding " + defaultEncoding);
logger.error(message.toString(), e);
thisWaveform.setEncoding(defaultEncoding);
dataValues = thisWaveform.getDecodedFloats(lastSampleValue); // using
// lastSampleValue
// as bias for
// decoding
}
//
// transcribe float values to a byte array four times as large
byte[] dataBytes = new byte[4 * dataValues.length];
for (int i = 0; i < dataValues.length; i++) {
System.arraycopy(floatToBytes(dataValues[i]), 0, dataBytes, 4 * i, 4);
lastSampleValue = dataValues[i];
}
// append data values to logical record, keeping count of the number of points.
// generate new (data) logical record(s) as needed.
int dataValueOffset = 0;
while (dataValueOffset < dataBytes.length) { // while we still have data to write...
int writeLength = logicalRecordLength - logicalRecord.position; // how much can we
// write for this
// logical record?
if (writeLength > dataBytes.length - dataValueOffset)
writeLength = dataBytes.length - dataValueOffset; // last of data?
// System.err.println("DEBUG: dataValueOffset: " + dataValueOffset + ",
// logicalRecord.position: " + logicalRecord.position + ", writelength: " +
// writeLength);
System.arraycopy(dataBytes, dataValueOffset, logicalRecord.contents, logicalRecord.position, writeLength);
logicalRecord.position += writeLength; // increment logical record index
dataValueOffset += writeLength; // increment data source index
if (logicalRecord.position >= logicalRecordLength) {
// if we have a full logical record, then create a new data section logical
// record
// System.err.println("DEBUG: starting new logical record...");
startNewLogical(null, true);
}
}
// increment sample count by the number of samples (32-bit float words) added.
sampleCount += numSamples;
} else {
// we shouldn't be given anything else
throw new BuilderException("SAC builder given invalid Blockette object type: " + bType);
}
// clear out the export mold for the next incoming object(s)
exportMold.clear();
}
/**
* Implement export script triggers here.
*/
protected boolean checkTrigger(String s) throws Exception {
if (s.equals("A")) {
// set the starting value for data trace end time
endTime = new Btime("1900,001,00:00:00.0000");
} else if (s.equals("B")) {
} else if (s.equals("C")) {
} else
return false; // no trigger, return false
return true; // we have found a trigger, so return true
}
/**
* Finish up volume export operations. We have finished the last SAC 'file', so truncate the
* data section to the last sample and write the final values to the header.
*/
protected void volumeFinish() throws BuilderException {
if (logicalRecords.size() > 0) {
// fill in the Ending Value (E) and Number Points (NPTS) header values
//
if (sampleCount == 0)
throw new BuilderException("Number of samples for " + stationName + "/" + networkCode + "/" + channelName + "/" + locationId
+ " has a value of zero.");
if (headerRecord == null)
throw new BuilderException("null headerRecord during EOF header update");
if (currentChannel == null)
throw new BuilderException("null channel reference during EOF header update");
// E -- word 6
int wordNum = 6; // this will be the 32-bit word we are writing to
float wordVal = ((float) (sampleCount - 1)) / currentChannel.sampleRate;
System.arraycopy(floatToBytes(wordVal), 0, headerRecord.contents, wordNum * 4, 4);
// NPTS -- word 79
wordNum = 79;
int wordInt = sampleCount;
System.arraycopy(Utility.longToIntBytes((long) wordInt), 0, headerRecord.contents, wordNum * 4, 4);
//
// resize the last logical array to fit to just the end of the data.
byte[] resizedRecord = new byte[logicalRecord.position];
System.arraycopy(logicalRecord.contents, 0, resizedRecord, 0, logicalRecord.position);
logicalRecord.contents = resizedRecord;
// flag end of logical -- set logicalPerPhysical to be the current
// logical record count so that output writing stops here.
endOfLogical = true;
logicalPerPhysical = logicalRecords.size();
// System.err.println("DEBUG: volumeFinish: number of logical records: " +
// logicalPerPhysical);
}
}
/**
* Create a new logical/physical SAC record and add to logical record vector. SeedObject will be
* ignored and should be set to null. Continuation will be false to build a record with SAC
* header, true for just data section.
*/
@SuppressWarnings("unchecked") // ExportBuilder.class cannot be changed in JavaSeed.jar
protected void startNewLogical(SeedObject obj, boolean continuation) throws Exception {
LogicalRecord logicalRecord = new LogicalRecord(); // create a new logical record
if (continuation) {
// data record
System.arraycopy(getBlank(false), 0, logicalRecord.contents, 0, logicalRecordLength);
} else {
// header record
System.arraycopy(getBlank(true), 0, logicalRecord.contents, 0, logicalRecordLength);
}
logicalRecord.position = 0; // make sure the position is set to 0
logicalRecords.add(logicalRecord); // push logical record to vector
}
// private methods
/**
* Return a byte array representing a blank SAC record. If header is true, then the byte array
* returned is formatted for the SAC header. When false, it's intended for the data section.
*/
private byte[] getBlank(boolean header) throws BuilderException {
// we only want to generate these blanks once, then save the results
// to a global variable for reuse. If it's already been generated, then return
// the array, else generate it.
if (header) {
if (sacHeaderBlank != null)
return sacHeaderBlank;
} else {
if (sacDataBlank != null)
return sacDataBlank;
}
byte[] currentBlank = new byte[logicalRecordLength]; // we will write to this temp array
int position = 0;
//
// seed the new logical record with default values.
// format depends on whether we are looking at the header or data section.
// the following variables represent the number of 32-bit words.
int wordStartInteger = 0;
int wordStartLogical = 0;
int wordStartCharacter = 0;
int wordPosLargeChar = 0;
int wordStartFloat = 0;
if (header) { // if building SAC header...
wordStartInteger = 70; // where integer values begin
wordStartLogical = 105; // where logical values begin
wordStartCharacter = 110; // where character values begin
wordPosLargeChar = 112; // where large character value is located
wordStartFloat = 158; // where float (data section) values begin
}
int wordLogicalMax = logicalRecordLength / 4; // maximum number of 4-byte words allowed
if (wordStartFloat > wordLogicalMax)
throw new BuilderException("logical record length configuration too small: " + logicalRecordLength);
byte[] floatUndef = floatToBytes(-12345.0F); // undefined float
byte[] intUndef = Utility.longToIntBytes(-12345L); // undefined integer
byte[] intFalse = new byte[]{ 0, 0, 0, 0 }; // false logical
byte[] charUndef = new byte[]{ '-', '1', '2', '3', '4', '5', ' ', ' ' }; // 8 bytes (2
// words)
byte[] charUndef2 = new byte[]{ '-', '1', '2', '3', '4', '5', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; // 16
// bytes
// (4
// words)
for (int i = 0; i < wordStartInteger; i++) {
// write out float undefined values (header)
position = i * 4; // this is our current byte position
System.arraycopy(floatUndef, 0, currentBlank, position, 4); // write word at position i
}
for (int i = wordStartInteger; i < wordStartCharacter; i++) {
// write out integer undefined values (header)
position = i * 4; // this is our current byte position
if (i >= wordStartLogical) {
// logical field
System.arraycopy(intFalse, 0, currentBlank, position, 4); // write word at
// position i
} else {
// integer field
System.arraycopy(intUndef, 0, currentBlank, position, 4); // write word at
// position i
}
}
for (int i = wordStartCharacter; i < wordStartFloat; i += 2) {
// write out character undefined values (header)
// note we are incrementing by two words now...
position = i * 4; // this is our current byte position
if (i == wordPosLargeChar) // special case (KEVNM)
System.arraycopy(charUndef2, 0, currentBlank, position, 16); // special 4 word
// field
else if (i == wordPosLargeChar + 2)
continue; // skip this offset because of previous 4-word write...
else
System.arraycopy(charUndef, 0, currentBlank, position, 8); // 2 word field
}
for (int i = wordStartFloat; i < wordLogicalMax; i++) {
// write out float undefined values (beginning of data section)
position = i * 4; // this is our current byte position
System.arraycopy(floatUndef, 0, currentBlank, position, 4); // write word at position i
}
// now choose which array we return based on the boolean flag
if (header) {
sacHeaderBlank = currentBlank;
return sacHeaderBlank;
} else {
sacDataBlank = currentBlank;
return sacDataBlank;
}
}
/**
* Generate the export script array from the script string.
*/
private void genScriptArray() {
StringTokenizer expTok = new StringTokenizer(scriptString, ",");
exportScript = new String[expTok.countTokens()];
int i = 0;
while (expTok.hasMoreTokens())
exportScript[i++] = expTok.nextToken();
}
/**
* Convert float to 4-byte array.
*/
private byte[] floatToBytes(float f) {
long longVal = (long) Float.floatToIntBits(f);
return Utility.longToIntBytes(longVal);
}
// inner classes
/**
* Store station info for Sac Export.
*/
private class SacStation {
protected String stationName = null;
protected String networkCode = null;
protected Btime startEffTime = null;
protected Btime endEffTime = null;
protected Vector<SacChannel> channels = new Vector<SacChannel>(8, 8);
}
/**
* Store channel info for SAC export.
*/
private class SacChannel {
protected String channelName = null;
protected String locationId = null;
protected float latitude = 0.0F;
protected float longitude = 0.0F;
protected float elevation = 0.0F;
protected float depth = 0.0F;
protected float azimuth = 0.0F;
protected float dip = 0.0F;
protected float scale = 1.0F;
protected float sampleRate = 0.0F;
protected Btime startEffTime = null;
protected Btime endEffTime = null;
}
/**
* Store optional event info for SAC export.
*/
private class SacEvent {
protected Btime eventTime = null;
protected float eventLat = 0.0F;
protected float eventLon = 0.0F;
protected float eventDep = 0.0F;
}
/**
* Filter thread that separates SAC header/data groups into individual files with unique names.
*/
class SacSplitFilter extends Thread {
/**
* Create a split filter connected to a Piped Input Stream with optional verbosity.
*/
public SacSplitFilter(InputStream is, boolean verboseFlag) {
din = new DataInputStream(new BufferedInputStream(is));
verbose = verboseFlag;
}
/**
* Thread run call.
*/
public void run() {
try {
while (!interrupted()) {
float[] floatVal = new float[floatMax];
int[] intVal = new int[integerMax - floatMax];
byte[] byteVal = new byte[(headerMax - integerMax) * 4];
counter = 0; // word counter
while (counter < floatMax) { // read in header floats
// System.err.println("DEBUG: reading header floats");
floatVal[counter++] = din.readFloat();
}
while (counter < integerMax) { // read in header integers and logicals
// System.err.println("DEBUG: reading header ints");
intVal[counter - floatMax] = din.readInt();
counter++;
}
while (counter < headerMax) { // read in header characters
// System.err.println("DEBUG: reading header chars");
din.readFully(byteVal, (counter - integerMax) * 4, 4); // read 4 bytes at a
// time
counter++;
}
// now that we have the header in its entirety
// let's figure out the filename
// System.err.println("DEBUG: generating filename");
filenameBuffer = new StringBuffer("");
timeBuffer = new StringBuffer("");
filenameBuffer.append(intVal[70 - floatMax]); // NZYEAR
filenameBuffer.append(".");
timeBuffer.append(intVal[70 - floatMax]);
timeBuffer.append(",");
//
DecimalFormat df = new DecimalFormat("000"); // 3 digit zero-padded
filenameBuffer.append(df.format(intVal[71 - floatMax])); // NZJDAY
filenameBuffer.append(".");
timeBuffer.append(df.format(intVal[71 - floatMax]));
timeBuffer.append(" ");
//
df = new DecimalFormat("00"); // 2 digit zero-padded
filenameBuffer.append(df.format(intVal[72 - floatMax])); // NZHOUR
filenameBuffer.append(".");
timeBuffer.append(df.format(intVal[72 - floatMax]));
timeBuffer.append(":");
//
filenameBuffer.append(df.format(intVal[73 - floatMax])); // NZMIN
filenameBuffer.append(".");
timeBuffer.append(df.format(intVal[73 - floatMax]));
timeBuffer.append(":");
//
filenameBuffer.append(df.format(intVal[74 - floatMax])); // NZSEC
filenameBuffer.append(".");
timeBuffer.append(df.format(intVal[74 - floatMax]));
timeBuffer.append(".");
//
df = new DecimalFormat("000"); // 3 digit zero-padded
filenameBuffer.append(df.format(intVal[75 - floatMax])); // NZMSEC
filenameBuffer.append("0."); // extend msec by an extra decimal place
timeBuffer.append(df.format(intVal[75 - floatMax]));
timeBuffer.append("0 UT"); // extend msec extra decimal and add UT suffix
//
chanBuffer = new StringBuffer(""); // storing station/channel name here
// KNETWK -- first 2 chars
int byteOffset = (152 - integerMax) * 4;
String stringVal = (new String(byteVal, byteOffset, 2)).trim();
filenameBuffer.append(stringVal);
chanBuffer.append(stringVal);
filenameBuffer.append(".");
chanBuffer.append(".");
//
// KSTNM -- first 5 chars
byteOffset = (110 - integerMax) * 4;
stringVal = (new String(byteVal, byteOffset, 5)).trim();
filenameBuffer.append(stringVal);
chanBuffer.append(stringVal);
filenameBuffer.append(".");
chanBuffer.append(".");
//
// KHOLE -- first 2 chars
byteOffset = (116 - integerMax) * 4;
stringVal = (new String(byteVal, byteOffset, 2)).trim();
filenameBuffer.append(stringVal);
chanBuffer.append(stringVal);
filenameBuffer.append(".");
chanBuffer.append(".");
//
// KCMPNM -- first 3 chars
byteOffset = (150 - integerMax) * 4;
stringVal = (new String(byteVal, byteOffset, 3)).trim();
filenameBuffer.append(stringVal);
chanBuffer.append(stringVal);
filenameBuffer.append(".");
chanBuffer.append(",");
//
// KUSER0 -- quality code might be here
byteOffset = (144 - integerMax) * 4;
if (byteVal[byteOffset] == 'Q' || byteVal[byteOffset] == 'R' || byteVal[byteOffset] == 'D') {
filenameBuffer.append(new String(byteVal, byteOffset, 1));
} else {
// default to 'U' if quality unknown
filenameBuffer.append("U");
}
//
filenameBuffer.append(".SAC"); // the file suffix
//
// now we have the filename, let's open it
// 'dout' is our filehandle
this.open(filenameBuffer.toString());
// transcribe header info to the file first
for (int i = 0; i < floatVal.length; i++) {
dout.writeFloat(floatVal[i]); // float values
}
for (int i = 0; i < intVal.length; i++) {
dout.writeInt(intVal[i]); // int values
}
for (int i = 0; i < byteVal.length; i++) {
dout.writeByte(byteVal[i]); // int values
}
// transcribe data info
numPts = intVal[79 - floatMax]; // get the number of data points
// verbose output: meant to mimic what rdseed displays
if (verbose)
System.err.println("Writing " + chanBuffer.toString() + " " + numPts + " samples (binary), starting " + timeBuffer);
counter = 0; // reset our word counter
float[] dataVal = new float[8]; // store data values here temporarily
int readSize = 8;
while (counter < numPts) { // begin transcribing data points
// read/write data in groups of 8
readSize = 8;
if (numPts - counter < 8)
readSize = numPts - counter; // the last values
for (int j = 0; j < readSize; j++) {
dataVal[j] = din.readFloat(); // read
}
for (int j = 0; j < readSize; j++) {
dout.writeFloat(dataVal[j]); // write
}
counter += readSize;
}
this.close(); // close the current file
TraceView.getDataModule().addDataSource(new SourceFileSAC(new File(TraceView.getConfiguration().getDataTempPath() + File.separator + filenameBuffer)));
}
} catch (IOException e1) {
logger.error("I/O Error in SAC file splitter: ", e1);
}
}
/**
* Open a new file with the indicated filename.
*/
public void open(String filename) throws IOException {
dout = new DataOutputStream(new FileOutputStream(TraceView.getConfiguration().getDataTempPath() + File.separator + filename));
}
/**
* Close the output file.
*/
public void close() throws IOException {
dout.flush();
dout.close();
dout = null;
}
DataInputStream din = null;
DataOutputStream dout = null;
int counter = 0; // word counter
int floatMax = 70; // maximum byte count for reading floats
int integerMax = 110; // maximum byte count for reading integers/logical values
int headerMax = 158; // maximum number of bytes in SAC header
int numPts = 0; // number of data points in current SAC file
StringBuffer filenameBuffer; // store the generated filename here
StringBuffer chanBuffer; // store a copy of the network/station/location/channel name
// here
StringBuffer timeBuffer; // store a copy of the time string here
boolean verbose = false; // set to true to print to stderr the SAC traces we are writing
}
// instance variables
private String scriptString; // string to be translated to a builder script
private byte dataQuality;
private String stationName = null;
private String locationId = null;
private String channelName = null;
private String networkCode = null;
private int numSamples = 0;
private int srFactor = 0;
private int srMult = 0;
private Btime startTime = null;
private Btime endTime = null;
private Btime prevEndTime = null;
private byte[] sacHeaderBlank = null;
private byte[] sacDataBlank = null;
private Vector<SacStation> stationList = null;
private SacStation currentStation = null;
private SacChannel currentChannel = null;
private SacEvent sacEvent = null;
private LogicalRecord headerRecord = null;
private int sampleCount = 0;
private float lastSampleValue = 0.0F;
private String fixedEncoding = null;
private SacSplitFilter splitter = null;
}