diff --git a/edexOsgi/com.raytheon.edex.plugin.sfcobs/META-INF/MANIFEST.MF b/edexOsgi/com.raytheon.edex.plugin.sfcobs/META-INF/MANIFEST.MF
index 1ceed53715..f7f8d9515a 100644
--- a/edexOsgi/com.raytheon.edex.plugin.sfcobs/META-INF/MANIFEST.MF
+++ b/edexOsgi/com.raytheon.edex.plugin.sfcobs/META-INF/MANIFEST.MF
@@ -11,7 +11,8 @@ Require-Bundle: com.raytheon.edex.common,
org.geotools,
javax.measure,
javax.persistence
-Export-Package: com.raytheon.edex.plugin.sfcobs.common
+Export-Package: com.raytheon.edex.plugin.sfcobs,
+ com.raytheon.edex.plugin.sfcobs.common
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: com.raytheon.uf.common.dataplugin.sfcobs,
com.raytheon.uf.common.pointdata,
diff --git a/edexOsgi/com.raytheon.edex.plugin.sfcobs/res/pointdata/sfcobs.xml b/edexOsgi/com.raytheon.edex.plugin.sfcobs/res/pointdata/sfcobs.xml
index 88154dbaa2..b386bb7d49 100644
--- a/edexOsgi/com.raytheon.edex.plugin.sfcobs/res/pointdata/sfcobs.xml
+++ b/edexOsgi/com.raytheon.edex.plugin.sfcobs/res/pointdata/sfcobs.xml
@@ -27,11 +27,11 @@
-
+
-
+
@@ -58,7 +58,7 @@
-
+
@@ -66,10 +66,10 @@
-
+
-
+
diff --git a/edexOsgi/com.raytheon.edex.plugin.sfcobs/src/com/raytheon/edex/plugin/sfcobs/SfcObsPointDataTransform.java b/edexOsgi/com.raytheon.edex.plugin.sfcobs/src/com/raytheon/edex/plugin/sfcobs/SfcObsPointDataTransform.java
index 1dad2a934f..b23225b004 100644
--- a/edexOsgi/com.raytheon.edex.plugin.sfcobs/src/com/raytheon/edex/plugin/sfcobs/SfcObsPointDataTransform.java
+++ b/edexOsgi/com.raytheon.edex.plugin.sfcobs/src/com/raytheon/edex/plugin/sfcobs/SfcObsPointDataTransform.java
@@ -20,14 +20,13 @@
package com.raytheon.edex.plugin.sfcobs;
import java.io.File;
+import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import javax.xml.bind.JAXBException;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -43,7 +42,7 @@ import com.raytheon.uf.common.serialization.SerializationException;
import com.raytheon.uf.edex.decodertools.time.TimeTools;
/**
- * TODO Add Description
+ * Populates point data views from fields in obs common records.
*
*
*
@@ -53,6 +52,7 @@ import com.raytheon.uf.edex.decodertools.time.TimeTools;
* ------------ ---------- ----------- --------------------------
* Oct 1, 2009 jkorman Initial creation
* Feb 15,2011 5705 cjeanbap Added wmoHeader to HDR_PARAMS_LIST.
+ * Apr 04,2014 2906 bclement made getDescription() and static constants public
*
*
*
@@ -66,143 +66,143 @@ public class SfcObsPointDataTransform {
private static Log logger = LogFactory
.getLog(SfcObsPointDataTransform.class);
- private static final int INT_DEFAULT = -9999;
+ public static final int INT_DEFAULT = -9999;
- private static final float FLOAT_DEFAULT = -9999.0f;
+ public static final float FLOAT_DEFAULT = -9999.0f;
// /pluginName/dataTime/reportType/corIndicator/latitude/longitude
// /sfcobs/2009-10-12_12:00:00.0/1001/null/71905/55.28/-77.76
// ** DataUri
- private static final String STATION_ID = "stationId";
+ public static final String STATION_ID = "stationId";
// ** DataUri
- private static final String LATITUDE = "latitude";
+ public static final String LATITUDE = "latitude";
// ** DataUri
- private static final String LONGITUDE = "longitude";
+ public static final String LONGITUDE = "longitude";
- private static final String ELEVATION = "elevation";
+ public static final String ELEVATION = "elevation";
- private static final String TIME_OBS = "timeObs";
+ public static final String TIME_OBS = "timeObs";
- private static final String TIME_NOMINAL = "timeNominal";
+ public static final String TIME_NOMINAL = "timeNominal";
// ** DataUri
- private static final String REPORT_TYPE = "reportType";
+ public static final String REPORT_TYPE = "reportType";
// ** DataUri
- private static final String COR_INDICATOR = "corIndicator";
+ public static final String COR_INDICATOR = "corIndicator";
- private static final String DATA_URI = "dataURI";
+ public static final String DATA_URI = "dataURI";
- private static final String WMO_HEADER = "wmoHeader";
+ public static final String WMO_HEADER = "wmoHeader";
- private static final String RAW_REPORT = "rawReport";
+ public static final String RAW_REPORT = "rawReport";
- private static final String PRESS_CHANGE_3HR = "pressChange3Hour";
+ public static final String PRESS_CHANGE_3HR = "pressChange3Hour";
- private static final String PRESS_CHANGE_CHAR = "pressChangeChar";
+ public static final String PRESS_CHANGE_CHAR = "pressChangeChar";
- private static final String PRECIP1_HOUR = "precip1Hour";
+ public static final String PRECIP1_HOUR = "precip1Hour";
- private static final String PRECIP6_HOUR = "precip6Hour";
+ public static final String PRECIP6_HOUR = "precip6Hour";
- private static final String PRECIP12_HOUR = "precip12Hour";
+ public static final String PRECIP12_HOUR = "precip12Hour";
- private static final String PRECIP18_HOUR = "precip18Hour";
+ public static final String PRECIP18_HOUR = "precip18Hour";
- private static final String PRECIP24_HOUR = "precip24Hour";
+ public static final String PRECIP24_HOUR = "precip24Hour";
- private static final String TEMPERATURE = "temperature";
+ public static final String TEMPERATURE = "temperature";
- private static final String DEWPOINT = "dewpoint";
+ public static final String DEWPOINT = "dewpoint";
- private static final String WIND_DIR = "windDir";
+ public static final String WIND_DIR = "windDir";
- private static final String WIND_GUST = "windGust";
+ public static final String WIND_GUST = "windGust";
- private static final String WIND_SPEED = "windSpeed";
+ public static final String WIND_SPEED = "windSpeed";
- private static final String PEAK_WIND_DIR = "peakWindDir";
+ public static final String PEAK_WIND_DIR = "peakWindDir";
- private static final String PEAK_WIND_SPEED = "peakWindSpeed";
+ public static final String PEAK_WIND_SPEED = "peakWindSpeed";
- private static final String PEAK_WIND_SPEED_TIME = "peakWindSpeedTime";
+ public static final String PEAK_WIND_SPEED_TIME = "peakWindSpeedTime";
- private static final String SEA_LEVEL_PRESS = "seaLevelPress";
+ public static final String SEA_LEVEL_PRESS = "seaLevelPress";
- private static final String ALTIMETER = "altimeter";
+ public static final String ALTIMETER = "altimeter";
- private static final String STATION_PRESS = "stationPress";
+ public static final String STATION_PRESS = "stationPress";
- private static final String VISIBILITY = "visibility";
+ public static final String VISIBILITY = "visibility";
- private static final String PRES_WEATHER = "presWeather";
+ public static final String PRES_WEATHER = "presWeather";
- private static final String WX_PRESENT = "wx_present";
+ public static final String WX_PRESENT = "wx_present";
- private static final String WX_PAST_1 = "wx_past_1";
+ public static final String WX_PAST_1 = "wx_past_1";
- private static final String WX_PAST_2 = "wx_past_2";
+ public static final String WX_PAST_2 = "wx_past_2";
- private static final String CLOUD_AMOUNT_TOT = "totCloudAmount";
+ public static final String CLOUD_AMOUNT_TOT = "totCloudAmount";
- private static final String CLOUD_HGT_LOW = "lowCloudHeight";
+ public static final String CLOUD_HGT_LOW = "lowCloudHeight";
- private static final String CLOUD_AMOUNT_LOW = "lowCloudAmount";
+ public static final String CLOUD_AMOUNT_LOW = "lowCloudAmount";
- private static final String CLOUD_TYPE_LOW = "lowCloudType";
+ public static final String CLOUD_TYPE_LOW = "lowCloudType";
- private static final String CLOUD_TYPE_MID = "midCloudType";
+ public static final String CLOUD_TYPE_MID = "midCloudType";
- private static final String CLOUD_TYPE_HI = "hiCloudType";
+ public static final String CLOUD_TYPE_HI = "hiCloudType";
- private static final String SEA_SFC_TEMP = "seaSurfaceTemp";
+ public static final String SEA_SFC_TEMP = "seaSurfaceTemp";
- private static final String ICE_CODE = "iceCode";
+ public static final String ICE_CODE = "iceCode";
- private static final String WET_BULB = "wetBulb";
+ public static final String WET_BULB = "wetBulb";
- private static final String PLATFORM_DIR = "platformTrueDirection";
+ public static final String PLATFORM_DIR = "platformTrueDirection";
- private static final String PLATFORM_SPD = "platformTrueSpeed";
+ public static final String PLATFORM_SPD = "platformTrueSpeed";
- private static final String WIND_SPD_EQUIV_10M = "equivWindSpeed10m";
+ public static final String WIND_SPD_EQUIV_10M = "equivWindSpeed10m";
- private static final String WIND_SPD_EQUIV_20M = "equivWindSpeed20m";
+ public static final String WIND_SPD_EQUIV_20M = "equivWindSpeed20m";
- private static final String WIND_WV_HGT = "windWaveHeight";
+ public static final String WIND_WV_HGT = "windWaveHeight";
- private static final String WIND_WV_PD = "windWavePeriod";
+ public static final String WIND_WV_PD = "windWavePeriod";
- private static final String WV_HGT = "waveHeight";
+ public static final String WV_HGT = "waveHeight";
- private static final String WV_PD = "wavePeriod";
+ public static final String WV_PD = "wavePeriod";
- private static final String WV_STEEPNESS = "waveSteepness";
+ public static final String WV_STEEPNESS = "waveSteepness";
- private static final String HI_RES_WV_HGT = "highResWaveHeight";
+ public static final String HI_RES_WV_HGT = "highResWaveHeight";
- private static final String PRI_SWELL_WV_DIR = "primarySwellWaveDir";
+ public static final String PRI_SWELL_WV_DIR = "primarySwellWaveDir";
- private static final String PRI_SWELL_WV_PD = "primarySwellWavePeriod";
+ public static final String PRI_SWELL_WV_PD = "primarySwellWavePeriod";
- private static final String PRI_SWELL_WV_HGT = "primarySwellWaveHeight";
+ public static final String PRI_SWELL_WV_HGT = "primarySwellWaveHeight";
- private static final String SEC_SWELL_WV_DIR = "secondarySwellWaveDir";
+ public static final String SEC_SWELL_WV_DIR = "secondarySwellWaveDir";
- private static final String SEC_SWELL_WV_PD = "secondarySwellWavePeriod";
+ public static final String SEC_SWELL_WV_PD = "secondarySwellWavePeriod";
- private static final String SEC_SWELL_WV_HGT = "secondarySwellWaveHeight";
+ public static final String SEC_SWELL_WV_HGT = "secondarySwellWaveHeight";
- private static final String NUM_INTER_WINDS = "numInterWinds";
+ public static final String NUM_INTER_WINDS = "numInterWinds";
- private static final String INTER_WIND_TIME = "interWindTime";
+ public static final String INTER_WIND_TIME = "interWindTime";
- private static final String INTER_WIND_DIR = "interWindDir";
+ public static final String INTER_WIND_DIR = "interWindDir";
- private static final String INTER_WIND_SPD = "interWindSpeed";
+ public static final String INTER_WIND_SPD = "interWindSpeed";
public static final String HDR_PARAMS_LIST;
static {
@@ -489,21 +489,28 @@ public class SfcObsPointDataTransform {
}
/**
+ * Read description file from classpath for specified type
*
* @param type
* @return
- * @throws JAXBException
+ * @throws SerializationException
*/
- private PointDataDescription getDescription(String type)
+ public static PointDataDescription getDescription(String type)
throws SerializationException {
- InputStream is = this.getClass().getResourceAsStream(
- "/res/pointdata/" + type + ".xml");
+ InputStream is = SfcObsPointDataTransform.class
+ .getResourceAsStream("/res/pointdata/" + type + ".xml");
if (is == null) {
throw new RuntimeException("Cannot find descriptor for: " + type);
}
- PointDataDescription d = PointDataDescription.fromStream(is);
-
- return d;
+ try {
+ return PointDataDescription.fromStream(is);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ logger.error(e.getLocalizedMessage(), e);
+ }
+ }
}
public static ObsCommon toSfcObsRecord(PointDataView pdv) {
diff --git a/edexOsgi/com.raytheon.uf.common.nc.bufr/src/com/raytheon/uf/common/nc/bufr/BufrFileSeparator.java b/edexOsgi/com.raytheon.uf.common.nc.bufr/src/com/raytheon/uf/common/nc/bufr/BufrFileSeparator.java
index d08421f5b4..4032e9cdbf 100644
--- a/edexOsgi/com.raytheon.uf.common.nc.bufr/src/com/raytheon/uf/common/nc/bufr/BufrFileSeparator.java
+++ b/edexOsgi/com.raytheon.uf.common.nc.bufr/src/com/raytheon/uf/common/nc/bufr/BufrFileSeparator.java
@@ -84,10 +84,12 @@ public class BufrFileSeparator {
* @throws IOException
*/
public static List separate(File mixedBufrFile) throws IOException {
- final File outputBaseDir = DEFAULT_TMP_DIR;
final String inputFile = mixedBufrFile.getAbsolutePath();
- final File outputDir = getOutputDir(mixedBufrFile.getName(),
- outputBaseDir);
+ final File outputDir = getOutputDir(mixedBufrFile);
+ if (outputDir.exists()) {
+ log.warn("BUFR splitter output directory already exists, is file "
+ + mixedBufrFile + " being processed twice?");
+ }
Options options = new Options() {
@Override
public String getFileSpec() {
@@ -115,18 +117,39 @@ public class BufrFileSeparator {
* Create a temporary output directory based on the input file name
*
* @param inputName
- * @param outputBaseDir
* @return
*/
- private static File getOutputDir(final String inputName,
- final File outputBaseDir) {
- String name = inputName + "-" + System.currentTimeMillis() + "-split";
- File rval = new File(outputBaseDir, name);
- if (rval.exists()) {
- log.warn("BUFR splitter output directory already exists, is file "
- + inputName + " being processed twice?");
- }
+ private static File getOutputDir(final File inputFile) {
+ String absPath = inputFile.getAbsolutePath();
+ /*
+ * include absolute path hash to account for files with the same name in
+ * different directories (ie hourly subdirectories)
+ */
+ String hash = Integer.toHexString(absPath.hashCode());
+ String name = inputFile.getName() + "-" + hash + "-split";
+ File rval = new File(DEFAULT_TMP_DIR, name);
return rval;
}
+ /**
+ * Clean up split files and temp directory
+ *
+ * @param mixedBufrFile
+ */
+ public static void clean(File mixedBufrFile) {
+ File outputDir = getOutputDir(mixedBufrFile);
+ if (!outputDir.exists()) {
+ log.debug("Split output directory removed before clean");
+ return;
+ }
+ for (File f : outputDir.listFiles(BUFR_FILTER)) {
+ if (!f.delete() && f.exists()) {
+ log.error("Unable to clean up temporary BUFR file: " + f);
+ }
+ }
+ if (!outputDir.delete() && outputDir.exists()) {
+ log.error("Unable to clean up temporary BUFR directory: "
+ + outputDir);
+ }
+ }
}
diff --git a/edexOsgi/com.raytheon.uf.common.nc.bufr/src/com/raytheon/uf/common/nc/bufr/BufrParser.java b/edexOsgi/com.raytheon.uf.common.nc.bufr/src/com/raytheon/uf/common/nc/bufr/BufrParser.java
index bb754eab7d..d717ac9031 100644
--- a/edexOsgi/com.raytheon.uf.common.nc.bufr/src/com/raytheon/uf/common/nc/bufr/BufrParser.java
+++ b/edexOsgi/com.raytheon.uf.common.nc.bufr/src/com/raytheon/uf/common/nc/bufr/BufrParser.java
@@ -817,4 +817,14 @@ public class BufrParser {
return rval;
}
+ /**
+ * Get the level of the current structure. 0 indicates that there is no
+ * current structure.
+ *
+ * @return
+ */
+ public int getStructLevel() {
+ return structStack.size();
+ }
+
}
diff --git a/edexOsgi/com.raytheon.uf.common.units/utility/common_static/base/unit/alias/udunits.xml b/edexOsgi/com.raytheon.uf.common.units/utility/common_static/base/unit/alias/udunits.xml
index 18eaf2b4f2..d5956f2334 100644
--- a/edexOsgi/com.raytheon.uf.common.units/utility/common_static/base/unit/alias/udunits.xml
+++ b/edexOsgi/com.raytheon.uf.common.units/utility/common_static/base/unit/alias/udunits.xml
@@ -31,7 +31,7 @@
kg m-2
kg m-2
- Degree
+ Degree true
Minute
Hour
Day
diff --git a/edexOsgi/com.raytheon.uf.edex.cots.feature/feature.xml b/edexOsgi/com.raytheon.uf.edex.cots.feature/feature.xml
index 120bc20d5e..623428a747 100644
--- a/edexOsgi/com.raytheon.uf.edex.cots.feature/feature.xml
+++ b/edexOsgi/com.raytheon.uf.edex.cots.feature/feature.xml
@@ -102,4 +102,10 @@
install-size="0"
version="0.0.0"/>
+
+
diff --git a/edexOsgi/com.raytheon.uf.edex.dataplugins.feature/feature.xml b/edexOsgi/com.raytheon.uf.edex.dataplugins.feature/feature.xml
index 5f6129c8a4..424863e684 100644
--- a/edexOsgi/com.raytheon.uf.edex.dataplugins.feature/feature.xml
+++ b/edexOsgi/com.raytheon.uf.edex.dataplugins.feature/feature.xml
@@ -353,4 +353,18 @@
version="0.0.0"
unpack="false"/>
+
+
+
+
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/META-INF/MANIFEST.MF b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/META-INF/MANIFEST.MF
index 1867e585df..de888c8121 100644
--- a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/META-INF/MANIFEST.MF
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/META-INF/MANIFEST.MF
@@ -10,4 +10,18 @@ Require-Bundle: com.raytheon.uf.common.nc.bufr,
ucar.nc2,
ucar.nc2.bufrsplitter,
com.raytheon.uf.common.status,
- org.geotools
+ org.geotools,
+ com.raytheon.uf.common.dataplugin,
+ com.raytheon.uf.common.dataplugin.sfcobs,
+ com.raytheon.uf.common.util,
+ com.raytheon.uf.common.localization,
+ com.raytheon.uf.common.pointdata,
+ javax.measure,
+ com.raytheon.edex.plugin.sfcobs,
+ com.raytheon.uf.common.serialization,
+ com.raytheon.uf.edex.pointdata,
+ com.raytheon.uf.common.datastorage,
+ com.raytheon.uf.common.units,
+ com.raytheon.uf.edex.database,
+ com.raytheon.uf.edex.core,
+ com.raytheon.uf.edex.decodertools
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/build.properties b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/build.properties
index 34d2e4d2da..39000608d1 100644
--- a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/build.properties
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/build.properties
@@ -1,4 +1,8 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
- .
+ .,\
+ res/,\
+ utility/
+src.includes = res/,\
+ utility/
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/res/spring/bufrobs-ingest.xml b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/res/spring/bufrobs-ingest.xml
new file mode 100644
index 0000000000..2a87dae620
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/res/spring/bufrobs-ingest.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sfcobs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ java.lang.Throwable
+
+
+
+
+
+ java.lang.Throwable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/AbstractBufrSfcObsDecoder.java b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/AbstractBufrSfcObsDecoder.java
new file mode 100644
index 0000000000..c84d3fb62a
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/AbstractBufrSfcObsDecoder.java
@@ -0,0 +1,896 @@
+/**
+ * This software was developed and / or modified by Raytheon Company,
+ * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
+ *
+ * U.S. EXPORT CONTROLLED TECHNICAL DATA
+ * This software product contains export-restricted data whose
+ * export/transfer/disclosure is restricted by U.S. law. Dissemination
+ * to non-U.S. persons whether in the United States or abroad requires
+ * an export license or other authorization.
+ *
+ * Contractor Name: Raytheon Company
+ * Contractor Address: 6825 Pine Street, Suite 340
+ * Mail Stop B8
+ * Omaha, NE 68106
+ * 402.291.0100
+ *
+ * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
+ * further licensing information.
+ **/
+package com.raytheon.uf.edex.plugin.bufrobs;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.measure.converter.UnitConverter;
+import javax.measure.unit.SI;
+import javax.measure.unit.Unit;
+import javax.xml.bind.JAXBException;
+
+import ucar.nc2.Attribute;
+import ucar.nc2.NetcdfFile;
+import ucar.nc2.Variable;
+
+import com.raytheon.edex.plugin.sfcobs.SfcObsDao;
+import com.raytheon.edex.plugin.sfcobs.SfcObsPointDataTransform;
+import com.raytheon.uf.common.dataplugin.PluginDataObject;
+import com.raytheon.uf.common.dataplugin.sfcobs.ObsCommon;
+import com.raytheon.uf.common.localization.IPathManager;
+import com.raytheon.uf.common.localization.LocalizationContext;
+import com.raytheon.uf.common.localization.LocalizationFile;
+import com.raytheon.uf.common.localization.PathManagerFactory;
+import com.raytheon.uf.common.nc.bufr.BufrDataItem;
+import com.raytheon.uf.common.nc.bufr.BufrParser;
+import com.raytheon.uf.common.nc.bufr.BufrParser.Event;
+import com.raytheon.uf.common.nc.bufr.tables.MappedValue;
+import com.raytheon.uf.common.nc.bufr.tables.ParsedTableUnit;
+import com.raytheon.uf.common.nc.bufr.tables.TableEntry;
+import com.raytheon.uf.common.nc.bufr.tables.TranslationTable;
+import com.raytheon.uf.common.nc.bufr.tables.TranslationTable.TableType;
+import com.raytheon.uf.common.nc.bufr.tables.TranslationTableManager;
+import com.raytheon.uf.common.nc.bufr.time.BufrTimeFieldParser;
+import com.raytheon.uf.common.nc.bufr.time.TimeFieldParseException;
+import com.raytheon.uf.common.nc.bufr.util.BufrMapper;
+import com.raytheon.uf.common.pointdata.ParameterDescription;
+import com.raytheon.uf.common.pointdata.PointDataContainer;
+import com.raytheon.uf.common.pointdata.PointDataDescription;
+import com.raytheon.uf.common.pointdata.PointDataView;
+import com.raytheon.uf.common.pointdata.spatial.SurfaceObsLocation;
+import com.raytheon.uf.common.status.IUFStatusHandler;
+import com.raytheon.uf.common.status.UFStatus;
+import com.raytheon.uf.common.time.DataTime;
+import com.raytheon.uf.common.units.UnitLookupException;
+import com.raytheon.uf.common.units.UnitMapper;
+import com.raytheon.uf.edex.decodertools.time.TimeTools;
+import com.raytheon.uf.edex.plugin.bufrobs.category.CategoryKey;
+import com.raytheon.uf.edex.plugin.bufrobs.category.CategoryParser;
+import com.raytheon.uf.edex.wmo.message.WMOHeader;
+
+/**
+ * Abstract class for decoding BUFR formatted sfcobs. Contains general methods
+ * that are oblivious to the specific fields that are in the BUFR structure.
+ *
+ *
+ *
+ * SOFTWARE HISTORY
+ *
+ * Date Ticket# Engineer Description
+ * ------------ ---------- ----------- --------------------------
+ * Mar 21, 2014 2906 bclement Initial creation
+ *
+ *
+ *
+ * @author bclement
+ * @version 1.0
+ */
+public abstract class AbstractBufrSfcObsDecoder {
+
+ public static final String localizationAliasDirectory = "bufrobs"
+ + File.separator + "alias";
+
+ public static final String UDUNITS_NAMESPACE = "udunits";
+
+ public static final String WMO_HEADER_ATTRIB = "WMO Header";
+
+ public static final String TIME_FIELD = "time";
+
+ public static final String WMO_BLOCK_FIELD = "WMO block number";
+
+ public static final String WMO_INDEX_FORMAT = "%02d%03d";
+
+ protected static final IUFStatusHandler log = UFStatus
+ .getHandler(AbstractBufrSfcObsDecoder.class);
+
+ private volatile BufrMapper mapper;
+
+ private final Object mapperMutex = new Object();
+
+ private final SfcObsDao dao;
+
+ private final String pluginName;
+
+ private final PointDataDescription description;
+
+ private final Map parameterDescriptionMap;
+
+ public static final String localizationTablesDirectory = "bufrobs"
+ + File.separator + "tables";
+
+ private final Map translationMap = new HashMap();
+
+ private volatile Map reportTypeMap;
+
+ private final Object reportTypeMutex = new Object();
+
+ private final UnitMapper unitMapper = UnitMapper.getInstance();
+
+ /**
+ * @param pluginName
+ * @throws BufrObsDecodeException
+ */
+ public AbstractBufrSfcObsDecoder(String pluginName)
+ throws BufrObsDecodeException {
+ try {
+ this.dao = new SfcObsDao(pluginName);
+ this.pluginName = pluginName;
+ this.description = SfcObsPointDataTransform
+ .getDescription(pluginName);
+ ParameterDescription[] params = description.parameters;
+ this.parameterDescriptionMap = createParamDefMap(params);
+ } catch (Exception e) {
+ throw new BufrObsDecodeException("Unable to initialize decoder", e);
+ }
+ }
+
+ /**
+ * Create mapping from parameter name to description
+ *
+ * @param params
+ * @return
+ */
+ private static Map createParamDefMap(
+ ParameterDescription[] params) {
+ Map map = new HashMap(
+ params.length);
+ for (ParameterDescription desc : params) {
+ map.put(desc.getParameterName(), desc);
+ }
+ return Collections.unmodifiableMap(map);
+ }
+
+ /**
+ * Parse structures from BUFR file and return results in obs common data
+ * object
+ *
+ * @param parser
+ * @param key
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ public PluginDataObject[] decode(BufrParser parser, CategoryKey key)
+ throws BufrObsDecodeException {
+ Map pointMap = new HashMap();
+ Map rtypeMap = getReportTypeMap();
+ Integer reportType = rtypeMap.get(key);
+ WMOHeader header = getWmoHeader(parser);
+ List rval = new ArrayList();
+ boolean hasMore = true;
+ while (hasMore) {
+ try {
+ try {
+ ObsCommon record = getNextRecord(parser, reportType,
+ header, pointMap);
+ if (record != null) {
+ rval.add(record);
+ } else {
+ hasMore = false;
+ }
+ } catch (MissingRequiredDataException e) {
+ /* missing data required for record, no stack trace */
+ log.warn(e.getLocalizedMessage());
+ findEndOfStructure(parser);
+ } catch (BufrObsDecodeException e) {
+ /* problem with individual structure, try to parse more */
+ log.error(e.getLocalizedMessage(), e);
+ findEndOfStructure(parser);
+ }
+ } catch (IOException e) {
+ /* problem with the parser itself, give up on file */
+ throw new BufrObsDecodeException("Problem parsing BUFR file: "
+ + parser.getFile(), e);
+ }
+ }
+ return rval.toArray(new PluginDataObject[rval.size()]);
+ }
+
+ /**
+ * Parse the next obs record or return null if there are no more records
+ *
+ * @param parser
+ * @param reportType
+ * @param header
+ * @param pointMap
+ * @return null if there are no more records
+ * @throws IOException
+ * @throws BufrObsDecodeException
+ */
+ private ObsCommon getNextRecord(BufrParser parser, Integer reportType,
+ WMOHeader header, Map pointMap)
+ throws IOException, BufrObsDecodeException {
+ ObsCommon currentRecord = null;
+ boolean done = false;
+ while (parser.hasNext() && !done) {
+ Event e = parser.next();
+ switch (e) {
+ case START_FILE:
+ log.debug("Started processing BUFR file: " + parser.getFile());
+ break;
+ case START_STRUCTURE:
+ if (parser.getStructLevel() == 1) {
+ /* started a new record */
+ currentRecord = createNewRecord(parser);
+ setTime(currentRecord, parser);
+ setWMOHeaderInfo(currentRecord, header);
+ currentRecord.setReportType(reportType);
+ PointDataContainer pdc = getPointDataContainer(pointMap,
+ currentRecord);
+ currentRecord.setPointDataView(pdc.append());
+ }
+ break;
+ case FIELD:
+ processField(currentRecord, parser);
+ break;
+ case END_STRUCTURE:
+ if (parser.getStructLevel() == 0) {
+ /* ended record */
+ currentRecord = finalizeRecord(parser, currentRecord);
+ done = true;
+ }
+ break;
+ case END_FILE:
+ log.debug("Finished processing BUFR file");
+ break;
+ default:
+ // no action
+ }
+ }
+ return currentRecord;
+ }
+
+ /**
+ * Run the parser until it is no longer in the current structure
+ *
+ * @param parser
+ * @throws IOException
+ */
+ private void findEndOfStructure(BufrParser parser) throws IOException {
+ while (parser.hasNext() && parser.getStructLevel() > 0) {
+ parser.next();
+ }
+ }
+
+ /**
+ * Set record fields that are sourced from WMO header
+ *
+ * @param currentRecord
+ * @param header
+ */
+ protected void setWMOHeaderInfo(ObsCommon currentRecord, WMOHeader header) {
+ currentRecord.setWmoHeader(header.getWmoHeader());
+ // Determine if this is a COR'd observation. Value must be in the
+ // range of "CC[A..Z]"
+ String cor = header.getBBBIndicator();
+ // must have 3 characters.
+ if ((cor != null) && (cor.length() == 2)) {
+ if ("CC".equals(cor.substring(0, 2))) {
+ char c = cor.charAt(2);
+ if ((c >= 'A') && (c <= 'Z')) {
+ currentRecord.setCorIndicator(cor);
+ }
+ }
+ }
+ /* shef converter looks for WMO header in obs text */
+ currentRecord.setObsText(header.getWmoHeader());
+ }
+
+ /**
+ * Get WMO header object from NetCDF file
+ *
+ * @param parser
+ * @throws MissingRequiredDataException
+ */
+ protected WMOHeader getWmoHeader(BufrParser parser)
+ throws MissingRequiredDataException {
+ NetcdfFile ncfile = parser.getNcfile();
+ Attribute attrib = ncfile
+ .findGlobalAttributeIgnoreCase(WMO_HEADER_ATTRIB);
+ if (attrib == null) {
+ throw new MissingRequiredDataException(
+ "Missing WMO Header in BUFR file: " + parser.getFile());
+ }
+ return new WMOHeader(attrib.getStringValue().getBytes());
+ }
+
+ /**
+ * Construct a new record instance
+ *
+ * @param parser
+ *
+ * @return
+ */
+ protected ObsCommon createNewRecord(BufrParser parser) {
+ ObsCommon rval = new ObsCommon();
+ rval.setLocation(new SurfaceObsLocation());
+ return rval;
+ }
+
+ /**
+ * Find point data container for record. Fields used in finding HDF file
+ * (like time) need to be populated in record.
+ *
+ * @param pointMap
+ * @param record
+ * @return
+ */
+ protected PointDataContainer getPointDataContainer(
+ Map pointMap, ObsCommon record) {
+ File f = this.dao.getFullFilePath(record);
+ PointDataContainer pdc = pointMap.get(f);
+ if (pdc == null) {
+ pdc = PointDataContainer.build(this.description);
+ pointMap.put(f, pdc);
+ }
+ return pdc;
+ }
+
+ /**
+ * Process BUFR field and place data in provided record
+ *
+ * @param record
+ * @param parser
+ * @throws BufrObsDecodeException
+ */
+ abstract protected void processField(ObsCommon record, BufrParser parser)
+ throws BufrObsDecodeException;
+
+ /**
+ * @param unitStr
+ * @return true if unit specified a BUFR lookup table
+ */
+ protected boolean isTableLookup(String unitStr) {
+ return ParsedTableUnit.TABLE_UNIT_PATTERN.matcher(unitStr).matches();
+ }
+
+ /**
+ * Perform actions to finalize record after parsing is over
+ *
+ * @param parser
+ *
+ * @param record
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ protected ObsCommon finalizeRecord(BufrParser parser, ObsCommon record)
+ throws BufrObsDecodeException {
+ return record;
+ }
+
+ /**
+ * Lookup translation table for tableId. Results are cached.
+ *
+ * @param tableId
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ protected TranslationTable getTranslationTable(String tableId)
+ throws BufrObsDecodeException {
+ TranslationTable rval;
+ synchronized (translationMap) {
+ rval = translationMap.get(tableId);
+ if (rval == null) {
+ IPathManager pathMgr = PathManagerFactory.getPathManager();
+ LocalizationContext edexStaticBase = pathMgr.getContext(
+ LocalizationContext.LocalizationType.EDEX_STATIC,
+ LocalizationContext.LocalizationLevel.BASE);
+ String fileName = localizationTablesDirectory + File.separator
+ + getTranslationFile(tableId);
+ LocalizationFile tableFile = pathMgr.getLocalizationFile(
+ edexStaticBase, fileName);
+ if (tableFile == null) {
+ throw new BufrObsDecodeException(
+ "Unable to find localization file for BUFR translation table: "
+ + tableId);
+ }
+ try {
+ TranslationTableManager instance = TranslationTableManager
+ .getInstance();
+ rval = (TranslationTable) instance
+ .unmarshalFromInputStream(tableFile
+ .openInputStream());
+ translationMap.put(tableId, rval);
+ } catch (Exception e) {
+ throw new BufrObsDecodeException(
+ "Problem creating BUFR translation table: "
+ + tableId, e);
+ }
+ }
+ }
+ return rval;
+ }
+
+ /**
+ * Get obs-type specific translation table file name
+ *
+ * @param tableId
+ * @return
+ */
+ abstract protected String getTranslationFile(String tableId);
+
+ /**
+ * Get obs-type specific parameter alias file name
+ *
+ * @return
+ */
+ abstract protected String getAliasMapFile();
+
+ /**
+ * Get obs-type specific category file name
+ *
+ * @return
+ */
+ abstract protected String getCategoryFile();
+
+ /**
+ * Get obs-type specific parameter alias mapper
+ *
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ protected BufrMapper getMapper() throws BufrObsDecodeException {
+ if (mapper == null) {
+ synchronized (mapperMutex) {
+ if (mapper == null) {
+ IPathManager pathMgr = PathManagerFactory.getPathManager();
+ File aliasFile = pathMgr
+ .getStaticFile(localizationAliasDirectory
+ + File.separator + getAliasMapFile());
+ try {
+ mapper = new BufrMapper(Arrays.asList(aliasFile));
+ } catch (JAXBException e) {
+ throw new BufrObsDecodeException(
+ "Unable to create alias map", e);
+ }
+ }
+ }
+ }
+ return mapper;
+ }
+
+ /**
+ * Locate time field in parser and populate time fields in record
+ *
+ * @param record
+ * @param parser
+ * @throws BufrObsDecodeException
+ */
+ private void setTime(ObsCommon record, BufrParser parser)
+ throws BufrObsDecodeException {
+ BufrDataItem timeData = parser.scanForStructField(TIME_FIELD, false);
+ if (timeData == null) {
+ throw new MissingRequiredDataException(
+ "Missing time field value in BUFR file: "
+ + parser.getFile());
+ }
+ try {
+ Variable var = timeData.getVariable();
+ Calendar cal = BufrTimeFieldParser.processTimeField(
+ timeData.getValue(), var.getUnitsString());
+ record.setDataTime(new DataTime(cal));
+ record.setTimeObs(cal);
+ record.setRefHour(TimeTools.copyToNearestHour(cal));
+ } catch (TimeFieldParseException e) {
+ throw new BufrObsDecodeException("Problem parsing time field: "
+ + e.getLocalizedMessage() + " in " + parser.getFile(), e);
+ }
+ }
+
+ /**
+ * Handles location-specific fields for obs
+ *
+ * @param location
+ * @param parser
+ * @param baseName
+ * @throws BufrObsDecodeException
+ */
+ protected void processLocationField(SurfaceObsLocation location,
+ BufrParser parser, String baseName) throws BufrObsDecodeException {
+ if (baseName.equalsIgnoreCase(SfcObsPointDataTransform.STATION_ID)) {
+ location.setStationId(createStationId(parser));
+ } else if (baseName.equalsIgnoreCase(SfcObsPointDataTransform.LATITUDE)) {
+ Double lat = (Double) getFieldValue(parser, false);
+ if (lat != null) {
+ location.assignLatitude(lat);
+ }
+ } else if (baseName
+ .equalsIgnoreCase(SfcObsPointDataTransform.LONGITUDE)) {
+ Double lon = (Double) getFieldValue(parser, false);
+ if (lon != null) {
+ location.assignLongitude(lon);
+ }
+ } else if (baseName
+ .equalsIgnoreCase(SfcObsPointDataTransform.ELEVATION)) {
+ Number elevation = getInUnits(parser, SI.METER);
+ if (elevation != null) {
+ location.setElevation(elevation.intValue());
+ }
+ } else {
+ log.warn("Unknown location base name: " + baseName);
+ }
+ }
+
+ /**
+ * Find and format WMO Index to stationId
+ *
+ * @param parser
+ * @return null if no station id found
+ * @throws BufrObsDecodeException
+ */
+ protected String createStationId(BufrParser parser)
+ throws BufrObsDecodeException {
+ /* WMO indexes have two parts, block and id */
+ Number id = (Number) getFieldValue(parser, false);
+ if (id == null) {
+ log.debug("BUFR file " + parser.getFile()
+ + " missing station id field: " + parser.getFieldName());
+ return null;
+ }
+ Number block = getWMOBlock(parser);
+ if (block == null) {
+ log.debug("BUFR file " + parser.getFile()
+ + " missing station id field: " + WMO_BLOCK_FIELD);
+ return null;
+ }
+
+ return String.format(WMO_INDEX_FORMAT, block, id);
+ }
+
+ /**
+ * Find WMO block number in parser
+ *
+ * @param parser
+ * @return null if not found
+ */
+ protected Number getWMOBlock(BufrParser parser) {
+ BufrDataItem data = parser.scanForStructField(WMO_BLOCK_FIELD, false);
+ if (data == null) {
+ return null;
+ }
+ return (Number) data.getValue();
+ }
+
+ /**
+ * {@link BufrParser#getFieldScalarValue(boolean)}
+ *
+ * @param parser
+ * @param charArrayAsString
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ protected Object getFieldValue(BufrParser parser, boolean charArrayAsString)
+ throws BufrObsDecodeException {
+ try {
+ return parser.getFieldScalarValue(charArrayAsString);
+ } catch (IOException e) {
+ throw new BufrObsDecodeException(
+ "Problem getting value for field: " + parser.getFieldName(),
+ e);
+ }
+ }
+
+ /**
+ * Get field and convert to specified units
+ *
+ * @param parser
+ * @param toUnit
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ protected Number getInUnits(BufrParser parser, Unit> toUnit)
+ throws BufrObsDecodeException {
+ Object obj = getFieldValue(parser, false);
+ if (obj == null) {
+ /* missing value */
+ return null;
+ }
+ return getInUnits(parser.getFieldVariable(), obj, toUnit);
+ }
+
+ /**
+ * Convert provided value to specified units
+ *
+ * @param var
+ * @param obj
+ * @param toUnit
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ protected Number getInUnits(Variable var, Object obj, Unit> toUnit)
+ throws BufrObsDecodeException {
+ Number rval;
+ if (obj instanceof Number) {
+ Number value = (Number) obj;
+ Unit> fromUnit = getSourceUnit(var, toUnit);
+ UnitConverter converter = fromUnit.getConverterTo(toUnit);
+ rval = converter.convert(value.doubleValue());
+ } else {
+ log.error("Attempted to convert non-numeric value '" + obj
+ + "' for field: " + var.getFullName());
+ rval = null;
+ }
+ return rval;
+ }
+
+ /**
+ * Get units used in BUFR file
+ *
+ * @param var
+ * @param toUnit
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ protected Unit> getSourceUnit(Variable var, Unit> toUnit)
+ throws BufrObsDecodeException {
+ String fieldUnits = var.getUnitsString();
+ Set> results;
+ try {
+ results = unitMapper.lookupCompatibleUnits(fieldUnits,
+ UDUNITS_NAMESPACE, toUnit);
+ if (results.isEmpty()) {
+ throw new UnitLookupException(
+ "No compatible unit mapping found for " + fieldUnits
+ + " to " + toUnit);
+ }
+ } catch (UnitLookupException e) {
+ throw new BufrObsDecodeException(
+ "Unable to convert units for field " + var.getFullName()
+ + ": " + fieldUnits, e);
+ }
+ return results.iterator().next();
+ }
+
+ /**
+ * Get mapping of category keys to report type integers. Caches result.
+ *
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ public Map getReportTypeMap()
+ throws BufrObsDecodeException {
+ if (reportTypeMap == null) {
+ synchronized (reportTypeMutex) {
+ if (reportTypeMap == null) {
+ reportTypeMap = getReportMapInternal();
+ }
+ }
+ }
+ return reportTypeMap;
+ }
+
+ /**
+ * Create mapping of category keys to report type integers
+ *
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ private Map getReportMapInternal()
+ throws BufrObsDecodeException {
+ try {
+ return CategoryParser.getReportTypeMap(getCategoryFile());
+ } catch (Exception e) {
+ throw new BufrObsDecodeException(e.getLocalizedMessage(), e);
+ }
+ }
+
+ /**
+ * Process fields that are indexes into BUFR code tables.
+ *
+ * @param record
+ * @param parser
+ * @param baseName
+ * @throws BufrObsDecodeException
+ */
+ protected void processLookupTableField(ObsCommon record, BufrParser parser,
+ String baseName) throws BufrObsDecodeException {
+ Map paramMap = getParameterDescriptionMap();
+ ParameterDescription desc = paramMap.get(baseName);
+ if (desc == null) {
+ log.error("Found field without parameter description: " + baseName);
+ return;
+ }
+ Object value = getFieldValue(parser, false);
+ String entryValue = null;
+ if (value != null) {
+ if (!(value instanceof Number)) {
+ log.error("Found non-numeric table value. Field: "
+ + parser.getFieldName() + ", Table: "
+ + parser.getFieldUnits() + ", value: " + value);
+ return;
+ }
+ entryValue = lookupTableEntry(parser, baseName,
+ ((Number) value).intValue());
+ }
+ PointDataView pdv = record.getPointDataView();
+ switch (desc.getType()) {
+ case INT:
+ int ival;
+ if (entryValue != null) {
+ ival = Integer.valueOf(entryValue);
+ } else {
+ ival = SfcObsPointDataTransform.INT_DEFAULT;
+ }
+ pdv.setInt(baseName, ival);
+ break;
+ case STRING:
+ pdv.setString(baseName, entryValue);
+ break;
+ default:
+ log.error("Unsupported type for field: " + baseName);
+ }
+ }
+
+ /**
+ * Find parameter value for index key in translation table for field
+ *
+ * @param parser
+ * @param baseName
+ * @param key
+ * @return null if not found
+ * @throws BufrObsDecodeException
+ */
+ protected String lookupTableEntry(BufrParser parser, String baseName,
+ int key) throws BufrObsDecodeException {
+ ParsedTableUnit tableUnit = ParsedTableUnit.parse(parser
+ .getFieldUnits());
+ TranslationTable transTable = getTranslationTable(tableUnit
+ .getTableId());
+ TableType type = tableUnit.getType();
+ if (!TableType.CODE.equals(type)) {
+ log.warn("Unsupported table type for field " + baseName + ": "
+ + type + ". Only first matched value will be used");
+ }
+ List entries = transTable.lookupEntries(key, type);
+ MappedValue match = null;
+ /* only flag tables return more than one entry */
+ for (TableEntry entry : entries) {
+ /* there are usually less than 3 mapped values in an entry */
+ for (MappedValue value : entry.getMappedValues()) {
+ if (value.getParameter().equalsIgnoreCase(baseName)) {
+ match = value;
+ break;
+ }
+ }
+ }
+ String rval = null;
+ if (match != null) {
+ rval = match.getValue();
+ } else {
+ log.warn("Translation table " + transTable.getBufrTable()
+ + " missing mapped value for field: " + baseName);
+ }
+ return rval;
+ }
+
+ /**
+ * Process fields that have quantitative values with associated units
+ *
+ * @param record
+ * @param parser
+ * @param baseName
+ * @throws BufrObsDecodeException
+ */
+ protected void processDirectField(ObsCommon record, BufrParser parser,
+ String baseName) throws BufrObsDecodeException {
+ Map paramMap = getParameterDescriptionMap();
+ ParameterDescription desc = paramMap.get(baseName);
+ if (desc == null) {
+ log.error("Found field without parameter description: " + baseName);
+ return;
+ }
+ Object value = getParameterValue(desc, parser);
+ PointDataView pdv = record.getPointDataView();
+ switch (desc.getType()) {
+ case FLOAT:
+ float fval;
+ if (value != null && value instanceof Number) {
+ fval = ((Number) value).floatValue();
+ } else {
+ fval = SfcObsPointDataTransform.FLOAT_DEFAULT;
+ }
+ pdv.setFloat(baseName, fval);
+ break;
+ case INT:
+ int ival;
+ if (value != null && value instanceof Number) {
+ ival = ((Number) value).intValue();
+ } else {
+ ival = SfcObsPointDataTransform.INT_DEFAULT;
+ }
+ pdv.setInt(baseName, ival);
+ break;
+ case STRING:
+ pdv.setString(baseName, String.valueOf(value));
+ break;
+ default:
+ log.error("Unsupported type for field: " + baseName);
+ }
+ }
+
+ /**
+ * Get field value given point data view parameter description details (ie
+ * units)
+ *
+ * @param desc
+ * @param parser
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ protected Object getParameterValue(ParameterDescription desc,
+ BufrParser parser) throws BufrObsDecodeException {
+ Unit> toUnit = null;
+ if (desc.getUnit() != null) {
+ toUnit = Unit.valueOf(desc.getUnit());
+ }
+ Object value;
+ if (toUnit != null) {
+ value = getInUnits(parser, toUnit);
+ } else {
+ value = getFieldValue(parser, false);
+ }
+ return value;
+ }
+
+ /**
+ * @return set of category keys that this parser handles
+ * @throws BufrObsDecodeException
+ */
+ public Set getCategoryKeys() throws BufrObsDecodeException {
+ return getReportTypeMap().keySet();
+ }
+
+ /**
+ * @return the dao
+ */
+ public SfcObsDao getDao() {
+ return dao;
+ }
+
+ /**
+ * @return the pluginName
+ */
+ public String getPluginName() {
+ return pluginName;
+ }
+
+ /**
+ * @return the description
+ */
+ public PointDataDescription getDescription() {
+ return description;
+ }
+
+ /**
+ * @return the parameterDescriptionMap
+ */
+ public Map getParameterDescriptionMap() {
+ return parameterDescriptionMap;
+ }
+
+}
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/BufrObsDecodeException.java b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/BufrObsDecodeException.java
new file mode 100644
index 0000000000..0d321a4481
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/BufrObsDecodeException.java
@@ -0,0 +1,70 @@
+/**
+ * This software was developed and / or modified by Raytheon Company,
+ * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
+ *
+ * U.S. EXPORT CONTROLLED TECHNICAL DATA
+ * This software product contains export-restricted data whose
+ * export/transfer/disclosure is restricted by U.S. law. Dissemination
+ * to non-U.S. persons whether in the United States or abroad requires
+ * an export license or other authorization.
+ *
+ * Contractor Name: Raytheon Company
+ * Contractor Address: 6825 Pine Street, Suite 340
+ * Mail Stop B8
+ * Omaha, NE 68106
+ * 402.291.0100
+ *
+ * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
+ * further licensing information.
+ **/
+package com.raytheon.uf.edex.plugin.bufrobs;
+
+/**
+ * Exception thrown when a BUFR obs cannot be decoded
+ *
+ *
+ *
+ * SOFTWARE HISTORY
+ *
+ * Date Ticket# Engineer Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 2, 2014 2906 bclement Initial creation
+ *
+ *
+ *
+ * @author bclement
+ * @version 1.0
+ */
+public class BufrObsDecodeException extends Exception {
+
+ private static final long serialVersionUID = 530575366878300860L;
+
+ /**
+ *
+ */
+ public BufrObsDecodeException() {
+ }
+
+ /**
+ * @param message
+ */
+ public BufrObsDecodeException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param cause
+ */
+ public BufrObsDecodeException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public BufrObsDecodeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/BufrObsProcessor.java b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/BufrObsProcessor.java
new file mode 100644
index 0000000000..9cf61cd242
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/BufrObsProcessor.java
@@ -0,0 +1,170 @@
+/**
+ * This software was developed and / or modified by Raytheon Company,
+ * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
+ *
+ * U.S. EXPORT CONTROLLED TECHNICAL DATA
+ * This software product contains export-restricted data whose
+ * export/transfer/disclosure is restricted by U.S. law. Dissemination
+ * to non-U.S. persons whether in the United States or abroad requires
+ * an export license or other authorization.
+ *
+ * Contractor Name: Raytheon Company
+ * Contractor Address: 6825 Pine Street, Suite 340
+ * Mail Stop B8
+ * Omaha, NE 68106
+ * 402.291.0100
+ *
+ * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
+ * further licensing information.
+ **/
+package com.raytheon.uf.edex.plugin.bufrobs;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import ucar.nc2.Attribute;
+import ucar.nc2.NetcdfFile;
+
+import com.raytheon.uf.common.dataplugin.PluginDataObject;
+import com.raytheon.uf.common.nc.bufr.BufrParser;
+import com.raytheon.uf.common.status.IUFStatusHandler;
+import com.raytheon.uf.common.status.UFStatus;
+import com.raytheon.uf.edex.plugin.bufrobs.category.CategoryKey;
+
+/**
+ * Entry point for BUFR obs decoding. Determines the correct decoder instance
+ * using BUFR attributes.
+ *
+ *
+ *
+ * SOFTWARE HISTORY
+ *
+ * Date Ticket# Engineer Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 1, 2014 2906 bclement Initial creation
+ *
+ *
+ *
+ * @author bclement
+ * @version 1.0
+ */
+public class BufrObsProcessor {
+
+ protected static final IUFStatusHandler log = UFStatus
+ .getHandler(BufrObsProcessor.class);
+
+ public static final String BUFR_CAT_ATTRIBUTE = "BUFR:category";
+
+ public static final String BUFR_SUBCAT_ATTRIBUTE = "BUFR:subCategory";
+
+ private static final List decoders = new ArrayList();
+
+ /**
+ * @param bufrFile
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ public PluginDataObject[] process(File bufrFile)
+ throws BufrObsDecodeException {
+ PluginDataObject[] rval;
+ try {
+ BufrParser parser = new BufrParser(bufrFile);
+ CategoryKey key = getBufrCategory(parser);
+ AbstractBufrSfcObsDecoder decoder = getDecoder(key);
+ if (decoder == null) {
+ throw new BufrObsDecodeException(
+ "Unable to find decoder for file: "
+ + bufrFile.getAbsolutePath() + ". Category: "
+ + key.getCategory() + ", Subcategory: "
+ + key.getSubcategory());
+ } else {
+ rval = decoder.decode(parser, key);
+ }
+ } catch (IOException e) {
+ throw new BufrObsDecodeException("Unable to read BUFR file: "
+ + bufrFile, e);
+ }
+ return rval;
+ }
+
+ /**
+ * Get category key from BUFR file
+ *
+ * @param parser
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ private CategoryKey getBufrCategory(BufrParser parser)
+ throws BufrObsDecodeException {
+ int cat = getIntAttribute(parser, BUFR_CAT_ATTRIBUTE);
+ int subcat = getIntAttribute(parser, BUFR_SUBCAT_ATTRIBUTE);
+ return new CategoryKey(cat, subcat);
+ }
+
+ /**
+ * Get integer global attribute from BUFR file with specified name
+ *
+ * @param parser
+ * @param name
+ * @return
+ * @throws BufrObsDecodeException
+ */
+ private int getIntAttribute(BufrParser parser, String name)
+ throws BufrObsDecodeException {
+ NetcdfFile ncfile = parser.getNcfile();
+ Attribute attrib = ncfile.findGlobalAttributeIgnoreCase(name);
+ if (attrib == null) {
+ throw new BufrObsDecodeException("BUFR file " + parser.getFile()
+ + " missing required attribute: " + name);
+ }
+ Number num = attrib.getNumericValue();
+ if (num == null) {
+ throw new BufrObsDecodeException("BUFR file " + parser.getFile()
+ + " unexpected type for attribute: " + name);
+ }
+ return num.intValue();
+ }
+
+ /**
+ * Registers decoder with processor
+ *
+ * @param decoder
+ * @return
+ */
+ public BufrObsProcessor register(AbstractBufrSfcObsDecoder decoder) {
+ synchronized (decoders) {
+ decoders.add(decoder);
+ }
+ return this;
+ }
+
+ /**
+ * Find the first decoder that supports category key
+ *
+ * @param key
+ * @return
+ */
+ public AbstractBufrSfcObsDecoder getDecoder(CategoryKey key) {
+ AbstractBufrSfcObsDecoder rval = null;
+ synchronized (decoders) {
+ for (AbstractBufrSfcObsDecoder decoder : decoders) {
+ Set categoryKeys;
+ try {
+ categoryKeys = decoder.getCategoryKeys();
+ } catch (BufrObsDecodeException e) {
+ log.error("Problem getting categories from decoder: "
+ + decoder, e);
+ continue;
+ }
+ if (categoryKeys.contains(key)) {
+ rval = decoder;
+ break;
+ }
+ }
+ }
+ return rval;
+ }
+}
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/MissingRequiredDataException.java b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/MissingRequiredDataException.java
new file mode 100644
index 0000000000..2b73de44b4
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/MissingRequiredDataException.java
@@ -0,0 +1,70 @@
+/**
+ * This software was developed and / or modified by Raytheon Company,
+ * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
+ *
+ * U.S. EXPORT CONTROLLED TECHNICAL DATA
+ * This software product contains export-restricted data whose
+ * export/transfer/disclosure is restricted by U.S. law. Dissemination
+ * to non-U.S. persons whether in the United States or abroad requires
+ * an export license or other authorization.
+ *
+ * Contractor Name: Raytheon Company
+ * Contractor Address: 6825 Pine Street, Suite 340
+ * Mail Stop B8
+ * Omaha, NE 68106
+ * 402.291.0100
+ *
+ * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
+ * further licensing information.
+ **/
+package com.raytheon.uf.edex.plugin.bufrobs;
+
+/**
+ * Exception thrown when BUFR obs is missing required data fields
+ *
+ *
+ *
+ * SOFTWARE HISTORY
+ *
+ * Date Ticket# Engineer Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 10, 2014 2906 bclement Initial creation
+ *
+ *
+ *
+ * @author bclement
+ * @version 1.0
+ */
+public class MissingRequiredDataException extends BufrObsDecodeException {
+
+ private static final long serialVersionUID = -7605144038224407338L;
+
+ /**
+ *
+ */
+ public MissingRequiredDataException() {
+ }
+
+ /**
+ * @param message
+ */
+ public MissingRequiredDataException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param cause
+ */
+ public MissingRequiredDataException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public MissingRequiredDataException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/category/CategoryKey.java b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/category/CategoryKey.java
new file mode 100644
index 0000000000..70376551f5
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/category/CategoryKey.java
@@ -0,0 +1,103 @@
+/**
+ * This software was developed and / or modified by Raytheon Company,
+ * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
+ *
+ * U.S. EXPORT CONTROLLED TECHNICAL DATA
+ * This software product contains export-restricted data whose
+ * export/transfer/disclosure is restricted by U.S. law. Dissemination
+ * to non-U.S. persons whether in the United States or abroad requires
+ * an export license or other authorization.
+ *
+ * Contractor Name: Raytheon Company
+ * Contractor Address: 6825 Pine Street, Suite 340
+ * Mail Stop B8
+ * Omaha, NE 68106
+ * 402.291.0100
+ *
+ * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
+ * further licensing information.
+ **/
+package com.raytheon.uf.edex.plugin.bufrobs.category;
+
+/**
+ * Unique key for BUFR categories from Table A
+ *
+ *
+ *
+ * SOFTWARE HISTORY
+ *
+ * Date Ticket# Engineer Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 1, 2014 2906 bclement Initial creation
+ *
+ *
+ *
+ * @author bclement
+ * @version 1.0
+ */
+public class CategoryKey {
+
+ private final int category;
+
+ private final int subcategory;
+
+ /**
+ * @param category
+ * @param subcategory
+ */
+ public CategoryKey(int category, int subcategory) {
+ this.category = category;
+ this.subcategory = subcategory;
+ }
+
+ /**
+ * @return the category
+ */
+ public int getCategory() {
+ return category;
+ }
+
+
+ /**
+ * @return the subcategory
+ */
+ public int getSubcategory() {
+ return subcategory;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + category;
+ result = prime * result + subcategory;
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ CategoryKey other = (CategoryKey) obj;
+ if (category != other.category)
+ return false;
+ if (subcategory != other.subcategory)
+ return false;
+ return true;
+ }
+
+}
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/category/CategoryParser.java b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/category/CategoryParser.java
new file mode 100644
index 0000000000..f5a356380b
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/category/CategoryParser.java
@@ -0,0 +1,154 @@
+/**
+ * This software was developed and / or modified by Raytheon Company,
+ * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
+ *
+ * U.S. EXPORT CONTROLLED TECHNICAL DATA
+ * This software product contains export-restricted data whose
+ * export/transfer/disclosure is restricted by U.S. law. Dissemination
+ * to non-U.S. persons whether in the United States or abroad requires
+ * an export license or other authorization.
+ *
+ * Contractor Name: Raytheon Company
+ * Contractor Address: 6825 Pine Street, Suite 340
+ * Mail Stop B8
+ * Omaha, NE 68106
+ * 402.291.0100
+ *
+ * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
+ * further licensing information.
+ **/
+package com.raytheon.uf.edex.plugin.bufrobs.category;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import com.raytheon.uf.common.localization.IPathManager;
+import com.raytheon.uf.common.localization.LocalizationContext;
+import com.raytheon.uf.common.localization.LocalizationFile;
+import com.raytheon.uf.common.localization.PathManagerFactory;
+import com.raytheon.uf.common.localization.exception.LocalizationException;
+
+/**
+ * Parsing utility for category to report type mapping configuration
+ *
+ *
+ *
+ * SOFTWARE HISTORY
+ *
+ * Date Ticket# Engineer Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 1, 2014 2906 bclement Initial creation
+ *
+ *
+ *
+ * @author bclement
+ * @version 1.0
+ */
+public class CategoryParser {
+
+ private static final XMLInputFactory factory = XMLInputFactory
+ .newInstance();
+
+ public static final String localizationCategoryDirectory = "bufrobs"
+ + File.separator + "category";
+
+ private static final String CAT_TAG = "category";
+
+ private static final String SUBCAT_TAG = "subcategory";
+
+ private static final String CODE_ATTRIB = "code";
+
+ private static final String REPORT_TYPE_ATTRIB = "reportType";
+
+ private CategoryParser() {
+ }
+
+ /**
+ * Read XML configuration mapping category keys to report type integers from
+ * localization
+ *
+ * @param categoryFileName
+ * @return
+ * @throws XMLStreamException
+ * @throws LocalizationException
+ */
+ public static Map getReportTypeMap(
+ String categoryFileName) throws XMLStreamException,
+ LocalizationException {
+ LocalizationFile file = getLocalizedCategoryFile(categoryFileName);
+ if ( file == null){
+ return null;
+ }
+ Map rval = new HashMap();
+ XMLStreamReader reader = factory.createXMLStreamReader(file
+ .openInputStream());
+ try {
+ int currentCategoryCode = -1;
+ while (reader.hasNext()) {
+ switch (reader.next()) {
+ case XMLStreamReader.START_ELEMENT:
+ QName name = reader.getName();
+ if (name.getLocalPart().equalsIgnoreCase(CAT_TAG)) {
+ currentCategoryCode = getIntAttrib(reader, CODE_ATTRIB);
+ } else if (name.getLocalPart().equalsIgnoreCase(SUBCAT_TAG)) {
+ int subCatCode = getIntAttrib(reader, CODE_ATTRIB);
+ int reportType = getIntAttrib(reader,
+ REPORT_TYPE_ATTRIB);
+ rval.put(new CategoryKey(currentCategoryCode,
+ subCatCode), reportType);
+ }
+ }
+ }
+ } finally {
+ reader.close();
+ }
+ return rval;
+ }
+
+ /**
+ * Get integer attribute from STAX parser element
+ *
+ * @param reader
+ * @param name
+ * @return
+ * @throws XMLStreamException
+ */
+ private static int getIntAttrib(XMLStreamReader reader, String name)
+ throws XMLStreamException {
+ String valStr = reader.getAttributeValue(null, name);
+ if (valStr == null) {
+ throw new XMLStreamException("Tag " + reader.getName()
+ + " missing required attribute: " + name);
+ }
+ try {
+ return Integer.valueOf(valStr);
+ } catch (Exception e) {
+ throw new XMLStreamException("Tag " + reader.getName()
+ + " invalid integer value for attribute: " + name);
+ }
+ }
+
+ /**
+ * Get category XML configuration from localization
+ *
+ * @param categoryFileName
+ * @return
+ */
+ public static LocalizationFile getLocalizedCategoryFile(
+ String categoryFileName) {
+ IPathManager pathMgr = PathManagerFactory.getPathManager();
+ LocalizationContext edexStaticBase = pathMgr.getContext(
+ LocalizationContext.LocalizationType.EDEX_STATIC,
+ LocalizationContext.LocalizationLevel.BASE);
+ String fileName = localizationCategoryDirectory + File.separator
+ + categoryFileName;
+ return pathMgr.getLocalizationFile(edexStaticBase, fileName);
+ }
+
+}
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/synoptic/SynopticLandBufrDecoder.java b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/synoptic/SynopticLandBufrDecoder.java
new file mode 100644
index 0000000000..bd21e7d799
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/synoptic/SynopticLandBufrDecoder.java
@@ -0,0 +1,338 @@
+/**
+ * This software was developed and / or modified by Raytheon Company,
+ * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
+ *
+ * U.S. EXPORT CONTROLLED TECHNICAL DATA
+ * This software product contains export-restricted data whose
+ * export/transfer/disclosure is restricted by U.S. law. Dissemination
+ * to non-U.S. persons whether in the United States or abroad requires
+ * an export license or other authorization.
+ *
+ * Contractor Name: Raytheon Company
+ * Contractor Address: 6825 Pine Street, Suite 340
+ * Mail Stop B8
+ * Omaha, NE 68106
+ * 402.291.0100
+ *
+ * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
+ * further licensing information.
+ **/
+package com.raytheon.uf.edex.plugin.bufrobs.synoptic;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.measure.unit.NonSI;
+import javax.measure.unit.SI;
+
+import com.raytheon.edex.plugin.sfcobs.SfcObsPointDataTransform;
+import com.raytheon.uf.common.dataplugin.PluginException;
+import com.raytheon.uf.common.dataplugin.sfcobs.ObsCommon;
+import com.raytheon.uf.common.nc.bufr.BufrDataItem;
+import com.raytheon.uf.common.nc.bufr.BufrParser;
+import com.raytheon.uf.common.nc.bufr.util.BufrMapper;
+import com.raytheon.uf.common.nc.bufr.util.TranslationTableGenerator;
+import com.raytheon.uf.common.pointdata.PointDataView;
+import com.raytheon.uf.common.pointdata.spatial.ObStation;
+import com.raytheon.uf.common.pointdata.spatial.SurfaceObsLocation;
+import com.raytheon.uf.common.serialization.SerializationException;
+import com.raytheon.uf.edex.database.DataAccessLayerException;
+import com.raytheon.uf.edex.decodertools.core.IDecoderConstants;
+import com.raytheon.uf.edex.plugin.bufrobs.AbstractBufrSfcObsDecoder;
+import com.raytheon.uf.edex.plugin.bufrobs.BufrObsDecodeException;
+import com.raytheon.uf.edex.plugin.bufrobs.MissingRequiredDataException;
+import com.raytheon.uf.edex.pointdata.spatial.ObStationDao;
+import com.vividsolutions.jts.geom.Point;
+
+/**
+ * Synoptic Land type decoder for BUFR observations. Handles fixed and mobile
+ * obs.
+ *
+ *
+ *
+ * SOFTWARE HISTORY
+ *
+ * Date Ticket# Engineer Description
+ * ------------ ---------- ----------- --------------------------
+ * Mar 21, 2014 2906 bclement Initial creation
+ *
+ *
+ *
+ * @author bclement
+ * @version 1.0
+ */
+public class SynopticLandBufrDecoder extends AbstractBufrSfcObsDecoder {
+
+ public static final Set LOCATION_FIELDS = new HashSet(
+ Arrays.asList(SfcObsPointDataTransform.LATITUDE,
+ SfcObsPointDataTransform.LONGITUDE,
+ SfcObsPointDataTransform.STATION_ID,
+ SfcObsPointDataTransform.ELEVATION));
+
+ public static final Set SUB_STRUCT_FIELDS = new HashSet(
+ Arrays.asList(SfcObsPointDataTransform.WIND_GUST));
+
+ public static final String SYNOPTIC_LAND_NAMESPACE = "synoptic_land";
+
+ public static final String ALIAS_FILE_NAME = SYNOPTIC_LAND_NAMESPACE
+ + "-alias.xml";
+
+ public static final String CATEGORY_FILE_NAME = SYNOPTIC_LAND_NAMESPACE
+ + "-category.xml";
+
+ public static final String PRECIP_FIELD = "precip";
+
+ public static final String TIME_PERIOD_FIELD = "Time period or displacement";
+
+ private final ObStationDao stationDao = new ObStationDao();
+
+ /**
+ * @param pluginName
+ * @throws BufrObsDecodeException
+ * @throws PluginException
+ * @throws SerializationException
+ */
+ public SynopticLandBufrDecoder(String pluginName)
+ throws BufrObsDecodeException {
+ super(pluginName);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.raytheon.uf.edex.plugin.bufrobs.AbstractBufrObsDecoder#processField
+ * (com.raytheon.uf.common.dataplugin.sfcobs.ObsCommon,
+ * com.raytheon.uf.common.nc.bufr.BufrParser, int)
+ */
+ @Override
+ protected void processField(ObsCommon record, BufrParser parser)
+ throws BufrObsDecodeException {
+ BufrMapper mapper = getMapper();
+ String bufrName = parser.getFieldName();
+ Set baseNames = mapper.lookupBaseNamesOrEmpty(bufrName,
+ SYNOPTIC_LAND_NAMESPACE);
+ if (baseNames.isEmpty()) {
+ log.debug("Skipping unmapped field: " + bufrName);
+ }
+ int level = parser.getStructLevel();
+ for (String baseName : baseNames) {
+ if (level == 1) {
+ /* process top level fields */
+ if (LOCATION_FIELDS.contains(baseName)) {
+ processLocationField(record.getLocation(), parser, baseName);
+ } else {
+ processGeneralFields(record, parser, baseName);
+ }
+ } else if (level > 1) {
+ /* process substructure fields */
+ if (PRECIP_FIELD.equalsIgnoreCase(baseName)) {
+ processPrecip(record, parser);
+ } else if (SUB_STRUCT_FIELDS.contains(baseName)) {
+ processGeneralFields(record, parser, baseName);
+ }
+ }
+ }
+ }
+
+ /**
+ * Determines if field is table lookup or direct and handles accordingly
+ *
+ * @param record
+ * @param parser
+ * @param baseName
+ * @throws BufrObsDecodeException
+ */
+ protected void processGeneralFields(ObsCommon record, BufrParser parser,
+ String baseName) throws BufrObsDecodeException {
+ String unitStr = parser.getFieldUnits();
+ if (unitStr != null && isTableLookup(unitStr)) {
+ processLookupTableField(record, parser, baseName);
+ } else {
+ processDirectField(record, parser, baseName);
+ }
+ }
+
+ /**
+ * Precip doesn't have individual fields for different hour periods. One
+ * structure contains the general precip field with a time period. This
+ * method parses the time period to determine what field the precip data is
+ * for.
+ *
+ * @param record
+ * @param parser
+ * @throws BufrObsDecodeException
+ */
+ protected void processPrecip(ObsCommon record, BufrParser parser)
+ throws BufrObsDecodeException {
+ Number precip = getInUnits(parser, SI.MILLIMETER);
+ if (precip == null) {
+ /* missing value */
+ return;
+ }
+ BufrDataItem timeData = parser.scanForStructField(TIME_PERIOD_FIELD,
+ false);
+ if (timeData == null || timeData.getValue() == null) {
+ log.error("Found precipitation field missing time data. Field: "
+ + parser.getFieldName() + ", Table: "
+ + parser.getFieldUnits());
+ return;
+ }
+ Number period = getInUnits(timeData.getVariable(), timeData.getValue(),
+ NonSI.HOUR);
+ PointDataView pdv = record.getPointDataView();
+ switch (Math.abs(period.intValue())) {
+ case 1:
+ pdv.setFloat(SfcObsPointDataTransform.PRECIP1_HOUR,
+ precip.intValue());
+ break;
+ case 3:
+ /* text synop obs doesn't have 3HR precip */
+ log.debug("Received precip period not supported by point data view: "
+ + Math.abs(period.intValue()) + " HOUR");
+ break;
+ case 6:
+ pdv.setFloat(SfcObsPointDataTransform.PRECIP6_HOUR,
+ precip.intValue());
+ break;
+ case 12:
+ pdv.setFloat(SfcObsPointDataTransform.PRECIP12_HOUR,
+ precip.intValue());
+ break;
+ case 18:
+ pdv.setFloat(SfcObsPointDataTransform.PRECIP18_HOUR,
+ precip.intValue());
+ break;
+ case 24:
+ pdv.setFloat(SfcObsPointDataTransform.PRECIP24_HOUR,
+ precip.intValue());
+ break;
+ default:
+ log.error("Unknown precipitation period '" + period.intValue()
+ + "' in BUFR file " + parser.getFile());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.raytheon.uf.edex.plugin.bufrobs.AbstractBufrSfcObsDecoder#finalizeRecord
+ * (com.raytheon.uf.common.nc.bufr.BufrParser,
+ * com.raytheon.uf.common.dataplugin.sfcobs.ObsCommon)
+ */
+ @Override
+ protected ObsCommon finalizeRecord(BufrParser parser, ObsCommon record)
+ throws BufrObsDecodeException {
+ record = super.finalizeRecord(parser, record);
+ finalizePresentWeather(parser, record);
+ finalizeLocation(parser, record);
+ return record;
+ }
+
+ /**
+ * perform any finishing actions on the present weather field
+ *
+ * @param parser
+ * @param record
+ */
+ protected void finalizePresentWeather(BufrParser parser,
+ ObsCommon record) {
+ /* this code comes from the abstract synoptic text decoder */
+ // Fixup the present weather string
+ PointDataView pdv = record.getPointDataView();
+ int pWx = pdv.getInt(SfcObsPointDataTransform.WX_PRESENT);
+ if (pWx != SfcObsPointDataTransform.INT_DEFAULT) {
+ if (pWx == 28 || (pWx > 41 && pWx < 48)) {
+ float t = pdv.getFloat(SfcObsPointDataTransform.TEMPERATURE);
+ if (t != SfcObsPointDataTransform.FLOAT_DEFAULT) {
+ String wx = (t > 273.15) ? "FG" : "FZFG";
+ pdv.setString(SfcObsPointDataTransform.PRES_WEATHER, wx);
+ }
+ }
+ }
+ }
+
+ /**
+ * perform any finishing actions on the location field
+ *
+ * @param parser
+ * @param record
+ * @throws BufrObsDecodeException
+ */
+ protected void finalizeLocation(BufrParser parser, ObsCommon record)
+ throws BufrObsDecodeException {
+ SurfaceObsLocation location = record.getLocation();
+ Point lonlat = location.getLocation();
+ String stationId = location.getStationId();
+ if (lonlat == null && stationId == null) {
+ throw new MissingRequiredDataException(
+ "Record missing location information in BUFR file: "
+ + parser.getFile());
+ } else if (lonlat == null) {
+ log.debug("Getting station info from database for BUFR file: "
+ + parser.getFile());
+ try {
+ Integer type = ObStation.CAT_TYPE_SFC_FXD;
+ if (record.getReportType() == IDecoderConstants.SYNOPTIC_MOBILE_LAND) {
+ type = ObStation.CAT_TYPE_SFC_MOB;
+ }
+ String gid = ObStation.createGID(type, stationId);
+ ObStation station = stationDao.queryByGid(gid);
+ if (station == null) {
+ throw new MissingRequiredDataException(
+ "Record missing location information in BUFR file: "
+ + parser.getFile());
+ }
+ location.setElevation(station.getElevation());
+ location.setLocation(station.getLocation());
+ } catch (DataAccessLayerException e) {
+ throw new BufrObsDecodeException(
+ "Problem querying the database for location", e);
+ }
+ } else if (stationId == null) {
+ log.debug("Generating station id from location for BUFR file: "
+ + parser.getFile());
+ location.generateCoordinateStationId();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.raytheon.uf.edex.plugin.bufrobs.AbstractBufrObsDecoder#getAliasMapFile
+ * ()
+ */
+ @Override
+ protected String getAliasMapFile() {
+ return ALIAS_FILE_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.raytheon.uf.edex.plugin.bufrobs.AbstractBufrObsDecoder#getTranslationFile
+ * (java.lang.String)
+ */
+ @Override
+ protected String getTranslationFile(String tableId) {
+ tableId = TranslationTableGenerator.replaceWhiteSpace(tableId, "_");
+ return SYNOPTIC_LAND_NAMESPACE + "-" + tableId + ".xml";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.raytheon.uf.edex.plugin.bufrobs.AbstractBufrObsDecoder#getCategoryFile
+ * ()
+ */
+ @Override
+ protected String getCategoryFile() {
+ return CATEGORY_FILE_NAME;
+ }
+
+}
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/util/RecordPopulator.java b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/util/RecordPopulator.java
new file mode 100644
index 0000000000..adb3b19c60
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/src/com/raytheon/uf/edex/plugin/bufrobs/util/RecordPopulator.java
@@ -0,0 +1,170 @@
+/**
+ * This software was developed and / or modified by Raytheon Company,
+ * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
+ *
+ * U.S. EXPORT CONTROLLED TECHNICAL DATA
+ * This software product contains export-restricted data whose
+ * export/transfer/disclosure is restricted by U.S. law. Dissemination
+ * to non-U.S. persons whether in the United States or abroad requires
+ * an export license or other authorization.
+ *
+ * Contractor Name: Raytheon Company
+ * Contractor Address: 6825 Pine Street, Suite 340
+ * Mail Stop B8
+ * Omaha, NE 68106
+ * 402.291.0100
+ *
+ * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
+ * further licensing information.
+ **/
+package com.raytheon.uf.edex.plugin.bufrobs.util;
+
+import com.raytheon.edex.plugin.sfcobs.SfcObsPointDataTransform;
+import com.raytheon.uf.common.dataplugin.PluginDataObject;
+import com.raytheon.uf.common.dataplugin.sfcobs.ObsCommon;
+import com.raytheon.uf.common.pointdata.PointDataView;
+
+/**
+ * Utility to populate an obs record using the point data view. This is
+ * necessary because the shef converter looks for data in the record instead of
+ * the point data view.
+ *
+ *
+ *
+ * SOFTWARE HISTORY
+ *
+ * Date Ticket# Engineer Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 4, 2014 2906 bclement Initial creation
+ *
+ *
+ *
+ * @author bclement
+ * @version 1.0
+ */
+public class RecordPopulator {
+
+ SfcObsPointDataTransform transform = new SfcObsPointDataTransform();
+
+ /**
+ *
+ */
+ public RecordPopulator() {
+ }
+
+ /**
+ * @param records
+ * @return
+ */
+ public PluginDataObject[] populate(PluginDataObject[] records) {
+ for (PluginDataObject pdo : records) {
+ if (pdo instanceof ObsCommon) {
+ populateObs((ObsCommon) pdo);
+ }
+ }
+ return records;
+ }
+
+ /**
+ * Populate record fields using the point data view
+ *
+ * @param obs
+ */
+ public static void populateObs(ObsCommon obs) {
+ PointDataView pdv = obs.getPointDataView();
+ obs.setTemp(pdv.getNumber(SfcObsPointDataTransform.TEMPERATURE)
+ .doubleValue());
+ obs.setDwpt(pdv.getNumber(SfcObsPointDataTransform.DEWPOINT)
+ .doubleValue());
+ obs.setWindSpeed(pdv.getNumber(SfcObsPointDataTransform.WIND_SPEED)
+ .doubleValue());
+ obs.setWindDirection(pdv.getNumber(SfcObsPointDataTransform.WIND_DIR)
+ .intValue());
+ obs.setWindGust(pdv.getNumber(SfcObsPointDataTransform.WIND_GUST)
+ .doubleValue());
+ obs.setPeakWindSpeed(pdv.getNumber(
+ SfcObsPointDataTransform.PEAK_WIND_SPEED).doubleValue());
+ obs.setPeakWindTime(pdv.getNumber(
+ SfcObsPointDataTransform.PEAK_WIND_SPEED_TIME).longValue());
+ obs.setPeakWindDir(pdv
+ .getNumber(SfcObsPointDataTransform.PEAK_WIND_DIR).intValue());
+
+ obs.setPressureSealevel(pdv.getNumber(
+ SfcObsPointDataTransform.SEA_LEVEL_PRESS).intValue());
+ obs.setPressureAltimeter(pdv.getNumber(
+ SfcObsPointDataTransform.ALTIMETER).intValue());
+ obs.setPressureStation(pdv.getNumber(
+ SfcObsPointDataTransform.STATION_PRESS).intValue());
+ obs.setPressChange3Hr(pdv.getNumber(
+ SfcObsPointDataTransform.PRESS_CHANGE_3HR).doubleValue());
+ obs.setPressChangeChar(pdv.getNumber(
+ SfcObsPointDataTransform.PRESS_CHANGE_CHAR).intValue());
+
+ obs.setHorzVisibility(pdv
+ .getNumber(SfcObsPointDataTransform.VISIBILITY).intValue());
+ obs.setWx_present(pdv.getNumber(SfcObsPointDataTransform.WX_PRESENT)
+ .intValue());
+ obs.setPresWeather(pdv.getString(SfcObsPointDataTransform.PRES_WEATHER));
+ obs.setWx_past_1(pdv.getNumber(SfcObsPointDataTransform.WX_PAST_1)
+ .intValue());
+ obs.setWx_past_2(pdv.getNumber(SfcObsPointDataTransform.WX_PAST_2)
+ .intValue());
+
+ obs.setTotalCloudCover(pdv.getNumber(
+ SfcObsPointDataTransform.CLOUD_AMOUNT_TOT).intValue());
+ obs.setCloudBaseHeight(pdv.getNumber(
+ SfcObsPointDataTransform.CLOUD_HGT_LOW).intValue());
+ obs.setLowCloudType(pdv.getNumber(
+ SfcObsPointDataTransform.CLOUD_TYPE_LOW).intValue());
+ obs.setMidCloudType(pdv.getNumber(
+ SfcObsPointDataTransform.CLOUD_TYPE_MID).intValue());
+ obs.setHighCloudType(pdv.getNumber(
+ SfcObsPointDataTransform.CLOUD_TYPE_HI).intValue());
+
+ obs.setWind10mSpeed(pdv.getNumber(
+ SfcObsPointDataTransform.WIND_SPD_EQUIV_10M).doubleValue());
+ obs.setWind20mSpeed(pdv.getNumber(
+ SfcObsPointDataTransform.WIND_SPD_EQUIV_20M).doubleValue());
+
+ // pdv.setInt(ICE_CODE,record.getIceCode);
+ obs.setSeaTemp(pdv.getNumber(SfcObsPointDataTransform.SEA_SFC_TEMP)
+ .doubleValue());
+ obs.setWetBulb(pdv.getNumber(SfcObsPointDataTransform.WET_BULB)
+ .doubleValue());
+
+ obs.setPlatformDirection(pdv.getNumber(
+ SfcObsPointDataTransform.PLATFORM_DIR).intValue());
+ obs.setPlatformMovement(pdv.getNumber(
+ SfcObsPointDataTransform.PLATFORM_SPD).doubleValue());
+
+ obs.setWindWaveHeight(pdv.getNumber(
+ SfcObsPointDataTransform.WIND_WV_HGT).doubleValue());
+ obs.setWindWavePeriod(pdv
+ .getNumber(SfcObsPointDataTransform.WIND_WV_PD).intValue());
+
+ obs.setWaveHeight(pdv.getNumber(SfcObsPointDataTransform.WV_HGT)
+ .doubleValue());
+ obs.setWavePeriod(pdv.getNumber(SfcObsPointDataTransform.WV_PD)
+ .intValue());
+ obs.setWaveSteepness(pdv.getNumber(
+ SfcObsPointDataTransform.WV_STEEPNESS).doubleValue());
+
+ obs.setHighResWaveHeight(pdv.getNumber(
+ SfcObsPointDataTransform.HI_RES_WV_HGT).doubleValue());
+
+ obs.setPrimarySwellWaveDir(pdv.getNumber(
+ SfcObsPointDataTransform.PRI_SWELL_WV_DIR).doubleValue());
+ obs.setPrimarySwellWavePeriod(pdv.getNumber(
+ SfcObsPointDataTransform.PRI_SWELL_WV_PD).intValue());
+ obs.setPrimarySwellWaveHeight(pdv.getNumber(
+ SfcObsPointDataTransform.PRI_SWELL_WV_HGT).doubleValue());
+
+ obs.setSecondarySwellWaveDir(pdv.getNumber(
+ SfcObsPointDataTransform.SEC_SWELL_WV_DIR).doubleValue());
+ obs.setSecondarySwellWavePeriod(pdv.getNumber(
+ SfcObsPointDataTransform.SEC_SWELL_WV_PD).intValue());
+ obs.setSecondarySwellWaveHeight(pdv.getNumber(
+ SfcObsPointDataTransform.SEC_SWELL_WV_HGT).doubleValue());
+ }
+
+}
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/utility/edex_static/base/bufrobs/alias/synoptic_land-alias.xml b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/utility/edex_static/base/bufrobs/alias/synoptic_land-alias.xml
new file mode 100644
index 0000000000..63c26267d2
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/utility/edex_static/base/bufrobs/alias/synoptic_land-alias.xml
@@ -0,0 +1,92 @@
+
+
+
+
+ WMO station number
+ Latitude (high accuracy)
+ Longitude (high accuracy)
+ Height of station ground above mean sea level
+ Temperature/dry-bulb temperature
+ Dew-point temperature
+ Wind speed
+ Wind direction
+ Maximum wind gust speed
+
+ Pressure reduced to mean sea level
+
+ Pressure
+
+ Characteristic of pressure tendency
+ 3-hour pressure change
+
+ Cloud amount
+ Height of base of cloud
+
+
+ Cloud type
+
+ Cloud type-1
+
+ Cloud type-2
+ Horizontal visibility
+
+ Present weather
+
+ Present weather
+
+
+ Total precipitation/total water equivalent
+ Total precipitation past 24 hours
+
+
\ No newline at end of file
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/utility/edex_static/base/bufrobs/category/synoptic_land-category.xml b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/utility/edex_static/base/bufrobs/category/synoptic_land-category.xml
new file mode 100644
index 0000000000..7c71cfce78
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/utility/edex_static/base/bufrobs/category/synoptic_land-category.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/utility/edex_static/base/distribution/bufrobs.xml b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/utility/edex_static/base/distribution/bufrobs.xml
new file mode 100644
index 0000000000..1ae3cd8709
--- /dev/null
+++ b/edexOsgi/com.raytheon.uf.edex.plugin.bufrobs/utility/edex_static/base/distribution/bufrobs.xml
@@ -0,0 +1,23 @@
+
+
+
+ ^IS[IMN]
+