VLab Issue #4638 - Fix cycle times in Ntrans decoder
Change-Id: I0bbf183c0cd0a22e8c8d6f49b1615c58e75d9bf9 Former-commit-id:65c994996e
[formerly7037d73d1d
] [formerlyed1faf2593
] [formerly65c994996e
[formerly7037d73d1d
] [formerlyed1faf2593
] [formerlyc7586b6739
[formerlyed1faf2593
[formerly ce241f8908f547326f0b813bf773f8d5debb33d3]]]] Former-commit-id:c7586b6739
Former-commit-id:9e0424cb11
[formerly0ec0e256f9
] [formerly 39efaab4813eb348052cd9e06986c6596dee12e4 [formerly0a3b19e323
]] Former-commit-id: 6c254ec0127609b7db97333a0013ae9d255575f2 [formerlyf4e96b8134
] Former-commit-id:bdb1bf4cef
This commit is contained in:
parent
17f5c4ec66
commit
15831235da
1 changed files with 458 additions and 126 deletions
|
@ -11,6 +11,9 @@ import java.nio.ByteOrder;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.raytheon.edex.exception.DecoderException;
|
||||
import com.raytheon.edex.plugin.AbstractDecoder;
|
||||
|
@ -33,22 +36,46 @@ import com.raytheon.uf.common.time.DataTime;
|
|||
* 10/2013 B. Hebbard Modify model name inference from metafile name
|
||||
* Aug 30, 2013 2298 rjpeter Make getPluginName abstract
|
||||
* 6/2014 T. Lee Added HYSPLIT and fixed "other" modelName
|
||||
* 08/2014 B. Hebbard Revise createDataTime() to correct end-of-month boundary bug
|
||||
* 08/2014 B. Hebbard Enhance to use (cycle) time info from metafile name, if available
|
||||
* 09/2014 B. Hebbard Normalize (shorten) metafile name to remove directory artifacts added during dataflow, so user will see that they're used to and will fit selection dialog column
|
||||
* </pre>
|
||||
*
|
||||
* This code has been developed by the SIB for use in the AWIPS2 system.
|
||||
*/
|
||||
public class NtransDecoder extends AbstractDecoder {
|
||||
|
||||
private final static int NTRANS_FILE_TITLE_SIZE = 32; // bytes
|
||||
|
||||
private final static int NTRANS_FRAME_LABEL_SIZE = 64;
|
||||
|
||||
private final static int NTRANS_FRAME_LABEL_TIME_SUBSTRING_SIZE = 9;
|
||||
|
||||
private final static int NTRANS_RESERVED_SPACE_SIZE = 38;
|
||||
|
||||
Calendar decodeTime = null;
|
||||
|
||||
private String normalizedMetafileName;
|
||||
|
||||
Integer yearFromFileName = null;
|
||||
|
||||
Integer monthFromFileName = null;
|
||||
|
||||
Integer dateFromFileName = null;
|
||||
|
||||
Integer hourFromFileName = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public NtransDecoder() throws DecoderException {
|
||||
decodeTime = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||
}
|
||||
|
||||
private class FrameHeader {
|
||||
String validTimeString;
|
||||
String frameHeaderTimeString;
|
||||
|
||||
String productNameString;
|
||||
|
||||
|
@ -78,17 +105,26 @@ public class NtransDecoder extends AbstractDecoder {
|
|||
int fileMachineType = 0;
|
||||
int frameSizeX = 0;
|
||||
int frameSizeY = 0;
|
||||
byte[] fileReservedSpace = new byte[38]; // TODO symbolic
|
||||
byte[] fileReservedSpace = new byte[NTRANS_RESERVED_SPACE_SIZE]; // TODO
|
||||
// symbolic
|
||||
|
||||
List<FrameHeader> frameHeaders = new ArrayList<FrameHeader>();
|
||||
List<NtransRecord> records = new ArrayList<NtransRecord>();
|
||||
|
||||
try {
|
||||
|
||||
decodeTime = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||
|
||||
fileName = inputFile.getName();
|
||||
|
||||
normalizedMetafileName = normalizeMetafileName(fileName);
|
||||
|
||||
getTimeFromMetafileName(normalizedMetafileName);
|
||||
|
||||
inputStream = new FileInputStream(inputFile);
|
||||
|
||||
// Read the entire file
|
||||
|
||||
// TODO: Wish we didn't have to do that, but decode method
|
||||
// wants to return all PDOs at once, so it's all got
|
||||
// to sit in memory anyway. Propose architecture change (?)
|
||||
|
@ -118,7 +154,7 @@ public class NtransDecoder extends AbstractDecoder {
|
|||
|
||||
// Read NTRANS metafile header
|
||||
|
||||
byte[] fileTitleBytes = new byte[32]; // TODO symbolic
|
||||
byte[] fileTitleBytes = new byte[NTRANS_FILE_TITLE_SIZE];
|
||||
byteBuffer.get(fileTitleBytes);
|
||||
fileTitle = new String(fileTitleBytes).trim();
|
||||
// System.out.println("[File title: " + fileTitle + "]");
|
||||
|
@ -129,18 +165,18 @@ public class NtransDecoder extends AbstractDecoder {
|
|||
frameSizeX = toUnsigned(byteBuffer.getShort());
|
||||
frameSizeY = toUnsigned(byteBuffer.getShort());
|
||||
|
||||
byte[] fileReserved = new byte[38];
|
||||
byte[] fileReserved = new byte[NTRANS_RESERVED_SPACE_SIZE];
|
||||
byteBuffer.get(fileReserved);
|
||||
|
||||
// Read NTRANS frame headers (follow file header; precede frame
|
||||
// contents)
|
||||
|
||||
for (int frame = 0; frame < fileMaxFrame; frame++) {
|
||||
byte[] labelTitleBytes = new byte[64]; // TODO symbolic
|
||||
byte[] labelTitleBytes = new byte[NTRANS_FRAME_LABEL_SIZE];
|
||||
byteBuffer.get(labelTitleBytes);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; (i < 64) && (labelTitleBytes[i] != 0x00); i++) { // TODO
|
||||
// symbolic
|
||||
for (int i = 0; (i < NTRANS_FRAME_LABEL_SIZE)
|
||||
&& (labelTitleBytes[i] != 0x00); i++) {
|
||||
sb.append((char) labelTitleBytes[i]);
|
||||
}
|
||||
String labelTitle = new String(sb);
|
||||
|
@ -152,12 +188,14 @@ public class NtransDecoder extends AbstractDecoder {
|
|||
// System.out.println("[startPos " + startPos + " endPos " +
|
||||
// endPos + "]");
|
||||
FrameHeader fh = new FrameHeader();
|
||||
if (labelTitle.length() < 8) { // TODO check!
|
||||
fh.validTimeString = labelTitle;
|
||||
if (labelTitle.length() < NTRANS_FRAME_LABEL_TIME_SUBSTRING_SIZE) {
|
||||
fh.frameHeaderTimeString = labelTitle;
|
||||
fh.productNameString = "";
|
||||
} else {
|
||||
fh.validTimeString = labelTitle.substring(0, 9);
|
||||
fh.productNameString = labelTitle.substring(9);
|
||||
fh.frameHeaderTimeString = labelTitle.substring(0,
|
||||
NTRANS_FRAME_LABEL_TIME_SUBSTRING_SIZE);
|
||||
fh.productNameString = labelTitle
|
||||
.substring(NTRANS_FRAME_LABEL_TIME_SUBSTRING_SIZE);
|
||||
}
|
||||
fh.startPos = startPos;
|
||||
fh.endPos = endPos;
|
||||
|
@ -184,7 +222,8 @@ public class NtransDecoder extends AbstractDecoder {
|
|||
record.setReportType("NTRANS");
|
||||
record.setModelName(inferModel(inputFile.getName()).replaceAll(
|
||||
"_", "-"));
|
||||
record.setMetafileName(inputFile.getName().replaceAll("_", "-"));
|
||||
record.setMetafileName(normalizedMetafileName.replaceAll("_",
|
||||
"-"));
|
||||
record.setProductName(fh.productNameString
|
||||
.trim()
|
||||
.replaceAll("_", "-")
|
||||
|
@ -195,8 +234,8 @@ public class NtransDecoder extends AbstractDecoder {
|
|||
// TODO acceptable??
|
||||
.replaceAll(",", "-").replaceAll("--", "-")
|
||||
.replaceAll("--", "-")); // twice
|
||||
record.setDataTime(createDataTime(fh.validTimeString));
|
||||
record.setValidTimeString(fh.validTimeString);
|
||||
record.setDataTime(createDataTime(fh.frameHeaderTimeString));
|
||||
record.setValidTimeString(fh.frameHeaderTimeString);
|
||||
record.setImageData(frameImage);
|
||||
record.setImageSizeX(frameSizeX);
|
||||
record.setImageSizeY(frameSizeY);
|
||||
|
@ -241,146 +280,439 @@ public class NtransDecoder extends AbstractDecoder {
|
|||
|
||||
}
|
||||
|
||||
private DataTime createDataTime(String validTimeString) {
|
||||
|
||||
// Create a standard DataTime object, with proper timing
|
||||
// determined from valid time string (e.g., "27/06V042")
|
||||
|
||||
// Get a Calendar object. Fields default to current time.
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
Calendar now = Calendar.getInstance();
|
||||
|
||||
// Get components of validTimeString as 'int's
|
||||
|
||||
// TODO -- generalize to take "F" as well as "V" strings?
|
||||
// if so, may want to put in central utilities.
|
||||
// TODO -- use more general/flexible pattern matching?
|
||||
// TODO -- improve error handling/recovery
|
||||
|
||||
// try {
|
||||
String validDateString = validTimeString.substring(0, 2);
|
||||
int validDate = Integer.parseInt(validDateString);
|
||||
String validHourString = validTimeString.substring(3, 5);
|
||||
int validHour = Integer.parseInt(validHourString);
|
||||
String fcstHourString = validTimeString.substring(6, 9);
|
||||
int fcstHour = Integer.parseInt(fcstHourString);
|
||||
// }
|
||||
// catch (Exception e) {
|
||||
// TODO
|
||||
// return new DataTime(calendar, 0);
|
||||
public String normalizeMetafileName(String fileName) {
|
||||
// Given..."gfs_gfs.20140901_gfs_20140901_12_ak"
|
||||
// Want....................."gfs_20140901_12_ak"
|
||||
//
|
||||
// @formatter:off
|
||||
// Darn... Following is thwarted by these cases:
|
||||
// gfs_gfs.20140901_gfsver_20140901_18_na_mar
|
||||
// ukmet.2014090_ukmet.2014090._ukmetver_20140901_00
|
||||
// wave_wave.20140901_nww3_20140901_12
|
||||
// wave_wave.20140902_nww3_20140902_00_akw
|
||||
//
|
||||
// final Pattern p = Pattern.compile("^(\\w+)_\\1\\.(\\d{6,8})_\\1_\\2");
|
||||
// Matcher m = p.matcher(fileName);
|
||||
// if (m.find()) {
|
||||
// fileName = fileName.replaceFirst("^(\\w+)_\\1\\.(\\d{6,8})_", "");
|
||||
// }
|
||||
//
|
||||
// @formatter:on
|
||||
// So, instead we...
|
||||
|
||||
// Alter specific fields to set to valid time.
|
||||
// TODO -- use cycle time string if available to set year, month
|
||||
// but must be very careful about applying rules
|
||||
// If the string constains a ".", then remove everything from start of
|
||||
// string through the FIRST "_" following the LAST "."
|
||||
|
||||
calendar.set(Calendar.DAY_OF_MONTH, validDate);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, validHour);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
if (fileName.contains(".")) {
|
||||
String[] splits = fileName.split("\\.");
|
||||
String lastSplit = splits[splits.length - 1];
|
||||
// "reluctant" (?) match to assure first "_"
|
||||
return lastSplit.replaceFirst("^.*?_", "");
|
||||
} else {
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
|
||||
// Now subtract forecast hours to get initial (reference) or cycle time
|
||||
private int normalizeYear(int shortYear) {
|
||||
|
||||
calendar.add(Calendar.HOUR_OF_DAY, -fcstHour);
|
||||
// Year can be 2 digits. If so, select century to make it the one
|
||||
// closest to the current year.
|
||||
|
||||
// Careful here: Calendar assumed valid time is in the current month
|
||||
// (which, even with current data, could be off one month either way).
|
||||
// (Yeah, I know it's overkill. But that's what we thought *last*
|
||||
// century...)
|
||||
|
||||
// What we want for the month of the valid time is the latest month
|
||||
// such that the (deduced) cycle time is not in the future (that is,
|
||||
// not later than decode time "now").
|
||||
if (shortYear > 99) {
|
||||
return shortYear;
|
||||
} else {
|
||||
if (decodeTime == null) {
|
||||
decodeTime = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||
}
|
||||
int currYear = decodeTime.get(Calendar.YEAR);
|
||||
int currCentury = currYear / 100;
|
||||
int breakpoint = (currYear + 50) % 100;
|
||||
int derivedYear = currCentury * 100 + shortYear;
|
||||
if (shortYear > breakpoint) {
|
||||
derivedYear++;
|
||||
}
|
||||
return derivedYear;
|
||||
}
|
||||
}
|
||||
|
||||
// The following two steps are designed to get us there. (Note that
|
||||
// either or both can execute.) First, if the inferred cycle time is
|
||||
// in the past, try adding a month...
|
||||
private int CalendarMonth(int goodOldMonthNumber) {
|
||||
// Just to be proper (since we don't control the values
|
||||
// of these "magic constants", and so can't assume they
|
||||
// won't change out from under us (say, in the unlikely
|
||||
// but possible case that Calendar decides to go with
|
||||
// 1-based months (like the rest of the world) instead
|
||||
// of 0-based ones. (We're 0-based [array] here, too, but
|
||||
// WE control the order, and shield it from the caller.)
|
||||
// @formatter:off
|
||||
final int[] CalendarMonthConstants = {
|
||||
Calendar.JANUARY,
|
||||
Calendar.FEBRUARY,
|
||||
Calendar.MARCH,
|
||||
Calendar.APRIL,
|
||||
Calendar.MAY,
|
||||
Calendar.JUNE,
|
||||
Calendar.JULY,
|
||||
Calendar.AUGUST,
|
||||
Calendar.SEPTEMBER,
|
||||
Calendar.OCTOBER,
|
||||
Calendar.NOVEMBER,
|
||||
Calendar.DECEMBER,
|
||||
};
|
||||
return CalendarMonthConstants[goodOldMonthNumber - 1];
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
if (calendar.before(now)) {
|
||||
calendar.add(Calendar.MONTH, 1);
|
||||
private void getTimeFromMetafileName(String fileName) {
|
||||
|
||||
// NTRANS metafile names (almost?) always contain date and (sometimes)
|
||||
// hour information. We assume that this refers to the cycle time of the
|
||||
// model run which produced the images it contains. Since individual
|
||||
// frame headers (contained within the body of the file) currently
|
||||
// (2014-08) provide only valid times with date -- and no month or year
|
||||
// -- taking these 'hints' from the file name, if available, allows us
|
||||
// to handle legacy data properly, even if months or years old.
|
||||
//
|
||||
// This method operates on instance variables for both input and output.
|
||||
//
|
||||
// Input is fileName, which is assumed to have been set to the full name
|
||||
// of the metafile currently being ingested, containing a substring of
|
||||
// one of the following forms:
|
||||
// @formatter:off
|
||||
// YYYYMMDD
|
||||
// YYYYMMDDHH
|
||||
// YYYYMMDD_HH
|
||||
// YYMMDD
|
||||
// YYMMDD_HH
|
||||
// @formatter:on
|
||||
//
|
||||
// Output takes the form of...
|
||||
// yearFromFileName, monthFromFileName,
|
||||
// dateFromFileName, hourFromFileName
|
||||
// These are (boxed) Integer variables; a null value indicates no value
|
||||
// is available for that field.
|
||||
|
||||
final Pattern p = Pattern.compile("((\\d\\d){3,4})_?(\\d\\d)?");
|
||||
Matcher m = p.matcher(fileName);
|
||||
|
||||
String matchString = "";
|
||||
String hourString = "";
|
||||
|
||||
String year = "";
|
||||
String month = "";
|
||||
String date = "";
|
||||
String hour = "";
|
||||
|
||||
while (m.find()) {
|
||||
if (m.group(0).length() >= matchString.length()) {
|
||||
matchString = m.group(0);
|
||||
|
||||
String dateString = m.group(1).replaceFirst("_", "");
|
||||
|
||||
date = dateString.substring(dateString.length() - 2);
|
||||
dateString = dateString.substring(0, dateString.length() - 2);
|
||||
month = dateString.substring(dateString.length() - 2);
|
||||
dateString = dateString.substring(0, dateString.length() - 2);
|
||||
year = dateString.substring(dateString.length() - 2);
|
||||
dateString = dateString.substring(0, dateString.length() - 2);
|
||||
assert (dateString.isEmpty());
|
||||
|
||||
hourString = m.group(3);
|
||||
}
|
||||
}
|
||||
|
||||
// Now -- regardless of whether the previous step executed (no "else"
|
||||
// here)
|
||||
// if the inferred cycle time is in the future, back up one month.
|
||||
if (!matchString.isEmpty()) {
|
||||
try {
|
||||
yearFromFileName = normalizeYear(Integer.parseInt(year));
|
||||
monthFromFileName = Integer.parseInt(month);
|
||||
dateFromFileName = Integer.parseInt(date);
|
||||
if (!hourString.isEmpty()) {
|
||||
hourFromFileName = Integer.parseInt(hourString);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO: ERROR
|
||||
// Set *FromFileName back to null? Or not (leave partial partial
|
||||
// parse results)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (calendar.after(now)) {
|
||||
calendar.add(Calendar.MONTH, -1);
|
||||
DataTime createDataTime(String frameHeaderTimeString,
|
||||
String simulatedFileName, Calendar simulatedDecodeTime) {
|
||||
decodeTime = simulatedDecodeTime;
|
||||
return createDataTime(frameHeaderTimeString, simulatedFileName);
|
||||
}
|
||||
|
||||
DataTime createDataTime(String frameHeaderTimeString,
|
||||
String simulatedFileName) {
|
||||
fileName = simulatedFileName;
|
||||
normalizedMetafileName = normalizeMetafileName(fileName);
|
||||
getTimeFromMetafileName(normalizedMetafileName);
|
||||
return createDataTime(frameHeaderTimeString);
|
||||
}
|
||||
|
||||
DataTime createDataTime(String frameHeaderTimeString) {
|
||||
|
||||
// Create a standard DataTime object, with proper timing
|
||||
// determined from given time string (e.g., "27/06V042")
|
||||
// (from the frame header), AND fields parsed earlier from
|
||||
// the metafile name (and stored in instance variables),
|
||||
// if available.
|
||||
|
||||
// --
|
||||
|
||||
// Get components of validframeHeaderTimeString as 'int's
|
||||
|
||||
// Be able to decode...
|
||||
// 8, 6, 4, or 2 digits, followed by...
|
||||
// "/" followed by...
|
||||
// 2 digits followed by...
|
||||
// "F" or "V" followed by...
|
||||
// any number of digits? 2-3? 1-6?
|
||||
// NOT with intervening spaces?
|
||||
|
||||
final Pattern p = Pattern
|
||||
.compile("((\\d\\d){1,4})/(\\d\\d)(F|V)(\\d{1,4})");
|
||||
Matcher m = p.matcher(frameHeaderTimeString);
|
||||
|
||||
boolean isV = false;
|
||||
|
||||
Integer centuryFromFrameHeader = null;
|
||||
Integer yearFromFrameHeader = null;
|
||||
Integer monthFromFrameHeader = null;
|
||||
Integer dateFromFrameHeader = null;
|
||||
Integer hourFromFrameHeader = null;
|
||||
Integer fcstHour = null;
|
||||
|
||||
String dateString, hourString, fOrV, fcstHourString;
|
||||
|
||||
if (m.find()) {
|
||||
dateString = m.group(1);
|
||||
hourString = m.group(3);
|
||||
fOrV = m.group(4);
|
||||
fcstHourString = m.group(5);
|
||||
} else {
|
||||
return new DataTime(decodeTime, 0); // should be error indication
|
||||
}
|
||||
|
||||
try {
|
||||
switch (dateString.length()) {
|
||||
case 8:
|
||||
centuryFromFrameHeader = Integer.parseInt(dateString.substring(
|
||||
0, 2));
|
||||
dateString = dateString.substring(2);
|
||||
// NO break;
|
||||
case 6:
|
||||
yearFromFrameHeader = Integer.parseInt(dateString.substring(0,
|
||||
2));
|
||||
dateString = dateString.substring(2);
|
||||
if (centuryFromFrameHeader == null) {
|
||||
yearFromFrameHeader = normalizeYear(yearFromFrameHeader);
|
||||
} else {
|
||||
yearFromFrameHeader += centuryFromFrameHeader * 100;
|
||||
}
|
||||
// NO break;
|
||||
case 4:
|
||||
monthFromFrameHeader = Integer.parseInt(dateString.substring(0,
|
||||
2));
|
||||
dateString = dateString.substring(2);
|
||||
// NO break;
|
||||
case 2:
|
||||
dateFromFrameHeader = Integer.parseInt(dateString.substring(0,
|
||||
2));
|
||||
dateString = dateString.substring(2);
|
||||
assert (dateString.isEmpty());
|
||||
break;
|
||||
default:
|
||||
// ERROR
|
||||
break;
|
||||
}
|
||||
hourFromFrameHeader = Integer.parseInt(hourString);
|
||||
isV = fOrV.equalsIgnoreCase("V");
|
||||
fcstHour = Integer.parseInt(fcstHourString);
|
||||
} catch (Exception e) {
|
||||
// TODO
|
||||
return new DataTime(Calendar.getInstance(TimeZone
|
||||
.getTimeZone("GMT")), 0);
|
||||
}
|
||||
|
||||
// Establish upper bound on what the specified time means.
|
||||
// For F-type string, that would be the cycle time.
|
||||
// For V-type string, that would be the valid time.
|
||||
|
||||
// Start with the decode (system) time...
|
||||
Calendar upperBoundTime = Calendar.getInstance(TimeZone
|
||||
.getTimeZone("GMT"));
|
||||
upperBoundTime.setTime(decodeTime.getTime());
|
||||
|
||||
// YEAR: If specified in the frame header, that takes priority...
|
||||
if (yearFromFrameHeader != null) {
|
||||
upperBoundTime.set(Calendar.YEAR, yearFromFrameHeader);
|
||||
}
|
||||
// ...otherwise use value from file name, if available...
|
||||
else if (yearFromFileName != null) {
|
||||
upperBoundTime.set(Calendar.YEAR, yearFromFileName);
|
||||
}
|
||||
// ...otherwise defaults to the current year.
|
||||
|
||||
// MONTH: If specified in the frame header, that takes priority...
|
||||
if (monthFromFrameHeader != null) {
|
||||
upperBoundTime.set(Calendar.MONTH,
|
||||
CalendarMonth(monthFromFrameHeader));
|
||||
}
|
||||
// ...otherwise use value from file name, if available
|
||||
else if (monthFromFileName != null) {
|
||||
upperBoundTime
|
||||
.set(Calendar.MONTH, CalendarMonth(monthFromFileName));
|
||||
}
|
||||
// ...otherwise defaults to current month
|
||||
|
||||
// DATE: If specified in the frame header, IGNORE FOR NOW...
|
||||
// if (dateFromFrameHeader != null) {
|
||||
// upperBoundTime.set(Calendar.DAY,
|
||||
// CalendarMonth(monthFromFrameHeader));
|
||||
// }
|
||||
// else
|
||||
// ...BUT do use value from file name, if available
|
||||
if (dateFromFileName != null) {
|
||||
upperBoundTime.set(Calendar.DAY_OF_MONTH, dateFromFileName);
|
||||
}
|
||||
// ...otherwise defaults to current date
|
||||
|
||||
// If we're dealing with a V-type string, we're determining the VALID
|
||||
// time. Add forecast hours.
|
||||
if (isV) {
|
||||
upperBoundTime.add(Calendar.HOUR_OF_DAY, fcstHour);
|
||||
}
|
||||
|
||||
// Now calculate the actual valid time, starting with the latest
|
||||
// possible...
|
||||
|
||||
Calendar calculatedValidTime = Calendar.getInstance(TimeZone
|
||||
.getTimeZone("GMT"));
|
||||
calculatedValidTime.setTime(upperBoundTime.getTime());
|
||||
|
||||
// ...setting the date field to the specified date...
|
||||
|
||||
calculatedValidTime.set(Calendar.DATE, dateFromFrameHeader);
|
||||
|
||||
// ...but if greater than the latest possible valid date...
|
||||
|
||||
int latestPossibleValidDate = upperBoundTime.get(Calendar.DAY_OF_MONTH);
|
||||
if (dateFromFrameHeader > latestPossibleValidDate) {
|
||||
// ...then it must be that date in the PRIOR month...
|
||||
calculatedValidTime.add(Calendar.MONTH, -1);
|
||||
}
|
||||
|
||||
// Now set the hour field to the specified hour...
|
||||
|
||||
calculatedValidTime.set(Calendar.HOUR_OF_DAY, hourFromFrameHeader);
|
||||
|
||||
// ...and finally set sub-hour fields all to zero
|
||||
|
||||
calculatedValidTime.set(Calendar.MINUTE, 0);
|
||||
calculatedValidTime.set(Calendar.SECOND, 0);
|
||||
calculatedValidTime.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
// Now calculate actual initial (reference) or cycle time
|
||||
|
||||
Calendar calculatedCycleTime = Calendar.getInstance(TimeZone
|
||||
.getTimeZone("GMT"));
|
||||
calculatedCycleTime.setTime(calculatedValidTime.getTime());
|
||||
|
||||
// Subtract forecast hours to get initial (reference) or cycle time
|
||||
if (isV) {
|
||||
calculatedCycleTime.add(Calendar.HOUR_OF_DAY, -fcstHour);
|
||||
}
|
||||
|
||||
// Sanity check against file name date and hour (if known)
|
||||
|
||||
int calculatedCycleDate = calculatedCycleTime
|
||||
.get(Calendar.DAY_OF_MONTH);
|
||||
int calculatedCycleHour = calculatedCycleTime.get(Calendar.HOUR_OF_DAY);
|
||||
if (dateFromFileName != null && dateFromFileName != calculatedCycleDate) {
|
||||
// WARNING!!
|
||||
logger.warn("Cycle date " + dateFromFileName
|
||||
+ " from metafile name " + fileName + " differs from "
|
||||
+ calculatedCycleDate + " inferred from frame header "
|
||||
+ frameHeaderTimeString);
|
||||
} else if (hourFromFileName != null
|
||||
&& hourFromFileName != calculatedCycleHour) {
|
||||
// WARNING!!
|
||||
logger.warn("Cycle hour " + hourFromFileName
|
||||
+ " from metafile name " + fileName + " differs from "
|
||||
+ calculatedCycleHour + " inferred from frame header "
|
||||
+ frameHeaderTimeString);
|
||||
}
|
||||
|
||||
// Return DataTime, constructed from cycle time and forecast hour.
|
||||
|
||||
DataTime dataTime = new DataTime(calendar, fcstHour * 3600);
|
||||
DataTime dataTime = new DataTime(calculatedCycleTime, fcstHour * 3600);
|
||||
|
||||
return dataTime;
|
||||
}
|
||||
|
||||
private DataTime createDataTime(String initialTimeString,
|
||||
String validTimeString) {
|
||||
|
||||
// FUTURE -- use initialTimeString to influence deduction of
|
||||
// full initial (reference) time from validTimeString
|
||||
|
||||
// For now, initialTimeString not used...
|
||||
|
||||
return createDataTime(validTimeString);
|
||||
|
||||
/*
|
||||
*
|
||||
* // Create a standard DataTime object, with proper timing //
|
||||
* determined from valid time string (e.g., "27/06V042")
|
||||
*
|
||||
* // Get components of validTimeString as 'int's // TODO -- generalize
|
||||
* to take "F" as well as "V" strings? // if so, may want to put in
|
||||
* central utilities. // TODO -- use more general/flexible pattern
|
||||
* matching?
|
||||
*
|
||||
* String validDateString = validTimeString.substring(0, 1); int
|
||||
* validDate = Integer.parseInt(validDateString); String validHourString
|
||||
* = validTimeString.substring(3, 4); int validHour =
|
||||
* Integer.parseInt(validHourString); String fcstHourString =
|
||||
* validTimeString.substring(6, 8); int fcstHour =
|
||||
* Integer.parseInt(fcstHourString);
|
||||
*
|
||||
* // Get a Calendar object. Fields default to current time.
|
||||
*
|
||||
* Calendar calendar = Calendar.getInstance();
|
||||
*
|
||||
* // Alter specific fields to set to valid time. // TODO -- use cycle
|
||||
* time string if available to set year, month // but must be very
|
||||
* careful about applying rules
|
||||
*
|
||||
* calendar.set(Calendar.DAY_OF_MONTH, validDate);
|
||||
* calendar.set(Calendar.HOUR_OF_DAY, validHour);
|
||||
* calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0);
|
||||
* calendar.set(Calendar.MILLISECOND, 0);
|
||||
*
|
||||
* // Now subtract forecast hours to get initial (reference) time...
|
||||
*
|
||||
* calendar.add(Calendar.HOUR_OF_DAY, -fcstHour);
|
||||
*
|
||||
* // ...for DataTime constructor that wants it // (DataTime has many
|
||||
* constructors, but none that // takes validTime and fcstHour)
|
||||
*
|
||||
* DataTime dataTime = new DataTime(calendar, fcstHour);
|
||||
*
|
||||
* return dataTime;
|
||||
*/
|
||||
}
|
||||
|
||||
private enum Model {
|
||||
// TODO - Remove this, to make decoder agnostic w.r.t. list of available
|
||||
// models.
|
||||
|
||||
// We do this temporarily because we don't yet know the possible formats
|
||||
// of filename strings we're going to be fed, so for now we just look
|
||||
// for
|
||||
// known model names appearing anywhere in the file name.
|
||||
// for known model names appearing anywhere in the file name.
|
||||
// NOTE: Sequence is important only insofar as any model name must
|
||||
// appear
|
||||
// after all model names of which it is a proper substring.
|
||||
// appear after all model names of which it is a proper substring.
|
||||
// Also, OPC_ENC comes first, since its metafiles may contain other
|
||||
// model substrings
|
||||
OPC_ENS, CMCE_AVGSPR, CMCE, CMCVER, CMC, CPC, DGEX, ECENS_AVGSPR, ECENS, ECMWFVER, ECMWF_HR, ECMWF, ENSVER, FNMOCWAVE, GDAS, GEFS_AVGSPR, GEFS, GFSP, GFSVERP, GFSVER, GFS, GHM, HPCQPF, HPCVER, HWRF, ICEACCR, JMAP, JMA, MEDRT, NAEFS, NAM20, NAM44, NAMVER, NAM, NAVGEM, NOGAPS, NWW3P, NWW3, RAPP, RAP, SREFX, SST, UKMETVER, UKMET, VAFTAD
|
||||
|
||||
// @formatter:off
|
||||
OPC_ENS,
|
||||
CMCE_AVGSPR,
|
||||
CMCE,
|
||||
CMCVER,
|
||||
CMC,
|
||||
CPC,
|
||||
DGEX,
|
||||
ECENS_AVGSPR,
|
||||
ECENS,
|
||||
ECMWFVER,
|
||||
ECMWF_HR,
|
||||
ECMWF,
|
||||
ENSVER,
|
||||
FNMOCWAVE,
|
||||
GDAS,
|
||||
GEFS_AVGSPR,
|
||||
GEFS,
|
||||
GFSP,
|
||||
GFSVERP,
|
||||
GFSVER,
|
||||
GFS,
|
||||
GHM,
|
||||
HPCQPF,
|
||||
HPCVER,
|
||||
HWRF,
|
||||
ICEACCR,
|
||||
JMAP,
|
||||
JMA,
|
||||
MEDRT,
|
||||
NAEFS,
|
||||
NAM20,
|
||||
NAM44,
|
||||
NAMVER,
|
||||
NAM,
|
||||
NAVGEM,
|
||||
NOGAPS,
|
||||
NWW3P,
|
||||
NWW3,
|
||||
RAPP,
|
||||
RAP,
|
||||
SREFX,
|
||||
SST,
|
||||
UKMETVER,
|
||||
UKMET,
|
||||
VAFTAD
|
||||
// @formatter:on
|
||||
};
|
||||
|
||||
private String inferModel(String fileName) {
|
||||
|
|
Loading…
Add table
Reference in a new issue