...
Code Block |
---|
package nl.wldelft.fews.system.plugin.dataImport; import nl.wldelft.util.ExceptionUtils; import nl.wldelft.util.FastGregorianCalendar; import nl.wldelft.util.TextUtils; import nl.wldelft.util.io.FileParser; import nl.wldelft.util.timeseries.DefaultTimeSeriesHeader; import nl.wldelft.util.timeseries.RelativeEquidistantTimeStep; import nl.wldelft.util.timeseries.SimpleEquidistantTimeStep; import nl.wldelft.util.timeseries.TimeSeriesContentHandler; import nl.wldelft.util.timeseries.TimeStep; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendarList; import java.util.ListLocale; import java.util.regex.Pattern; /** * TimeSeries parser for SHEF version 2.0. * http://www.nws.noaa.gov/os/hod/SHManual/SHMan051_shef.htm * http://www.nws.noaa.gov/om/water/resources/SHEF_CodeManual_5July2012.pdf */ public class ShefTimeSeriesParser implements FileParser<TimeSeriesContentHandler> { private static final Logger log = LogManager.getLogger(); public static final String readerType = "SHEF"; private static final String SINGLE_LOCATION_MULTIPLE_PARAMETERS_TYPE = ".A"; private static final String END_TOKEN_TYPE = ".END"; private static final Pattern COMPILE_PATTERN_B_HEADER = Pattern.compile(".B|.BR"); // B type record private static final Pattern COMPILE_PATTERN_A_CONTINUATION = Pattern.compile(".A\\d+"); // Start with a .A followed by one or more digits. private static final Pattern COMPILE_PATTERN_B_CONTINUATION = Pattern.compile(".B\\d+|.BR\\d+"); // Start with a .B followed by one or more digits. private static final String SINGLE_LOCATION_MULTIPLE_PARAMETERS_CONTINUATION_TYPE = ".AR"; private static final Pattern COMPILE_PATTERN_ER_AR = Pattern.compile("\\.ER?$|.AR?$"); private static final Pattern COMPILE_PATTERN_ER = Pattern.compile("\\.ER?$"); private static final Pattern COMPILE_PATTERN_ER_D_AR_D = Pattern.compile("\\.ER?\\d+|\\.AR?\\d+"); private static final Pattern COMPILE_PATTERN_D_STAR = Pattern.compile("DS.*|DN.*|DH.*|DD.*|DM.*|DY.*|DJ.*|DR.*"); private static final Pattern COMPILE_PATTERN_D = Pattern.compile("D.*|"); private static final Pattern COMPILE_PATTERN_DU = Pattern.compile("DU.*|"); private static final Pattern COMPILE_PATTERN_DI = Pattern.compile("DI.*"); private static final Pattern COMPILE_PATTERN_DH_DD = Pattern.compile("DH\\d\\d.*"); private static final Pattern COMPILE_PATTERN_START_DIGIT = Pattern.compile("[^0-9]"); private static final Pattern COMPILE_PATTERN_DIH = Pattern.compile(".*DIH.*"); private static final Pattern COMPILE_PATTERN_DIN = Pattern.compile(".*DIN.*"); private static final Pattern COMPILE_PATTERN_DID = Pattern.compile(".*DID.*"); private static final Pattern COMPILE_PATTERN_DR_SHIFT = Pattern.compile("DRS*[+-]\\d+|DRN*[+-]\\d+|DRH*[+-]\\d+|DRD*[+-]\\d+|DRM*[+-]\\d+|DRD*[+-]\\d+"); private static final char quoteChar = '\"'; private CalendarFastGregorianCalendar calendar = new GregorianCalendar()null; private TimeSeriesContentHandler contentHandler = null; // Variables for parsing the file private long time = 0; private long dtime = 0; private boolean separatorOnLastLine = false; private static final int E_TYPE = 0; private static final int A_TYPE = 1; private int messageType = 0; private String aContinuationLocationId = null; // string array contains all fields before the first / separator private int firstSlashSeparatorIdx = 0; @Override public void parse(File file, TimeSeriesContentHandler contentHandler) throws Exception { this.contentHandler = contentHandler; calendar.setTimeZone = new FastGregorianCalendar(this.contentHandler.getDefaultTimeZone(), Locale.US); boolean isValid = readFile(file); if (!isValid) { throw new IOException("Error parsing: " + file.getName()); } } private boolean readFile(File file) throws Exception { BufferedReader reader = null; boolean done = false; //Open file try { //noinspection resource reader = new BufferedReader(new FileReader(file)); done = true; } catch (FileNotFoundException e) { log.error(file + " could not be opened.", e); } //Read/parse the file if (done) { try { if (!parseFile(reader)) { done = false; log.error("The file " + file + " has unknown format."); } } catch (IOException e) { done = false; log.error("Error while reading the file " + file + " : " + ExceptionUtils.getMessage(e), e); } closeReader(reader); } return done; } private static void closeReader(BufferedReader reader) { try { reader.close(); } catch (IOException e) { log.error("Cannot close file " + reader + " : " + ExceptionUtils.getMessage(e), e); } } /** * Read file content and store it into the memory * Comments on the SHEF file format: * - The method recognises .ER and .Ed lines only (d=digit) * - All other lines are ignored at the moment * <p/> * Note: * We assume that the fields are separated by spaces and that the * .ER and .E records do not contain timeseries data! * * @return * @throws IOException */ private boolean parseFile(BufferedReader reader) throws Exception { boolean okay = true; String line; while ((line = reader.readLine()) != null && okay) { StringBuilder commentFreeLine = removeCommentsFromLine(line); String[] pieces = TextUtils.split(commentFreeLine.toString(), ':', '\0', quoteChar, false); //noinspection UnusedAssignment String[] fields = TextUtils.split(pieces[0], ' ', '\0', quoteChar, true); // get fields, first part is split by space next part by '/' // first split with separator '/' will split up line in first part containing spaces (position part) // followed by the datastring fields (separated by '/') String[] tmpPieces = pieces[0].split("/"); String[] positionFields = TextUtils.split(tmpPieces[0], ' ', '\"'); firstSlashSeparatorIdx = positionFields.length; fields = new String[tmpPieces.length + positionFields.length - 1]; System.arraycopy(positionFields, 0, fields, 0, positionFields.length); System.arraycopy(tmpPieces, 1, fields, positionFields.length, tmpPieces.length - 1); //Header lines start with .E or .ER (but may comtain data) //Data-only lines start with .Ed or .ERd - d a digit if (fields.length > 0) { if (COMPILE_PATTERN_A_CONTINUATION.matcher(fields[0]).matches()) { parseMultiParameterContinuationSeriesData(fields); } else if (COMPILE_PATTERN_ER_AR.matcher(fields[0]).matches()) { this.messageType = COMPILE_PATTERN_ER.matcher(fields[0]).matches() ? E_TYPE : A_TYPE; // bug in OHD output for .A messages. missing '/' slash between parameter id and // value. Remove this when fixed if (this.messageType == A_TYPE) { String[] splitfields = TextUtils.split(fields[fields.length - 1], ' ', '\"'); if (splitfields.length > 1) { String[] tmpFields = new String[fields.length + 1]; System.arraycopy(fields, 0, tmpFields, 0, fields.length - 1); System.arraycopy(splitfields, 0, tmpFields, fields.length - 1, 2); fields = tmpFields; } } if (SINGLE_LOCATION_MULTIPLE_PARAMETERS_TYPE.equals(fields[0])) { // .A type no headers all data is on one line with multiple parameters. parseMultiParameterSeriesData(fields); } else { // .AR type. Revision on earlier measurement. Assumption is that only one parameter at a time is passed. okay = getSeriesParameters(fields); if (okay) { // see if there are any values on this row, if so start fill series data // because parameter and timestep are mandatory values can be started // from firstSlashSeparator untill end of fields if (fields.length == (firstSlashSeparatorIdx + 2) && SINGLE_LOCATION_MULTIPLE_PARAMETERS_CONTINUATION_TYPE.equals(fields[0])) { String value = fields[fields.length - 1]; okay = getContinuationSeriesData(value); } if (fields.length > firstSlashSeparatorIdx + 2) { for (int i = firstSlashSeparatorIdx + 2; i < fields.length; i++) { if (isFloat(fields[i])) { String[] values = new String[fields.length - i]; System.arraycopy(fields, i, values, 0, fields.length - i); okay = getSeriesData(values); break; } } } } } } else if (COMPILE_PATTERN_B_HEADER.matcher(fields[0]).matches()) { // parse complete .B type record parseMultipleLocationMultipleParametersSeries(fields, reader); } else if (COMPILE_PATTERN_ER_D_AR_D.matcher(fields[0]).matches()) { // continued line String[] datafields; if (positionFields.length == 1 && separatorOnLastLine) { // slash between rowcontinuation and first value. If previous line ended with a slash // a null value is assumed fields[0] = null; datafields = fields; } else { // skip first column with normal linecontinuation datafields = new String[fields.length - 1]; System.arraycopy(fields, 1, datafields, 0, fields.length - 1); } okay = getSeriesData(datafields); } separatorOnLastLine = line.endsWith("/"); } } return okay; } private static StringBuilder removeCommentsFromLine(String line) { //Split the line into separate fields: //Remove any comment first String[] piecesWithComments = TextUtils.split(line, ':', ':', quoteChar, true); StringBuilder commentFreeLine = new StringBuilder(line.length()); if (!line.contains(":")) { commentFreeLine.append(line); } else { int togglePosition = 0; if (line.startsWith(":")) { togglePosition = 1; // if the line starts with a : , the first entry should be skipped. } if (piecesWithComments.length > 0) { // we found some comments. for (int i = 0; i < piecesWithComments.length; i++) { if (i % 2 == togglePosition) { // the comment toggle is off. // see: http://www.nws.noaa.gov/om/water/resources/SHEF_CodeManual_5July2012.pdf commentFreeLine.append(piecesWithComments[i]); } } } } else { return commentFreeLine; } commentFreeLine.append(line); } return commentFreeLine; } private static boolean isFloat(String value) { try { //noinspection UnusedDeclaration Float f = TextUtils.parseFloat(value); } catch (NumberFormatException e) { return false; } return true; } // Read a complete set of .B type records spanning multiple lines private void parseMultipleLocationMultipleParametersSeries(String[] fields, BufferedReader reader) throws Exception { // get time, as a combination of date, [observation time] String date = fields[2]; int ifield = 3; while (!COMPILE_PATTERN_D_STAR.matcher(fields[ifield]).matches()) { ifield++; } String observationTime = fields[ifield]; // The start date/time, and the time step: time = parseDate(date, observationTime); // skip optional D* fields while (COMPILE_PATTERN_D.matcher(fields[ifield]).matches()) { ifield++; } // The list of parameter/time shift headers, including .Bp continuation lines, if any String line; String header; List<String> headerList = new ArrayList<>(); do { for (int i = ifield; i < headerList.addAll(Arrays.asList(fields).subList(ifield, fields.length));fields.length; i++) { if (!COMPILE_PATTERN_DU.matcher(fields[i]).matches()) headerList.add(fields[i]); } // continue on next line ? line = removeCommentsFromLine(reader.readLine()).toString(); line = line.replace(" ", " "); int ix = line.indexOf(" "); header = ix > 0 ? line.substring(0, ix) : ""; String newline = line.substring(ix + 1).replace(" ", ""); fields = TextUtils.split(newline, '/'); // some inconsistency in '/' separators on continuation lines ! ifield = TextUtils.equals(fields[0].trim(), "") ? 1 : 0; } while (COMPILE_PATTERN_B_CONTINUATION.matcher(header).matches()); // The data lines do { line = removeCommentsFromLine(line).toString(); line = line.replace(" ", " "); if (!line.isEmpty()) { int ix = line.indexOf(" "); String location = line.substring(0, ix); line = line.substring(ix + 1).replace(" ", ""); fields = TextUtils.split(line, '/'); writeBFormatLineContent(time, location, headerList, fields); } } while ((line = reader.readLine()) != null && !line.contains(END_TOKEN_TYPE)); } private void writeBFormatLineContent(long time, String location, List<String> headerList, String[] fields) throws Exception { DefaultTimeSeriesHeader timeSeriesHeader = new DefaultTimeSeriesHeader(); timeSeriesHeader.setLocationId(location); long startTime = time; int iheaderiHeader = 0; for (int ifieldiField = 0; ifieldiField < fields.length; ifieldiField++) { // in addition to parameter names, header may contain "DRx" date/time shift codes while (COMPILE_PATTERN_DR_SHIFT.matcher(headerList.get(iheaderiHeader)).matches()) { time = applyDateTimeShift(startTime, headerList.get(iheaderiHeader)); iheaderiHeader++; } // in addition to location code, line may contain date-time override code while (COMPILE_PATTERN_D_STAR.matcher(fields[ifieldiField]).matches()) { time = applyDateTimeOverride(startTime, fields[ifieldiField]); ifieldiField++; } timeSeriesHeader.setParameterId(headerList.get(iheaderiHeader)); String text = fields[ifieldiField].trim(); float value = TextUtilstext.equalsisEmpty("", text) || TextUtils.equals("+", text) || TextUtils.equals("M", text) ? Float.NaN : Float.parseFloat(text); contentHandler.setTimeSeriesHeader(timeSeriesHeader); contentHandler.setTime(time); contentHandler.setValue(value); contentHandler.applyCurrentFields(); iheaderiHeader++; } } private long applyDateTimeOverride(long time, String token) throws Exception { if (!COMPILE_PATTERN_D_STAR.matcher(token).matches()) return time; calendar.setTimeInMillis(time); String value = token.substring(2); String code = token.substring(0, 2); switch (code) { case "DY": calendar.set(Calendar.YEAR, Integer.parseInt(value.substring(0, 2))); time = applydateTimeYear(value, true); break; calendar.set(Calendar.MONTH, Integer.parseInt(value.substring(2, 4))); case "DM": calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(value.substring(4, 6))time = applyDateTimeMonth(value, true); calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(value.substring(6, 8)))break; calendar.set(Calendar.MINUTE, Integer.parseInt(value.substring(8, 10)));case "DD": if (value.length() > 10) calendar.set(Calendar.SECOND, Integer.parseInt(value.substring(10))time = applyDateTimeDay(value); break; case "DMDH": calendar.set(Calendar.MONTH, Integer.parseInt(value.substring(0, 2))time = applyDateTimeHour(value); calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(value.substring(2, 4)))break; case "DN": time = applyDateTimeMinute(value); break; case "DS": time = applyDateTimeSeconds(value); break; default: throw new Exception("ShefTimeSeriesParser: invalid 'Date/Time override code' : " + token); } return time; } private long applydateTimeYear(String value, boolean fixCentury) { int i = Integer.parseInt(value.substring(0, 2)); if (fixCentury) { // see SHEF coding manual 4.1.4 : a 10 year in the future and 90 year in the past window is used to assign the century int y = calendar.get(Calendar.YEAR); int c = y / 100; if (100 * c + i > y + 10) c--; i = 100 * c + i; } calendar.set(Calendar.HOUR_OF_DAY, Integer.parseIntYEAR, i); return applyDateTimeMonth(value.substring(42), 6))false); } private long applyDateTimeMonth(String value, boolean fixYear) { int calendar.set(Calendar.MINUTE,i = Integer.parseInt(value.substring(60, 8)2)); if (fixYear) { if (value.length() > 8) calendar.set(Calendar.SECOND, Integer.parseInt(value.substring(8))); break; case "DD": // see SHEF coding manual 4.1.4 : a 12-month window is used to assign the year that causes the date code to be nearest the system/start date int m = calendar.setget(Calendar.DAY_OF_MONTH, Integer.parseInt(value.substring(0, 2))).MONTH) +1; int y = calendar.setget(Calendar.HOUR_OF_DAY, Integer.parseInt(value.substring(2, 4))YEAR); if (i > calendar.set(Calendar.MINUTE, Integer.parseInt(value.substring(4,m + 6))); { y--; } else if (value.length() > 6) calendar.set(Calendar.SECOND, Integer.parseInt(value.substring(6)));i <= m - 6) { breaky++; case "DH": } calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(value.substring(0, 2))YEAR, y); } calendar.set(Calendar.MINUTEMONTH, Integer.parseInt(value.substring(2, 4)) i - 1); if return applyDateTimeDay(value.lengthsubstring(2) > 4) {); } private long applyDateTimeDay(String value) { calendar.set(Calendar.SECONDDATE, Integer.parseInt(value.substring(40, 62))); return applyDateTimeHour(value.substring(2)); } private long applyDateTimeHour(String value) }{ breakcalendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(value.substring(0, 2))); return applyDateTimeMinute(value.substring(2)); case "DN": } private long applyDateTimeMinute(String value) { calendar.set(Calendar.MINUTE, Integer.parseInt(value.substring(0, 2))); if return applyDateTimeSeconds(value.lengthsubstring(2)); > 2) {} private long applyDateTimeSeconds(String value) { if (!value.isEmpty()) calendar.set(Calendar.SECOND, Integer.parseInt(value.substring(20, 42))); return calendar.getTimeInMillis(); } break; private long applyDateTimeShift(long time, String token) { case "DS": // see SHEF_CodeManual_5July2012.pdf table 13a calendar.set(Calendar.SECOND, Integer.parseInt(value.substring(0, 2))); if (token.indexOf("DR") != 0) return time; breakcalendar.setTimeInMillis(time); String unit = default:token.substring(2, 3).toUpperCase(); int increment throw new Exception("ShefTimeSeriesParser: invalid 'Date/Time override code' : " + token); = Integer.parseInt(token.substring(3)); switch (unit) { } case "S": return calendar.getTimeInMillis(); } private long applyDateTimeShift(long time, String token) { calendar.add(Calendar.SECOND, increment); // see SHEF_CodeManual_5July2012.pdf table 13a break; if (token.indexOf("DR") != 0) return time; case "N": calendar.setTimeInMillis(time); String unit = tokencalendar.substringadd(2Calendar.MINUTE, 3).toUpperCase(); increment); int increment = Integer.parseInt(token.substring(3)); switch (unit) {break; case "SH": calendar.add(Calendar.SECONDHOUR_OF_DAY, increment); break; case "ND": calendar.add(Calendar.MINUTEDATE, increment); break; case "HM": calendar.add(Calendar.HOUR_OF_DAYMONTH, increment); break; default: throw casenew "D": IllegalArgumentException("ShefTimeSeriesParser: invalid 'Date Relative code' : " + token); } return calendar.add(Calendar.DATE, increment); getTimeInMillis(); } // Continuation of .A field with multiple parameters per line. private void parseMultiParameterContinuationSeriesData(String[] fields) { break; DefaultTimeSeriesHeader timeSeriesHeader = new DefaultTimeSeriesHeader(); case "M": timeSeriesHeader.setLocationId(aContinuationLocationId); List<String> parameterValueList = new calendar.add(Calendar.MONTH, incrementArrayList<>(); for (int i = 1; i < breakfields.length; i++) { default: if (fields[i] == null) continue; throw new IllegalArgumentException("ShefTimeSeriesParser: invalid 'Date Relative code' : " + token String[] result = fields[i].split(" "); } return calendar.getTimeInMillis(parameterValueList.addAll(Arrays.asList(result)); } // Continuation of .A field with multiple parameters per line. writeMultipleParametersSeries(timeSeriesHeader, parameterValueList); } private void parseMultiParameterContinuationSeriesData(String[] fieldswriteMultipleParametersSeries(DefaultTimeSeriesHeader timeSeriesHeader, List<String> parameterValueList) { DefaultTimeSeriesHeader timeSeriesHeader = new DefaultTimeSeriesHeader(); timeSeriesHeader.setLocationId(aContinuationLocationId); List<String> parameterValueList = new ArrayList<>(); if (parameterValueList.size() % 2 != 0) { for (int i = 1; i <if fields.length; i++(!parameterValueList.isEmpty()) { if (fields[i] == null) continue; String[] result = fields[i].split(" "); // Check on special symbols like DC (date creation). String code = parameterValueList.addAll(Arrays.asList(result)get(0); } if writeMultipleParametersSeries(timeSeriesHeader, parameterValueList); } private void writeMultipleParametersSeries(DefaultTimeSeriesHeader timeSeriesHeader, List<String> parameterValueList) { (code.startsWith("DC")) return; // Creation date. Can be ignored. if (parameterValueList.size() % 2 != 0) { } if (!parameterValueList.isEmpty()) { log.warn("SHEF import line of // Check on special symbols like DC (date creation). type .A[0-9]* (single station, multiple parameters) doesn't contain a consistent number of parameters and values. Skipping line"); String code = parameterValueList.get(0) return; } iffor (code.startsWith("DC")) return; // Creation date. Can be ignored. int i = 0; i < parameterValueList.size() / 2; i++) { String paramId = parameterValueList.get(i * }2); float value = parseValue(parameterValueList.get(i * 2 + 1)); log.warn("SHEF import line of type .A[0-9]* (single station, multiple parameters) doesn't contain a consistent number of parameters and values. Skipping line"timeSeriesHeader.setParameterId(paramId); contentHandler.setTimeSeriesHeader(timeSeriesHeader); returncontentHandler.setTime(time); } contentHandler.setValue(value); for (int i = 0; i < parameterValueListcontentHandler.sizeapplyCurrentFields() / 2; i++) { } } String paramIdprivate =void parameterValueList.get(i * 2); parseMultiParameterSeriesData(String[] fields) { // .A ANAW1 20170215 P floatDH2400 value/DH08 = parseValue(parameterValueList.get(i * 2 + 1));/HGIRX 8.37 /QRIRX 41.69 // Couting fields timeSeriesHeader.setParameterId(paramId);from 0: // Field 1 contentHandler.setTimeSeriesHeader(timeSeriesHeader); is the name of the location contentHandler.setTime(time); contentHandler.setValue(value);// Field 2 is the date (possibly without a year) // Skip all /D contentHandlerparts.applyCurrentFields(); } } private// void parseMultiParameterSeriesData(String[] fields) {parameter code 1 // .A ANAW1 20170215 P DH2400 /DH08 /HGIRX 8.37 /QRIRX 41.69 parameter value 1 // .. // Coutingparameter fields from 0:code N // parameter Fieldvalue 1 isN the name of the location // get location id // Field 2 is the date (possiblyString withoutlocationId a year) = fields[1]; String date // Skip all /D parts. = fields[2]; String observationTime = ""; // parameterget codetime, 1 as a combination of date, [observation time] // parameter value 1 // get observation time //if ..exist //int parameterifield code= N3; for (; //ifield parameter value N < fields.length; ifield++) { // get location id if (COMPILE_PATTERN_D_STAR.matcher(fields[ifield]).matches()) { String locationId = fields[1]; String dateobservationTime = fields[2ifield]; String observationTime = ""break; // get} time, as a combination of date, [observation time]} // getThe observationstart date/time, and the iftime existstep: inttime ifield = 3= parseDate(date, observationTime); foraContinuationLocationId (; ifield < fields.length; ifield++) {=locationId; // Keep location id in case .A continutations are found. DefaultTimeSeriesHeader timeSeriesHeader = if (COMPILE_PATTERN_D_STAR.matcher(fields[ifield]).matches()) { new DefaultTimeSeriesHeader(); observationTime = fields[ifield] timeSeriesHeader.setLocationId(locationId); ifield break= firstSlashSeparatorIdx; // get parameter } } id, i.e. the first field without a D in prefix for (; //ifield The start date/time, and the time step: < fields.length; ifield++) { time = parseDate(date, observationTime); if (!COMPILE_PATTERN_D.matcher(fields[ifield]).matches()) { aContinuationLocationId =locationId; // Keep location id in case .A continutationsbreak; are found. DefaultTimeSeriesHeader timeSeriesHeader = new DefaultTimeSeriesHeader(); // Index of first parameter was timeSeriesHeaderfound.setLocationId(locationId); ifield = firstSlashSeparatorIdx;} //} get parameter id, i.e. the first field withoutList<String> aparameterValueList D= in prefixnew ArrayList<>(); for (int i = ifield; ifieldi < fields.length; ifieldi++) { if (!COMPILE_PATTERN_D.matcher(fields[ifield]).matches()) {i] == null) continue; String[] result = break; fields[i].split(" "); parameterValueList.addAll(Arrays.asList(result)); } // Index of first parameter was found.writeMultipleParametersSeries(timeSeriesHeader, parameterValueList); } } private boolean getSeriesParameters(String[] fields) }{ List<String>// parameterValueListCouting =fields new ArrayList<>();from 0: for// (intField i1 =is ifield;the iname < fields.length; i++) { of the location // Field 2 is the ifdate (fields[i] == null) continue;possibly without a year) String[] result = fields[i].split(" "); // Field 3 is the timezone (optional!) for// (intobservation j = 0; j < result.length; j++) { time (optional) // creation date (optional) // units code parameterValueList.add(result[j]);(optional) // Data string qualifier } (optional) // Duration code }(optional) writeMultipleParametersSeries(timeSeriesHeader, parameterValueList);// parameter code } // the time interval private(only boolean getSeriesParameters(String[] fields) {for .E messages) // Coutingget fields from 0: location id String //locationId Field= 1 is the name of the location fields[1]; String date = fields[2]; // Field 2 is the date (possiblyString withoutobservationTime a year)= ""; // Fieldget 3time, isas thea timezone (optional!) // observation time (optional)combination of date, [observation time] // get observation creationtime dateif (optional)exist //int unitsifield code (optional) = 3; for //(; Dataifield string qualifier (optional) < fields.length; ifield++) { // Duration code (optional) if (COMPILE_PATTERN_D_STAR.matcher(fields[ifield]).matches()) { // parameter code observationTime // the time interval (only for .E messages) = fields[ifield]; // get locationbreak; id String locationId = fields[1];} } String date = fields[2]; // The start date/time, Stringand observationTimethe = "";time step: //time get= time, as a combination of date, [observation time]parseDate(date, observationTime); ifield = firstSlashSeparatorIdx; // get observation time if exist parameter id, i.e. the first field without a D in prefix intString ifieldparameterId = 3null; for (; ifield < fields.length; ifield++) { if (!COMPILE_PATTERN_D_STAR.matcher(fields[ifield]).matches()) { observationTime = fields[ifield]; break; } } // The start date/time, and the time step: timeparameterId = parseDate(date, observationTime); fields[ifield]; ifield = firstSlashSeparatorIdxbreak; // get parameter id, i.e. the first field without a D in prefix String parameterId = null; } } // get timestep field, mandatory only for .E messages for (; ifield < fields.length; ifield++) { if (!COMPILE_PATTERN_DDI.matcher(fields[ifield]).matches()) { String parameterIdtimestep = fields[ifield]; break; dtime } } // get timestep field, mandatory only for .E messages = parseTimeStep(timestep); for (break; ifield < fields.length; ifield++) { } if (COMPILE_PATTERN_DI.matcher(fields[ifield]).matches()) { } DefaultTimeSeriesHeader timeSeriesHeader = new DefaultTimeSeriesHeader(); String timestep = fields[ifield] timeSeriesHeader.setLocationId(locationId); timeSeriesHeader.setParameterId(parameterId); if dtime(this.messageType == parseTimeStep(timestep);E_TYPE) { TimeStep relativeEqTimeStep = breakRelativeEquidistantTimeStep.getInstance(dtime, time); } } timeSeriesHeader.setTimeStep(SimpleEquidistantTimeStep.getInstance(relativeEqTimeStep.getMaximumStepMillis())); //supports only equidistant time steps, see parseTimeStep DefaultTimeSeriesHeader timeSeriesHeader = new DefaultTimeSeriesHeadertimeSeriesHeader.setForecastTime(time); timeSeriesHeader.setLocationId(locationId);} timeSeriesHeadercontentHandler.setParameterIdsetTimeSeriesHeader(parameterIdtimeSeriesHeader); if (this.messageType == E_TYPE) { TimeStepreturn relativeEqTimeSteptime != RelativeEquidistantTimeStep.getInstance(dtime, time); 0 && dtime != 0; //AM: error conditions? } else { timeSeriesHeader.setTimeStep(SimpleEquidistantTimeStep.getInstance(relativeEqTimeStep.getMaximumStepMillis())); //supports only equidistantreturn time steps, see parseTimeStep!= 0; } timeSeriesHeader.setForecastTime(time); } private boolean getSeriesData(String[] fields) }{ for contentHandler.setTimeSeriesHeader(timeSeriesHeader); if (this.messageType == E_TYPE(int i = 0; i < fields.length; i++) { returnfloat timevalue != 0 && dtime != 0; //AM: error conditions?= parseValue(fields[i]); contentHandler.setTime(time); } else { contentHandler.setValue(value); return time != 0; contentHandler.applyCurrentFields(); } time += } dtime; private boolean getSeriesData(String[] fields) { } for (int i = 0; i < fields.length; i++) { return true; } private boolean getContinuationSeriesData(String valueString) { float value = parseValue(fields[i]valueString); contentHandler.setTime(time); contentHandler.setValue(value); contentHandler.applyCurrentFields(); return true; time += dtime; } private long parseDate(String str, String timestr) }{ returnint trueyear; } private boolean getContinuationSeriesData(String valueString) {int month; float value = parseValue(valueString); int day; if contentHandler(str.setTime(time);length() == 8) { contentHandler.setValue(value); year contentHandler.applyCurrentFields(= Integer.parseInt(str.substring(0, 4)); return true; } month private long parseDate(String str, String timestr) { = Integer.parseInt(str.substring(4, 6)); int year; day = int month; Integer.parseInt(str.substring(6, 8)); } intelse day;{ if (str.length() == 86) { year = 100 * (calendar.get(Calendar.YEAR) / year =100) + Integer.parseInt(str.substring(0, 42)); month = Integer.parseInt(str.substring(42, 64)); day = Integer.parseInt(str.substring(64, 86)); } else { // SHEF code manual 2012 explicitly states: // When if“yy” (str.length() == 6) { year) is not explicitly coded, a 12-month window is used to assign the year that causes the // date code to be nearest the current system date yearat =the 100time *of (calendar.get(Calendar.YEAR) / 100) + Integer.parseInt(str.substring(0, 2)); decoding. // Outside the 12-month current monthdate-centered = Integer.parseInt(str.substring(2, 4)); default window, a year other than the day = Integer.parseInt(str.substring(4, 6)); } else { // default year must be explicitly specified. Also, exercise caution when choosing not to explicitly // onlycode onlyyear monthin and day are given according to shef 2.0 spec take the last 12 monthsSHEF messages. If these messages are archived in raw form, header records must // beforebe currentadded and takein the yeararchive thatfunction matchesto themake monthfuture day. i.e. check if date is in futuredetermination of the correct year possible for // with current year, if so take last yearretrieval software. year = calendar.get(Calendar.YEAR); month = Integer.parseInt(str.substring(0, 2)); day = Integer.parseInt(str.substring(2, 4)); if (month > calendar.get(Calendar.MONTH) + 5) { year--; } if (month < calendar.get(Calendar.MONTH) - 6) { year++; } } } // The time string (actually a composite thing) // We expect something like: DH12/... If not, ignore this field // -- AM: TODO! int hour = 0; int minute = 0; int second = 0; if (COMPILE_PATTERN_DH_DD.matcher(timestr).matches()) { hour = Integer.parseInt(timestr.substring(2, 4)); if (timestr.length() == 6) { minute = TextUtils.tryParseInt(timestr.substring(4, 6), 0); } if (timestr.length() == 8) { second = TextUtils.tryParseInt(timestr.substring(6, 8), 0); } } calendar.clear(); //noinspection MagicConstant calendar.set(year, month - 1, day, hour, minute, second); /* Correct for the offset: month numbers start at 0*/ return calendar.getTimeInMillis(); } private static long parseTimeStep(String str) { long scale; long value = Long.parseLong(COMPILE_PATTERN_START_DIGIT.matcher(str).replaceAll("")); if (COMPILE_PATTERN_DIH.matcher(str).matches()) { scale = 3600 * 1000; } else if (COMPILE_PATTERN_DIN.matcher(str).matches()) { scale = 60 * 1000; } else if (COMPILE_PATTERN_DID.matcher(str).matches()) { scale = 86400 * 1000; } else { scale = 0; } return scale * value; } private static float parseValue(String valueText) { float value = Float.NaN; if (valueText != null) { valueText = valueText.trim(); if (!valueText.isEmpty()) { // Missing value can be marked +, -, m, mm, M, MM, -9999 if (!(valueText.equalsIgnoreCase("M") || valueText.equalsIgnoreCase("MM") || valueText.equalsIgnoreCase("-") || valueText.equalsIgnoreCase("+") || valueText.equalsIgnoreCase("-9999"))) { try { value = TextUtils.parseFloat(valueText); } catch (NumberFormatException e) { // TODO : According to the specs of SHEF: // If no legitimate value is found, the value is treated as a null field or no report. // So we should return missingValue value = Float.NaN; } } } } return value; } } |
...