/** * */ package com.isti.traceview.data; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Serializable; import java.util.Date; import java.util.SortedMap; import java.util.TreeMap; import org.apache.log4j.Logger; import com.isti.traceview.common.TimeInterval; /** * Represent continuous set of raw trace data without gaps belongs to same data source. A Seismic trace * therefore is defined as a sorted (by time) list of segments. * * @author Max Kokoulin */ public class Segment implements Externalizable, Cloneable { public static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(Segment.class); /** * Gap Tolerance - 1.0 is a gap of 2*sample rate */ private static double gapTolerance = 1.0; private int[] data = null; private int currentPos = 0; private long startTime; private double sampleRate; /** * Quantity of data values in the segment */ private int sampleCount; /** * Starting position of this segment in the data source */ private long startOffset; private ISource dataSource; /** * Offset of this segment in trace data serialized temporary file, if it exist */ private long startOffsetSerial; /** * Maximal data value in segment */ private int maxValue; /** * Minimal data value in segment */ private int minValue; /** * ordinal number segment's data source in raw data provider */ private int sourceSerialNumber; /** * Segment ordinal number in channel, differ from sourceSerialNumber as in this point of view * segment can lay in several data sources if it hasn't gaps between */ private int channelSerialNumber; /** * Sequential number of continue data area in trace, to which this segment belongs. * Similar to channelSerialNumber, but takes into account only gaps, not overlaps */ private int continueAreaNumber; private RawDataProvider rdp = null; // map of time-offset pairs for blocks to quick find block by time private SortedMap<Long, Long> blockMap = null; private transient BufferedRandomAccessFile dataStream = null; // MTH: Use to combine segments read with -t and -d within a single PlotDataProvider private boolean isLoaded = false; /** * @param dataSource * data source containing this segment * @param startOffset * segment starting offset in data source * @param startTime * segment data start time * @param sampleRate * segment data sample rate * @param sampleCount * count of samples in the segment * @param RDPserialNumber * ordinal number of segment in the data source */ public Segment(ISource dataSource, long startOffset, Date startTime, double sampleRate, int sampleCount, int RDPserialNumber) { this.dataSource = dataSource; this.startOffset = startOffset; this.startTime = startTime.getTime(); this.sampleCount = sampleCount; this.sampleRate = sampleRate; this.sourceSerialNumber = RDPserialNumber; this.maxValue = Integer.MIN_VALUE; this.minValue = Integer.MAX_VALUE; data = null; currentPos = 0; logger.debug("Created: " + this); } /** * Constructor to work during deserialization */ public Segment() { logger.debug("Created empty segment"); } /** * Getter of the property <tt>startTime</tt> * * @return segment data start time */ public Date getStartTime() { return new Date(startTime); } /** * Getter of the property <tt>endTime</tt> * * @return segment data end time */ public Date getEndTime() { long time = new Double((sampleRate * sampleCount)).longValue(); return new Date(getStartTime().getTime() + time); } /** * Gets ordinal number of segment in the data source */ public int getSourceSerialNumber() { return sourceSerialNumber; } /** * Sets ordinal number of segment in the data source */ public void setSourceSerialNumber(int serialNumber) { this.sourceSerialNumber = serialNumber; } /** * Sets ordinal number of segment in the trace, we count only gaps, not sources boundaries in * this case */ public int getChannelSerialNumber() { return channelSerialNumber; } /** * Gets ordinal number of segment in the trace, we count only gaps and overlaps, not sources boundaries in * this case */ public void setChannelSerialNumber(int serialNumber) { this.channelSerialNumber = serialNumber; } /** * Gets sequential number of continue data area in the trace, to which this segment belongs. * We takes into account only gaps, not overlaps in this case. */ public int getContinueAreaNumber() { return continueAreaNumber; } /** * Sets sequential number of continue data area in the trace, to which this segment belongs. * We takes into account only gaps, not overlaps in this case. */ public void setContinueAreaNumber(int serialNumber) { this.continueAreaNumber = serialNumber; } /** * @return raw data provider to which this segment belongs */ public RawDataProvider getRawDataProvider() { return rdp; } /** * @param rawDataProvider * raw data provider to which this segment belongs */ public void setRawDataProvider(RawDataProvider rawDataProvider) { this.rdp = rawDataProvider; } /** * MTH: Currently not using this since serialized data is being * {@literal read in dumpData() --> InitCache --> Segment.getData()} * * Load the int[] data from a .DATA file into this Segment data[] * Needed so that -T will work with existing serialized data */ public int[] loadDataInt() { int[] ret = null; if (dataStream == null) { logger.error("dataStream == null!! --> Exiting"); System.exit(0); } else { ret = new int[sampleCount]; try { dataStream.seek(startOffsetSerial); for (int i = 0; i < sampleCount; i++) { ret[i] = dataStream.readInt(); } } catch (IOException e) { logger.error("IOException:", e); } // Copy into this Segment's int[] data: data = new int[sampleCount]; System.arraycopy(ret, 0, data, 0, sampleCount); } return ret; } /** * Reads all data from loaded segment * * NOTE: Will add {@code ArrayList<Integer>} dataList constructor for SegmentData (for future use) */ public SegmentData getData() { if (dataStream == null) { return new SegmentData(startTime, sampleRate, sourceSerialNumber, channelSerialNumber, continueAreaNumber, data); } else { int[] ret = new int[sampleCount]; try { dataStream.seek(startOffsetSerial); for (int i = 0; i < sampleCount; i++) { ret[i] = dataStream.readInt(); } } catch (IOException e) { logger.error("IOException:", e); } return new SegmentData(startTime, sampleRate, sourceSerialNumber, channelSerialNumber, continueAreaNumber, ret); } } /** * Reads range of data from loaded segment */ public SegmentData getData(TimeInterval ti) { return getData(ti.getStart(), ti.getEnd()); } /** * returns array of data in requested time range, from loaded segment. * * @param start * start time in milliseconds * @param end * end time in milliseconds */ @SuppressWarnings("null") public SegmentData getData(double start, double end) { // lg.debug("startTime=" + startTime +", endTime=" + getEndTime().getTime()); int[] ret = null; int previous = Integer.MAX_VALUE; int next = Integer.MAX_VALUE; double startt = Math.max(startTime, start); double endt = Math.min(getEndTime().getTime(), end); int startIndex = new Double((startt - startTime) / sampleRate).intValue(); //int startIndex = new Long(Math.round(new Double((startt - startTime) / sampleRate))).intValue(); int endIndex = new Double((endt - startTime) / sampleRate).intValue(); if (startIndex != endIndex) { ret = new int[endIndex - startIndex]; logger.debug("Getting segment data: startindex " + startIndex + ", endindex " + endIndex); if (dataStream == null) { logger.debug("== dataStream == null --> Get points from RAM data[] " + "startTime=" + startTime + " endTime=" + getEndTime().getTime()); // we use internal data in the ram for (int i = startIndex; i < endIndex; i++) { ret[i - startIndex] = data[i]; } if (startIndex > 0) { previous = data[startIndex-1]; } if (endIndex < sampleCount) { next = data[endIndex]; } } else { // we use serialized data file logger.debug("== dataStream is NOT null --> Load points from dataStream.readInt() to data[] " + "startTime=" + startTime + " endTime=" + getEndTime().getTime()); try { if(startIndex>0){ dataStream.seek(startOffsetSerial + startIndex * 4 - 4); previous = dataStream.readInt(); } else { dataStream.seek(startOffsetSerial); } for (int i = startIndex; i < endIndex; i++) { ret[i - startIndex] = dataStream.readInt(); } if(endIndex<sampleCount){ next = dataStream.readInt(); } // MTH: Use this if we are in the -T mode and we need to load existing serialized data (from .DATA) if (com.isti.traceview.TraceView.getConfiguration().getDumpData()) { logger.debug("We are in -T dataDump mode --> read this Segment from dataStream"); if (data == null) { if (ret.length != sampleCount) { //System.out.format("== Segment.getData(): Warning: sampleCount=[%d pnts] BUT data.length=[%d pnts]\n", sampleCount, ret.length); logger.warn(String.format("sampleCount=[%d pnts] BUT data.length=[%d pnts]\n", sampleCount, ret.length)); } //data = new int[sampleCount]; data = new int[ret.length]; System.arraycopy(ret, 0, data, 0, ret.length); } else { //System.out.println("== Segment.getData(): We are in -T dataDump mode but data IS NOT null!!!"); logger.debug("We are in -T dataDump mode but data IS NOT null!!!"); } } } catch (IOException e) { logger.error("IOException:", e); } } } else { if (dataStream == null) { ret = new int[1]; ret[0] = data[startIndex]; if(startIndex>0) { previous = data[startIndex-1]; } if (endIndex<sampleCount) { next = data[endIndex]; } } else { try { if(startIndex>0){ dataStream.seek(startOffsetSerial + startIndex * 4 - 4); previous = dataStream.readInt(); } else { dataStream.seek(startOffsetSerial); } ret[0] = dataStream.readInt(); if(endIndex<sampleCount){ next = dataStream.readInt(); } } catch (IOException e) { logger.error("IOException:", e); } } } return new SegmentData(new Double(startTime + startIndex*sampleRate).longValue(), sampleRate, sourceSerialNumber, channelSerialNumber, continueAreaNumber, previous, next, ret); } /** * Loads segment data from memory from data source */ public void load() { dataSource.load(this); } /** * Adds sample to the end of segment data */ public synchronized void addDataPoint(int value) { if (data == null){ data = new int[sampleCount]; } data[currentPos++] = value; setMaxValue(value); setMinValue(value); } /** * Getter of the property <tt>sampleRate</tt> * * @return Returns the sample rate. */ public double getSampleRate() { return sampleRate; } /** * Getter of the property <tt>startOffset</tt> * * @return Starting position of this segment in the data source */ public long getStartOffset() { return startOffset; } /** * @return data source to which this segment belongs */ public ISource getDataSource() { return dataSource; } /** * Getter of the property <tt>sampleCount</tt> * * @return the count of samples in the segment data */ public int getSampleCount() { return sampleCount; } /** * Clears segment data */ public void drop() { for (int i = 0; i < currentPos; i++) { data[i] = 0; } data = null; currentPos = 0; } /** * Getter of the property <tt>maxValue</tt> * * @return maximum raw data value in the segment */ public int getMaxValue() { return maxValue; } /** * Setter of the property <tt>maxValue</tt> * * @param maxValue * The maxValue to set. */ public void setMaxValue(int maxValue) { if (maxValue > this.maxValue) { this.maxValue = maxValue; } } /** * Getter of the property <tt>minValue</tt> * * @return minimum raw data value in the segment */ public int getMinValue() { return minValue; } /** * Setter of the property <tt>minValue</tt> * * @param minValue * The minValue to set. */ public void setMinValue(int minValue) { if (minValue < this.minValue) { this.minValue = minValue; } } public void addBlockDescription(long startTime, long offset){ if(blockMap == null){ blockMap = new TreeMap<Long, Long>(); } blockMap.put(startTime, offset); } public String getBlockHeaderText(long time){ if(blockMap != null){ long blockStartTime = blockMap.headMap(time).lastKey(); return dataSource.getBlockHeaderText(blockMap.get(blockStartTime)); } else { return "<html>Block marks are unavailable for this file type</html>"; } } public String toString() { return "Segment: startTime " + TimeInterval.formatDate(new Date(startTime), TimeInterval.DateFormatType.DATE_FORMAT_NORMAL) + ", endTime " + TimeInterval.formatDate(new Date(new Double(startTime + sampleRate * sampleCount).longValue()), TimeInterval.DateFormatType.DATE_FORMAT_NORMAL) + ", sampleRate " + sampleRate + ", sampleCount " + sampleCount + ", startOffset " + startOffset + ", maxValue " + maxValue + ", minValue " + minValue + ", rdpNumber " + sourceSerialNumber + ", serialNumber " + channelSerialNumber + ", isLoaded=" + isLoaded + ";"; //+ ", serialNumber " + channelSerialNumber + ";"; } /** * Sets data stream to serialize this segment * * @param dataStream the new dataStream */ public void setDataStream(BufferedRandomAccessFile dataStream) { this.dataStream = dataStream; } // MTH: public BufferedRandomAccessFile getDataStream() { return dataStream; } /** * Special deserialization handler * * @param in * stream to deserialize object * @see Serializable * @throws IOException if thrown while reading ObjectInpu * @throws ClassNotFoundException if thrown while reading the dataSource object */ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { logger.debug("== ENTER"); dataSource = (ISource) in.readObject(); currentPos = in.readInt(); startTime = in.readLong(); sampleRate = in.readDouble(); sampleCount = in.readInt(); startOffset = in.readLong(); maxValue = in.readInt(); minValue = in.readInt(); startOffsetSerial = in.readLong(); sourceSerialNumber = in.readInt(); channelSerialNumber = in.readInt(); continueAreaNumber = in.readInt(); // we don't load serialized channel data at start time - we do it when we need it //data = new int[sampleCount]; //for (int i = 0; i < sampleCount; i++) { //data[i] = inData.readInt(); //MTH: This should be data[i] = dataStream.readInt(); logger.debug("== EXIT: Deserialized " + this); } /** * Special serialization handler * * @param out * stream to serialize this object * @see Serializable * @throws IOException if there are problems writing the serialized file */ public void writeExternal(ObjectOutput out) throws IOException { logger.debug("== Output the Segment to serial stream:"); logger.debug(" Segment:" + this.toString() ); logger.debug(" Segment: ObjectOutputStream:" + out.toString() ); logger.debug(" Segment: dataSource:" + dataSource ); logger.debug(" Segment: dataStream:" + dataStream ); logger.debug(" Segment: sampleCount:" + sampleCount ); out.writeObject(dataSource); out.writeInt(currentPos); out.writeLong(startTime); out.writeDouble(sampleRate); out.writeInt(sampleCount); out.writeLong(startOffset); out.writeInt(maxValue); out.writeInt(minValue); out.writeLong(dataStream.getFilePointer()); out.writeInt(sourceSerialNumber); out.writeInt(channelSerialNumber); out.writeInt(continueAreaNumber); for (int i = 0; i < sampleCount; i++) { dataStream.writeInt(data[i]); } logger.debug("== DONE"); } public Object clone() throws CloneNotSupportedException { return super.clone(); } /** * Sets gap tolerance to detect gaps between segments. 1.0 is a gap of 2*sample rate * * @param tolerance the new gapTolerance */ public static void setGapTolerance(double tolerance) { gapTolerance = tolerance; } /** * detect is there is data break (gap or overlay) between two time points * * @param firstEndTime * first time point * @param secondStartTime * second time point * @param sampleRate * sample rate */ public static boolean isDataBreak(long firstEndTime, long secondStartTime, double sampleRate) { double gap = gapTolerance * 2.0 * sampleRate; long timediff = Math.abs(firstEndTime - secondStartTime); boolean compare = timediff > gap; return compare; } public static boolean isDataGap(long firstEndTime, long secondStartTime, double sampleRate) { double gap = gapTolerance * 2.0 * sampleRate; long timediff = secondStartTime - firstEndTime; boolean compare = timediff > gap; return compare; } public static boolean isDataOverlay(long firstEndTime, long secondStartTime, double sampleRate) { double gap = gapTolerance * 2.0 * sampleRate; long timediff = firstEndTime - secondStartTime; boolean compare = timediff > gap; return compare; } public boolean getIsLoaded() { return isLoaded; } public void setIsLoaded(boolean isLoaded) { this.isLoaded = isLoaded; } }