Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.


Code Block
languagejava
linenumberstrue
package nl.wldelft.fews.pi;

import nl.wldelft.util.*Arguments;
import nl.wldelft.util.coverage.GeometryBinaryUtils;
import nl.wldelft.util.coverage.GeometryUtilsClasz;
import nl.wldelft.util.geodatum.GeoPointFastDateFormat;
import nl.wldelft.util.io.VirtualOutputDirFileUtils;
import nl.wldelft.util.io.VirtualOutputDirConsumerMathUtils;
import nl.wldelft.util.io.XmlSerializerNumberType;
import nl.wldelft.util.timeseries.TimeSeriesContentObjectUtils;
import nl.wldelft.util.timeseries.TimeSeriesHeaderPeriod;
import nl.wldelft.util.timeseries.TimeStepProperties;

import javaxnl.xmlwldelft.streamutil.XMLStreamExceptionTextUtils;
import javaxnl.xmlwldelft.streamutil.XMLStreamWriterTimeZoneUtils;
import java.io.IOExceptionnl.wldelft.util.function.Supplier;
import javanl.wldelft.util.io.OutputStreamVirtualOutputDir;
import java.nio.ByteOrdernl.wldelft.util.io.VirtualOutputDirConsumer;
import javanl.wldelft.util.io.ArraysXmlChunkedSerializer;
import javanl.wldelft.util.timeseries.LocaleProduct;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

public class PiTimeSeriesSerializer implements XmlSerializer<TimeSeriesContent>, VirtualOutputDirConsumer {
    private static final int BUFFER_SIZE = 2048;
    private TimeZone timeZoneNoDst = null;

    public enum EventDestination {
        XML_EMBEDDED, SEPARATE_BINARY_FILE, ONLY_HEADERS
    }

    public enum EnsembleMemberFormat {
        INDEX, ID, HIDE
    }

    private EventDestination eventDestination = EventDestination.XML_EMBEDDED;
    private PiVersion version = PiVersion.VERSION_1_2;
    private EnsembleMemberFormat ensembleMemberFormat = EnsembleMemberFormat.INDEX;

    private FastDateFormat dateFormat = null;
    private FastDateFormat timeFormat = null;
    private TimeSeriesContent timeSeriesContent = null;
    private XMLStreamWriter writer = null;
    private VirtualOutputDir virtualOutputDir = null;
    private OutputStream binaryOutputSteam = null;
    private byte[] byteBuffer = null;
    private float[] floatBuffer = null;
    private int bufferPos = 0;
    private long[] cachedTimes = new long[4];
    private String[] cachedDateStrings = new String[4];
    private String[] cachedTimeStrings = new String[4];
    private int cachedSeconds = -1;
    private String cachedSecondsString = null;

    public PiTimeSeriesSerializer() {
    }

    public PiTimeSeriesSerializer(PiVersion version) {
        this.version = versionnl.wldelft.util.timeseries.ProductInfo;
import nl.wldelft.util.timeseries.Statistics;
import nl.wldelft.util.timeseries.TimeSeriesContent;
import nl.wldelft.util.timeseries.TimeSeriesHeader;
import nl.wldelft.util.timeseries.TimeStep;
import nl.wldelft.util.timeseries.TimesOfDayDaylightSavingTimeStep;
import nl.wldelft.util.timeseries.TimesOfDayTimeStep;
import nl.wldelft.util.timeseries.TimesOfHourTimeStep;
import nl.wldelft.util.timeseries.ValueSource;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Locale;
import java.util.TimeZone;

public class PiTimeSeriesSerializer implements XmlChunkedSerializer<TimeSeriesContent>, VirtualOutputDirConsumer {
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_2 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_2);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_3 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_3);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_4 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_4);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_5 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_5);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_6 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_6);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_7 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_7);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_8 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_8);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_9 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_9);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_10 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_10);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_11 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_11);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_12 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_12);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_13 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_13);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_14 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_14);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_15 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_15);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_16 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_16);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_17 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_17);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_18 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_18);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_19 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_19);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_20 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_20);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_21 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_21);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_22 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_22);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_23 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_23);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_24 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_24);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_25 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_25);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_26 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_26);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_27 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_27);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_28 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_28);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_29 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_29);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_30 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_30);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_31 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_31);

    private static final int BUFFER_SIZE = 2048;
    private TimeZone timeZone = null;

    private String missingValueText = "NaN";
    private float missingValue = Float.NaN;
    private String virtualFileName = null;
    private boolean skipEmptyTimeSeries = false;

    public void setSkipEmptyTimeSeries(boolean skipEmptyTimeSeries) {
        this.skipEmptyTimeSeries = skipEmptyTimeSeries;
    }

    public void setUseMilliseconds (boolean useMilliseconds) {this.useMilliseconds = useMilliseconds; }

    public enum EventDestination {
        XML_EMBEDDED, SEPARATE_BINARY_FILE, ONLY_HEADERS
    }

    public enum EnsembleMemberFormat {
        INDEX, ID, HIDE
    }

    private EventDestination eventDestination = EventDestination.XML_EMBEDDED;
    private PiVersion version = PiVersion.VERSION_1_2;
    private EnsembleMemberFormat ensembleMemberFormat = EnsembleMemberFormat.INDEX;

    private boolean anyTimeSeriesWritten = false;
    private FastDateFormat dateFormat = null;
    private FastDateFormat timeFormat = null;
    private boolean useMilliseconds = false;
    private TimeSeriesContent timeSeriesContent = null;
    private XMLStreamWriter writer = null;
    private VirtualOutputDir virtualOutputDir = null;
    private OutputStream binaryOutputSteam = null;
    private byte[] byteBuffer = null;
    private char[] charBuffer = null;
    private float[] floatBuffer = null;
    private int bufferPos = 0;
    private long[] cachedTimes = new long[6];
    private String[] cachedDateStrings = new String[6];
    private String[] cachedTimeStrings = new String[6];
    private int cachedSeconds = -1;
    private String cachedSecondsString = null;
    private float[][] domainAxesValues = null;
    private float[][] newDomainValues = null;

    public PiTimeSeriesSerializer() {
    }

    public PiTimeSeriesSerializer(PiVersion version) {
        this.version = version;
    }

    public void setEventDestination(EventDestination eventDestination) {
        this.eventDestination = eventDestination;
    }

    public PiVersion getVersion() {
        return version;
    }

    public void setVersion(PiVersion version) {
        Arguments.require.notNull(version);
        this.version = version;
    }

    public void setEnsembleMemberFormat(EnsembleMemberFormat ensembleMemberFormat) {
        Arguments.require.notNull(ensembleMemberFormat);
        this.ensembleMemberFormat = ensembleMemberFormat;
    }

    @Override
    public void setVirtualOutputDir(VirtualOutputDir virtualOutputDir) {
        this.virtualOutputDir = virtualOutputDir;
    }

    @Override
    public void serializeStartDocument(XMLStreamWriter streamWriter, String virtualFileName) throws Exception {
        anyTimeSeriesWritten = false;
        this.virtualFileName = virtualFileName;
        this.writer = streamWriter;
        if (ensembleMemberFormat == EnsembleMemberFormat.ID && version.getIntId() < PiVersion.VERSION_1_10.getIntId()) {
            throw new IOException("ensembleMemberId not supported for pi version " + version);
        }

        writeStartDocument();

        String binFileName = FileUtils.getPathWithOtherExtension(virtualFileName, "bin");
        if (virtualOutputDir != null) virtualOutputDir.delete(binFileName);

        if (eventDestination != EventDestination.SEPARATE_BINARY_FILE) return;
        if (virtualOutputDir == null)
            throw new IllegalStateException("virtualOutputDir == null");

        binaryOutputSteam = virtualOutputDir.getOutputStream(binFileName);
        bufferPos = 0;
        if (byteBuffer != null) return;
        byteBuffer = new byte[BUFFER_SIZE * NumberType.FLOAT_SIZE];
        floatBuffer = new float[BUFFER_SIZE];
    }

    public void writeStartDocument() throws XMLStreamException {
        writer.writeStartDocument();
        writer.writeStartElement("TimeSeries");
        writer.setDefaultNamespace("http://www.wldelft.nl/fews/PI");
        writer.writeNamespace("", "http://www.wldelft.nl/fews/PI");
        writer.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
//        writer.writeAttribute("xmlns", "http://www.wldelft.nl/fews/PI");
        writer.writeAttribute("xsi:schemaLocation", PiSchemaLocations.get("pi_timeseries.xsd"));
        writer.writeAttribute("version", version.toString());
        if (version.getIntId() >= PiVersion.VERSION_1_19.getIntId()) {
            writer.writeAttribute("xmlns:fs", "http://www.wldelft.nl/fews/fs");
        }
    }

    @Override
    public void serializeContent(TimeSeriesContent timeSeriesContent) throws Exception {
        if (timeSeriesContent.getTimeSeriesCount() == 0) return;
        this.timeSeriesContent = timeSeriesContent;
        timeSeriesContent.setRequireEnsembleMemberIndices(ensembleMemberFormat == EnsembleMemberFormat.INDEX);
        if (!anyTimeSeriesWritten) {
            anyTimeSeriesWritten = true;
            initMissingValueText();
            initTimeZone();
            writeTimeZone();
        }

        for (int i = 0, n = timeSeriesContent.getTimeSeriesCount(); i < n; i++) {
            timeSeriesContent.setTimeSeriesIndex(i);
            if (skipEmptyTimeSeries && timeSeriesContent.isTimeSeriesEmpty()) continue;
            if (!timeSeriesContent.isTimeSeriesEmpty()) timeSeriesContent.setContentTimeIndex(0);
            writer.writeStartElement("series");
            writeHeader();
            writeEvents();
            writer.writeEndElement();
        }
    }

    @Override
    public void serializeEndDocument() throws Exception {
        if (!anyTimeSeriesWritten) throw new PiTimeSeriesNotFoundException("No TimeSeries to be Written to " + virtualFileName);
        writer.writeEndElement();
        writer.writeEndDocument();
        if (eventDestination != EventDestination.SEPARATE_BINARY_FILE) return;
        flushBinEvents();
        binaryOutputSteam.close();
        binaryOutputSteam = null;
    }

    private void initMissingValueText() {
        missingValueText = timeSeriesContent.getDefaultMissingValue('.');
        try {
            missingValue = Float.parseFloat(missingValueText);
        } catch (NumberFormatException e) {
            timeSeriesContent.setMissingValue(Float.NaN);
            missingValue = Float.NaN;
            missingValueText = "NaN";
        }
    }

    private void initTimeZone() {
        if (!ObjectUtils.equals(timeZone, this.timeSeriesContent.getDefaultTimeZone())) {
            Arrays.fill(cachedTimes, Long.MIN_VALUE);
            timeZone = timeSeriesContent.getDefaultTimeZone();
        }

        dateFormat = FastDateFormat.getInstance("yyyy-MM-dd", timeZone, Locale.US, dateFormat);
        timeFormat = useMilliseconds?FastDateFormat.getInstance("HH:mm:ss.SSS", timeZone, Locale.US, timeFormat) : FastDateFormat.getInstance("HH:mm:ss", timeZone, Locale.US, timeFormat);
    }

    private void writeTimeZone() throws XMLStreamException {
        if (timeZone.useDaylightTime()) {
            PiSerializerUtils.writeElement(writer, "daylightSavingObservingTimeZone", timeZone.getID());
        } else {
            PiSerializerUtils.writeElement(writer, "timeZone", String.valueOf(TimeZoneUtils.getTimeZoneOffsetHours(timeZone)));
        }
    }

    private void writeHeader() throws Exception {
        TimeSeriesHeader header = timeSeriesContent.getTimeSeriesHeader();
        PiTimeSeriesHeader piHeader = header instanceof PiTimeSeriesHeader ? (PiTimeSeriesHeader) header : new PiTimeSeriesHeader();

        writer.writeStartElement("header");
        PiSerializerUtils.writeElement(writer, "type", header.getParameterType() == null ? "instantaneous" : header.getParameterType().getName());
        if (version.getIntId() >= PiVersion.VERSION_1_17.getIntId() && header.getModuleInstanceId() != null) PiSerializerUtils.writeElement(writer, "moduleInstanceId", header.getModuleInstanceId());
        PiSerializerUtils.writeElement(writer, "locationId", header.getLocationId() == null ? "unknown" : header.getLocationId());
        PiSerializerUtils.writeElement(writer, "parameterId", header.getParameterId() == null ? "unknown" : header.getParameterId());
        if (version.getIntId() >= PiVersion.VERSION_1_4.getIntId()) {
            for (int i = 0, n = header.getQualifierCount(); i < n; i++) {
                PiSerializerUtils.writeElement(writer, "qualifierId", header.getQualifierId(i));
            }
        }
        if (version.getIntId() >= PiVersion.VERSION_1_4.getIntId() && header.getEnsembleId() != null && !header.getEnsembleId().equals("main")) {
            if (ensembleMemberFormat != EnsembleMemberFormat.HIDE) {
                writeOptionalElement("ensembleId", header.getEnsembleId());
                writeOptionalElement(ensembleMemberFormat == EnsembleMemberFormat.INDEX
                        ? "ensembleMemberIndex" : "ensembleMemberId", header.getEnsembleMemberId());
            }
        }

        writeTimeStep(header);
        writePeriod();
        if (version.getIntId() >= PiVersion.VERSION_1_5.getIntId()) writeTime("forecastDate", header.getForecastTime(), 2);
        if (version.getIntId() >= PiVersion.VERSION_1_20.getIntId()) writeTime("approvedDate", header.getApprovedTime(), 4);

        PiSerializerUtils.writeElement(writer, "missVal", timeSeriesContent.getDefaultMissingValue('.'));
        writeOptionalElement("longName", piHeader.getLongName());
        writeOptionalElement("stationName", header.getLocationName());

        if (version.getIntId() >= PiVersion.VERSION_1_7.getIntId()) PiSerializerUtils.writeCoordinates(writer, header, version);
        writeOptionalElement("units", header.getUnit());
        for (int i = 0, n = header.getDomainParameterCount(); i < n; i++) {
            writeDomainAxis(header.getDomainParameterId(i), header.getDomainUnit(i));
        }

        writeOptionalElement("sourceOrganisation", piHeader.getSourceOrganisation());
        writeOptionalElement("sourceSystem", piHeader.getSourceSystem());
        writeOptionalElement("fileDescription", piHeader.getFileDescription());


        if (header.getCreationTime() != Long.MIN_VALUE) {
            updateDateTimeStringCache(header.getCreationTime(), 3);
            PiSerializerUtils.writeElement(writer, "creationDate", cachedDateStrings[3]);
            PiSerializerUtils.writeElement(writer, "creationTime", cachedTimeStrings[3]);
        }

        writeOptionalElement("region", piHeader.getRegion());

        if (header.getHighLevelThresholdCount() > 0) writeHighThresholds(header);

        if (version.getIntId() >= PiVersion.VERSION_1_16.getIntId() && timeSeriesContent.hasStatistics()) writeTimeSeriesStatistics();

        if (version.getIntId() >= PiVersion.VERSION_1_26.getIntId() && timeSeriesContent.getProduct() != null) writeProduct();

        writer.writeEndElement();
    }

    publicprivate EventDestinationvoid getEventDestinationwriteProduct() throws XMLStreamException {
        returnProduct eventDestination;
product    }

    public void setEventDestination(EventDestination eventDestination) {= timeSeriesContent.getProduct();
        this.eventDestination = eventDestinationwriter.writeStartElement("product");
    }

    public PiVersion getVersion() {writer.writeAttribute("id", product.getId());
        return version;writer.writeAttribute("name", product.getName());

    }

    public void setVersion(PiVersion version) {
writeTime("productDate", product.getProductTime(), 5);

        writer.writeEmptyElement("category");
  if (version == null)
   writer.writeAttribute("id", product.getCategory().getId());
        throw new IllegalArgumentException("version == null"writer.writeAttribute("name", product.getCategory().getName());

        if this.version = version;
    }

    public EnsembleMemberFormat getEnsembleMemberFormat((product.getProductInfos() != null && product.getProductInfos().length > 0) {
        return ensembleMemberFormat;
   for }

(int i = 0; publici void< setEnsembleMemberFormat(EnsembleMemberFormat ensembleMemberFormat) {
        if (ensembleMemberFormat == null)
product.getProductInfos().length; i++) {
                ProductInfo throwproductInfo new= IllegalArgumentException("ensembleMemberFormat == null");

product.getProductInfos()[i];
        this.ensembleMemberFormat = ensembleMemberFormat;
    }

    @Override  writer.writeStartElement("productInfo");

    public void setVirtualOutputDir(VirtualOutputDir virtualOutputDir) {
        this.virtualOutputDir = virtualOutputDirwriter.writeStartElement("user");
    }

    @Override
    public void serialize(TimeSeriesContent timeSeriesContent, XMLStreamWriter streamWriter, String virtualFileName) throws Exception {
 writer.writeCharacters(productInfo.getUser());
              this.timeSeriesContent = timeSeriesContentwriter.writeEndElement();

        if (timeSeriesContent.getTimeSeriesCount() <=0) throw new IOException("No TimeSeries to be Written to " + virtualFileName);
        writer.writeStartElement("confidence");
                this.writer = streamWriter;

writer.writeCharacters(productInfo.getConfidence());
         if (ensembleMemberFormat == EnsembleMemberFormat.ID && version.getIntId() < PiVersion.VERSION_1_10.getIntId()) {
 writer.writeEndElement();

           throw new IOException("ensembleMemberId not supported for pi version " + version writer.writeStartElement("classification");
        }

        timeSeriesContentwriter.setRequireEnsembleMemberIndices(ensembleMemberFormat == EnsembleMemberFormat.INDEXwriteCharacters(productInfo.getClassification());

              if (!ObjectUtils.equals(timeZoneNoDst, this.timeSeriesContent.getDefaultTimeZone())) {
 writer.writeEndElement();

                Arrayswriter.fill(cachedTimes, Long.MIN_VALUEwriteStartElement("comment");
              timeZoneNoDst = timeSeriesContent.getDefaultTimeZone(writer.writeCharacters(productInfo.getComment());
        }
        if (timeZoneNoDstwriter.useDaylightTimewriteEndElement()){;

            //Make timeZone not use day light saving time (DST) because the timeZone field does not use DST therefore
 writer.writeEndElement(); // productInfo
            }
      // all values}
 must also not use DST
   writer.writeEndElement(); // product
    }

   timeZoneNoDst =private newvoid SimpleTimeZonewriteTimeSeriesStatistics(timeZoneNoDst.getRawOffset(), timeZoneNoDst.getID(), 0, 0, 0, 0, 0, 0, 0, 0);
 throws XMLStreamException {
        Statistics timeSeriesInfoStatistics  }= timeSeriesContent.getStatistics();
        dateFormat = FastDateFormat.getInstance("yyyy-MM-dd", timeZoneNoDst, Locale.US, dateFormat)if (timeSeriesInfoStatistics == null) return;
        timeFormat = FastDateFormat.getInstance("HH:mm:ss", timeZoneNoDst, Locale.US, timeFormatif (timeSeriesInfoStatistics.hasFirstValueTime()) writeTime("firstValueTime", timeSeriesInfoStatistics.getFirstValueTime(), 0);
        String binFileName = FileUtils.getPathWithOtherExtension(virtualFileName, "bin");

if (timeSeriesInfoStatistics.hasLastValueTime()) writeTime("lastValueTime", timeSeriesInfoStatistics.getLastValueTime(), 1);
        if (virtualOutputDir != null) virtualOutputDir.delete(binFileName);
timeSeriesInfoStatistics.hasMaxValue()) PiSerializerUtils.writeElement(writer, "maxValue", timeSeriesInfoStatistics.getMaxValue('.'));
        if (timeSeriesInfoStatistics.hasMinValue()) PiSerializerUtils.writeElement(eventDestination == EventDestination.SEPARATE_BINARY_FILE) {writer, "minValue", timeSeriesInfoStatistics.getMinValue('.'));
            if (virtualOutputDir == null)
                throw new IllegalStateException("virtualOutputDir == null"(timeSeriesInfoStatistics.hasValueCount()) PiSerializerUtils.writeElement(writer,"valueCount", Integer.toString(timeSeriesInfoStatistics.getValueCount()));
        if (timeSeriesInfoStatistics.hasMaxWarningLevelName()) PiSerializerUtils.writeElement(writer,  binaryOutputSteam = virtualOutputDir.getOutputStream(binFileName"maxWarningLevelName", timeSeriesInfoStatistics.getMaxWarningLevelName());
    }

    private void writeHighThresholds(TimeSeriesHeader header) ifthrows (byteBuffer == null) XMLStreamException {
        boolean thresholdsAvailable = false;
     byteBuffer = new byte[BUFFER_SIZE * NumberType.FLOAT_SIZE];
   boolean firstThreshold = true;

        for (int floatBufferi = new float[BUFFER_SIZE];
 0; i < header.getHighLevelThresholdCount(); i++) {
             }
TimeSeriesHeader.Threshold threshold = header.getHighLevelThreshold(i);
            String try {id = threshold.getId();
            String name =  serializethreshold.getName();
            } finally {
                bufferPos = 0;//Note: here value can be NaN for LevelThresholdValues that have different values for different aggregationTimeSpans configured.
            float value =  binaryOutputSteamthreshold.closegetValue();
            }
            return;
 if (Float.isNaN(value)) {
       }

        binaryOutputSteam = nullcontinue;
        serialize();
    }

    private void serialize() throws Exception {
    thresholdsAvailable    writer.writeStartDocument()= true;
        writer.writeStartElement("TimeSeries");
       if writer.setDefaultNamespace("http://www.wldelft.nl/fews/PI");(firstThreshold) {
        writer.writeNamespace("", "http://www.wldelft.nl/fews/PI");
        writer.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instancewriteStartElement("thresholds");
//        writer.writeAttribute("xmlns", "http://www.wldelft.nl/fews/PI");
        writer.writeAttribute("xsi:schemaLocation", PiSchemaLocations.get("pi_timeseries.xsd"));
 firstThreshold = false;
           writer.writeAttribute("version", version.toString());
 }

         writeElement("timeZone", String.valueOf((double) timeSeriesContent.getDefaultTimeZone().getRawOffset() / (double) TimeUnit.HOUR_MILLIS));
   if (version.getIntId() >= PiVersion.VERSION_1_14.getIntId()) {
           for  (int i = 0,String nlabel = timeSeriesContent.getTimeSeriesCount()null;
 i   < n; i++) {
         String comment  timeSeriesContent.setTimeSeriesIndex(i)= null;
                writer.writeStartElement("series")String description = null;
            writeHeader    if (version.getIntId();
 >= PiVersion.VERSION_1_22.getIntId()){
          writeEvents();
          label = writerthreshold.writeEndElementgetLabel();
        }
        writer.writeEndElement();}
        writer.writeEndDocument();
        if (version.getIntId(eventDestination) >== EventDestinationPiVersion.SEPARATEVERSION_BINARY_FILE) flushBinEvents();1_27.getIntId()){
    }

    private void writeHeader() throws Exception {
        TimeSeriesHeader headercomment = timeSeriesContentthreshold.getTimeSeriesHeadergetComment();
         PiTimeSeriesHeader   piHeader = header instanceof PiTimeSeriesHeader ? (PiTimeSeriesHeader) header :description new= PiTimeSeriesHeaderthreshold.getDescription();

                writer.writeStartElement("header");}
        writeElement("type", header.getParameterType() == null ? "instantaneous" : header.getParameterType().getName());
         if (threshold.getGroupCount() > 0) {
       writeElement("locationId", header.getLocationId() == null ? "unknown" : header.getLocationId());
       for writeElement("parameterId", header.getParameterId()int j == null0, ?m "unknown" : header.getParameterId= threshold.getGroupCount());
 j < m; j++) {
    if (version.getIntId() >= PiVersion.VERSION_1_4.getIntId()) {
            for (int i = 0,String ngroupId = headerthreshold.getQualifierCountgetGroupId(j);
 i      < n; i++) {
              String  writeElement("qualifierId", header.getQualifierId(i));
 groupName = threshold.getGroupName(j);
           }
        }
        if (versionthreshold.getIntIdgetGroupCount() >== PiVersion.VERSION_1_4.getIntId() && header.getEnsembleId()groupId != null && !header.getEnsembleId().equalsgroupId.equalsIgnoreCase("mainDeprecatedThresholdsConfigured")) {
              if (ensembleMemberFormat != EnsembleMemberFormat.HIDE) {
                  groupId = null;
            writeOptionalElement("ensembleId", header.getEnsembleId());
                writeOptionalElement(ensembleMemberFormatgroupName == EnsembleMemberFormat.INDEXnull;
                        ?}
 "ensembleMemberIndex" : "ensembleMemberId", header.getEnsembleMemberId());
            }
        }

        writeTimeStep(headerwriteThreshold(id, name, value, groupId, groupName, label, description, comment);
        writePeriod();
        if (version.getIntId() >= PiVersion.VERSION_1_5.getIntId()) writeTime("forecastDate", header.getForecastTime(), 2);
     }
        writeElement("missVal", Float.toString(timeSeriesContent.getDefaultMissingValue()));
        writeOptionalElement("longName", piHeader.getLongName());
} else {
          writeOptionalElement("stationName", header.getLocationName());

        if (version.getIntId() >= PiVersion.VERSION_1_7.getIntId()) writeCoordinates(header);
writeThreshold(id, name, value, null, null, label, description, comment);
               writeOptionalElement("units", header.getUnit());
 }
         writeOptionalElement("sourceOrganisation", piHeader.getSourceOrganisation());
    } else {
       writeOptionalElement("sourceSystem", piHeader.getSourceSystem());
        writeOptionalElementwriteThreshold("fileDescription", piHeader.getFileDescription());


        if (header.getCreationTime() != Long.MIN_VALUE) {
id, name, value, null, null, null, null, null);
            }
    updateDateTimeStringCache(header.getCreationTime(), 3);
   }

        if writeElement("creationDate", cachedDateStrings[3]);(thresholdsAvailable) {
            writeElement("creationTime", cachedTimeStrings[3]writer.writeEndElement();
        }
    }

    private void writeThreshold(String id, String name, float writeOptionalElement("region", piHeader.getRegion());

        if (header.getHighLevelThresholdCount() > 0)value, String groupId, String groupName, String label, String description, String comment) throws XMLStreamException {
            writeHighThresholds(headerwriter.writeStartElement("highLevelThreshold");
        }
writer.writeAttribute("id", id);
        writer.writeEndElement(writeAttribute("name", name);
    }

    privateif void writeProperties(Properties properties) throws XMLStreamException {(label != null) writer.writeAttribute("label", label);
        if (version.getIntId(description != null) < PiVersion.VERSION_1_13.getIntId()) returnwriter.writeAttribute("description", description);
        if (properties.isEmpty()) {comment != null) writer.writeAttribute("comment", comment);
            writer.writeEmptyElementwriteAttribute("properties")value", Float.toString(value));
        if (groupId !=  // next events don't have properties
null) writer.writeAttribute("groupId", groupId);
        if (groupName != null)     return;
        }writer.writeAttribute("groupName", groupName);

        writer.writeStartElementwriteEndElement("properties");
    }

    forprivate (int i = 0, n = properties.sizevoid writePeriod(); i < n; i++)throws XMLStreamException {
        TimeStep timeStep   String key = properties.getKey(itimeSeriesContent.getTimeSeriesHeader().getTimeStep();
            PropertyType typePeriod period = propertiestimeSeriesContent.getTypegetTimeSeriesPeriod(i);
            switch (type) {Period headerPeriod;
                case STRING:
        if (period == Period.NEVER) {
            writer.writeEmptyElement("string");
    // create a dummy period
            long now   writer.writeAttribute("key", key= timeStep.nearestTime(System.currentTimeMillis());
            headerPeriod =       writer.writeAttribute("value", properties.getString(i)new Period(now, now);
        } else {
          continue;
  headerPeriod = period;
            case INT:}

        writeTime("startDate", headerPeriod.getStartTime(), 0);
          writer.writeEmptyElement("int"writeTime("endDate", headerPeriod.getEndTime(), 1);
    }

    private void updateDateTimeStringCache(long time, int cacheIndex) {
        if writer.writeAttribute("key", key)(cachedTimes[cacheIndex] == time) return;
        cachedTimes[cacheIndex] = time;
        cachedDateStrings[cacheIndex] = writerdateFormat.writeAttribute("value", TextUtils.toString(properties.getInt(i)))format(time);
                    continuecachedTimeStrings[cacheIndex] = timeFormat.format(time);
    }

    private void writeTime(String name, long time, int cacheIndex) throws caseXMLStreamException FLOAT:{
        if (time == Long.MIN_VALUE) return;
        writer.writeEmptyElement("float"name);

        updateDateTimeStringCache(time, cacheIndex);
           writer.writeAttribute("keydate", keycachedDateStrings[cacheIndex]);
                    writer.writeAttribute("valuetime", Float.toString(properties.getInt(i)));cachedTimeStrings[cacheIndex]);
    }

    private void writeTimeStep(TimeSeriesHeader header) throws XMLStreamException {
          continuewriter.writeEmptyElement("timeStep");
        TimeStep timeStep = header.getTimeStep();

     case DOUBLE:
  // todo add support for month time step
        if   writer.writeEmptyElement("float");
      (timeStep.isEquidistantMillis()) {
              writer.writeAttribute("keyunit", key"second");
            int seconds       writer.writeAttribute("value", Double.toString(properties.getDouble(i))= (int) (timeStep.getStepMillis() / 1000);
            if (cachedSeconds != seconds) {
    continue;
             cachedSecondsString =  case BOOLEAN:
TextUtils.toString(seconds);
                cachedSeconds = seconds;
    writer.writeEmptyElement("bool");
        }
            writer.writeAttribute("keymultiplier", keycachedSecondsString);
        } else if (timeStep instanceof TimesOfDayDaylightSavingTimeStep && version.getIntId() >= PiVersion.VERSION_1_18.getIntId()) {
            writer.writeAttribute("valuetimes", BooleanPiCastorUtils.toStringgetTimesOfDayString(properties.getBool(i)(TimesOfDayDaylightSavingTimeStep) timeStep));
        } else if (timeStep instanceof TimesOfDayTimeStep && version.getIntId() >=    continue;PiVersion.VERSION_1_18.getIntId()) {
            writer.writeAttribute("times",    case DATE_TIME:PiCastorUtils.getTimesOfDayString((TimesOfDayTimeStep) timeStep));
        } else if (timeStep instanceof TimesOfHourTimeStep && version.getIntId()     writer.writeEmptyElement("dateTime");
        >= PiVersion.VERSION_1_24.getIntId()) {
            writer.writeAttribute("keyminutes", keyPiCastorUtils.getTimesOfHourString((TimesOfHourTimeStep) timeStep));
        } else {
          long dateTime = propertieswriter.getDateTime(iwriteAttribute("unit", "nonequidistant");
        }
    }

    private void writeEvents()  writer.writeAttribute("date", dateFormat.format(dateTime));throws Exception {
        switch (eventDestination) {
          writer.writeAttribute("time", timeFormat.format(dateTime));
   case ONLY_HEADERS:
        }
        }
    return;
    writer.writeEndElement();
    }

    private void writeHighThresholds(TimeSeriesHeader header) throws XMLStreamException {
case SEPARATE_BINARY_FILE:
             boolean thresholdsAvailable = falsewriteBinEvents();
         boolean firstThreshold = true;

    return;
    for (int i = 0; i < header.getHighLevelThresholdCount(); i++) { case XML_EMBEDDED:
            String  id = header.getHighLevelThresholdIdwriteXmlEvents(i);
        }
    String name = header.getHighLevelThresholdName(i);
}

    private void writeBinEvents() throws Exception {
        for (int i = 0, n = timeSeriesContent.getContentTimeCount(); i //Note: here value can be NaN for LevelThresholdValues that have different values for different aggregationTimeSpans configured.< n; i++) {
            timeSeriesContent.setContentTimeIndex(i);
            float value = header.getHighLevelThresholdValue(i)if (!timeSeriesContent.isTimeAvailable()) continue;
            if (Float.isNaN(value)) {bufferPos == BUFFER_SIZE) flushBinEvents();
            floatBuffer[bufferPos++]    continue= timeSeriesContent.getValue();
        }
    }

    private void flushBinEvents() throws     thresholdsAvailable = true;IOException {
        if (bufferPos   if (firstThreshold== 0) {return;
        BinaryUtils.copy(floatBuffer, 0, bufferPos, byteBuffer, 0, bufferPos *  writer.writeStartElement("thresholds"NumberType.FLOAT_SIZE, ByteOrder.LITTLE_ENDIAN);
        binaryOutputSteam.write(byteBuffer, 0, bufferPos * NumberType.FLOAT_SIZE);
        firstThresholdbufferPos = false0;
    }

    private void writeXmlEvents() throws XMLStreamException  }{
        Properties properties =  writer.writeStartElement("highLevelThreshold")Properties.NONE;
        TimeSeriesHeader header =  writertimeSeriesContent.writeAttribute("id", idgetTimeSeriesHeader();
        domainAxesValues    writer.writeAttribute("name", name)= null;
        int domainParameterCount =  writer.writeAttribute("value", Float.toString(value)header.getDomainParameterCount();
            if (version.getIntId() >= PiVersion.VERSION_1_14.getIntId()) {
   float[] floatBuffer = this.floatBuffer;
        for (int i = 0, String groupIdn = headertimeSeriesContent.getHighLevelThresholdGroupIdgetContentTimeCount(i);
 i < n; i++) {
           if (groupId != null){timeSeriesContent.setContentTimeIndex(i);
                    writer.writeAttribute("groupId", groupId)if (!timeSeriesContent.isTimeAvailable()) continue;
            Properties        String groupName newProperties = headertimeSeriesContent.getHighLevelThresholdGroupNamegetProperties(i);
                    if (groupName != null) writer.writeAttribute("groupName", groupName);!newProperties.equals(properties)) {
                }
properties = newProperties;
          }
      PiSerializerUtils.writeProperties(properties, version, writer,    writer.writeEndElement(dateFormat, timeFormat);
        }

    }
    if (thresholdsAvailable) {
      boolean valueMissing =    writer.writeEndElementtimeSeriesContent.isValueMissing();
        }
    }

if (domainParameterCount > 0 private void writePeriod() throws XMLStreamException&& !valueMissing) {
        TimeStep timeStep = timeSeriesContent.getTimeSeriesHeader().getTimeStep();
        Periodint periodvalueCount = timeSeriesContent.getTimeSeriesPeriodwriteAxisValues();
        Period headerPeriod;
        if (periodfloatBuffer == Period.NEVERnull || floatBuffer.length != valueCount) {
                   // createfloatBuffer a= dummy periodnew float[valueCount];
            long now        this.floatBuffer = timeStep.nearestTime(System.currentTimeMillis())floatBuffer;
            headerPeriod = new Period(now, now);
 }
             }  else {
 timeSeriesContent.readValues(floatBuffer);
             headerPeriod = period;
}
            if (domainParameterCount == 0 }

  || valueMissing) {
      writeTime("startDate", headerPeriod.getStartTime(), 0);
        writeTimewriter.writeEmptyElement("endDate", headerPeriod.getEndTime(), 1)event");
    }

    private void writeCoordinates(TimeSeriesHeader header) throws} XMLStreamExceptionelse {
        Geometry geometry = header.getGeometry();
        if (geometry == null) returnwriter.writeStartElement("event");
        GeoPoint geoPoint = GeometryUtils.getPoint(geometry, 0);
 }
           writeElement("lat", Double.toString(geoPoint.getLatitude writeTime()));
        writeElement("lon", Double.toString(geoPoint.getLongitude()));
    if (domainParameterCount == 0) writeValue();
           writeElement("x", Double.toString(geoPoint.getX()));
 PiSerializerUtils.writeFlagsUserComment(writer, timeSeriesContent, version);
            if writeElement("y", Double.toString(geoPoint.getY()));
        writeElement("z", Double.toString(geoPoint.getZ()))(domainParameterCount == 0 || valueMissing) continue;
            int valuesPerRow = domainParameterCount == 1 ? 1 : domainAxesValues[1].length;
    }

      private void updateDateTimeStringCachewriteValues(long timefloatBuffer, int cacheIndexvaluesPerRow);
 {
        if (cachedTimes[cacheIndex] == time) return writer.writeEndElement();
        cachedTimes[cacheIndex] = time;}
    }

    cachedDateStrings[cacheIndex] = dateFormat.format(time);
private void writeValue() throws XMLStreamException {
        String cachedTimeStrings[cacheIndex]stringValue = timeFormattimeSeriesContent.formatgetValue(time'.');
    }

    private void writeTime(String name, long time, int cacheIndex) throws XMLStreamException {
writer.writeAttribute("value", stringValue);
        if (version.ordinal() >=  if (timePiVersion.VERSION_1_31.ordinal() && timeSeriesContent.getValueSource() == LongValueSource.MIN_VALUEMANUAL) returnwriter.writeAttribute("valueSource", "MAN");
        if writer(version.writeEmptyElementordinal(name);

        updateDateTimeStringCache(time, cacheIndex);
< PiVersion.VERSION_1_23.ordinal()) return;
        if writer(timeSeriesContent.writeAttribute("date", cachedDateStrings[cacheIndex])isValueMissing()) return;
        writer.writeAttribute("time", cachedTimeStrings[cacheIndex]float value = timeSeriesContent.getValue();
    }

    private void writeTimeStep(TimeSeriesHeader header) throws XMLStreamException {
        writer.writeEmptyElement("timeStep"if (timeSeriesContent.getMinValue() != value) writer.writeAttribute("minValue", timeSeriesContent.getMinValue('.'));
        TimeStep timeStep = header.getTimeStep();

if (timeSeriesContent.getMaxValue() != value) writer.writeAttribute("maxValue", timeSeriesContent.getMaxValue('.'));
         // todo add support for month time stepif (version.ordinal() >= PiVersion.VERSION_1_29.ordinal() && !"".equals(timeSeriesContent.getDetectionSymbol())) writer.writeAttribute("detection", timeSeriesContent.getDetectionSymbol());
    }

    if (timeStep.isEquidistantMillis())private void writeTime() throws XMLStreamException {
        long time =  writertimeSeriesContent.writeAttribute("unit", "second"getTime();
        String    int seconds = (int) (timeStep.getStepMillis() / 1000)dateText = dateFormat.format(time);
            if (cachedSeconds != seconds) {writer.writeAttribute("date", dateText);
        String timeText = timeFormat.format(time);
      cachedSecondsString = TextUtils.toString(secondswriter.writeAttribute("time", timeText);
        if (version.ordinal() < PiVersion.VERSION_1_23.ordinal()) return;
    cachedSeconds = seconds;
  long startTime = timeSeriesContent.getRangeStartTime();
       }
 long endTime = timeSeriesContent.getRangeEndTime();
        writer.writeAttribute("multiplier"startDate", dateFormat, startTime, time, cachedSecondsStringdateText);
        } else {
    writeAttribute("startTime", timeFormat, startTime, time, timeText);
        writer.writeAttribute("unit", "nonequidistant"endDate", dateFormat, endTime, time, dateText);
        writeAttribute("endTime" , timeFormat, endTime, time, }timeText);
    }

    private void writeEventswriteAttribute()String throwsattributeName, ExceptionFastDateFormat {
dateFormat, long time, long defaultTime, String defaultText) throws switch (eventDestination)XMLStreamException {
        if (time ==  case ONLY_HEADERS:defaultTime) return;
        String text       return= dateFormat.format(time);
        if (TextUtils.equals(text,   case SEPARATE_BINARY_FILE:
defaultText)) return;
        writer.writeAttribute(attributeName, text);
    }

    private int writeBinEventswriteAxisValues();
 throws XMLStreamException {
        TimeSeriesHeader header = timeSeriesContent.getTimeSeriesHeader();
  return;
      if (domainAxesValues == null) domainAxesValues = case XML_EMBEDDED:new float[header.getDomainParameterCount()][];
        if (newDomainValues == null) newDomainValues =   writeXmlEventsnew float[header.getDomainParameterCount()][];
         }assert !timeSeriesContent.isValueMissing();
    }

    private void writeBinEvents()int throwsres Exception= {1;
        for (int i = 0, n = timeSeriesContentheader.getContentTimeCountgetDomainParameterCount(); i < n; i++) {
            timeSeriesContent.setContentTimeIndex(i);
            if (!timeSeriesContent.isTimeAvailable()) continuefloat[] values = newDomainValues[i];
            ifint (bufferPosvalueCount == BUFFER_SIZE) flushBinEvents( timeSeriesContent.getDomainAxisValueCount(i);
            floatBuffer[bufferPos++] = timeSeriesContent.getValue()res *= valueCount;
        }
    if }

    private void flushBinEvents() throws IOException (values == null || values.length != valueCount) {
        if  (bufferPos == 0) return;
   values = new float[valueCount];
  BinaryUtils.copy(floatBuffer, 0, bufferPos, byteBuffer, 0, bufferPos * NumberType.FLOAT_SIZE, ByteOrder.LITTLE_ENDIAN);
      newDomainValues[i]  binaryOutputSteam.write(byteBuffer, 0, bufferPos * NumberType.FLOAT_SIZE);
= values;
            bufferPos}
 = 0;
    }

    private void writeXmlEvents() throws XMLStreamException {
timeSeriesContent.readDomainAxisValues(i, values);
            Properties properties = Properties.NONEif (Arrays.equals(values, domainAxesValues[i])) continue;
        for (int i = 0, n = timeSeriesContent.getContentTimeCount(); i < n; i++) {
 writer.writeStartElement("domainAxisValues");
              timeSeriesContent.setContentTimeIndexwriter.writeAttribute("parameterId", header.getDomainParameterId(i));
            int valuePerRow =  if (!timeSeriesContent.isTimeAvailable()) continuei == 0 ? 1 : values.length;
            Properties newProperties = timeSeriesContent.getProperties(writeValues(values, valuePerRow);
            if (!newProperties.equals(properties)) {domainAxesValues[i] = Clasz.floats.copyOfArray(values);
                properties = newPropertieswriter.writeEndElement();
        }
        return writeProperties(properties)res;
    }

    private void writeValues(float[] values, }
int valuesPerRow) throws XMLStreamException {
        long time = assert !timeSeriesContent.getTimeisValueMissing();
        char[] buffer =  writer.writeEmptyElement("event")this.charBuffer;
         if (buffer  writer.writeAttribute("date", dateFormat.format(time));
== null) {
            buffer  writer.writeAttribute("time", timeFormat.format(time))= new char[15];
            writer.writeAttribute("value", timeSeriesContent.getValue('.'))this.charBuffer = buffer;
            writer.writeAttribute("flag", timeSeriesContent.getStringFlag());}
        for (int i = if (version.getIntId() >= PiVersion.VERSION_1_11.getIntId())0; i < values.length; i++) {
            buffer[0] = i % StringvaluesPerRow flagSource == timeSeriesContent.getFlagSource();
    0 ? '\n' : '\t';
            iffloat (flagSourcevalue != null) writer.writeAttribute("flagSource", flagSource)values[i];
            int }endPos;
            if (versionMathUtils.getIntIdequals() >= PiVersion.VERSION_1_3.getIntId(value, missingValue)) {
                String comment = timeSeriesContent.getComment() missingValueText.getChars(0, missingValueText.length(), buffer, 1);
                if (commentendPos != nullmissingValueText.length() writer.writeAttribute("comment", comment)+ 1;
            }
      else {
      if (version.getIntId() >= PiVersion.VERSION_1_10.getIntId()) {
      endPos = TextUtils.format(buffer, 1, value,      String user = timeSeriesContent.getUser('.', 0, 10);
            }
    if (user != null)        writer.writeAttribute("user"writeCharacters(buffer, 0, userendPos);
  // start with space
        }
        writer.writeCharacters("\n    }    ");
    }

    private void writeOptionalElement(String elementName, String s) throws XMLStreamException {
        if (s == null) return;
        if (s.trim().isEmpty()) return;
        PiSerializerUtils.writeElement(writer, elementName, s);
    }

    private void writeElementwriteDomainAxis(String nameparameterId, String valueunit) throws XMLStreamException {
        writer.writeStartElementwriteEmptyElement(name"domainAxis");
        writer.writeCharacters(valuewriteAttribute("parameterId", parameterId);
        if (unit != null) writer.writeEndElement(writeAttribute("units", unit);
    }
}

 

...