package nl.wldelft.fews.pi; import nl.wldelft.util.Arguments; import nl.wldelft.util.BinaryUtils; import nl.wldelft.util.Clasz; import nl.wldelft.util.FastDateFormat; import nl.wldelft.util.FileUtils; import nl.wldelft.util.MathUtils; import nl.wldelft.util.NumberType; import nl.wldelft.util.ObjectUtils; import nl.wldelft.util.Period; import nl.wldelft.util.Properties; import nl.wldelft.util.TextUtils; import nl.wldelft.util.TimeZoneUtils; import nl.wldelft.util.function.Supplier; import nl.wldelft.util.io.VirtualOutputDir; import nl.wldelft.util.io.VirtualOutputDirConsumer; import nl.wldelft.util.io.XmlChunkedSerializer; import nl.wldelft.util.timeseries.Product; import nl.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(); } private void writeProduct() throws XMLStreamException { Product product = timeSeriesContent.getProduct(); writer.writeStartElement("product"); writer.writeAttribute("id", product.getId()); writer.writeAttribute("name", product.getName()); writeTime("productDate", product.getProductTime(), 5); writer.writeEmptyElement("category"); writer.writeAttribute("id", product.getCategory().getId()); writer.writeAttribute("name", product.getCategory().getName()); if (product.getProductInfos() != null && product.getProductInfos().length > 0) { for (int i = 0; i < product.getProductInfos().length; i++) { ProductInfo productInfo = product.getProductInfos()[i]; writer.writeStartElement("productInfo"); writer.writeStartElement("user"); writer.writeCharacters(productInfo.getUser()); writer.writeEndElement(); writer.writeStartElement("confidence"); writer.writeCharacters(productInfo.getConfidence()); writer.writeEndElement(); writer.writeStartElement("classification"); writer.writeCharacters(productInfo.getClassification()); writer.writeEndElement(); writer.writeStartElement("comment"); writer.writeCharacters(productInfo.getComment()); writer.writeEndElement(); writer.writeEndElement(); // productInfo } } writer.writeEndElement(); // product } private void writeTimeSeriesStatistics() throws XMLStreamException { Statistics timeSeriesInfoStatistics = timeSeriesContent.getStatistics(); if (timeSeriesInfoStatistics == null) return; if (timeSeriesInfoStatistics.hasFirstValueTime()) writeTime("firstValueTime", timeSeriesInfoStatistics.getFirstValueTime(), 0); if (timeSeriesInfoStatistics.hasLastValueTime()) writeTime("lastValueTime", timeSeriesInfoStatistics.getLastValueTime(), 1); if (timeSeriesInfoStatistics.hasMaxValue()) PiSerializerUtils.writeElement(writer, "maxValue", timeSeriesInfoStatistics.getMaxValue('.')); if (timeSeriesInfoStatistics.hasMinValue()) PiSerializerUtils.writeElement(writer, "minValue", timeSeriesInfoStatistics.getMinValue('.')); if (timeSeriesInfoStatistics.hasValueCount()) PiSerializerUtils.writeElement(writer,"valueCount", Integer.toString(timeSeriesInfoStatistics.getValueCount())); if (timeSeriesInfoStatistics.hasMaxWarningLevelName()) PiSerializerUtils.writeElement(writer, "maxWarningLevelName", timeSeriesInfoStatistics.getMaxWarningLevelName()); } private void writeHighThresholds(TimeSeriesHeader header) throws XMLStreamException { boolean thresholdsAvailable = false; boolean firstThreshold = true; for (int i = 0; i < header.getHighLevelThresholdCount(); i++) { TimeSeriesHeader.Threshold threshold = header.getHighLevelThreshold(i); String id = threshold.getId(); String name = threshold.getName(); //Note: here value can be NaN for LevelThresholdValues that have different values for different aggregationTimeSpans configured. float value = threshold.getValue(); if (Float.isNaN(value)) { continue; } thresholdsAvailable = true; if (firstThreshold) { writer.writeStartElement("thresholds"); firstThreshold = false; } if (version.getIntId() >= PiVersion.VERSION_1_14.getIntId()) { String label = null; String comment = null; String description = null; if (version.getIntId() >= PiVersion.VERSION_1_22.getIntId()){ label = threshold.getLabel(); } if (version.getIntId() >= PiVersion.VERSION_1_27.getIntId()){ comment = threshold.getComment(); description = threshold.getDescription(); } if (threshold.getGroupCount() > 0) { for (int j = 0, m = threshold.getGroupCount(); j < m; j++) { String groupId = threshold.getGroupId(j); String groupName = threshold.getGroupName(j); if (threshold.getGroupCount() == 1 && groupId != null && groupId.equalsIgnoreCase("DeprecatedThresholdsConfigured")) { groupId = null; groupName = null; } writeThreshold(id, name, value, groupId, groupName, label, description, comment); } } else { writeThreshold(id, name, value, null, null, label, description, comment); } } else { writeThreshold(id, name, value, null, null, null, null, null); } } if (thresholdsAvailable) { writer.writeEndElement(); } } private void writeThreshold(String id, String name, float value, String groupId, String groupName, String label, String description, String comment) throws XMLStreamException { writer.writeStartElement("highLevelThreshold"); writer.writeAttribute("id", id); writer.writeAttribute("name", name); if (label != null) writer.writeAttribute("label", label); if (description != null) writer.writeAttribute("description", description); if (comment != null) writer.writeAttribute("comment", comment); writer.writeAttribute("value", Float.toString(value)); if (groupId != null) writer.writeAttribute("groupId", groupId); if (groupName != null) writer.writeAttribute("groupName", groupName); writer.writeEndElement(); } private void writePeriod() throws XMLStreamException { TimeStep timeStep = timeSeriesContent.getTimeSeriesHeader().getTimeStep(); Period period = timeSeriesContent.getTimeSeriesPeriod(); Period headerPeriod; if (period == Period.NEVER) { // create a dummy period long now = timeStep.nearestTime(System.currentTimeMillis()); headerPeriod = new Period(now, now); } else { headerPeriod = period; } writeTime("startDate", headerPeriod.getStartTime(), 0); writeTime("endDate", headerPeriod.getEndTime(), 1); } private void updateDateTimeStringCache(long time, int cacheIndex) { if (cachedTimes[cacheIndex] == time) return; cachedTimes[cacheIndex] = time; cachedDateStrings[cacheIndex] = dateFormat.format(time); cachedTimeStrings[cacheIndex] = timeFormat.format(time); } private void writeTime(String name, long time, int cacheIndex) throws XMLStreamException { if (time == Long.MIN_VALUE) return; writer.writeEmptyElement(name); updateDateTimeStringCache(time, cacheIndex); writer.writeAttribute("date", cachedDateStrings[cacheIndex]); writer.writeAttribute("time", cachedTimeStrings[cacheIndex]); } private void writeTimeStep(TimeSeriesHeader header) throws XMLStreamException { writer.writeEmptyElement("timeStep"); TimeStep timeStep = header.getTimeStep(); // todo add support for month time step if (timeStep.isEquidistantMillis()) { writer.writeAttribute("unit", "second"); int seconds = (int) (timeStep.getStepMillis() / 1000); if (cachedSeconds != seconds) { cachedSecondsString = TextUtils.toString(seconds); cachedSeconds = seconds; } writer.writeAttribute("multiplier", cachedSecondsString); } else if (timeStep instanceof TimesOfDayDaylightSavingTimeStep && version.getIntId() >= PiVersion.VERSION_1_18.getIntId()) { writer.writeAttribute("times", PiCastorUtils.getTimesOfDayString((TimesOfDayDaylightSavingTimeStep) timeStep)); } else if (timeStep instanceof TimesOfDayTimeStep && version.getIntId() >= PiVersion.VERSION_1_18.getIntId()) { writer.writeAttribute("times", PiCastorUtils.getTimesOfDayString((TimesOfDayTimeStep) timeStep)); } else if (timeStep instanceof TimesOfHourTimeStep && version.getIntId() >= PiVersion.VERSION_1_24.getIntId()) { writer.writeAttribute("minutes", PiCastorUtils.getTimesOfHourString((TimesOfHourTimeStep) timeStep)); } else { writer.writeAttribute("unit", "nonequidistant"); } } private void writeEvents() throws Exception { switch (eventDestination) { case ONLY_HEADERS: return; case SEPARATE_BINARY_FILE: writeBinEvents(); return; case XML_EMBEDDED: writeXmlEvents(); } } private void writeBinEvents() throws Exception { for (int i = 0, n = timeSeriesContent.getContentTimeCount(); i < n; i++) { timeSeriesContent.setContentTimeIndex(i); if (!timeSeriesContent.isTimeAvailable()) continue; if (bufferPos == BUFFER_SIZE) flushBinEvents(); floatBuffer[bufferPos++] = timeSeriesContent.getValue(); } } private void flushBinEvents() throws IOException { if (bufferPos == 0) return; BinaryUtils.copy(floatBuffer, 0, bufferPos, byteBuffer, 0, bufferPos * NumberType.FLOAT_SIZE, ByteOrder.LITTLE_ENDIAN); binaryOutputSteam.write(byteBuffer, 0, bufferPos * NumberType.FLOAT_SIZE); bufferPos = 0; } private void writeXmlEvents() throws XMLStreamException { Properties properties = Properties.NONE; TimeSeriesHeader header = timeSeriesContent.getTimeSeriesHeader(); domainAxesValues = null; int domainParameterCount = header.getDomainParameterCount(); float[] floatBuffer = this.floatBuffer; for (int i = 0, n = timeSeriesContent.getContentTimeCount(); i < n; i++) { timeSeriesContent.setContentTimeIndex(i); if (!timeSeriesContent.isTimeAvailable()) continue; Properties newProperties = timeSeriesContent.getProperties(); if (!newProperties.equals(properties)) { properties = newProperties; PiSerializerUtils.writeProperties(properties, version, writer, dateFormat, timeFormat); } boolean valueMissing = timeSeriesContent.isValueMissing(); if (domainParameterCount > 0 && !valueMissing) { int valueCount = writeAxisValues(); if (floatBuffer == null || floatBuffer.length != valueCount) { floatBuffer = new float[valueCount]; this.floatBuffer = floatBuffer; } timeSeriesContent.readValues(floatBuffer); } if (domainParameterCount == 0 || valueMissing) { writer.writeEmptyElement("event"); } else { writer.writeStartElement("event"); } writeTime(); if (domainParameterCount == 0) writeValue(); PiSerializerUtils.writeFlagsUserComment(writer, timeSeriesContent, version); if (domainParameterCount == 0 || valueMissing) continue; int valuesPerRow = domainParameterCount == 1 ? 1 : domainAxesValues[1].length; writeValues(floatBuffer, valuesPerRow); writer.writeEndElement(); } } private void writeValue() throws XMLStreamException { String stringValue = timeSeriesContent.getValue('.'); writer.writeAttribute("value", stringValue); if (version.ordinal() >= PiVersion.VERSION_1_31.ordinal() && timeSeriesContent.getValueSource() == ValueSource.MANUAL) writer.writeAttribute("valueSource", "MAN"); if (version.ordinal() < PiVersion.VERSION_1_23.ordinal()) return; if (timeSeriesContent.isValueMissing()) return; float value = timeSeriesContent.getValue(); if (timeSeriesContent.getMinValue() != value) writer.writeAttribute("minValue", timeSeriesContent.getMinValue('.')); if (timeSeriesContent.getMaxValue() != value) writer.writeAttribute("maxValue", timeSeriesContent.getMaxValue('.')); if (version.ordinal() >= PiVersion.VERSION_1_29.ordinal() && !"".equals(timeSeriesContent.getDetectionSymbol())) writer.writeAttribute("detection", timeSeriesContent.getDetectionSymbol()); } private void writeTime() throws XMLStreamException { long time = timeSeriesContent.getTime(); String dateText = dateFormat.format(time); writer.writeAttribute("date", dateText); String timeText = timeFormat.format(time); writer.writeAttribute("time", timeText); if (version.ordinal() < PiVersion.VERSION_1_23.ordinal()) return; long startTime = timeSeriesContent.getRangeStartTime(); long endTime = timeSeriesContent.getRangeEndTime(); writeAttribute("startDate", dateFormat, startTime, time, dateText); writeAttribute("startTime", timeFormat, startTime, time, timeText); writeAttribute("endDate", dateFormat, endTime, time, dateText); writeAttribute("endTime" , timeFormat, endTime, time, timeText); } private void writeAttribute(String attributeName, FastDateFormat dateFormat, long time, long defaultTime, String defaultText) throws XMLStreamException { if (time == defaultTime) return; String text = dateFormat.format(time); if (TextUtils.equals(text, defaultText)) return; writer.writeAttribute(attributeName, text); } private int writeAxisValues() throws XMLStreamException { TimeSeriesHeader header = timeSeriesContent.getTimeSeriesHeader(); if (domainAxesValues == null) domainAxesValues = new float[header.getDomainParameterCount()][]; if (newDomainValues == null) newDomainValues = new float[header.getDomainParameterCount()][]; assert !timeSeriesContent.isValueMissing(); int res = 1; for (int i = 0, n = header.getDomainParameterCount(); i < n; i++) { float[] values = newDomainValues[i]; int valueCount = timeSeriesContent.getDomainAxisValueCount(i); res *= valueCount; if (values == null || values.length != valueCount) { values = new float[valueCount]; newDomainValues[i] = values; } timeSeriesContent.readDomainAxisValues(i, values); if (Arrays.equals(values, domainAxesValues[i])) continue; writer.writeStartElement("domainAxisValues"); writer.writeAttribute("parameterId", header.getDomainParameterId(i)); int valuePerRow = i == 0 ? 1 : values.length; writeValues(values, valuePerRow); domainAxesValues[i] = Clasz.floats.copyOfArray(values); writer.writeEndElement(); } return res; } private void writeValues(float[] values, int valuesPerRow) throws XMLStreamException { assert !timeSeriesContent.isValueMissing(); char[] buffer = this.charBuffer; if (buffer == null) { buffer = new char[15]; this.charBuffer = buffer; } for (int i = 0; i < values.length; i++) { buffer[0] = i % valuesPerRow == 0 ? '\n' : '\t'; float value = values[i]; int endPos; if (MathUtils.equals(value, missingValue)) { missingValueText.getChars(0, missingValueText.length(), buffer, 1); endPos = missingValueText.length() + 1; } else { endPos = TextUtils.format(buffer, 1, value, '.', 0, 10); } writer.writeCharacters(buffer, 0, endPos); // 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 writeDomainAxis(String parameterId, String unit) throws XMLStreamException { writer.writeEmptyElement("domainAxis"); writer.writeAttribute("parameterId", parameterId); if (unit != null) writer.writeAttribute("units", unit); } }