package nl.wldelft.timeseriesparsers; import nl.wldelft.util.TextUtils; import nl.wldelft.util.TimeZoneUtils; import nl.wldelft.util.io.XmlParser; import nl.wldelft.util.timeseries.DefaultTimeSeriesHeader; import nl.wldelft.util.timeseries.TimeSeriesContentHandler; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * * @autor pelgrim * @author ekkelenk * Date: 8/9/13 * Time: 9:35 AM * Import the warnings from the AifsML format */ public class AifsMLTimeSeriesParser implements XmlParser<TimeSeriesContentHandler> { public static final String HYFS_MARKER = "[HYFS]"; private TimeSeriesContentHandler contentHandler = null; private XMLStreamReader reader = null; private DefaultTimeSeriesHeader header = null; //Use for all private String title = null; private String identifier = null; private String externalForecastTime = null; @Override public void parse(XMLStreamReader reader, String virtualFileName, TimeSeriesContentHandler contentHandler) throws Exception { this.reader = reader; this.contentHandler = contentHandler; header = new DefaultTimeSeriesHeader(); reader.require(XMLStreamConstants.START_DOCUMENT, null, null); reader.nextTag(); reader.require(XMLStreamConstants.START_ELEMENT, null, "product"); parseIdentifier(); if (!XmlStreamReaderUtils.goTo(reader, "warning")) { // There has to be a warning element. throw new IOException("Input file doesn't contain a warning element: " + virtualFileName); } reader.require(XMLStreamConstants.START_ELEMENT, null, "warning"); reader.nextTag(); reader.require(XMLStreamConstants.START_ELEMENT, null, "warning-info"); parseWarningInfo(); parseWarningAreas(); } /** * Retrieve the identifier from the source element * * @throws XMLStreamException; */ private void parseIdentifier() throws XMLStreamException { XmlStreamReaderUtils.goTo(reader, "identifier"); if (TextUtils.equals(reader.getLocalName(), "identifier")) { this.identifier = reader.getElementText(); } } /** * filter the warning sequence and warning title text attributes from the text elements. * they will be used for all areas. * * @throws XMLStreamException */ private void parseWarningInfo() throws XMLStreamException { do { if (!XmlStreamReaderUtils.goTo(reader, "text", "warning-info")) { // no more text areas, return. return; } // we only need the the warning title. if (this.title == null) { String type = reader.getAttributeValue(null, "type"); if (TextUtils.equals("warning_title", type)) { this.title = reader.getElementText(); } } if (this.title != null) { // we're finished with the text elements, skip to the areas return; } } while (reader.hasNext()); } /** * Retrieve the locationId from the area element(s) and call separate method to parse forecast data * * @throws XMLStreamException; */ private void parseWarningAreas() throws XMLStreamException { // parse alle areas binnen een warning element. do { if (!XmlStreamReaderUtils.goTo(reader, "area", "warning")) { // alle area's have been processed or an end tag warning has been found. return; } String locationId = reader.getAttributeValue(null, "aac"); // an area can have more that one fore-cast periods. parseForecastData(locationId); } while (reader.hasNext()); } /** * helper class to store forecast data. */ private static class ForecastData { private String parameter = null; private String value = null; private String localTime = null; private String comment = null; } /** * Retrieve the variable, value, unit, time and comment from the forecast data element(s) * Retrieve comment text from forecast text element * return true if more forecast periods are available * * @throws XMLStreamException; */ private void parseForecastData(String locationId) throws XMLStreamException { // not every area has a forecast-period. Try to find the next forecast-period, // but stop if not found, or an end-tag area has been found. List<ForecastData> forecastDataList = new ArrayList<>(); while (XmlStreamReaderUtils.goTo(reader, "forecast-period", "area")) { this.externalForecastTime = reader.getAttributeValue(null, "start-time-utc"); // inside the forecast-period we need to process the text elements first. do { // process all elements until we reach the end tag forecast-period. if (!XmlStreamReaderUtils.goTo(reader, "text", "forecast-period")) { // break so we can write this forecast as a timeseries. break; } String sequence = reader.getAttributeValue(null, "type"); if (!TextUtils.equals("river_level_prediction", sequence)) { // we only want the river level prediction. continue; } // we found a river_level_prediction. String value = null; String comment = null; String time = null; String riverLevelPredictionText = reader.getElementText(); int index = riverLevelPredictionText.indexOf(HYFS_MARKER); if (index != -1) { String valueComment = riverLevelPredictionText.substring(index + HYFS_MARKER.length()); int split = valueComment.indexOf(","); if (split != -1) { value = valueComment.substring(0, split); comment = valueComment.substring(split + 1); time = parseTime(); } } if (time != null) { ForecastData forecastData = new ForecastData(); forecastData.comment = comment; forecastData.localTime = time; forecastData.value = value; forecastData.parameter = "prediction"; forecastDataList.add(forecastData); } } while (reader.hasNext()); // All events have been collected writeTimeSerie(locationId, forecastDataList); } } private String parseTime() throws XMLStreamException { // if we get here, a river_level_prediction was found. No we need to parse all elements until we find the end of the forecast period do { // process all elements until we reach the end tag forecast-period. if (!XmlStreamReaderUtils.goTo(reader, "element", "forecast-period")) { // no element found return null; } String typeAttribute = reader.getAttributeValue(null, "type"); if (TextUtils.equals("time", typeAttribute)) { String timeString = reader.getElementText(); int index = timeString.indexOf(HYFS_MARKER); if (index != -1) { return timeString.substring(index + HYFS_MARKER.length()); } } } while (reader.hasNext()); return null; } /** * Write a time serie header and it's events based on the forecastData. * @param locationId location of the timeserie * @param externalForecastTime forecast time. * @param forecastCollection collection of forecast events. */ private void writeTimeSerie(String locationId, Collection<ForecastData> forecastCollection) { //noinspection SpellCheckingInspection header.setForecastTime(TimeZoneUtils.GMT, "yyyy-MM-dd'T'HH:mm:ss'Z'", this.externalForecastTime); header.setLocationId(locationId); for (ForecastData forecastData : forecastCollection) { contentHandler.setComment(identifier + ": " + title + '\n' + forecastData.comment); header.setParameterId(forecastData.parameter); contentHandler.setValue('.', forecastData.value); // header.setUnit(forecastData.unit); long rawTimeZoneOffset = TimeZoneUtils.parseRawTimeZoneOffset(forecastData.localTime.substring(forecastData.localTime.length() - 6)); contentHandler.setTime(TimeZoneUtils.getTimeZone(rawTimeZoneOffset), "yyyy-MM-dd'T'HH:mm:ss", forecastData.localTime.substring(0, forecastData.localTime.length() - 6)); contentHandler.setTimeSeriesHeader(header); contentHandler.applyCurrentFields(); } } }