package nl.wldelft.timeseriesparsers; import nl.wldelft.util.DateUtils; import nl.wldelft.util.IOUtils; import nl.wldelft.util.io.BinaryParser; import nl.wldelft.util.io.LittleEndianDataInputStream; import nl.wldelft.util.timeseries.DefaultTimeSeriesHeader; import nl.wldelft.util.timeseries.TimeSeriesContentHandler; import org.apache.log4j.Logger; import java.io.BufferedInputStream; import java.io.IOException; /** * TimeSeries reader for Keller AG *.IDC files * */ public class IdcTimeSeriesParser implements BinaryParser<TimeSeriesContentHandler> { private static final Logger log = Logger.getLogger(IdcTimeSeriesParser.class); // Constanten private static final int DF_ID_FILE = 0; private static final int DF_ID_DEVICE = 1; private static final int DF_ID_DATA = 2; private static final int DF_ID_UNITS = 3; private static final int DF_ID_PROFILE = 4; private static final int DF_ID_CONFIG = 5; private static final int DF_ID_WL_CONVERTED = 6; private static final int DF_ID_AIR_COMPENSATED = 7; private static final int DF_ID_INFO = 8; // private LittleEndianDataInputStream is = null; private TimeSeriesContentHandler contentHandler = null; private int rawTimeZoneOffset; /** * Parse Keller AG *.idc bestand * * @param inputStream * @param virtualFileName * @param contentHandler * @throws Exception */ @Override public void parse(BufferedInputStream inputStream, String virtualFileName, TimeSeriesContentHandler contentHandler) throws Exception { this.is = new LittleEndianDataInputStream(inputStream); this.contentHandler = contentHandler; this.rawTimeZoneOffset = contentHandler.getDefaultTimeZone().getRawOffset(); boolean abVersion0310 = false; String locationId = ""; float[] userValArr = new float[12]; float installationDepth = 0f; float heightOfWellhead = 0f; float offset = 0f; float waterDensity = 0f; byte batteryCapacity = 0; long startTime = Long.MIN_VALUE; DefaultTimeSeriesHeader header = new DefaultTimeSeriesHeader(); DefaultTimeSeriesHeader headerEq = new DefaultTimeSeriesHeader(); // Continue to read lines while // there are still some left to read while (true) { is.mark(1); int nextByte = is.read(); if (nextByte == -1) break; // EOF is.reset(); // Read block ID short blockId = readBlock(); switch (blockId) { case DF_ID_FILE: // File Identification String version = readString(); if (log.isDebugEnabled()) log.debug("version = " + version); break; case DF_ID_DEVICE: // Device properties int lw = this.is.readInt(); int w1 = lw / 65536; int w2 = lw % 65536; int klasse = w1 / 256; int groep = w1 % 256; int jaar = w2 / 256; int week = w2 % 256; if (jaar == 3) { if (week >= 10) { abVersion0310 = true; } } if (jaar > 3) { abVersion0310 = true; } int serialNumber = this.is.readInt(); boolean configuredAsWaterlevel = this.is.readBoolean(); locationId = readString(); String comment = readString(); if (log.isDebugEnabled()) { log.debug("serial number = " + serialNumber); log.debug("configured as waterlevel = " + configuredAsWaterlevel); log.debug("comment = " + comment); log.debug("location id = " + locationId); } break; case DF_ID_DATA: // Data records int z = this.is.readInt(); for (int i = 0; i < z; i++) { // datum 8 bytes double // channel 1 byte // 3 bytes skip // value 4 bytes float // lw 4 bytes int // 4 bytes skip // The date is stored as the number of days since 30 Dec 1899. Quite why it is not 31 Dec is not clear. 01 Jan 1900 has a days value of 2. double doubleTime = is.readDouble(); long time = DateUtils.toTimeFromMicrosoftComDate(doubleTime); // Bewaar lijst met tijden voor de reeks met inhangdiepte's if (startTime == Long.MIN_VALUE) startTime = time; contentHandler.setTime(new Long(time)); byte channel = this.is.readByte(); contentHandler.setTimeSeriesHeader(channel); IOUtils.skipFully(is, 3); float singleValue = this.is.readFloat(); contentHandler.setValue(singleValue); contentHandler.applyCurrentFields(); int longValue = this.is.readInt(); // Skip 4 bytes IOUtils.skipFully(is, 4); } break; case DF_ID_UNITS: boolean retval = parseUnits(locationId, header); if (!retval){ log.error("Bestand bevat niet de juiste eenheden"); throw new Exception("Bestand bevat niet de juiste eenheden"); } break; case DF_ID_PROFILE: // Read device profile for (int i = 0; i < userValArr.length; i++) { userValArr[i] = this.is.readFloat(); if (log.isDebugEnabled()) log.debug("userValArr " + userValArr[i]); } installationDepth = userValArr[2]; if (log.isDebugEnabled()) log.debug("installation depth " + installationDepth); heightOfWellhead = userValArr[3]; if (log.isDebugEnabled()) log.debug("Height of wellhead above sea level " + heightOfWellhead); offset = userValArr[4]; if (log.isDebugEnabled()) log.debug("Offset " + offset); waterDensity = userValArr[5]; if (log.isDebugEnabled()) log.debug("Water density " + waterDensity); short availableChannels = this.is.readShort(); if ((availableChannels & 2) == 2) { float p1min = this.is.readFloat(); float p1max = this.is.readFloat(); if (log.isDebugEnabled()) log.debug("P1 min " + p1min); if (log.isDebugEnabled()) log.debug("P1 max " + p1max); } if ((availableChannels & 4) == 4) { float p2min = this.is.readFloat(); float p2max = this.is.readFloat(); if (log.isDebugEnabled()) log.debug("P2 min " + p2min); if (log.isDebugEnabled()) log.debug("P2 max " + p2max); } if ((availableChannels & 8) == 8) { float t1min = this.is.readFloat(); float t1max = this.is.readFloat(); if (log.isDebugEnabled()) log.debug("T1 min " + t1min); if (log.isDebugEnabled()) log.debug("T1 max " + t1max); } if ((availableChannels & 16) == 16) { float tob1min = this.is.readFloat(); float tob1max = this.is.readFloat(); if (log.isDebugEnabled()) log.debug("TOB1 min " + tob1min); if (log.isDebugEnabled()) log.debug("TOB1 max " + tob1max); } if ((availableChannels & 32) == 32) { float tob2min = this.is.readFloat(); float tob2max = this.is.readFloat(); if (log.isDebugEnabled()) log.debug("TOB2 min " + tob2min); if (log.isDebugEnabled()) log.debug("TOB2 max " + tob2max); } break; case DF_ID_CONFIG: // Record configuration int startDate = this.is.readInt(); int stopDate = this.is.readInt(); lw = this.is.readInt(); int recordedChannels = lw / 65536; int recordModus = lw % 65536; float trigger1 = this.is.readFloat(); float trigger2 = this.is.readFloat(); if (abVersion0310) { int recFixCounter = this.is.readInt(); short recModCounter = this.is.readShort(); } else { lw = this.is.readInt(); int recFixCounter = lw / 65536; int tmp = lw % 65536; short recModCounter = (short) tmp; } short sw = this.is.readShort(); int tmp = sw / 256; short recModChannel = (short) tmp; tmp = sw % 256; short recSaveCounter = (short) tmp; short recFastModCounter = this.is.readShort(); boolean recEndless = this.is.readBoolean(); break; case DF_ID_WL_CONVERTED: // Waterlevel converted boolean convertedIntoWaterlevel = is.readBoolean(); break; case DF_ID_AIR_COMPENSATED: // Airpressure compensation boolean airCompensated = is.readBoolean(); break; case DF_ID_INFO: // Additional information batteryCapacity = this.is.readByte(); for (int i = 0; i < 10; i++) { int reserve = this.is.readInt(); } // Read CRC16 sum of the whole file short crc16 = is.readShort(); break; } } // Inhangdiepte headerEq.setLocationId(locationId); contentHandler.setTime(startTime); headerEq.setParameterId("InstallationDepth"); headerEq.setUnit("m"); contentHandler.setTimeSeriesHeader(headerEq); contentHandler.setValue(installationDepth); contentHandler.applyCurrentFields(); headerEq.setParameterId("HeightOfWellhead"); headerEq.setUnit("m"); contentHandler.setTimeSeriesHeader(headerEq); contentHandler.setValue(heightOfWellhead); contentHandler.applyCurrentFields(); headerEq.setParameterId("Offset"); headerEq.setUnit("m"); contentHandler.setTimeSeriesHeader(headerEq); contentHandler.setValue(offset); contentHandler.applyCurrentFields(); headerEq.setParameterId("WaterDensity"); headerEq.setUnit("kg/m3"); contentHandler.setTimeSeriesHeader(headerEq); contentHandler.setValue(waterDensity); contentHandler.applyCurrentFields(); headerEq.setParameterId("BatteryCapacity"); headerEq.setUnit("%"); contentHandler.setTimeSeriesHeader(headerEq); contentHandler.setValue(batteryCapacity); contentHandler.applyCurrentFields(); } /** * Read block identification * @return block identification * @throws IOException */ private short readBlock() throws IOException { short block = this.is.readShort(); short w1 = this.is.readShort(); short w2 = this.is.readShort(); int n = (65536 * w1) + w2; return block; } /** * * * * @param locationId * @param header * @return * @throws java.io.IOException */ private boolean parseUnits( String locationId, DefaultTimeSeriesHeader header) throws IOException { boolean retval = true; short availableChannels = this.is.readShort(); short amountOfUnits = this.is.readShort(); for (int i = 0; i < amountOfUnits; i++) { // Kanaal byte channel = this.is.readByte(); // Eenheid byte[] bytes = new byte[7]; this.is.read(bytes, 0, 7); String unit = new String(bytes, 1, bytes[0]); if(unit.equalsIgnoreCase("m")){ retval = false; } header.setUnit(unit); if (unit.contains("°C")) { header.setUnit("deg C"); } // Multiplier float multiplier = this.is.readFloat(); // Offet float offset = this.is.readFloat(); // Description bytes = new byte[41]; this.is.read(bytes, 0, 41); String description = new String(bytes, 1, bytes[0]); IOUtils.skipFully(is, 3); if (log.isDebugEnabled()) { log.debug("channel " + channel); log.debug("multiplier " + multiplier); log.debug("offset " + offset); log.debug("unit " + unit); log.debug("description " + description); } header.setLocationId(locationId); header.setParameterId(Byte.toString(channel)); contentHandler.createTimeSeriesHeaderAlias(channel, header); } return retval; } /** * Read a string from file * @return string from file * @throws Exception */ private String readString() throws IOException { String retval = ""; // lees lengte van de string short length = this.is.readShort(); if (length > 0) { // Create the byte array to hold the data byte[] bytes = new byte[length]; int nob = this.is.read(bytes, 0, length); if (nob == length) { retval = new String(bytes); } } return retval; } }