package nl.wldelft.timeseriesparsers; import nl.wldelft.util.FastDateFormat; import nl.wldelft.util.TextUtils; import nl.wldelft.util.TimeSpan; import nl.wldelft.util.io.LineReader; import nl.wldelft.util.io.TextParser; import nl.wldelft.util.timeseries.ComplexEquidistantTimeStep; import nl.wldelft.util.timeseries.DefaultTimeSeriesHeader; import nl.wldelft.util.timeseries.SimpleEquidistantTimeStep; import nl.wldelft.util.timeseries.TimeSeriesContentHandler; import nl.wldelft.util.timeseries.TimeStep; import java.io.IOException; import java.text.ParseException; import java.util.Locale; /** * TimeSeries reader for TVA CorpsFlowsheet * * <p> * A detailed description can be found in JIRA issue FEWS-11705 *<pre> * * The date is assumed to be on the 2nd line, and always formatted exactly the same in terms of spacing and layout. * Daily values at midnight central time (GMT-7) period ending. Format yyyy-mm-dd * * The number of days in the forecast will be on the 3rd line * flow in 1000cfs. Elevation in ft. * * Example: * * Cumberland Flowsheet * Start_Date: 2014-10-01 * 11 day forecast * WolfCreek * In 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.0 * Elev 702.5 702.4 702.2 702.1 701.9 701.8 701.6 701.5 701.4 701.2 701.1 * Out 4.0 4.0 4.0 3.1 3.1 3.1 3.1 3.1 3.1 3.1 3.1 * MWH 1080.0 1080.0 1080.0 855.0 855.0 855.0 855.0 855.0 855.0 855.0 855.0 * *</pre> */ public class CorpsFlowsheetTimeSeriesParser implements TextParser<TimeSeriesContentHandler> { public static final int DAY_MILLISECONDS = 1000 * 60 * 60 * 24; private TimeSeriesContentHandler contentHandler = null; private final static char COLUMN_SEPARATOR_CHAR = ' '; private long numberOfDayForecast = 0L; private String[] buffer = null; private long startDate = 0L; private String currentLocation = null; private TimeStep equidistantTimeStep = null; private final static char decimalSeparator = '.'; @Override public void parse(LineReader reader, String virtualFileName, TimeSeriesContentHandler contentHandler) throws IOException { this.contentHandler = contentHandler; parseHeader(reader); while (true) { buffer = reader.readLine(COLUMN_SEPARATOR_CHAR); if (buffer == null) { break; } if (buffer.length == 1) { // a new location currentLocation = buffer[0]; } else { parseValues(reader, decimalSeparator, buffer); } } } private void parseHeader(LineReader reader) throws IOException { // skip the firste line. reader.readLine(); // On the second line we expect 2 columns in yyyy-dd-mm format. buffer = reader.readLine(COLUMN_SEPARATOR_CHAR); if (buffer == null || buffer.length != 2 || !TextUtils.equals("Start_Date:", buffer[0])) { throw new IOException("Start_Date hasn't been specified correctly. Should be specified as follows: Start_Date: yyyy-mm-dd.\n" + reader.getFileAndLineNumber()); } FastDateFormat dateFormat = FastDateFormat.getInstance("yyyy-MM-dd HHmm", this.contentHandler.getDefaultTimeZone(), Locale.US, null); try { this.startDate = dateFormat.parseToMillis(buffer[1]+" 0000"); } catch (ParseException e) { throw new IOException("Start_Date hasn't been formatted correctly. Should be specified as follows: Start_Date: yyyy-mm-dd.\n" + reader.getFileAndLineNumber()); } if (!this.contentHandler.getDefaultTimeZone().useDaylightTime()) { // use the simple equidistant timestep equidistantTimeStep = SimpleEquidistantTimeStep.getInstance(DAY_MILLISECONDS); } else { equidistantTimeStep = ComplexEquidistantTimeStep.getInstance(TimeSpan.DAY, this.contentHandler.getDefaultTimeZone()); } buffer = reader.readLine(COLUMN_SEPARATOR_CHAR); this.numberOfDayForecast = Long.parseLong(buffer[0]); } private void parseValues(LineReader reader, char decimalSeparator, String[] buffer) throws IOException { //parse values for current header. // for values we expect exactly the number of forecast days + 1 for the paramater. if (buffer.length != numberOfDayForecast + 1) { throw new IOException("Value rows should contain " + (numberOfDayForecast + 1) + " columns.\n" + reader.getFileAndLineNumber() + ' ' + buffer.length); } String parameter = buffer[0]; DefaultTimeSeriesHeader header = new DefaultTimeSeriesHeader(); header.setForecastTime(startDate); header.setLocationId(this.currentLocation); header.setParameterId(parameter); this.contentHandler.setTimeSeriesHeader(header); long forecastTime = startDate; for (int i = 0; i < numberOfDayForecast; i++) { //noinspection StringConcatenationMissingWhitespace forecastTime = equidistantTimeStep.nextTime(forecastTime); this.contentHandler.setTime(forecastTime); this.contentHandler.setValue(decimalSeparator, buffer[i + 1]); this.contentHandler.applyCurrentFields(); } } }