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;
    }
}
  • No labels