/* ================================================================ * Delft FEWS * ================================================================ * * Project Info: http://www.wldelft.nl/soft/fews/index.html * Project Lead: Karel Heynert (karel.heynert@wldelft.nl) * * (C) Copyright 2003, by WL | Delft Hydraulics * P.O. Box 177 * 2600 MH Delft * The Netherlands * http://www.wldelft.nl * * DELFT-FEWS is a sophisticated collection of modules designed * for building a FEWS customised to the specific requirements * of individual agencies. An open modelling approach allows users * to add their own modules in an efficient way. * * ---------------------------------------------------------------- * SwissRadarTimeSeriesParser.java * ---------------------------------------------------------------- * (C) Copyright 2003, by WL | Delft Hydraulics * * Original Author: Jitka Tacoma */ package nl.wldelft.fews.system.plugin.dataImport; import nl.wldelft.util.TextUtils; import nl.wldelft.util.io.LineReader; import nl.wldelft.util.io.TextParser; import nl.wldelft.util.timeseries.DefaultTimeSeriesHeader; import nl.wldelft.util.timeseries.TimeSeriesContentHandler; import nl.wldelft.util.timeseries.TimeSeriesHeader; import nl.wldelft.util.timeseries.TimeSeriesHeadersConsumer; import org.apache.log4j.Logger; import java.io.IOException; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Locale; /** * SwissRadarTimeSeriesParser reads Swiss Radar file. * The file contains a single date/time set of values. Identification should be on Keywords: * Line containing “BASIN_TIME” holds the time of the readings * Line containing “BASIN_MISSING” holds the missing value indicator used * Line containing “ACCUM_UNIT” holds the unit. Other header lines can be ignored. * Lines containing “BASIN_VALUE” contain a reading. This also contains the ID of the location (e.g. SchussenGerbetshaus and the reading. *

* File example: * SOURCE: ACQUIRE * VERSION: 3.5 (5Dec06, uge) * ACCUM_N: 4 * ACCUM_PS: 1 2 6 12 * ACCUM_DT: 5.000000 * ACCUM_UNIT: millimetre * BASIN_MAP_N: 11 * BASIN_MAP_IDS: 100 101 102 110 500 501 502 503 504 505 506 *

* BASIN_TIME: 20Jan2007020 1200UTC * BASIN_MISSING: -99.000000 * BASIN_VALUE_505_003_SchussenGerbetshaus: 0.000316 * BASIN_VALUE_505_003_WuttachOberlauchringen: 0.007470 */ public class SwissRadarTimeSeriesParser implements TextParser, TimeSeriesHeadersConsumer { private static final Logger log = Logger.getLogger(SwissRadarTimeSeriesParser.class); private final static String KEY_UNIT = "ACCUM_UNIT"; private final static String KEY_MISSING_VALUE = "BASIN_MISSING"; private final static String KEY_TIME = "BASIN_TIME"; private final static String KEY_VALUE = "BASIN_VALUE"; private final static char KEY_DELIMITER = ':'; private static final long TIME_UNDEFINED = Long.MIN_VALUE; private static final float VALUE_UNDEFINED = Float.NEGATIVE_INFINITY; private final DateFormat dateFormat = new SimpleDateFormat("ddMMMyyyy HHmmz",Locale.US); private final DateFormat debugDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.US); private LineReader reader; private TimeSeriesContentHandler contentHandler; private HashMap wantedLocationsMap; private String headerUnit; private long headerTime; private float headerMissValue; private TimeSeriesHeader[] wantedHeaders = null; @SuppressWarnings({"AssignmentToCollectionOrArrayFieldFromParameter"}) @Override public void setTimeSeriesHeaders(TimeSeriesHeader[] timeSeriesHeaders) { this.wantedHeaders = timeSeriesHeaders; } @Override public void parse(LineReader reader, String virtualFileName, TimeSeriesContentHandler contentHandler) throws IOException { this.contentHandler = contentHandler; dateFormat.setTimeZone(this.contentHandler.getDefaultTimeZone()); this.reader = reader; readFile(); } private void readFile() throws IOException { //Initialize private fields that holds info about the file initialize(); //Read unit, missing value and time from the file. String firstDataLine = readHeader(); this.contentHandler.addMissingValue(this.headerMissValue); if (firstDataLine != null) { //Create headers and put them to the contentHandler setTimeSeriesHeaders(this.headerUnit); //Read data line and put values to the contentHanlder if (log.isDebugEnabled()) log.debug("Reading data for the header (unit,miss.value,time) : " + this.headerUnit + " , " + this.headerMissValue + " , " + debugDateFormat.format(new Date(this.headerTime))); readData(firstDataLine); } } private void initialize() { this.wantedLocationsMap = new HashMap(); this.headerUnit = null; this.headerTime = TIME_UNDEFINED; this.headerMissValue = VALUE_UNDEFINED; } /** * Read lines from the file up to the first line beginning with the key KEY_VALUE. * Parse unit, time and missing value from the header. * Return the first data line or null if any error ocurrs while reading header * * @throws IOException when any IO error occurs */ private String readHeader() throws IOException { String firstDataLine = null; boolean allKeys = false; String line; while ((line = this.reader.readLine()) != null) { if (line.startsWith(KEY_UNIT)) { this.headerUnit = getUnit(line); if (this.headerUnit == null) break; allKeys = getAllKeysFound(this.headerUnit, this.headerTime, this.headerMissValue); continue; } if (line.startsWith(KEY_TIME)) { this.headerTime = getTime(line); if (this.headerTime == TIME_UNDEFINED) break; allKeys = getAllKeysFound(this.headerUnit, this.headerTime, this.headerMissValue); continue; } if (line.startsWith(KEY_MISSING_VALUE)) { this.headerMissValue = getValue(line); if (Float.isInfinite(this.headerMissValue)) break; allKeys = getAllKeysFound(this.headerUnit, this.headerTime, this.headerMissValue); continue; } if (line.startsWith(KEY_VALUE)) { if (allKeys) { firstDataLine = line; } else { log.error("The file has wrong format, not all required keys " + KEY_UNIT + ',' + KEY_TIME + ',' + KEY_MISSING_VALUE + " specified before the key " + KEY_VALUE); } break; } } return firstDataLine; } /** * Initialize the columns in contentHandler with the headers. * * @param unit read from file header */ private void setTimeSeriesHeaders(String unit) { for (int i = 0; i < wantedHeaders.length; i++) { String loc = wantedHeaders[i].getLocationId(); DefaultTimeSeriesHeader header = new DefaultTimeSeriesHeader(); header.setLocationId(wantedHeaders[i].getLocationId()); header.setUnit(unit); this.contentHandler.createTimeSeriesHeaderAlias(i, header); if (this.wantedLocationsMap.get(loc) == null) { this.wantedLocationsMap.put(loc, new Integer(i)); //Remember the column index for each location } else { log.error("Location " + loc + " is configured more than once in the timeseries import configfile ? " + " Please check it. Parser continues reading data ...."); } } } /** * @param firstDataLine first line in the file with keyword KEY_VALUE * @throws IOException if any IO error occurs */ private void readData(String firstDataLine) throws IOException { this.contentHandler.setTime(this.headerTime); //Get location and value from the first line in the file. If loc. required, put the data in the handler putLocationAndValue(firstDataLine); String line; while ((line = this.reader.readLine()) != null) { if (!line.startsWith(KEY_VALUE)) continue; //Get location and value from the line. If loc. required, put the data in the handler putLocationAndValue(line); } } private void putLocationAndValue(String buffer) throws IOException { //Get location from the line and check whether it is required String location = getLocation(buffer); if (location == null) return; Object objColumn = this.wantedLocationsMap.get(location); if (objColumn == null) return; //not required //get value from the line float value = getValue(buffer); if (Float.isInfinite(value)) return; //Get column for this location and put data to the handler int column = ((Integer) objColumn).intValue(); this.contentHandler.setTimeSeriesHeader(column); this.contentHandler.setValue(value); this.contentHandler.applyCurrentFields(); } private boolean getAllKeysFound(String unit, long time, float missingValue) { if (unit != null && time != TIME_UNDEFINED && !Float.isInfinite(missingValue)) { return true; } else { return false; } } private String getUnit(String buffer) { String unit = TextUtils.rightFrom(buffer, KEY_DELIMITER); if (unit == null || unit.trim().length() < 1) { log.warn("No unit specified in the line: " + buffer); return null; } else { return unit.trim(); } } private long getTime(String buffer) { long time = TIME_UNDEFINED; String str = TextUtils.rightFrom(buffer, KEY_DELIMITER); if (str != null) { String[] subStr = TextUtils.split(str, ' '); if (subStr.length > 1) { String strDate = subStr[0].substring(0, 9); //get date specification to parse it with ddMMMyyyy String dateTimeString = strDate + ' ' + subStr[1]; //subStr[1] contains time in the format HHmmz time = parseTime(dateTimeString); } } if (time == TIME_UNDEFINED) { log.error("Time format not correct. It should be for date ddMMMyyyy and for time HHmmz. Line: " + buffer); } return time; } private float getValue(String buffer) { float value = VALUE_UNDEFINED; String str = TextUtils.rightFrom(buffer, KEY_DELIMITER); if (str != null) value = parseValue(str); if (Float.isInfinite(value)) { log.error("The line has a value in wrong format: " + buffer); } return value; } private String getLocation(String buffer) { String location = null; String substr = TextUtils.leftFrom(buffer, KEY_DELIMITER); if (substr != null) location = TextUtils.rightFromLast(substr.trim(), '_'); if (location == null || location.trim().length() < 1) { log.warn("No location specified in the line: " + buffer); return null; } else { return location.trim(); } } private long parseTime(String str) { long time; try { time = dateFormat.parse(str).getTime(); } catch (ParseException ex) { time = TIME_UNDEFINED; } return time; } private float parseValue(String buffer) { float value = VALUE_UNDEFINED; if (buffer != null) { String valueText = buffer.trim(); if (valueText.length() > 0) { try { value = TextUtils.parseFloat(valueText); } catch (NumberFormatException e) { value = VALUE_UNDEFINED; } } } return value; } }